ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析

ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析

  • APP层函数编写及源码分析
  • At508_GetSernum()
  • GetSernum抓包记录
  • At508_GetRandom( )
  • GetRandom抓包记录

经过前两章节,CryptoAuthlib库已经成功适配在代码中了,下面简单测试一下API函数,并分析下I2C通讯流程:


APP层函数编写及源码分析

利用lib/atca_basic.c中的函数,简单封装成业务所需API

1、`At508_GetSernum( uint8_t *sernum )

函数功能比较简单,只是读取508A串号,并打印出来:
其中SerialNum是全局变量,用来存储串号(9byte); atcab_init,在经常调用508情况下,只需要init一次,并不用release

ATCAIfaceCfg *gCfg = &cfg_ateccx08a_i2c_default;
uint8_t SerialNum[ATCA_SERIAL_NUM_SIZE];

ATCA_STATUS At508_GetSernum( uint8_t *sernum )
{
	ATCA_STATUS status;
	atcab_init( gCfg );
	status = atcab_read_serial_number( sernum );
	atcab_release();
    char displayStr[ATCA_SERIAL_NUM_SIZE*3];
    int displen = sizeof(displayStr);
			       atcab_bin2hex(sernum,ATCA_SERIAL_NUM_SIZE,displayStr,&displen);
    atcab_printbin_label((const uint8_t*)"\r\n Serial Number from atcab is: ",sernum, ATCA_SERIAL_NUM_SIZE);
	return status;
}

而结构体指针gCfg 是ATCAIfaceCfg 类型,其实就是前几节所说的Iface对象,定义如下:

typedef struct {

	ATCAIfaceType iface_type;       // active iface - how to interpret the union below
	ATCADeviceType devtype;         // explicit device type

	union {                         // each instance of an iface cfg defines a single type of interface
		struct ATCAI2C {
			uint8_t slave_address;  // 8-bit slave address
			uint8_t bus;            // logical i2c bus number, 0-based - HAL will map this to a pin pair for SDA SCL
			uint32_t baud;          // typically 400000
		} atcai2c;

		struct ATCASWI {
			uint8_t bus;        // logical SWI bus - HAL will map this to a pin	or uart port
		} atcaswi;

		struct ATCAUART {
			int port;           // logic port number
			uint32_t baud;      // typically 115200
			uint8_t wordsize;   // usually 8
			uint8_t parity;     // 0 == even, 1 == odd, 2 == none
			uint8_t stopbits;   // 0,1,2
		} atcauart;

		struct ATCAHID {
			int idx;                // HID enumeration index
			uint32_t vid;           // Vendor ID of kit (0x03EB for CK101)
			uint32_t pid;           // Product ID of kit (0x2312 for CK101)
			uint32_t packetsize;    // Size of the USB packet
			uint8_t guid[16];       // The GUID for this HID device
		} atcahid;

	};
	uint16_t wake_delay;    // microseconds of tWHI + tWLO which varies based on chip type
	int rx_retries;         // the number of retries to attempt for receiving bytes
	void     *cfg_data;     // opaque data used by HAL in device discovery
} ATCAIfaceCfg;

gCfg 被初始化为:cfg_ateccx08a_i2c_default,既默认的508 I2C配置。可以看出,508芯片默认I2C地址都是0xC0(默认地址存在ConfigZone,可更改),波特率为400k。

ATCAIfaceCfg cfg_ateccx08a_i2c_default = {
	.iface_type				= ATCA_I2C_IFACE,
	.devtype				= ATECC508A,
	.atcai2c.slave_address	= 0xC0,
	.atcai2c.bus			= 2,
	.atcai2c.baud			= 400000,
	//.atcai2c.baud = 100000,
	.wake_delay				= 800,
	.rx_retries				= 20
};

可以看出,At508_GetSernum是调用了508API:atcab_read_serial_number()函数实现了读取串号,其实现如下:

ATCA_STATUS atcab_read_serial_number(uint8_t* serial_number)
{
	// read config zone bytes 0-3 and 4-7, concatenate the two bits into serial_number
	uint8_t status = ATCA_GEN_FAIL;
	uint8_t bytes_read[ATCA_BLOCK_SIZE];
	uint8_t block = 0;
	uint8_t cpyIndex = 0;
	uint8_t offset = 0;

	do {
		memset(serial_number, 0x00, ATCA_SERIAL_NUM_SIZE);
		// Read first 32 byte block.  Copy the bytes into the config_data buffer
		block = 0;
		offset = 0;
		if ( (status = atcab_read_zone(ATCA_ZONE_CONFIG, 0, block, offset, bytes_read, ATCA_WORD_SIZE)) != ATCA_SUCCESS )
			break;

		memcpy(&serial_number[cpyIndex], bytes_read, ATCA_WORD_SIZE);
		cpyIndex += ATCA_WORD_SIZE;

		block = 0;
		offset = 2;
		if ( (status = atcab_read_zone(ATCA_ZONE_CONFIG, 0, block, offset, bytes_read, ATCA_WORD_SIZE)) != ATCA_SUCCESS )
			break;

		memcpy(&serial_number[cpyIndex], bytes_read, ATCA_WORD_SIZE);
	_atcab_exit();
	return status;
}

atcab_read_serial_number()中,其实是通过atcab_read_zone()读取了508A芯片中配置区所存储的串号,然后返回而已。atcab_read_zone()里面,其实就是实现了508A Read命令消息封装,以及发送。如下代码:

ATCA_STATUS atcab_read_zone(uint8_t zone, uint8_t slot, uint8_t block, uint8_t offset, uint8_t *data, uint8_t len)
{
	ATCA_STATUS status = ATCA_SUCCESS;
	ATCAPacket packet;
	uint16_t addr;
	uint16_t execution_time = 0;

	do {
		// Check the input parameters
		if (data == NULL)
			return ATCA_BAD_PARAM;
		if ( len != 4 && len != 32 )
			return ATCA_BAD_PARAM;
		// The get address function checks the remaining variables
		if ( (status = atcab_get_addr(zone, slot, block, offset, &addr)) != ATCA_SUCCESS )
			break;
		// If there are 32 bytes to write, then xor the bit into the mode
		if (len == ATCA_BLOCK_SIZE)
			zone = zone | ATCA_ZONE_READWRITE_32;
		// build a read command
		packet.param1 = zone;
		packet.param2 = addr;
		if ( (status = atRead( _gCommandObj, &packet )) != ATCA_SUCCESS )
			break;
		execution_time = atGetExecTime( _gCommandObj, CMD_READMEM);
		if ( (status = atcab_wakeup()) != ATCA_SUCCESS ) break;
		// send the command
		if ( (status = atsend( _gIface, (uint8_t*)&packet, packet.txsize )) != ATCA_SUCCESS )
			break;
		// delay the appropriate amount of time for command to execute
		atca_delay_ms(execution_time);
		// receive the response
		if ( (status = atreceive( _gIface, packet.data, &(packet.rxsize) )) != ATCA_SUCCESS )
			break;
		// Check response size
		if (packet.rxsize < 4) {
			if (packet.rxsize > 0)
				status = ATCA_RX_FAIL;
			else
				status = ATCA_RX_NO_RESPONSE;
			break;
		}
		if ( (status = isATCAError(packet.data)) != ATCA_SUCCESS )
			break;
		memcpy( data, &packet.data[1], len );
	} while (0);
	_atcab_exit();
	return status;
}

atcab_read_zone()首先会根据传入参数(所读取的zone、slot),通过atRead组建 read命令,存储在packet中;之后通过atcab_wakeup()命令唤醒508A,也起到了查找设备的作用,atcab_wakeup()会调用atwake(),最终还是通过Hal层的hal_i2c_wake()函数,执行wake操作。

ATCA_STATUS hal_i2c_wake(ATCAIface iface)
{
	ATCAIfaceCfg *cfg = atgetifacecfg(iface);
	ATCA_STATUS status = ATCA_WAKE_FAILED;
	int bus     = cfg->atcai2c.bus;
	uint8_t response[4] = { 0x00, 0x00, 0x00, 0x00 };
	uint8_t expected_response[4] = { 0x04, 0x11, 0x33, 0x43 };

	i2c_set_pin(i2c_hal_data[bus]->pin_sda, i2c_hal_data[bus]->pin_scl);

	i2c_send_wake_token();
	atca_delay_us(cfg->wake_delay);
    uint16_t len = 4;
	//! Receive Wake Response
	//status = hal_i2c_receive(iface, response, sizeof(response));
    status = hal_i2c_receive(iface, response, &len);
	if (status == ATCA_SUCCESS) {
		//! Compare response with expected_response
		if (memcmp(response, expected_response, 4) != 0)
			status = ATCA_WAKE_FAILED;
	}

	return status;
}

hal_i2c_wake()中,通过I2C驱动向设备发送了一串为0的数,而根据508A Datasheet,508A接受到00会返回一条固定的数据:0x04, 0x11, 0x33, 0x43 ,因此hal_i2c_wake()就是发送00然后接受数据,判断是否为上述数据,如果是,则唤醒成功。。同理,atcab_read_zone()在wakeup后,通过atsend()发送组装好的packet数据,也是同样的流程,只不过底层调用的是hal_i2c_send()罢了。
–这些步骤也对应了上一节的API访问底层I2C驱动的逻辑结构关系。

GetSernum抓包记录:

通过逻辑分析仪连接到MCU与508A通讯的I2C总线上,执行At508_GetSernum()就可以看到如下抓包记录:(遵循Datasheet命令格式)

I2C发送唤醒:MCU向0xC0地址设备发送wakeup数据帧,(图片看不清可右键在新页面打开观看。)
ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析_第1张图片

I2C接收唤醒:508A返回的唤醒相应消息

ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析_第2张图片

I2C发送:获取串号SerialNum命令:
ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析_第3张图片

I2C接收串号:508A返回串号数据(该芯片串号为 0123…)
ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析_第4张图片

总体效果图:

ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析_第5张图片


2、At508_GetRandom( uint8_t *randomnum)

与GetSN类似,GetRandom调用了atcab_random()实现随机数获取,其中RandomNum是一个508A返回的32Byte随机数的全局变量,atcab_bin2hex只是打印输出需要。

uint8_t RandomNum[RANDOM_RSP_SIZE];
ATCA_STATUS At508_GetRandom( uint8_t *randomnum)
{
	ATCA_STATUS status = ATCA_GEN_FAIL;
    char displayStr[RANDOM_RSP_SIZE*3];
    int displen = sizeof(displayStr);

	atcab_init( gCfg );
	status = atcab_random( randomnum );
	atcab_release();
	atcab_bin2hex(randomnum,32,displayStr,&displen);
    atcab_printbin_label((const uint8_t*)"\r\n Random Number is: ",randomnum, RANDOM_RSP_SIZE);

    return status;
}

官方API函数atcab_random()与上面atcab_read_zone()如出一辙,也是先wakeup,之后atsend发送组装的命令,然后atreceive等待接收508返回的数据。

ATCA_STATUS atcab_random(uint8_t *rand_out)
{
	ATCA_STATUS status = ATCA_GEN_FAIL;
	ATCAPacket packet;
	uint16_t execution_time = 0;
	if ( !_gDevice )
		return ATCA_GEN_FAIL;
	// build an random command
	packet.param1 = RANDOM_SEED_UPDATE;
    //packet.param1 = RANDOM_NO_SEED_UPDATE;
	packet.param2 = 0x0000;
	status = atRandom( _gCommandObj, &packet );
	execution_time = atGetExecTime( _gCommandObj, CMD_RANDOM);
	do {
		if ( (status = atcab_wakeup()) != ATCA_SUCCESS )
			break;
		// send the command
		if ( (status = atsend( _gIface, (uint8_t*)&packet, packet.txsize )) != ATCA_SUCCESS)
			break;
		// delay the appropriate amount of time for command to execute
		atca_delay_ms(execution_time);

		// receive the response
		if ( (status = atreceive( _gIface, packet.data, &packet.rxsize)) != ATCA_SUCCESS)
			break;
		// Check response size
		if (packet.rxsize < 4) {
			if (packet.rxsize > 0)
				status = ATCA_RX_FAIL;
			else
				status = ATCA_RX_NO_RESPONSE;
			break;
		}
		if ( (status = isATCAError(packet.data)) != ATCA_SUCCESS )
			break;
		memcpy( rand_out, &packet.data[1], 32 );  // data[0] is the length byte of the response
	} while (0);

	_atcab_exit();
	return status;
}

####GetRandom抓包记录:
–>wakeup省略。以下是508A返回:

ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析_第6张图片

注意:508A拿到手是需要先手动config,并且Lock的(官方叫做Provision)。如果不进行该步骤,既没有Lock配置区,很多功能是不能使用,例如GetRandom,如果ConfigZone和DataZone没有被Lock,就会返回FFFF0000FFFF00…只有Lock后才能正确产生随机数。
–>然而ConfigZone Lock后就是一次性的,无法再解锁,所以下节会介绍如何根据需求正确配置508的ConfigZone以及DataZOne,既如何实现Provison流程。


我之前记录的读写508 某个Slot的数据,GetSN、GetRandom的截图(数据只是测试所用):
(slot是508A存储的一个单元区,可以放密钥、证书等其他数据,下节会介绍)
ATECC508A芯片开发笔记(三):获取508A串号、随机数源码及I2C抓包分析_第7张图片

##欢迎转载,Howie原创作品,本文地址:
http://write.blog.csdn.net/mdeditor#!postId=75208753

##谢谢

你可能感兴趣的:(网络安全-ATECC508A)