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:
parent
c1c627ce5a
commit
7f4d2c741b
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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_ */
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
246
subsys/mgmt/osdp/src/osdp_sc.c
Normal file
246
subsys/mgmt/osdp/src/osdp_sc.c
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user