diff --git a/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino b/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino index 07a7deed3..c45e6f705 100644 --- a/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino +++ b/examples/MKR1000_Cloud_Blink/MKR1000_Cloud_Blink.ino @@ -1,11 +1,14 @@ -#include +#include // change to WiFiNINA.h if you are using the MKR WiFi 1010 or MKR Vidor 4000 #include - #include "arduino_secrets.h" + +#define TIMEOUT 7000 + ///////please enter your sensitive data in the Secret tab/arduino_secrets.h char ssid[] = SECRET_SSID; // your network SSID (name) char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP) int status = WL_IDLE_STATUS; // the WiFi radio's status +String cloudSerialBuffer = ""; // the string used to compose network messages from the received characters WiFiClient wifiClient; @@ -13,12 +16,18 @@ unsigned long getTime() { return WiFi.getTime(); } +int position; + +void onPositionUpdate() { + Serial.print("New position value: "); + Serial.println(position); +} + void setup() { //Initialize serial and wait for port to open: Serial.begin(9600); - while (!Serial) { - ; // wait for serial port to connect. Needed for native USB port only - } + int timeout = millis() + TIMEOUT; + while (!Serial && (millis() < timeout)) {} // check for the presence of the shield: if (WiFi.status() == WL_NO_SHIELD) { @@ -33,7 +42,8 @@ void setup() { } // attempt to connect to WiFi network: - while (status != WL_CONNECTED) { + int attempts = 0; + while (status != WL_CONNECTED && attempts < 6) { Serial.print("Attempting to connect to WPA SSID: "); Serial.println(ssid); // Connect to WPA/WPA2 network: @@ -41,34 +51,99 @@ void setup() { // wait 10 seconds for connection: delay(10000); + attempts++; + } + + if (status != WL_CONNECTED) { + Serial.println("Failed to connect to Wifi!"); + while (true); } // you're connected now, so print out the data: Serial.print("You're connected to the network"); Serial.println(); - Serial.println("Attempting to connect to Arduino Cloud ..."); + Serial.println("Attempting to connect to Arduino Cloud"); ArduinoCloud.onGetTime(getTime); - if (!ArduinoCloud.connect()) { + + attempts = 0; + while (!ArduinoCloud.connect() && attempts < 10) { + Serial.print("."); + attempts++; + } + + if (attempts >= 10) { Serial.println("Failed to connect to Arduino Cloud!"); while (1); } Serial.println("Successfully connected to Arduino Cloud :)"); + ArduinoCloud.addProperty(position, READ, 10*SECONDS, onPositionUpdate); + CloudSerial.begin(9600); + CloudSerial.print("I'm ready for blinking!\n"); } void loop() { ArduinoCloud.poll(); + // check if there is something waiting to be read if (CloudSerial.available()) { - Serial.write(CloudSerial.read()); + char character = CloudSerial.read(); + cloudSerialBuffer += character; + + // if a \n character has been received, there should be a complete command inside cloudSerialBuffer + if (character == '\n') { + manageString(); + } + } + else // if there is nothing to read, it could be that the last command didn't end with a '\n'. Check. + { + manageString(); } + // Just to be able to simulate the board responses through the serial monitor if (Serial.available()) { CloudSerial.write(Serial.read()); } } +void manageString() { + // Don't proceed if the string is empty + if (cloudSerialBuffer.equals("")) return; + + // Remove whitespaces + cloudSerialBuffer.trim(); + + // Make it uppercase; + cloudSerialBuffer.toUpperCase(); + + if (cloudSerialBuffer.equals("ON")) { + digitalWrite(6, HIGH); + } + if (cloudSerialBuffer.equals("OFF")) { + digitalWrite(6, LOW); + } + + sendString(cloudSerialBuffer); + + // Reset cloudSerialBuffer + cloudSerialBuffer = ""; +} + +// sendString sends a string to the Arduino Cloud. +void sendString(String stringToSend) { + // send the characters one at a time + char lastSentChar = 0; + for (int i = 0; i < stringToSend.length(); i++) { + lastSentChar = stringToSend.charAt(i); + CloudSerial.write(lastSentChar); + } + + // if the last sent character wasn't a '\n' add it + if (lastSentChar != '\n') { + CloudSerial.write('\n'); + } +} diff --git a/examples/utility/Provisioning/Provisioning.ino b/examples/utility/Provisioning/Provisioning.ino index 29cb46793..cf18f0664 100644 --- a/examples/utility/Provisioning/Provisioning.ino +++ b/examples/utility/Provisioning/Provisioning.ino @@ -3,12 +3,13 @@ #include #include -#include +#include -const int keySlot = 0; -const int compressedCertSlot = 10; -const int serialNumberSlot = 11; -const int thingIdSlot = 12; +const bool DEBUG = true; +const int keySlot = 0; +const int compressedCertSlot = 10; +const int serialNumberAndAuthorityKeyIdentifierSlot = 11; +const int thingIdSlot = 12; void setup() { Serial.begin(9600); @@ -55,7 +56,8 @@ void setup() { while (1); } - ECCX08Cert.setSubjectCommonName(ECCX08.serialNumber()); + String thingId = promptAndReadLine("Please enter the thing id: "); + ECCX08Cert.setSubjectCommonName(thingId); String csr = ECCX08Cert.endCSR(); @@ -68,37 +70,37 @@ void setup() { Serial.println(); Serial.println(csr); - String thingId = promptAndReadLine("Please enter the thing id: "); - String issueYear = promptAndReadLine("Please enter the issue year of the certificate (2000 - 2031): "); - String issueMonth = promptAndReadLine("Please enter the issue month of the certificate (1 - 12): "); - String issueDay = promptAndReadLine("Please enter the issue day of the certificate (1 - 31): "); - String issueHour = promptAndReadLine("Please enter the issue hour of the certificate (0 - 23): "); - String expireYears = promptAndReadLine("Please enter how many years the certificate is valid for (0 - 31): "); - String serialNumber = promptAndReadLine("Please enter the certificates serial number: "); - String signature = promptAndReadLine("Please enter the certificates signature: "); - - serialNumber.toUpperCase(); - signature.toUpperCase(); + String issueYear = promptAndReadLine("Please enter the issue year of the certificate (2000 - 2031): "); + String issueMonth = promptAndReadLine("Please enter the issue month of the certificate (1 - 12): "); + String issueDay = promptAndReadLine("Please enter the issue day of the certificate (1 - 31): "); + String issueHour = promptAndReadLine("Please enter the issue hour of the certificate (0 - 23): "); + String expireYears = promptAndReadLine("Please enter how many years the certificate is valid for (0 - 31): "); + String serialNumber = promptAndReadLine("Please enter the certificates serial number: "); + String authorityKeyIdentifier = promptAndReadLine("Please enter the certificates authority key identifier: "); + String signature = promptAndReadLine("Please enter the certificates signature: "); byte thingIdBytes[72]; byte serialNumberBytes[16]; + byte authorityKeyIdentifierBytes[20]; byte signatureBytes[64]; thingId.getBytes(thingIdBytes, sizeof(thingIdBytes)); hexStringToBytes(serialNumber, serialNumberBytes, sizeof(serialNumberBytes)); - hexStringToBytes(signature, signatureBytes, 64); + hexStringToBytes(authorityKeyIdentifier, authorityKeyIdentifierBytes, sizeof(authorityKeyIdentifierBytes)); + hexStringToBytes(signature, signatureBytes, sizeof(signatureBytes)); if (!ECCX08.writeSlot(thingIdSlot, thingIdBytes, sizeof(thingIdBytes))) { Serial.println("Error storing thing id!"); while (1); } - if (!ECCX08Cert.beginStorage(compressedCertSlot, serialNumberSlot)) { + if (!ECCX08Cert.beginStorage(compressedCertSlot, serialNumberAndAuthorityKeyIdentifierSlot)) { Serial.println("Error starting ECCX08 storage!"); while (1); } ECCX08Cert.setSignature(signatureBytes); + ECCX08Cert.setAuthorityKeyIdentifier(authorityKeyIdentifierBytes); ECCX08Cert.setSerialNumber(serialNumberBytes); ECCX08Cert.setIssueYear(issueYear.toInt()); ECCX08Cert.setIssueMonth(issueMonth.toInt()); @@ -111,7 +113,7 @@ void setup() { while (1); } - if (!ECCX08Cert.beginReconstruction(keySlot, compressedCertSlot, serialNumberSlot)) { + if (!ECCX08Cert.beginReconstruction(keySlot, compressedCertSlot, serialNumberAndAuthorityKeyIdentifierSlot)) { Serial.println("Error starting ECCX08 cert reconstruction!"); while (1); } @@ -126,6 +128,10 @@ void setup() { while (1); } + if (!DEBUG) { + return; + } + Serial.println("Compressed cert = "); const byte* certData = ECCX08Cert.bytes(); @@ -175,8 +181,9 @@ String readLine() { return line; } -void hexStringToBytes(const String& in, byte out[], int length) { +void hexStringToBytes(String& in, byte out[], int length) { int inLength = in.length(); + in.toUpperCase(); int outLength = 0; for (int i = 0; i < inLength && outLength < length; i += 2) { @@ -186,7 +193,6 @@ void hexStringToBytes(const String& in, byte out[], int length) { byte highByte = (highChar <= '9') ? (highChar - '0') : (highChar + 10 - 'A'); byte lowByte = (lowChar <= '9') ? (lowChar - '0') : (lowChar + 10 - 'A'); - out[outLength++] = (highByte << 4) | lowByte; + out[outLength++] = (highByte << 4) | (lowByte & 0xF); } } - diff --git a/src/ArduinoCloud.cpp b/src/ArduinoCloud.cpp index 05159a144..4a7f9e37f 100644 --- a/src/ArduinoCloud.cpp +++ b/src/ArduinoCloud.cpp @@ -5,16 +5,17 @@ #include "ArduinoCloudV2.h" -const static char server[] = "a19g5nbe27wn47.iot.us-east-1.amazonaws.com"; //"xxxxxxxxxxxxxx.iot.xx-xxxx-x.amazonaws.com"; +const static char server[] = "mqtts-sa.iot.oniudra.cc"; -const static int keySlot = 0; -const static int compressedCertSlot = 10; -const static int serialNumberSlot = 11; -const static int thingIdSlot = 12; +const static int keySlot = 0; +const static int compressedCertSlot = 10; +const static int serialNumberAndAuthorityKeyIdentifierSlot = 11; +const static int thingIdSlot = 12; ArduinoCloudClass::ArduinoCloudClass() : _bearSslClient(NULL), - _mqttClient(256) + // Size of the receive buffer + _mqttClient(MQTT_BUFFER_SIZE) { } @@ -38,11 +39,11 @@ int ArduinoCloudClass::begin(Client& net) } _id = (char*)thingIdBytes; - if (!ECCX08Cert.beginReconstruction(keySlot, compressedCertSlot, serialNumberSlot)) { + if (!ECCX08Cert.beginReconstruction(keySlot, compressedCertSlot, serialNumberAndAuthorityKeyIdentifierSlot)) { return 0; } - ECCX08Cert.setSubjectCommonName(ECCX08.serialNumber()); + ECCX08Cert.setSubjectCommonName(_id); ECCX08Cert.setIssuerCountryName("US"); ECCX08Cert.setIssuerOrganizationName("Arduino LLC US"); ECCX08Cert.setIssuerOrganizationalUnitName("IT"); @@ -58,29 +59,104 @@ int ArduinoCloudClass::begin(Client& net) _bearSslClient = new BearSSLClient(net); _bearSslClient->setEccSlot(keySlot, ECCX08Cert.bytes(), ECCX08Cert.length()); - _mqttClient.onMessageAdvanced(ArduinoCloudClass::onMessage); - _mqttClient.begin(server, 8883, *_bearSslClient); + // Begin function for the MQTTClient + mqttClientBegin(*_bearSslClient); - _stdoutTopic = "$aws/things/" + _id + "/stdout"; - _stdinTopic = "$aws/things/" + _id + "/stdin"; + Thing.begin(); return 1; } +// private class method used to initialize mqttClient class member. (called in the begin class method) +void ArduinoCloudClass::mqttClientBegin(Client& net) +{ + // MQTT topics definition + _stdoutTopic = "/a/d/" + _id + "/s/o"; + _stdinTopic = "/a/d/" + _id + "/s/i"; + _dataTopicIn = "/a/d/" + _id + "/e/i"; + _dataTopicOut = "/a/d/" + _id + "/e/o"; + + // use onMessage as callback for received mqtt messages + _mqttClient.onMessageAdvanced(ArduinoCloudClass::onMessage); + _mqttClient.begin(server, 8883, net); + + // Set MQTT connection options + _mqttClient.setOptions(mqttOpt.keepAlive, mqttOpt.cleanSession, mqttOpt.timeout); +} + int ArduinoCloudClass::connect() { + // Username: device id + // Password: empty if (!_mqttClient.connect(_id.c_str())) { return 0; } - _mqttClient.subscribe(_stdinTopic); + _mqttClient.subscribe(_dataTopicIn); return 1; } +bool ArduinoCloudClass::disconnect() +{ + return _mqttClient.disconnect(); +} + void ArduinoCloudClass::poll() { + // If user call poll() without parameters use the default ones + poll(MAX_RETRIES, RECONNECTION_TIMEOUT); +} + +bool ArduinoCloudClass::mqttReconnect(int maxRetries, int timeout) +{ + // Counter for reconnection retries + int retries = 0; + unsigned long start = millis(); + + // Check for MQTT broker connection, of if maxReties limit is reached + // if MQTTClient is connected , simply do nothing and retun true + while(!_mqttClient.connected() && (retries++ < maxRetries) && (millis() - start < timeout)) { + + // Get last MTTQClient error, (a common error may be a buffer overflow) + lwmqtt_err_t err = _mqttClient.lastError(); + + // try establish the MQTT broker connection + connect(); + } + + // It was impossible to establish a connection, return + if ((retries == maxRetries) || (millis() - start >= timeout)) + return false; + + return true; +} + +void ArduinoCloudClass::poll(int reconnectionMaxRetries, int reconnectionTimeoutMs) +{ + // Method's argument controls + int maxRetries = (reconnectionMaxRetries > 0) ? reconnectionMaxRetries : MAX_RETRIES; + int timeout = (reconnectionTimeoutMs > 0) ? reconnectionTimeoutMs : RECONNECTION_TIMEOUT; + + // If the reconnect() culd not establish the connection, return the control to the user sketch + if (!mqttReconnect(maxRetries, timeout)) + return; + + // MTTQClient connected!, poll() used to retrieve data from MQTT broker _mqttClient.loop(); + + uint8_t data[MQTT_BUFFER_SIZE]; + int length = Thing.poll(data, sizeof(data)); + if (length > 0) { + writeProperties(data, length); + } +} + +void ArduinoCloudClass::reconnect(Client& net) +{ + // Initialize again the MQTTClient, otherwise it would not be able to receive messages through its callback + mqttClientBegin(net); + connect(); } void ArduinoCloudClass::onGetTime(unsigned long(*callback)(void)) @@ -93,6 +169,11 @@ int ArduinoCloudClass::connected() return _mqttClient.connected(); } +int ArduinoCloudClass::writeProperties(const byte data[], int length) +{ + return _mqttClient.publish(_dataTopicOut.c_str(), (const char*)data, length); +} + int ArduinoCloudClass::writeStdout(const byte data[], int length) { return _mqttClient.publish(_stdoutTopic.c_str(), (const char*)data, length); @@ -108,6 +189,9 @@ void ArduinoCloudClass::handleMessage(char topic[], char bytes[], int length) if (_stdinTopic == topic) { CloudSerial.appendStdin((uint8_t*)bytes, length); } + if (_dataTopicIn == topic) { + Thing.decode((uint8_t*)bytes, length); + } } ArduinoCloudClass ArduinoCloud; diff --git a/src/ArduinoCloudV2.h b/src/ArduinoCloudV2.h index 6a592565f..dc2a89714 100644 --- a/src/ArduinoCloudV2.h +++ b/src/ArduinoCloudV2.h @@ -3,9 +3,17 @@ #include #include +#include #include "CloudSerial.h" +// Declaration of the struct for the mqtt connection options +typedef struct mqtt_opt{ + int keepAlive; + bool cleanSession; + int timeout; +} mqttConnectionOptions; + class ArduinoCloudClass { public: @@ -14,33 +22,66 @@ class ArduinoCloudClass { int begin(Client& net); - int connect(); + // Class constant declaration + static const int MQTT_BUFFER_SIZE = 256; + static const int MAX_RETRIES = 5; + static const int RECONNECTION_TIMEOUT = 2000; + const mqttConnectionOptions mqttOpt = {120, false, 1000}; + int connect(); + bool disconnect(); void poll(); + // defined for users who want to specify max reconnections reties and timeout between them + void poll(int reconnectionMaxRetries, int reconnectionTimeoutMs); + // It must be a user defined function, in order to avoid ArduinoCloud include specific WiFi file + // in this case this library is independent from the WiFi one void onGetTime(unsigned long(*)(void)); int connected(); + // Clean up existing Mqtt connection, create a new one and initialize it + void reconnect(Client& net); + + #define addProperty( v, ...) addPropertyReal(v, #v, __VA_ARGS__) + + template void addPropertyReal(T& property, String name, permissionType _permission = READWRITE, long seconds = ON_CHANGE, T minDelta = T(0), void(*fn)(void) = NULL) { + Thing.addPropertyReal(property, name, _permission, seconds, fn, minDelta); + } + + template void addPropertyReal(T& property, String name, permissionType _permission = READWRITE, long seconds = ON_CHANGE, void(*fn)(void) = NULL, T minDelta = T(0)) { + Thing.addPropertyReal(property, name, _permission, seconds, fn, minDelta); + } + + template void addPropertyReal(T& property, String name, permissionType _permission = READWRITE, void(*fn)(void) = NULL, long seconds = ON_CHANGE, T minDelta = T(0)) { + Thing.addPropertyReal(property, name, _permission, seconds, fn, minDelta); + } protected: friend class CloudSerialClass; int writeStdout(const byte data[], int length); + int writeProperties(const byte data[], int length); + // Used to initialize MQTTClient + void mqttClientBegin(Client& net); + // Function in charge of perform MQTT reconnection, basing on class parameters(retries,and timeout) + bool mqttReconnect(int maxRetries, int timeout); private: static void onMessage(MQTTClient *client, char topic[], char bytes[], int length); - void handleMessage(char topic[], char bytes[], int length); -private: String _id; + ArduinoCloudThing Thing; BearSSLClient* _bearSslClient; MQTTClient _mqttClient; + // Class attribute to define MTTQ topics 2 for stdIn/out and 2 for data, in order to avoid getting previous pupblished payload String _stdinTopic; String _stdoutTopic; + String _dataTopicOut; + String _dataTopicIn; + String _otaTopic; }; - extern ArduinoCloudClass ArduinoCloud; #endif diff --git a/src/utility/ECCX08Cert.cpp b/src/utility/ECCX08Cert.cpp index 1311c3092..7d38969bc 100644 --- a/src/utility/ECCX08Cert.cpp +++ b/src/utility/ECCX08Cert.cpp @@ -18,7 +18,13 @@ struct __attribute__((__packed__)) CompressedCert { byte unused[5]; }; -#define SERIAL_NUMBER_LENGTH 16 +#define SERIAL_NUMBER_LENGTH 16 +#define AUTHORITY_KEY_IDENTIFIER_LENGTH 20 + +struct __attribute__((__packed__)) SerialNumberAndAuthorityKeyIdentifier { + byte serialNumber[SERIAL_NUMBER_LENGTH]; + byte authorityKeyIdentifier[AUTHORITY_KEY_IDENTIFIER_LENGTH]; +}; static String base64Encode(const byte in[], unsigned int length, const char* prefix, const char* suffix) { @@ -72,7 +78,7 @@ static String base64Encode(const byte in[], unsigned int length, const char* pre ECCX08CertClass::ECCX08CertClass() : _keySlot(-1), _compressedCertSlot(-1), - _serialNumberSlot(-1), + _serialNumberAndAuthorityKeyIdentifierSlot(-1), _bytes(NULL), _length(0) { @@ -184,18 +190,18 @@ String ECCX08CertClass::endCSR() return base64Encode(csr, csrLen + csrHeaderLen, "-----BEGIN CERTIFICATE REQUEST-----\n", "\n-----END CERTIFICATE REQUEST-----\n"); } -int ECCX08CertClass::beginStorage(int compressedCertSlot, int serialNumberSlot) +int ECCX08CertClass::beginStorage(int compressedCertSlot, int serialNumberAndAuthorityKeyIdentifierSlot) { if (compressedCertSlot < 8 || compressedCertSlot > 15) { return 0; } - if (serialNumberSlot < 8 || serialNumberSlot > 15) { + if (serialNumberAndAuthorityKeyIdentifierSlot < 8 || serialNumberAndAuthorityKeyIdentifierSlot > 15) { return 0; } _compressedCertSlot = compressedCertSlot; - _serialNumberSlot = serialNumberSlot; + _serialNumberAndAuthorityKeyIdentifierSlot = serialNumberAndAuthorityKeyIdentifierSlot; memset(_temp, 0x00, sizeof(_temp)); @@ -255,25 +261,30 @@ void ECCX08CertClass::setExpireYears(int expireYears) compressedCert->dates[2] |= expireYears; } -void ECCX08CertClass::setSerialNumber(byte serialNumber[]) +void ECCX08CertClass::setSerialNumber(const byte serialNumber[]) { memcpy(&_temp[72], serialNumber, SERIAL_NUMBER_LENGTH); } +void ECCX08CertClass::setAuthorityKeyIdentifier(const byte authorityKeyIdentifier[]) +{ + memcpy(&_temp[88], authorityKeyIdentifier, AUTHORITY_KEY_IDENTIFIER_LENGTH); +} + int ECCX08CertClass::endStorage() { if (!ECCX08.writeSlot(_compressedCertSlot, &_temp[0], 72)) { return 0; } - if (!ECCX08.writeSlot(_serialNumberSlot, &_temp[72], SERIAL_NUMBER_LENGTH)) { + if (!ECCX08.writeSlot(_serialNumberAndAuthorityKeyIdentifierSlot, &_temp[72], SERIAL_NUMBER_LENGTH + AUTHORITY_KEY_IDENTIFIER_LENGTH)) { return 0; } return 1; } -int ECCX08CertClass::beginReconstruction(int keySlot, int compressedCertSlot, int serialNumberSlot) +int ECCX08CertClass::beginReconstruction(int keySlot, int compressedCertSlot, int serialNumberAndAuthorityKeyIdentifierSlot) { if (keySlot < 0 || keySlot > 8) { return 0; @@ -283,13 +294,13 @@ int ECCX08CertClass::beginReconstruction(int keySlot, int compressedCertSlot, in return 0; } - if (serialNumberSlot < 8 || serialNumberSlot > 15) { + if (serialNumberAndAuthorityKeyIdentifierSlot < 8 || serialNumberAndAuthorityKeyIdentifierSlot > 15) { return 0; } _keySlot = keySlot; _compressedCertSlot = compressedCertSlot; - _serialNumberSlot = serialNumberSlot; + _serialNumberAndAuthorityKeyIdentifierSlot = serialNumberAndAuthorityKeyIdentifierSlot; return 1; } @@ -298,7 +309,7 @@ int ECCX08CertClass::endReconstruction() { byte publicKey[64]; struct CompressedCert compressedCert; - byte serialNumber[SERIAL_NUMBER_LENGTH]; + struct SerialNumberAndAuthorityKeyIdentifier serialNumberAndAuthorityKeyIdentifier; if (!ECCX08.generatePublicKey(_keySlot, publicKey)) { return 0; @@ -308,11 +319,11 @@ int ECCX08CertClass::endReconstruction() return 0; } - if (!ECCX08.readSlot(_serialNumberSlot, serialNumber, sizeof(serialNumber))) { + if (!ECCX08.readSlot(_serialNumberAndAuthorityKeyIdentifierSlot, (byte*)&serialNumberAndAuthorityKeyIdentifier, sizeof(serialNumberAndAuthorityKeyIdentifier))) { return 0; } - int serialNumberLen = serialNumberLength(serialNumber); + int serialNumberLen = serialNumberLength(serialNumberAndAuthorityKeyIdentifier.serialNumber); int issuerLen = issuerOrSubjectLength(_issuerCountryName, _issuerStateProvinceName, @@ -334,10 +345,19 @@ int ECCX08CertClass::endReconstruction() int publicKeyLen = publicKeyLength(); + int authorityKeyIdentifierLen = authorityKeyIdentifierLength(serialNumberAndAuthorityKeyIdentifier.authorityKeyIdentifier); + int signatureLen = signatureLength(compressedCert.signature); int certInfoLen = 5 + serialNumberLen + 12 + issuerHeaderLen + issuerLen + 32 + - subjectHeaderLen + subjectLen + publicKeyLen + 4; + subjectHeaderLen + subjectLen + publicKeyLen; + + if (authorityKeyIdentifierLen) { + certInfoLen += authorityKeyIdentifierLen; + } else { + certInfoLen += 4; + } + int certInfoHeaderLen = sequenceHeaderLength(certInfoLen); int certDataLen = certInfoLen + certInfoHeaderLen + signatureLen; @@ -367,7 +387,7 @@ int ECCX08CertClass::endReconstruction() *out++ = 0x02; // serial number - appendSerialNumber(serialNumber, out); + appendSerialNumber(serialNumberAndAuthorityKeyIdentifier.serialNumber, out); out += serialNumberLen; // ecdsaWithSHA256 @@ -411,11 +431,16 @@ int ECCX08CertClass::endReconstruction() appendPublicKey(publicKey, out); out += publicKeyLen; - // null sequence - *out++ = 0xA3; - *out++ = 0x02; - *out++ = 0x30; - *out++ = 0x00; + if (authorityKeyIdentifierLen) { + appendAuthorityKeyIdentifier(serialNumberAndAuthorityKeyIdentifier.authorityKeyIdentifier, out); + out += authorityKeyIdentifierLen; + } else { + // null sequence + *out++ = 0xA3; + *out++ = 0x02; + *out++ = 0x30; + *out++ = 0x00; + } // signature appendSignature(compressedCert.signature, out); @@ -546,6 +571,21 @@ int ECCX08CertClass::publicKeyLength() return (2 + 2 + 9 + 10 + 4 + 64); } +int ECCX08CertClass::authorityKeyIdentifierLength(const byte authorityKeyIdentifier[]) +{ + bool set = false; + + // check if the authority key identifier is non-zero + for (int i = 0; i < AUTHORITY_KEY_IDENTIFIER_LENGTH; i++) { + if (authorityKeyIdentifier[i] != 0) { + set = true; + break; + } + } + + return (set ? 37 : 0); +} + int ECCX08CertClass::signatureLength(const byte signature[]) { const byte* r = &signature[0]; @@ -684,6 +724,41 @@ void ECCX08CertClass::appendPublicKey(const byte publicKey[], byte out[]) memcpy(out, publicKey, 64); } +void ECCX08CertClass::appendAuthorityKeyIdentifier(const byte authorityKeyIdentifier[], byte out[]) +{ + // [3] + *out++ = 0xa3; + *out++ = 0x23; + + // sequence + *out++ = ASN1_SEQUENCE; + *out++ = 0x21; + + // sequence + *out++ = ASN1_SEQUENCE; + *out++ = 0x1f; + + // 2.5.29.35 authorityKeyIdentifier(X.509 extension) + *out++ = 0x06; + *out++ = 0x03; + *out++ = 0x55; + *out++ = 0x1d; + *out++ = 0x23; + + // octet string + *out++ = 0x04; + *out++ = 0x18; + + // sequence + *out++ = ASN1_SEQUENCE; + *out++ = 0x16; + + *out++ = 0x80; + *out++ = 0x14; + + memcpy(out, authorityKeyIdentifier, 20); +} + void ECCX08CertClass::appendSignature(const byte signature[], byte out[]) { // signature algorithm diff --git a/src/utility/ECCX08Cert.h b/src/utility/ECCX08Cert.h index 882ba76d1..2b35f7777 100644 --- a/src/utility/ECCX08Cert.h +++ b/src/utility/ECCX08Cert.h @@ -12,17 +12,18 @@ class ECCX08CertClass { int beginCSR(int keySlot, bool newPrivateKey = true); String endCSR(); - int beginStorage(int compressedCertSlot, int serialNumberSlot); + int beginStorage(int compressedCertSlot, int serialNumberAndAuthorityKeyIdentifierSlot); void setSignature(byte signature[]); void setIssueYear(int issueYear); void setIssueMonth(int issueMonth); void setIssueDay(int issueDay); void setIssueHour(int issueHour); void setExpireYears(int expireYears); - void setSerialNumber(byte serialNumber[]); + void setSerialNumber(const byte serialNumber[]); + void setAuthorityKeyIdentifier(const byte authorityKeyIdentifier[]); int endStorage(); - int beginReconstruction(int keySlot, int compressedCertSlot, int serialNumberSlot); + int beginReconstruction(int keySlot, int compressedCertSlot, int serialNumberAndAuthorityKeyIdentifierSlot); int endReconstruction(); byte* bytes(); @@ -54,6 +55,8 @@ class ECCX08CertClass { int publicKeyLength(); + int authorityKeyIdentifierLength(const byte authorityKeyIdentifier[]); + int signatureLength(const byte signature[]); int serialNumberLength(const byte serialNumber[]); @@ -72,6 +75,8 @@ class ECCX08CertClass { void appendPublicKey(const byte publicKey[], byte out[]); + void appendAuthorityKeyIdentifier(const byte authorityKeyIdentifier[], byte out[]); + void appendSignature(const byte signature[], byte out[]); void appendSerialNumber(const byte serialNumber[], byte out[]); @@ -87,7 +92,7 @@ class ECCX08CertClass { private: int _keySlot; int _compressedCertSlot; - int _serialNumberSlot; + int _serialNumberAndAuthorityKeyIdentifierSlot; String _issuerCountryName; String _issuerStateProvinceName; @@ -103,7 +108,7 @@ class ECCX08CertClass { String _subjectOrganizationalUnitName; String _subjectCommonName; - byte _temp[88]; + byte _temp[108]; byte* _bytes; int _length; };