Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Darwin framework APIs for generating root/ICA certificates. #18237

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 /* MatterCertificates.h in Headers */ = {isa = PBXBuildFile; fileRef = 517BF3EE282B62B800A8B7DB /* MatterCertificates.h */; settings = {ATTRIBUTES = (Public, ); }; };
517BF3F1282B62B800A8B7DB /* MatterCertificates.mm in Sources */ = {isa = PBXBuildFile; fileRef = 517BF3EF282B62B800A8B7DB /* MatterCertificates.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 /* MatterCertificates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MatterCertificates.h; sourceTree = "<group>"; };
517BF3EF282B62B800A8B7DB /* MatterCertificates.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MatterCertificates.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 /* MatterCertificates.h */,
517BF3EF282B62B800A8B7DB /* MatterCertificates.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 /* MatterCertificates.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 /* MatterCertificates.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",
"MatterCertificates.h",
"MatterCertificates.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/MatterCertificates.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 "MatterCertificates.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 ([MatterCertificates 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/MatterCertificates.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 MatterCertificates : 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