diff --git a/src/ECP256Certificate.cpp b/src/ECP256Certificate.cpp index e146a2f..7bd4272 100644 --- a/src/ECP256Certificate.cpp +++ b/src/ECP256Certificate.cpp @@ -15,6 +15,7 @@ /* This is needed for memmem */ #define _GNU_SOURCE #include +#include #include "ECP256Certificate.h" /****************************************************************************** @@ -29,58 +30,6 @@ #define ASN1_SEQUENCE 0x30 #define ASN1_SET 0x31 -/****************************************************************************** - * LOCAL MODULE FUNCTIONS - ******************************************************************************/ - -static String base64Encode(const byte in[], unsigned int length, const char* prefix, const char* suffix) { - static const char* CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - int b; - String out; - - int reserveLength = 4 * ((length + 2) / 3) + ((length / 3 * 4) / 76) + strlen(prefix) + strlen(suffix); - out.reserve(reserveLength); - - if (prefix) { - out += prefix; - } - - for (unsigned int i = 0; i < length; i += 3) { - if (i > 0 && (i / 3 * 4) % 76 == 0) { - out += '\n'; - } - - 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]; - out += '='; - } - } else { - out += CODES[b]; - out += "=="; - } - } - - if (suffix) { - out += suffix; - } - - return out; -} - /****************************************************************************** * CTOR/DTOR ******************************************************************************/ @@ -184,7 +133,7 @@ int ECP256Certificate::signCSR(byte * signature) String ECP256Certificate::getCSRPEM() { - return base64Encode(_certBuffer, _certBufferLen, "-----BEGIN CERTIFICATE REQUEST-----\n", "\n-----END CERTIFICATE REQUEST-----\n"); + return b64::pemEncode(_certBuffer, _certBufferLen, "-----BEGIN CERTIFICATE REQUEST-----\n", "\n-----END CERTIFICATE REQUEST-----\n"); } int ECP256Certificate::buildCert() @@ -323,7 +272,7 @@ int ECP256Certificate::signCert() String ECP256Certificate::getCertPEM() { - return base64Encode(_certBuffer, _certBufferLen, "-----BEGIN CERTIFICATE-----\n", "\n-----END CERTIFICATE-----\n"); + return b64::pemEncode(_certBuffer, _certBufferLen, "-----BEGIN CERTIFICATE-----\n", "\n-----END CERTIFICATE-----\n"); } void ECP256Certificate::getDateFromCompressedData(DateInfo& date) { diff --git a/src/ECP256Certificate.h b/src/ECP256Certificate.h index 2be7c5c..681f806 100644 --- a/src/ECP256Certificate.h +++ b/src/ECP256Certificate.h @@ -101,6 +101,11 @@ class ECP256Certificate { /* Import DER buffer into CertClass*/ int importCert(const byte certDER[], size_t derLen); +protected: + + int publicKeyLength(); + int appendPublicKey(const byte publicKey[], byte out[]); + private: struct CertInfo { @@ -154,7 +159,6 @@ class ECP256Certificate { int versionLength(); int issuerOrSubjectLength(const CertInfo& issuerOrSubjectData); int sequenceHeaderLength(int length); - int publicKeyLength(); int signatureLength(const byte signature[]); int serialNumberLength(const byte serialNumber[], int length); int authorityKeyIdLength(const byte authorityKeyId[], int length); @@ -171,7 +175,6 @@ class ECP256Certificate { int appendVersion(int version, byte out[]); int appendName(const String& name, int type, byte out[]); int appendIssuerOrSubject(const CertInfo& issuerOrSubjectData, byte out[]); - int appendPublicKey(const byte publicKey[], byte out[]); int appendSignature(const byte signature[], byte out[]); int appendSerialNumber(const byte serialNumber[], int length, byte out[]); int appendDate(int year, int month, int day, int hour, int minute, int second, byte out[]); diff --git a/src/utility/SElementArduinoCloudJWT.cpp b/src/utility/SElementArduinoCloudJWT.cpp new file mode 100644 index 0000000..1853c8b --- /dev/null +++ b/src/utility/SElementArduinoCloudJWT.cpp @@ -0,0 +1,23 @@ +/* + 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 "SElementArduinoCloudJWT.h" + +constexpr char JWT_HEADER[] = "{\"alg\":\"ES256\",\"typ\":\"JWT\"}"; +String getAIoTCloudJWT(SecureElement &se, String issuer, uint64_t iat, uint8_t slot) +{ + SElementJWS jws; + String jwtClaim = "{\"iat\":"; + jwtClaim += String((uint32_t)iat); + jwtClaim += ",\"iss\":\""; + jwtClaim += issuer; + jwtClaim += "\"}"; + String token = jws.sign(se, slot, JWT_HEADER, jwtClaim.c_str()); + return token; +} diff --git a/src/utility/SElementArduinoCloudJWT.h b/src/utility/SElementArduinoCloudJWT.h new file mode 100644 index 0000000..8658be4 --- /dev/null +++ b/src/utility/SElementArduinoCloudJWT.h @@ -0,0 +1,17 @@ +/* + 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_AIoTCloud_JWT_H_ +#define SECURE_ELEMENT_AIoTCloud_JWT_H_ +#include "SElementJWS.h" + +String getAIoTCloudJWT(SecureElement &se, String issuer, uint64_t iat, uint8_t slot = 1); + +#endif diff --git a/src/utility/SElementBase64.cpp b/src/utility/SElementBase64.cpp new file mode 100644 index 0000000..97323f2 --- /dev/null +++ b/src/utility/SElementBase64.cpp @@ -0,0 +1,101 @@ +/* + 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 + +namespace arduino { namespace b64 { + +String urlEncode(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 pemEncode(const byte in[], unsigned int length, const char* prefix, const char* suffix) { +static const char* CODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + int b; + String out; + + int reserveLength = 4 * ((length + 2) / 3) + ((length / 3 * 4) / 76) + strlen(prefix) + strlen(suffix); + out.reserve(reserveLength); + + if (prefix) { + out += prefix; + } + + for (unsigned int i = 0; i < length; i += 3) { + if (i > 0 && (i / 3 * 4) % 76 == 0) { + out += '\n'; + } + + 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]; + out += '='; + } + } else { + out += CODES[b]; + out += "=="; + } + } + + if (suffix) { + out += suffix; + } + + return out; +} + +}} // arduino::b64 diff --git a/src/utility/SElementBase64.h b/src/utility/SElementBase64.h new file mode 100644 index 0000000..8446a02 --- /dev/null +++ b/src/utility/SElementBase64.h @@ -0,0 +1,20 @@ +/* + 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/. +*/ + +#pragma once + +#include + +namespace arduino { namespace b64 { + + String urlEncode(const byte in[], unsigned int length); + String pemEncode(const byte in[], unsigned int length, const char* prefix, const char* suffix); + +}} // arduino::b64 diff --git a/src/utility/SElementJWS.cpp b/src/utility/SElementJWS.cpp new file mode 100644 index 0000000..ac30bfe --- /dev/null +++ b/src/utility/SElementJWS.cpp @@ -0,0 +1,85 @@ +/* + 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 + +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 = publicKeyLength(); + byte out[length]; + + appendPublicKey(publicKey, out); + + return b64::pemEncode(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 = b64::urlEncode((const byte*)header, strlen(header)); + String encodedPayload = b64::urlEncode((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 = b64::urlEncode(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..82f392b --- /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 ECP256Certificate +{ +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_ */