mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-13 03:13:41 +00:00
ateccx08: inttial implementation for ATECCx08
This working implementation for the ATECCx08 family of cryptgraphic processors has random number generation and other needed supporting functions. It also includes a sample of how to connect it to the Go crypto/rand package. More cryptographic functions await a future interation. Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
// Package ateccx08 provides a driver for the ATECCx08 I2C cryptographic co-processor.
|
||||
//
|
||||
// Datasheet: https://datasheet.octopart.com/ATSAMA5D27-WLSOM1-Microchip-datasheet-149595509.pdf
|
||||
package ateccx08 // import "tinygo.org/x/drivers/ateccx08"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
var (
|
||||
maxCommandTime = (200 + 50) * time.Millisecond
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWakeup = errors.New("error on wakeup")
|
||||
ErrInvalidCRCCheck = errors.New("invalid CRC check")
|
||||
ErrLockFailed = errors.New("locked failed")
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
}
|
||||
|
||||
// New returns ATECCx08 device for the provided I2C bus using default address.
|
||||
func New(i2c drivers.I2C) *Device {
|
||||
return &Device{
|
||||
bus: i2c,
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the ATECCx08 device.
|
||||
func (d *Device) Configure() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connected returns whether ATECCx08 has been found.
|
||||
func (d *Device) Connected() bool {
|
||||
if err := d.Wakeup(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
v, err := d.Version()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return (v == ATECC508 || v == ATECC608)
|
||||
}
|
||||
|
||||
// Wakeup the ATECC by trying to write something to address 0x00
|
||||
func (d *Device) Wakeup() error {
|
||||
d.bus.Tx(uint16(0x0), []byte{0x00}, nil)
|
||||
time.Sleep(1500 * time.Microsecond)
|
||||
d.bus.Tx(uint16(d.Address), []byte{0x00}, nil)
|
||||
time.Sleep(maxCommandTime)
|
||||
|
||||
var status [4]byte
|
||||
if err := d.readResponse(status[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status[0] != StatusAfterWake {
|
||||
return ErrWakeup
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sleep puts the ATECC to sleep.
|
||||
func (d *Device) Sleep() {
|
||||
d.bus.Tx(uint16(d.Address), []byte{0x01}, nil)
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// Idle puts the ATECC in idle mode.
|
||||
func (d *Device) Idle() {
|
||||
d.bus.Tx(uint16(d.Address), []byte{0x02}, nil)
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
type ATECCVersion uint16
|
||||
|
||||
func (at ATECCVersion) String() string {
|
||||
switch at {
|
||||
case ATECC508:
|
||||
return "ATECC508"
|
||||
case ATECC608:
|
||||
return "ATECC608"
|
||||
case ATECCNone:
|
||||
return "No ATECCx08"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns what version of ATECC is being used.
|
||||
// Either ATECC508, ATECC608, or ATECCNone.
|
||||
func (d *Device) Version() (ATECCVersion, error) {
|
||||
var version [4]byte
|
||||
d.Wakeup()
|
||||
defer d.Idle()
|
||||
|
||||
d.sendCommand(cmdInfo, 0x00, 0, nil)
|
||||
|
||||
time.Sleep(maxCommandTime)
|
||||
if err := d.readResponse(version[:]); err != nil {
|
||||
return ATECCNone, err
|
||||
}
|
||||
|
||||
return ATECCVersion(uint16(version[2])<<8 | uint16(version[3])&0xf000), nil
|
||||
}
|
||||
|
||||
// Random returns an array of 32 byte-sized random numbers.
|
||||
func (d *Device) Random() ([32]byte, error) {
|
||||
var random [32]byte
|
||||
d.Wakeup()
|
||||
defer d.Idle()
|
||||
|
||||
d.sendCommand(cmdRandom, 0x00, 0, nil)
|
||||
time.Sleep(23 * time.Millisecond)
|
||||
|
||||
err := d.readResponse(random[:])
|
||||
return random, err
|
||||
}
|
||||
|
||||
// Read reads from the device memory.
|
||||
func (d *Device) Read(zone, address int, data []byte) error {
|
||||
d.Wakeup()
|
||||
defer d.Idle()
|
||||
|
||||
d.sendCommand(cmdRead, byte(zone), uint16(address), nil)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
return d.readResponse(data)
|
||||
}
|
||||
|
||||
// IsLocked checks to see if the ATECC is locked.
|
||||
// Config zone (0) must be locked to generate random numbers.
|
||||
func (d *Device) IsLocked() bool {
|
||||
return d.IsZoneLocked(0)
|
||||
}
|
||||
|
||||
// IsZoneLocked checks to see if a specific zone in the ATECC is locked.
|
||||
func (d *Device) IsZoneLocked(zone int) bool {
|
||||
var config [4]byte
|
||||
|
||||
if zone < 0 || zone > 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch zone {
|
||||
case 0, 1:
|
||||
if err := d.Read(0, 0x15, config[:]); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// LockConfig
|
||||
loc := 3
|
||||
|
||||
// LockData
|
||||
if zone == 1 {
|
||||
loc = 2
|
||||
}
|
||||
|
||||
if config[loc] == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
default:
|
||||
if err := d.Read(0, 0x16, config[:]); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
slot := byte(zone<<2) | 2
|
||||
|
||||
if (config[0] & slot) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Lock locks a zone in the device.
|
||||
// Note that you cannot unlock a device zone once locked,
|
||||
// so make sure you know what you are doing!
|
||||
func (d *Device) Lock(zone int) error {
|
||||
var status [1]byte
|
||||
d.Wakeup()
|
||||
defer d.Idle()
|
||||
|
||||
d.sendCommand(cmdLock, byte(zone)|0x80, 0, nil)
|
||||
time.Sleep(32 * time.Millisecond)
|
||||
|
||||
d.readResponse(status[:])
|
||||
|
||||
if status[0] != 0 {
|
||||
return ErrLockFailed
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmdBuf [64]byte
|
||||
|
||||
func (d *Device) sendCommand(opcode, param1 byte, param2 uint16, data []byte) error {
|
||||
cmdBuf[0] = 0x03
|
||||
cmdBuf[1] = byte(8 + len(data) - 1)
|
||||
cmdBuf[2] = opcode
|
||||
cmdBuf[3] = param1
|
||||
cmdBuf[4] = byte(param2 & 0xff)
|
||||
cmdBuf[5] = byte(param2 >> 8)
|
||||
copy(cmdBuf[6:], data)
|
||||
|
||||
crc := crc16(cmdBuf[1 : 6+len(data)])
|
||||
cmdBuf[6+len(data)] = crc[0]
|
||||
cmdBuf[6+len(data)+1] = crc[1]
|
||||
|
||||
if err := d.bus.Tx(uint16(d.Address), cmdBuf[:6+len(data)+2], nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) readResponse(data []byte) error {
|
||||
var sz [1]byte
|
||||
if err := d.bus.Tx(uint16(d.Address), []byte{cmdAddress}, sz[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rx := make([]byte, sz[0])
|
||||
if err := d.bus.Tx(uint16(d.Address), []byte{cmdAddress}, rx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := len(rx) - 2
|
||||
payload := rx[:size]
|
||||
payloaddata := rx[1:size]
|
||||
payloadcrc := rx[size:]
|
||||
|
||||
crcCheck := crc16(payload)
|
||||
if !(crcCheck[0] == payloadcrc[0] &&
|
||||
crcCheck[1] == payloadcrc[1]) {
|
||||
return ErrInvalidCRCCheck
|
||||
}
|
||||
|
||||
copy(data, payloaddata)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// from https://github.com/usbarmory/armoryctl/blob/master/atecc608/atecc608.go#L104
|
||||
// thank you!
|
||||
package ateccx08
|
||||
|
||||
const (
|
||||
CRC16Poly uint16 = 0x8005
|
||||
)
|
||||
|
||||
func crc16(data []byte) []byte {
|
||||
var crc uint16
|
||||
|
||||
for i := 0; i < len(data); i++ {
|
||||
for shift := uint8(0x01); shift > 0x00; shift <<= 1 {
|
||||
// data and crc bits
|
||||
var d uint8
|
||||
var c uint8
|
||||
|
||||
if uint8(data[i])&uint8(shift) != 0 {
|
||||
d = 1
|
||||
}
|
||||
|
||||
c = uint8(crc >> 15)
|
||||
crc <<= 1
|
||||
|
||||
if d != c {
|
||||
crc ^= CRC16Poly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []byte{byte(crc & 0xff), byte(crc >> 8)}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package ateccx08
|
||||
|
||||
const (
|
||||
// Address is default I2C address.
|
||||
Address = 0x60
|
||||
)
|
||||
|
||||
const (
|
||||
ATECCNone = 0
|
||||
ATECC508 = 0x5000
|
||||
ATECC608 = 0x6000
|
||||
)
|
||||
|
||||
const (
|
||||
cmdAddress = 0x03
|
||||
cmdCounter = 0x24
|
||||
cmdGenKey = 0x40
|
||||
cmdInfo = 0x30
|
||||
cmdLock = 0x17
|
||||
cmdNonce = 0x16
|
||||
cmdRandom = 0x1B
|
||||
cmdSHA = 0x47
|
||||
cmdSign = 0x41
|
||||
cmdWrite = 0x12
|
||||
cmdRead = 0x02
|
||||
)
|
||||
|
||||
const (
|
||||
StatusSuccess = 0x00
|
||||
StatusMiscompare = 0x01
|
||||
StatusParseError = 0x03
|
||||
StatusECCFault = 0x05
|
||||
StatusSelfTestError = 0x07
|
||||
StatusHealthTestError = 0x08
|
||||
StatusExecutionError = 0x0f
|
||||
StatusAfterWake = 0x11
|
||||
StatusWatchdogExpire = 0xee
|
||||
StatusCRCError = 0xff
|
||||
)
|
||||
Reference in New Issue
Block a user