Device interface library for multiple platforms including Mbed.
Dependents: DeepCover Embedded Security in IoT MaximInterface MAXREFDES155#
Maxim Interface is a library framework focused on providing flexible and expressive hardware interfaces. Both communication interfaces such as I2C and 1-Wire and device interfaces such as DS18B20 are supported. Modern C++ concepts are used extensively while keeping compatibility with C++98/C++03 and requiring no external dependencies. The embedded-friendly design does not depend on exceptions or RTTI.
The full version of the project is hosted on GitLab: https://gitlab.com/iabenz/MaximInterface
Diff: MaximInterfaceDevices/DS28C40.cpp
- Revision:
- 7:9cd16581b578
- Child:
- 8:5ea891c7d1a1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MaximInterfaceDevices/DS28C40.cpp Mon Jul 22 11:44:07 2019 -0500 @@ -0,0 +1,655 @@ +/******************************************************************************* +* Copyright (C) 2019 Maxim Integrated Products, Inc., All Rights Reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation +* the rights to use, copy, modify, merge, publish, distribute, sublicense, +* and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included +* in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL MAXIM INTEGRATED BE LIABLE FOR ANY CLAIM, DAMAGES +* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +* +* Except as contained in this notice, the name of Maxim Integrated +* Products, Inc. shall not be used except as stated in the Maxim Integrated +* Products, Inc. Branding Policy. +* +* The mere transfer of this software does not imply any licenses +* of trade secrets, proprietary technology, copyrights, patents, +* trademarks, maskwork rights, or any other form of intellectual +* property whatsoever. Maxim Integrated Products, Inc. retains all +* ownership rights. +*******************************************************************************/ + +#include <stddef.h> +#include <algorithm> +#include <MaximInterfaceCore/Error.hpp> +#include "DS28C40.hpp" + +namespace MaximInterfaceDevices { + +using namespace Core; +using std::copy; + +static const int readMemoryTimeMs = 2; +static const int writeMemoryTimeMs = 150; +static const int writeStateTimeMs = 15; +static const int generateEccKeyPairTimeMs = 500; +static const int generateEcdsaSignatureTimeMs = 50; +static const int computeTimeMs = 4; +static const int verifyEcdsaTimeMs = 160; +static const int trngGenerationTimeMs = 50; +static const int trngOnDemandCheckTimeMs = 50; + +static const uint_least8_t computeAndReadPageAuthenticationCmd = 0xA5; +static const uint_least8_t readRngCmd = 0xD2; + +const int DS28C40::publicKeyAxPage; +const int DS28C40::publicKeyAyPage; +const int DS28C40::publicKeyBxPage; +const int DS28C40::publicKeyByPage; +const int DS28C40::authorityPublicKeyAxPage; +const int DS28C40::authorityPublicKeyAyPage; +const int DS28C40::authorityPublicKeyBxPage; +const int DS28C40::authorityPublicKeyByPage; +const int DS28C40::privateKeyAPage; +const int DS28C40::privateKeyBPage; +const int DS28C40::secretAPage; +const int DS28C40::secretBPage; +const int DS28C40::romOptionsPage; +const int DS28C40::gpioControlPage; +const int DS28C40::publicKeySxPage; +const int DS28C40::publicKeySyPage; + +const int DS28C40::memoryPages; +const int DS28C40::protectionBlocks; + +error_code DS28C40::writeMemory(int pageNum, Page::const_span page) { + if (pageNum < 0 || pageNum >= memoryPages) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t request[2 + Page::size]; + request[0] = 0x96; + request[1] = pageNum; + copy(page.begin(), page.end(), request + 2); + return runCommand(request, writeMemoryTimeMs); +} + +error_code DS28C40::readMemory(int pageNum, Page::span page) { + if (pageNum < 0 || pageNum >= memoryPages) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t buffer[1 + Page::size]; + buffer[0] = 0x44; + buffer[1] = pageNum; + span<uint_least8_t> response(buffer); + const error_code result = + runCommand(make_span(buffer, 2), readMemoryTimeMs, response); + if (!result) { + copy(response.begin(), response.end(), page.begin()); + } + return result; +} + +error_code +DS28C40::encryptedReadMemory(int pageNum, KeySecret secret, + EncryptionChallenge::span encryptionChallenge, + Page::span encryptedPage) { + if (pageNum < 0 || pageNum >= memoryPages) { + return make_error_code(InvalidParameterError); + } + + const size_t requestSize = 3; + const size_t responseSize = 1 + EncryptionChallenge::size + Page::size; + uint_least8_t buffer[MaximInterfaceCore_MAX(requestSize, responseSize)]; + buffer[0] = 0x4B; + buffer[1] = pageNum; + buffer[2] = secret; + span<uint_least8_t> response(buffer, responseSize); + const error_code result = + runCommand(make_span(buffer, requestSize), + readMemoryTimeMs + computeTimeMs, response); + if (!result) { + span<uint_least8_t>::const_iterator begin = response.begin(); + span<uint_least8_t>::const_iterator end = + begin + encryptionChallenge.size(); + copy(begin, end, encryptionChallenge.begin()); + begin = end; + end = begin + encryptedPage.size(); + copy(begin, end, encryptedPage.begin()); + } + return result; +} + +error_code DS28C40::readBlockProtection(int blockNumber, + Optional<KeySecret> & keySecret, + BlockProtection & protection) { + if (blockNumber < 0 || blockNumber >= protectionBlocks) { + return make_error_code(InvalidParameterError); + } + + const size_t requestSize = 2; + const size_t responseSize = 3; + uint_least8_t buffer[MaximInterfaceCore_MAX(requestSize, responseSize)]; + buffer[0] = 0xAA; + buffer[1] = blockNumber; + span<uint_least8_t> response(buffer, responseSize); + const error_code result = + runCommand(make_span(buffer, requestSize), readMemoryTimeMs, response); + if (result) { + return result; + } + if ((response[0] & 0x3F) != blockNumber) { + return make_error_code(InvalidResponseError); + } + switch (response[0] >> 6) { + case 0: + keySecret = none; + break; + case 1: + keySecret = KeySecretA; + break; + case 2: + keySecret = KeySecretB; + break; + default: + return make_error_code(InvalidResponseError); + } + if ((response[1] & 0x20) != 0) { + return make_error_code(InvalidResponseError); + } + protection = response[1]; + return error_code(); +} + +error_code DS28C40::setBlockProtection(int blockNum, KeySecret keySecret, + const BlockProtection & protection) { + if (blockNum < 0 || blockNum >= protectionBlocks || keySecret == KeySecretS) { + return make_error_code(InvalidParameterError); + } + + const uint_least8_t request[] = { + 0xC3, + static_cast<uint_least8_t>((keySecret == KeySecretB ? 0x80 : 0x40) | + blockNum), + static_cast<uint_least8_t>(protection.to_ulong())}; + return runCommand(request, writeStateTimeMs); +} + +error_code +DS28C40::computeAndReadPageAuthentication(int pageNum, KeySecret key, + Page::const_span challenge, + Ecc256::Signature::span signature) { + if (pageNum < 0 || pageNum >= memoryPages || key == KeySecretS) { + return make_error_code(InvalidParameterError); + } + + const size_t requestSize = 3 + Page::size; + const size_t responseSize = 1 + 2 * Ecc256::Scalar::size; + uint_least8_t buffer[MaximInterfaceCore_MAX(requestSize, responseSize)]; + buffer[0] = computeAndReadPageAuthenticationCmd; + buffer[1] = pageNum; + buffer[2] = key + 3; + copy(challenge.begin(), challenge.end(), buffer + 3); + span<uint_least8_t> response(buffer, responseSize); + const error_code result = runCommand(make_span(buffer, requestSize), + generateEcdsaSignatureTimeMs, response); + if (!result) { + span<uint_least8_t>::const_iterator begin = response.begin(); + span<uint_least8_t>::const_iterator end = begin + signature.s.size(); + copy(begin, end, signature.s.begin()); + begin = end; + end = begin + signature.r.size(); + copy(begin, end, signature.r.begin()); + } + return result; +} + +error_code DS28C40::computeAndReadPageAuthentication(int pageNum, + KeySecret secret, + Page::const_span challenge, + Page::span hmac) { + if (pageNum < 0 || pageNum >= memoryPages) { + return make_error_code(InvalidParameterError); + } + + const size_t requestSize = 3 + Page::size; + const size_t responseSize = 1 + Page::size; + uint_least8_t buffer[MaximInterfaceCore_MAX(requestSize, responseSize)]; + buffer[0] = computeAndReadPageAuthenticationCmd; + buffer[1] = pageNum; + buffer[2] = secret; + copy(challenge.begin(), challenge.end(), buffer + 3); + span<uint_least8_t> response(buffer, responseSize); + const error_code result = + runCommand(make_span(buffer, requestSize), computeTimeMs, response); + if (!result) { + copy(response.begin(), response.end(), hmac.begin()); + } + return result; +} + +error_code DS28C40::computeMultiblockHash(bool firstBlock, bool lastBlock, + span<const uint_least8_t> data) { + const span<const uint_least8_t>::index_type maxDataSize = 64; + + if (data.size() < 1 || data.size() > maxDataSize) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t buffer[2 + maxDataSize]; + buffer[0] = 0x33; + buffer[1] = 0; + if (firstBlock) { + buffer[1] |= 0x40; + } + if (lastBlock) { + buffer[1] |= 0x80; + } + copy(data.begin(), data.end(), buffer + 2); + return runCommand(make_span(buffer, 2 + data.size()), computeTimeMs); +} + +error_code DS28C40::verifyEcdsaSignature( + KeySecret key, bool authorityKey, GpioState gpioState, + Ecc256::Signature::const_span signature, span<const uint_least8_t> data) { + return verifyEcdsaSignature(key, authorityKey, DataInput, gpioState, + signature, data); +} + +error_code DS28C40::verifyEcdsaSignature( + KeySecret key, bool authorityKey, GpioState gpioState, + Ecc256::Signature::const_span signature, Page::const_span hash) { + return verifyEcdsaSignature(key, authorityKey, HashInput, gpioState, + signature, hash); +} + +error_code +DS28C40::verifyEcdsaSignature(KeySecret key, bool authorityKey, + GpioState gpioState, + Ecc256::Signature::const_span signature) { + return verifyEcdsaSignature(key, authorityKey, THASH, gpioState, signature, + span<const uint_least8_t>()); +} + +error_code DS28C40::verifyEcdsaSignature( + KeySecret key, bool authorityKey, HashType hashType, GpioState gpioState, + Ecc256::Signature::const_span signature, span<const uint_least8_t> buffer) { + const span<const uint_least8_t>::index_type maxBufferSize = 61; + + if (buffer.size() > maxBufferSize) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t request[2 + 2 * Ecc256::Scalar::size + maxBufferSize]; + uint_least8_t * requestIt = request; + *requestIt++ = 0x59; + switch (key) { + case KeySecretA: + if (authorityKey) { + *requestIt = 2; + } else { + *requestIt = 0; + } + break; + case KeySecretB: + if (authorityKey) { + *requestIt = 3; + } else { + *requestIt = 1; + } + break; + case KeySecretS: + if (!authorityKey) { + *requestIt = 4; + break; + } + // else: Go to default case. + default: + return make_error_code(InvalidParameterError); + } + *requestIt |= hashType << 3; + if (gpioState != Unchanged) { + *requestIt |= 0x40; + } + if (gpioState == Conducting) { + *requestIt |= 0x20; + } + requestIt = copy(signature.r.begin(), signature.r.end(), ++requestIt); + requestIt = copy(signature.s.begin(), signature.s.end(), requestIt); + requestIt = copy(buffer.begin(), buffer.end(), requestIt); + return runCommand(make_span(request, requestIt), + verifyEcdsaTimeMs + + (hashType == DataInput ? computeTimeMs : 0)); +} + +error_code DS28C40::authenticateEcdsaPublicKey( + KeySecret key, Ecc256::Signature::const_span cert, + span<const uint_least8_t> certCustomization) { + return authenticateEcdsaPublicKey(key, true, cert, certCustomization, NULL); +} + +error_code DS28C40::authenticateEcdsaPublicKey( + KeySecret key, bool authWrites, Ecc256::Signature::const_span cert, + span<const uint_least8_t> certCustomization, + span<const uint_least8_t> ecdhCustomization) { + return authenticateEcdsaPublicKey(key, authWrites, cert, certCustomization, + &ecdhCustomization); +} + +error_code DS28C40::authenticateEcdsaPublicKey( + KeySecret key, bool authWrites, Ecc256::Signature::const_span cert, + span<const uint_least8_t> certCustomization, + const span<const uint_least8_t> * ecdhCustomization) { + const span<const uint_least8_t>::index_type minCustomizationSize = 1; + const span<const uint_least8_t>::index_type maxCertCustomizationSize = 32; + const span<const uint_least8_t>::index_type maxEcdhCustomizationSize = 48; + const span<const uint_least8_t>::index_type maxTotalCustomizationSize = 60; + + if (!(certCustomization.size() >= minCustomizationSize && + certCustomization.size() <= maxCertCustomizationSize && + (!ecdhCustomization || + (ecdhCustomization->size() >= minCustomizationSize && + ecdhCustomization->size() <= maxEcdhCustomizationSize && + certCustomization.size() + ecdhCustomization->size() <= + maxTotalCustomizationSize)))) { + return make_error_code(InvalidParameterError); + } + + if (key == KeySecretS) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t + request[2 + 2 * Ecc256::Scalar::size + maxTotalCustomizationSize]; + uint_least8_t * requestIt = request; + *requestIt++ = 0xA8; + *requestIt++ = static_cast<uint_least8_t>( + ((certCustomization.size() - 1) << 3) | (key << 2) | + ((ecdhCustomization != NULL) << 1) | (authWrites << 0)); + requestIt = copy(cert.r.begin(), cert.r.end(), requestIt); + requestIt = copy(cert.s.begin(), cert.s.end(), requestIt); + requestIt = + copy(certCustomization.begin(), certCustomization.end(), requestIt); + int delay = verifyEcdsaTimeMs; + if (ecdhCustomization) { + const span<const uint_least8_t>::index_type certCustomizationPaddingSize = + maxCertCustomizationSize - certCustomization.size(); + std::fill_n(requestIt, certCustomizationPaddingSize, 0); + requestIt += certCustomizationPaddingSize; + requestIt = + copy(ecdhCustomization->begin(), ecdhCustomization->end(), requestIt); + delay += verifyEcdsaTimeMs; + } + return runCommand(make_span(request, requestIt), delay); +} + +error_code DS28C40::authenticatedEcdsaWriteMemory( + int pageNum, bool useKeyS, Page::const_span newPageData, + Ecc256::Signature::const_span signature) { + return authenticatedEcdsaWriteMemory(pageNum, useKeyS, newPageData, signature, + NULL); +} + +error_code DS28C40::authenticatedEcdsaWriteMemory( + int pageNum, bool useKeyS, Page::const_span newPageData, + Ecc256::Signature::const_span signature, + EncryptionChallenge::const_span challenge) { + return authenticatedEcdsaWriteMemory(pageNum, useKeyS, newPageData, signature, + &challenge); +} + +error_code DS28C40::authenticatedEcdsaWriteMemory( + int pageNum, bool useKeyS, Page::const_span newPageData, + Ecc256::Signature::const_span signature, + const EncryptionChallenge::const_span * challenge) { + if (pageNum < 0 || pageNum >= memoryPages) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t request[2 + Page::size + 2 * Ecc256::Scalar::size + + EncryptionChallenge::size]; + uint_least8_t * requestIt = request; + *requestIt++ = 0x89; + *requestIt = pageNum; + if (useKeyS) { + *requestIt |= 0x80; + } + requestIt = copy(newPageData.begin(), newPageData.end(), ++requestIt); + requestIt = copy(signature.r.begin(), signature.r.end(), requestIt); + requestIt = copy(signature.s.begin(), signature.s.end(), requestIt); + int delay = verifyEcdsaTimeMs + writeMemoryTimeMs; + if (challenge) { + requestIt = copy(challenge->begin(), challenge->end(), requestIt); + delay += computeTimeMs; + } + return runCommand(make_span(request, requestIt), delay); +} + +error_code DS28C40::authenticatedSha256WriteMemory(int pageNum, bool useSecretS, + Page::const_span newPageData, + Page::const_span hmac) { + return authenticatedSha256WriteMemory(pageNum, useSecretS, newPageData, hmac, + NULL); +} + +error_code DS28C40::authenticatedSha256WriteMemory( + int pageNum, bool useSecretS, Page::const_span newPageData, + Page::const_span hmac, EncryptionChallenge::const_span challenge) { + return authenticatedSha256WriteMemory(pageNum, useSecretS, newPageData, hmac, + &challenge); +} + +error_code DS28C40::authenticatedSha256WriteMemory( + int pageNum, bool useSecretS, Page::const_span newPageData, + Page::const_span hmac, const EncryptionChallenge::const_span * challenge) { + if (pageNum < 0 || pageNum >= memoryPages) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t request[3 + 2 * Page::size + EncryptionChallenge::size]; + uint_least8_t * requestIt = request; + *requestIt++ = 0x99; + *requestIt++ = pageNum; + *requestIt++ = useSecretS ? 2 : 0; + requestIt = copy(newPageData.begin(), newPageData.end(), requestIt); + requestIt = copy(hmac.begin(), hmac.end(), requestIt); + int delay = writeMemoryTimeMs + computeTimeMs; + if (challenge) { + requestIt = copy(challenge->begin(), challenge->end(), requestIt); + delay += computeTimeMs; + } + return runCommand(make_span(request, requestIt), delay); +} + +error_code +DS28C40::computeAndWriteSha256Secret(int pageNum, KeySecret masterSecret, + KeySecret destinationSecret, + Page::const_span partialSecret) { + if (pageNum < 0 || pageNum >= memoryPages) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t request[3 + Page::size]; + request[0] = 0x3C; + request[1] = pageNum; + request[2] = (destinationSecret << 2) | masterSecret; + copy(partialSecret.begin(), partialSecret.end(), request + 3); + return runCommand(request, writeMemoryTimeMs + computeTimeMs); +} + +error_code DS28C40::generateEcc256KeyPair(KeySecret key) { + if (key == KeySecretS) { + return make_error_code(InvalidParameterError); + } + + const uint_least8_t request[] = {0xCB, key == KeySecretB}; + return runCommand(request, generateEccKeyPairTimeMs); +} + +error_code DS28C40::readRng(span<uint_least8_t> data) { + const span<uint_least8_t>::index_type maxDataSize = 64; + if ((data.size() < 1) || (data.size() > maxDataSize)) { + return make_error_code(InvalidParameterError); + } + + uint_least8_t buffer[1 + maxDataSize]; + buffer[0] = readRngCmd; + buffer[1] = static_cast<uint_least8_t>(data.size() - 1); + span<uint_least8_t> response(buffer, 1 + data.size()); + const error_code result = + runCommand(make_span(buffer, 2), trngGenerationTimeMs, response); + if (!result) { + copy(response.begin(), response.end(), data.begin()); + } + return result; +} + +error_code DS28C40::entropyHealthTest() { + const uint_least8_t request[] = {readRngCmd, 0x80}; + return runCommand(request, trngOnDemandCheckTimeMs); +} + +error_code DS28C40::runCommand(span<const uint_least8_t> request, int delayTime, + span<uint_least8_t> & response) { + const span<const uint_least8_t>::index_type responseInputSize = + response.size(); + error_code result = doRunCommand(request, delayTime, response); + if (result) { + return result; + } + if (response.empty()) { + return make_error_code(InvalidResponseError); + } + // Parse command result byte. + switch (response[0]) { + case 0xAA: + // Success response. + if (response.size() != responseInputSize) { + result = make_error_code(InvalidResponseError); + } + break; + + case 0x00: + result = make_error_code(AuthenticationError); + break; + + default: + result.assign(response[0], errorCategory()); + break; + } + response = response.subspan(1); + return result; +} + +error_code DS28C40::runCommand(span<const uint_least8_t> request, + int delayTime) { + uint_least8_t buffer; + span<uint_least8_t> response(&buffer, 1); + return runCommand(request, delayTime, response); +} + +const error_category & DS28C40::errorCategory() { + static class : public error_category { + public: + virtual const char * name() const { return "DS28C40"; } + + virtual std::string message(int condition) const { + switch (condition) { + case InvalidOperationError: + return "Invalid Operation Error"; + + case InvalidParameterError: + return "Invalid Parameter Error"; + + case InvalidSequenceError: + return "Invalid Sequence Error"; + + case AuthenticationError: + return "Authentication Error"; + + case InternalError: + return "Internal Error"; + + case DeviceDisabledError: + return "Device Disabled Error"; + + case InvalidResponseError: + return "Invalid Response Error"; + } + return defaultErrorMessage(condition); + } + } instance; + return instance; +} + +error_code computeMultiblockHash(DS28C40 & device, + span<const uint_least8_t> data) { + error_code result; + span<const uint_least8_t>::index_type dataIdx = 0; + while (dataIdx < data.size() && !result) { + const span<const uint_least8_t>::index_type remainingSize = + data.size() - dataIdx; + const span<const uint_least8_t>::index_type chunkSize = + std::min<span<const uint_least8_t>::index_type>(remainingSize, 64); + result = + device.computeMultiblockHash(dataIdx == 0, remainingSize == chunkSize, + data.subspan(dataIdx, chunkSize)); + dataIdx += chunkSize; + } + return result; +} + +error_code readRomIdAndManId(DS28C40 & device, RomId::span romId, + ManId::span manId) { + DS28C40::Page::array page; + error_code result = device.readMemory(DS28C40::romOptionsPage, page); + if (!result) { + const DS28C40::RomOptions romOptions(page); + copy(romOptions.romId(), romId); + copy(romOptions.manId(), manId); + } + return result; +} + +static void setAnonymous(RomId::span romId) { + std::fill(romId.begin(), romId.end(), 0xFF); +} + +DS28C40::PageAuthenticationData & +DS28C40::PageAuthenticationData::setAnonymousRomId() { + setAnonymous(romId()); + return *this; +} + +DS28C40::ComputeSecretData::ComputeSecretData() : data() { + setPageNum(0); + setManId(ManId::array()); +} + +DS28C40::ComputeSecretData & +DS28C40::ComputeSecretData::setManId(ManId::const_span manId) { + ManId::array validatedManId; + copy(manId, make_span(validatedManId)); + validatedManId[1] |= 0x80; + data.setManId(validatedManId); + return *this; +} + +DS28C40::DecryptionHmacData & DS28C40::DecryptionHmacData::setAnonymousRomId() { + setAnonymous(romId()); + return *this; +} + +} // namespace MaximInterfaceDevices