mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-13 03:13:41 +00:00
68d4991da1
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>
259 lines
5.1 KiB
Go
259 lines
5.1 KiB
Go
// 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
|
|
}
|