diff --git a/examples/chip-tool-darwin/BUILD.gn b/examples/chip-tool-darwin/BUILD.gn index edc4af6bf47932..d7ad1c08ec7f8b 100644 --- a/examples/chip-tool-darwin/BUILD.gn +++ b/examples/chip-tool-darwin/BUILD.gn @@ -23,6 +23,8 @@ executable("chip-tool-darwin") { sources = [ "commands/clusters/ModelCommandBridge.mm", "commands/common/CHIPCommandBridge.mm", + "commands/common/CHIPCommandStorageDelegate.mm", + "commands/common/CHIPToolKeypair.mm", "commands/pairing/Commands.h", "commands/pairing/PairingCommandBridge.mm", "commands/pairing/PairingDelegateBridge.mm", diff --git a/examples/chip-tool-darwin/commands/common/CHIPCommandBridge.mm b/examples/chip-tool-darwin/commands/common/CHIPCommandBridge.mm index e8d5a1a311911c..c422058021e817 100644 --- a/examples/chip-tool-darwin/commands/common/CHIPCommandBridge.mm +++ b/examples/chip-tool-darwin/commands/common/CHIPCommandBridge.mm @@ -18,15 +18,19 @@ #include "CHIPCommandBridge.h" +#import "CHIPToolKeypair.h" #import #include #include const uint16_t kListenPort = 5541; +static CHIPToolPersistentStorageDelegate * storage = nil; CHIP_ERROR CHIPCommandBridge::Run() { ChipLogProgress(chipTool, "Running Command"); + CHIPToolKeypair * nocSigner = [[CHIPToolKeypair alloc] init]; + storage = [[CHIPToolPersistentStorageDelegate alloc] init]; mController = [CHIPDeviceController sharedController]; if (mController == nil) { @@ -36,7 +40,9 @@ [mController setListenPort:kListenPort]; - if (![mController startup:nil vendorId:0 nocSigner:nil]) { + [nocSigner createOrLoadKeys:storage]; + + if (![mController startup:storage vendorId:0 nocSigner:nocSigner]) { ChipLogError(chipTool, "Controller startup failure."); return CHIP_ERROR_INTERNAL; } diff --git a/examples/chip-tool-darwin/commands/common/CHIPCommandStorageDelegate.h b/examples/chip-tool-darwin/commands/common/CHIPCommandStorageDelegate.h new file mode 100644 index 00000000000000..d3087cd4d23632 --- /dev/null +++ b/examples/chip-tool-darwin/commands/common/CHIPCommandStorageDelegate.h @@ -0,0 +1,8 @@ +#import +#import + +@interface CHIPToolPersistentStorageDelegate : NSObject +- (NSString *)CHIPGetKeyValue:(NSString *)key; +- (void)CHIPSetKeyValue:(NSString *)key value:(NSString *)value; +- (void)CHIPDeleteKeyValue:(NSString *)key; +@end diff --git a/examples/chip-tool-darwin/commands/common/CHIPCommandStorageDelegate.mm b/examples/chip-tool-darwin/commands/common/CHIPCommandStorageDelegate.mm new file mode 100644 index 00000000000000..a54936f54663a1 --- /dev/null +++ b/examples/chip-tool-darwin/commands/common/CHIPCommandStorageDelegate.mm @@ -0,0 +1,47 @@ +#include "CHIPCommandStorageDelegate.h" + +NSString * const kCHIPToolDefaultsDomain = @"com.apple.chiptool"; + +id CHIPGetDomainValueForKey(NSString * domain, NSString * key) +{ + id value = (id) CFBridgingRelease(CFPreferencesCopyAppValue((CFStringRef) key, (CFStringRef) domain)); + if (value) { + return value; + } + return nil; +} + +void CHIPSetDomainValueForKey(NSString * domain, NSString * key, id value) +{ + CFPreferencesSetAppValue((CFStringRef) key, (__bridge CFPropertyListRef _Nullable)(value), (CFStringRef) domain); + CFPreferencesAppSynchronize((CFStringRef) domain); +} + +void CHIPRemoveDomainValueForKey(NSString * domain, NSString * key) +{ + CFPreferencesSetAppValue((CFStringRef) key, NULL, (CFStringRef) domain); + CFPreferencesAppSynchronize((CFStringRef) domain); +} + +@implementation CHIPToolPersistentStorageDelegate + +// MARK: CHIPPersistentStorageDelegate + +- (NSString *)CHIPGetKeyValue:(NSString *)key +{ + NSString * value = CHIPGetDomainValueForKey(kCHIPToolDefaultsDomain, key); + NSLog(@"CHIPPersistentStorageDelegate Get Value for Key: %@, value %@", key, value); + return value; +} + +- (void)CHIPSetKeyValue:(NSString *)key value:(NSString *)value +{ + CHIPSetDomainValueForKey(kCHIPToolDefaultsDomain, key, value); +} + +- (void)CHIPDeleteKeyValue:(NSString *)key +{ + CHIPRemoveDomainValueForKey(kCHIPToolDefaultsDomain, key); +} + +@end diff --git a/examples/chip-tool-darwin/commands/common/CHIPToolKeypair.h b/examples/chip-tool-darwin/commands/common/CHIPToolKeypair.h new file mode 100644 index 00000000000000..08ae047cf1b97e --- /dev/null +++ b/examples/chip-tool-darwin/commands/common/CHIPToolKeypair.h @@ -0,0 +1,14 @@ +#include "CHIPCommandStorageDelegate.h" +#import +#import +#include + +@interface CHIPToolKeypair : NSObject +- (BOOL)initialize; +- (NSData *)ECDSA_sign_hash:(NSData *)hash; +- (SecKeyRef)pubkey; +- (CHIP_ERROR)Serialize:(chip::Crypto::P256SerializedKeypair &)output; +- (CHIP_ERROR)Deserialize:(chip::Crypto::P256SerializedKeypair &)input; +- (CHIP_ERROR)createOrLoadKeys:(CHIPToolPersistentStorageDelegate *)storage; + +@end diff --git a/examples/chip-tool-darwin/commands/common/CHIPToolKeypair.mm b/examples/chip-tool-darwin/commands/common/CHIPToolKeypair.mm new file mode 100644 index 00000000000000..198f054f2f7d56 --- /dev/null +++ b/examples/chip-tool-darwin/commands/common/CHIPToolKeypair.mm @@ -0,0 +1,156 @@ +#import "CHIPToolKeypair.h" +#include +#import +#include +#include +#include +#include +#include +#include + +NSString * const kOperationalCredentialsIssuerKeypairStorage = @"ChipToolOpCredsCAKey"; + +std::string StringToBase64(const std::string & value) +{ + std::unique_ptr buffer(new char[BASE64_ENCODED_LEN(value.length())]); + + uint32_t len = chip::Base64Encode32( + reinterpret_cast(value.data()), static_cast(value.length()), buffer.get()); + if (len == UINT32_MAX) { + return ""; + } + + return std::string(buffer.get(), len); +} + +std::string Base64ToString(const std::string & b64Value) +{ + std::unique_ptr buffer(new uint8_t[BASE64_MAX_DECODED_LEN(b64Value.length())]); + + uint32_t len = chip::Base64Decode32(b64Value.data(), static_cast(b64Value.length()), buffer.get()); + if (len == UINT32_MAX) { + return ""; + } + + return std::string(reinterpret_cast(buffer.get()), len); +} + +@interface CHIPToolKeypair () +@property (nonatomic) chip::Crypto::P256Keypair mKeyPair; +@property (nonatomic) chip::Crypto::P256Keypair mIssuer; +@property (atomic) uint32_t mNow; +@end + +@implementation CHIPToolKeypair +- (instancetype)init +{ + if (self = [super init]) { + _mNow = 0; + } + return self; +} + +- (BOOL)initialize +{ + return _mKeyPair.Initialize() == CHIP_NO_ERROR; +} + +- (NSData *)ECDSA_sign_hash:(NSData *)hash +{ + chip::Crypto::P256ECDSASignature signature; + NSData * out_signature; + CHIP_ERROR signing_error = _mKeyPair.ECDSA_sign_hash((const uint8_t *) [hash bytes], (const size_t)[hash length], signature); + if (signing_error != CHIP_NO_ERROR) + return nil; + out_signature = [NSData dataWithBytes:signature length:sizeof(signature)]; + return out_signature; +} + +- (SecKeyRef)pubkey +{ + chip::Crypto::P256PublicKey publicKey = _mKeyPair.Pubkey(); + NSData * publicKeyNSData = [NSData dataWithBytes:publicKey.Bytes() length:publicKey.Length()]; + NSDictionary * attributes = @{ + (__bridge NSString *) kSecAttrKeyType : (__bridge NSString *) kSecAttrKeyTypeECDSA, + (__bridge NSString *) kSecAttrKeyClass : (__bridge NSString *) kSecAttrKeyClassPublic + }; + return SecKeyCreateWithData((__bridge CFDataRef) publicKeyNSData, (__bridge CFDictionaryRef) attributes, NULL); +} + +- (CHIP_ERROR)Deserialize:(chip::Crypto::P256SerializedKeypair &)input +{ + return _mKeyPair.Deserialize(input); +} + +- (CHIP_ERROR)Serialize:(chip::Crypto::P256SerializedKeypair &)output +{ + return _mKeyPair.Serialize(output); +} + +- (CHIP_ERROR)createOrLoadKeys:(CHIPToolPersistentStorageDelegate *)storage +{ + chip::ASN1::ASN1UniversalTime effectiveTime; + chip::Crypto::P256SerializedKeypair serializedKey; + NSString * value; + CHIP_ERROR err = CHIP_NO_ERROR; + + // Initializing the default start validity to start of 2021. The default validity duration is 10 years. + CHIP_ZERO_AT(effectiveTime); + effectiveTime.Year = 2021; + effectiveTime.Month = 1; + effectiveTime.Day = 1; + ReturnErrorOnFailure(chip::Credentials::ASN1ToChipEpochTime(effectiveTime, _mNow)); + + uint16_t keySize = static_cast(serializedKey.Capacity()); + + value = [storage CHIPGetKeyValue:kOperationalCredentialsIssuerKeypairStorage]; + err = [self decodeNSStringWithValue:value serializedKey:serializedKey]; + serializedKey.SetLength(keySize); + + if (err != CHIP_NO_ERROR) { + // Storage doesn't have an existing keypair. Let's create one and add it to the storage. + if (![self initialize]) { + return CHIP_ERROR_INTERNAL; + } + ReturnErrorOnFailure([self Serialize:serializedKey]); + + keySize = static_cast(serializedKey.Capacity()); + std::string serializeString(serializedKey.Bytes(), serializedKey.Bytes() + serializedKey.Capacity()); + std::string base64Value = StringToBase64(serializeString); + NSString * valueString = [NSString stringWithUTF8String:base64Value.c_str()]; + [storage CHIPSetKeyValue:kOperationalCredentialsIssuerKeypairStorage value:valueString]; + } else { + ReturnErrorOnFailure([self Deserialize:serializedKey]); + } + return err; +} + +- (CHIP_ERROR)decodeNSStringWithValue:(NSString *)value serializedKey:(chip::Crypto::P256SerializedKeypair &)serializedKey +{ + CHIP_ERROR err = CHIP_NO_ERROR; + uint16_t keySize = static_cast(serializedKey.Capacity()); + if (value != nil) { + std::string decoded = Base64ToString([value UTF8String]); + + if (decoded.length() > UINT16_MAX) { + err = CHIP_ERROR_BUFFER_TOO_SMALL; + } else { + if (serializedKey != nullptr) { + memcpy(serializedKey, decoded.data(), std::min(decoded.length(), keySize)); + if (keySize < decoded.length()) { + err = CHIP_ERROR_BUFFER_TOO_SMALL; + } + } else { + err = CHIP_ERROR_NO_MEMORY; + } + keySize = static_cast(decoded.length()); + } + } else { + err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; + } + + serializedKey.SetLength(keySize); + return err; +} + +@end