diff --git a/examples/ECCX08AES/ECCX08AES.ino b/examples/ECCX08AES/ECCX08AES.ino new file mode 100644 index 0000000..024e54c --- /dev/null +++ b/examples/ECCX08AES/ECCX08AES.ino @@ -0,0 +1,81 @@ +/* + ECCX08 AES-GCM + + This sketch uses the ECC608 to compute + the AES_128_GCM encryption for some data + + This sketch assumes TempKey has been set + (ECDH sketch will do this) + and derives a deviceID from the public key + corresponding to the key in slot 0. + +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + if (!ECCX08.begin()) { + Serial.println("Failed to communicate with ECC508/ECC608!"); + while (1); + } + + if (!ECCX08.locked()) { + Serial.println("The ECC508/ECC608 is not locked!"); + while (1); + } + + byte devicePubKey[64]; + if (!ECCX08.generatePublicKey(0, devicePubKey)){ + Serial.println("Failed to generate private key."); + } else { + Serial.println("Device Public key:"); + printHex(devicePubKey, 64); + } + + byte ad[20] = {0x14}; + uint64_t adLength = (sizeof(ad)); + Serial.print("AD: "); + printHex(ad, adLength); + byte pt[40] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28}; + uint64_t ptLength = (sizeof(pt)); + Serial.print("PT: "); + printHex(pt, ptLength); + + byte IV[12]; + byte ct[40]; + byte tag[16]; + if (!ECCX08.AESEncrypt(IV, ad, pt, ct, tag, adLength, ptLength)){ + Serial.println("Failed to encrypt."); + } else { + Serial.print("IV: "); + printHex(IV, 12); + Serial.print("CT: "); + printHex(ct, ptLength); + Serial.print("tag: "); + printHex(tag, 16); + } + + byte ptDecrypted[40]; + if (!ECCX08.AESDecrypt(IV, ad, ptDecrypted, ct, tag, adLength, ptLength)){ + Serial.println("Failed to decrypt."); + } else { + Serial.print("PT: "); + printHex(ptDecrypted, ptLength); + } +} + +void loop() { +} + +void printHex(uint8_t *data, uint8_t length) { + char tmp[16]; + for (int i = 0; i < length; i++) { + sprintf(tmp, "%.2X", data[i]); + Serial.print(tmp); + Serial.print(" "); + } + Serial.println(); +} diff --git a/examples/ECCX08ECDH/ECCX08ECDH.ino b/examples/ECCX08ECDH/ECCX08ECDH.ino new file mode 100644 index 0000000..d35698f --- /dev/null +++ b/examples/ECCX08ECDH/ECCX08ECDH.ino @@ -0,0 +1,75 @@ +/* + ECCX08 ECDH + + This sketch uses the ECC608 to compute the ECDH shared secret + for a ECCX08 private key and a public key. + +*/ + +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + if (!ECCX08.begin()) { + Serial.println("Failed to communicate with ECC508/ECC608!"); + while (1); + } + + if (!ECCX08.locked()) { + Serial.println("The ECC508/ECC608 is not locked!"); + while (1); + } + + int privateKeySlot = 2; + int counterpartyKeySlot = 3; + + byte devicePubKey[64]; + if (!ECCX08.generatePrivateKey(privateKeySlot, devicePubKey)){ + Serial.println("Failed to generate private key."); + } else { + Serial.println("Device Public key:"); + printHex(devicePubKey, 64); + } + + byte counterPartyPubKey[64]; + // Yes, this function actually returns a public key. + if (!ECCX08.generatePrivateKey(counterpartyKeySlot, counterPartyPubKey)){ + Serial.println("Failed to generate public key."); + } else { + Serial.println("Counterparty Public key:"); + printHex(counterPartyPubKey, 64); + } + + byte sharedSecret[32]; + if (!ECCX08.ecdh(privateKeySlot, ECDH_MODE_OUTPUT, counterPartyPubKey, sharedSecret)) { + Serial.println("The ecdh function failed!"); + while (1); + } + + Serial.print("Shared secret = "); + printHex(sharedSecret, 32); + + byte output[1]; + if (!ECCX08.ecdh(privateKeySlot, ECDH_MODE_TEMPKEY, counterPartyPubKey, output)) { + Serial.println("The ecdh function failed!"); + while (1); + } + + Serial.print("Return code = "); + printHex(output, 1); +} + +void loop() { +} + +void printHex(uint8_t *data, uint8_t length) { + char tmp[16]; + for (int i = 0; i < length; i++) { + sprintf(tmp, "%.2X", data[i]); + Serial.print(tmp); + Serial.print(" "); + } + Serial.println(); +} diff --git a/src/ECCX08.cpp b/src/ECCX08.cpp index 8bb1a9a..a29af7d 100644 --- a/src/ECCX08.cpp +++ b/src/ECCX08.cpp @@ -326,6 +326,476 @@ int ECCX08Class::endSHA256(const byte data[], int length, byte result[]) return 1; } +int ECCX08Class::ecdh(int slot, byte mode, const byte pubKeyXandY[], byte output[]) +{ + if (!wakeup()) { + return 0; + } + + if (!sendCommand(0x43, mode, slot, pubKeyXandY, 64)) { + return 0; + } + + delay(55); + + if (mode == ECDH_MODE_OUTPUT) { + if (!receiveResponse(output, 32)) { + return 0; + } + } else if (mode == ECDH_MODE_TEMPKEY) { + if (!receiveResponse(output, 1)) { + return 0; + } + } + + delay(1); + idle(); + + return 1; +} + +/** \brief AES_GCM encryption function, see + * NIST Special Publication 800-38D + * 7.1, using TempKey. + * + * \param[out] IV Initialization vector + * (12 bytes) + * \param[in] ad Associated data + * \param[in] pt Plaintext + * \param[out] ct Ciphertext + * \param[out] tag Authentication tag + * (16 bytes) + * \param[in] adLength The length of ad + * \param[in] ptLength The length of pt + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ptLength) +{ + byte H[16] = {0x00}; + if (!AESBlockEncrypt(H)){ + Serial.println("AESEncrypt: failed to compute H."); + return 0; + } + + byte J0[16] = {0x00}; + if (!AESGenIV(IV)){ + Serial.println("AESEncrypt: failed to generate IV."); + return 0; + } + memcpy(J0, IV, 12); + J0[15] = 0x01; + + byte counterBlock[16]; + memcpy(counterBlock, J0, 16); + if (!AESIncrementBlock(counterBlock)){ + Serial.println("AESEncrypt: failed to increment counter block."); + return 0; + } + + if (!AESGCTR(counterBlock, pt, ct, ptLength)){ + Serial.println("AESEncrypt: failed to encrypt."); + return 0; + } + + int adPad = (-adLength) % 16; + int ctPad = (-ptLength) % 16; + + byte S[16]; + uint64_t inputLength = adLength+adPad+ptLength+ctPad+16; + byte input[inputLength]; + memcpy(input, ad, adLength); + memset(input+adLength, 0, adPad); + memcpy(input+adLength+adPad, ct, ptLength); + memset(input+adLength+adPad+ptLength, 0, ctPad); + // Device is little endian. + // GCM specification requires big endian representation + // of bit length. + // Hence we multiply by 8 and + // reverse the byte order of adLength and ptLength. + for (int i=0; i<8; i++){ + input[adLength+adPad+ptLength+ctPad+i] = (adLength*8 >> (56-8*i)) & 0xFF; + input[adLength+adPad+ptLength+ctPad+8+i] = (ptLength*8 >> (56-8*i)) & 0xFF; + } + + if (!AESGHASH(H, input, S, inputLength)){ + Serial.println("AESEncrypt: failed to compute GHASH."); + return 0; + } + + if (!AESGCTR(J0, S, tag, 16)){ + Serial.println("AESEncrypt: failed to compute tag."); + return 0; + } + + return 1; +} + +/** \brief AES_GCM decryption function, see + * NIST Special Publication 800-38D + * 7.2, using TempKey. + * + * \param[in] IV Initialization vector + * (12 bytes) + * \param[in] ad Associated data + * \param[out] pt Plaintext + * \param[in] ct Ciphertext + * \param[in] tag Authentication tag + * (16 bytes) + * \param[in] adLength The length of ad + * \param[in] ctLength The length of ct + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ctLength) +{ + uint64_t maxLength = 1ull << 36; + if (adLength >= maxLength || ctLength >= maxLength){ + return 0; + } + + byte H[16] = {0x00}; + if (!AESBlockEncrypt(H)){ + return 0; + } + + byte J0[16] = {0x00}; + memcpy(J0, IV, 12); + J0[15] = 0x01; + + int adPad = (-adLength) % 16; + int ctPad = (-ctLength) % 16; + + byte S[16]; + uint64_t inputLength = adLength+adPad+ctLength+ctPad+16; + byte input[inputLength]; + memcpy(input, ad, adLength); + memset(input+adLength, 0, adPad); + memcpy(input+adLength+adPad, ct, ctLength); + memset(input+adLength+adPad+ctLength, 0, ctPad); + // Device is little endian. + // GCM specification requires big endian representation + // of bit length. + // Hence we multiply by 8 and + // reverse the byte order of adLength and ptLength. + for (int i=0; i<8; i++){ + input[adLength+adPad+ctLength+ctPad+i] = (adLength*8 >> (56-8*i)) & 0xFF; + input[adLength+adPad+ctLength+ctPad+8+i] = (ctLength*8 >> (56-8*i)) & 0xFF; + } + + if (!AESGHASH(H, input, S, inputLength)){ + return 0; + } + + byte tagComputed[16]; + if (!AESGCTR(J0, S, tagComputed, 16)){ + return 0; + } + + uint8_t equalBytes=0; + for (int i=0; i<16; i++){ + equalBytes += (tag[i]==tagComputed[i]); + } + if (equalBytes!=16){ + // tag mismatch + return 0; + } + + byte counterBlock[16]; + memcpy(counterBlock, J0, 16); + if (!AESIncrementBlock(counterBlock)){ + return 0; + } + + if (!AESGCTR(counterBlock, ct, pt, ctLength)){ + return 0; + } + + return 1; +} + + +/** \brief GCTR function, see + * NIST Special Publication 800-38D + * 6.5 + * + * \param[in,out] counterBlock The initial counter block + * (16 bytes). + * \param[in] input The input bit string + * \param[out] output The output bit string + * \param[in] inputLength The length of the input + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESGCTR(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength) +{ + if(inputLength == 0){ + return 1; + } + int remainder = inputLength % 16; + int n = inputLength / 16 + (remainder != 0); + + int i; + for (i=0; i= 4) { + Serial.println("AESIncrementBlock: counter overflowed."); + return 0; + } + return 1; +} + +/** \brief AES encrypts a block using TempKey + * + * \param[in,out] block The block to be encrypted + * (16 bytes). + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESBlockEncrypt(byte block[]) +{ + if (!wakeup()) { + return 0; + } + if (!sendCommand(0x51, 0x00, 0xFFFF, block, 16)) { + return 0; + } + + delay(9); + + if (!receiveResponse(block, 16)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + +/** \brief AES block multiplication with hash key H + * + * \param[in] H The hash subkey + * (16 bytes) + * \param[in,out] block The block to be multiplied + * (16 bytes). + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESBlockMultiplication(byte H[], byte block[]) +{ + if (!wakeup()) { + return 0; + } + byte data[32]; + memcpy(data, H, 16); + memcpy(data+16, block, 16); + if (!sendCommand(0x51, 0x03, 0xFFFF, data, 32)) { + return 0; + } + + delay(9); + + if (!receiveResponse(block, 16)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + +/** \brief Generates AES GCM initialization vector. + * + * \param[out] IV Initialization vector to be generated + * (12 bytes). See + * NIST Special Publication 800-38D + * 8.2.1 Deterministic Construction + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::AESGenIV(byte IV[]) +{ + // The device ID is determined by the public key in slot 0 + byte pubKey[64]; + if (!generatePublicKey(0, pubKey)){ + Serial.println("AESGenIV: failed to obtain device ID"); + return 0; + } + // XOR the 64 public key bytes to get 4 bytes + byte deviceID[4] = {0x00}; + for (int i=0; i<64; i++){ + deviceID[i%4] ^= pubKey[i]; + } + // First 4 bytes of IV are device ID + for (int i=0; i<4; i++){ + IV[i] = deviceID[i]; + } + + // Device only has two 4 byte counters + // instead of 8 byte counter. + // We increment one counter and read the other + // This should be enough for the lifetime of the device + byte counter0[4]; + if (!incrementCounter(0, counter0)){ + Serial.println("AESGenIV: failed to increment counter"); + return 0; + } + byte counter1[4]; + if (!readCounter(1, counter1)){ + Serial.println("AESGenIV: failed to read counter"); + return 0; + } + // Last 8 bytes of IV are counter + for (int i=0; i<4; i++){ + // chip counter is little endian + IV[11-i] = counter0[i]; + IV[7-i] = counter1[i]; + } + + return 1; +} + +/** \brief Reads the counter on the device. + * + * \param[in] slot counter slot to read from + * \param[out] counter counter value (4 bytes) + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::readCounter(int slot, byte counter[]) +{ + if (!wakeup()) { + return 0; + } + if (!sendCommand(0x24, 0x00, slot)) { + return 0; + } + + delay(9); + + if (!receiveResponse(counter, 4)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + +/** \brief Increments the counter on the device. + * + * \param[in] slot counter slot to increment + * \param[out] counter counter value (4 bytes) + * + * \return 1 on success, otherwise 0. + */ +int ECCX08Class::incrementCounter(int slot, byte counter[]) +{ + if (!wakeup()) { + return 0; + } + if (!sendCommand(0x24, 0x01, slot)) { + return 0; + } + + delay(9); + + if (!receiveResponse(counter, 4)) { + return 0; + } + + delay(1); + idle(); + + return 1; +} + int ECCX08Class::readSlot(int slot, byte data[], int length) { if (slot < 0 || slot > 15) { diff --git a/src/ECCX08.h b/src/ECCX08.h index 50490a5..3257215 100644 --- a/src/ECCX08.h +++ b/src/ECCX08.h @@ -50,6 +50,24 @@ class ECCX08Class int endSHA256(byte result[]); int endSHA256(const byte data[], int length, byte result[]); + int ecdh(int slot, byte mode, const byte pubKeyXandY[], byte sharedSecret[]); + #define ECDH_MODE_TEMPKEY ((uint8_t)0x08) //!< ECDH mode: write to TempKey + #define ECDH_MODE_OUTPUT ((uint8_t)0x0c) //!< ECDH mode: write to buffer + + int AESEncrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ptLength); + int AESDecrypt(byte IV[], byte ad[], byte pt[], byte ct[], byte tag[], const uint64_t adLength, const uint64_t ctLength); + + int AESGCTR(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength); + int AESGHASH(byte counterBlock[], byte input[], byte output[], const uint64_t inputLength); + + int AESIncrementBlock(byte counterBlock[]); + int AESBlockEncrypt(byte block[]); + int AESBlockMultiplication(byte H[], byte block[]); + + int AESGenIV(byte IV[]); + int incrementCounter(int slot, byte counter[]); + int readCounter(int slot, byte counter[]); + int readSlot(int slot, byte data[], int length); int writeSlot(int slot, const byte data[], int length); diff --git a/src/utility/PEMUtils.cpp b/src/utility/PEMUtils.cpp index e619e65..e93faeb 100644 --- a/src/utility/PEMUtils.cpp +++ b/src/utility/PEMUtils.cpp @@ -18,6 +18,7 @@ */ #include "PEMUtils.h" +#include "b64.h" String PEMUtilsClass::base64Encode(const byte in[], unsigned int length, const char* prefix, const char* suffix) { @@ -68,4 +69,52 @@ String PEMUtilsClass::base64Encode(const byte in[], unsigned int length, const c return out; } +int PEMUtilsClass::base64Decode(const String in, byte out[]) +{ + String inBase64 = String(in); + inBase64.trim(); + // check PEM encoded + if ( inBase64.startsWith("-----BEGIN ") && inBase64.endsWith("-----")) { + int endLine1Char = inBase64.indexOf('\n'); + if (endLine1Char < 0) { + return -2; + } + int endLastLineChar = inBase64.lastIndexOf('\n'); + if (endLastLineChar < 0) { + return -3; + } + inBase64 = inBase64.substring(endLine1Char, endLastLineChar); + for (int i=inBase64.indexOf('\n'); i != -1; i=inBase64.indexOf('\n')) { + inBase64.remove(i,1); + } + } + + int len = decode_base64((unsigned char*)inBase64.c_str(), out); + return len; +} + +int PEMUtilsClass::xyFromPubKeyPEM(const String publicKeyPem, byte xy[64]) +{ + byte derBytes[256]; + int derLen = PEMUtils.base64Decode(publicKeyPem, derBytes); + if (derLen < 1) { + return -4; + } + if (derLen < 65) { + return -5; + } + + int j = derLen-64; + if (derBytes[j-1] != 0x04) { // ASN.1 uncompressed public key + return -6; + } + + // the public key X and Y values are the last 64 bytes + for (int i = 0; i < 64; i++) { + xy[i] = derBytes[j++]; + } + + return 0; +} + PEMUtilsClass PEMUtils; diff --git a/src/utility/PEMUtils.h b/src/utility/PEMUtils.h index 177aeec..ff2a007 100644 --- a/src/utility/PEMUtils.h +++ b/src/utility/PEMUtils.h @@ -25,6 +25,8 @@ class PEMUtilsClass { public: String base64Encode(const byte in[], unsigned int length, const char* prefix, const char* suffix); + int base64Decode(const String in, byte out[]); + int xyFromPubKeyPEM(const String in, byte xy[64]); }; extern PEMUtilsClass PEMUtils; diff --git a/src/utility/b64.h b/src/utility/b64.h new file mode 100644 index 0000000..6ad7b59 --- /dev/null +++ b/src/utility/b64.h @@ -0,0 +1,73 @@ +#ifndef _B64_H_ +#define _B64_H_ +/* + This base64 decode function is copied from v1.1.1 of: https://github.com/Densaugeo/base64_arduino + + MIT License (MIT) Copyright (c) 2016 Densaugeo + + Any other base64 decode function would do if there is an Arduino preferred one somewhere? +*/ +unsigned char base64_to_binary(unsigned char c) { + // Capital letters - 'A' is ascii 65 and base64 0 + if('A' <= c && c <= 'Z') return c - 'A'; + + // Lowercase letters - 'a' is ascii 97 and base64 26 + if('a' <= c && c <= 'z') return c - 71; + + // Digits - '0' is ascii 48 and base64 52 + if('0' <= c && c <= '9') return c + 4; + + // '+' is ascii 43 and base64 62 + if(c == '+') return 62; + + // '/' is ascii 47 and base64 63 + if(c == '/') return 63; + + return 255; +} + +unsigned int decode_base64_length(unsigned char input[]) { + unsigned char *start = input; + + while(base64_to_binary(input[0]) < 64) { + ++input; + } + + unsigned int input_length = input - start; + + unsigned int output_length = input_length/4*3; + + switch(input_length % 4) { + default: return output_length; + case 2: return output_length + 1; + case 3: return output_length + 2; + } +} + +unsigned int decode_base64(unsigned char input[], unsigned char output[]) { + unsigned int output_length = decode_base64_length(input); + + // While there are still full sets of 24 bits... + for(unsigned int i = 2; i < output_length; i += 3) { + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; + output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]); + + input += 4; + output += 3; + } + + switch(output_length % 3) { + case 1: + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + break; + case 2: + output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4; + output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2; + break; + } + + return output_length; +} + +#endif