Skip to content

Commit 1089578

Browse files
tcarmelveilleuxbzbarsky-applerestyled-commits
authored andcommitted
Implement CSR generation from first principles to support commissioners (#18631)
* Implement CSR generation from first principles to support commissioners - When a commissioner is backing their key with OS or hardware support, the built-in P256Keypair::NewCertificateSigningRequest will not be usable since it relies on internal P256Keypair base class access to key state, as opposed to just using Pubkey() and ECDSA_sign_message primitives. This is OK on some embedded usecases that make use of P256Keypair backend directly, but not for many other usecases. - On iOS/Darwin and on native Android, backing the P256Keypair * by derived classes is bridgeable to platform APIs, but those platform APIs do not offer easy/direct CSR generation, and on Darwin, there are not ASN.1 APIs anymore. - If trying to make use of Darwin APIs introduced in #18519, there is no easy way to write code interfacing with an external CA to provide a CSR for a natively bridged keypair. This PR adds a first-principle CSR generator, written and audited by Google personel, using the ASN1Writer API already used in CHIPCert.h and used by all Commissioner code making use of SDK today. This is a straightforward implementation that directly uses a P256Keypair * (or a derived class thereof!) to generate a CSR against it, without depending on direct key access like like the native version P256Keypair::NewCerticateSigningRequest does. This PR also fixes constness of operations on P256Keypair. Issue #18444 Testing done: - Added unit tests for the new primitive - Validated generated CSR with OpenSSL - Validated equivalence to generated CSR from P256Keypair, on both mbedTLS and OpenSSL - Not used by CHIP-tool but usable by Darwin and Android framework users. * Update src/crypto/CHIPCryptoPAL.h Co-authored-by: Boris Zbarsky <[email protected]> * Fix CI * Restyled by clang-format Co-authored-by: Boris Zbarsky <[email protected]> Co-authored-by: Restyled.io <[email protected]>
1 parent 8ef1823 commit 1089578

12 files changed

+299
-30
lines changed

src/crypto/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ static_library("crypto") {
7070

7171
public_deps = [
7272
":crypto_buildconfig",
73+
"${chip_root}/src/lib/asn1",
7374
"${chip_root}/src/lib/core",
7475
"${nlassert_root}:nlassert",
7576
]

src/crypto/CHIPCryptoPAL.cpp

+185
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
*/
2222

2323
#include "CHIPCryptoPAL.h"
24+
#include <lib/asn1/ASN1.h>
25+
#include <lib/asn1/ASN1Macros.h>
2426
#include <lib/core/CHIPEncoding.h>
2527
#include <lib/support/BufferReader.h>
2628
#include <lib/support/BufferWriter.h>
@@ -34,6 +36,8 @@ using chip::MutableByteSpan;
3436
using chip::Encoding::BufferWriter;
3537
using chip::Encoding::LittleEndian::Reader;
3638

39+
using namespace chip::ASN1;
40+
3741
namespace {
3842

3943
constexpr uint8_t kIntegerTag = 0x02u;
@@ -869,5 +873,186 @@ CHIP_ERROR ExtractVIDPIDFromAttributeString(DNAttrType attrType, const ByteSpan
869873
return CHIP_NO_ERROR;
870874
}
871875

876+
// Generates the to-be-signed portion of a PKCS#10 CSR (`CertificationRequestInformation`)
877+
// that contains the
878+
static CHIP_ERROR GenerateCertificationRequestInformation(ASN1Writer & writer, const Crypto::P256PublicKey & pubkey)
879+
{
880+
CHIP_ERROR err = CHIP_NO_ERROR;
881+
/**
882+
*
883+
* CertificationRequestInfo ::=
884+
* SEQUENCE {
885+
* version INTEGER { v1(0) } (v1,...),
886+
* subject Name,
887+
* subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
888+
* attributes [0] Attributes{{ CRIAttributes }}
889+
* }
890+
*/
891+
ASN1_START_SEQUENCE
892+
{
893+
ASN1_ENCODE_INTEGER(0); // version INTEGER { v1(0) }
894+
895+
// subject Name
896+
ASN1_START_SEQUENCE
897+
{
898+
ASN1_START_SET
899+
{
900+
ASN1_START_SEQUENCE
901+
{
902+
// Any subject, placeholder is good, since this
903+
// is going to usually be ignored
904+
ASN1_ENCODE_OBJECT_ID(kOID_AttributeType_OrganizationalUnitName);
905+
ASN1_ENCODE_STRING(kASN1UniversalTag_UTF8String, "CSA", static_cast<uint16_t>(strlen("CSA")));
906+
}
907+
ASN1_END_SEQUENCE;
908+
}
909+
ASN1_END_SET;
910+
}
911+
ASN1_END_SEQUENCE;
912+
913+
// subjectPKInfo
914+
ASN1_START_SEQUENCE
915+
{
916+
ASN1_START_SEQUENCE
917+
{
918+
ASN1_ENCODE_OBJECT_ID(kOID_PubKeyAlgo_ECPublicKey);
919+
ASN1_ENCODE_OBJECT_ID(kOID_EllipticCurve_prime256v1);
920+
}
921+
ASN1_END_SEQUENCE;
922+
ReturnErrorOnFailure(writer.PutBitString(0, pubkey, static_cast<uint8_t>(pubkey.Length())));
923+
}
924+
ASN1_END_SEQUENCE;
925+
926+
// attributes [0]
927+
ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0)
928+
{
929+
// Using a plain empty attributes request
930+
ASN1_START_SEQUENCE
931+
{
932+
ASN1_ENCODE_OBJECT_ID(kOID_Extension_CSRRequest);
933+
ASN1_START_SET
934+
{
935+
ASN1_START_SEQUENCE {}
936+
ASN1_END_SEQUENCE;
937+
}
938+
ASN1_END_SET;
939+
}
940+
ASN1_END_SEQUENCE;
941+
}
942+
ASN1_END_CONSTRUCTED;
943+
}
944+
ASN1_END_SEQUENCE;
945+
exit:
946+
return err;
947+
}
948+
949+
CHIP_ERROR GenerateCertificateSigningRequest(const P256Keypair * keypair, MutableByteSpan & csr_span)
950+
{
951+
VerifyOrReturnError(keypair != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
952+
VerifyOrReturnError(csr_span.size() >= kMAX_CSR_Length, CHIP_ERROR_BUFFER_TOO_SMALL);
953+
954+
// First pass: Generate the CertificatioRequestInformation inner
955+
// encoding one time, to sign it, before re-generating it within the
956+
// full ASN1 writer later, since it's easier than trying to
957+
// figure-out the span we need to sign of the overall object.
958+
P256ECDSASignature signature;
959+
960+
{
961+
// The first pass will just generate a signature, so we can use the
962+
// output buffer as scratch to avoid needing more stack space. There
963+
// are no secrets here and the contents is not reused since all we
964+
// need is the signature which is already separately stored.
965+
ASN1Writer toBeSignedWriter;
966+
toBeSignedWriter.Init(csr_span);
967+
CHIP_ERROR err = GenerateCertificationRequestInformation(toBeSignedWriter, keypair->Pubkey());
968+
ReturnErrorOnFailure(err);
969+
970+
size_t encodedLen = (uint16_t) toBeSignedWriter.GetLengthWritten();
971+
// This should not/will not happen
972+
if (encodedLen > csr_span.size())
973+
{
974+
return CHIP_ERROR_INTERNAL;
975+
}
976+
977+
err = keypair->ECDSA_sign_msg(csr_span.data(), encodedLen, signature);
978+
ReturnErrorOnFailure(err);
979+
}
980+
981+
// Second pass: Generate the entire CSR body, restarting a new write
982+
// of the CertificationRequestInformation (cheap) and adding the
983+
// signature.
984+
//
985+
// See RFC2986 for ASN.1 module, repeated here in snippets
986+
CHIP_ERROR err = CHIP_NO_ERROR;
987+
988+
ASN1Writer writer;
989+
writer.Init(csr_span);
990+
991+
ASN1_START_SEQUENCE
992+
{
993+
994+
/* CertificationRequestInfo ::=
995+
* SEQUENCE {
996+
* version INTEGER { v1(0) } (v1,...),
997+
* subject Name,
998+
* subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }},
999+
* attributes [0] Attributes{{ CRIAttributes }}
1000+
* }
1001+
*/
1002+
GenerateCertificationRequestInformation(writer, keypair->Pubkey());
1003+
1004+
// algorithm AlgorithmIdentifier
1005+
ASN1_START_SEQUENCE
1006+
{
1007+
// See RFC5480 sec 2.1
1008+
ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256);
1009+
}
1010+
ASN1_END_SEQUENCE;
1011+
1012+
// signature BIT STRING --> ECDSA-with-SHA256 signature with P256 key with R,S integers format
1013+
// (see RFC3279 sec 2.2.3 ECDSA Signature Algorithm)
1014+
ASN1_START_BIT_STRING_ENCAPSULATED
1015+
{
1016+
// Convert raw signature to embedded signature
1017+
FixedByteSpan<Crypto::kP256_ECDSA_Signature_Length_Raw> rawSig(signature.Bytes());
1018+
1019+
uint8_t derInt[kP256_FE_Length + kEmitDerIntegerWithoutTagOverhead];
1020+
1021+
// Ecdsa-Sig-Value ::= SEQUENCE
1022+
ASN1_START_SEQUENCE
1023+
{
1024+
using P256IntegerSpan = FixedByteSpan<Crypto::kP256_FE_Length>;
1025+
// r INTEGER
1026+
{
1027+
MutableByteSpan derIntSpan(derInt, sizeof(derInt));
1028+
ReturnErrorOnFailure(ConvertIntegerRawToDerWithoutTag(P256IntegerSpan(rawSig.data()), derIntSpan));
1029+
ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false,
1030+
derIntSpan.data(), static_cast<uint16_t>(derIntSpan.size())));
1031+
}
1032+
1033+
// s INTEGER
1034+
{
1035+
MutableByteSpan derIntSpan(derInt, sizeof(derInt));
1036+
ReturnErrorOnFailure(
1037+
ConvertIntegerRawToDerWithoutTag(P256IntegerSpan(rawSig.data() + kP256_FE_Length), derIntSpan));
1038+
ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false,
1039+
derIntSpan.data(), static_cast<uint16_t>(derIntSpan.size())));
1040+
}
1041+
}
1042+
ASN1_END_SEQUENCE;
1043+
}
1044+
ASN1_END_ENCAPSULATED;
1045+
}
1046+
ASN1_END_SEQUENCE;
1047+
1048+
exit:
1049+
// Update size of output buffer on success
1050+
if (err == CHIP_NO_ERROR)
1051+
{
1052+
csr_span.reduce_size(writer.GetLengthWritten());
1053+
}
1054+
return err;
1055+
}
1056+
8721057
} // namespace Crypto
8731058
} // namespace chip

src/crypto/CHIPCryptoPAL.h

+28-7
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ class ECPKeypair
322322
*CSR.
323323
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
324324
**/
325-
virtual CHIP_ERROR NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) = 0;
325+
virtual CHIP_ERROR NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) const = 0;
326326

327327
/**
328328
* @brief A function to sign a msg using ECDSA
@@ -332,7 +332,7 @@ class ECPKeypair
332332
* in raw <r,s> point form (see SEC1).
333333
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
334334
**/
335-
virtual CHIP_ERROR ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, Sig & out_signature) = 0;
335+
virtual CHIP_ERROR ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, Sig & out_signature) const = 0;
336336

337337
/**
338338
* @brief A function to sign a hash using ECDSA
@@ -342,7 +342,7 @@ class ECPKeypair
342342
* in raw <r,s> point form (see SEC1).
343343
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
344344
**/
345-
virtual CHIP_ERROR ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, Sig & out_signature) = 0;
345+
virtual CHIP_ERROR ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, Sig & out_signature) const = 0;
346346

347347
/** @brief A function to derive a shared secret using ECDH
348348
* @param remote_public_key Public key of remote peer with which we are trying to establish secure channel. remote_public_key is
@@ -416,7 +416,7 @@ class P256Keypair : public P256KeypairBase
416416
*CSR.
417417
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
418418
**/
419-
CHIP_ERROR NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) override;
419+
CHIP_ERROR NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) const override;
420420

421421
/**
422422
* @brief A function to sign a msg using ECDSA
@@ -426,7 +426,7 @@ class P256Keypair : public P256KeypairBase
426426
* in raw <r,s> point form (see SEC1).
427427
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
428428
**/
429-
CHIP_ERROR ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, P256ECDSASignature & out_signature) override;
429+
CHIP_ERROR ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, P256ECDSASignature & out_signature) const override;
430430

431431
/**
432432
* @brief A function to sign a hash using ECDSA
@@ -436,7 +436,7 @@ class P256Keypair : public P256KeypairBase
436436
* in raw <r,s> point form (see SEC1).
437437
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
438438
**/
439-
CHIP_ERROR ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, P256ECDSASignature & out_signature) override;
439+
CHIP_ERROR ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, P256ECDSASignature & out_signature) const override;
440440

441441
/**
442442
* @brief A function to derive a shared secret using ECDH
@@ -462,7 +462,7 @@ class P256Keypair : public P256KeypairBase
462462

463463
private:
464464
P256PublicKey mPublicKey;
465-
P256KeypairContext mKeypair;
465+
mutable P256KeypairContext mKeypair;
466466
bool mInitialized = false;
467467
};
468468

@@ -616,6 +616,27 @@ CHIP_ERROR AES_CCM_decrypt(const uint8_t * ciphertext, size_t ciphertext_length,
616616
const uint8_t * tag, size_t tag_length, const uint8_t * key, size_t key_length, const uint8_t * nonce,
617617
size_t nonce_length, uint8_t * plaintext);
618618

619+
/**
620+
* @brief Generate a PKCS#10 CSR, usable for Matter, from a P256Keypair.
621+
*
622+
* This uses first principles ASN.1 encoding to avoid relying on the CHIPCryptoPAL backend
623+
* itself, other than to provide an implementation of a P256Keypair * that supports
624+
* at least `::Pubkey()` and `::ECDSA_sign_msg`. This allows using it with
625+
* OS/Platform-bridged private key handling, without requiring a specific
626+
* implementation of other bits like ASN.1.
627+
*
628+
* The CSR will have subject OU set to `CSA`. This is needed since omiting
629+
* subject altogether often trips CSR parsing code. The profile at the CA can
630+
* be configured to ignore CSR requested subject.
631+
*
632+
* @param keypair The key pair for which a CSR should be generated. Must not be null.
633+
* @param csr_span Span to hold the resulting CSR. Must be at least kMAX_CSR_Length. Otherwise returns CHIP_ERROR_BUFFER_TOO_SMALL.
634+
* It will get resized to actual size needed on success.
635+
636+
* @return Returns a CHIP_ERROR from P256Keypair or ASN.1 backend on error, CHIP_NO_ERROR otherwise
637+
**/
638+
CHIP_ERROR GenerateCertificateSigningRequest(const P256Keypair * keypair, MutableByteSpan & csr_span);
639+
619640
/**
620641
* @brief Verify the Certificate Signing Request (CSR). If successfully verified, it outputs the public key from the CSR.
621642
* @param csr CSR in DER format

src/crypto/CHIPCryptoPALOpenSSL.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ static inline const EC_KEY * to_const_EC_KEY(const P256KeypairContext * context)
609609
return *SafePointerCast<const EC_KEY * const *>(context);
610610
}
611611

612-
CHIP_ERROR P256Keypair::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_length, P256ECDSASignature & out_signature)
612+
CHIP_ERROR P256Keypair::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_length, P256ECDSASignature & out_signature) const
613613
{
614614
VerifyOrReturnError((msg != nullptr) && (msg_length > 0), CHIP_ERROR_INVALID_ARGUMENT);
615615

@@ -620,7 +620,7 @@ CHIP_ERROR P256Keypair::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_len
620620
return ECDSA_sign_hash(&digest[0], sizeof(digest), out_signature);
621621
}
622622

623-
CHIP_ERROR P256Keypair::ECDSA_sign_hash(const uint8_t * hash, const size_t hash_length, P256ECDSASignature & out_signature)
623+
CHIP_ERROR P256Keypair::ECDSA_sign_hash(const uint8_t * hash, const size_t hash_length, P256ECDSASignature & out_signature) const
624624
{
625625
ERR_clear_error();
626626

@@ -1110,7 +1110,7 @@ P256Keypair::~P256Keypair()
11101110
Clear();
11111111
}
11121112

1113-
CHIP_ERROR P256Keypair::NewCertificateSigningRequest(uint8_t * out_csr, size_t & csr_length)
1113+
CHIP_ERROR P256Keypair::NewCertificateSigningRequest(uint8_t * out_csr, size_t & csr_length) const
11141114
{
11151115
ERR_clear_error();
11161116
CHIP_ERROR error = CHIP_NO_ERROR;

src/crypto/CHIPCryptoPALmbedTLS.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,7 @@ static inline const mbedtls_ecp_keypair * to_const_keypair(const P256KeypairCont
503503
return SafePointerCast<const mbedtls_ecp_keypair *>(context);
504504
}
505505

506-
CHIP_ERROR P256Keypair::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_length, P256ECDSASignature & out_signature)
506+
CHIP_ERROR P256Keypair::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_length, P256ECDSASignature & out_signature) const
507507
{
508508
#if defined(MBEDTLS_ECDSA_C)
509509
VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE);
@@ -519,7 +519,7 @@ CHIP_ERROR P256Keypair::ECDSA_sign_msg(const uint8_t * msg, const size_t msg_len
519519
#endif
520520
}
521521

522-
CHIP_ERROR P256Keypair::ECDSA_sign_hash(const uint8_t * hash, const size_t hash_length, P256ECDSASignature & out_signature)
522+
CHIP_ERROR P256Keypair::ECDSA_sign_hash(const uint8_t * hash, const size_t hash_length, P256ECDSASignature & out_signature) const
523523
{
524524
#if defined(MBEDTLS_ECDSA_C)
525525
VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE);
@@ -812,7 +812,7 @@ P256Keypair::~P256Keypair()
812812
Clear();
813813
}
814814

815-
CHIP_ERROR P256Keypair::NewCertificateSigningRequest(uint8_t * out_csr, size_t & csr_length)
815+
CHIP_ERROR P256Keypair::NewCertificateSigningRequest(uint8_t * out_csr, size_t & csr_length) const
816816
{
817817
CHIP_ERROR error = CHIP_NO_ERROR;
818818

src/crypto/hsm/nxp/CHIPCryptoPALHsm_SE05X_P256.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ CHIP_ERROR P256KeypairHSM::Initialize()
121121
return CHIP_NO_ERROR;
122122
}
123123

124-
CHIP_ERROR P256KeypairHSM::ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, P256ECDSASignature & out_signature)
124+
CHIP_ERROR P256KeypairHSM::ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, P256ECDSASignature & out_signature) const
125125
{
126126
CHIP_ERROR error = CHIP_ERROR_INTERNAL;
127127
sss_digest_t digest_ctx = { 0 };
@@ -205,7 +205,7 @@ CHIP_ERROR P256KeypairHSM::ECDSA_sign_msg(const uint8_t * msg, size_t msg_length
205205
return error;
206206
}
207207

208-
CHIP_ERROR P256KeypairHSM::ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, P256ECDSASignature & out_signature)
208+
CHIP_ERROR P256KeypairHSM::ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, P256ECDSASignature & out_signature) const
209209
{
210210
CHIP_ERROR error = CHIP_ERROR_INTERNAL;
211211
sss_asymmetric_t asymm_ctx = { 0 };
@@ -574,7 +574,7 @@ static void add_tlv(uint8_t * buf, size_t buf_index, uint8_t tag, size_t len, ui
574574
*
575575
*/
576576

577-
CHIP_ERROR P256KeypairHSM::NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length)
577+
CHIP_ERROR P256KeypairHSM::NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) const
578578
{
579579
CHIP_ERROR error = CHIP_ERROR_INTERNAL;
580580
sss_status_t status = kStatus_SSS_Success;

0 commit comments

Comments
 (0)