|  | /* | 
|  | * | 
|  | *    Copyright (c) 2020-2022 Project CHIP Authors | 
|  | * | 
|  | *    Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | *    you may not use this file except in compliance with the License. | 
|  | *    You may obtain a copy of the License at | 
|  | * | 
|  | *        http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | *    Unless required by applicable law or agreed to in writing, software | 
|  | *    distributed under the License is distributed on an "AS IS" BASIS, | 
|  | *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | *    See the License for the specific language governing permissions and | 
|  | *    limitations under the License. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | *    @file | 
|  | *      Platform agnostic implementation of CHIP crypto algorithms | 
|  | */ | 
|  |  | 
|  | #include "CHIPCryptoPAL.h" | 
|  | #include <lib/asn1/ASN1.h> | 
|  | #include <lib/asn1/ASN1Macros.h> | 
|  | #include <lib/core/CHIPEncoding.h> | 
|  | #include <lib/support/BufferReader.h> | 
|  | #include <lib/support/BufferWriter.h> | 
|  | #include <lib/support/BytesToHex.h> | 
|  | #include <lib/support/CodeUtils.h> | 
|  | #include <lib/support/Span.h> | 
|  | #include <string.h> | 
|  |  | 
|  | using chip::ByteSpan; | 
|  | using chip::MutableByteSpan; | 
|  | using chip::Encoding::BufferWriter; | 
|  | using chip::Encoding::LittleEndian::Reader; | 
|  |  | 
|  | using namespace chip::ASN1; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | constexpr uint8_t kIntegerTag         = 0x02u; | 
|  | constexpr uint8_t kSeqTag             = 0x30u; | 
|  | constexpr size_t kMinSequenceOverhead = 1 /* tag */ + 1 /* length */ + 1 /* actual data or second length byte*/; | 
|  |  | 
|  | /** | 
|  | * @brief Utility to read a length field after a tag in a DER-encoded stream. | 
|  | * @param[in] reader Reader instance from which the input will be read | 
|  | * @param[out] length Length of the following element read from the stream | 
|  | * @return CHIP_ERROR_INVALID_ARGUMENT or CHIP_ERROR_BUFFER_TOO_SMALL on error, CHIP_NO_ERROR otherwise | 
|  | */ | 
|  | CHIP_ERROR ReadDerLength(Reader & reader, uint8_t & length) | 
|  | { | 
|  | length = 0; | 
|  |  | 
|  | uint8_t cur_byte = 0; | 
|  | ReturnErrorOnFailure(reader.Read8(&cur_byte).StatusCode()); | 
|  |  | 
|  | if ((cur_byte & (1u << 7)) == 0) | 
|  | { | 
|  | // 7 bit length, the rest of the byte is the length. | 
|  | length = cur_byte & 0x7Fu; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | // Did not early return: > 7 bit length, the number of bytes of the length is provided next. | 
|  | uint8_t length_bytes = cur_byte & 0x7Fu; | 
|  |  | 
|  | if ((length_bytes > 1) || !reader.HasAtLeast(length_bytes)) | 
|  | { | 
|  | // We only support lengths of 0..255 over 2 bytes | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | // Next byte has length 0..255. | 
|  | return reader.Read8(&length).StatusCode(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief Utility to convert DER-encoded INTEGER into a raw integer buffer in big-endian order | 
|  | *        with leading zeroes if the output buffer is larger than needed. | 
|  | * @param[in] reader Reader instance from which the input will be read | 
|  | * @param[out] raw_integer_out Buffer to receive the DER-encoded integer | 
|  | * @return CHIP_ERROR_INVALID_ARGUMENT or CHIP_ERROR_BUFFER_TOO_SMALL on error, CHIP_NO_ERROR otherwise | 
|  | */ | 
|  | CHIP_ERROR ReadDerUnsignedIntegerIntoRaw(Reader & reader, MutableByteSpan raw_integer_out) | 
|  | { | 
|  | uint8_t cur_byte = 0; | 
|  |  | 
|  | ReturnErrorOnFailure(reader.Read8(&cur_byte).StatusCode()); | 
|  |  | 
|  | // We expect first tag to be INTEGER | 
|  | VerifyOrReturnError(cur_byte == kIntegerTag, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | // Read the length | 
|  | uint8_t integer_len = 0; | 
|  | ReturnErrorOnFailure(ReadDerLength(reader, integer_len)); | 
|  |  | 
|  | // Clear the destination buffer, so we can blit the unsigned value into place | 
|  | memset(raw_integer_out.data(), 0, raw_integer_out.size()); | 
|  |  | 
|  | // Check for pseudo-zero to mark unsigned value | 
|  | // This means we have too large an integer (should be at most 1 byte too large), it's invalid | 
|  | ReturnErrorCodeIf(integer_len > (raw_integer_out.size() + 1), CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | if (integer_len == (raw_integer_out.size() + 1u)) | 
|  | { | 
|  | // Means we had a 0x00 byte stuffed due to MSB being high in original integer | 
|  | ReturnErrorOnFailure(reader.Read8(&cur_byte).StatusCode()); | 
|  |  | 
|  | // The extra byte must be a leading zero | 
|  | VerifyOrReturnError(cur_byte == 0, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | --integer_len; | 
|  | } | 
|  |  | 
|  | // We now have the rest of the tag that is a "minimal length" unsigned integer. | 
|  | // Blit it at the correct offset, since the order we use is MSB first for | 
|  | // both ASN.1 and EC curve raw points. | 
|  | size_t offset = raw_integer_out.size() - integer_len; | 
|  | return reader.ReadBytes(raw_integer_out.data() + offset, integer_len).StatusCode(); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ConvertIntegerRawToDerInternal(const ByteSpan & raw_integer, MutableByteSpan & out_der_integer, | 
|  | bool include_tag_and_length) | 
|  | { | 
|  | if (!IsSpanUsable(raw_integer) || !IsSpanUsable(out_der_integer)) | 
|  | { | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | Reader reader(raw_integer); | 
|  | BufferWriter writer(out_der_integer); | 
|  |  | 
|  | bool needs_leading_zero_byte = false; | 
|  |  | 
|  | uint8_t cur_byte = 0; | 
|  | while ((reader.Remaining() > 0) && (reader.Read8(&cur_byte).StatusCode() == CHIP_NO_ERROR) && (cur_byte == 0)) | 
|  | { | 
|  | // Omit all leading zeros | 
|  | } | 
|  |  | 
|  | if ((cur_byte & 0x80u) != 0) | 
|  | { | 
|  | // If overall MSB (from leftmost byte) is set, we will need to push out a zero to avoid it being | 
|  | // considered a negative number. | 
|  | needs_leading_zero_byte = true; | 
|  | } | 
|  |  | 
|  | // The + 1 is to account for the last consumed byte of the loop to skip leading zeros | 
|  | size_t length = reader.Remaining() + 1 + (needs_leading_zero_byte ? 1 : 0); | 
|  |  | 
|  | if (length > 127) | 
|  | { | 
|  | // We do not support length over more than 1 bytes. | 
|  | return CHIP_ERROR_INVALID_ARGUMENT; | 
|  | } | 
|  |  | 
|  | if (include_tag_and_length) | 
|  | { | 
|  | // Put INTEGER tag | 
|  | writer.Put(kIntegerTag); | 
|  |  | 
|  | // Put length over 1 byte (i.e. MSB clear) | 
|  | writer.Put(static_cast<uint8_t>(length)); | 
|  | } | 
|  |  | 
|  | // If leading zero or no more bytes remaining, must ensure we start with at least a zero byte | 
|  | if (needs_leading_zero_byte) | 
|  | { | 
|  | writer.Put(static_cast<uint8_t>(0u)); | 
|  | } | 
|  |  | 
|  | // Put first consumed byte from last read iteration of leading zero suppression | 
|  | writer.Put(cur_byte); | 
|  |  | 
|  | // Fill the rest from the input in order | 
|  | while (reader.Read8(&cur_byte).StatusCode() == CHIP_NO_ERROR) | 
|  | { | 
|  | // Emit all other bytes as-is | 
|  | writer.Put(cur_byte); | 
|  | } | 
|  |  | 
|  | size_t actually_written = 0; | 
|  | if (!writer.Fit(actually_written)) | 
|  | { | 
|  | return CHIP_ERROR_BUFFER_TOO_SMALL; | 
|  | } | 
|  |  | 
|  | out_der_integer = out_der_integer.SubSpan(0, actually_written); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | namespace chip { | 
|  | namespace Crypto { | 
|  |  | 
|  | #ifdef ENABLE_HSM_HKDF | 
|  | using HKDF_sha_crypto = HKDF_shaHSM; | 
|  | #else | 
|  | using HKDF_sha_crypto = HKDF_sha; | 
|  | #endif | 
|  |  | 
|  | CHIP_ERROR Spake2p::InternalHash(const uint8_t * in, size_t in_len) | 
|  | { | 
|  | const uint64_t u64_len = in_len; | 
|  |  | 
|  | uint8_t lb[8]; | 
|  | lb[0] = static_cast<uint8_t>((u64_len >> 0) & 0xff); | 
|  | lb[1] = static_cast<uint8_t>((u64_len >> 8) & 0xff); | 
|  | lb[2] = static_cast<uint8_t>((u64_len >> 16) & 0xff); | 
|  | lb[3] = static_cast<uint8_t>((u64_len >> 24) & 0xff); | 
|  | lb[4] = static_cast<uint8_t>((u64_len >> 32) & 0xff); | 
|  | lb[5] = static_cast<uint8_t>((u64_len >> 40) & 0xff); | 
|  | lb[6] = static_cast<uint8_t>((u64_len >> 48) & 0xff); | 
|  | lb[7] = static_cast<uint8_t>((u64_len >> 56) & 0xff); | 
|  |  | 
|  | ReturnErrorOnFailure(Hash(lb, sizeof(lb))); | 
|  | if (in != nullptr) | 
|  | { | 
|  | ReturnErrorOnFailure(Hash(in, in_len)); | 
|  | } | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | Spake2p::Spake2p(size_t _fe_size, size_t _point_size, size_t _hash_size) | 
|  | { | 
|  | fe_size    = _fe_size; | 
|  | point_size = _point_size; | 
|  | hash_size  = _hash_size; | 
|  |  | 
|  | Kca = &Kcab[0]; | 
|  | Kcb = &Kcab[hash_size / 2]; | 
|  | Ka  = &Kae[0]; | 
|  | Ke  = &Kae[hash_size / 2]; | 
|  |  | 
|  | M  = nullptr; | 
|  | N  = nullptr; | 
|  | G  = nullptr; | 
|  | X  = nullptr; | 
|  | Y  = nullptr; | 
|  | L  = nullptr; | 
|  | Z  = nullptr; | 
|  | V  = nullptr; | 
|  | w0 = nullptr; | 
|  | w1 = nullptr; | 
|  | xy = nullptr; | 
|  |  | 
|  | order  = nullptr; | 
|  | tempbn = nullptr; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::Init(const uint8_t * context, size_t context_len) | 
|  | { | 
|  | if (state != CHIP_SPAKE2P_STATE::PREINIT) | 
|  | { | 
|  | Clear(); | 
|  | } | 
|  |  | 
|  | ReturnErrorOnFailure(InitImpl()); | 
|  | ReturnErrorOnFailure(PointLoad(spake2p_M_p256, sizeof(spake2p_M_p256), M)); | 
|  | ReturnErrorOnFailure(PointLoad(spake2p_N_p256, sizeof(spake2p_N_p256), N)); | 
|  | ReturnErrorOnFailure(InternalHash(context, context_len)); | 
|  |  | 
|  | state = CHIP_SPAKE2P_STATE::INIT; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::WriteMN() | 
|  | { | 
|  | ReturnErrorOnFailure(InternalHash(spake2p_M_p256, sizeof(spake2p_M_p256))); | 
|  | ReturnErrorOnFailure(InternalHash(spake2p_N_p256, sizeof(spake2p_N_p256))); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::BeginVerifier(const uint8_t * my_identity, size_t my_identity_len, const uint8_t * peer_identity, | 
|  | size_t peer_identity_len, const uint8_t * w0in, size_t w0in_len, const uint8_t * Lin, | 
|  | size_t Lin_len) | 
|  | { | 
|  | VerifyOrReturnError(state == CHIP_SPAKE2P_STATE::INIT, CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | ReturnErrorOnFailure(InternalHash(peer_identity, peer_identity_len)); | 
|  | ReturnErrorOnFailure(InternalHash(my_identity, my_identity_len)); | 
|  | ReturnErrorOnFailure(WriteMN()); | 
|  | ReturnErrorOnFailure(FELoad(w0in, w0in_len, w0)); | 
|  | ReturnErrorOnFailure(PointLoad(Lin, Lin_len, L)); | 
|  |  | 
|  | state = CHIP_SPAKE2P_STATE::STARTED; | 
|  | role  = CHIP_SPAKE2P_ROLE::VERIFIER; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::BeginProver(const uint8_t * my_identity, size_t my_identity_len, const uint8_t * peer_identity, | 
|  | size_t peer_identity_len, const uint8_t * w0in, size_t w0in_len, const uint8_t * w1in, | 
|  | size_t w1in_len) | 
|  | { | 
|  | VerifyOrReturnError(state == CHIP_SPAKE2P_STATE::INIT, CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | ReturnErrorOnFailure(InternalHash(my_identity, my_identity_len)); | 
|  | ReturnErrorOnFailure(InternalHash(peer_identity, peer_identity_len)); | 
|  | ReturnErrorOnFailure(WriteMN()); | 
|  | ReturnErrorOnFailure(FELoad(w0in, w0in_len, w0)); | 
|  | ReturnErrorOnFailure(FELoad(w1in, w1in_len, w1)); | 
|  |  | 
|  | state = CHIP_SPAKE2P_STATE::STARTED; | 
|  | role  = CHIP_SPAKE2P_ROLE::PROVER; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::ComputeRoundOne(const uint8_t * pab, size_t pab_len, uint8_t * out, size_t * out_len) | 
|  | { | 
|  | CHIP_ERROR error = CHIP_ERROR_INTERNAL; | 
|  | void * MN        = nullptr; // Choose M if a prover, N if a verifier | 
|  | void * XY        = nullptr; // Choose X if a prover, Y if a verifier | 
|  |  | 
|  | VerifyOrExit(state == CHIP_SPAKE2P_STATE::STARTED, error = CHIP_ERROR_INTERNAL); | 
|  | VerifyOrExit(*out_len >= point_size, error = CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | ReturnErrorOnFailure(FEGenerate(xy)); | 
|  |  | 
|  | if (role == CHIP_SPAKE2P_ROLE::PROVER) | 
|  | { | 
|  | MN = M; | 
|  | XY = X; | 
|  | } | 
|  | else if (role == CHIP_SPAKE2P_ROLE::VERIFIER) | 
|  | { | 
|  | MN = N; | 
|  | XY = Y; | 
|  | } | 
|  | VerifyOrExit(MN != nullptr, error = CHIP_ERROR_INTERNAL); | 
|  | VerifyOrExit(XY != nullptr, error = CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | SuccessOrExit(error = PointAddMul(XY, G, xy, MN, w0)); | 
|  | SuccessOrExit(error = PointWrite(XY, out, *out_len)); | 
|  |  | 
|  | state = CHIP_SPAKE2P_STATE::R1; | 
|  | error = CHIP_NO_ERROR; | 
|  | exit: | 
|  | *out_len = point_size; | 
|  | return error; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::ComputeRoundTwo(const uint8_t * in, size_t in_len, uint8_t * out, size_t * out_len) | 
|  | { | 
|  | CHIP_ERROR error = CHIP_ERROR_INTERNAL; | 
|  | MutableByteSpan out_span{ out, *out_len }; | 
|  | uint8_t point_buffer[kMAX_Point_Length]; | 
|  | void * MN        = nullptr; // Choose N if a prover, M if a verifier | 
|  | void * XY        = nullptr; // Choose Y if a prover, X if a verifier | 
|  | uint8_t * Kcaorb = nullptr; // Choose Kca if a prover, Kcb if a verifier | 
|  |  | 
|  | VerifyOrExit(*out_len >= hash_size, error = CHIP_ERROR_INTERNAL); | 
|  | VerifyOrExit(state == CHIP_SPAKE2P_STATE::R1, error = CHIP_ERROR_INTERNAL); | 
|  | VerifyOrExit(in_len == point_size, error = CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | if (role == CHIP_SPAKE2P_ROLE::PROVER) | 
|  | { | 
|  | SuccessOrExit(error = PointWrite(X, point_buffer, point_size)); | 
|  | SuccessOrExit(error = InternalHash(point_buffer, point_size)); | 
|  | SuccessOrExit(error = InternalHash(in, in_len)); | 
|  |  | 
|  | MN     = N; | 
|  | XY     = Y; | 
|  | Kcaorb = Kca; | 
|  | } | 
|  | else if (role == CHIP_SPAKE2P_ROLE::VERIFIER) | 
|  | { | 
|  | SuccessOrExit(error = InternalHash(in, in_len)); | 
|  | SuccessOrExit(error = PointWrite(Y, point_buffer, point_size)); | 
|  | SuccessOrExit(error = InternalHash(point_buffer, point_size)); | 
|  |  | 
|  | MN     = M; | 
|  | XY     = X; | 
|  | Kcaorb = Kcb; | 
|  | } | 
|  | VerifyOrExit(MN != nullptr, error = CHIP_ERROR_INTERNAL); | 
|  | VerifyOrExit(XY != nullptr, error = CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | SuccessOrExit(error = PointLoad(in, in_len, XY)); | 
|  | SuccessOrExit(error = PointIsValid(XY)); | 
|  | SuccessOrExit(error = FEMul(tempbn, xy, w0)); | 
|  | SuccessOrExit(error = PointInvert(MN)); | 
|  | SuccessOrExit(error = PointAddMul(Z, XY, xy, MN, tempbn)); | 
|  | SuccessOrExit(error = PointCofactorMul(Z)); | 
|  |  | 
|  | if (role == CHIP_SPAKE2P_ROLE::PROVER) | 
|  | { | 
|  | SuccessOrExit(error = FEMul(tempbn, w1, w0)); | 
|  | SuccessOrExit(error = PointAddMul(V, XY, w1, MN, tempbn)); | 
|  | } | 
|  | else if (role == CHIP_SPAKE2P_ROLE::VERIFIER) | 
|  | { | 
|  | SuccessOrExit(error = PointMul(V, L, xy)); | 
|  | } | 
|  |  | 
|  | SuccessOrExit(error = PointCofactorMul(V)); | 
|  | SuccessOrExit(error = PointWrite(Z, point_buffer, point_size)); | 
|  | SuccessOrExit(error = InternalHash(point_buffer, point_size)); | 
|  |  | 
|  | SuccessOrExit(error = PointWrite(V, point_buffer, point_size)); | 
|  | SuccessOrExit(error = InternalHash(point_buffer, point_size)); | 
|  |  | 
|  | SuccessOrExit(error = FEWrite(w0, point_buffer, fe_size)); | 
|  | SuccessOrExit(error = InternalHash(point_buffer, fe_size)); | 
|  |  | 
|  | SuccessOrExit(error = GenerateKeys()); | 
|  |  | 
|  | SuccessOrExit(error = Mac(Kcaorb, hash_size / 2, in, in_len, out_span)); | 
|  | VerifyOrExit(out_span.size() == hash_size, error = CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | state = CHIP_SPAKE2P_STATE::R2; | 
|  | error = CHIP_NO_ERROR; | 
|  | exit: | 
|  | *out_len = hash_size; | 
|  | return error; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::GenerateKeys() | 
|  | { | 
|  | static const uint8_t info_keyconfirm[16] = { 'C', 'o', 'n', 'f', 'i', 'r', 'm', 'a', 't', 'i', 'o', 'n', 'K', 'e', 'y', 's' }; | 
|  |  | 
|  | MutableByteSpan Kae_span{ &Kae[0], sizeof(Kae) }; | 
|  |  | 
|  | ReturnErrorOnFailure(HashFinalize(Kae_span)); | 
|  | ReturnErrorOnFailure(KDF(Ka, hash_size / 2, nullptr, 0, info_keyconfirm, sizeof(info_keyconfirm), Kcab, hash_size)); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::KeyConfirm(const uint8_t * in, size_t in_len) | 
|  | { | 
|  | uint8_t point_buffer[kP256_Point_Length]; | 
|  | void * XY        = nullptr; // Choose X if a prover, Y if a verifier | 
|  | uint8_t * Kcaorb = nullptr; // Choose Kcb if a prover, Kca if a verifier | 
|  |  | 
|  | VerifyOrReturnError(state == CHIP_SPAKE2P_STATE::R2, CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | if (role == CHIP_SPAKE2P_ROLE::PROVER) | 
|  | { | 
|  | XY     = X; | 
|  | Kcaorb = Kcb; | 
|  | } | 
|  | else if (role == CHIP_SPAKE2P_ROLE::VERIFIER) | 
|  | { | 
|  | XY     = Y; | 
|  | Kcaorb = Kca; | 
|  | } | 
|  | VerifyOrReturnError(XY != nullptr, CHIP_ERROR_INTERNAL); | 
|  | VerifyOrReturnError(Kcaorb != nullptr, CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | ReturnErrorOnFailure(PointWrite(XY, point_buffer, point_size)); | 
|  |  | 
|  | CHIP_ERROR err = MacVerify(Kcaorb, hash_size / 2, in, in_len, point_buffer, point_size); | 
|  | if (err == CHIP_ERROR_INTERNAL) | 
|  | { | 
|  | ChipLogError(SecureChannel, "Failed to verify peer's MAC. This can happen when setup code is incorrect."); | 
|  | } | 
|  | ReturnErrorOnFailure(err); | 
|  |  | 
|  | state = CHIP_SPAKE2P_STATE::KC; | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p::GetKeys(uint8_t * out, size_t * out_len) | 
|  | { | 
|  | CHIP_ERROR error = CHIP_ERROR_INTERNAL; | 
|  |  | 
|  | VerifyOrExit(state == CHIP_SPAKE2P_STATE::KC, error = CHIP_ERROR_INTERNAL); | 
|  | VerifyOrExit(*out_len >= hash_size / 2, error = CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | memcpy(out, Ke, hash_size / 2); | 
|  | error = CHIP_NO_ERROR; | 
|  | exit: | 
|  | *out_len = hash_size / 2; | 
|  | return error; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::InitImpl() | 
|  | { | 
|  | ReturnErrorOnFailure(sha256_hash_ctx.Begin()); | 
|  | ReturnErrorOnFailure(InitInternal()); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::Hash(const uint8_t * in, size_t in_len) | 
|  | { | 
|  | ReturnErrorOnFailure(sha256_hash_ctx.AddData(ByteSpan{ in, in_len })); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::HashFinalize(MutableByteSpan & out_span) | 
|  | { | 
|  | ReturnErrorOnFailure(sha256_hash_ctx.Finish(out_span)); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::KDF(const uint8_t * ikm, const size_t ikm_len, const uint8_t * salt, | 
|  | const size_t salt_len, const uint8_t * info, const size_t info_len, uint8_t * out, | 
|  | size_t out_len) | 
|  | { | 
|  | HKDF_sha_crypto mHKDF; | 
|  |  | 
|  | ReturnErrorOnFailure(mHKDF.HKDF_SHA256(ikm, ikm_len, salt, salt_len, info, info_len, out, out_len)); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::ComputeW0(uint8_t * w0out, size_t * w0_len, const uint8_t * w0sin, size_t w0sin_len) | 
|  | { | 
|  | ReturnErrorOnFailure(FELoad(w0sin, w0sin_len, w0)); | 
|  | ReturnErrorOnFailure(FEWrite(w0, w0out, *w0_len)); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2pVerifier::Serialize(MutableByteSpan & outSerialized) const | 
|  | { | 
|  | VerifyOrReturnError(outSerialized.size() >= kSpake2p_VerifierSerialized_Length, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | memcpy(&outSerialized.data()[0], mW0, sizeof(mW0)); | 
|  | memcpy(&outSerialized.data()[sizeof(mW0)], mL, sizeof(mL)); | 
|  |  | 
|  | outSerialized.reduce_size(kSpake2p_VerifierSerialized_Length); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2pVerifier::Deserialize(const ByteSpan & inSerialized) | 
|  | { | 
|  | VerifyOrReturnError(inSerialized.size() >= kSpake2p_VerifierSerialized_Length, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | memcpy(mW0, &inSerialized.data()[0], sizeof(mW0)); | 
|  | memcpy(mL, &inSerialized.data()[sizeof(mW0)], sizeof(mL)); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2pVerifier::Generate(uint32_t pbkdf2IterCount, const ByteSpan & salt, uint32_t setupPin) | 
|  | { | 
|  | uint8_t serializedWS[kSpake2p_WS_Length * 2] = { 0 }; | 
|  | ReturnErrorOnFailure(ComputeWS(pbkdf2IterCount, salt, setupPin, serializedWS, sizeof(serializedWS))); | 
|  |  | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  | size_t len; | 
|  |  | 
|  | // Create local Spake2+ object for w0 and L computations. | 
|  | #ifdef ENABLE_HSM_SPAKE | 
|  | Spake2pHSM_P256_SHA256_HKDF_HMAC spake2p; | 
|  | #else | 
|  | Spake2p_P256_SHA256_HKDF_HMAC spake2p; | 
|  | #endif | 
|  | uint8_t context[kSHA256_Hash_Length] = { 0 }; | 
|  | SuccessOrExit(err = spake2p.Init(context, sizeof(context))); | 
|  |  | 
|  | // Compute w0 | 
|  | len = sizeof(mW0); | 
|  | SuccessOrExit(err = spake2p.ComputeW0(mW0, &len, &serializedWS[0], kSpake2p_WS_Length)); | 
|  | VerifyOrExit(len == sizeof(mW0), err = CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | // Compute L | 
|  | len = sizeof(mL); | 
|  | SuccessOrExit(err = spake2p.ComputeL(mL, &len, &serializedWS[kSpake2p_WS_Length], kSpake2p_WS_Length)); | 
|  | VerifyOrExit(len == sizeof(mL), err = CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | exit: | 
|  | spake2p.Clear(); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR Spake2pVerifier::ComputeWS(uint32_t pbkdf2IterCount, const ByteSpan & salt, uint32_t setupPin, uint8_t * ws, | 
|  | uint32_t ws_len) | 
|  | { | 
|  | #ifdef ENABLE_HSM_PBKDF2 | 
|  | PBKDF2_sha256HSM pbkdf2; | 
|  | #else | 
|  | PBKDF2_sha256 pbkdf2; | 
|  | #endif | 
|  | uint8_t littleEndianSetupPINCode[sizeof(uint32_t)]; | 
|  | Encoding::LittleEndian::Put32(littleEndianSetupPINCode, setupPin); | 
|  |  | 
|  | ReturnErrorCodeIf(salt.size() < kSpake2p_Min_PBKDF_Salt_Length || salt.size() > kSpake2p_Max_PBKDF_Salt_Length, | 
|  | CHIP_ERROR_INVALID_ARGUMENT); | 
|  | ReturnErrorCodeIf(pbkdf2IterCount < kSpake2p_Min_PBKDF_Iterations || pbkdf2IterCount > kSpake2p_Max_PBKDF_Iterations, | 
|  | CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | return pbkdf2.pbkdf2_sha256(littleEndianSetupPINCode, sizeof(littleEndianSetupPINCode), salt.data(), salt.size(), | 
|  | pbkdf2IterCount, ws_len, ws); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ConvertIntegerRawToDerWithoutTag(const ByteSpan & raw_integer, MutableByteSpan & out_der_integer) | 
|  | { | 
|  | return ConvertIntegerRawToDerInternal(raw_integer, out_der_integer, /* include_tag_and_length = */ false); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ConvertIntegerRawToDer(const ByteSpan & raw_integer, MutableByteSpan & out_der_integer) | 
|  | { | 
|  | return ConvertIntegerRawToDerInternal(raw_integer, out_der_integer, /* include_tag_and_length = */ true); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR EcdsaRawSignatureToAsn1(size_t fe_length_bytes, const ByteSpan & raw_sig, MutableByteSpan & out_asn1_sig) | 
|  | { | 
|  | VerifyOrReturnError(fe_length_bytes > 0, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(raw_sig.size() == (2u * fe_length_bytes), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(out_asn1_sig.size() >= (raw_sig.size() + kMax_ECDSA_X9Dot62_Asn1_Overhead), CHIP_ERROR_BUFFER_TOO_SMALL); | 
|  |  | 
|  | // Write both R an S integers past the overhead, we will shift them back later if we only needed 2 size bytes. | 
|  | uint8_t * cursor = out_asn1_sig.data() + kMinSequenceOverhead; | 
|  | size_t remaining = out_asn1_sig.size() - kMinSequenceOverhead; | 
|  |  | 
|  | size_t integers_length = 0; | 
|  |  | 
|  | // Write R (first `fe_length_bytes` block of raw signature) | 
|  | { | 
|  | MutableByteSpan out_der_integer(cursor, remaining); | 
|  | ReturnErrorOnFailure(ConvertIntegerRawToDer(raw_sig.SubSpan(0, fe_length_bytes), out_der_integer)); | 
|  | VerifyOrReturnError(out_der_integer.size() <= remaining, CHIP_ERROR_INTERNAL); | 
|  |  | 
|  | integers_length += out_der_integer.size(); | 
|  | remaining -= out_der_integer.size(); | 
|  | cursor += out_der_integer.size(); | 
|  | } | 
|  |  | 
|  | // Write S (second `fe_length_bytes` block of raw signature) | 
|  | { | 
|  | MutableByteSpan out_der_integer(cursor, remaining); | 
|  | ReturnErrorOnFailure(ConvertIntegerRawToDer(raw_sig.SubSpan(fe_length_bytes, fe_length_bytes), out_der_integer)); | 
|  | VerifyOrReturnError(out_der_integer.size() <= remaining, CHIP_ERROR_INTERNAL); | 
|  | integers_length += out_der_integer.size(); | 
|  | } | 
|  |  | 
|  | // We only support outputs that would use 1 or 2 bytes of DER length after the SEQUENCE tag | 
|  | VerifyOrReturnError(integers_length <= UINT8_MAX, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | // We now know the length of both variable sized integers in the sequence, so we | 
|  | // can write the tag and length. | 
|  | BufferWriter writer(out_asn1_sig); | 
|  |  | 
|  | // Put SEQUENCE tag | 
|  | writer.Put(kSeqTag); | 
|  |  | 
|  | // Put the length over 1 or two bytes depending on case | 
|  | constexpr uint8_t kExtendedLengthMarker = 0x80u; | 
|  | if (integers_length > 127u) | 
|  | { | 
|  | writer.Put(static_cast<uint8_t>(kExtendedLengthMarker | 1)); // Length is extended length, over 1 subsequent byte | 
|  | writer.Put(static_cast<uint8_t>(integers_length)); | 
|  | } | 
|  | else | 
|  | { | 
|  | // Length is directly in the first byte with MSB clear if <= 127. | 
|  | writer.Put(static_cast<uint8_t>(integers_length)); | 
|  | } | 
|  |  | 
|  | // Put the contents of the integers previously serialized in the buffer. | 
|  | // The writer.Put is memmove-safe, so the shifting will happen from the read | 
|  | // of the same buffer where the write is taking place. | 
|  | writer.Put(out_asn1_sig.data() + kMinSequenceOverhead, integers_length); | 
|  |  | 
|  | size_t actually_written = 0; | 
|  | VerifyOrReturnError(writer.Fit(actually_written), CHIP_ERROR_BUFFER_TOO_SMALL); | 
|  |  | 
|  | out_asn1_sig = out_asn1_sig.SubSpan(0, actually_written); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR EcdsaAsn1SignatureToRaw(size_t fe_length_bytes, const ByteSpan & asn1_sig, MutableByteSpan & out_raw_sig) | 
|  | { | 
|  | VerifyOrReturnError(fe_length_bytes > 0, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(asn1_sig.size() > kMinSequenceOverhead, CHIP_ERROR_BUFFER_TOO_SMALL); | 
|  |  | 
|  | // Output raw signature is <r,s> both of which are of fe_length_bytes (see SEC1). | 
|  | VerifyOrReturnError(out_raw_sig.size() >= (2u * fe_length_bytes), CHIP_ERROR_BUFFER_TOO_SMALL); | 
|  |  | 
|  | Reader reader(asn1_sig); | 
|  |  | 
|  | // Make sure we have a starting Sequence | 
|  | uint8_t tag = 0; | 
|  | ReturnErrorOnFailure(reader.Read8(&tag).StatusCode()); | 
|  | VerifyOrReturnError(tag == kSeqTag, CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | // Read length of sequence | 
|  | uint8_t tag_len = 0; | 
|  | ReturnErrorOnFailure(ReadDerLength(reader, tag_len)); | 
|  |  | 
|  | // Length of sequence must match what is left of signature | 
|  | VerifyOrReturnError(tag_len == reader.Remaining(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | // Can now clear raw signature integers r,s one by one | 
|  | uint8_t * raw_cursor = out_raw_sig.data(); | 
|  |  | 
|  | // Read R | 
|  | ReturnErrorOnFailure(ReadDerUnsignedIntegerIntoRaw(reader, MutableByteSpan{ raw_cursor, fe_length_bytes })); | 
|  |  | 
|  | raw_cursor += fe_length_bytes; | 
|  |  | 
|  | // Read S | 
|  | ReturnErrorOnFailure(ReadDerUnsignedIntegerIntoRaw(reader, MutableByteSpan{ raw_cursor, fe_length_bytes })); | 
|  |  | 
|  | out_raw_sig = out_raw_sig.SubSpan(0, (2u * fe_length_bytes)); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR AES_CTR_crypt(const uint8_t * input, size_t input_length, const Aes128KeyHandle & key, const uint8_t * nonce, | 
|  | size_t nonce_length, uint8_t * output) | 
|  | { | 
|  | // Discard tag portion of CCM to apply only CTR mode encryption/decryption. | 
|  | constexpr size_t kTagLen = Crypto::kAES_CCM128_Tag_Length; | 
|  | uint8_t tag[kTagLen]; | 
|  |  | 
|  | return AES_CCM_encrypt(input, input_length, nullptr, 0, key, nonce, nonce_length, output, tag, kTagLen); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR GenerateCompressedFabricId(const Crypto::P256PublicKey & root_public_key, uint64_t fabric_id, | 
|  | MutableByteSpan & out_compressed_fabric_id) | 
|  | { | 
|  | VerifyOrReturnError(root_public_key.IsUncompressed(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(out_compressed_fabric_id.size() >= kCompressedFabricIdentifierSize, CHIP_ERROR_BUFFER_TOO_SMALL); | 
|  |  | 
|  | // Ensure proper endianness for Fabric ID (i.e. big-endian as it appears in certificates) | 
|  | uint8_t fabric_id_as_big_endian_salt[kCompressedFabricIdentifierSize]; | 
|  | chip::Encoding::BigEndian::Put64(&fabric_id_as_big_endian_salt[0], fabric_id); | 
|  |  | 
|  | // Compute Compressed fabric reference per spec pseudocode | 
|  | //   CompressedFabricIdentifier = | 
|  | //     CHIP_Crypto_KDF( | 
|  | //       inputKey := TargetOperationalRootPublicKey, | 
|  | //       salt:= TargetOperationalFabricID, | 
|  | //       info := CompressedFabricInfo, | 
|  | //       len := 64) | 
|  | // | 
|  | // NOTE: len=64 bits is implied by output buffer size when calling HKDF_sha::HKDF_SHA256. | 
|  |  | 
|  | constexpr uint8_t kCompressedFabricInfo[16] = /* "CompressedFabric" */ | 
|  | { 0x43, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x46, 0x61, 0x62, 0x72, 0x69, 0x63 }; | 
|  | HKDF_sha hkdf; | 
|  |  | 
|  | // Must drop uncompressed point form format specifier (first byte), per spec method | 
|  | ByteSpan input_key_span(root_public_key.ConstBytes() + 1, root_public_key.Length() - 1); | 
|  |  | 
|  | CHIP_ERROR status = hkdf.HKDF_SHA256( | 
|  | input_key_span.data(), input_key_span.size(), &fabric_id_as_big_endian_salt[0], sizeof(fabric_id_as_big_endian_salt), | 
|  | &kCompressedFabricInfo[0], sizeof(kCompressedFabricInfo), out_compressed_fabric_id.data(), kCompressedFabricIdentifierSize); | 
|  |  | 
|  | // Resize output to final bounds on success | 
|  | if (status == CHIP_NO_ERROR) | 
|  | { | 
|  | out_compressed_fabric_id = out_compressed_fabric_id.SubSpan(0, kCompressedFabricIdentifierSize); | 
|  | } | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR GenerateCompressedFabricId(const Crypto::P256PublicKey & rootPublicKey, uint64_t fabricId, uint64_t & compressedFabricId) | 
|  | { | 
|  | uint8_t allocated[sizeof(fabricId)]; | 
|  | MutableByteSpan span(allocated); | 
|  | ReturnErrorOnFailure(GenerateCompressedFabricId(rootPublicKey, fabricId, span)); | 
|  | // Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId() | 
|  | // returns a binary buffer and is agnostic of usage of the output as an integer type. | 
|  | compressedFabricId = Encoding::BigEndian::Get64(allocated); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | /* Operational Group Key Group, Security Info: "GroupKey v1.0" */ | 
|  | static const uint8_t kGroupSecurityInfo[] = { 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x20, 0x76, 0x31, 0x2e, 0x30 }; | 
|  |  | 
|  | /* Group Key Derivation Function, Info: "GroupKeyHash" ” */ | 
|  | static const uint8_t kGroupKeyHashInfo[]  = { 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68 }; | 
|  | static const uint8_t kGroupKeyHashSalt[0] = {}; | 
|  |  | 
|  | /* | 
|  | OperationalGroupKey = | 
|  | Crypto_KDF | 
|  | ( | 
|  | InputKey = Epoch Key, | 
|  | Salt = CompressedFabricIdentifier, | 
|  | Info = "GroupKey v1.0", | 
|  | Length = CRYPTO_SYMMETRIC_KEY_LENGTH_BITS | 
|  | ) | 
|  | */ | 
|  | CHIP_ERROR DeriveGroupOperationalKey(const ByteSpan & epoch_key, const ByteSpan & compressed_fabric_id, MutableByteSpan & out_key) | 
|  | { | 
|  | VerifyOrReturnError(Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == epoch_key.size(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES <= out_key.size(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | Crypto::HKDF_sha crypto; | 
|  | return crypto.HKDF_SHA256(epoch_key.data(), epoch_key.size(), compressed_fabric_id.data(), compressed_fabric_id.size(), | 
|  | kGroupSecurityInfo, sizeof(kGroupSecurityInfo), out_key.data(), | 
|  | Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES); | 
|  | } | 
|  |  | 
|  | /* | 
|  | GKH = Crypto_KDF ( | 
|  | InputKey = OperationalGroupKey, | 
|  | Salt = [], | 
|  | Info = "GroupKeyHash", | 
|  | Length = 16) | 
|  | */ | 
|  | CHIP_ERROR DeriveGroupSessionId(const ByteSpan & operational_key, uint16_t & session_id) | 
|  | { | 
|  | VerifyOrReturnError(Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == operational_key.size(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | Crypto::HKDF_sha crypto; | 
|  | uint8_t out_key[sizeof(uint16_t)]; | 
|  |  | 
|  | ReturnErrorOnFailure(crypto.HKDF_SHA256(operational_key.data(), operational_key.size(), kGroupKeyHashSalt, | 
|  | sizeof(kGroupKeyHashSalt), kGroupKeyHashInfo, sizeof(kGroupKeyHashInfo), out_key, | 
|  | sizeof(out_key))); | 
|  | session_id = Encoding::BigEndian::Get16(out_key); | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | /* Operational Group Key Group, PrivacyKey Info: "PrivacyKey" */ | 
|  | static const uint8_t kGroupPrivacyInfo[] = { 'P', 'r', 'i', 'v', 'a', 'c', 'y', 'K', 'e', 'y' }; | 
|  |  | 
|  | /* | 
|  | PrivacyKey = | 
|  | Crypto_KDF | 
|  | ( | 
|  | InputKey = EncryptionKey, | 
|  | Salt = [], | 
|  | Info = "PrivacyKey", | 
|  | Length = CRYPTO_SYMMETRIC_KEY_LENGTH_BITS | 
|  | ) | 
|  | */ | 
|  | CHIP_ERROR DeriveGroupPrivacyKey(const ByteSpan & encryption_key, MutableByteSpan & out_key) | 
|  | { | 
|  | VerifyOrReturnError(Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == encryption_key.size(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES <= out_key.size(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | const ByteSpan null_span = ByteSpan(nullptr, 0); | 
|  |  | 
|  | Crypto::HKDF_sha crypto; | 
|  | return crypto.HKDF_SHA256(encryption_key.data(), encryption_key.size(), null_span.data(), null_span.size(), kGroupPrivacyInfo, | 
|  | sizeof(kGroupPrivacyInfo), out_key.data(), Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES); | 
|  | } | 
|  |  | 
|  | CHIP_ERROR DeriveGroupOperationalCredentials(const ByteSpan & epoch_key, const ByteSpan & compressed_fabric_id, | 
|  | GroupOperationalCredentials & operational_credentials) | 
|  | { | 
|  | MutableByteSpan encryption_key(operational_credentials.encryption_key); | 
|  | MutableByteSpan privacy_key(operational_credentials.privacy_key); | 
|  |  | 
|  | ReturnErrorOnFailure(Crypto::DeriveGroupOperationalKey(epoch_key, compressed_fabric_id, encryption_key)); | 
|  | ReturnErrorOnFailure(Crypto::DeriveGroupSessionId(encryption_key, operational_credentials.hash)); | 
|  | ReturnErrorOnFailure(Crypto::DeriveGroupPrivacyKey(encryption_key, privacy_key)); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR ExtractVIDPIDFromAttributeString(DNAttrType attrType, const ByteSpan & attr, | 
|  | AttestationCertVidPid & vidpidFromMatterAttr, AttestationCertVidPid & vidpidFromCNAttr) | 
|  | { | 
|  | ReturnErrorCodeIf(attrType == DNAttrType::kUnspecified, CHIP_NO_ERROR); | 
|  | ReturnErrorCodeIf(attr.empty(), CHIP_ERROR_INVALID_ARGUMENT); | 
|  |  | 
|  | if (attrType == DNAttrType::kMatterVID || attrType == DNAttrType::kMatterPID) | 
|  | { | 
|  | uint16_t matterAttr; | 
|  | VerifyOrReturnError(attr.size() == kVIDandPIDHexLength, CHIP_ERROR_WRONG_CERT_DN); | 
|  | VerifyOrReturnError(Encoding::UppercaseHexToUint16(reinterpret_cast<const char *>(attr.data()), attr.size(), matterAttr) == | 
|  | sizeof(matterAttr), | 
|  | CHIP_ERROR_WRONG_CERT_DN); | 
|  |  | 
|  | if (attrType == DNAttrType::kMatterVID) | 
|  | { | 
|  | // Not more than one VID attribute can be present. | 
|  | ReturnErrorCodeIf(vidpidFromMatterAttr.mVendorId.HasValue(), CHIP_ERROR_WRONG_CERT_DN); | 
|  | vidpidFromMatterAttr.mVendorId.SetValue(static_cast<VendorId>(matterAttr)); | 
|  | } | 
|  | else | 
|  | { | 
|  | // Not more than one PID attribute can be present. | 
|  | ReturnErrorCodeIf(vidpidFromMatterAttr.mProductId.HasValue(), CHIP_ERROR_WRONG_CERT_DN); | 
|  | vidpidFromMatterAttr.mProductId.SetValue(matterAttr); | 
|  | } | 
|  | } | 
|  | // Otherwise, it is a CommonName attribute. | 
|  | else if (!vidpidFromCNAttr.Initialized()) | 
|  | { | 
|  | char cnAttr[kMax_CommonNameAttr_Length + 1]; | 
|  | if (attr.size() <= chip::Crypto::kMax_CommonNameAttr_Length) | 
|  | { | 
|  | memcpy(cnAttr, attr.data(), attr.size()); | 
|  | cnAttr[attr.size()] = 0; | 
|  |  | 
|  | char * vid = strstr(cnAttr, kVIDPrefixForCNEncoding); | 
|  | if (vid != nullptr) | 
|  | { | 
|  | vid += strlen(kVIDPrefixForCNEncoding); | 
|  | if (cnAttr + attr.size() >= vid + kVIDandPIDHexLength) | 
|  | { | 
|  | uint16_t matterAttr; | 
|  | if (Encoding::UppercaseHexToUint16(vid, kVIDandPIDHexLength, matterAttr) == sizeof(matterAttr)) | 
|  | { | 
|  | vidpidFromCNAttr.mVendorId.SetValue(static_cast<VendorId>(matterAttr)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | char * pid = strstr(cnAttr, kPIDPrefixForCNEncoding); | 
|  | if (pid != nullptr) | 
|  | { | 
|  | pid += strlen(kPIDPrefixForCNEncoding); | 
|  | if (cnAttr + attr.size() >= pid + kVIDandPIDHexLength) | 
|  | { | 
|  | uint16_t matterAttr; | 
|  | if (Encoding::UppercaseHexToUint16(pid, kVIDandPIDHexLength, matterAttr) == sizeof(matterAttr)) | 
|  | { | 
|  | vidpidFromCNAttr.mProductId.SetValue(matterAttr); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | // Generates the to-be-signed portion of a PKCS#10 CSR (`CertificationRequestInformation`) | 
|  | // that contains the | 
|  | static CHIP_ERROR GenerateCertificationRequestInformation(ASN1Writer & writer, const Crypto::P256PublicKey & pubkey) | 
|  | { | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  | /** | 
|  | * | 
|  | *  CertificationRequestInfo ::= | 
|  | *     SEQUENCE { | 
|  | *        version       INTEGER { v1(0) } (v1,...), | 
|  | *        subject       Name, | 
|  | *        subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, | 
|  | *        attributes    [0] Attributes{{ CRIAttributes }} | 
|  | * } | 
|  | */ | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | ASN1_ENCODE_INTEGER(0); // version INTEGER { v1(0) } | 
|  |  | 
|  | // subject Name | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | ASN1_START_SET | 
|  | { | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | // Any subject, placeholder is good, since this | 
|  | // is going to usually be ignored | 
|  | ASN1_ENCODE_OBJECT_ID(kOID_AttributeType_OrganizationalUnitName); | 
|  | ASN1_ENCODE_STRING(kASN1UniversalTag_UTF8String, "CSA", static_cast<uint16_t>(strlen("CSA"))); | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  | } | 
|  | ASN1_END_SET; | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  |  | 
|  | // subjectPKInfo | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | ASN1_ENCODE_OBJECT_ID(kOID_PubKeyAlgo_ECPublicKey); | 
|  | ASN1_ENCODE_OBJECT_ID(kOID_EllipticCurve_prime256v1); | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  | ReturnErrorOnFailure(writer.PutBitString(0, pubkey, static_cast<uint8_t>(pubkey.Length()))); | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  |  | 
|  | // attributes [0] | 
|  | ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) | 
|  | { | 
|  | // Using a plain empty attributes request | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | ASN1_ENCODE_OBJECT_ID(kOID_Extension_CSRRequest); | 
|  | ASN1_START_SET | 
|  | { | 
|  | ASN1_START_SEQUENCE {} | 
|  | ASN1_END_SEQUENCE; | 
|  | } | 
|  | ASN1_END_SET; | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  | } | 
|  | ASN1_END_CONSTRUCTED; | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  | exit: | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR GenerateCertificateSigningRequest(const P256Keypair * keypair, MutableByteSpan & csr_span) | 
|  | { | 
|  | VerifyOrReturnError(keypair != nullptr, CHIP_ERROR_INVALID_ARGUMENT); | 
|  | VerifyOrReturnError(csr_span.size() >= kMAX_CSR_Length, CHIP_ERROR_BUFFER_TOO_SMALL); | 
|  |  | 
|  | // First pass: Generate the CertificatioRequestInformation inner | 
|  | // encoding one time, to sign it, before re-generating it within the | 
|  | // full ASN1 writer later, since it's easier than trying to | 
|  | // figure-out the span we need to sign of the overall object. | 
|  | P256ECDSASignature signature; | 
|  |  | 
|  | { | 
|  | // The first pass will just generate a signature, so we can use the | 
|  | // output buffer as scratch to avoid needing more stack space. There | 
|  | // are no secrets here and the contents is not reused since all we | 
|  | // need is the signature which is already separately stored. | 
|  | ASN1Writer toBeSignedWriter; | 
|  | toBeSignedWriter.Init(csr_span); | 
|  | CHIP_ERROR err = GenerateCertificationRequestInformation(toBeSignedWriter, keypair->Pubkey()); | 
|  | ReturnErrorOnFailure(err); | 
|  |  | 
|  | size_t encodedLen = (uint16_t) toBeSignedWriter.GetLengthWritten(); | 
|  | // This should not/will not happen | 
|  | if (encodedLen > csr_span.size()) | 
|  | { | 
|  | return CHIP_ERROR_INTERNAL; | 
|  | } | 
|  |  | 
|  | err = keypair->ECDSA_sign_msg(csr_span.data(), encodedLen, signature); | 
|  | ReturnErrorOnFailure(err); | 
|  | } | 
|  |  | 
|  | // Second pass: Generate the entire CSR body, restarting a new write | 
|  | // of the CertificationRequestInformation (cheap) and adding the | 
|  | // signature. | 
|  | // | 
|  | // See RFC2986 for ASN.1 module, repeated here in snippets | 
|  | CHIP_ERROR err = CHIP_NO_ERROR; | 
|  |  | 
|  | ASN1Writer writer; | 
|  | writer.Init(csr_span); | 
|  |  | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  |  | 
|  | /*  CertificationRequestInfo ::= | 
|  | *     SEQUENCE { | 
|  | *        version       INTEGER { v1(0) } (v1,...), | 
|  | *        subject       Name, | 
|  | *        subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, | 
|  | *        attributes    [0] Attributes{{ CRIAttributes }} | 
|  | *     } | 
|  | */ | 
|  | GenerateCertificationRequestInformation(writer, keypair->Pubkey()); | 
|  |  | 
|  | // algorithm  AlgorithmIdentifier | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | // See RFC5480 sec 2.1 | 
|  | ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256); | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  |  | 
|  | // signature  BIT STRING --> ECDSA-with-SHA256 signature with P256 key with R,S integers format | 
|  | // (see RFC3279 sec 2.2.3 ECDSA Signature Algorithm) | 
|  | ASN1_START_BIT_STRING_ENCAPSULATED | 
|  | { | 
|  | // Convert raw signature to embedded signature | 
|  | FixedByteSpan<Crypto::kP256_ECDSA_Signature_Length_Raw> rawSig(signature.Bytes()); | 
|  |  | 
|  | uint8_t derInt[kP256_FE_Length + kEmitDerIntegerWithoutTagOverhead]; | 
|  |  | 
|  | // Ecdsa-Sig-Value ::= SEQUENCE | 
|  | ASN1_START_SEQUENCE | 
|  | { | 
|  | using P256IntegerSpan = FixedByteSpan<Crypto::kP256_FE_Length>; | 
|  | // r INTEGER | 
|  | { | 
|  | MutableByteSpan derIntSpan(derInt, sizeof(derInt)); | 
|  | ReturnErrorOnFailure(ConvertIntegerRawToDerWithoutTag(P256IntegerSpan(rawSig.data()), derIntSpan)); | 
|  | ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false, | 
|  | derIntSpan.data(), static_cast<uint16_t>(derIntSpan.size()))); | 
|  | } | 
|  |  | 
|  | // s INTEGER | 
|  | { | 
|  | MutableByteSpan derIntSpan(derInt, sizeof(derInt)); | 
|  | ReturnErrorOnFailure( | 
|  | ConvertIntegerRawToDerWithoutTag(P256IntegerSpan(rawSig.data() + kP256_FE_Length), derIntSpan)); | 
|  | ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false, | 
|  | derIntSpan.data(), static_cast<uint16_t>(derIntSpan.size()))); | 
|  | } | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  | } | 
|  | ASN1_END_ENCAPSULATED; | 
|  | } | 
|  | ASN1_END_SEQUENCE; | 
|  |  | 
|  | exit: | 
|  | // Update size of output buffer on success | 
|  | if (err == CHIP_NO_ERROR) | 
|  | { | 
|  | csr_span.reduce_size(writer.GetLengthWritten()); | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | CHIP_ERROR VerifyCertificateSigningRequestFormat(const uint8_t * csr, size_t csr_length) | 
|  | { | 
|  | // Ensure we have enough size to validate header | 
|  | VerifyOrReturnError((csr_length >= 16) && (csr_length <= kMAX_CSR_Length), CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); | 
|  |  | 
|  | Reader reader(csr, csr_length); | 
|  |  | 
|  | // Ensure we have an outermost SEQUENCE | 
|  | uint8_t seq_header = 0; | 
|  | ReturnErrorOnFailure(reader.Read8(&seq_header).StatusCode()); | 
|  | VerifyOrReturnError(seq_header == kSeqTag, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); | 
|  |  | 
|  | uint8_t seq_length = 0; | 
|  | VerifyOrReturnError(ReadDerLength(reader, seq_length) == CHIP_NO_ERROR, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); | 
|  |  | 
|  | // Ensure that outer length matches sequence length + tag overhead, otherwise | 
|  | // we have trailing garbage | 
|  | size_t header_overhead = (seq_length <= 127) ? 2 : 3; | 
|  | VerifyOrReturnError(csr_length == (seq_length + header_overhead), CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); | 
|  |  | 
|  | return CHIP_NO_ERROR; | 
|  | } | 
|  |  | 
|  | } // namespace Crypto | 
|  | } // namespace chip |