From 7ccfb38515febe48ee321901eb82be5ebbc690e3 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 12 May 2022 14:42:49 -0400 Subject: [PATCH] Add Darwin framework APIs for generating root/ICA certificates. (#18237) * Add Darwin framework APIs for generating root/ICA certificates. * Address review comment. * Address review comment about naming --- .../Framework/CHIP.xcodeproj/project.pbxproj | 12 ++ src/darwin/Framework/CHIP/BUILD.gn | 2 + src/darwin/Framework/CHIP/CHIP.h | 1 + src/darwin/Framework/CHIP/CHIPCluster.mm | 3 +- .../CHIP/CHIPOperationalCredentialsDelegate.h | 24 +++- .../CHIPOperationalCredentialsDelegate.mm | 97 +++++++++++++++ .../Framework/CHIP/CHIPP256KeypairBridge.h | 3 + .../Framework/CHIP/CHIPP256KeypairBridge.mm | 7 +- src/darwin/Framework/CHIP/MTRCertificates.h | 81 +++++++++++++ src/darwin/Framework/CHIP/MTRCertificates.mm | 110 ++++++++++++++++++ .../Framework/CHIP/NSDataSpanConversion.h | 33 ++++++ .../CHIPTests/MatterCertificateTests.m | 59 ++++++++++ 12 files changed, 426 insertions(+), 6 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTRCertificates.h create mode 100644 src/darwin/Framework/CHIP/MTRCertificates.mm create mode 100644 src/darwin/Framework/CHIP/NSDataSpanConversion.h create mode 100644 src/darwin/Framework/CHIPTests/MatterCertificateTests.m diff --git a/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj b/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj index 0405947dc40bd9..510dd2289b30fb 100644 --- a/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/CHIP.xcodeproj/project.pbxproj @@ -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 */; }; @@ -146,6 +149,9 @@ 513DDB892761F6F900DAA01A /* CHIPAttributeTLVValueDecoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CHIPAttributeTLVValueDecoder.mm; path = "zap-generated/CHIPAttributeTLVValueDecoder.mm"; sourceTree = ""; }; 51431AF827D2973E008A7943 /* CHIPIMDispatch.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CHIPIMDispatch.mm; sourceTree = ""; }; 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 = ""; }; + 517BF3EE282B62B800A8B7DB /* MTRCertificates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRCertificates.h; sourceTree = ""; }; + 517BF3EF282B62B800A8B7DB /* MTRCertificates.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRCertificates.mm; sourceTree = ""; }; + 517BF3F2282B62CB00A8B7DB /* MatterCertificateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MatterCertificateTests.m; sourceTree = ""; }; 51B22C1D2740CB0A008D5055 /* CHIPStructsObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CHIPStructsObjc.h; path = "zap-generated/CHIPStructsObjc.h"; sourceTree = ""; }; 51B22C212740CB1D008D5055 /* CHIPCommandPayloadsObjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CHIPCommandPayloadsObjc.h; path = "zap-generated/CHIPCommandPayloadsObjc.h"; sourceTree = ""; }; 51B22C252740CB32008D5055 /* CHIPStructsObjc.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CHIPStructsObjc.mm; path = "zap-generated/CHIPStructsObjc.mm"; sourceTree = ""; }; @@ -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 */, @@ -367,6 +375,7 @@ 5A7947DD27BEC3F500434CF2 /* CHIPXPCListenerSampleTests.m */, B2F53AF1245B0DCF0010745E /* CHIPSetupPayloadParserTests.m */, 997DED1926955D0200975E97 /* CHIPThreadOperationalDatasetTests.mm */, + 517BF3F2282B62CB00A8B7DB /* MatterCertificateTests.m */, B202529D2459E34F00F97062 /* Info.plist */, ); path = CHIPTests; @@ -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 */, @@ -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 */, @@ -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; diff --git a/src/darwin/Framework/CHIP/BUILD.gn b/src/darwin/Framework/CHIP/BUILD.gn index 2ba9da90acff03..216e5cab608dac 100644 --- a/src/darwin/Framework/CHIP/BUILD.gn +++ b/src/darwin/Framework/CHIP/BUILD.gn @@ -63,6 +63,8 @@ static_library("framework") { "CHIPQRCodeSetupPayloadParser.mm", "CHIPSetupPayload.h", "CHIPSetupPayload.mm", + "MTRCertificates.h", + "MTRCertificates.mm", "MatterControllerFactory.h", "MatterControllerFactory.mm", "MatterControllerFactory_Internal.h", diff --git a/src/darwin/Framework/CHIP/CHIP.h b/src/darwin/Framework/CHIP/CHIP.h index 823806cb155d38..b609d3459cbe3c 100644 --- a/src/darwin/Framework/CHIP/CHIP.h +++ b/src/darwin/Framework/CHIP/CHIP.h @@ -34,6 +34,7 @@ #import #import #import +#import #import #import diff --git a/src/darwin/Framework/CHIP/CHIPCluster.mm b/src/darwin/Framework/CHIP/CHIPCluster.mm index 1212c488d230db..6b5bc48d75df51 100644 --- a/src/darwin/Framework/CHIP/CHIPCluster.mm +++ b/src/darwin/Framework/CHIP/CHIPCluster.mm @@ -17,6 +17,7 @@ #import "CHIPCluster_internal.h" #import "CHIPDevice.h" +#import "NSDataSpanConversion.h" using namespace ::chip; @@ -50,7 +51,7 @@ - (instancetype)initWithDevice:(CHIPDevice *)device endpoint:(EndpointId)endpoin - (chip::ByteSpan)asByteSpan:(NSData *)value { - return chip::ByteSpan(static_cast(value.bytes), value.length); + return AsByteSpan(value); } - (chip::CharSpan)asCharSpan:(NSString *)value diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h index 05664812e4c086..549755363438d5 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h @@ -21,6 +21,7 @@ #import #import "CHIPError_Internal.h" +#import "CHIPKeypair.h" #import "CHIPP256KeypairBridge.h" #import "CHIPPersistentStorageDelegateBridge.h" @@ -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 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 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; diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm index 7b9090081d1f69..18746e07d7cfd5 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm @@ -24,6 +24,8 @@ #include #import "CHIPLogging.h" +#import "MTRCertificates.h" +#import "NSDataSpanConversion.h" #include #include @@ -177,3 +179,98 @@ uint8_t second = static_cast([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 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 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; +} diff --git a/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.h b/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.h index 356d8a03b1067f..b1beb7a846e168 100644 --- a/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.h +++ b/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.h @@ -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 _Nullable mKeypair; chip::Crypto::P256PublicKey mPubkey; diff --git a/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.mm b/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.mm index ef31304b27d236..1d97909bfe6936 100644 --- a/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.mm +++ b/src/darwin/Framework/CHIP/CHIPP256KeypairBridge.mm @@ -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; @@ -142,7 +143,7 @@ return CHIP_ERROR_INTERNAL; } chip::FixedByteSpan pubkeyBytes((const uint8_t *) pubkeyData.bytes); - mPubkey = P256PublicKey(pubkeyBytes); + *matterPubKey = P256PublicKey(pubkeyBytes); return CHIP_NO_ERROR; } diff --git a/src/darwin/Framework/CHIP/MTRCertificates.h b/src/darwin/Framework/CHIP/MTRCertificates.h new file mode 100644 index 00000000000000..edf1af5cfeec03 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRCertificates.h @@ -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 + +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)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)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)keypair; + +@end + +NS_ASSUME_NONNULL_END + +#endif // MATTER_CERTIFICATES_H diff --git a/src/darwin/Framework/CHIP/MTRCertificates.mm b/src/darwin/Framework/CHIP/MTRCertificates.mm new file mode 100644 index 00000000000000..fcc6aac456b45b --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRCertificates.mm @@ -0,0 +1,110 @@ +/** + * 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. + */ + +#import "MTRCertificates.h" +#import "CHIPError_Internal.h" +#import "CHIPOperationalCredentialsDelegate.h" +#import "CHIPP256KeypairBridge.h" +#import "NSDataSpanConversion.h" + +#include +#include +#include + +using namespace chip; +using namespace chip::Crypto; +using namespace chip::Credentials; + +// RAII helper for doing MemoryInit/MemoryShutdown, just in case the underlying +// Matter APIs we are using use Platform::Memory. MemoryInit/MemoryShutdown are +// refcounted, so it's OK if we use AutoPlatformMemory after MemoryInit has +// already happened elsewhere. +struct AutoPlatformMemory { + AutoPlatformMemory() { Platform::MemoryInit(); } + ~AutoPlatformMemory() { Platform::MemoryShutdown(); } +}; + +@implementation MTRCertificates + ++ (nullable NSData *)generateRootCertificate:(id)keypair + issuerId:(nullable NSNumber *)issuerId + fabricId:(nullable NSNumber *)fabricId + error:(NSError * __autoreleasing *)error +{ + NSLog(@"Generating root certificate"); + + AutoPlatformMemory platformMemory; + + NSData * rootCert = nil; + CHIP_ERROR err = CHIPOperationalCredentialsDelegate::GenerateRootCertificate(keypair, issuerId, fabricId, &rootCert); + if (error) { + *error = [CHIPError errorForCHIPErrorCode:err]; + } + + if (err != CHIP_NO_ERROR) { + NSLog(@"Generating root certificate failed: %s", ErrorStr(err)); + } + + return rootCert; +} + ++ (nullable NSData *)generateIntermediateCertificate:(id)rootKeypair + rootCertificate:(NSData *)rootCertificate + intermediatePublicKey:(SecKeyRef)intermediatePublicKey + issuerId:(nullable NSNumber *)issuerId + fabricId:(nullable NSNumber *)fabricId + error:(NSError * __autoreleasing *)error +{ + NSLog(@"Generating intermediate certificate"); + + AutoPlatformMemory platformMemory; + + NSData * intermediate = nil; + CHIP_ERROR err = CHIPOperationalCredentialsDelegate::GenerateIntermediateCertificate( + rootKeypair, rootCertificate, intermediatePublicKey, issuerId, fabricId, &intermediate); + if (error) { + *error = [CHIPError errorForCHIPErrorCode:err]; + } + + if (err != CHIP_NO_ERROR) { + NSLog(@"Generating intermediate certificate failed: %s", ErrorStr(err)); + } + + return intermediate; +} + ++ (BOOL)keypairMatchesCertificate:(NSData *)certificate keypair:(id)keypair +{ + P256PublicKey keypairPubKey; + CHIP_ERROR err = CHIPP256KeypairBridge::MatterPubKeyFromSecKeyRef(keypair.pubkey, &keypairPubKey); + if (err != CHIP_NO_ERROR) { + NSLog(@"Can't extract public key from keypair: %s", ErrorStr(err)); + return NO; + } + P256PublicKeySpan keypairKeySpan(keypairPubKey.ConstBytes()); + + P256PublicKey certPubKey; + err = ExtractPubkeyFromX509Cert(AsByteSpan(certificate), certPubKey); + if (err != CHIP_NO_ERROR) { + NSLog(@"Can't extract public key from certificate: %s", ErrorStr(err)); + return NO; + } + P256PublicKeySpan certKeySpan(certPubKey.ConstBytes()); + + return certKeySpan.data_equal(keypairKeySpan); +} + +@end diff --git a/src/darwin/Framework/CHIP/NSDataSpanConversion.h b/src/darwin/Framework/CHIP/NSDataSpanConversion.h new file mode 100644 index 00000000000000..d854d46be03ef3 --- /dev/null +++ b/src/darwin/Framework/CHIP/NSDataSpanConversion.h @@ -0,0 +1,33 @@ +/** + * 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. + */ + +#pragma once + +#import "Foundation/Foundation.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +/** + * Utilities for converting between NSData and chip::Span. + */ + +inline chip::ByteSpan AsByteSpan(NSData * data) { return chip::ByteSpan(static_cast(data.bytes), data.length); } + +inline NSData * AsData(chip::ByteSpan span) { return [NSData dataWithBytes:span.data() length:span.size()]; } + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIPTests/MatterCertificateTests.m b/src/darwin/Framework/CHIPTests/MatterCertificateTests.m new file mode 100644 index 00000000000000..eedaffe762c1d2 --- /dev/null +++ b/src/darwin/Framework/CHIPTests/MatterCertificateTests.m @@ -0,0 +1,59 @@ +/** + * 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. + */ + +#import + +// system dependencies +#import + +#import "CHIPTestKeys.h" + +@interface MatterCertificateTests : XCTestCase + +@end + +@implementation MatterCertificateTests + +- (void)testGenerateRootCert +{ + __auto_type * testKeys = [[CHIPTestKeys alloc] init]; + XCTAssertNotNil(testKeys); + + __auto_type * rootCert = [MTRCertificates generateRootCertificate:testKeys issuerId:nil fabricId:nil error:nil]; + XCTAssertNotNil(rootCert); +} + +- (void)testGenerateIntermediateCert +{ + __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); +} + +@end