ATECC508A开发笔记

本文为ATECC508A开发笔记。

Features

  • Cryptographic Co-processor with Secure Hardware-Based Key Storage
  • Performs High-Speed Public Key (PKI) Algorithms
    – ECDSA: FIPS186-3 Elliptic Curve Digital Signature Algorithm
    – ECDH: FIPS SP800-56A Elliptic Curve Diffie-Hellman Algorithm
  • NIST Standard P256 Elliptic Curve Support
  • SHA-256 Hash Algorithm with HMAC Option
  • Host and Client Operations
  • 256-bit Key Length
  • Storage for up to 16 Keys
  • Two High-Endurance Monotonic Counters
  • Guaranteed Unique 72-bit Serial Number
  • Internal High-Quality FIPS Random Number Generator (RNG)
  • 10 Kb EEPROM Memory for Keys, Certificates, and Data
  • Multiple Options for Consumption Logging and One-Time Write Information
  • Intrusion Latch for External Tamper Switch or Power-on Chip Enablement. Multiple I/O Options:
    – High-speed Single Pin Interface, with One GPIO Pin
    – 1 MHz Standard I2C Interface
  • 2.0V to 5.5V Supply Voltage Range
  • 1.8V to 5.5V IO levels
  • <150 nA Sleep Current
  • 8-pad UDFN, 8-lead SOIC, and 3-lead CONTACT Packages

Introduction

Device Organization

 ATECC508A包含EEPROM存储以及SRAM buffer。

 其中EEPROM总计11200bit=1400byte,被分为如下区:

zone 描述 术语
Data 总计1208byte,分为16个只读或者独写存储区,每个存储区由36byte、72byte或者416byte构成,每个存储区可以存储公钥私钥、签名、证书、校准或者其它信息等。每个slot的存取策略由对应地configuration值决定。注意,只有当LockValue被置位后权限才会生效。 Slot\ =data区域slot YY存储的全部内容
Configuration 总计128byte。存储了串号、其它ID信息、每个data slot的访存取策略。configuration区域在被锁定前,均可被修改。 SN\<a:b> =configuration区域中的一段字节
One Time Programmable (OTP) 总计64byte。在锁定前,可以使用标准Write指令写入数据。OTP区域可以用于存储只读数据或者单向熔断型消耗记录信息(one-way fuse type consumption logging information,即只能对位进行复位)。 OTP\ = OPT区域的一个字节

 文档术语

术语 含义
Block 特定存储区上一个256bit/32byte的区域。
KeyID 用于存储key的slot编号。Key 1即存储在slot<1>。
param 命令参数或者配置字节中的1位。
SRAM 包括输入和输出buffer,以及状态存储位置。

EEPROM Data Zone

slot blocks bytes bits typical use notes
0-7 2 36 288 Private or Secret Key 也可用于存储数据
8 13 416 3328 Data
9-14 3 72 576 Public Key, Signature or Certificate
15 3 72 576 Private Data, Secret Key, Signature, or Certificate 仅该slot可以支持128计数限制使用特性

 注意到不同slot的大小不同,推荐使用slot0-7存储私钥,slot8存储证书,slot9-15存储公钥等。

EEPROM Configuration Zone

 配置区共128byte,具体如下:

 Configuration区重要配置如下:

  • byte0-15为只读区,包含芯片的串号、版本号、I2C使能等信息;
  • byte16配置芯片I2C地址;
  • byte18为OTPMode,设置OTP区的读写权限;
  • byte19为ChipMode,设置芯片看门狗时长、输入逻辑电平、SelectorMode;
  • byte20-51为SlotConfig,共计32byte,其中每两个byte对应一个数据区slot,用于控制该slot的访问以及使用权限;
  • byte52-59为Counter<0>,共8byte,为一个单调计数器,可以通过SlotConfig.LimitedUse位关联key;
  • byte60-67为Counter<1>,共8byte,不能关联到key;
  • byte68-83为LastKeyUse,共16byte,用于限制KeyID 15的使用次数,初始值为0xFF;
  • byte84为UserExtra,当数据区锁定后,可以通过UpdateExtra命令进行修改;
  • byte85为Selector,用于选定Pause指令执行后保持活跃状态的器件;
  • byte86为LockValue,用于使能配置区设定的数据与OTP区访问规则;
  • byte87为LockConfig,控制配置区的修改权限;
  • byte88-89为SlotLocked,每个bit对应一个slot。如果slot对应的位为0,则slot中的内容任何条件下无法被修改;
  • byte90-91为RFU,必须为0;
  • byte92-95为X509format,共4byte,对应4个独立的格式字节,关联了设备中存储的X.509证书公钥格式;
  • byte96-127为KeyConfig,共32byte,其中每两个byte对应一个数据区slot,用于额外的访问以及使用权限。

node-auth-basic代码分析

client_provision()

 client_provision()函数用于设置ATECC508A芯片,具体内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/** \brief entry point called from the console interaction to provision the client side certs and device key.  This is
* a self-contained example of the provisioning process that would normally happen at the factory using high security
* modules (HSM) and production facilities. This is functional code which can be used to self-provisioin a device for
* illustration, example, or demo purposes.
*/

int client_provision(void)
{
int ret = 0;
bool lockstate = 0;
uint8_t signer_ca_private_key_slot = 7;
uint8_t signer_private_key_slot = 2;
uint8_t signer_id[2] = {0xC4, 0x8B};
const atcacert_tm_utc_t signer_issue_date = {
.tm_year = 2014 - 1900,
.tm_mon = 8 - 1,
.tm_mday = 2,
.tm_hour = 20,
.tm_min = 0,
.tm_sec = 0
};
uint8_t device_private_key_slot = 0;
const atcacert_tm_utc_t device_issue_date = {
.tm_year = 2015 - 1900,
.tm_mon = 9 - 1,
.tm_mday = 3,
.tm_hour = 21,
.tm_min = 0,
.tm_sec = 0
};
static const uint8_t access_key_slot = 4;
const uint8_t access_key[] = {
0x32, 0x12, 0xd0, 0x66, 0xf5, 0xed, 0x52, 0xc7, 0x79, 0x98, 0xff, 0xaa, 0xac, 0x43, 0x22, 0x60,
0xdd, 0xff, 0x9c, 0x10, 0x99, 0x6f, 0x41, 0x66, 0x3a, 0x60, 0x23, 0xfa, 0xf6, 0xaa, 0x3e, 0xc5
};
uint8_t config64[64];
bool is_signer_ca_slot_ext_sig = false;
bool is_signer_ca_slot_priv_write = false;
uint8_t signer_public_key[64];
uint8_t device_public_key[64];
uint8_t signer_cert_ref[512];
size_t signer_cert_ref_size = 0;
uint8_t device_cert_ref[512];
size_t device_cert_ref_size = 0;

atcab_sleep();

ret = atcab_is_locked(LOCK_ZONE_CONFIG, &lockstate);
if (ret != ATCA_SUCCESS) return ret;
if (!lockstate)
{
ret = atcab_write_config_zone(g_ecc_configdata);
if (ret != ATCA_SUCCESS) return ret;

ret = atcab_lock_config_zone();
if (ret != ATCA_SUCCESS) return ret;
}

// Read the first 64 bytes of the config zone to get the slot config at least
ret = atcab_read_zone(ATCA_ZONE_CONFIG, 0, 0, 0, &config64[0], 32);
if (ret != ATCA_SUCCESS) return ret;
ret = atcab_read_zone(ATCA_ZONE_CONFIG, 0, 1, 0, &config64[32], 32);
if (ret != ATCA_SUCCESS) return ret;

is_signer_ca_slot_ext_sig = (config64[20 + signer_ca_private_key_slot*2] & 0x01);
is_signer_ca_slot_priv_write = (config64[20 + signer_ca_private_key_slot*2 + 1] & 0x40);

ret = atcab_is_locked(LOCK_ZONE_DATA, &lockstate);
if (ret != ATCA_SUCCESS) return ret;
if (!lockstate)
{
ret = atcab_priv_write(signer_ca_private_key_slot, g_signer_ca_private_key, 0, NULL);
if (ret != ATCA_SUCCESS) return ret;

ret = atcab_write_zone(DEVZONE_DATA, access_key_slot, 0, 0, access_key, 32);
if (ret != ATCA_SUCCESS) return ret;

ret = atcab_lock_data_zone();
if (ret != ATCA_SUCCESS) return ret;
}
else if (!is_signer_ca_slot_ext_sig)
{
// The signer CA slot can't perform external signs.
// Use the signer slot for both. A little weird, but it lets the example run.
printf("Signer CA slot %d not available. Signer CA and signer will be sharing a key.\r\n", signer_ca_private_key_slot);
signer_ca_private_key_slot = signer_private_key_slot;
}
else if (is_signer_ca_slot_priv_write)
{
ret = atcab_priv_write(signer_ca_private_key_slot, g_signer_ca_private_key, access_key_slot, access_key);
if (ret != ATCA_SUCCESS) return printf("Warning: PrivWrite to slot %d failed. Example may still work though.\n", signer_ca_private_key_slot);
}

if (signer_ca_private_key_slot != signer_private_key_slot)
{
ret = atcab_get_pubkey(signer_ca_private_key_slot, g_signer_ca_public_key);
if (ret == ATCA_EXECUTION_ERROR)
ret = atcab_genkey(signer_ca_private_key_slot, g_signer_ca_public_key);
if (ret != ATCA_SUCCESS) return ret;
atcab_printbin_label("Signer CA Public Key:\r\n", g_signer_ca_public_key, ATCA_PUB_KEY_SIZE);
}

ret = atcab_genkey(signer_private_key_slot, signer_public_key);
if (ret != ATCA_SUCCESS) return ret;
if (signer_ca_private_key_slot == signer_private_key_slot)
{
memcpy(g_signer_ca_public_key, signer_public_key, sizeof(g_signer_ca_public_key));
atcab_printbin_label("Signer CA Public Key:\r\n", g_signer_ca_public_key, ATCA_PUB_KEY_SIZE);
}
atcab_printbin_label("Signer Public Key:\r\n", signer_public_key, ATCA_PUB_KEY_SIZE);

ret = atcab_genkey(device_private_key_slot, device_public_key);
if (ret != ATCA_SUCCESS) return ret;
atcab_printbin_label("Device Public Key:\r\n", device_public_key, ATCA_PUB_KEY_SIZE);

// Build signer cert
signer_cert_ref_size = sizeof(signer_cert_ref);
ret = build_and_save_cert(
g_cert_def_2_device.ca_cert_def,
signer_cert_ref,
&signer_cert_ref_size,
g_signer_ca_public_key,
signer_public_key,
signer_id,
&signer_issue_date,
config64,
signer_ca_private_key_slot);
if (ret != ATCA_SUCCESS) return ret;
atcab_printbin_label("Signer Certificate:\r\n", signer_cert_ref, signer_cert_ref_size);

device_cert_ref_size = sizeof(device_cert_ref);
ret = build_and_save_cert(
&g_cert_def_2_device,
device_cert_ref,
&device_cert_ref_size,
signer_public_key,
device_public_key,
signer_id,
&device_issue_date,
config64,
signer_private_key_slot);
if (ret != ATCA_SUCCESS) return ret;
atcab_printbin_label("Device Certificate:\r\n", device_cert_ref, device_cert_ref_size);

return 0;
}

 变量定义:

  • signer_ca_private_key_slot为7,即CA的私钥存储在slot 7;signer_private_key_slot为2,即signer的私钥存储在slot2;
  • signer_id为2byte,用于制作证书;
  • signer_issue_date记录signer日期,用于制作证书;
  • device_private_key_slot为0,即设备的私钥存储在slot0;
  • device_issue_date记录设备日期,用于制作证书;
  • access_key_slot为4,即access_key存储在slot4,同时定义access_key[32]数组;
  • config64[64]数据用于存储配置区前64byte的配置数据;
  • is_signer_ca_slot_ext_sig为标志位,代表CA所在slot是否允许外部签名,初始为false;
  • is_signer_ca_slot_priv_write为标志位,代表CA所在slot是否为为priv_write,初始为false;
  • signer_public_key[64]记录signer的公钥;
  • device_public_key[64]记录设备的公钥;
  • signer_cert_ref[512]记录signer证书,signer_cert_ref_size为其长度;
  • device_cert_ref[512]记录设备证书,device_cert_ref_size为其长度。

 程序执行:

 配置区设置流程如下:

  • 调用atcab_sleep(),设备进入休眠模式;
  • 调用atcab_is_locked(LOCK_ZONE_CONFIG, &lockstate)判断配置区是不是已经锁定;如果未锁定,则通过atcab_write_config_zone(g_ecc_configdata)函数向配置区写入配置数据,g_ecc_configdata为配置数据,总计128byte;写入配置数据后,调用atcab_lock_config_zone()函数锁定配置区;
  • 调用atcab_read_zone(ATCA_ZONE_CONFIG, 0, 0, 0, &config64[0], 32)和atcab_read_zone(ATCA_ZONE_CONFIG, 0, 1, 0, &config64[0], 32)获取配置区前64字节数据,存储在config64[64]中;
  • is_signer_ca_slot_ext_sig等于CA签名所在slot对应的SlotConfig中bit0,当该位为1时,说明CA所在slot允许执行外部签名;
  • is_signer_ca_slot_priv_write等于CA签名所在slot对应的SlotConfig中bit14,当改为为1时,代表该slot需要priv_write;

 数据区配置流程如下:

  • 调用atcab_is_locked(LOCK_ZONE_DATA, &lockstate)判断数据区是不是已经锁定;
  • 如果数据区未锁定,则通过atcab_priv_write(signer_ca_private_key_slot, g_signer_ca_private_key, 0, NULL)写入CA的私钥,再通过atcab_write_zone(DEVZONE_DATA, access_key_slot, 0, 0, access_key, 32)写入access_key,再调用atcab_lock_data_zone()锁定数据区;
  • 如果数据区已经锁定,则判断is_signer_ca_slot_ext_sig是否为0,如果为0则说明CA所在slot不能执行外部签名,则signer_ca_private_key_slot = signer_private_key_slot;如果is_signer_ca_slot_ext_sig为1,则判断is_signer_ca_slot_priv_write是否为1。如果is_signer_ca_slot_priv_write为1,则利用access_key再次执行atcab_priv_write(signer_ca_private_key_slot, g_signer_ca_private_key, access_key_slot, access_key),即写入CA私钥;
  • 此时,CA的私钥已经写入到相应的slot中,调用atcab_get_pubkey(signer_ca_private_key_slot, g_signer_ca_public_key)可以得到CA私钥对应地公钥,存储在g_signer_ca_public_key;
  • 调用atcab_genkey(signer_private_key_slot, signer_public_key),生成signer的私钥和公钥,公钥存储在signer_public_key;
  • 调用atcab_genkey(device_private_key_slot, device_public_key),生成设备的私钥和公钥,公钥存储在device_public_key;
  • 之后,分别调用build_and_save_cert生成CA和signer证书。

 配置区的具体配置如下:

1
2
3
4
5
6
7
8
9
static const uint8_t g_ecc_configdata[128] = {
0x01, 0x23, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x04, 0x05, 0x06, 0x07, 0xEE, 0x00, 0x01, 0x00,
D_I2C, 0x00, 0x55, 0x00, 0x8F, 0x20, 0xC4, 0x44, 0x87, 0x20, 0xC4, 0x44, 0x8F, 0x0F, 0x8F, 0x8F,
0x9F, 0x8F, 0x83, 0x64, 0xC4, 0x44, 0xC4, 0x44, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x33, 0x00, 0x1C, 0x00, 0x13, 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x1C, 0x00, 0x1C, 0x00, 0x33, 0x00,
0x1C, 0x00, 0x1C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00 };

 所有slot的配置如下:

这里的工具软件是基于608A的,与508A并不完全兼容。

 现在,来分别看一下对应CA私钥、signer私钥、设备私钥的各个slot配置。

 CA对应的SlotConfig配置为0x6483;signer的配置为0x2087;设备的配置为0x208F。

bit name device(slot0) signer(slot2) CA(slot7)
15-12 WriteConfig 0010 0010 0110
11-8 WriteKey 0000 0000 0100
7 IsSecret 1 1 1
6 EncryptRead 0 0 0
5 LimitedUse 0 0 0
4 NoMac 0 0 0
3-0 ReadKey 1111 0111 0011

 CA对应的KeyConfig配置为0x0033;signer的配置为0x0013;设备的配置为0x0033。

bit name device(slot0) signer(slot2) CA(slot7)
15-14 X509id 00 00 00
13 RFU 0 0 0
12 IntrusionDisable 0 0 0
11-8 AuthKey 0000 0000 0000
7 ReqAuth 0 0 0
6 ReqRandom 0 0 0
5 Lockable 1 0 1
4-2 KeyType 100 100 100
1 PubInfo 1 1 1
0 Private 1 1 1
  • 根据KeyConfig中KeyType、PubInfo、Private可以得知,CA、signer和设备的私钥均为ECC私钥,且均可生成公钥;
  • Slot Locking设置:CA、signer和设备对应slot的SlotLocked均为1,但是Lockable不同。当Lockable为1,说明可以通过Lock指令修改SlotLocked,将slot锁定;

 证书生成与保存函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/** \brief example of building a save a cert in the ATECC508A.  Normally this would be done during
* production and provisioning at a factory and therefore things like the root CA public key and
* other attributes would be coming from the server and HSM. This is a functional piece of code
* which emulates that process for illustration, example, and demonstration purposes.
*/
static int build_and_save_cert(
const atcacert_def_t* cert_def,
uint8_t* cert,
size_t* cert_size,
const uint8_t ca_public_key[64],
const uint8_t public_key[64],
const uint8_t signer_id[2],
const atcacert_tm_utc_t* issue_date,
const uint8_t config32[32],
uint8_t ca_slot)
{
int ret;
atcacert_build_state_t build_state;
uint8_t tbs_digest[32];
uint8_t signature[64];
size_t max_cert_size = *cert_size;
atcacert_tm_utc_t expire_date = {
.tm_year = issue_date->tm_year + cert_def->expire_years,
.tm_mon = issue_date->tm_mon,
.tm_mday = issue_date->tm_mday,
.tm_hour = issue_date->tm_hour,
.tm_min = 0,
.tm_sec = 0
};
const atcacert_device_loc_t config32_dev_loc = {
.zone = DEVZONE_CONFIG,
.offset = 0,
.count = 32
};
atcacert_device_loc_t device_locs[4];
size_t device_locs_count = 0;
size_t i;

if (cert_def->expire_years == 0)
{
ret = atcacert_date_get_max_date(cert_def->expire_date_format, &expire_date);
if (ret != ATCACERT_E_SUCCESS) return ret;
}

ret = atcacert_cert_build_start(&build_state, cert_def, cert, cert_size, ca_public_key);
if (ret != ATCACERT_E_SUCCESS) return ret;

ret = atcacert_set_subj_public_key(build_state.cert_def, build_state.cert, *build_state.cert_size, public_key);
if (ret != ATCACERT_E_SUCCESS) return ret;
ret = atcacert_set_issue_date(build_state.cert_def, build_state.cert, *build_state.cert_size, issue_date);
if (ret != ATCACERT_E_SUCCESS) return ret;
ret = atcacert_set_expire_date(build_state.cert_def, build_state.cert, *build_state.cert_size, &expire_date);
if (ret != ATCACERT_E_SUCCESS) return ret;
ret = atcacert_set_signer_id(build_state.cert_def, build_state.cert, *build_state.cert_size, signer_id);
if (ret != ATCACERT_E_SUCCESS) return ret;
ret = atcacert_cert_build_process(&build_state, &config32_dev_loc, config32);
if (ret != ATCACERT_E_SUCCESS) return ret;

ret = atcacert_cert_build_finish(&build_state);
if (ret != ATCACERT_E_SUCCESS) return ret;

ret = atcacert_get_tbs_digest(build_state.cert_def, build_state.cert, *build_state.cert_size, tbs_digest);
if (ret != ATCACERT_E_SUCCESS) return ret;

ret = atcab_sign(ca_slot, tbs_digest, signature);
if (ret != ATCA_SUCCESS) return ret;

ret = atcacert_set_signature(cert_def, cert, cert_size, max_cert_size, signature);
if (ret != ATCACERT_E_SUCCESS) return ret;

ret = atcacert_get_device_locs(cert_def, device_locs, &device_locs_count, sizeof(device_locs) / sizeof(device_locs[0]), 32);
if (ret != ATCACERT_E_SUCCESS) return ret;

for (i = 0; i < device_locs_count; i++)
{
size_t end_block;
size_t start_block;
uint8_t data[96];
uint8_t block;

if (device_locs[i].zone == DEVZONE_CONFIG)
continue;
if (device_locs[i].zone == DEVZONE_DATA && device_locs[i].is_genkey)
continue;

ret = atcacert_get_device_data(cert_def, cert, *cert_size, &device_locs[i], data);
if (ret != ATCACERT_E_SUCCESS) return ret;

start_block = device_locs[i].offset / 32;
end_block = (device_locs[i].offset + device_locs[i].count) / 32;
for (block = start_block; block < end_block; block++)
{
ret = atcab_write_zone(device_locs[i].zone, device_locs[i].slot, block, 0, &data[(block - start_block) * 32], 32);
if (ret != ATCA_SUCCESS) return ret;
}
}

return 0;
}

client_rebuild_certs()

 client_rebuild_certs()函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/** \brief This client role method demonstrates how to read cert data stored in the ATECC508A and reconstruct
* a full X.509 cert in DER format. Because this is an example, it prints the reconstructed cert
* data to the console in ASCII hex format.
*/

int client_rebuild_certs(void)
{
int ret = 0;
uint8_t signer_public_key[64];

g_signer_cert_size = sizeof(g_signer_cert);
ret = atcacert_read_cert(g_cert_def_2_device.ca_cert_def, g_signer_ca_public_key, g_signer_cert, &g_signer_cert_size);
if (ret != ATCACERT_E_SUCCESS) return ret;
atcab_printbin_label("CLIENT: Rebuilt Signer Certificate:\r\n", g_signer_cert, g_signer_cert_size);

ret = atcacert_get_subj_public_key(g_cert_def_2_device.ca_cert_def, g_signer_cert, g_signer_cert_size, signer_public_key);
if (ret != ATCACERT_E_SUCCESS) return ret;

g_device_cert_size = sizeof(g_device_cert);
ret = atcacert_read_cert(&g_cert_def_2_device, signer_public_key, g_device_cert, &g_device_cert_size);
if (ret != ATCACERT_E_SUCCESS) return ret;
atcab_printbin_label("CLIENT: Rebuilt Device Certificate:\r\n", g_device_cert, g_device_cert_size);

return 0;
}

host_verify_cert_chain()

 host_verify_cert_chain()函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/** \brief This host role method demonstrates how to do a chain verify.  In this example, there is a root certificate
* authority which signed the signer's cert. The signer signed the device cert. The chain verification
* performs an ECDSA verification of each link in the cert chain. This verifies that the device has been
* properly signed into the chain starting from the CA root of trust (RoT).
*/

int host_verify_cert_chain(void)
{
int ret = 0;
uint8_t signer_public_key[64];

// Validate signer cert against its certificate authority (CA) public key
ret = atcacert_verify_cert_hw(g_cert_def_2_device.ca_cert_def, g_signer_cert, g_signer_cert_size, g_signer_ca_public_key);
if (ret != ATCACERT_E_SUCCESS) return ret;
printf("HOST: Signer certificate verified against signer certificate authority (CA) public key!\r\n");

// Get the signer's public key from its certificate
ret = atcacert_get_subj_public_key(g_cert_def_2_device.ca_cert_def, g_signer_cert, g_signer_cert_size, signer_public_key);
if (ret != ATCACERT_E_SUCCESS) return ret;

// Validate the device cert against its certificate authority (CA) which is the signer
ret = atcacert_verify_cert_hw(&g_cert_def_2_device, g_device_cert, g_device_cert_size, signer_public_key);
if (ret != ATCACERT_E_SUCCESS) return ret;
printf("HOST: Device certificate verified against signer public key!\r\n");

return 0;
}
  • 首先调用atcacert_verify_cert_hw(g_cert_def_2_device.ca_cert_def, g_signer_cert, g_signer_cert_size, g_signer_ca_public_key)函数,利用CA的公钥g_signer_ca_public_key验证signer的签名;
  • 当signer签名验证完成后,调用atcacert_get_subj_public_key(g_cert_def_2_device.ca_cert_def, g_signer_cert, g_signer_cert_size, signer_public_key)函数从signer证书中获取signer的公钥;
  • 再调用atcacert_verify_cert_hw(&g_cert_def_2_device, g_device_cert, g_device_cert_size, signer_public_key)验证设备的签名;

 注意到存在atcacert_verify_cert_sw(&g_cert_def_2_device, g_device_cert, g_device_cert_size, signer_public_key)函数,应该是证书验证的软件实现,但是该函数无法成功运行,后确认如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/** \brief return software generated ECDSA verification result and the function is currently not implemented
* \param[in] msg ptr to message or challenge
* \param[in] signature ptr to the signature to verify
* \param[in] public_key ptr to public key of device which signed the challenge
* return ATCA_UNIMPLEMENTED , as the function is currently not implemented
*/

int atcac_sw_ecdsa_verify_p256(const uint8_t msg[ATCA_ECC_P256_FIELD_SIZE],
const uint8_t signature[ATCA_ECC_P256_SIGNATURE_SIZE],
const uint8_t public_key[ATCA_ECC_P256_PUBLIC_KEY_SIZE])
{
return ATCA_UNIMPLEMENTED;
}
  • atcac_sw_ecdsa_verify_p256()函数未给出软件实现。

如果证书的认证需要外部设备确认,我认为这存在一个风险:如果自己使用逻辑芯片,伪装一个认证成功的结果,则会成功欺骗主机。

host_generate_challenge()

 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/** \brief This host role method generates a challenge to be signed by the CryptoAuth device.  This challenge is
* basically a random number that once signed can be verified by the host.
*/

int host_generate_challenge(void)
{
int ret = 0;

ret = atcacert_gen_challenge_hw(g_challenge);
if (ret != ATCACERT_E_SUCCESS) return ret;
atcab_printbin_label("HOST: Generated challenge:\r\n", g_challenge, sizeof(g_challenge));

return 0;
}

int atcacert_gen_challenge_hw(uint8_t challenge[32])
{
if (challenge == NULL)
{
return ATCACERT_E_BAD_PARAMS;
}

return atcab_random(challenge);
}

/** \brief Executes Random command, which generates a 32 byte random number
* from the CryptoAuth device.
*
* \param[out] rand_out 32 bytes of random data is returned here.
*
* \return ATCA_SUCCESS on success, otherwise an error code.
*/
ATCA_STATUS atcab_random(uint8_t *rand_out)
{
ATCAPacket packet;
ATCACommand ca_cmd = _gDevice->mCommands;
ATCA_STATUS status = ATCA_GEN_FAIL;

do
{
// build an random command
packet.param1 = RANDOM_SEED_UPDATE;
packet.param2 = 0x0000;

if ((status = atRandom(ca_cmd, &packet)) != ATCA_SUCCESS)
{
break;
}

if ((status = atca_execute_command(&packet, _gDevice)) != ATCA_SUCCESS)
{
break;
}

if (packet.data[ATCA_COUNT_IDX] != RANDOM_RSP_SIZE)
{
status = ATCA_RX_FAIL;
break;
}

if (rand_out)
{
memcpy(rand_out, &packet.data[ATCA_RSP_DATA_IDX], RANDOM_NUM_SIZE);
}
}
while (0);


return status;
}
  • 最终调用atcab_random()函数,生成32byte的随机数;

client_generate_response()

 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
int client_generate_response(void)
{
int ret = 0;

ret = atcacert_get_response(g_cert_def_2_device.private_key_slot, g_challenge, g_response);
if (ret != ATCACERT_E_SUCCESS) return ret;
atcab_printbin_label("CLIENT: Calculated response to host challenge:\r\n", g_response, sizeof(g_response));

return 0;
}

int atcacert_get_response(uint8_t device_private_key_slot,
const uint8_t challenge[32],
uint8_t response[64])
{
if (device_private_key_slot > 15 || challenge == NULL || response == NULL)
{
return ATCACERT_E_BAD_PARAMS;
}

return atcab_sign(device_private_key_slot, challenge, response);
}

/** \brief Executes Sign command, to sign a 32-byte external message using the
* private key in the specified slot. The message to be signed
* will be loaded into the Message Digest Buffer to the
* ATECC608A device or TempKey for other devices.
*
* \param[in] key_id Slot of the private key to be used to sign the
* message.
* \param[in] msg 32-byte message to be signed. Typically the SHA256
* hash of the full message.
* \param[out] signature Signature will be returned here. Format is R and S
* integers in big-endian format. 64 bytes for P256
* curve.
*
* \return ATCA_SUCCESS on success, otherwise an error code.
*/
ATCA_STATUS atcab_sign(uint16_t key_id, const uint8_t *msg, uint8_t *signature)
{
ATCA_STATUS status = ATCA_GEN_FAIL;
uint8_t nonce_target = NONCE_MODE_TARGET_TEMPKEY;
uint8_t sign_source = SIGN_MODE_SOURCE_TEMPKEY;

do
{
// Make sure RNG has updated its seed
if ((status = atcab_random(NULL)) != ATCA_SUCCESS)
{
break;
}

// Load message into device
if (_gDevice->mCommands->dt == ATECC608A)
{
// Use the Message Digest Buffer for the ATECC608A
nonce_target = NONCE_MODE_TARGET_MSGDIGBUF;
sign_source = SIGN_MODE_SOURCE_MSGDIGBUF;
}
if ((status = atcab_nonce_load(nonce_target, msg, 32)) != ATCA_SUCCESS)
{
break;
}

// Sign the message
if ((status = atcab_sign_base(SIGN_MODE_EXTERNAL | sign_source, key_id, signature)) != ATCA_SUCCESS)
{
break;
}
}
while (0);

return status;
}
  • 利用atcacert_get_response()函数,用设备的私钥对挑战用的随机数进行签名,将其存储在g_response;

host_verify_response()

 函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/** \brief This host role method verifies the signature of the challenge. This step insures that
* the device can prove it owns the private key associated with the public key of its certificate.
* Since the certificate is verified to be in a root of trust certificate chain, this ECDSA verification
* step makes sure the public key the device says it has is truly owned by the device and not a forgery.
*/
int host_verify_response(void)
{
int ret = 0;
uint8_t device_public_key[64];

ret = atcacert_get_subj_public_key(&g_cert_def_2_device, g_device_cert, g_device_cert_size, device_public_key);
if (ret != ATCACERT_E_SUCCESS) return ret;
atcab_printbin_label("HOST: Device public key from certificate:\r\n", device_public_key, sizeof(device_public_key));

ret = atcacert_verify_response_hw(device_public_key, g_challenge, g_response);
if (ret != ATCACERT_E_SUCCESS) return ret;
printf("HOST: Device response to challenge verified!\r\n");

return 0;
}
  • 调用atcacert_verify_response_hw()函数,利用设备公钥、挑战、回复进行ECDSA认证。

注意到,atcacert_verify_response_hw()的软件实现atcacert_verify_response_sw()也需要atcac_sw_ecdsa_verify_p256()函数。

MicroChip建议使用micro-ECC来完成该函数。

MicroECC移植

 基于MicroECC可以实现公钥私钥对生成、签名、认证等功能。注意Curve support selection选择uECC_SUPPORTS_secp256k1。

 利用uECC_verify()函数可以验证ATECC508A芯片。

用于设备验证的ATECC508A设置

 根据密码学基本知识文中介绍,当ATECC508A用于设备验证时,其需要具备如下要素:

  1. ATECC508A上存在一对关联的公钥和私钥,其中私钥无法用任何方式读取,而公钥允许被读取;
  2. ATECC508A的公钥以及其它信息(非必需)构成一组公开数据,经过哈希计算后得到digest;
  3. 使用signer私钥对该digest进行签名,并将签名保存在ATECC508A中,该签名允许读取;

 设备对ATECC508A进行验证的流程如下:

  1. signer的公钥需要预先保存在设备中;
  2. 设备利用signer的公钥、ATECC508A上用于签名的公开数据(包含ATECC508A的公钥)哈希值和签名进行验证,确认该签名是否有效。如果有效,则说明该ATECC508A的签名来自于signer;
  3. 如果签名确认有效,则需要进一步验证ATECC508A的私钥;当前已经可以获取ATECC508A的公钥,设备向ATECC508A发送一组随机数进行挑战,ATECC508A利用其私钥对该随机数进行签名,将其作为应答发回设备;
  4. 设备利用ATECC508A的的公钥、用于挑战的随机数以及ATECC508A的应答,确认是否有效。如果有效则说明该ATECC508A的公钥与私钥是关联的。则验证真实;

作为signer的ATECC508A设置

 当ATECC508A作为signer时,其需要具备如下要素:

  1. ATECC508A上存在一对关联的公钥和私钥,其中私钥无法用任何方式读取,而公钥允许被读取;
  2. ATECC508A的公钥以及其它信息(非必需)构成一组公开数据,经过哈希计算后得到digest;
  3. 使用CA私钥对该digest进行签名,并将签名保存在ATECC508A中,该签名允许读取;

实际上CA签名只用于信任链建立,如果将signer公钥存储在设备程序中,则步骤2和3可以省略。

以下流程基于省略步骤2和3的方式。

 利用ATECC508A作为signer对用于设备验证的ATECC508A进行签名,具体流程如下:

  1. 用于设备验证的ATECC508A生成一对相关联的私钥和公钥,其中私钥无法用任何方式读取,而公钥允许被读取;
  2. 用于设备验证的ATECC508A以及其它信息(非必需)构成一组公开数据,经过哈希计算后得到digest;
  3. signer对digest进行签名,并将签名写入到用于设备验证的ATECC508A中,改签名允许被读取;
  4. 锁定用于设备验证的ATECC508A私钥、公开数据、签名相关slot。
  1. 出于安全性考虑,signer的公钥也应该永远无法被读取,也就是不能以写入形式保存在ATECC508A中,要用生成方式;因为如果私钥是已知的,则必然存在泄露风险,最佳的方案就是永远无法获知的私钥;
  2. 同时,需要限定单个signer的签名生成次数,例如限定100次,防止signer丢失被重复使用。

node-auth-basic中ATECC508A配置分析

SlotConfig与KeyConfig配置

SlotConfig的配置如下(0-7):

bit name slot0(dev_pri) slot1 slot2(signer_pri) slot3 slot4(access_key) slot5 slot6 slot7(CA_pri)
all SlotConfig 0x208F 0x44C4 0x2087 0x44C4 0x0F8F 0x8F8F 0x8F9F 0x6483
15-12 WriteConfig 0010(Never) 0100(Encrypt) 0010(Never) 0100(Encrypt) 0000(Always) 1000(Never) 1000(Never) 0110(Encrypt)
11-8 WriteKey 0000 0100 0000 0100 1111 1111 1111 0100
7 IsSecret 1 1 1 1 1 1 1 1
6 EncryptRead 0 1 0 1 0 0 0 0
5 LimitedUse 0 0 0 0 0 0 0 0
4 NoMac 0 0 0 0 0 0 1 0
3-0 ReadKey 1111 0100 0111 0100 1111 1111 1111 0011
DeriveKeyConfig NoAuthRoll NotAllowed NoAuthRoll NotAllowed NotAllowed NotAllowed NotAllowed NoAuthRoll
ReadConfig Secret Encrypted Secret Encrypted Secret Secret Secret Secret

KeyConfig的配置如下(0-7):

bit name slot0 slot1 slot2 slot3 slot4 slot5 slot6 slot7
all KeyConfig 0x0033 0x001C 0x0013 0x001C 0x003C 0x001C 0x001C 0x0033
15-14 X509id 00 00 00 00 00 00 00 00
13 RFU 0 0 0 0 0 0 0 0
12 IntrusionDisable 0 0 0 0 0 0 0 0
11-8 AuthKey 0000 0000 0000 0000 0000 0000 0000 0000
7 ReqAuth 0 0 0 0 0 00 00 0
6 ReqRandom 0 0 0 0 0 0 0 0
5 Lockable 1 0 0 0 1 0 0 1
4-2 KeyType 100(P256) 111(NotECC) 100(P256) 111(NotECC) 111(NotECC) 111(NotECC) 111(NotECC) 100(P256)
1 PubInfo 1 0 1 0 0 0 0 1
0 Private 1 0 1 0 0 0 0 1

SlotConfig的配置如下(8-15):

bit name slot8 slot9 slotA slotB slotC slotD slotE slotF
all SlotConfig 0x44C4 0x44C4 0x0F0F 0x0F0F 0x0F0F 0x0F0F 0x0F0F 0x0F0F
15-12 WriteConfig 0100(Encrypt) 0100(Encrypt) 0000(Always) 0000(Always) 0000(Always) 0000(Always) 0000(Always) 0000(Always)
11-8 WriteKey 0100 0100 1111 1111 1111 1111 1111 1111
7 IsSecret 1 1 0 0 0 0 0 0
6 EncryptRead 1 1 0 0 0 0 0 0
5 LimitedUse 0 0 0 0 0 0 0 0
4 NoMac 0 0 0 0 0 0 0 0
3-0 ReadKey 0100 0100 1111 1111 1111 1111 1111 1111
DeriveKeyConfig NotAllowed NotAllowed NotAllowed NotAllowed NotAllowed NotAllowed NotAllowed NotAllowed
ReadConfig Encrypted Encrypted ClearText ClearText ClearText ClearText ClearText ClearText

KeyConfig的配置如下(8-15):

bit name slot8 slot9 slotA slotB slotC slotD slotE slotF
all KeyConfig 0x001C 0x001C 0x003C 0x003C 0x003C 0x003C 0x003C 0x003C
15-14 X509id 00 00 00 00 00 00 00 00
13 RFU 0 0 0 0 0 0 0 0
12 IntrusionDisable 0 0 0 0 0 0 0 0
11-8 AuthKey 0000 0000 0000 0000 0000 0000 0000 0000
7 ReqAuth 0 0 0 0 0 00 00 0
6 ReqRandom 0 0 0 0 0 0 0 0
5 Lockable 1 0 0 0 1 0 0 1
4-2 KeyType 100(P256) 111(NotECC) 100(P256) 111(NotECC) 111(NotECC) 111(NotECC) 111(NotECC) 100(P256)
1 PubInfo 1 0 1 0 0 0 0 1
0 Private 1 0 1 0 0 0 0 1

每个slot存储内容

slot number content write function public
0 device_private_key atcab_genkey(device_private_key_slot, device_public_key)
1
2 signer_private_key atcab_genkey(signer_private_key_slot, signer_public_key)
3
4 access_key atcab_write_zone(DEVZONE_DATA, access_key_slot, 0, 0, access_key, 32)
5
6
7 signer_ca_private_key atcab_priv_write(signer_ca_private_key_slot, g_signer_ca_private_key, 0, NULL) atcab_get_pubkey(signer_ca_private_key_slot, g_signer_ca_public_key)
8
9
A g_cert_def_2_device comp_cert_dev_loc
B ca_cert_def public_key_dev_loc
C ca_cert_def comp_cert_dev_loc
D
E
F
  • CA证书g_cert_def_2_device.ca_cert_def,即g_cert_def_1_signer定义中,public_key_dev_loc对应slot为11(is_genkey = 0),comp_cert_dev_loc对应slot为12(is_genkey = 0);
  • Signer证书g_cert_def_2_device定义中,public_key_dev_loc对应的slot为0(is_genkey = 1),comp_cert_dev_loc对应slot为10(is_genkey = 0);
文章目录
  1. 1. Features
  2. 2. Introduction
  3. 3. Device Organization
    1. 3.1. EEPROM Data Zone
    2. 3.2. EEPROM Configuration Zone
  4. 4. node-auth-basic代码分析
    1. 4.1. client_provision()
    2. 4.2. client_rebuild_certs()
    3. 4.3. host_verify_cert_chain()
    4. 4.4. host_generate_challenge()
    5. 4.5. client_generate_response()
    6. 4.6. host_verify_response()
  5. 5. MicroECC移植
  6. 6. 用于设备验证的ATECC508A设置
  7. 7. 作为signer的ATECC508A设置
  8. 8. node-auth-basic中ATECC508A配置分析
    1. 8.1. SlotConfig与KeyConfig配置
    2. 8.2. 每个slot存储内容
|