diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index 6362f9c..cd8afc4 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -68,6 +68,7 @@ jobs: - name: arduino:samd libraries: | - name: ArduinoECCX08 + - name: Arduino_JSON - board: platform-name: arduino:mbed_portenta platforms: | @@ -75,6 +76,7 @@ jobs: - name: arduino:mbed_portenta libraries: | - name: ArduinoECCX08 + - name: Arduino_JSON - board: platform-name: arduino:mbed_nano platforms: | @@ -82,11 +84,15 @@ jobs: - name: arduino:mbed_nano libraries: | - name: ArduinoECCX08 + - name: Arduino_JSON - board: platform-name: arduino:mbed_nicla platforms: | # Install Arduino mbed_nano Boards via Boards Manager - name: arduino:mbed_nicla + libraries: | + - name: ArduinoECCX08 + - name: Arduino_JSON - board: platform-name: arduino:mbed_opta platforms: | @@ -94,6 +100,7 @@ jobs: - name: arduino:mbed_opta libraries: | - name: ArduinoECCX08 + - name: Arduino_JSON - board: platform-name: arduino:mbed_giga platforms: | @@ -101,16 +108,23 @@ jobs: - name: arduino:mbed_giga libraries: | - name: ArduinoECCX08 + - name: Arduino_JSON - board: platform-name: arduino:renesas_portenta platforms: | # Install Arduino renesas_portenta Boards via Boards Manager - name: arduino:renesas_portenta + libraries: | + - name: ArduinoECCX08 + - name: Arduino_JSON - board: platform-name: arduino:renesas_uno platforms: | # Install Arduino renesas_uno Boards via Boards Manager - name: arduino:renesas_uno + libraries: | + - name: ArduinoECCX08 + - name: Arduino_JSON steps: - name: Checkout diff --git a/examples/JSONWebToken/JSONWebToken.ino b/examples/JSONWebToken/JSONWebToken.ino new file mode 100644 index 0000000..698906e --- /dev/null +++ b/examples/JSONWebToken/JSONWebToken.ino @@ -0,0 +1,147 @@ +/* + ArduinoSecureElement - JSON Web Token + + This sketch can be used to generate a JSON Web Token from a private key + stored in an ECC508/ECC608 or SE050 crypto chip slot. + + If the SecureElement is not configured and locked it prompts + the user to configure and lock the chip with a default TLS + configuration. + + The user can also select the slot number to use for the private key. + A new private key can also be generated in this slot. + + The circuit: + - A board equipped with ECC508 or ECC608 or SE050 chip + + This example code is in the public domain. +*/ + +#include +#include +#include + +void setup() { + Serial.begin(9600); + while (!Serial); + + SecureElement secureElement; + + if (!secureElement.begin()) { + Serial.println("No SecureElement present!"); + while (1); + } + + String serialNumber = secureElement.serialNumber(); + + Serial.print("SecureElement Serial Number = "); + Serial.println(serialNumber); + Serial.println(); + + if (!secureElement.locked()) { + String lock = promptAndReadLine("The SecureElement on your board is not locked, would you like to PERMANENTLY configure and lock it now? (y/N)", "N"); + lock.toLowerCase(); + + if (!lock.startsWith("y")) { + Serial.println("Unfortunately you can't proceed without locking it :("); + while (1); + } + + if (!secureElement.writeConfiguration()) { + Serial.println("Writing SecureElement configuration failed!"); + while (1); + } + + if (!secureElement.lock()) { + Serial.println("Locking SecureElement configuration failed!"); + while (1); + } + + Serial.println("SecureElement locked successfully"); + Serial.println(); + } + + Serial.println("Hi there, in order to generate a PEM public key for your board, we'll need the following information ..."); + Serial.println(); + + String slot = promptAndReadLine("What slot would you like to use? (0 - 4)", "0"); + String generateNewKey = promptAndReadLine("Would you like to generate a new private key? (Y/n)", "Y"); + String issuer = promptAndReadLine("Issuer (Device UID)", ""); + String iat = promptAndReadLine("Issued at (Unix Timestamp)", ""); + String exp = promptAndReadLine("Expires at (Unix Timestamp)", ""); + + Serial.println(); + + generateNewKey.toLowerCase(); + + SElementJWS jws; + + String publicKeyPem = jws.publicKey(secureElement, slot.toInt(), generateNewKey.startsWith("y")); + + if (!publicKeyPem || publicKeyPem == "") { + Serial.println("Error generating public key!"); + while (1); + } + + Serial.println("Here's your public key PEM, enjoy!"); + Serial.println(); + Serial.println(publicKeyPem); + + JSONVar jwtHeader; + JSONVar jwtClaim; + + jwtHeader["alg"] = "ES256"; + jwtHeader["typ"] = "JWT"; + + jwtClaim["iss"] = issuer; + jwtClaim["iat"] = iat.toInt(); + jwtClaim["exp"] = exp.toInt(); + + String token = jws.sign(secureElement, slot.toInt(), JSON.stringify(jwtHeader), JSON.stringify(jwtClaim)); + + Serial.println("Here's your JSON Web Token, enjoy!"); + Serial.println(); + Serial.println(token); +} + +void loop() { + // do nothing +} + +String promptAndReadLine(const char* prompt, const char* defaultValue) { + Serial.print(prompt); + Serial.print(" ["); + Serial.print(defaultValue); + Serial.print("]: "); + + String s = readLine(); + + if (s.length() == 0) { + s = defaultValue; + } + + Serial.println(s); + + return s; +} + +String readLine() { + String line; + + while (1) { + if (Serial.available()) { + char c = Serial.read(); + + if (c == '\r') { + // ignore + continue; + } else if (c == '\n') { + break; + } + + line += c; + } + } + + return line; +} diff --git a/src/utility/SElementJWS.cpp b/src/utility/SElementJWS.cpp new file mode 100644 index 0000000..af88d80 --- /dev/null +++ b/src/utility/SElementJWS.cpp @@ -0,0 +1,126 @@ +/* + This file is part of the Arduino_SecureElement library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include +#include +#include +#include + +static String base64urlEncode(const byte in[], unsigned int length) +{ + static const char* CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_="; + + int b; + String out; + + int reserveLength = 4 * ((length + 2) / 3); + out.reserve(reserveLength); + + for (unsigned int i = 0; i < length; i += 3) { + b = (in[i] & 0xFC) >> 2; + out += CODES[b]; + + b = (in[i] & 0x03) << 4; + if (i + 1 < length) { + b |= (in[i + 1] & 0xF0) >> 4; + out += CODES[b]; + b = (in[i + 1] & 0x0F) << 2; + if (i + 2 < length) { + b |= (in[i + 2] & 0xC0) >> 6; + out += CODES[b]; + b = in[i + 2] & 0x3F; + out += CODES[b]; + } else { + out += CODES[b]; + } + } else { + out += CODES[b]; + } + } + + while (out.lastIndexOf('=') != -1) { + out.remove(out.length() - 1); + } + + return out; +} + +String SElementJWS::publicKey(SecureElement & se, int slot, bool newPrivateKey) +{ + if (slot < 0 || slot > 8) { + return ""; + } + + byte publicKey[64]; + + if (newPrivateKey) { + if (!se.generatePrivateKey(slot, publicKey)) { + return ""; + } + } else { + if (!se.generatePublicKey(slot, publicKey)) { + return ""; + } + } + + int length = ASN1Utils.publicKeyLength(); + byte out[length]; + + ASN1Utils.appendPublicKey(publicKey, out); + + return PEMUtils.base64Encode(out, length, "-----BEGIN PUBLIC KEY-----\n", "\n-----END PUBLIC KEY-----\n"); +} + +String SElementJWS::sign(SecureElement & se, int slot, const char* header, const char* payload) +{ + if (slot < 0 || slot > 8) { + return ""; + } + + String encodedHeader = base64urlEncode((const byte*)header, strlen(header)); + String encodedPayload = base64urlEncode((const byte*)payload, strlen(payload)); + + String toSign; + toSign.reserve(encodedHeader.length() + 1 + encodedPayload.length()); + + toSign += encodedHeader; + toSign += '.'; + toSign += encodedPayload; + + + byte toSignSha256[32]; + byte signature[64]; + + se.SHA256((const uint8_t*)toSign.c_str(), toSign.length(), toSignSha256); + + if (!se.ecSign(slot, toSignSha256, signature)) { + return ""; + } + + String encodedSignature = base64urlEncode(signature, sizeof(signature)); + + String result; + result.reserve(toSign.length() + 1 + encodedSignature.length()); + + result += toSign; + result += '.'; + result += encodedSignature; + + return result; +} + +String SElementJWS::sign(SecureElement & se, int slot, const String& header, const String& payload) +{ + return sign(se, slot, header.c_str(), payload.c_str()); +} diff --git a/src/utility/SElementJWS.h b/src/utility/SElementJWS.h new file mode 100644 index 0000000..74400f7 --- /dev/null +++ b/src/utility/SElementJWS.h @@ -0,0 +1,36 @@ +/* + This file is part of the Arduino_SecureElement library. + + Copyright (c) 2024 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef SECURE_ELEMENT_JWS_H_ +#define SECURE_ELEMENT_JWS_H_ + +/****************************************************************************** + * INCLUDE + ******************************************************************************/ + +#include + + /****************************************************************************** + * CLASS DECLARATION + ******************************************************************************/ + +class SElementJWS +{ +public: + + String publicKey(SecureElement & se, int slot, bool newPrivateKey = true); + + String sign(SecureElement & se, int slot, const char* header, const char* payload); + String sign(SecureElement & se, int slot, const String& header, const String& payload); + +}; + + +#endif /* SECURE_ELEMENT_JWS_H_ */