diff --git a/README.md b/README.md index d2e663f..28c42d0 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 90 devices are supported. +The following 91 devices are supported. | Device Name | Interface Type | |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| @@ -63,6 +63,7 @@ The following 90 devices are supported. | [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI | | [APDS9960 Digital proximity, ambient light, RGB and gesture sensor](https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf) | I2C | | [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C | +| [ATECCx08 cryptographic processor](https://datasheet.octopart.com/ATSAMA5D27-WLSOM1-Microchip-datasheet-149595509.pdf) | I2C | | [AXP192 single Cell Li-Battery and Power System Management](https://github.com/m5stack/M5-Schematic/blob/master/Core/AXP192%20Datasheet_v1.1_en_draft_2211.pdf) | I2C | | [BBC micro:bit LED matrix](https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf) | GPIO | | [BH1750 ambient light sensor](https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf) | I2C | diff --git a/ateccx08/ateccx08.go b/ateccx08/ateccx08.go new file mode 100644 index 0000000..5edea82 --- /dev/null +++ b/ateccx08/ateccx08.go @@ -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 +} diff --git a/ateccx08/crc16.go b/ateccx08/crc16.go new file mode 100644 index 0000000..6c46bc3 --- /dev/null +++ b/ateccx08/crc16.go @@ -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)} +} diff --git a/ateccx08/registers.go b/ateccx08/registers.go new file mode 100644 index 0000000..4ea09b9 --- /dev/null +++ b/ateccx08/registers.go @@ -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 +) diff --git a/examples/ateccx08/lock/main.go b/examples/ateccx08/lock/main.go new file mode 100644 index 0000000..421be81 --- /dev/null +++ b/examples/ateccx08/lock/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "machine" + + "encoding/hex" + "time" + + "tinygo.org/x/drivers/ateccx08" +) + +func main() { + time.Sleep(5 * time.Second) + println("Looking for ATECCx08...") + + machine.I2C0.Configure(machine.I2CConfig{}) + + atecc := ateccx08.New(machine.I2C0) + atecc.Configure() + + if !atecc.Connected() { + for { + println("could not connect to ATECCx08") + time.Sleep(time.Second) + } + } + + version, _ := atecc.Version() + + println(version.String(), "started") + + if !atecc.IsLocked() { + for i := 10; i > 0; i-- { + println(version.String(), "is not locked. Locking in", i, "seconds...") + time.Sleep(time.Second) + } + + // locks the Configuration zone... PERMANENTLY! + atecc.Lock(0) + } + + println(version.String(), "locked.") + + for { + data, err := atecc.Random() + if err != nil { + println(err) + } + + println(hex.EncodeToString(data[:])) + time.Sleep(500 * time.Millisecond) + } +} diff --git a/examples/ateccx08/random/main.go b/examples/ateccx08/random/main.go new file mode 100644 index 0000000..49d9d77 --- /dev/null +++ b/examples/ateccx08/random/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "machine" + + "crypto/rand" + "encoding/hex" + "time" + + "tinygo.org/x/drivers/ateccx08" +) + +var atecc *ateccx08.Device + +func main() { + time.Sleep(5 * time.Second) + println("Looking for ATECCx08...") + + machine.I2C0.Configure(machine.I2CConfig{}) + + atecc = ateccx08.New(machine.I2C0) + atecc.Configure() + + if !atecc.Connected() { + for { + println("could not connect to ATECCx08") + time.Sleep(time.Second) + } + } + + version, _ := atecc.Version() + + println(version.String(), "started") + + if !atecc.IsLocked() { + for { + println(version.String(), "is not locked. Random numbers will not actually be random.") + time.Sleep(time.Second) + } + } + + var result [13]byte + for { + rand.Read(result[:]) + encodedString := hex.EncodeToString(result[:]) + println(encodedString) + time.Sleep(500 * time.Millisecond) + } +} diff --git a/examples/ateccx08/random/rand.go b/examples/ateccx08/random/rand.go new file mode 100644 index 0000000..81f0978 --- /dev/null +++ b/examples/ateccx08/random/rand.go @@ -0,0 +1,52 @@ +// connects the Go crypto/rand package to the random number generation +// on the ATECCx08 cryptographic processor. +package main + +import ( + "crypto/rand" + "errors" +) + +var ( + errNoATECC = errors.New("no ATECCx08") +) + +func init() { + rand.Reader = &reader{} +} + +type reader struct{} + +func (r *reader) Read(b []byte) (n int, err error) { + if len(b) == 0 { + return + } + + if atecc == nil { + return 0, errNoATECC + } + + if !atecc.IsLocked() { + panic("ATECCx08 is not locked and cannot produce random numbers!") + } + + rnds, err := atecc.Random() + if err != nil { + return 0, err + } + + for i := 0; i < len(b); i += 32 { + if i+32 > len(b) { + copy(b[i:], rnds[:(len(b)-i)]) + break + } + + copy(b[i:], rnds[:]) + rnds, err = atecc.Random() + if err != nil { + return 0, err + } + } + + return len(b), nil +}