diff --git a/SignalServiceKit/Example/TSKitiOSTestApp/Podfile.lock b/SignalServiceKit/Example/TSKitiOSTestApp/Podfile.lock index 963d596fa..31551849e 100644 --- a/SignalServiceKit/Example/TSKitiOSTestApp/Podfile.lock +++ b/SignalServiceKit/Example/TSKitiOSTestApp/Podfile.lock @@ -38,7 +38,7 @@ PODS: - Reachability (3.2) - SAMKeychain (1.5.2) - SignalServiceKit (0.9.0): - - 25519 + - '25519' - AFNetworking - AxolotlKit - CocoaLumberjack @@ -134,7 +134,7 @@ CHECKOUT OPTIONS: :git: https://github.com/facebook/SocketRocket.git SPEC CHECKSUMS: - 25519: dc4bad7e2dbcbf1efa121068a705a44cd98c80fc + '25519': dc4bad7e2dbcbf1efa121068a705a44cd98c80fc AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 AxolotlKit: a9530d6835baae0f204b1f6b9dd79b7901176f0d CocoaLumberjack: aa9dcab71bdf9eaf2a63bbd9ddc87863efe45457 @@ -154,4 +154,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 8eff8ab93f8a0a1024e17b16fee43f3a93656c2c -COCOAPODS: 1.3.1 +COCOAPODS: 1.2.1 diff --git a/SignalServiceKit/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj b/SignalServiceKit/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj index 12991eb6c..688f3648c 100644 --- a/SignalServiceKit/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj +++ b/SignalServiceKit/Example/TSKitiOSTestApp/TSKitiOSTestApp.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 34D99C891F2250FF00D284D6 /* OWSAnalyticsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D99C881F2250FF00D284D6 /* OWSAnalyticsTests.m */; }; 45046FE01D95A6130015EFF2 /* TSMessagesManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 45046FDF1D95A6130015EFF2 /* TSMessagesManagerTest.m */; }; 450E3C9A1D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 450E3C991D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m */; }; + 451686AE1F527A9C00AC3D4B /* OWSProvisioningCipherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 451686AD1F527A9C00AC3D4B /* OWSProvisioningCipherTest.m */; }; 4516E3E81DD153CC00DC4206 /* TSGroupThreadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4516E3E71DD153CC00DC4206 /* TSGroupThreadTest.m */; }; 4516E3EA1DD1542300DC4206 /* TSContactThreadTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 4516E3E91DD1542300DC4206 /* TSContactThreadTest.m */; }; 452137231E8D6D2F0048FD10 /* OWSFakeMessageSender.m in Sources */ = {isa = PBXBuildFile; fileRef = 452137221E8D6D2F0048FD10 /* OWSFakeMessageSender.m */; }; @@ -70,6 +71,7 @@ 36DA6C703F99948D553F4E3F /* Pods-TSKitiOSTestAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TSKitiOSTestAppTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TSKitiOSTestAppTests/Pods-TSKitiOSTestAppTests.debug.xcconfig"; sourceTree = ""; }; 45046FDF1D95A6130015EFF2 /* TSMessagesManagerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSMessagesManagerTest.m; path = ../../../tests/Messages/TSMessagesManagerTest.m; sourceTree = ""; }; 450E3C991D96DD2600BF4EB6 /* OWSDisappearingMessagesJobTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSDisappearingMessagesJobTest.m; path = ../../../tests/Messages/OWSDisappearingMessagesJobTest.m; sourceTree = ""; }; + 451686AD1F527A9C00AC3D4B /* OWSProvisioningCipherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OWSProvisioningCipherTest.m; path = ../../../tests/Devices/OWSProvisioningCipherTest.m; sourceTree = ""; }; 4516E3E71DD153CC00DC4206 /* TSGroupThreadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSGroupThreadTest.m; path = ../../../tests/Contacts/TSGroupThreadTest.m; sourceTree = ""; }; 4516E3E91DD1542300DC4206 /* TSContactThreadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TSContactThreadTest.m; path = ../../../tests/Contacts/TSContactThreadTest.m; sourceTree = ""; }; 452137211E8D6D2F0048FD10 /* OWSFakeMessageSender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OWSFakeMessageSender.h; path = ../../../tests/TestSupport/Fakes/OWSFakeMessageSender.h; sourceTree = ""; }; @@ -263,6 +265,7 @@ children = ( 45D7243E1D67899F00E0CA54 /* OWSDeviceProvisionerTest.m */, 45B840201D988DA100F9E938 /* OWSReadReceiptTest.m */, + 451686AD1F527A9C00AC3D4B /* OWSProvisioningCipherTest.m */, ); name = Devices; sourceTree = ""; @@ -468,7 +471,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 276B029791E679B0E87877B7 /* [CP] Copy Pods Resources */ = { @@ -551,7 +554,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -590,6 +593,7 @@ 45AE48491E072711004D96C2 /* OWSUnitTestEnvironment.m in Sources */, 459850C11D22C6F2006FFEDB /* PhoneNumberTest.m in Sources */, 45DC30C71F3B69B7008C4378 /* OWSFakeProfileManager.m in Sources */, + 451686AE1F527A9C00AC3D4B /* OWSProvisioningCipherTest.m in Sources */, 45458B7A1CC342B600A02153 /* TSStorageSignedPreKeyStore.m in Sources */, 453E1FDB1DA83EFB00DDD7B7 /* OWSFakeContactsUpdater.m in Sources */, 452137231E8D6D2F0048FD10 /* OWSFakeMessageSender.m in Sources */, diff --git a/SignalServiceKit/src/Devices/OWSDeviceProvisioner.m b/SignalServiceKit/src/Devices/OWSDeviceProvisioner.m index 3b01291e4..7816b5495 100644 --- a/SignalServiceKit/src/Devices/OWSDeviceProvisioner.m +++ b/SignalServiceKit/src/Devices/OWSDeviceProvisioner.m @@ -1,9 +1,12 @@ -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "OWSDeviceProvisioner.h" #import "OWSDeviceProvisioningCodeService.h" #import "OWSDeviceProvisioningService.h" #import "OWSProvisioningMessage.h" +#import "OWSError.h" NS_ASSUME_NONNULL_BEGIN @@ -88,7 +91,14 @@ NS_ASSUME_NONNULL_BEGIN profileKey:self.profileKey provisioningCode:provisioningCode]; - [self.provisioningService provisionWithMessageBody:[message buildEncryptedMessageBody] + NSData *_Nullable messageBody = [message buildEncryptedMessageBody]; + if (messageBody == nil) { + NSError *error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToEncryptMessage, @"Failed building provisioning message"); + failureCallback(error); + return; + } + + [self.provisioningService provisionWithMessageBody:messageBody ephemeralDeviceId:self.ephemeralDeviceId success:^{ DDLogInfo(@"ProvisioningService SUCCEEDED"); diff --git a/SignalServiceKit/src/Devices/OWSProvisioningCipher.h b/SignalServiceKit/src/Devices/OWSProvisioningCipher.h index 908031bf7..0e651aa69 100644 --- a/SignalServiceKit/src/Devices/OWSProvisioningCipher.h +++ b/SignalServiceKit/src/Devices/OWSProvisioningCipher.h @@ -1,4 +1,6 @@ -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// NS_ASSUME_NONNULL_BEGIN @@ -7,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSData *ourPublicKey; - (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey; -- (NSData *)encrypt:(NSData *)plainText; +- (nullable NSData *)encrypt:(NSData *)plainText; @end diff --git a/SignalServiceKit/src/Devices/OWSProvisioningCipher.m b/SignalServiceKit/src/Devices/OWSProvisioningCipher.m index 711e1188d..1f2c2b77e 100644 --- a/SignalServiceKit/src/Devices/OWSProvisioningCipher.m +++ b/SignalServiceKit/src/Devices/OWSProvisioningCipher.m @@ -1,4 +1,6 @@ -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "OWSProvisioningCipher.h" #import <25519/Curve25519.h> @@ -11,21 +13,33 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSData *theirPublicKey; @property (nonatomic, readonly) ECKeyPair *ourKeyPair; +@property (nonatomic, readonly) NSData *initializationVector; @end @implementation OWSProvisioningCipher - (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey +{ + return [self initWithTheirPublicKey:theirPublicKey + ourKeyPair:[Curve25519 generateKeyPair] + initializationVector:[Cryptography generateRandomBytes:kCCBlockSizeAES128]]; +} + +// Private method which exposes dependencies for testing +- (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey + ourKeyPair:(ECKeyPair *)ourKeyPair + initializationVector:(NSData *)initializationVector { self = [super init]; if (!self) { return self; } - + _theirPublicKey = theirPublicKey; - _ourKeyPair = [Curve25519 generateKeyPair]; - + _ourKeyPair = ourKeyPair; + _initializationVector = initializationVector; + return self; } @@ -34,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN return self.ourKeyPair.publicKey; } -- (NSData *)encrypt:(NSData *)dataToEncrypt +- (nullable NSData *)encrypt:(NSData *)dataToEncrypt { NSData *sharedSecret = [Curve25519 generateSharedSecretFromPublicKey:self.theirPublicKey andKeyPair:self.ourKeyPair]; @@ -50,7 +64,12 @@ NS_ASSUME_NONNULL_BEGIN u_int8_t versionByte[] = { 0x01 }; NSMutableData *message = [NSMutableData dataWithBytes:&versionByte length:1]; - NSData *cipherText = [self encrypt:dataToEncrypt withKey:cipherKey]; + NSData *_Nullable cipherText = [self encrypt:dataToEncrypt withKey:cipherKey]; + if (cipherText == nil) { + OWSFail(@"Provisioning cipher failed."); + return nil; + } + [message appendData:cipherText]; NSData *mac = [self macForMessage:message withKey:macKey]; @@ -59,12 +78,27 @@ NS_ASSUME_NONNULL_BEGIN return [message copy]; } -- (NSData *)encrypt:(NSData *)dataToEncrypt withKey:(NSData *)cipherKey +- (nullable NSData *)encrypt:(NSData *)dataToEncrypt withKey:(NSData *)cipherKey { - NSData *iv = [Cryptography generateRandomBytes:kCCBlockSizeAES128]; + NSData *iv = self.initializationVector; + if (iv.length != kCCBlockSizeAES128) { + OWSFail(@"Unexpected length for iv"); + return nil; + } + // allow space for message + padding any incomplete block - size_t bufferSize = dataToEncrypt.length + kCCBlockSizeAES128; - void *buffer = malloc(bufferSize); + NSUInteger blockCount = ceil((double)dataToEncrypt.length / (double)kCCBlockSizeAES128); + size_t ciphertextBufferSize = blockCount * kCCBlockSizeAES128; + + // message format is (iv || ciphertext) + NSMutableData *encryptedMessage = [NSMutableData dataWithLength:iv.length + ciphertextBufferSize]; + + // write the iv + [encryptedMessage replaceBytesInRange:NSMakeRange(0, iv.length) withBytes:iv.bytes]; + + // cipher text follows iv + char *ciphertextBuffer = encryptedMessage.mutableBytes + iv.length; + size_t bytesEncrypted = 0; CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt, @@ -75,26 +109,25 @@ NS_ASSUME_NONNULL_BEGIN iv.bytes, dataToEncrypt.bytes, dataToEncrypt.length, - buffer, - bufferSize, + ciphertextBuffer, + ciphertextBufferSize, &bytesEncrypted); if (cryptStatus != kCCSuccess) { DDLogError(@"Encryption failed with status: %d", cryptStatus); + return nil; } - - NSMutableData *encryptedMessage = [[NSMutableData alloc] initWithData:iv]; - [encryptedMessage appendBytes:buffer length:bytesEncrypted]; - + return [encryptedMessage copy]; } - (NSData *)macForMessage:(NSData *)message withKey:(NSData *)macKey { - uint8_t hmacBytes[CC_SHA256_DIGEST_LENGTH] = { 0 }; - CCHmac(kCCHmacAlgSHA256, macKey.bytes, macKey.length, message.bytes, message.length, hmacBytes); + NSMutableData *hmac = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; + + CCHmac(kCCHmacAlgSHA256, macKey.bytes, macKey.length, message.bytes, message.length, hmac.mutableBytes); - return [NSData dataWithBytes:hmacBytes length:CC_SHA256_DIGEST_LENGTH]; + return [hmac copy]; } diff --git a/SignalServiceKit/src/Devices/OWSProvisioningMessage.h b/SignalServiceKit/src/Devices/OWSProvisioningMessage.h index 8d47f7c5f..37d46902f 100644 --- a/SignalServiceKit/src/Devices/OWSProvisioningMessage.h +++ b/SignalServiceKit/src/Devices/OWSProvisioningMessage.h @@ -1,4 +1,6 @@ -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// NS_ASSUME_NONNULL_BEGIN @@ -11,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN profileKey:(NSData *)profileKey provisioningCode:(NSString *)provisioningCode; -- (NSData *)buildEncryptedMessageBody; +- (nullable NSData *)buildEncryptedMessageBody; @end diff --git a/SignalServiceKit/src/Devices/OWSProvisioningMessage.m b/SignalServiceKit/src/Devices/OWSProvisioningMessage.m index 7908a084c..68022a7c1 100644 --- a/SignalServiceKit/src/Devices/OWSProvisioningMessage.m +++ b/SignalServiceKit/src/Devices/OWSProvisioningMessage.m @@ -1,4 +1,6 @@ -// Copyright © 2016 Open Whisper Systems. All rights reserved. +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// #import "OWSProvisioningMessage.h" #import "OWSProvisioningCipher.h" @@ -44,7 +46,7 @@ NS_ASSUME_NONNULL_BEGIN return self; } -- (NSData *)buildEncryptedMessageBody +- (nullable NSData *)buildEncryptedMessageBody { OWSProvisioningProtosProvisionMessageBuilder *messageBuilder = [OWSProvisioningProtosProvisionMessageBuilder new]; [messageBuilder setIdentityKeyPublic:self.myPublicKey]; @@ -57,7 +59,11 @@ NS_ASSUME_NONNULL_BEGIN NSData *plainTextProvisionMessage = [[messageBuilder build] data]; OWSProvisioningCipher *cipher = [[OWSProvisioningCipher alloc] initWithTheirPublicKey:self.theirPublicKey]; - NSData *encryptedProvisionMessage = [cipher encrypt:plainTextProvisionMessage]; + NSData *_Nullable encryptedProvisionMessage = [cipher encrypt:plainTextProvisionMessage]; + if (encryptedProvisionMessage == nil) { + DDLogError(@"Failed to encrypt provision message"); + return nil; + } OWSProvisioningProtosProvisionEnvelopeBuilder *envelopeBuilder = [OWSProvisioningProtosProvisionEnvelopeBuilder new]; // Note that this is a one-time-use *cipher* public key, not our Signal *identity* public key diff --git a/SignalServiceKit/tests/Devices/OWSProvisioningCipherTest.m b/SignalServiceKit/tests/Devices/OWSProvisioningCipherTest.m new file mode 100644 index 000000000..edde5864c --- /dev/null +++ b/SignalServiceKit/tests/Devices/OWSProvisioningCipherTest.m @@ -0,0 +1,134 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import +#import +#import <25519/Curve25519.h> +#import + +@interface OWSProvisioningCipher(Testing) + +// Expose private method for testing. +- (instancetype)initWithTheirPublicKey:(NSData *)theirPublicKey + ourKeyPair:(ECKeyPair *)ourKeyPair + initializationVector:(NSData *)initializationVector; + +@end + +@interface OWSProvisioningCipherTest : XCTestCase + +@end + +@implementation OWSProvisioningCipherTest + +- (NSData *)knownInitializationVector +{ + uint8_t initilizationVectorBytes[] = { + 0xec, 0x67, 0x0b, 0xb7, + 0x18, 0xe1, 0xe9, 0x0a, + 0xcc, 0x5e, 0xcb, 0x37, + 0xab, 0x79, 0xe0, 0x09 + }; + return [NSData dataWithBytes:initilizationVectorBytes length:16]; +} + +- (NSData *)knownPublicKey +{ + uint8_t knownPublicKeyBytes[] = { + 0x5e, 0x23, 0xe8, 0x49, + 0xb2, 0x23, 0x21, 0xdb, + 0x2e, 0x3a, 0x77, 0x74, + 0x6f, 0x3b, 0x44, 0x18, + 0xcc, 0x6c, 0x81, 0xce, + 0xd5, 0xc2, 0x91, 0xaf, + 0xed, 0xfb, 0x21, 0x4e, + 0x59, 0xcc, 0x19, 0xa4 + }; + return [NSData dataWithBytes:knownPublicKeyBytes length: 32]; +} + +- (ECKeyPair *)knownKeyPair +{ + uint8_t privateKeyBytes[] = { + 0x60, 0xfd, 0xc1, 0xeb, + 0x6a, 0x68, 0x3d, 0x2b, + 0x51, 0x23, 0x1f, 0xea, + 0x1a, 0x5e, 0x80, 0x88, + 0x0c, 0x65, 0x2d, 0x3d, + 0x47, 0x9e, 0x28, 0xc1, + 0x9f, 0x48, 0x2c, 0x66, + 0xde, 0x48, 0x5d, 0x57 + }; + + uint8_t publicKeyBytes[] = { + 0x02, 0x62, 0x7b, 0x5c, + 0x21, 0x15, 0x59, 0x1b, + 0x37, 0xd1, 0xfe, 0xeb, + 0x15, 0x5d, 0xd2, 0x95, + 0x0a, 0xce, 0xe8, 0xb2, + 0x1e, 0x8e, 0xc8, 0xd6, + 0x53, 0x4f, 0x1a, 0xcd, + 0xf2, 0x00, 0x98, 0x32 + }; + + // Righteous hack to build a deterministic ECKeyPair + // The publicKey/privateKey ivars are private but it's possible to `initWithCoder:` given the proper keys. + NSKeyedArchiver *archiver = [NSKeyedArchiver new]; + [archiver encodeBytes:publicKeyBytes length:ECCKeyLength forKey:@"TSECKeyPairPublicKey"]; + [archiver encodeBytes:privateKeyBytes length:ECCKeyLength forKey:@"TSECKeyPairPrivateKey"]; + NSData *serialized = [archiver encodedData]; + + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:serialized]; + return [[ECKeyPair alloc] initWithCoder:unarchiver]; +} + +- (NSData *)knownData +{ + uint8_t knownBytes[] = { + 0x19, 0x33, 0x78, 0x64, + 0x96, 0x56, 0xa7, 0xd0, + 0x6e, 0xff, 0x37, 0x1d + }; + + return [NSData dataWithBytes:knownBytes length:12]; +} + +- (void)testEncrypt +{ + NSData *theirPublicKey = [self knownPublicKey]; + ECKeyPair *ourKeyPair = [self knownKeyPair]; + NSData *initializationVector = [self knownInitializationVector]; + + OWSProvisioningCipher *cipher = [[OWSProvisioningCipher alloc] initWithTheirPublicKey:theirPublicKey + ourKeyPair:ourKeyPair + initializationVector:initializationVector]; + + NSData *message = [self knownData]; + NSData *actualOutput = [cipher encrypt:message]; + + uint8_t expectedBytes[] = { + 0x01, 0xec, 0x67, 0x0b, + 0xb7, 0x18, 0xe1, 0xe9, + 0x0a, 0xcc, 0x5e, 0xcb, + 0x37, 0xab, 0x79, 0xe0, + 0x09, 0xf7, 0x2b, 0xf7, + 0x14, 0x3d, 0x45, 0xd7, + 0x45, 0x79, 0x1e, 0x4f, + 0x9d, 0x34, 0x8a, 0x2d, + 0x43, 0x64, 0xd4, 0x7d, + 0x48, 0x9a, 0xdc, 0x5a, + 0xc3, 0x72, 0xfa, 0x63, + 0x41, 0x7a, 0xa8, 0x45, + 0x36, 0xe9, 0xc5, 0xcb, + 0xee, 0x9b, 0xc1, 0x1f, + 0xec, 0x31, 0x1e, 0xc2, + 0x33, 0x2d, 0x95, 0x54, + 0xcc + }; + NSData *expectedOutput = [NSData dataWithBytes:expectedBytes length:65]; + + XCTAssertEqualObjects(expectedOutput, actualOutput); +} + +@end