mgmt/osdp: Rework secure channel key management

OSDP command KEYSET is used to set the secure channel base key for all
connected PDs. This key is then used to derive the session keys for each
secure channel session. When the app wants to set the this key, it has
to issue a command and then both the CP/PD has to be notified of this
change so they can store this key in a non-volatile medium for future
operations across power cycles.

The current implementation of OSDP had limited support for key
management. This patch adds all the bells and whistles needed to handle
keyset command/event in the CP/PD application.

Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
This commit is contained in:
Siddharth Chandrasekaran 2022-06-07 20:37:26 +02:00 committed by Carles Cufí
parent 431cac80f4
commit 1648e86f4b
4 changed files with 105 additions and 44 deletions

View file

@ -15,6 +15,7 @@
#define OSDP_PACKET_BUF_SIZE CONFIG_OSDP_UART_BUFFER_LENGTH
#define OSDP_PD_SC_TIMEOUT_MS (800)
#define OSDP_ONLINE_RETRY_WAIT_MAX_MS (300 * 1000)
#define OSDP_PD_MAX CONFIG_OSDP_NUM_CONNECTED_PD
#define OSDP_QUEUE_SLAB_SIZE \
(sizeof(union osdp_ephemeral_data) * CONFIG_OSDP_PD_COMMAND_QUEUE_SIZE)
@ -123,6 +124,7 @@
#define PD_FLAG_SC_SCBKD_DONE 0x00000200 /* SCBKD check is done */
#define PD_FLAG_PKT_HAS_MARK 0x00000400 /* Packet has mark byte */
#define PD_FLAG_PKT_SKIP_MARK 0x00000800 /* CONFIG_OSDP_SKIP_MARK_BYTE */
#define PD_FLAG_HAS_SCBK 0x00001000 /* PD has a dedicated SCBK */
#define PD_FLAG_INSTALL_MODE 0x40000000 /* PD is in install mode */
#define PD_FLAG_PD_MODE 0x80000000 /* device is setup as PD */
@ -499,6 +501,8 @@ struct osdp {
cp_event_callback_t event_callback;
};
void osdp_keyset_complete(struct osdp_pd *pd);
/* from osdp_phy.c */
int osdp_phy_packet_init(struct osdp_pd *p, uint8_t *buf, int max_len);
int osdp_phy_packet_finalize(struct osdp_pd *p, uint8_t *buf,
@ -531,7 +535,7 @@ void osdp_decrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len);
#endif
/* from osdp_sc.c */
void osdp_compute_scbk(struct osdp_pd *pd, uint8_t *scbk);
void osdp_compute_scbk(struct osdp_pd *pd, uint8_t *master_key, uint8_t *scbk);
void osdp_compute_session_keys(struct osdp_pd *pd);
void osdp_compute_cp_cryptogram(struct osdp_pd *pd);
int osdp_verify_cp_cryptogram(struct osdp_pd *pd);
@ -542,7 +546,7 @@ int osdp_decrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int len);
int osdp_encrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int len);
int osdp_compute_mac(struct osdp_pd *pd, int is_cmd,
const uint8_t *data, int len);
void osdp_sc_init(struct osdp_pd *pd);
void osdp_sc_setup(struct osdp_pd *pd);
void osdp_fill_random(uint8_t *buf, int len);
/* must be implemented by CP or PD */

View file

@ -241,10 +241,22 @@ static int cp_build_command(struct osdp_pd *pd, uint8_t *buf, int max_len)
return OSDP_CP_ERR_GENERIC;
}
assert_buf_len(CMD_KEYSET_LEN, max_len);
cmd = (struct osdp_cmd *)pd->ephemeral_data;
if (cmd->keyset.length != 16) {
LOG_ERR("Invalid key length");
return OSDP_CP_ERR_GENERIC;
}
buf[len++] = pd->cmd_id;
buf[len++] = 1; /* key type (1: SCBK) */
buf[len++] = 16; /* key length in bytes */
osdp_compute_scbk(pd, buf + len);
if (cmd->keyset.type == 1) { /* SCBK */
memcpy(buf + len, cmd->keyset.data, 16);
} else if (cmd->keyset.type == 0) { /* master_key */
osdp_compute_scbk(pd, cmd->keyset.data, buf + len);
} else {
LOG_ERR("Unknown key type (%d)", cmd->keyset.type);
return OSDP_CP_ERR_GENERIC;
}
len += 16;
break;
case CMD_CHLNG:
@ -253,7 +265,6 @@ static int cp_build_command(struct osdp_pd *pd, uint8_t *buf, int max_len)
LOG_ERR("Invalid secure message block!");
return -1;
}
osdp_fill_random(pd->sc.cp_random, 8);
smb[0] = 3; /* length */
smb[1] = SCS_11; /* type */
smb[2] = ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD) ? 0 : 1;
@ -757,6 +768,11 @@ static int cp_cmd_dispatcher(struct osdp_pd *pd, int cmd)
}
c->id = cmd;
if (c->id == CMD_KEYSET) {
memcpy(&c->keyset, pd->ephemeral_data, sizeof(c->keyset));
}
cp_cmd_enqueue(pd, c);
SET_FLAG(pd, PD_FLAG_AWAIT_RESP);
return OSDP_CP_ERR_INPROG;
@ -764,8 +780,12 @@ static int cp_cmd_dispatcher(struct osdp_pd *pd, int cmd)
static int state_update(struct osdp_pd *pd)
{
int phy_state;
bool soft_fail;
int phy_state;
#ifdef CONFIG_OSDP_SC_ENABLED
struct osdp *ctx = pd_to_osdp(pd);
struct osdp_cmd_keyset *keyset;
#endif
phy_state = cp_phy_state_update(pd);
if (phy_state == OSDP_CP_ERR_INPROG ||
@ -847,7 +867,7 @@ static int state_update(struct osdp_pd *pd)
break;
#ifdef CONFIG_OSDP_SC_ENABLED
case OSDP_CP_STATE_SC_INIT:
osdp_sc_init(pd);
osdp_sc_setup(pd);
cp_set_state(pd, OSDP_CP_STATE_SC_CHLNG);
__fallthrough;
case OSDP_CP_STATE_SC_CHLNG:
@ -898,6 +918,17 @@ static int state_update(struct osdp_pd *pd)
cp_set_online(pd);
break;
case OSDP_CP_STATE_SET_SCBK:
if (!ISSET_FLAG(pd, PD_FLAG_AWAIT_RESP)) {
keyset = (struct osdp_cmd_keyset *)pd->ephemeral_data;
if (ISSET_FLAG(pd, PD_FLAG_HAS_SCBK)) {
memcpy(keyset->data, pd->sc.scbk, 16);
keyset->type = 1;
} else {
keyset->type = 0;
memcpy(keyset->data, ctx->sc_master_key, 16);
}
keyset->length = 16;
}
if (cp_cmd_dispatcher(pd, CMD_KEYSET) != 0) {
break;
}
@ -906,10 +937,7 @@ static int state_update(struct osdp_pd *pd)
cp_set_online(pd);
break;
}
LOG_INF("SCBK set; restarting SC to verify new SCBK");
CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
cp_set_state(pd, OSDP_CP_STATE_SC_INIT);
osdp_keyset_complete(pd);
pd->seq_number = -1;
break;
#endif /* CONFIG_OSDP_SC_ENABLED */
@ -921,31 +949,57 @@ static int state_update(struct osdp_pd *pd)
}
#ifdef CONFIG_OSDP_SC_ENABLED
static int osdp_cp_send_command_keyset(struct osdp_cmd_keyset *cmd)
static int osdp_cp_send_command_keyset(struct osdp_cmd_keyset *p)
{
int i;
struct osdp_cmd *p;
int i, res = 0;
struct osdp_cmd *cmd[OSDP_PD_MAX] = { 0 };
struct osdp_pd *pd;
struct osdp *ctx = osdp_get_ctx();
if (osdp_get_sc_status_mask() != PD_MASK(ctx)) {
LOG_WRN("CMD_KEYSET can be sent only when all PDs are "
"ONLINE and SC_ACTIVE.");
return 1;
for (i = 0; i < NUM_PD(ctx); i++) {
pd = osdp_to_pd(ctx, i);
if (!ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
LOG_WRN("master_key based key set can be performed only"
" when all PDs are ONLINE, SC_ACTIVE");
return -1;
}
}
LOG_INF("master_key based key set is a global command; "
"all connected PDs will be affected.");
for (i = 0; i < NUM_PD(ctx); i++) {
pd = osdp_to_pd(ctx, i);
cmd[i] = cp_cmd_alloc(pd);
if (cmd[i] == NULL) {
res = -1;
break;
}
cmd[i]->id = CMD_KEYSET;
memcpy(&cmd[i]->keyset, p, sizeof(struct osdp_cmd_keyset));
}
for (i = 0; i < NUM_PD(ctx); i++) {
pd = osdp_to_pd(ctx, i);
p = cp_cmd_alloc(pd);
if (p == NULL) {
return -1;
if (res == 0) {
cp_cmd_enqueue(pd, cmd[i]);
} else if (cmd[i]) {
cp_cmd_free(pd, cmd[i]);
}
p->id = CMD_KEYSET;
memcpy(&p->keyset, &cmd, sizeof(struct osdp_cmd_keyset));
cp_cmd_enqueue(pd, p);
}
return 0;
return res;
}
void osdp_keyset_complete(struct osdp_pd *pd)
{
struct osdp_cmd *c = (struct osdp_cmd *)pd->ephemeral_data;
sc_deactivate(pd);
CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
memcpy(pd->sc.scbk, c->keyset.data, 16);
cp_set_state(pd, OSDP_CP_STATE_SC_INIT);
LOG_INF("SCBK set; restarting SC to verify new SCBK");
}
#endif /* CONFIG_OSDP_SC_ENABLED */

View file

@ -508,8 +508,8 @@ static int pd_decode_command(struct osdp_pd *pd, uint8_t *buf, int len)
if (!pd_cmd_cap_ok(pd, NULL)) {
break;
}
osdp_sc_init(pd);
sc_deactivate(pd);
osdp_sc_setup(pd);
memcpy(pd->sc.cp_random, buf + pos, 8);
pd->reply_id = REPLY_CCRYPT;
ret = OSDP_PD_ERR_NONE;
@ -739,6 +739,7 @@ static int pd_build_reply(struct osdp_pd *pd, uint8_t *buf, int max_len)
if (osdp_verify_cp_cryptogram(pd) == 0) {
smb[2] = 1; /* CP auth succeeded */
sc_activate(pd);
pd->sc_tstamp = osdp_millis_now();
if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
LOG_WRN("SC Active with SCBK-D");
} else {

View file

@ -18,31 +18,30 @@ static const uint8_t osdp_scbk_default[16] = {
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
void osdp_compute_scbk(struct osdp_pd *pd, uint8_t *scbk)
void osdp_compute_scbk(struct osdp_pd *pd, uint8_t *master_key, uint8_t *scbk)
{
int i;
struct osdp *ctx = pd_to_osdp(pd);
memcpy(scbk, pd->sc.pd_client_uid, 8);
for (i = 8; i < 16; i++) {
scbk[i] = ~scbk[i - 8];
}
osdp_encrypt(ctx->sc_master_key, NULL, scbk, 16);
osdp_encrypt(master_key, NULL, scbk, 16);
}
void osdp_compute_session_keys(struct osdp_pd *pd)
{
int i;
struct osdp *ctx = pd_to_osdp(pd);
uint8_t scbk[16];
if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
memcpy(pd->sc.scbk, osdp_scbk_default, 16);
memcpy(scbk, osdp_scbk_default, 16);
} else {
/**
* Compute SCBK only in CP mode. PD mode, expect to already have
* the SCBK (sent from application layer).
*/
if (ISSET_FLAG(pd, PD_FLAG_PD_MODE) == 0) {
osdp_compute_scbk(pd, pd->sc.scbk);
if (is_cp_mode(pd) && !ISSET_FLAG(pd, PD_FLAG_HAS_SCBK)) {
osdp_compute_scbk(pd, ctx->sc_master_key, scbk);
} else {
memcpy(scbk, pd->sc.scbk, 16);
}
}
@ -63,9 +62,9 @@ void osdp_compute_session_keys(struct osdp_pd *pd)
pd->sc.s_mac2[i] = pd->sc.cp_random[i - 2];
}
osdp_encrypt(pd->sc.scbk, NULL, pd->sc.s_enc, 16);
osdp_encrypt(pd->sc.scbk, NULL, pd->sc.s_mac1, 16);
osdp_encrypt(pd->sc.scbk, NULL, pd->sc.s_mac2, 16);
osdp_encrypt(scbk, NULL, pd->sc.s_enc, 16);
osdp_encrypt(scbk, NULL, pd->sc.s_mac1, 16);
osdp_encrypt(scbk, NULL, pd->sc.s_mac2, 16);
}
void osdp_compute_cp_cryptogram(struct osdp_pd *pd)
@ -222,16 +221,17 @@ int osdp_compute_mac(struct osdp_pd *pd, int is_cmd,
return 0;
}
void osdp_sc_init(struct osdp_pd *pd)
void osdp_sc_setup(struct osdp_pd *pd)
{
uint8_t key[16];
uint8_t scbk[16];
bool preserve_scbk = is_pd_mode(pd) || ISSET_FLAG(pd, PD_FLAG_HAS_SCBK);
if (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
memcpy(key, pd->sc.scbk, 16);
if (preserve_scbk) {
memcpy(scbk, pd->sc.scbk, 16);
}
memset(&pd->sc, 0, sizeof(struct osdp_secure_channel));
if (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
memcpy(pd->sc.scbk, key, 16);
if (preserve_scbk) {
memcpy(pd->sc.scbk, scbk, 16);
}
if (is_pd_mode(pd)) {
pd->sc.pd_client_uid[0] = BYTE_0(pd->id.vendor_code);
@ -242,5 +242,7 @@ void osdp_sc_init(struct osdp_pd *pd)
pd->sc.pd_client_uid[5] = BYTE_1(pd->id.serial_number);
pd->sc.pd_client_uid[6] = BYTE_2(pd->id.serial_number);
pd->sc.pd_client_uid[7] = BYTE_3(pd->id.serial_number);
} else {
osdp_fill_random(pd->sc.cp_random, 8);
}
}