Skip to content

Commit

Permalink
Add Darwin framework APIs for generating root/ICA certificates. (#18237)
Browse files Browse the repository at this point in the history
* Add Darwin framework APIs for generating root/ICA certificates.

* Address review comment.

* Address review comment about naming
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Jul 21, 2023
1 parent dee6adf commit 2318dbf
Show file tree
Hide file tree
Showing 12 changed files with 426 additions and 6 deletions.
12 changes: 12 additions & 0 deletions src/darwin/Framework/CHIP.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
513DDB8A2761F6F900DAA01A /* CHIPAttributeTLVValueDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 513DDB892761F6F900DAA01A /* CHIPAttributeTLVValueDecoder.mm */; };
51431AF927D2973E008A7943 /* CHIPIMDispatch.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51431AF827D2973E008A7943 /* CHIPIMDispatch.mm */; };
51431AFB27D29CA4008A7943 /* ota-provider.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 51431AFA27D29CA4008A7943 /* ota-provider.cpp */; };
517BF3F0282B62B800A8B7DB /* MTRCertificates.h in Headers */ = {isa = PBXBuildFile; fileRef = 517BF3EE282B62B800A8B7DB /* MTRCertificates.h */; settings = {ATTRIBUTES = (Public, ); }; };
517BF3F1282B62B800A8B7DB /* MTRCertificates.mm in Sources */ = {isa = PBXBuildFile; fileRef = 517BF3EF282B62B800A8B7DB /* MTRCertificates.mm */; };
517BF3F3282B62CB00A8B7DB /* MatterCertificateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 517BF3F2282B62CB00A8B7DB /* MatterCertificateTests.m */; };
51B22C1E2740CB0A008D5055 /* CHIPStructsObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B22C1D2740CB0A008D5055 /* CHIPStructsObjc.h */; settings = {ATTRIBUTES = (Public, ); }; };
51B22C222740CB1D008D5055 /* CHIPCommandPayloadsObjc.h in Headers */ = {isa = PBXBuildFile; fileRef = 51B22C212740CB1D008D5055 /* CHIPCommandPayloadsObjc.h */; settings = {ATTRIBUTES = (Public, ); }; };
51B22C262740CB32008D5055 /* CHIPStructsObjc.mm in Sources */ = {isa = PBXBuildFile; fileRef = 51B22C252740CB32008D5055 /* CHIPStructsObjc.mm */; };
Expand Down Expand Up @@ -146,6 +149,9 @@
513DDB892761F6F900DAA01A /* CHIPAttributeTLVValueDecoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CHIPAttributeTLVValueDecoder.mm; path = "zap-generated/CHIPAttributeTLVValueDecoder.mm"; sourceTree = "<group>"; };
51431AF827D2973E008A7943 /* CHIPIMDispatch.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CHIPIMDispatch.mm; sourceTree = "<group>"; };
51431AFA27D29CA4008A7943 /* ota-provider.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "ota-provider.cpp"; path = "../../../app/clusters/ota-provider/ota-provider.cpp"; sourceTree = "<group>"; };
517BF3EE282B62B800A8B7DB /* MTRCertificates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCertificates.h; sourceTree = "<group>"; };
517BF3EF282B62B800A8B7DB /* MTRCertificates.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRCertificates.mm; sourceTree = "<group>"; };
517BF3F2282B62CB00A8B7DB /* MatterCertificateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MatterCertificateTests.m; sourceTree = "<group>"; };
51B22C1D2740CB0A008D5055 /* CHIPStructsObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CHIPStructsObjc.h; path = "zap-generated/CHIPStructsObjc.h"; sourceTree = "<group>"; };
51B22C212740CB1D008D5055 /* CHIPCommandPayloadsObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CHIPCommandPayloadsObjc.h; path = "zap-generated/CHIPCommandPayloadsObjc.h"; sourceTree = "<group>"; };
51B22C252740CB32008D5055 /* CHIPStructsObjc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CHIPStructsObjc.mm; path = "zap-generated/CHIPStructsObjc.mm"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -338,6 +344,8 @@
5136661228067D550025EDAE /* MatterControllerFactory.h */,
5136661028067D540025EDAE /* MatterControllerFactory.mm */,
5A7947E227C0101200434CF2 /* CHIPDeviceController+XPC.h */,
517BF3EE282B62B800A8B7DB /* MTRCertificates.h */,
517BF3EF282B62B800A8B7DB /* MTRCertificates.mm */,
5A7947E327C0129500434CF2 /* CHIPDeviceController+XPC.m */,
B20252912459E34F00F97062 /* Info.plist */,
998F286C26D55E10001846C6 /* CHIPKeypair.h */,
Expand Down Expand Up @@ -367,6 +375,7 @@
5A7947DD27BEC3F500434CF2 /* CHIPXPCListenerSampleTests.m */,
B2F53AF1245B0DCF0010745E /* CHIPSetupPayloadParserTests.m */,
997DED1926955D0200975E97 /* CHIPThreadOperationalDatasetTests.mm */,
517BF3F2282B62CB00A8B7DB /* MatterCertificateTests.m */,
B202529D2459E34F00F97062 /* Info.plist */,
);
path = CHIPTests;
Expand All @@ -388,6 +397,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
517BF3F0282B62B800A8B7DB /* MTRCertificates.h in Headers */,
5136661628067D550025EDAE /* MatterControllerFactory.h in Headers */,
5A6FEC9927B5C88900F25F42 /* CHIPDeviceOverXPC.h in Headers */,
51B22C222740CB1D008D5055 /* CHIPCommandPayloadsObjc.h in Headers */,
Expand Down Expand Up @@ -575,6 +585,7 @@
B2E0D7B3245B0B5C003C5B48 /* CHIPError.mm in Sources */,
1E85730C265519AE0050A4D9 /* callback-stub.cpp in Sources */,
1ED276E026C57CF000547A89 /* CHIPCallbackBridge.mm in Sources */,
517BF3F1282B62B800A8B7DB /* MTRCertificates.mm in Sources */,
5A6FEC9627B5983000F25F42 /* CHIPDeviceControllerXPCConnection.m in Sources */,
5ACDDD7D27CD16D200EFD68A /* CHIPAttributeCacheContainer.mm in Sources */,
513DDB8A2761F6F900DAA01A /* CHIPAttributeTLVValueDecoder.mm in Sources */,
Expand Down Expand Up @@ -606,6 +617,7 @@
5AE6D4E427A99041001F2493 /* CHIPDeviceTests.m in Sources */,
5A7947DE27BEC3F500434CF2 /* CHIPXPCListenerSampleTests.m in Sources */,
B2F53AF2245B0DCF0010745E /* CHIPSetupPayloadParserTests.m in Sources */,
517BF3F3282B62CB00A8B7DB /* MatterCertificateTests.m in Sources */,
51E24E73274E0DAC007CCF6E /* CHIPErrorTestUtils.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
2 changes: 2 additions & 0 deletions src/darwin/Framework/CHIP/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ static_library("framework") {
"CHIPQRCodeSetupPayloadParser.mm",
"CHIPSetupPayload.h",
"CHIPSetupPayload.mm",
"MTRCertificates.h",
"MTRCertificates.mm",
"MatterControllerFactory.h",
"MatterControllerFactory.mm",
"MatterControllerFactory_Internal.h",
Expand Down
1 change: 1 addition & 0 deletions src/darwin/Framework/CHIP/CHIP.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#import <CHIP/CHIPSetupPayload.h>
#import <CHIP/CHIPStructsObjc.h>
#import <CHIP/CHIPThreadOperationalDataset.h>
#import <CHIP/MTRCertificates.h>
#import <CHIP/MatterClusterConstants.h>
#import <CHIP/MatterControllerFactory.h>

Expand Down
3 changes: 2 additions & 1 deletion src/darwin/Framework/CHIP/CHIPCluster.mm
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#import "CHIPCluster_internal.h"
#import "CHIPDevice.h"
#import "NSDataSpanConversion.h"

using namespace ::chip;

Expand Down Expand Up @@ -50,7 +51,7 @@ - (instancetype)initWithDevice:(CHIPDevice *)device endpoint:(EndpointId)endpoin

- (chip::ByteSpan)asByteSpan:(NSData *)value
{
return chip::ByteSpan(static_cast<const uint8_t *>(value.bytes), value.length);
return AsByteSpan(value);
}

- (chip::CharSpan)asCharSpan:(NSString *)value
Expand Down
24 changes: 22 additions & 2 deletions src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#import <Security/Security.h>

#import "CHIPError_Internal.h"
#import "CHIPKeypair.h"
#import "CHIPP256KeypairBridge.h"
#import "CHIPPersistentStorageDelegateBridge.h"

Expand Down Expand Up @@ -60,15 +61,34 @@ class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalC

const chip::Crypto::AesCcm128KeySpan GetIPK() { return mIPK.Span(); }

// Generate a root (self-signed) DER-encoded X.509 certificate for the given
// CHIPKeypair. If issuerId is provided, it is used; otherwise a random one
// is generated. If a fabric id is provided it is added to the subject DN
// of the certificate.
//
// The outparam must not be null and is set to nil on errors.
static CHIP_ERROR GenerateRootCertificate(id<CHIPKeypair> keypair, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId,
NSData * _Nullable __autoreleasing * _Nonnull rootCert);

// Generate an intermediate DER-encoded X.509 certificate for the given root
// and intermediate public key. If issuerId is provided, it is used;
// otherwise a random one is generated. If a fabric id is provided it is
// added to the subject DN of the certificate.
//
// The outparam must not be null and is set to nil on errors.
static CHIP_ERROR GenerateIntermediateCertificate(id<CHIPKeypair> rootKeypair, NSData * rootCertificate,
SecKeyRef intermediatePublicKey, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId,
NSData * _Nullable __autoreleasing * _Nonnull intermediateCert);

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

ChipP256KeypairPtr mIssuerKey;
uint64_t mIssuerId = 1234;

chip::Crypto::AesCcm128Key mIPK;

const uint32_t kCertificateValiditySecs = 365 * 24 * 60 * 60;
static const uint32_t kCertificateValiditySecs = 365 * 24 * 60 * 60;

CHIPPersistentStorageDelegateBridge * mStorage;

Expand Down
97 changes: 97 additions & 0 deletions src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include <Security/SecKey.h>

#import "CHIPLogging.h"
#import "MTRCertificates.h"
#import "NSDataSpanConversion.h"

#include <credentials/CHIPCert.h>
#include <crypto/CHIPCryptoPAL.h>
Expand Down Expand Up @@ -177,3 +179,98 @@
uint8_t second = static_cast<uint8_t>([components second]);
return chip::CalendarToChipEpochTime(year, month, day, hour, minute, second, epoch);
}

namespace {
uint64_t GetIssuerId(NSNumber * _Nullable providedIssuerId)
{
if (providedIssuerId != nil) {
return [providedIssuerId unsignedLongLongValue];
}

return (uint64_t(arc4random()) << 32) | arc4random();
}
} // anonymous namespace

CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateRootCertificate(id<CHIPKeypair> keypair, NSNumber * _Nullable issuerId,
NSNumber * _Nullable fabricId, NSData * _Nullable __autoreleasing * _Nonnull rootCert)
{
*rootCert = nil;
CHIPP256KeypairBridge keypairBridge;
ReturnErrorOnFailure(keypairBridge.Init(keypair));
CHIPP256KeypairNativeBridge nativeKeypair(keypairBridge);

ChipDN rcac_dn;
ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterRCACId(GetIssuerId(issuerId)));

if (fabricId != nil) {
ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterFabricId([fabricId unsignedLongLongValue]));
}

uint32_t validityStart, validityEnd;

if (!ToChipEpochTime(0, validityStart)) {
NSLog(@"Failed in computing certificate validity start date");
return CHIP_ERROR_INTERNAL;
}

if (!ToChipEpochTime(kCertificateValiditySecs, validityEnd)) {
NSLog(@"Failed in computing certificate validity end date");
return CHIP_ERROR_INTERNAL;
}

uint8_t rcacBuffer[Controller::kMaxCHIPDERCertLength];
MutableByteSpan rcac(rcacBuffer);
X509CertRequestParams rcac_request = { 0, validityStart, validityEnd, rcac_dn, rcac_dn };
ReturnErrorOnFailure(NewRootX509Cert(rcac_request, nativeKeypair, rcac));
*rootCert = AsData(rcac);
return CHIP_NO_ERROR;
}

CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateIntermediateCertificate(id<CHIPKeypair> rootKeypair,
NSData * rootCertificate, SecKeyRef intermediatePublicKey, NSNumber * _Nullable issuerId, NSNumber * _Nullable fabricId,
NSData * _Nullable __autoreleasing * _Nonnull intermediateCert)
{
*intermediateCert = nil;

// Verify that the provided certificate public key matches the root keypair.
if ([MTRCertificates keypairMatchesCertificate:rootCertificate keypair:rootKeypair] == NO) {
return CHIP_ERROR_INVALID_ARGUMENT;
}

CHIPP256KeypairBridge keypairBridge;
ReturnErrorOnFailure(keypairBridge.Init(rootKeypair));
CHIPP256KeypairNativeBridge nativeRootKeypair(keypairBridge);

ByteSpan rcac = AsByteSpan(rootCertificate);

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

ChipDN rcac_dn;
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn));

ChipDN icac_dn;
ReturnErrorOnFailure(icac_dn.AddAttribute_MatterICACId(GetIssuerId(issuerId)));
if (fabricId != nil) {
ReturnErrorOnFailure(icac_dn.AddAttribute_MatterFabricId([fabricId unsignedLongLongValue]));
}

uint32_t validityStart, validityEnd;

if (!ToChipEpochTime(0, validityStart)) {
NSLog(@"Failed in computing certificate validity start date");
return CHIP_ERROR_INTERNAL;
}

if (!ToChipEpochTime(kCertificateValiditySecs, validityEnd)) {
NSLog(@"Failed in computing certificate validity end date");
return CHIP_ERROR_INTERNAL;
}

uint8_t icacBuffer[Controller::kMaxCHIPDERCertLength];
MutableByteSpan icac(icacBuffer);
X509CertRequestParams icac_request = { 0, validityStart, validityEnd, icac_dn, rcac_dn };
ReturnErrorOnFailure(NewICAX509Cert(icac_request, pubKey, nativeRootKeypair, icac));
*intermediateCert = AsData(icac);
return CHIP_NO_ERROR;
}
3 changes: 3 additions & 0 deletions src/darwin/Framework/CHIP/CHIPP256KeypairBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class CHIPP256KeypairBridge : public chip::Crypto::P256KeypairBase

const chip::Crypto::P256PublicKey & Pubkey() const override { return mPubkey; };

// On success, writes to *pubKey.
static CHIP_ERROR MatterPubKeyFromSecKeyRef(SecKeyRef pubkeyRef, chip::Crypto::P256PublicKey * matterPubKey);

private:
id<CHIPKeypair> _Nullable mKeypair;
chip::Crypto::P256PublicKey mPubkey;
Expand Down
7 changes: 4 additions & 3 deletions src/darwin/Framework/CHIP/CHIPP256KeypairBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,10 @@
return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
}

CHIP_ERROR CHIPP256KeypairBridge::setPubkey()
CHIP_ERROR CHIPP256KeypairBridge::setPubkey() { return MatterPubKeyFromSecKeyRef([mKeypair pubkey], &mPubkey); }

CHIP_ERROR CHIPP256KeypairBridge::MatterPubKeyFromSecKeyRef(SecKeyRef pubkeyRef, P256PublicKey * matterPubKey)
{
SecKeyRef pubkeyRef = [mKeypair pubkey];
if (!pubkeyRef) {
CHIP_LOG_ERROR("Unable to initialize Pubkey");
return CHIP_ERROR_INTERNAL;
Expand All @@ -142,7 +143,7 @@
return CHIP_ERROR_INTERNAL;
}
chip::FixedByteSpan<kP256_PublicKey_Length> pubkeyBytes((const uint8_t *) pubkeyData.bytes);
mPubkey = P256PublicKey(pubkeyBytes);
*matterPubKey = P256PublicKey(pubkeyBytes);

return CHIP_NO_ERROR;
}
81 changes: 81 additions & 0 deletions src/darwin/Framework/CHIP/MTRCertificates.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Copyright (c) 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.
*/
#ifndef MATTER_CERTIFICATES_H
#define MATTER_CERTIFICATES_H

/**
* Utilities for working with Matter certificates.
*/

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol CHIPKeypair;

@interface MTRCertificates : NSObject

/**
* Generate a root (self-signed) X.509 DER encoded certificate that has the
* right fields to be a valid Matter root certificate.
*
* If issuerId is nil, a random issuer id is generated. Otherwise the provided
* issuer id is used.
*
* If fabricId is not nil, it will be included in the subject DN of the
* certificate.
*
* On failure returns nil and if "error" is not null sets *error to the relevant
* error.
*/
+ (nullable NSData *)generateRootCertificate:(id<CHIPKeypair>)keypair
issuerId:(nullable NSNumber *)issuerId
fabricId:(nullable NSNumber *)fabricId
error:(NSError * __autoreleasing _Nullable * _Nullable)error;

/**
* Generate an intermediate X.509 DER encoded certificate that has the
* right fields to be a valid Matter intermediate certificate.
*
* If issuerId is nil, a random issuer id is generated. Otherwise the provided
* issuer id is used.
*
* If fabricId is not nil, it will be included in the subject DN of the
* certificate.
*
* On failure returns nil and if "error" is not null sets *error to the relevant
* error.
*/
+ (nullable NSData *)generateIntermediateCertificate:(id<CHIPKeypair>)rootKeypair
rootCertificate:(NSData *)rootCertificate
intermediatePublicKey:(SecKeyRef)intermediatePublicKey
issuerId:(nullable NSNumber *)issuerId
fabricId:(nullable NSNumber *)fabricId
error:(NSError * __autoreleasing _Nullable * _Nullable)error;

/**
* Check whether the given keypair's public key matches the given certificate's
* public key.
*
* Will return NO on failures to extract public keys from the objects.
*/
+ (BOOL)keypairMatchesCertificate:(NSData *)certificate keypair:(id<CHIPKeypair>)keypair;

@end

NS_ASSUME_NONNULL_END

#endif // MATTER_CERTIFICATES_H
Loading

0 comments on commit 2318dbf

Please sign in to comment.