diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 4c55e845b..23ef51c2d 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -1017,7 +1017,6 @@ A157071D17F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShortAuthenticationStringGeneratorTest.m; sourceTree = ""; }; A157071F17F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PregeneratedKeyAgreementParticipantProtocol.h; sourceTree = ""; }; A157072017F0CD6D007C2BD6 /* PregeneratedKeyAgreementParticipantProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PregeneratedKeyAgreementParticipantProtocol.m; sourceTree = ""; }; - A157072117F0CD6D007C2BD6 /* ZrtpTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZrtpTest.h; sourceTree = ""; }; A157072217F0CD6D007C2BD6 /* ZrtpTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZrtpTest.m; sourceTree = ""; }; A157072517F0CD6D007C2BD6 /* LowLatencyConnectorTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LowLatencyConnectorTest.m; sourceTree = ""; }; A157072817F0CD6D007C2BD6 /* NetworkStreamTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkStreamTest.m; sourceTree = ""; }; @@ -2271,7 +2270,6 @@ A157071B17F0CD6D007C2BD6 /* MasterSecretTest.m */, A157071D17F0CD6D007C2BD6 /* ShortAuthenticationStringGeneratorTest.m */, A157071E17F0CD6D007C2BD6 /* utilities */, - A157072117F0CD6D007C2BD6 /* ZrtpTest.h */, A157072217F0CD6D007C2BD6 /* ZrtpTest.m */, E16E5BF818AAF02100B7C403 /* EC25AgreerTest.m */, ); diff --git a/Signal/src/environment/Environment.h b/Signal/src/environment/Environment.h index 4da5d58df..1e0795109 100644 --- a/Signal/src/environment/Environment.h +++ b/Signal/src/environment/Environment.h @@ -17,6 +17,7 @@ #define ENVIRONMENT_TESTING_OPTION_LOSE_CONF_ACK_ON_PURPOSE @"LoseConfAck" #define ENVIRONMENT_TESTING_OPTION_ALLOW_NETWORK_STREAM_TO_NON_SECURE_END_POINTS @"AllowTcpWithoutTls" #define ENVIRONMENT_LEGACY_OPTION_RTP_PADDING_BIT_IMPLIES_EXTENSION_BIT_AND_TWELVE_EXTRA_ZERO_BYTES_IN_HEADER @"LegacyAndroidInterop_1" +#define TESTING_OPTION_USE_DH_FOR_HANDSHAKE @"DhKeyAgreementOnly" @class RecentCallManager; @class ContactsManager; diff --git a/Signal/src/environment/Release.m b/Signal/src/environment/Release.m index 8699c23a7..25e6b9e2a 100644 --- a/Signal/src/environment/Release.m +++ b/Signal/src/environment/Release.m @@ -67,6 +67,11 @@ static unsigned char DH3K_PRIME[]={ } +(Environment*) unitTestEnvironment:(NSArray*)testingAndLegacyOptions { + NSArray* keyAgreementProtocols = self.supportedKeyAgreementProtocols; + if ([testingAndLegacyOptions containsObject:TESTING_OPTION_USE_DH_FOR_HANDSHAKE]) { + keyAgreementProtocols = @[[Release supportedDH3KKeyAgreementProtocol]]; + } + return [Environment environmentWithLogging:[DiscardingLog discardingLog] andErrorNoter:^(id error, id relatedInfo, bool causedTermination) {} andServerPort:31337 @@ -75,7 +80,7 @@ static unsigned char DH3K_PRIME[]={ andRelayServerHostNameSuffix:@"whispersystems.org" andCertificate:[Certificate certificateFromResourcePath:@"whisperReal" ofType:@"cer"] andCurrentRegionCodeForPhoneNumbers:@"US" - andSupportedKeyAgreementProtocols:[self supportedKeyAgreementProtocols] + andSupportedKeyAgreementProtocols:keyAgreementProtocols andPhoneManager:nil andRecentCallManager:nil andTestingAndLegacyOptions:testingAndLegacyOptions diff --git a/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m b/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m index 527004d30..3c457a793 100644 --- a/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m +++ b/Signal/src/network/rtp/zrtp/agreement/EvpKeyAgreement.m @@ -1,12 +1,11 @@ #import "EvpKeyAgreement.h" -#import "Constraints.h" -#import "NumberUtil.h" +#import "Util.h" #import #import #import #import -#define checkEvpSucess(expr, desc) checkSecurityOperation((expr) == 1, desc) +#define checkEvpOperationResult(expr) checkSecurityOperation((expr) == 1, @"An elliptic curve operation didn't succeed.") #define checkEvpNotNull(expr, desc) checkSecurityOperation((expr) != NULL, desc) #define EC25_COORDINATE_LENGTH 32 @@ -64,8 +63,7 @@ enum KeyAgreementType { ctx = EVP_PKEY_CTX_new_id(keyAgreementType, NULL); checkEvpNotNull(ctx , @"pctx_new_id"); - int ret = EVP_PKEY_paramgen_init(ctx); - checkEvpSucess(ret, @"paramgen_init"); + checkEvpOperationResult(EVP_PKEY_paramgen_init(ctx)); return ctx; } @@ -79,15 +77,15 @@ enum KeyAgreementType { @try{ checkEvpNotNull(dh, @"dh_new"); - + dh->p= [self generateBignumberFor:modulus]; dh->g= [self generateBignumberFor:generator]; - + if ((dh->p == NULL) || (dh->g == NULL)) { [self reportError:@"DH Parameters uninitialized"]; } - + [self createNewEvpKeyFreePreviousIfNessesary:¶ms]; EVP_PKEY_set1_DH(params, dh); @@ -100,56 +98,47 @@ enum KeyAgreementType { -(void) generateEc25Parameters { EVP_PKEY_CTX* pctx = [self createParameterContext]; - int ret; - ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NAMED_ELLIPTIC_CURVE); - checkEvpSucess(ret, @"pctx_ec_init"); + checkEvpOperationResult(EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NAMED_ELLIPTIC_CURVE)); - ret = EVP_PKEY_paramgen(pctx, ¶ms); - checkEvpSucess(ret,@"pctx_paramgen" ); + checkEvpOperationResult(EVP_PKEY_paramgen(pctx, ¶ms)); EVP_PKEY_CTX_free(pctx); } -(void) generateKeyPair { - int ret; - EVP_PKEY_CTX* kctx; - checkEvpNotNull(params, @"parameters uninitialized"); - kctx = EVP_PKEY_CTX_new(params, NULL); - checkEvpNotNull(kctx, @"key_ctx"); - - ret = EVP_PKEY_keygen_init(kctx); - checkEvpSucess(ret, @"keygen_init"); - - ret = EVP_PKEY_keygen(kctx, &pkey); - checkEvpSucess(ret, @"keygen"); - - EVP_PKEY_CTX_free(kctx); + EVP_PKEY_CTX* kctx = NULL; + @try { + kctx = EVP_PKEY_CTX_new(params, NULL); + checkEvpNotNull(kctx, @"key_ctx"); + + checkEvpOperationResult(EVP_PKEY_keygen_init(kctx)); + + checkEvpOperationResult(EVP_PKEY_keygen(kctx, &pkey)); + } @finally { + if (kctx != NULL) EVP_PKEY_CTX_free(kctx); + } } --(NSData*) getSharedSecretForRemotePublicKey:(NSData*) publicKey{ - size_t secret_len; - unsigned char* secret; +-(NSData*) getSharedSecretForRemotePublicKey:(NSData*)publicKey { + EVP_PKEY* peerkey = [self deserializePublicKey:publicKey]; + EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey, NULL); + checkEvpNotNull(ctx, @"ctx_new"); - EVP_PKEY* peerkey = [self deserializePublicKey:[publicKey bytes] withLength:publicKey.length]; - EVP_PKEY_CTX* ctx; + checkEvpOperationResult(EVP_PKEY_derive_init(ctx)); + checkEvpOperationResult(EVP_PKEY_derive_set_peer(ctx, peerkey)); + size_t secret_len; + checkEvpOperationResult(EVP_PKEY_derive(ctx, NULL, &secret_len)); - ctx = EVP_PKEY_CTX_new(pkey, NULL); - checkEvpNotNull(ctx, @"ctx_new"); + unsigned char* secret = OPENSSL_malloc(secret_len); + checkEvpNotNull(secret, @"OPENSSL_malloc"); - checkEvpSucess(EVP_PKEY_derive_init(ctx), @"derive_init"); - checkEvpSucess(EVP_PKEY_derive_set_peer(ctx, peerkey), @"set_peer"); - checkEvpSucess(EVP_PKEY_derive(ctx, NULL, &secret_len), @"derive_step1"); + checkEvpOperationResult(EVP_PKEY_derive(ctx, secret, &secret_len)); - secret = OPENSSL_malloc(secret_len); - checkEvpNotNull(secret, @"secret_malloc"); - - checkEvpSucess(EVP_PKEY_derive(ctx, secret, &secret_len), @"derive_step2"); - NSData* secretData = [NSData dataWithBytes:secret length:secret_len]; EVP_PKEY_CTX_free(ctx); @@ -173,80 +162,124 @@ enum KeyAgreementType { } -(NSData*) serializeDhPublicKey:(EVP_PKEY*)evkey { - - DH* dh; - unsigned char* buf; - - dh = EVP_PKEY_get1_DH(evkey); - - int bufsize = BN_num_bytes(dh->pub_key); - buf = OPENSSL_malloc(bufsize); - checkEvpNotNull(buf, @"dh_pubkey_buffer"); - - BN_bn2bin(dh->pub_key, buf); - NSData* pub_key = [NSData dataWithBytes:buf length:(unsigned int)bufsize]; - - DH_free(dh); - OPENSSL_free(buf); - - return pub_key; + DH* dh = NULL; + unsigned char* buf = NULL; + @try { + dh = EVP_PKEY_get1_DH(evkey); + checkEvpNotNull(dh, @"EVP_PKEY_get1_DH"); + + int publicKeySize = BN_num_bytes(dh->pub_key); + NSMutableData* publicKeyBuffer = [NSMutableData dataWithLength:(NSUInteger)publicKeySize]; + + int wroteLength = BN_bn2bin(dh->pub_key, publicKeyBuffer.mutableBytes); + checkSecurityOperation(wroteLength == (long long)publicKeyBuffer.length, @"BN_bn2bin"); + + return publicKeyBuffer; + } @finally { + if (dh != NULL) DH_free(dh); + if (buf != NULL) OPENSSL_free(buf); + } } -(NSData*) serializeEcPublicKey:(EVP_PKEY*)evkey { + require(evkey != NULL); - EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(evkey); - const EC_POINT* ec_pub = EC_KEY_get0_public_key(ec_key); - const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); - - NSData* data = [self packEcCoordinatesFromEcPoint:ec_pub withEcGroup:ec_group]; - - EC_KEY_free(ec_key); - - return data; + EC_KEY* ec_key = NULL; + @try { + ec_key = EVP_PKEY_get1_EC_KEY(evkey); + checkEvpNotNull(ec_key, @"EVP_PKEY_get1_EC_KEY"); + + const EC_POINT* ec_pub = EC_KEY_get0_public_key(ec_key); + checkEvpNotNull(ec_pub, @"EC_KEY_get0_public_key"); + + const EC_GROUP* ec_group = EC_KEY_get0_group(ec_key); + checkEvpNotNull(ec_group, @"EC_KEY_get0_group"); + + return [self packEcCoordinatesFromEcPoint:ec_pub withEcGroup:ec_group]; + } @finally { + EC_KEY_free(ec_key); + } } --(EVP_PKEY*) deserializePublicKey:(const unsigned char*) buf withLength:(size_t) bufsize { +-(EVP_PKEY*) deserializePublicKey:(NSData*)buf { switch (keyAgreementType) { case KeyAgreementType_DH: - return [self deserializeDhPublicKey:buf withLength:bufsize]; + return [self deserializeDhPublicKey:buf]; case KeyAgreementType_ECDH: - return [self deserializeEcPublicKey:buf withLength:bufsize]; + return [self deserializeEcPublicKey:buf]; default: [self reportError:@"Undefined KeyType"]; } } --(EVP_PKEY*) deserializeDhPublicKey:(const unsigned char*) buf withLength:(size_t) bufsize { - - EVP_PKEY* evpk = EVP_PKEY_new(); - DH* dh = DH_new(); - - BIGNUM* bn = BN_new(); - BN_bin2bn(buf, [NumberUtil assertConvertNSUIntegerToInt:bufsize], bn); - dh->pub_key = bn; - - EVP_PKEY_assign_DH(evpk, dh); - - return evpk; +-(EVP_PKEY*) deserializeDhPublicKey:(NSData*)buf { + EVP_PKEY* evpk = NULL; + DH* dh = NULL; + BIGNUM* bn = NULL; + @try { + evpk = EVP_PKEY_new(); + checkEvpNotNull(evpk, @"EVP_PKEY_new"); + + dh = DH_new(); + checkEvpNotNull(dh, @"DH_new"); + + bn = BN_bin2bn(buf.bytes, [NumberUtil assertConvertNSUIntegerToInt:buf.length], NULL); + checkEvpNotNull(bn, @"BN_bin2bn"); + + dh->pub_key = bn; + checkEvpOperationResult(EVP_PKEY_assign_DH(evpk, dh)); + + // Return without cleaning up the result + EVP_PKEY* result = evpk; + evpk = NULL; + dh = NULL; + bn = NULL; + return result; + } @finally { + if (evpk != NULL) EVP_PKEY_free(evpk); + if (dh != NULL) DH_free(dh); + if (bn != NULL) BN_free(bn); + } } --(EVP_PKEY*) deserializeEcPublicKey:(const unsigned char*) buf withLength:(size_t) bufsize { - EC_KEY* eck = EC_KEY_new_by_curve_name(NAMED_ELLIPTIC_CURVE); - const EC_GROUP* ecg = EC_KEY_get0_group(eck); - - EC_POINT* ecp = EC_POINT_new(ecg); - [self unpackEcCoordinatesFromBuffer:buf ofSize:bufsize toEcPoint:ecp withEcGroup:ecg]; - - EVP_PKEY* evpk = EVP_PKEY_new(); - EC_KEY_set_public_key(eck, ecp); - EVP_PKEY_assign_EC_KEY(evpk, eck); - - EC_POINT_free(ecp); - - return evpk; - +-(EVP_PKEY*) deserializeEcPublicKey:(NSData*)buf { + EC_KEY* key = NULL; + EC_POINT* publicKeyPoint = NULL; + EVP_PKEY* publicKey = NULL; + @try { + key = EC_KEY_new_by_curve_name(NAMED_ELLIPTIC_CURVE); + checkSecurityOperation(key != NULL, @"EC_KEY_new_by_curve_name"); + + const EC_GROUP* group = EC_KEY_get0_group(key); + checkSecurityOperation(group != NULL, @"EC_KEY_get0_group"); + + publicKeyPoint = EC_POINT_new(group); + checkSecurityOperation(publicKeyPoint != NULL, @"EC_POINT_new"); + + [self unpackEcCoordinatesFromBuffer:buf + toEcPoint:publicKeyPoint + withEcGroup:group]; + + publicKey = EVP_PKEY_new(); + checkSecurityOperation(publicKey != NULL, @"EVP_PKEY_new"); + + checkEvpOperationResult(EC_KEY_set_public_key(key, publicKeyPoint)); + + checkEvpOperationResult(EVP_PKEY_assign_EC_KEY(publicKey, key)); + + // Return without cleaning up the result + EVP_PKEY* result = publicKey; + publicKey = NULL; + key = NULL; + publicKeyPoint = NULL; + return result; + } @finally { + if (key != NULL) EC_KEY_free(key); + if (publicKeyPoint != NULL) EC_POINT_free(publicKeyPoint); + if (publicKey != NULL) EVP_PKEY_free(publicKey); + } } -(NSData*) packEcCoordinatesFromEcPoint:(const EC_POINT*) ec_point withEcGroup:(const EC_GROUP*) ec_group { @@ -256,16 +289,16 @@ enum KeyAgreementType { x = BN_new(); y = BN_new(); checkSecurityOperation(x != NULL && y != NULL, @"BN_new"); - - checkSecurityOperation(1 == EC_POINT_get_affine_coordinates_GFp(ec_group, ec_point, x, y, NULL), @"EC_POINT_get_affine_coordinates_GFp"); - + + checkEvpOperationResult(EC_POINT_get_affine_coordinates_GFp(ec_group, ec_point, x, y, NULL)); + int len_x = BN_num_bytes(x); int len_y = BN_num_bytes(y); checkSecurityOperation(len_x >= 0 && len_x <= EC25_COORDINATE_LENGTH, @"BN_num_bytes(x)"); checkSecurityOperation(len_y >= 0 && len_y <= EC25_COORDINATE_LENGTH, @"BN_num_bytes(y)"); int unused_x = EC25_COORDINATE_LENGTH - len_x; int unused_y = EC25_COORDINATE_LENGTH - len_y; - + NSMutableData* data = [NSMutableData dataWithLength:EC25_COORDINATE_LENGTH*2]; // We offset the writes to keep things constant sized. @@ -281,23 +314,25 @@ enum KeyAgreementType { } } --(void) unpackEcCoordinatesFromBuffer:(const unsigned char*) buffer - ofSize:(size_t) bufsize - toEcPoint:(EC_POINT*) ecp - withEcGroup:(const EC_GROUP*) ecg { - - checkOperation(2*EC25_COORDINATE_LENGTH == bufsize); - - BIGNUM* x = BN_new(); - BIGNUM* y = BN_new(); +-(void) unpackEcCoordinatesFromBuffer:(NSData*)buffer + toEcPoint:(EC_POINT*)ecp + withEcGroup:(const EC_GROUP*)ecg { - BN_bin2bn(buffer, EC25_COORDINATE_LENGTH, x); - BN_bin2bn(buffer+EC25_COORDINATE_LENGTH, EC25_COORDINATE_LENGTH, y); + checkOperation(buffer.length == 2*EC25_COORDINATE_LENGTH); - EC_POINT_set_affine_coordinates_GFp(ecg, ecp, x, y, NULL); - - BN_free(x); - BN_free(y); + BIGNUM* x = NULL; + BIGNUM* y = NULL; + @try { + const unsigned char* bytes = buffer.bytes; + x = BN_bin2bn(bytes, EC25_COORDINATE_LENGTH, NULL); + y = BN_bin2bn(bytes + EC25_COORDINATE_LENGTH, EC25_COORDINATE_LENGTH, NULL); + checkSecurityOperation(x != NULL && y != NULL, @"BN_bin2bn"); + + checkEvpOperationResult(EC_POINT_set_affine_coordinates_GFp(ecg, ecp, x, y, NULL)); + } @finally { + if (x != NULL) BN_free(x); + if (y != NULL) BN_free(y); + } } #pragma mark Helper Functions @@ -324,9 +359,4 @@ enum KeyAgreementType { [SecurityFailure raise:[NSString stringWithFormat:@"Security related failure: %@ (in %s at line %d)", errorString,__FILE__,__LINE__]]; } - - - - - @end diff --git a/Signal/test/network/rtp/zrtp/ZrtpTest.h b/Signal/test/network/rtp/zrtp/ZrtpTest.h deleted file mode 100644 index 1e81e2fc3..000000000 --- a/Signal/test/network/rtp/zrtp/ZrtpTest.h +++ /dev/null @@ -1,17 +0,0 @@ -#import -#import "ZrtpManager.h" -#import "HelloPacket.h" -#import "ConfirmPacket.h" -#import "DhPacket.h" -#import "CommitPacket.h" -#import "HandshakePacket.h" -#import "Util.h" -#import "DH3KKeyAgreementProtocol.h" -#import "PregeneratedKeyAgreementParticipantProtocol.h" -#import "MasterSecret.h" -#import "ZrtpResponder.h" -#import "ZrtpInitiator.h" - -@interface ZrtpTest : XCTestCase - -@end diff --git a/Signal/test/network/rtp/zrtp/ZrtpTest.m b/Signal/test/network/rtp/zrtp/ZrtpTest.m index 947cbee6a..4b746a9d5 100644 --- a/Signal/test/network/rtp/zrtp/ZrtpTest.m +++ b/Signal/test/network/rtp/zrtp/ZrtpTest.m @@ -1,6 +1,8 @@ -#import "ZrtpTest.h" -#import "ZrtpHandshakeResult.h" +#import #import "TestUtil.h" +#import "Util.h" +#import "CallController.h" +#import "ZrtpManager.h" #import "ThreadManager.h" #import "ZrtpHandshakeResult.h" #import "DiscardingLog.h" @@ -19,6 +21,9 @@ bool pm(HandshakePacket* p1, HandshakePacket* p2) { } #define AssertPacketsMatch(p1, p2) STAssertTrue(pm(p1, p2), @"") +@interface ZrtpTest : XCTestCase +@end + @implementation ZrtpTest - (void)setUp{ @@ -94,4 +99,32 @@ bool pm(HandshakePacket* p1, HandshakePacket* p2) { [cc2 terminateWithReason:CallTerminationType_HangupLocal withFailureInfo:nil andRelatedInfo:nil]; } +-(void) testDhHandshake { + [Environment setCurrent:testEnvWith(TESTING_OPTION_USE_DH_FOR_HANDSHAKE)]; + + IpEndPoint* receiver = [IpEndPoint ipEndPointAtAddress:[IpAddress localhost] + onPort:10000 + (in_port_t)arc4random_uniform(20000)]; + + UdpSocket* u1 = [UdpSocket udpSocketToFirstSenderOnLocalPort:receiver.port]; + CallController* cc1 = [CallController callControllerForCallInitiatedLocally:true + withRemoteNumber:testPhoneNumber1 + andOptionallySpecifiedContact:nil]; + TOCFuture* f1 = [ZrtpManager asyncPerformHandshakeOver:[RtpSocket rtpSocketOverUdp:u1 interopOptions:@[]] + andCallController:cc1]; + + UdpSocket* u2 = [UdpSocket udpSocketTo:receiver]; + CallController* cc2 = [CallController callControllerForCallInitiatedLocally:false + withRemoteNumber:testPhoneNumber2 + andOptionallySpecifiedContact:nil]; + TOCFuture* f2 = [ZrtpManager asyncPerformHandshakeOver:[RtpSocket rtpSocketOverUdp:u2 interopOptions:@[]] + andCallController:cc2]; + + testChurnUntil(!f1.isIncomplete && !f2.isIncomplete, 15.0); + test(f1.hasResult); + test(f2.hasResult); + + [cc1 terminateWithReason:CallTerminationType_HangupLocal withFailureInfo:nil andRelatedInfo:nil]; + [cc2 terminateWithReason:CallTerminationType_HangupLocal withFailureInfo:nil andRelatedInfo:nil]; +} + @end