1 特性
1 出厂带有9字节的序列号,存储在EEPROM中。
2 可以产生高质量随机数,可以作为设备的加密协议的一部分。
3 支持标准的'挑战-应答'协议。
1.1 名词
1.MAC:message authentication code。消息认证码。
1.2 设备组织
包含两个区块,分为eeprom和sram。
eeprom有664字节,分为这些区:
1.数据区
有512字节,分为16个通用功能,只读或者可读写的每个部分有32字节的内容的块。
每个块可以用来存储keys,校正数据,型号,或者其他信息。每一个块会根据配置区的的内容,有不同的访问权限。
2.配置区
有88字节,包含序列号,id信息,以及数据区的访问权限。配置区图如下:
3 一次性编程区
有64字节,可以存储只读数据,或者单向信息。在锁住OTP区域前,需要使用标准的写命令。这里也是EEPROM的一部分。在出厂的时候,这里面的数据内容全部为1。也就是为0xffffffff。
1.3 设备上锁
有两种不同的锁:
1.锁配置区,这是通过设置LockConfig。
2.锁数据区和一次编程区,这是通过设置LockData。
上面锁的配置是在配置区,使用Lock命令来使能的。一旦上锁,就不能再开锁了。
1.4 SRAM
该芯片包含一个sram数组,可以用来存储输入命令或者输出结果,中间计算值,临时密钥等。临时密钥定义为TempKey,可以作为MAC,HMAC,CheckMAC,GenDig和DeriveKey命令的输入。也可以用来作为数据保护密钥。
1.4.1 TempKey
是一个存储寄存器,可以用来存储来自Nonce,GenDig,CheckMac或者SHA命令等临时密钥。
临时密钥存储寄存器的valid位在如下情况下,会被清零:
1.上电,sleep,电压过低,看门狗超时,篡改检测。在空闲模式下,TempKey的值不消失。
2.在执行除了Nonce或者GenDig命令后,不管是否执行成功,可以使用CheckMac命令清零。除非通信错误,例如crc错误。
3.在转换的过程中出错,或者执行了GenDig或者Nonce命令。
4.执行GenDig会替换前面的Nonce命令的输出,执行Nonce命令也会替换前面的GenDig命令的输出。
2 数据传输
2.1 I2C数据传输
注意:
在i2c写数据包的过程中,ATSHA204A将第二个字节的数据解释为word address,来表明数据包的功能,如下图所示:
2.2 I2C同步
同步过程的执行,需要执行以下步骤:
1.执行标准的i2c软件复位序列,如下:
a.start启动信号。
b.9个周期的SCL,SDA保持高电平。
c.另外一个启动信号。
d.停止信号。
发送完成以上内容之后,才可以发送读序列,如果同步已经完成,ATSHA204A就会给回应ACK。
在ATSHA204A响应了设备地址之后,系统应该复位内部地址计数器。可以通过发送word address 0x00(复位),紧随着停止条件。
2.如果设备不给响应,它有可能处在asleep状态。在这种条件下,系统应该发送完整的wake序列,然后在上升沿后等待Twhi时间。系统应该发送另外的读序列,如果同步完成,设备就会返回ACK信号。
3.如果设备仍然不回复ACK信号,它有可能处于busy状态,那么需要等待Texec时间,然后再发送读序列,这会给出响应。
3 验证类型
3.1 固定的挑战认证
3.2 随机挑战认证
该方式比固定挑战增加了可变信息的挑战数据。该方式可以让系统防御重放攻击方式。
3.3 独特的挑战认证
相比固定的挑战认证,只是在每次的请求中加上了一个特殊的挑战。该方式也能方式重放攻击。
3.4 多元化的密钥认证
多元化的密钥认证能让主机识别特定的器件。那么就可以使用访问列表的方法。
3.5 例子
固定和随机挑战:
这种方法,理解起来,加密思路是这样的:
ATSHA204A数据区有512字节,在做加密的时候,只使用512字节的(16片区)*(32)的一个片区,也就是32字节。在做密钥匹配的时候,主机存有密钥,所以自己知道位置。ATSHA204A本身就需要主机通知了,这会塞进I2C数据里面。
1.将密钥通过PC连接器写入ATSHA204A的数据区,总共512字节,然后将芯片锁住,我们只使用了有效的32字节的密钥区。
2.将该密钥写入程序中。
3.主机给从机发送有效的7字节随机码生成请求,然后从机给主机返回数据,有效数据为32字节,该数据称为挑战码。
4.主机将32位有效挑战码发送给从机,从机内部进行计算,返回结果给主机,这个称为响应,有效数据为32字节。
5.主机按照已知的密钥,计算出结果。
6.将4生成的结果和5的结果进行比较,共32位数据,相同则校验通过。否则,失败。
多元随机挑战
多元随机挑战,挑战-响应的次数比较多,列出如下:
4 命令格式
4.1 命令格式总分布图
在ATSHA204A接收到该block数据后,会转向busy状态并试图执行命令。在busy的时候,状态或者结果都是不能读取到的。
4.2 读操作
数据区:
若数据区锁住,该命令会返回错误。否则,对应的SlotConfig将会控制访问的值。
配置区:
该区的数据是永远可读的,不管LockConfig的值是什么。
OTP区:
若OTP区没有锁住,该命令会返回错误。一旦锁住,若OTP不在传统模式,所有的数据都是可读取的。若OTP是在传统的模式,那么只有4个字节的数据可读,为0或者1的地址会返回错误。
具体固定位置的指令图如下:
操作码需要为读,值为0x02。第一个参数是区域,分为3个部分。
关于zone encoding我再贴一次图:
5 在树莓派A20上驱动程序编写
5.1 硬件配置
$ cd /home/wityuan/Downloads/MarsBoard-A20-Linux-SDK-V1.1/tools/sunxi-tools/
$ vi sys_config.fex
添加项目:
[atsha204_para]
atsha204_used = 1
si2c_scl = port:PB20<1><default><default><1>
si2c_sda = port:PB21<1><default><default><1>
如下图所示:
上图将PB20,PB21配置为输出。
同时需要去掉本身自带的配置:
要修改为:
5.2 内核配置
在/linux-sunxi/drivers/char目录下,修改Kconfig文件,添加如下内容:
config ATSHA204
tristate "Atmel CryptoAuthentication ATSHA204A Device"
depends on I2C
default y
help
The Atmel CryptoAuthentication ATSHA204A is a full turnkey security device.
Access to the chip is through a standard I2C interface at speeds up to 1Mb/sec.
在Makefile中添加:
obj-$(CONFIG_ATSHA204) += atsha204.o
如下图所示:
然后,
$ make menuconfig
我们能找到配置信息:
在内核的根目录下的.config中能找到配置(这是内核自动添加上去的):
5.3 驱动程序编写
将驱动程序命名为atsha204.c,然后编辑内容,最后将文件放在linux-sunxi/drivers/char目录下,驱动程序内容为:
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <plat/sys_config.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/export.h>
#include <linux/module.h>
#define ATSHA204_DEBUG
#ifdef ATSHA204_DEBUG
#define atsha204_debug(fmt, ...) printk(KERN_INFO "[ATSHA204][BUG][%d]"fmt, __LINE__, ##__VA_ARGS__)
#else
#define atsha204_debug(fmt, ...)
#endif /* ATSHA204_DEBUG */
#define atsha204_error(fmt, ...) printk(KERN_INFO "[ATSHA204][ERR]"fmt"\n", ##__VA_ARGS__)
#define DRV_NAME "atsha204"
//! maximum size of command packet (CheckMac)
#define ATSHA204_CMD_SIZE_MAX ((u8) 84)
//!< maximum size of response packet
#define ATSHA204_RSP_SIZE_MAX ((u8) 35)
#define SLAVE_ADDR_DEFAULT (0xC8 >> 1)
#define ATSHA204_CMD_MAGIC 'S'
#define ATSHA204_CMD_GET_SLAVE_ADDR _IOR(ATSHA204_CMD_MAGIC, 0, u8)
#define ATSHA204_CMD_SET_SLAVE_ADDR _IOW(ATSHA204_CMD_MAGIC, 1, u8)
#define ATSHA204_CMD_WAKEUP _IOR(ATSHA204_CMD_MAGIC, 2, u8[4])
#define SCL_DELAY_US 5
#define SDA_DELAY_US (SCL_DELAY_US - 1)
#define IDLE_DELAY_US (SDA_DELAY_US / 2)
enum PinDirections {
PIN_IN,
PIN_OUT,
};
//SI2C管脚ID,与si2c_pin_name对应
enum SI2C_PinNameId {
SI2C_PIN_SCL,
SI2C_PIN_SDA,
};
#define SI2C_PIN_NAME_SCL "si2c_scl"
#define SI2C_PIN_NAME_SDA "si2c_sda"
static const u8 *si2c_pin_name[] = {SI2C_PIN_NAME_SCL, SI2C_PIN_NAME_SDA};
struct atsha204info {
u8 slave_addr;
unsigned int gpio_handle;
struct miscdevice misc;
};
struct atsha204_write_data {
u8 command;
u8 length;
u8 data[ATSHA204_CMD_SIZE_MAX];
};
static struct atsha204info atsha204_info = {.slave_addr = SLAVE_ADDR_DEFAULT};
/* Set pin direction -> out (mul_sel = 1), pin_data -> value */
static int atsha204_gpio_set_direction(const u32 p_handle, const enum SI2C_PinNameId pin_name_id, const enum PinDirections dire, int value)
{
int ret;
if ((p_handle == 0) || ((pin_name_id < SI2C_PIN_SCL) && (pin_name_id > SI2C_PIN_SDA))) {
atsha204_error("[%s]parameter invalid", __FUNCTION__);
return -1;
}
if (dire == PIN_IN) {
ret = gpio_set_one_pin_io_status(p_handle, PIN_IN, si2c_pin_name[pin_name_id]);
if (ret < 0) {
atsha204_error("gpio_set_one_pin_io_status error");
return -1;
}
} else {
ret = gpio_set_one_pin_io_status(p_handle, PIN_OUT, si2c_pin_name[pin_name_id]);
if (ret < 0) {
atsha204_error("gpio_set_one_pin_io_status error");
return -1;
}
ret = gpio_write_one_pin_value(p_handle, (value == 0) ? 0 : 1, si2c_pin_name[pin_name_id]);
if (ret < 0) {
atsha204_error("gpio_write_one_pin_value error");
return -1;
}
}
return 0;
}
/* Get gpio pin value */
static int atsha204_gpio_get_value(const u32 p_handle, const enum SI2C_PinNameId pin_name_id)
{
if ((p_handle == 0) || ((pin_name_id < SI2C_PIN_SCL) && (pin_name_id > SI2C_PIN_SDA))) {
atsha204_error("[%s]parameter invalid", __FUNCTION__);
return -1;
}
return gpio_read_one_pin_value(p_handle, si2c_pin_name[pin_name_id]);
}
/* Set pin value (output mode) */
static int atsha204_gpio_set_value(const u32 p_handle, const enum SI2C_PinNameId pin_name_id, int value)
{
if ((p_handle == 0) || ((pin_name_id < SI2C_PIN_SCL) && (pin_name_id > SI2C_PIN_SDA))) {
atsha204_error("[%s]parameter invalid", __FUNCTION__);
return -1;
}
return gpio_write_one_pin_value(p_handle, (value == 0) ? 0 : 1, si2c_pin_name[pin_name_id]);
}
#define SDA_IN() atsha204_gpio_set_direction(atsha204_info.gpio_handle, SI2C_PIN_SDA, PIN_IN, 0)
#define SDA_OUT() atsha204_gpio_set_direction(atsha204_info.gpio_handle, SI2C_PIN_SDA, PIN_OUT, 1)
#define SCL_SET(BitVal) atsha204_gpio_set_value(atsha204_info.gpio_handle, SI2C_PIN_SCL, BitVal)
#define SDA_SET(BitVal) atsha204_gpio_set_value(atsha204_info.gpio_handle, SI2C_PIN_SDA, BitVal)
#define SDA_GET() atsha204_gpio_get_value(atsha204_info.gpio_handle, SI2C_PIN_SDA)
/*
* @brief SI2C起始信号
*/
static void SI2C_Start(void)
{
SDA_OUT();
SDA_SET(1);
udelay(SDA_DELAY_US);
SCL_SET(1);
udelay(SCL_DELAY_US);
SDA_SET(0);
udelay(SDA_DELAY_US);
SCL_SET(0);
udelay(SCL_DELAY_US);
}
/*
* @brief SI2C结束信号
*/
static void SI2C_Stop(void)
{
SDA_OUT();
SDA_SET(0);
udelay(SDA_DELAY_US);
SCL_SET(1);
udelay(SCL_DELAY_US);
SDA_SET(1);
udelay(SDA_DELAY_US);
SCL_SET(0);
udelay(SCL_DELAY_US);
}
/*
* @brief SI2C应答信号
*/
static void SI2C_Ack(void)
{
SCL_SET(0);
udelay(SCL_DELAY_US);
SDA_OUT();
SDA_SET(1);
udelay(SDA_DELAY_US);
SDA_SET(0);
udelay(SDA_DELAY_US);
SCL_SET(1);
udelay(SCL_DELAY_US);
SCL_SET(0);
udelay(SCL_DELAY_US);
SDA_SET(1);
udelay(SDA_DELAY_US);
}
/*
* @brief SI2C非应答信号
*/
static void SI2C_NAck(void)
{
SCL_SET(0);
udelay(SCL_DELAY_US);
SDA_OUT();
SDA_SET(1);
udelay(SDA_DELAY_US);
SCL_SET(1);
udelay(SCL_DELAY_US);
SCL_SET(0);
udelay(SCL_DELAY_US);
}
/*
* @brief SI2C等待从设备响应信号
*/
static int SI2C_Wait_Ack(void)
{
uint8_t is_time_out = 0;
SCL_SET(0);
udelay(SCL_DELAY_US);
SDA_IN();
while (SDA_GET() != 0)
{
++is_time_out;
if (is_time_out > 250)
{
SI2C_Stop();
return -1;
}
}
SCL_SET(1);
udelay(SCL_DELAY_US);
SCL_SET(0);
udelay(SCL_DELAY_US);
return 0;
}
/*
* @brief SI2C Send Byte
*/
static int SI2C_Send_Byte(u8 TxByte)
{
u8 i = 0;
SDA_OUT();
SCL_SET(0);
udelay(SCL_DELAY_US);
for (i = 0; i < 8; ++i)
{
if (TxByte & 0x80)
{
SDA_SET(1);
}
else
{
SDA_SET(0);
}
TxByte <<= 1;
udelay(SDA_DELAY_US);
SCL_SET(1);
udelay(SCL_DELAY_US);
SCL_SET(0);
udelay(SCL_DELAY_US);
}
if (SI2C_Wait_Ack() != 0)
{
//此处必须有延时,不然会出错,至少延时1ms,由于只有出
//错才会进入此处(次数少),可以多延时一点,取5ms
// udelay(5000);
msleep(5000 / 1000);
SI2C_Stop();
return -1;
}
return 0;
}
/*
* @brief SI2C Recv Byte
*/
static u8 SI2C_Recv_Byte(void)
{
u8 i = 0;
u8 recv = 0;
SDA_IN();
for (i = 0; i < 8; ++i)
{
SCL_SET(0);
udelay(SCL_DELAY_US);
SCL_SET(1);
udelay(SCL_DELAY_US);
recv <<= 1;
if (SDA_GET())
{
++recv;
}
}
SI2C_Ack();
udelay(IDLE_DELAY_US);
return recv;
}
/*
*以下部分是与ATSHA204A相关的接口,它并非标准的I2C协议,需要重新实现读写
*/
/*
* @brief SI2C send many byte
*/
static int SI2C_Send_Bytes(u8 *TxBytes, u8 TxByteCount)
{
u8 i = 0;
u8 data = 0;
SDA_OUT();
SCL_SET(0);
udelay(SCL_DELAY_US);
for (i = 0; i < TxByteCount; ++i)
{
data = TxBytes[i];
if (SI2C_Send_Byte(data) != 0)
{
SI2C_Stop();
return -1;
}
}
return 0;
}
/*
* @brief SI2C recv many byte
*/
static void SI2C_Recv_Bytes(u8 *RxBytes, u8 RxByteCount)
{
u8 i = 0;
for (i = 0; i < RxByteCount - 1; ++i)
{
// udelay(IDLE_DELAY_US);
RxBytes[i] = SI2C_Recv_Byte();
}
RxBytes[i] = SI2C_Recv_Byte();
SI2C_NAck();
}
/*
* @brief SI2C SHA204A Wakeup
*/
static void SI2C_SHA204A_Wake(void)
{
SI2C_Start();
udelay(85);
SI2C_Send_Byte(0x00);
udelay(60);
SI2C_Stop();
// udelay(3800);
msleep(4);
}
/*
* @brief SI2C Read many byte from slave device
*/
static int SI2C_SHA204A_Read(u8 *RxBytes, u8 RxByteLength)
{
u8 count = 0;
SI2C_Start();
if (SI2C_Send_Byte(((atsha204_info.slave_addr) << 1) + 1) != 0)
{
// SI2C_Stop();
atsha204_error("1.send err");
return -1;
}
//read count byte
count = SI2C_Recv_Byte();
if ((count < 4) || (count > RxByteLength))
{
atsha204_error("2.[%d]count err", count);
return -1;
}
RxBytes[0] = count;
SI2C_Recv_Bytes(&RxBytes[1], count - 1);
SI2C_Stop();
return 0;
}
/*
* @brief SI2C Write many byte from slave device
*/
static int SI2C_SHA204A_Write(u8 WordAddress, u8 *TxBytes, u8 TxByteLength)
{
int ret = 0;
SI2C_Start();
if (SI2C_Send_Byte((atsha204_info.slave_addr) << 1) != 0)
{
SI2C_Stop();
return -1;
}
//write WordAddress(data type)
if (SI2C_Send_Bytes(&WordAddress, 1) != 0)
{
return -1;
}
if (TxByteLength == 0) {
// We are done for packets that are not commands (Sleep, Idle, Reset).
SI2C_Stop();
return 0;
}
ret = SI2C_Send_Bytes(TxBytes, TxByteLength);
SI2C_Stop();
return ret;
}
//唤醒atsha204芯片至唤醒模式
static int atsha204_wakeup(u8 *response)
{
u8 read_buf[4] = {0};
SI2C_SHA204A_Wake();
if (SI2C_SHA204A_Read(read_buf, sizeof(read_buf)) < 0) {
atsha204_error("SI2C_SHA204A_Read error");
return -1;
} else {
if (copy_to_user(response, read_buf, sizeof(read_buf)) != 0) {
atsha204_error("[%s]copy_to_user error", __FUNCTION__);
return -1;
}
return 0;
}
}
static int atsha204_open(struct inode *inode, struct file *file)
{
return 0;
}
static int atsha204_close(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t atsha204_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
u8 read_buf[ATSHA204_RSP_SIZE_MAX] = {0};
int len = 0;
if (count > ATSHA204_RSP_SIZE_MAX) {
return -1;
}
if (SI2C_SHA204A_Read(read_buf, count) < 0) {
atsha204_error("SI2C_SHA204A_Read error");
return -1;
} else {
len = read_buf[0];
if (copy_to_user(buf, read_buf, len) != 0) {
atsha204_error("[%s]copy_to_user error", __FUNCTION__);
return -1;
}
return len;
}
}
static ssize_t atsha204_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
struct atsha204_write_data data_tmp = {0};
u8 len = 0;
if (copy_from_user(&data_tmp, buf, sizeof(struct atsha204_write_data)) != 0) {
atsha204_error("copy_from_user error");
return -1;
}
atsha204_debug("command:%d, length:%d, count=%d\n", data_tmp.command, data_tmp.length, count);
len = (ATSHA204_CMD_SIZE_MAX > data_tmp.length) ? data_tmp.length : ATSHA204_CMD_SIZE_MAX;
//write data address(data type)
if (SI2C_SHA204A_Write(data_tmp.command, data_tmp.data, len) < 0) {
atsha204_error("SI2C_SHA204A_Write error");
return -1;
}
return len;
}
static long atsha204_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
atsha204_debug("cmd = %d, type:%c\n", cmd, _IOC_TYPE(cmd));
if (_IOC_TYPE(cmd) != ATSHA204_CMD_MAGIC)
return -1;
if (cmd == ATSHA204_CMD_GET_SLAVE_ADDR) {
if (copy_to_user((u8 *)arg, &atsha204_info.slave_addr, sizeof(u8)) != 0) {
atsha204_error("[%s]copy_to_user error", __FUNCTION__);
return -1;
}
return 0;
} else if (cmd == ATSHA204_CMD_SET_SLAVE_ADDR) {
if (copy_from_user(&atsha204_info.slave_addr, (u8 *)arg, sizeof(u8)) != 0) {
atsha204_error("copy_from_user error");
return -1;
}
return 0;
} else if (cmd == ATSHA204_CMD_WAKEUP) {
return atsha204_wakeup((u8 *)arg);
} else {
return -1;
}
}
static const struct file_operations atsha204_fops = {
.owner = THIS_MODULE,
.open = atsha204_open,
.release = atsha204_close,
.unlocked_ioctl = atsha204_ioctl,
.read = atsha204_read,
.write = atsha204_write,
};
static int __init atsha204_init(void)
{
int atsha204_used = 0;
int ret = 0;
struct miscdevice *misc = &atsha204_info.misc;
atsha204_debug("atsha204 driver init\n");
atsha204_debug("Addr:0x%X\n", atsha204_info.slave_addr);
ret = script_parser_fetch("atsha204_para", "atsha204_used", &atsha204_used, sizeof(atsha204_used)/sizeof(int));
if (ret) {
/* Not error - just info */
atsha204_error("can't find script data '[atsha204_para]' 'atsha204_used'\n");
return -1;
}
if (!atsha204_used) {
atsha204_error("atsha204_used is false. Skip atsha204 initialization\n");
return -1;
}
atsha204_info.gpio_handle= gpio_request_ex("atsha204_para", NULL);
if(!atsha204_info.gpio_handle) {
atsha204_error("atsha204_para request gpio fail!\n");
return -1;
}
if (gpio_set_one_pin_io_status(atsha204_info.gpio_handle, 1, "si2c_scl") < 0) {
atsha204_error("set si2c_scl gpio output fail!\n");
return -1;
}
if (gpio_set_one_pin_io_status(atsha204_info.gpio_handle, 1, "si2c_sda") < 0) {
atsha204_error("set si2c_scl gpio output fail!\n");
return -1;
}
//-----------------------------------
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = DRV_NAME;
misc->fops = &atsha204_fops;
ret = misc_register(misc);
if (ret) {
atsha204_error("Unable to register a misc device\n");
return -1;
}
atsha204_debug("Register a misc device Ok\n");
return ret;
}
static void __exit atsha204_exit(void)
{
struct miscdevice *misc = &atsha204_info.misc;
atsha204_debug("In %s\n", __FUNCTION__);
gpio_release(atsha204_info.gpio_handle, 2);
misc_deregister(misc);
return;
}
module_init(atsha204_init);
module_exit(atsha204_exit);
MODULE_AUTHOR("peace <whb_xxx@sina.com>");
MODULE_DESCRIPTION("Atmel CryptoAuthentication driver for atsha204A");
MODULE_LICENSE("GPL");
注册的是misc设备类,所以在开发板下能找到:
root@marsboard:~# ls /sys/class/misc/
ashmem cachefiles fuse log_system rfkill
atsha204 cpu_dma_latency log_events loop-control
autofs cuse log_main network_latency
binder device-mapper log_radio network_throughput
root@marsboard:~#
root@marsboard:~# ls /dev/atsha204
/dev/atsha204
root@marsboard:~#
root@marsboard:~# ./sha204_test
ATSHA204 Test
<open device ok
6>[ATSHA204][BUG][547]cmd = -2147200254, type:S
<6>[ATSHA204][ERR]1.send err
<6>[ATSHA204][ERR]SI2C_SHA204A_Read error
ioctl error: Operation not permitted
[TEST][ERR][53]sha204c_wakeup error
[ATSHA204][ERR][156]write error
[TEST][ERR][80]sha204c_sleep error
Error, Quit
root@marsboard:~#