mgmt/osdp: Add support for Secure Channel

This patch adds Secure Channel capabilities to osdp Control Panel and
Peripheral Device modes.

Signed-off-by: Siddharth Chandrasekaran <siddharth@embedjournal.com>
This commit is contained in:
Siddharth Chandrasekaran 2020-09-11 20:31:43 +05:30 committed by Carles Cufí
parent c1c627ce5a
commit 7f4d2c741b
12 changed files with 1032 additions and 14 deletions

View File

@ -235,6 +235,12 @@ int osdp_cp_send_cmd_comset(int pd, struct osdp_cmd_comset *p);
#endif /* CONFIG_OSDP_MODE_PD */
#ifdef CONFIG_OSDP_SC_ENABLED
uint32_t osdp_get_sc_status_mask(void);
#endif
#ifdef __cplusplus
}
#endif

View File

@ -12,6 +12,9 @@ zephyr_library_sources_ifdef(CONFIG_OSDP_MODE_PD src/osdp_pd.c)
# CP mode specific sources
zephyr_library_sources_ifdef(CONFIG_OSDP_MODE_CP src/osdp_cp.c)
# Secure Channel sources
zephyr_library_sources_ifdef(CONFIG_OSDP_SC_ENABLED src/osdp_sc.c)
# Common sources
zephyr_library_sources_ifdef(CONFIG_OSDP
src/osdp_phy.c

View File

@ -58,7 +58,7 @@ config OSDP_UART_BUFFER_LENGTH
config OSDP_THREAD_STACK_SIZE
int "OSDP Thread stack size"
default 512
default 1024
help
Thread stack size for osdp refresh thread
@ -68,6 +68,36 @@ config OSDP_PACKET_TRACE
Prints bytes sent/received over OSDP to console for debugging.
LOG_HEXDUMP_DBG() is used to achieve this and can be very verbose.
config OSDP_SC_ENABLED
bool "Enable OSDP Secure Channel"
default y
select CRYPTO
select CRYPTO_MBEDTLS_SHIM
select MBEDTLS
select MBEDTLS_CIPHER_CCM_ENABLED
select ENTROPY_GENERATOR
help
Secure the OSDP communication channel with encryption and mutual
authentication.
if OSDP_SC_ENABLED
config OSDP_SC_RETRY_WAIT_SEC
int "Retry wait time in seconds after a Secure Channel error"
default 600
help
Time in seconds to wait after a secure channel failure, and before
retrying to establish it.
config OSDP_CRYPTO_DRV_NAME
string "Crypto driver to use with OSDP"
default "CRYPTO_MTLS"
help
OSDP Secure Channel uses AES-128 to secure communication between
CP and PD. Provide an available crypto driver name here.
endif # OSDP_SC_ENABLED
if OSDP_MODE_PD
source "subsys/mgmt/osdp/Kconfig.pd"
endif

View File

@ -41,3 +41,14 @@ config OSDP_PD_POLL_RATE
The Control Panel must query the Peripheral Device periodically to
maintain connection sequence and to get status and events. This option
defined the number of times such a POLL command is sent per second.
if OSDP_SC_ENABLED
config OSDP_MASTER_KEY
string "Secure Channel Master Key"
default "NONE"
help
Hexadecimal string representation of the the 16 byte OSDP Secure Channel
master Key. This is a mandatory key when secure channel is enabled.
endif # OSDP_SC_ENABLED

View File

@ -31,6 +31,22 @@ config OSDP_PD_ADDRESS
Address 0x7F is reserved as a broadcast address to which all PDs would
respond.
if OSDP_SC_ENABLED
config OSDP_PD_SCBK
string "Secure Channel Base Key (SCBK)"
default "NONE"
help
Hexadecimal string representation of the the 16 byte OSDP PD Secure
Channel Base Key. When this field is sent to "NONE", the PD is set to
"Install Mode". In this mode, the PD would allow a CP to setup a secure
channel with default SCBK. Once as secure channel is active with the
default key, the CP can send a KEYSET command to set new keys to the PD.
It is up to the user to make sure that the PD enters the "Install Mode"
only during provisioning time (controlled environment).
endif # OSDP_SC_ENABLED
menu "Peripheral Device ID Information"
config OSDP_PD_ID_VENDOR_CODE

View File

@ -16,6 +16,12 @@
LOG_MODULE_REGISTER(osdp, CONFIG_OSDP_LOG_LEVEL);
#ifdef CONFIG_OSDP_MODE_PD
#define OSDP_KEY_STRING CONFIG_OSDP_PD_SCBK
#else
#define OSDP_KEY_STRING CONFIG_OSDP_MASTER_KEY
#endif
struct osdp_device {
struct ring_buf rx_buf;
struct ring_buf tx_buf;
@ -152,7 +158,8 @@ void osdp_refresh(void *arg1, void *arg2, void *arg3)
static int osdp_init(const struct device *arg)
{
ARG_UNUSED(arg);
uint8_t c;
int len;
uint8_t c, *key = NULL, key_buf[16];
struct osdp *ctx;
struct osdp_device *p = &osdp_device;
struct osdp_channel channel = {
@ -202,7 +209,24 @@ static int osdp_init(const struct device *arg)
LOG_ERR("OSDP build ctx failed!");
k_panic();
}
if (osdp_setup(ctx)) {
if (IS_ENABLED(CONFIG_OSDP_SC_ENABLED)) {
if (strcmp(OSDP_KEY_STRING, "NONE") != 0) {
len = strlen(OSDP_KEY_STRING);
if (len != 32) {
LOG_ERR("Key string length must be 32");
k_panic();
}
len = hex2bin(OSDP_KEY_STRING, 32, key_buf, 16);
if (len != 16) {
LOG_ERR("Failed to parse key buffer");
k_panic();
}
key = key_buf;
}
}
if (osdp_setup(ctx, key)) {
LOG_ERR("Failed to setup OSDP device!");
k_panic();
}

View File

@ -10,6 +10,11 @@
#include <sys/crc.h>
#include <logging/log.h>
#ifdef CONFIG_OSDP_SC_ENABLED
#include <crypto/cipher.h>
#include <random/rand32.h>
#endif
#include "osdp_common.h"
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
@ -74,3 +79,122 @@ struct osdp_cmd *osdp_cmd_get_last(struct osdp_pd *pd)
{
return (struct osdp_cmd *)sys_slist_peek_tail(&pd->cmd.queue);
}
#ifdef CONFIG_OSDP_SC_ENABLED
void osdp_encrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len)
{
const struct device *dev;
struct cipher_ctx ctx = {
.keylen = 16,
.key.bit_stream = key,
.flags = CAP_NO_IV_PREFIX
};
struct cipher_pkt encrypt = {
.in_buf = data,
.in_len = len,
.out_buf = data,
.out_len = len
};
dev = device_get_binding(CONFIG_OSDP_CRYPTO_DRV_NAME);
if (dev == NULL) {
LOG_ERR("Failed to get crypto dev binding!");
return;
}
if (iv != NULL) {
if (cipher_begin_session(dev, &ctx,
CRYPTO_CIPHER_ALGO_AES,
CRYPTO_CIPHER_MODE_CBC,
CRYPTO_CIPHER_OP_ENCRYPT)) {
LOG_ERR("Failed at cipher_begin_session");
return;
}
if (cipher_cbc_op(&ctx, &encrypt, iv)) {
LOG_ERR("CBC ENCRYPT - Failed");
}
} else {
if (cipher_begin_session(dev, &ctx,
CRYPTO_CIPHER_ALGO_AES,
CRYPTO_CIPHER_MODE_ECB,
CRYPTO_CIPHER_OP_ENCRYPT)) {
LOG_ERR("Failed at cipher_begin_session");
return;
}
if (cipher_block_op(&ctx, &encrypt)) {
LOG_ERR("ECB ENCRYPT - Failed");
}
}
cipher_free_session(dev, &ctx);
}
void osdp_decrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len)
{
const struct device *dev;
struct cipher_ctx ctx = {
.keylen = 16,
.key.bit_stream = key,
.flags = CAP_NO_IV_PREFIX
};
struct cipher_pkt decrypt = {
.in_buf = data,
.in_len = len,
.out_buf = data,
.out_len = len
};
dev = device_get_binding(CONFIG_OSDP_CRYPTO_DRV_NAME);
if (dev == NULL) {
LOG_ERR("Failed to get crypto dev binding!");
return;
}
if (iv != NULL) {
if (cipher_begin_session(dev, &ctx,
CRYPTO_CIPHER_ALGO_AES,
CRYPTO_CIPHER_MODE_CBC,
CRYPTO_CIPHER_OP_DECRYPT)) {
LOG_ERR("Failed at cipher_begin_session");
return;
}
if (cipher_cbc_op(&ctx, &decrypt, iv)) {
LOG_ERR("CBC DECRYPT - Failed");
}
} else {
if (cipher_begin_session(dev, &ctx,
CRYPTO_CIPHER_ALGO_AES,
CRYPTO_CIPHER_MODE_ECB,
CRYPTO_CIPHER_OP_DECRYPT)) {
LOG_ERR("Failed at cipher_begin_session");
return;
}
if (cipher_block_op(&ctx, &decrypt)) {
LOG_ERR("ECB DECRYPT - Failed");
}
}
cipher_free_session(dev, &ctx);
}
void osdp_fill_random(uint8_t *buf, int len)
{
sys_csrand_get(buf, len);
}
uint32_t osdp_get_sc_status_mask(void)
{
int i;
uint32_t mask = 0;
struct osdp_pd *pd;
struct osdp *ctx = osdp_get_ctx();
for (i = 0; i < NUM_PD(ctx); i++) {
pd = TO_PD(ctx, i);
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
mask |= 1 << i;
}
}
return mask;
}
#endif /* CONFIG_OSDP_SC_ENABLED */

View File

@ -37,7 +37,7 @@
TO_CP(p)->pd_offset = i; \
} while (0)
#define PD_MASK(ctx) \
(uint32_t)((1 << (TO_CP(ctx)->num_pd + 1)) - 1)
(uint32_t)((1 << (TO_CP(ctx)->num_pd)) - 1)
#define AES_PAD_LEN(x) ((x + 16 - 1) & (~(16 - 1)))
#define NUM_PD(ctx) (TO_CP(ctx)->num_pd)
#define OSDP_COMMAND_DATA_MAX_LEN sizeof(struct osdp_cmd)
@ -124,6 +124,8 @@
#define PD_FLAG_SKIP_SEQ_CHECK 0x00000040 /* disable seq checks (debug) */
#define PD_FLAG_SC_USE_SCBKD 0x00000080 /* in this SC attempt, use SCBKD */
#define PD_FLAG_SC_ACTIVE 0x00000100 /* secure channel is active */
#define PD_FLAG_SC_SCBKD_DONE 0x00000200 /* SCBKD check is done */
#define PD_FLAG_INSTALL_MODE 0x40000000 /* PD is in install mode */
#define PD_FLAG_PD_MODE 0x80000000 /* device is setup as PD */
enum osdp_pd_nak_code_e {
@ -395,6 +397,22 @@ struct osdp_notifiers {
int (*cardread)(int address, int format, uint8_t *data, int len);
};
#ifdef CONFIG_OSDP_SC_ENABLED
struct osdp_secure_channel {
uint8_t scbk[16];
uint8_t s_enc[16];
uint8_t s_mac1[16];
uint8_t s_mac2[16];
uint8_t r_mac[16];
uint8_t c_mac[16];
uint8_t cp_random[8];
uint8_t pd_random[8];
uint8_t pd_client_uid[8];
uint8_t cp_cryptogram[16];
uint8_t pd_cryptogram[16];
};
#endif
struct osdp_pd {
void *__parent;
int offset;
@ -425,6 +443,10 @@ struct osdp_pd {
struct osdp_channel channel;
struct osdp_cmd_queue cmd;
#ifdef CONFIG_OSDP_SC_ENABLED
int64_t sc_tstamp;
struct osdp_secure_channel sc;
#endif
};
struct osdp_cp {
@ -439,9 +461,11 @@ struct osdp_cp {
struct osdp {
int magic;
uint32_t flags;
struct osdp_cp *cp;
struct osdp_pd *pd;
#ifdef CONFIG_OSDP_SC_ENABLED
uint8_t sc_master_key[16];
#endif
};
/* from osdp_phy.c */
@ -472,8 +496,28 @@ struct osdp *osdp_get_ctx();
int osdp_extract_address(int *address);
#endif
#ifdef CONFIG_OSDP_SC_ENABLED
void osdp_encrypt(uint8_t *key, uint8_t *iv, uint8_t *data, int len);
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_session_keys(struct osdp *ctx);
void osdp_compute_cp_cryptogram(struct osdp_pd *pd);
int osdp_verify_cp_cryptogram(struct osdp_pd *pd);
void osdp_compute_pd_cryptogram(struct osdp_pd *pd);
int osdp_verify_pd_cryptogram(struct osdp_pd *pd);
void osdp_compute_rmac_i(struct osdp_pd *pd);
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_fill_random(uint8_t *buf, int len);
/* must be implemented by CP or PD */
int osdp_setup(struct osdp *ctx);
int osdp_setup(struct osdp *ctx, uint8_t *key);
void osdp_update(struct osdp *ctx);
#endif /* _OSDP_COMMON_H_ */

View File

@ -15,6 +15,10 @@ LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#define OSDP_PD_POLL_TIMEOUT_MS (1000 / CONFIG_OSDP_PD_POLL_RATE)
#define OSDP_CMD_RETRY_WAIT_MS (CONFIG_OSDP_CMD_RETRY_WAIT_SEC * 1000)
#ifdef CONFIG_OSDP_SC_ENABLED
#define OSDP_PD_SC_RETRY_MS (CONFIG_OSDP_SC_RETRY_WAIT_SEC * 1000)
#endif
#define CMD_POLL_LEN 1
#define CMD_LSTAT_LEN 1
#define CMD_ISTAT_LEN 1
@ -28,6 +32,9 @@ LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#define CMD_BUZ_LEN 6
#define CMD_TEXT_LEN 7 /* variable length command */
#define CMD_COMSET_LEN 6
#define CMD_KEYSET_LEN 19
#define CMD_CHLNG_LEN 9
#define CMD_SCRYPT_LEN 17
#define REPLY_ACK_DATA_LEN 0
#define REPLY_PDID_DATA_LEN 12
@ -36,6 +43,8 @@ LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#define REPLY_RSTATR_DATA_LEN 1
#define REPLY_COM_DATA_LEN 5
#define REPLY_NAK_DATA_LEN 1
#define REPLY_CCRYPT_DATA_LEN 32
#define REPLY_RMAC_I_DATA_LEN 16
#define REPLY_KEYPPAD_DATA_LEN 2 /* variable length command */
#define REPLY_RAW_DATA_LEN 4 /* variable length command */
#define REPLY_FMT_DATA_LEN 3 /* variable length command */
@ -80,6 +89,10 @@ static int cp_build_command(struct osdp_pd *pd, uint8_t *buf, int max_len)
data_off = osdp_phy_packet_get_data_offset(pd, buf);
#ifdef CONFIG_OSDP_SC_ENABLED
uint8_t *smb = osdp_phy_packet_get_smb(pd, buf);
#endif
buf += data_off;
max_len -= data_off;
if (max_len <= 0) {
@ -212,11 +225,65 @@ static int cp_build_command(struct osdp_pd *pd, uint8_t *buf, int max_len)
}
ret = 0;
break;
#ifdef CONFIG_OSDP_SC_ENABLED
case CMD_KEYSET:
if (!ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
LOG_ERR(TAG "Cannot perform KEYSET without SC!");
return -1;
}
if (max_len < CMD_KEYSET_LEN) {
break;
}
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);
len += 16;
ret = 0;
break;
case CMD_CHLNG:
if (smb == NULL || max_len < CMD_CHLNG_LEN) {
break;
}
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;
buf[len++] = pd->cmd_id;
for (i = 0; i < 8; i++)
buf[len++] = pd->sc.cp_random[i];
ret = 0;
break;
case CMD_SCRYPT:
if (smb == NULL || max_len < CMD_SCRYPT_LEN) {
break;
}
osdp_compute_cp_cryptogram(pd);
smb[0] = 3; /* length */
smb[1] = SCS_13; /* type */
smb[2] = ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD) ? 0 : 1;
buf[len++] = pd->cmd_id;
for (i = 0; i < 16; i++)
buf[len++] = pd->sc.cp_cryptogram[i];
ret = 0;
break;
#endif /* CONFIG_OSDP_SC_ENABLED */
default:
LOG_ERR(TAG "Unknown/Unsupported command %02x", pd->cmd_id);
return OSDP_CP_ERR_GENERIC;
}
#ifdef CONFIG_OSDP_SC_ENABLED
if (smb && (smb[1] > SCS_14) && ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
/**
* When SC active and current cmd is not a handshake (<= SCS_14)
* then we must set SCS type to 17 if this message has data
* bytes and 15 otherwise.
*/
smb[0] = 2;
smb[1] = (len > 1) ? SCS_17 : SCS_15;
}
#endif /* CONFIG_OSDP_SC_ENABLED */
if (ret < 0) {
LOG_ERR(TAG "Unable to build command %02x", pd->cmd_id);
return OSDP_CP_ERR_GENERIC;
@ -289,10 +356,11 @@ static int cp_decode_response(struct osdp_pd *pd, uint8_t *buf, int len)
}
/* post-capabilities hooks */
t2 = OSDP_PD_CAP_COMMUNICATION_SECURITY;
if (pd->cap[t2].compliance_level & 0x01)
if (pd->cap[t2].compliance_level & 0x01) {
SET_FLAG(pd, PD_FLAG_SC_CAPABLE);
else
} else {
CLEAR_FLAG(pd, PD_FLAG_SC_CAPABLE);
}
ret = 0;
break;
case REPLY_LSTATR:
@ -392,6 +460,38 @@ static int cp_decode_response(struct osdp_pd *pd, uint8_t *buf, int len)
}
ret = OSDP_CP_ERR_RETRY_CMD;
break;
#ifdef CONFIG_OSDP_SC_ENABLED
case REPLY_CCRYPT:
if (len != REPLY_CCRYPT_DATA_LEN) {
break;
}
for (i = 0; i < 8; i++) {
pd->sc.pd_client_uid[i] = buf[pos++];
}
for (i = 0; i < 8; i++) {
pd->sc.pd_random[i] = buf[pos++];
}
for (i = 0; i < 16; i++) {
pd->sc.pd_cryptogram[i] = buf[pos++];
}
osdp_compute_session_keys(TO_CTX(pd));
if (osdp_verify_pd_cryptogram(pd) != 0) {
LOG_ERR(TAG "failed to verify PD_crypt");
return -1;
}
ret = 0;
break;
case REPLY_RMAC_I:
if (len != REPLY_RMAC_I_DATA_LEN) {
break;
}
for (i = 0; i < 16; i++) {
pd->sc.r_mac[i] = buf[pos++];
}
SET_FLAG(pd, PD_FLAG_SC_ACTIVE);
ret = 0;
break;
#endif /* CONFIG_OSDP_SC_ENABLED */
default:
LOG_DBG(TAG "unexpected reply: 0x%02x", pd->reply_id);
return OSDP_CP_ERR_GENERIC;
@ -497,6 +597,7 @@ static void cp_flush_command_queue(struct osdp_pd *pd)
static inline void cp_set_offline(struct osdp_pd *pd)
{
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
pd->state = OSDP_CP_STATE_OFFLINE;
pd->tstamp = osdp_millis_now();
}
@ -505,7 +606,6 @@ static inline void cp_reset_state(struct osdp_pd *pd)
{
pd->state = OSDP_CP_STATE_INIT;
osdp_phy_state_reset(pd);
pd->flags = 0;
}
static inline void cp_set_state(struct osdp_pd *pd, enum osdp_cp_state_e state)
@ -641,6 +741,15 @@ static int state_update(struct osdp_pd *pd)
switch (pd->state) {
case OSDP_CP_STATE_ONLINE:
#ifdef CONFIG_OSDP_SC_ENABLED
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) == false &&
ISSET_FLAG(pd, PD_FLAG_SC_CAPABLE) == true &&
osdp_millis_since(pd->sc_tstamp) > OSDP_PD_SC_RETRY_MS) {
LOG_INF("retry SC after retry timeout");
cp_set_state(pd, OSDP_CP_STATE_SC_INIT);
break;
}
#endif
if (osdp_millis_since(pd->tstamp) < OSDP_PD_POLL_TIMEOUT_MS) {
break;
}
@ -672,8 +781,82 @@ static int state_update(struct osdp_pd *pd)
if (pd->reply_id != REPLY_PDCAP) {
cp_set_offline(pd);
}
#ifdef CONFIG_OSDP_SC_ENABLED
if (ISSET_FLAG(pd, PD_FLAG_SC_CAPABLE)) {
CLEAR_FLAG(pd, PD_FLAG_SC_SCBKD_DONE);
CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
cp_set_state(pd, OSDP_CP_STATE_SC_INIT);
break;
}
#endif /* CONFIG_OSDP_SC_ENABLED */
cp_set_state(pd, OSDP_CP_STATE_ONLINE);
break;
#ifdef CONFIG_OSDP_SC_ENABLED
case OSDP_CP_STATE_SC_INIT:
osdp_sc_init(pd);
cp_set_state(pd, OSDP_CP_STATE_SC_CHLNG);
/* FALLTHRU */
case OSDP_CP_STATE_SC_CHLNG:
if (cp_cmd_dispatcher(pd, CMD_CHLNG) != 0) {
break;
}
if (phy_state < 0) {
if (ISSET_FLAG(pd, PD_FLAG_SC_SCBKD_DONE)) {
LOG_INF(TAG "SC Failed; online without SC");
pd->sc_tstamp = osdp_millis_now();
cp_set_state(pd, OSDP_CP_STATE_ONLINE);
break;
}
SET_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
SET_FLAG(pd, PD_FLAG_SC_SCBKD_DONE);
cp_set_state(pd, OSDP_CP_STATE_SC_INIT);
pd->phy_state = 0; /* soft reset phy state */
LOG_WRN(TAG "SC Failed; retry with SCBK-D");
break;
}
if (pd->reply_id != REPLY_CCRYPT) {
LOG_ERR(TAG "CHLNG failed. Online without SC");
pd->sc_tstamp = osdp_millis_now();
cp_set_state(pd, OSDP_CP_STATE_ONLINE);
break;
}
cp_set_state(pd, OSDP_CP_STATE_SC_SCRYPT);
/* FALLTHRU */
case OSDP_CP_STATE_SC_SCRYPT:
if (cp_cmd_dispatcher(pd, CMD_SCRYPT) != 0) {
break;
}
if (pd->reply_id != REPLY_RMAC_I) {
LOG_ERR(TAG "SCRYPT failed. Online without SC");
pd->sc_tstamp = osdp_millis_now();
cp_set_state(pd, OSDP_CP_STATE_ONLINE);
break;
}
if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
LOG_WRN(TAG "SC ACtive with SCBK-D; Set SCBK");
cp_set_state(pd, OSDP_CP_STATE_SET_SCBK);
break;
}
LOG_INF(TAG "SC Active");
pd->sc_tstamp = osdp_millis_now();
cp_set_state(pd, OSDP_CP_STATE_ONLINE);
break;
case OSDP_CP_STATE_SET_SCBK:
if (cp_cmd_dispatcher(pd, CMD_KEYSET) != 0) {
break;
}
if (pd->reply_id == REPLY_NAK) {
LOG_WRN(TAG "Failed to set SCBK; continue with SCBK-D");
cp_set_state(pd, OSDP_CP_STATE_ONLINE);
break;
}
LOG_INF(TAG "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);
pd->seq_number = -1;
break;
#endif /* CONFIG_OSDP_SC_ENABLED */
default:
break;
}
@ -681,6 +864,35 @@ static int state_update(struct osdp_pd *pd)
return 0;
}
#ifdef CONFIG_OSDP_SC_ENABLED
static int osdp_cp_send_cmd_keyset(struct osdp_cmd_keyset *cmd)
{
int i;
struct osdp_cmd *p;
struct osdp_pd *pd;
struct osdp *ctx = osdp_get_ctx();
if (osdp_get_sc_status_mask() != PD_MASK(ctx)) {
LOG_WRN(TAG "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 = TO_PD(ctx, i);
p = osdp_cmd_alloc(pd);
if (p == NULL) {
return -1;
}
p->id = CMD_KEYSET;
memcpy(&p->keyset, &cmd, sizeof(struct osdp_cmd_keyset));
osdp_cmd_enqueue(pd, p);
}
return 0;
}
#endif /* CONFIG_OSDP_SC_ENABLED */
void osdp_update(struct osdp *ctx)
{
int i;
@ -691,9 +903,18 @@ void osdp_update(struct osdp *ctx)
}
}
int osdp_setup(struct osdp *ctx)
int osdp_setup(struct osdp *ctx, uint8_t *key)
{
ARG_UNUSED(ctx);
ARG_UNUSED(key);
#ifdef CONFIG_OSDP_SC_ENABLED
if (key == NULL) {
LOG_ERR(TAG "Master key cannot be null");
return -1;
}
memcpy(ctx->sc_master_key, key, 16);
#endif
return 0;
}

View File

@ -26,6 +26,9 @@ LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#define CMD_BUZ_DATA_LEN 5
#define CMD_TEXT_DATA_LEN 6 /* variable length command */
#define CMD_COMSET_DATA_LEN 5
#define CMD_KEYSET_DATA_LEN 18
#define CMD_CHLNG_DATA_LEN 8
#define CMD_SCRYPT_DATA_LEN 16
#define REPLY_ACK_LEN 1
#define REPLY_PDID_LEN 13
@ -35,6 +38,8 @@ LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#define REPLY_RSTATR_LEN 2
#define REPLY_COM_LEN 6
#define REPLY_NAK_LEN 2
#define REPLY_CCRYPT_LEN 33
#define REPLY_RMAC_I_LEN 17
static struct osdp_pd_id osdp_pd_id = {
.version = CONFIG_OSDP_PD_ID_VERSION,
@ -51,7 +56,16 @@ static struct osdp_pd_cap osdp_pd_cap[] = {
1, /* The PD supports the 16-bit CRC-16 mode */
0, /* N/A */
},
{
OSDP_PD_CAP_COMMUNICATION_SECURITY,
#ifdef CONFIG_OSDP_SC_ENABLED
1, /* (Bit-0) AES128 support */
1, /* (Bit-0) default AES128 key */
#else
0, /* SC not supported */
0, /* SC not supported */
#endif
},
/* Configured from Kconfig */
{
OSDP_PD_CAP_CONTACT_STATUS_MONITORING,
@ -93,7 +107,7 @@ static struct osdp_pd_cap osdp_pd_cap[] = {
static void pd_decode_command(struct osdp_pd *pd, uint8_t *buf, int len)
{
int i, ret = -1, pos = 0;
int i, ret = -1, pos = 0, tmp;
struct osdp_cmd *cmd;
pd->reply_id = 0;
@ -275,6 +289,75 @@ static void pd_decode_command(struct osdp_pd *pd, uint8_t *buf, int len)
pd->reply_id = REPLY_COM;
ret = 0;
break;
#ifdef CONFIG_OSDP_SC_ENABLED
case CMD_KEYSET:
if (len != CMD_KEYSET_DATA_LEN) {
LOG_ERR(TAG "CMD_KEYSET length mismatch! %d/18", len);
break;
}
/**
* For CMD_KEYSET to be accepted, PD must be
* ONLINE and SC_ACTIVE.
*/
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) == 0) {
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
LOG_ERR(TAG "Keyset with SC inactive");
break;
}
/* only key_type == 1 (SCBK) and key_len == 16 is supported */
if (buf[pos] != 1 || buf[pos + 1] != 16) {
LOG_ERR(TAG "Keyset invalid len/type: %d/%d",
buf[pos], buf[pos + 1]);
break;
}
cmd = osdp_cmd_alloc(pd);
if (cmd == NULL) {
LOG_ERR(TAG "cmd alloc error");
break;
}
cmd->id = OSDP_CMD_KEYSET;
cmd->keyset.type = buf[pos++];
cmd->keyset.length = buf[pos++];
memcpy(cmd->keyset.data, buf + pos, 16);
memcpy(pd->sc.scbk, buf + pos, 16);
osdp_cmd_enqueue(pd, cmd);
CLEAR_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
CLEAR_FLAG(pd, PD_FLAG_INSTALL_MODE);
pd->reply_id = REPLY_ACK;
ret = 0;
break;
case CMD_CHLNG:
tmp = OSDP_PD_CAP_COMMUNICATION_SECURITY;
if (pd->cap[tmp].compliance_level == 0) {
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_UNSUP;
break;
}
if (len != CMD_CHLNG_DATA_LEN) {
LOG_ERR(TAG "CMD_CHLNG length mismatch! %d/8", len);
break;
}
osdp_sc_init(pd);
CLEAR_FLAG(pd, PD_FLAG_SC_ACTIVE);
for (i = 0; i < 8; i++) {
pd->sc.cp_random[i] = buf[pos++];
}
pd->reply_id = REPLY_CCRYPT;
ret = 0;
break;
case CMD_SCRYPT:
if (len != CMD_SCRYPT_DATA_LEN) {
LOG_ERR(TAG "CMD_SCRYPT length mismatch! %d/16", len);
break;
}
for (i = 0; i < 16; i++) {
pd->sc.cp_cryptogram[i] = buf[pos++];
}
pd->reply_id = REPLY_RMAC_I;
ret = 0;
break;
#endif /* CONFIG_OSDP_SC_ENABLED */
default:
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_CMD_UNKNOWN;
@ -305,6 +388,9 @@ static int pd_build_reply(struct osdp_pd *pd, uint8_t *buf, int max_len)
struct osdp_cmd *cmd;
data_off = osdp_phy_packet_get_data_offset(pd, buf);
#ifdef CONFIG_OSDP_SC_ENABLED
uint8_t *smb = osdp_phy_packet_get_smb(pd, buf);
#endif
buf += data_off;
max_len -= data_off;
if (max_len <= 0) {
@ -428,8 +514,72 @@ static int pd_build_reply(struct osdp_pd *pd, uint8_t *buf, int max_len)
buf[len++] = pd->cmd_data[0];
ret = 0;
break;
#ifdef CONFIG_OSDP_SC_ENABLED
case REPLY_CCRYPT:
if (smb == NULL) {
break;
}
if (max_len < REPLY_CCRYPT_LEN) {
LOG_ERR(TAG "Out of buffer space!");
return -1;
}
osdp_fill_random(pd->sc.pd_random, 8);
osdp_compute_session_keys(TO_CTX(pd));
osdp_compute_pd_cryptogram(pd);
buf[len++] = pd->reply_id;
for (i = 0; i < 8; i++) {
buf[len++] = pd->sc.pd_client_uid[i];
}
for (i = 0; i < 8; i++) {
buf[len++] = pd->sc.pd_random[i];
}
for (i = 0; i < 16; i++) {
buf[len++] = pd->sc.pd_cryptogram[i];
}
smb[0] = 3; /* length */
smb[1] = SCS_12; /* type */
smb[2] = ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD) ? 0 : 1;
ret = 0;
break;
case REPLY_RMAC_I:
if (smb == NULL) {
break;
}
if (max_len < REPLY_RMAC_I_LEN) {
LOG_ERR(TAG "Out of buffer space!");
return -1;
}
osdp_compute_rmac_i(pd);
buf[len++] = pd->reply_id;
for (i = 0; i < 16; i++) {
buf[len++] = pd->sc.r_mac[i];
}
smb[0] = 3; /* length */
smb[1] = SCS_14; /* type */
if (osdp_verify_cp_cryptogram(pd) == 0) {
smb[2] = 1; /* CP auth succeeded */
SET_FLAG(pd, PD_FLAG_SC_ACTIVE);
if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
LOG_WRN(TAG "SC Active with SCBK-D");
} else {
LOG_INF(TAG "SC Active");
}
} else {
smb[2] = 0; /* CP auth failed */
LOG_WRN(TAG "failed to verify CP_crypt");
}
ret = 0;
break;
#endif /* CONFIG_OSDP_SC_ENABLED */
}
#ifdef CONFIG_OSDP_SC_ENABLED
if (smb && (smb[1] > SCS_14) && ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
smb[0] = 2; /* length */
smb[1] = (len > 1) ? SCS_18 : SCS_16;
}
#endif /* CONFIG_OSDP_SC_ENABLED */
if (ret != 0) {
/* catch all errors and report it as a RECORD error to CP */
LOG_ERR(TAG "ReplyID unknown or insufficent space or some other"
@ -559,7 +709,8 @@ void osdp_update(struct osdp *ctx)
if (ret == 1) {
break;
}
if (ret == -1 || (pd->rx_buf_len > 0 &&
if (ret == -1 || ((pd->rx_buf_len > 0 ||
ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) &&
osdp_millis_since(pd->tstamp) > OSDP_RESP_TOUT_MS)) {
/**
* When we receive a command from PD after a timeout,
@ -617,8 +768,9 @@ static void osdp_pd_set_attributes(struct osdp_pd *pd, struct osdp_pd_cap *cap,
}
}
int osdp_setup(struct osdp *ctx)
int osdp_setup(struct osdp *ctx, uint8_t *key)
{
ARG_UNUSED(key);
struct osdp_pd *pd;
if (ctx->cp->num_pd != 1) {
@ -627,6 +779,15 @@ int osdp_setup(struct osdp *ctx)
pd = TO_PD(ctx, 0);
osdp_pd_set_attributes(pd, osdp_pd_cap, &osdp_pd_id);
SET_FLAG(pd, PD_FLAG_PD_MODE);
#ifdef CONFIG_OSDP_SC_ENABLED
if (key == NULL) {
LOG_WRN(TAG "SCBK not provided. PD is in INSTALL_MODE");
SET_FLAG(pd, PD_FLAG_INSTALL_MODE);
} else {
memcpy(pd->sc.scbk, key, 16);
}
SET_FLAG(pd, PD_FLAG_SC_CAPABLE);
#endif
return 0;
}

View File

@ -146,6 +146,57 @@ int osdp_phy_packet_finalize(struct osdp_pd *pd, uint8_t *buf,
pkt->len_lsb = BYTE_0(len - 1 + 2);
pkt->len_msb = BYTE_1(len - 1 + 2);
#ifdef CONFIG_OSDP_SC_ENABLED
uint8_t *data;
int i, is_cmd, data_len;
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) &&
pkt->control & PKT_CONTROL_SCB &&
pkt->data[1] >= SCS_15) {
is_cmd = !ISSET_FLAG(pd, PD_FLAG_PD_MODE);
if (pkt->data[1] == SCS_17 || pkt->data[1] == SCS_18) {
/**
* Only the data portion of message (after id byte)
* is encrypted. While (en/de)crypting, we must skip
* header, security block, and cmd/reply ID byte.
*
* Note: if cmd/reply has no data, we must set type to
* SCS_15/SCS_16 and send them.
*/
data = pkt->data + pkt->data[0] + 1;
data_len = len - (sizeof(struct osdp_packet_header)
+ pkt->data[0] + 1);
len -= data_len;
/**
* check if the passed buffer can hold the encrypted
* data where length may be rounded up to the nearest
* 16 byte block bondary.
*/
if (AES_PAD_LEN(data_len + 1) > max_len) {
/* data_len + 1 for OSDP_SC_EOM_MARKER */
goto out_of_space_error;
}
len += osdp_encrypt_data(pd, is_cmd, data, data_len);
}
/* len: with 4bytes MAC; with 2 byte CRC; without 1 byte mark */
if (len + 4 > max_len) {
goto out_of_space_error;
}
/* len: without 1 mark byte; with 2 byte CRC; with 4 byte MAC */
pkt->len_lsb = BYTE_0(len - 1 + 2 + 4);
pkt->len_msb = BYTE_1(len - 1 + 2 + 4);
/* compute and extend the buf with 4 MAC bytes */
osdp_compute_mac(pd, is_cmd, buf + 1, len - 1);
data = is_cmd ? pd->sc.c_mac : pd->sc.r_mac;
for (i = 0; i < 4; i++) {
buf[len + i] = data[i];
}
len += 4;
}
#endif /* CONFIG_OSDP_SC_ENABLED */
/* fill crc16 */
if (len + 2 > max_len) {
goto out_of_space_error;
@ -266,6 +317,87 @@ int osdp_phy_decode_packet(struct osdp_pd *pd, uint8_t *buf, int len)
}
data = pkt->data;
#ifdef CONFIG_OSDP_SC_ENABLED
uint8_t *mac;
int is_cmd;
if (pkt->control & PKT_CONTROL_SCB) {
if (pd_mode && !ISSET_FLAG(pd, PD_FLAG_SC_CAPABLE)) {
LOG_ERR(TAG "PD is not SC capable");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_UNSUP;
return OSDP_ERR_PKT_FMT;
}
if (pkt->data[1] < SCS_11 || pkt->data[1] > SCS_18) {
LOG_ERR(TAG "invalid SB Type");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
if (pkt->data[1] == SCS_11 || pkt->data[1] == SCS_13) {
/**
* CP signals PD to use SCBKD by setting SB data byte
* to 0. In CP, PD_FLAG_SC_USE_SCBKD comes from FSM; on
* PD we extract it from the command itself. But this
* usage of SCBKD is allowed only when the PD is in
* install mode (indicated by PD_FLAG_INSTALL_MODE).
*/
if (ISSET_FLAG(pd, PD_FLAG_INSTALL_MODE) &&
pkt->data[2] == 0) {
SET_FLAG(pd, PD_FLAG_SC_USE_SCBKD);
}
}
data = pkt->data + pkt->data[0];
len -= pkt->data[0]; /* consume security block */
} else {
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE)) {
LOG_ERR(TAG "Received plain-text message in SC");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
}
if (ISSET_FLAG(pd, PD_FLAG_SC_ACTIVE) &&
pkt->control & PKT_CONTROL_SCB &&
pkt->data[1] >= SCS_15) {
/* validate MAC */
is_cmd = ISSET_FLAG(pd, PD_FLAG_PD_MODE);
osdp_compute_mac(pd, is_cmd, buf + 1, mac_offset);
mac = is_cmd ? pd->sc.c_mac : pd->sc.r_mac;
if (memcmp(buf + 1 + mac_offset, mac, 4) != 0) {
LOG_ERR(TAG "invalid MAC");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
len -= 4; /* consume MAC */
/* decrypt data block */
if (pkt->data[1] == SCS_17 || pkt->data[1] == SCS_18) {
/**
* Only the data portion of message (after id byte)
* is encrypted. While (en/de)crypting, we must skip
* header (6), security block (2) and cmd/reply id (1)
* bytes if cmd/reply has no data, use SCS_15/SCS_16.
*
* At this point, the header and security block is
* already consumed. So we can just skip the cmd/reply
* ID (data[0]) when calling osdp_decrypt_data().
*/
len = osdp_decrypt_data(pd, is_cmd, data + 1, len - 1);
if (len <= 0) {
LOG_ERR(TAG "failed at decrypt");
pd->reply_id = REPLY_NAK;
pd->cmd_data[0] = OSDP_PD_NAK_SC_COND;
return OSDP_ERR_PKT_FMT;
}
len += 1; /* put back cmd/reply ID */
}
}
#endif /* CONFIG_OSDP_SC_ENABLED */
memmove(buf, data, len);
return len;
}

View File

@ -0,0 +1,246 @@
/*
* Copyright (c) 2019 Siddharth Chandrasekaran <siddharth@embedjournal.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_DECLARE(osdp, CONFIG_OSDP_LOG_LEVEL);
#include <string.h>
#include "osdp_common.h"
#define TAG "SC: "
#define OSDP_SC_EOM_MARKER 0x80 /* End of Message Marker */
/* Default key as specified in OSDP specification */
static const uint8_t osdp_scbk_default[16] = {
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
void osdp_compute_scbk(struct osdp_pd *pd, uint8_t *scbk)
{
int i;
struct osdp *ctx = TO_CTX(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);
}
void osdp_compute_session_keys(struct osdp *ctx)
{
int i;
struct osdp_pd *pd = GET_CURRENT_PD(ctx);
if (ISSET_FLAG(pd, PD_FLAG_SC_USE_SCBKD)) {
memcpy(pd->sc.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);
}
}
memset(pd->sc.s_enc, 0, 16);
memset(pd->sc.s_mac1, 0, 16);
memset(pd->sc.s_mac2, 0, 16);
pd->sc.s_enc[0] = 0x01; pd->sc.s_enc[1] = 0x82;
pd->sc.s_mac1[0] = 0x01; pd->sc.s_mac1[1] = 0x01;
pd->sc.s_mac2[0] = 0x01; pd->sc.s_mac2[1] = 0x02;
for (i = 2; i < 8; i++) {
pd->sc.s_enc[i] = pd->sc.cp_random[i - 2];
pd->sc.s_mac1[i] = pd->sc.cp_random[i - 2];
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);
}
void osdp_compute_cp_cryptogram(struct osdp_pd *pd)
{
/* cp_cryptogram = AES-ECB( pd_random[8] || cp_random[8], s_enc ) */
memcpy(pd->sc.cp_cryptogram + 0, pd->sc.pd_random, 8);
memcpy(pd->sc.cp_cryptogram + 8, pd->sc.cp_random, 8);
osdp_encrypt(pd->sc.s_enc, NULL, pd->sc.cp_cryptogram, 16);
}
/**
* Like memcmp; but operates at constant time.
*
* Returns 0 if memory pointed to by s1 and and s2 are identical; non-zero
* otherwise.
*/
static int osdp_ct_compare(const void *s1, const void *s2, size_t len)
{
size_t i, ret = 0;
const uint8_t *_s1 = s1;
const uint8_t *_s2 = s2;
for (i = 0; i < len; i++) {
ret |= _s1[i] ^ _s2[i];
}
return (int)ret;
}
int osdp_verify_cp_cryptogram(struct osdp_pd *pd)
{
uint8_t cp_crypto[16];
/* cp_cryptogram = AES-ECB( pd_random[8] || cp_random[8], s_enc ) */
memcpy(cp_crypto + 0, pd->sc.pd_random, 8);
memcpy(cp_crypto + 8, pd->sc.cp_random, 8);
osdp_encrypt(pd->sc.s_enc, NULL, cp_crypto, 16);
if (osdp_ct_compare(pd->sc.cp_cryptogram, cp_crypto, 16) != 0) {
return -1;
}
return 0;
}
void osdp_compute_pd_cryptogram(struct osdp_pd *pd)
{
/* pd_cryptogram = AES-ECB( cp_random[8] || pd_random[8], s_enc ) */
memcpy(pd->sc.pd_cryptogram + 0, pd->sc.cp_random, 8);
memcpy(pd->sc.pd_cryptogram + 8, pd->sc.pd_random, 8);
osdp_encrypt(pd->sc.s_enc, NULL, pd->sc.pd_cryptogram, 16);
}
int osdp_verify_pd_cryptogram(struct osdp_pd *pd)
{
uint8_t pd_crypto[16];
/* pd_cryptogram = AES-ECB( cp_random[8] || pd_random[8], s_enc ) */
memcpy(pd_crypto + 0, pd->sc.cp_random, 8);
memcpy(pd_crypto + 8, pd->sc.pd_random, 8);
osdp_encrypt(pd->sc.s_enc, NULL, pd_crypto, 16);
if (osdp_ct_compare(pd->sc.pd_cryptogram, pd_crypto, 16) != 0) {
return -1;
}
return 0;
}
void osdp_compute_rmac_i(struct osdp_pd *pd)
{
/* rmac_i = AES-ECB( AES-ECB( cp_cryptogram, s_mac1 ), s_mac2 ) */
memcpy(pd->sc.r_mac, pd->sc.cp_cryptogram, 16);
osdp_encrypt(pd->sc.s_mac1, NULL, pd->sc.r_mac, 16);
osdp_encrypt(pd->sc.s_mac2, NULL, pd->sc.r_mac, 16);
}
int osdp_decrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int length)
{
int i;
uint8_t iv[16];
if (length % 16 != 0) {
LOG_ERR(TAG "decrypt_pkt invalid len:%d", length);
return -1;
}
memcpy(iv, is_cmd ? pd->sc.r_mac : pd->sc.c_mac, 16);
for (i = 0; i < 16; i++) {
iv[i] = ~iv[i];
}
osdp_decrypt(pd->sc.s_enc, iv, data, length);
while (data[length - 1] == 0x00) {
length--;
}
if (data[length - 1] != OSDP_SC_EOM_MARKER) {
return -1;
}
data[length - 1] = 0;
return length - 1;
}
int osdp_encrypt_data(struct osdp_pd *pd, int is_cmd, uint8_t *data, int length)
{
int i, pad_len;
uint8_t iv[16];
data[length] = OSDP_SC_EOM_MARKER; /* append EOM marker */
pad_len = AES_PAD_LEN(length + 1);
if ((pad_len - length - 1) > 0) {
memset(data + length + 1, 0, pad_len - length - 1);
}
memcpy(iv, is_cmd ? pd->sc.r_mac : pd->sc.c_mac, 16);
for (i = 0; i < 16; i++) {
iv[i] = ~iv[i];
}
osdp_encrypt(pd->sc.s_enc, iv, data, pad_len);
return pad_len;
}
int osdp_compute_mac(struct osdp_pd *pd, int is_cmd,
const uint8_t *data, int len)
{
int pad_len;
uint8_t buf[CONFIG_OSDP_UART_BUFFER_LENGTH] = { 0 };
uint8_t iv[16];
memcpy(buf, data, len);
pad_len = (len % 16 == 0) ? len : AES_PAD_LEN(len);
if (len % 16 != 0) {
buf[len] = 0x80; /* end marker */
}
/**
* MAC for data blocks B[1] .. B[N] (post padding) is computed as:
* IV1 = R_MAC (or) C_MAC -- depending on is_cmd
* IV2 = B[N-1] after -- AES-CBC ( IV1, B[1] to B[N-1], SMAC-1 )
* MAC = AES-ECB ( IV2, B[N], SMAC-2 )
*/
memcpy(iv, is_cmd ? pd->sc.r_mac : pd->sc.c_mac, 16);
if (pad_len > 16) {
/* N-1 blocks -- encrypted with SMAC-1 */
osdp_encrypt(pd->sc.s_mac1, iv, buf, pad_len - 16);
/* N-1 th block is the IV for N th block */
memcpy(iv, buf + pad_len - 32, 16);
}
/* N-th Block encrypted with SMAC-2 == MAC */
osdp_encrypt(pd->sc.s_mac2, iv, buf + pad_len - 16, 16);
memcpy(is_cmd ? pd->sc.c_mac : pd->sc.r_mac, buf + pad_len - 16, 16);
return 0;
}
void osdp_sc_init(struct osdp_pd *pd)
{
uint8_t key[16];
if (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
memcpy(key, 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 (ISSET_FLAG(pd, PD_FLAG_PD_MODE)) {
pd->sc.pd_client_uid[0] = BYTE_0(pd->id.vendor_code);
pd->sc.pd_client_uid[1] = BYTE_1(pd->id.vendor_code);
pd->sc.pd_client_uid[2] = BYTE_0(pd->id.model);
pd->sc.pd_client_uid[3] = BYTE_1(pd->id.version);
pd->sc.pd_client_uid[4] = BYTE_0(pd->id.serial_number);
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);
}
}