Skip to content

Commit

Permalink
Add Darwin framework API for creating operational certificates. (#18510)
Browse files Browse the repository at this point in the history
Will be useful for writing tests, if nothing else.
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Jul 21, 2023
1 parent a2f7ee3 commit 1726524
Show file tree
Hide file tree
Showing 5 changed files with 264 additions and 8 deletions.
11 changes: 11 additions & 0 deletions src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,20 @@ class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalC
SecKeyRef intermediatePublicKey, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId,
NSData * _Nullable __autoreleasing * _Nonnull intermediateCert);

// Generate an operational DER-encoded X.509 certificate for the given
// signing certificate and operational public key, using the given fabric
// id, node id, and CATs.
static CHIP_ERROR GenerateOperationalCertificate(id<CHIPKeypair> signingKeypair, NSData * signingCertificate,
SecKeyRef operationalPublicKey, NSNumber * fabricId, NSNumber * nodeId,
NSArray<NSNumber *> * _Nullable caseAuthenticatedTags, NSData * _Nullable __autoreleasing * _Nonnull operationalCert);

private:
static bool ToChipEpochTime(uint32_t offset, uint32_t & epoch);

static CHIP_ERROR GenerateNOC(chip::Crypto::P256Keypair & signingKeypair, NSData * signingCertificate, chip::NodeId nodeId,
chip::FabricId fabricId, const chip::CATValues & cats, const chip::Crypto::P256PublicKey & pubkey,
chip::MutableByteSpan & noc);

ChipP256KeypairPtr mIssuerKey;

chip::Crypto::AesCcm128Key mIPK;
Expand Down
66 changes: 60 additions & 6 deletions src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@

CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNOC(
NodeId nodeId, FabricId fabricId, const chip::CATValues & cats, const Crypto::P256PublicKey & pubkey, MutableByteSpan & noc)
{
return GenerateNOC(
*mIssuerKey, (mIntermediateCert != nil) ? mIntermediateCert : mRootCert, nodeId, fabricId, cats, pubkey, noc);
}

CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNOC(P256Keypair & signingKeypair, NSData * signingCertificate, NodeId nodeId,
FabricId fabricId, const CATValues & cats, const P256PublicKey & pubkey, MutableByteSpan & noc)
{
uint32_t validityStart, validityEnd;

Expand All @@ -90,16 +97,15 @@
}

ChipDN signerSubject;
NSData * signer = (mIntermediateCert != nil) ? mIntermediateCert : mRootCert;
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(AsByteSpan(signer), signerSubject));
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(AsByteSpan(signingCertificate), signerSubject));

ChipDN noc_dn;
ReturnErrorOnFailure(noc_dn.AddAttribute_MatterFabricId(fabricId));
ReturnErrorOnFailure(noc_dn.AddAttribute_MatterNodeId(nodeId));
ReturnErrorOnFailure(noc_dn.AddCATs(cats));

X509CertRequestParams noc_request = { 1, validityStart, validityEnd, noc_dn, signerSubject };
return NewNodeOperationalX509Cert(noc_request, pubkey, *mIssuerKey, noc);
return NewNodeOperationalX509Cert(noc_request, pubkey, signingKeypair, noc);
}

CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNOCChain(const chip::ByteSpan & csrElements, const chip::ByteSpan & csrNonce,
Expand Down Expand Up @@ -199,7 +205,9 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId)
ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterRCACId(GetIssuerId(issuerId)));

if (fabricId != nil) {
ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterFabricId([fabricId unsignedLongLongValue]));
FabricId fabric = [fabricId unsignedLongLongValue];
VerifyOrReturnError(fabric != kUndefinedFabricId, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterFabricId(fabric));
}

uint32_t validityStart, validityEnd;
Expand Down Expand Up @@ -228,7 +236,7 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId)
{
*intermediateCert = nil;

// Verify that the provided certificate public key matches the root keypair.
// Verify that the provided root certificate public key matches the root keypair.
if ([MTRCertificates keypair:rootKeypair matchesCertificate:rootCertificate] == NO) {
return CHIP_ERROR_INVALID_ARGUMENT;
}
Expand All @@ -248,7 +256,9 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId)
ChipDN icac_dn;
ReturnErrorOnFailure(icac_dn.AddAttribute_MatterICACId(GetIssuerId(issuerId)));
if (fabricId != nil) {
ReturnErrorOnFailure(icac_dn.AddAttribute_MatterFabricId([fabricId unsignedLongLongValue]));
FabricId fabric = [fabricId unsignedLongLongValue];
VerifyOrReturnError(fabric != kUndefinedFabricId, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(icac_dn.AddAttribute_MatterFabricId(fabric));
}

uint32_t validityStart, validityEnd;
Expand All @@ -270,3 +280,47 @@ uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId)
*intermediateCert = AsData(icac);
return CHIP_NO_ERROR;
}

CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateOperationalCertificate(id<CHIPKeypair> signingKeypair,
NSData * signingCertificate, SecKeyRef operationalPublicKey, NSNumber * fabricId, NSNumber * nodeId,
NSArray<NSNumber *> * _Nullable caseAuthenticatedTags, NSData * _Nullable __autoreleasing * _Nonnull operationalCert)
{
*operationalCert = nil;

// Verify that the provided signing certificate public key matches the signing keypair.
if ([MTRCertificates keypair:signingKeypair matchesCertificate:signingCertificate] == NO) {
return CHIP_ERROR_INVALID_ARGUMENT;
}

if ([caseAuthenticatedTags count] > kMaxSubjectCATAttributeCount) {
return CHIP_ERROR_INVALID_ARGUMENT;
}

FabricId fabric = [fabricId unsignedLongLongValue];
VerifyOrReturnError(fabric != kUndefinedFabricId, CHIP_ERROR_INVALID_ARGUMENT);

NodeId node = [nodeId unsignedLongLongValue];
VerifyOrReturnError(IsOperationalNodeId(node), CHIP_ERROR_INVALID_ARGUMENT);

CHIPP256KeypairBridge keypairBridge;
ReturnErrorOnFailure(keypairBridge.Init(signingKeypair));
CHIPP256KeypairNativeBridge nativeSigningKeypair(keypairBridge);

P256PublicKey pubKey;
ReturnErrorOnFailure(CHIPP256KeypairBridge::MatterPubKeyFromSecKeyRef(operationalPublicKey, &pubKey));

CATValues cats;
if (caseAuthenticatedTags != nil) {
size_t idx = 0;
for (NSNumber * cat in caseAuthenticatedTags) {
cats.values[idx++] = [cat unsignedIntValue];
}
}

uint8_t nocBuffer[Controller::kMaxCHIPDERCertLength];
MutableByteSpan noc(nocBuffer);
ReturnErrorOnFailure(GenerateNOC(nativeSigningKeypair, signingCertificate, node, fabric, cats, pubKey, noc));

*operationalCert = AsData(noc);
return CHIP_NO_ERROR;
}
33 changes: 31 additions & 2 deletions src/darwin/Framework/CHIP/MTRCertificates.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ NS_ASSUME_NONNULL_BEGIN
* issuer id is used.
*
* If fabricId is not nil, it will be included in the subject DN of the
* certificate.
* certificate. In this case it must be a valid Matter fabric id.
*
* On failure returns nil and if "error" is not null sets *error to the relevant
* error.
Expand All @@ -54,7 +54,7 @@ NS_ASSUME_NONNULL_BEGIN
* issuer id is used.
*
* If fabricId is not nil, it will be included in the subject DN of the
* certificate.
* certificate. In this case it must be a valid Matter fabric id.
*
* On failure returns nil and if "error" is not null sets *error to the relevant
* error.
Expand All @@ -66,6 +66,35 @@ NS_ASSUME_NONNULL_BEGIN
fabricId:(nullable NSNumber *)fabricId
error:(NSError * __autoreleasing _Nullable * _Nullable)error;

/**
* Generate an X.509 DER encoded certificate that has the
* right fields to be a valid Matter operational certificate.
*
* signingKeypair and signingCertificate are the root or intermediate that is
* signing the operational certificate.
*
* nodeId and fabricId are expected to be 64-bit unsigned integers.
*
* nodeId must be a valid Matter operational node id.
*
* fabricId must be a valid Matter fabric id.
*
* caseAuthenticatedTags may be nil to indicate no CASE Authenticated Tags
* should be used. If caseAuthenticatedTags is not nil, it must have length at
* most 3 and the values in the array are expected to be 32-bit unsigned Case
* Authenticated Tag values.
*
* On failure returns nil and if "error" is not null sets *error to the relevant
* error.
*/
+ (nullable NSData *)generateOperationalCertificate:(id<CHIPKeypair>)signingKeypair
signingCertificate:(NSData *)signingCertificate
operationalPublicKey:(SecKeyRef)operationalPublicKey
fabricId:(NSNumber *)fabricId
nodeId:(NSNumber *)nodeId
caseAuthenticatedTags:(NSArray<NSNumber *> * _Nullable)caseAuthenticatedTags
error:(NSError * __autoreleasing _Nullable * _Nullable)error;

/**
* Check whether the given keypair's public key matches the given certificate's
* public key. The certificate is expected to be an X.509 DER encoded
Expand Down
26 changes: 26 additions & 0 deletions src/darwin/Framework/CHIP/MTRCertificates.mm
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,32 @@ + (nullable NSData *)generateIntermediateCertificate:(id<CHIPKeypair>)rootKeypai
return intermediate;
}

+ (nullable NSData *)generateOperationalCertificate:(id<CHIPKeypair>)signingKeypair
signingCertificate:(NSData *)signingCertificate
operationalPublicKey:(SecKeyRef)operationalPublicKey
fabricId:(NSNumber *)fabricId
nodeId:(NSNumber *)nodeId
caseAuthenticatedTags:(NSArray<NSNumber *> * _Nullable)caseAuthenticatedTags
error:(NSError * __autoreleasing _Nullable * _Nullable)error
{
NSLog(@"Generating operational certificate");

AutoPlatformMemory platformMemory;

NSData * opcert = nil;
CHIP_ERROR err = CHIPOperationalCredentialsDelegate::GenerateOperationalCertificate(
signingKeypair, signingCertificate, operationalPublicKey, fabricId, nodeId, caseAuthenticatedTags, &opcert);
if (error) {
*error = [CHIPError errorForCHIPErrorCode:err];
}

if (err != CHIP_NO_ERROR) {
NSLog(@"Generating operational certificate failed: %s", ErrorStr(err));
}

return opcert;
}

+ (BOOL)keypair:(id<CHIPKeypair>)keypair matchesCertificate:(NSData *)certificate
{
P256PublicKey keypairPubKey;
Expand Down
136 changes: 136 additions & 0 deletions src/darwin/Framework/CHIPTests/MatterCertificateTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,140 @@ - (void)testGenerateIntermediateCert
XCTAssertNotNil(intermediateCert);
}

- (void)testGenerateOperationalCertNoIntermediate
{
__auto_type * rootKeys = [[CHIPTestKeys alloc] init];
XCTAssertNotNil(rootKeys);

__auto_type * rootCert = [MTRCertificates generateRootCertificate:rootKeys issuerId:nil fabricId:nil error:nil];
XCTAssertNotNil(rootCert);

__auto_type * operationalKeys = [[CHIPTestKeys alloc] init];
XCTAssertNotNil(operationalKeys);

__auto_type * cats = [[NSMutableArray alloc] initWithCapacity:3];
[cats addObject:@1];
[cats addObject:@2];
[cats addObject:@3];

__auto_type * operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys
signingCertificate:rootCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@1
nodeId:@1
caseAuthenticatedTags:cats
error:nil];
XCTAssertNotNil(operationalCert);
}

- (void)testGenerateOperationalCertWithIntermediate
{
__auto_type * rootKeys = [[CHIPTestKeys alloc] init];
XCTAssertNotNil(rootKeys);

__auto_type * rootCert = [MTRCertificates generateRootCertificate:rootKeys issuerId:nil fabricId:nil error:nil];
XCTAssertNotNil(rootCert);

__auto_type * intermediateKeys = [[CHIPTestKeys alloc] init];
XCTAssertNotNil(intermediateKeys);

__auto_type * intermediateCert = [MTRCertificates generateIntermediateCertificate:rootKeys
rootCertificate:rootCert
intermediatePublicKey:intermediateKeys.pubkey
issuerId:nil
fabricId:nil
error:nil];
XCTAssertNotNil(intermediateCert);

__auto_type * operationalKeys = [[CHIPTestKeys alloc] init];
XCTAssertNotNil(operationalKeys);

__auto_type * operationalCert = [MTRCertificates generateOperationalCertificate:intermediateKeys
signingCertificate:intermediateCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@1
nodeId:@1
caseAuthenticatedTags:nil
error:nil];
XCTAssertNotNil(operationalCert);
}

- (void)testGenerateOperationalCertErrorCases
{
__auto_type * rootKeys = [[CHIPTestKeys alloc] init];
XCTAssertNotNil(rootKeys);

__auto_type * rootCert = [MTRCertificates generateRootCertificate:rootKeys issuerId:nil fabricId:nil error:nil];
XCTAssertNotNil(rootCert);

__auto_type * operationalKeys = [[CHIPTestKeys alloc] init];
XCTAssertNotNil(operationalKeys);

__auto_type * cats = [[NSMutableArray alloc] initWithCapacity:4];
[cats addObject:@1];
[cats addObject:@2];
[cats addObject:@3];
[cats addObject:@4];

// Check basic case works
__auto_type * operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys
signingCertificate:rootCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@1
nodeId:@1
caseAuthenticatedTags:nil
error:nil];
XCTAssertNotNil(operationalCert);

// CATs too long
operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys
signingCertificate:rootCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@1
nodeId:@1
caseAuthenticatedTags:cats
error:nil];
XCTAssertNil(operationalCert);

// Signing key mismatch
operationalCert = [MTRCertificates generateOperationalCertificate:operationalKeys
signingCertificate:rootCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@1
nodeId:@1
caseAuthenticatedTags:nil
error:nil];
XCTAssertNil(operationalCert);

// Invalid fabric id
operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys
signingCertificate:rootCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@0
nodeId:@1
caseAuthenticatedTags:nil
error:nil];
XCTAssertNil(operationalCert);

// Undefined node id
operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys
signingCertificate:rootCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@1
nodeId:@0
caseAuthenticatedTags:nil
error:nil];
XCTAssertNil(operationalCert);

// Non-operational node id
operationalCert = [MTRCertificates generateOperationalCertificate:rootKeys
signingCertificate:rootCert
operationalPublicKey:operationalKeys.pubkey
fabricId:@1
nodeId:@(0xFFFFFFFFFFFFFFFFLLU)
caseAuthenticatedTags:nil
error:nil];
XCTAssertNil(operationalCert);
}

@end

0 comments on commit 1726524

Please sign in to comment.