From ccee56beee32b0caaf8130622606f044b7913d46 Mon Sep 17 00:00:00 2001 From: Michael Iedema Date: Thu, 5 Jul 2018 15:28:24 +0200 Subject: [PATCH] Add certificate generate/set functionality to bring iOS closer to JS API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JS API supports two operations which have never been implemented in the iOS counterpart: - generate a new certificate - use this certificate when creating a new PeerConnection Both functions are illustrated in the generateCertificate example code: - https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/generateCertificate Currently, on iOS, a new certificate is automatically generated for every PeerConnection with no programmatic way to set a specific certificate. Work sponsored by |pipe| Bug: webrtc:9498 Change-Id: Ic1936c3de8b8bd18aef67c784727b72f90e7157c Reviewed-on: https://webrtc-review.googlesource.com/87303 Commit-Queue: Steve Anton Reviewed-by: Kári Helgason Reviewed-by: Steve Anton Cr-Commit-Position: refs/heads/master@{#24276} --- AUTHORS | 1 + examples/objc/AppRTCMobile/ARDAppClient.m | 6 ++ sdk/BUILD.gn | 3 + .../Classes/PeerConnection/RTCCertificate.mm | 70 +++++++++++++++++ .../PeerConnection/RTCConfiguration.mm | 37 +++++++-- .../Framework/Headers/WebRTC/RTCCertificate.h | 44 +++++++++++ .../Headers/WebRTC/RTCConfiguration.h | 5 +- .../Framework/UnitTests/RTCCertificateTest.mm | 77 +++++++++++++++++++ 8 files changed, 236 insertions(+), 7 deletions(-) create mode 100644 sdk/objc/Framework/Classes/PeerConnection/RTCCertificate.mm create mode 100644 sdk/objc/Framework/Headers/WebRTC/RTCCertificate.h create mode 100644 sdk/objc/Framework/UnitTests/RTCCertificateTest.mm diff --git a/AUTHORS b/AUTHORS index 69b5e2fc4a5..41833d090bf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -36,6 +36,7 @@ Manish Jethani Martin Storsjo Matthias Liebig Maxim Potapov +Michael Iedema Mike Gilbert Mo Zanaty Pali Rohar diff --git a/examples/objc/AppRTCMobile/ARDAppClient.m b/examples/objc/AppRTCMobile/ARDAppClient.m index e4dbad416eb..465ce8c9b80 100644 --- a/examples/objc/AppRTCMobile/ARDAppClient.m +++ b/examples/objc/AppRTCMobile/ARDAppClient.m @@ -533,8 +533,14 @@ - (void)startSignalingIfReady { // Create peer connection. RTCMediaConstraints *constraints = [self defaultPeerConnectionConstraints]; RTCConfiguration *config = [[RTCConfiguration alloc] init]; + RTCCertificate *pcert = [RTCCertificate generateCertificateWithParams:@{ + @"expires" : @100000, + @"name" : @"RSASSA-PKCS1-v1_5" + }]; config.iceServers = _iceServers; config.sdpSemantics = RTCSdpSemanticsUnifiedPlan; + config.certificate = pcert; + _peerConnection = [_factory peerConnectionWithConfiguration:config constraints:constraints delegate:self]; diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index 7c95f0fe9e2..4abea8cf031 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -631,6 +631,7 @@ if (is_ios || is_mac) { "objc/Framework/Classes/PeerConnection/RTCAudioSource.mm", "objc/Framework/Classes/PeerConnection/RTCAudioTrack+Private.h", "objc/Framework/Classes/PeerConnection/RTCAudioTrack.mm", + "objc/Framework/Classes/PeerConnection/RTCCertificate.mm", "objc/Framework/Classes/PeerConnection/RTCConfiguration+Native.h", "objc/Framework/Classes/PeerConnection/RTCConfiguration+Private.h", "objc/Framework/Classes/PeerConnection/RTCConfiguration.mm", @@ -693,6 +694,7 @@ if (is_ios || is_mac) { "objc/Framework/Classes/PeerConnection/RTCVideoTrack.mm", "objc/Framework/Headers/WebRTC/RTCAudioSource.h", "objc/Framework/Headers/WebRTC/RTCAudioTrack.h", + "objc/Framework/Headers/WebRTC/RTCCertificate.h", "objc/Framework/Headers/WebRTC/RTCConfiguration.h", "objc/Framework/Headers/WebRTC/RTCDataChannel.h", "objc/Framework/Headers/WebRTC/RTCDataChannelConfiguration.h", @@ -868,6 +870,7 @@ if (is_ios || is_mac) { testonly = true sources = [ + "objc/Framework/UnitTests/RTCCertificateTest.mm", "objc/Framework/UnitTests/RTCConfigurationTest.mm", "objc/Framework/UnitTests/RTCDataChannelConfigurationTest.mm", "objc/Framework/UnitTests/RTCIceCandidateTest.mm", diff --git a/sdk/objc/Framework/Classes/PeerConnection/RTCCertificate.mm b/sdk/objc/Framework/Classes/PeerConnection/RTCCertificate.mm new file mode 100644 index 00000000000..34fa837bdca --- /dev/null +++ b/sdk/objc/Framework/Classes/PeerConnection/RTCCertificate.mm @@ -0,0 +1,70 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import "WebRTC/RTCCertificate.h" +#import "WebRTC/RTCLogging.h" + +#include "rtc_base/logging.h" +#include "rtc_base/rtccertificategenerator.h" +#include "rtc_base/sslidentity.h" + +@implementation RTCCertificate + +@synthesize private_key = _private_key; +@synthesize certificate = _certificate; + +- (id)copyWithZone:(NSZone *)zone { + id copy = [[[self class] alloc] initWithPrivateKey:[self.private_key copyWithZone:zone] + certificate:[self.certificate copyWithZone:zone]]; + return copy; +} + +- (instancetype)initWithPrivateKey:(NSString *)private_key certificate:(NSString *)certificate { + if (self = [super init]) { + _private_key = [private_key copy]; + _certificate = [certificate copy]; + } + return self; +} + ++ (nullable RTCCertificate *)generateCertificateWithParams:(NSDictionary *)params { + rtc::KeyType keyType = rtc::KT_ECDSA; + NSString *keyTypeString = [params valueForKey:@"name"]; + if (keyTypeString && [keyTypeString isEqualToString:@"RSASSA-PKCS1-v1_5"]) { + keyType = rtc::KT_RSA; + } + + NSNumber *expires = [params valueForKey:@"expires"]; + rtc::scoped_refptr cc_certificate = nullptr; + if (expires != nil) { + uint64_t expirationTimestamp = [expires unsignedLongLongValue]; + cc_certificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(keyType), + expirationTimestamp); + } else { + cc_certificate = + rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(keyType), absl::nullopt); + } + if (!cc_certificate) { + RTCLogError(@"Failed to generate certificate."); + return nullptr; + } + // grab PEMs and create an NS RTCCerticicate + rtc::RTCCertificatePEM pem = cc_certificate->ToPEM(); + std::string pem_private_key = pem.private_key(); + std::string pem_certificate = pem.certificate(); + RTC_LOG(LS_INFO) << "CERT PEM "; + RTC_LOG(LS_INFO) << pem_certificate; + + RTCCertificate *cert = [[RTCCertificate alloc] initWithPrivateKey:@(pem_private_key.c_str()) + certificate:@(pem_certificate.c_str())]; + return cert; +} + +@end diff --git a/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm b/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm index a357085caf0..17483774fc0 100644 --- a/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm +++ b/sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm @@ -23,6 +23,7 @@ @implementation RTCConfiguration @synthesize iceServers = _iceServers; +@synthesize certificate = _certificate; @synthesize iceTransportPolicy = _iceTransportPolicy; @synthesize bundlePolicy = _bundlePolicy; @synthesize rtcpMuxPolicy = _rtcpMuxPolicy; @@ -63,6 +64,14 @@ - (instancetype)initWithNativeConfiguration: [iceServers addObject:iceServer]; } _iceServers = iceServers; + if (!config.certificates.empty()) { + rtc::scoped_refptr native_cert; + native_cert = config.certificates[0]; + rtc::RTCCertificatePEM native_pem = native_cert->ToPEM(); + _certificate = + [[RTCCertificate alloc] initWithPrivateKey:@(native_pem.private_key().c_str()) + certificate:@(native_pem.certificate().c_str())]; + } _iceTransportPolicy = [[self class] transportPolicyForTransportsType:config.type]; _bundlePolicy = @@ -168,16 +177,32 @@ - (NSString *)description { _iceBackupCandidatePairPingInterval; rtc::KeyType keyType = [[self class] nativeEncryptionKeyTypeForKeyType:_keyType]; - // Generate non-default certificate. - if (keyType != rtc::KT_DEFAULT) { - rtc::scoped_refptr certificate = - rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(keyType), - absl::optional()); + if (_certificate != nullptr) { + // if offered a pemcert use it... + RTC_LOG(LS_INFO) << "Have configured cert - using it."; + std::string pem_private_key = [[_certificate private_key] UTF8String]; + std::string pem_certificate = [[_certificate certificate] UTF8String]; + rtc::RTCCertificatePEM pem = rtc::RTCCertificatePEM(pem_private_key, pem_certificate); + rtc::scoped_refptr certificate = rtc::RTCCertificate::FromPEM(pem); + RTC_LOG(LS_INFO) << "Created cert from PEM strings."; if (!certificate) { - RTCLogError(@"Failed to generate certificate."); + RTC_LOG(LS_ERROR) << "Failed to generate certificate from PEM."; return nullptr; } nativeConfig->certificates.push_back(certificate); + } else { + RTC_LOG(LS_INFO) << "Don't have configured cert."; + // Generate non-default certificate. + if (keyType != rtc::KT_DEFAULT) { + rtc::scoped_refptr certificate = + rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(keyType), + absl::optional()); + if (!certificate) { + RTCLogError(@"Failed to generate certificate."); + return nullptr; + } + nativeConfig->certificates.push_back(certificate); + } } nativeConfig->ice_candidate_pool_size = _iceCandidatePoolSize; nativeConfig->prune_turn_ports = _shouldPruneTurnPorts ? true : false; diff --git a/sdk/objc/Framework/Headers/WebRTC/RTCCertificate.h b/sdk/objc/Framework/Headers/WebRTC/RTCCertificate.h new file mode 100644 index 00000000000..6b4ea4323a0 --- /dev/null +++ b/sdk/objc/Framework/Headers/WebRTC/RTCCertificate.h @@ -0,0 +1,44 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +RTC_EXPORT +@interface RTCCertificate : NSObject + +/** Private key in PEM. */ +@property(nonatomic, readonly, copy) NSString *private_key; + +/** Public key in an x509 cert encoded in PEM. */ +@property(nonatomic, readonly, copy) NSString *certificate; + +/** + * Initialize an RTCCertificate with PEM strings for private_key and certificate. + */ +- (instancetype)initWithPrivateKey:(NSString *)private_key + certificate:(NSString *)certificate NS_DESIGNATED_INITIALIZER; + +- (instancetype)init NS_UNAVAILABLE; + +/** Generate a new certificate for 're' use. + * + * Optional dictionary of parameters. Defaults to KeyType ECDSA if none are + * provided. + * - name: "ECDSA" or "RSASSA-PKCS1-v1_5" + */ ++ (nullable RTCCertificate *)generateCertificateWithParams:(NSDictionary *)params; + +@end + +NS_ASSUME_NONNULL_END diff --git a/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h b/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h index d274530ae24..11ea6a35479 100644 --- a/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h +++ b/sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h @@ -10,6 +10,7 @@ #import +#import #import @class RTCIceServer; @@ -67,13 +68,15 @@ typedef NS_ENUM(NSInteger, RTCSdpSemantics) { }; NS_ASSUME_NONNULL_BEGIN - RTC_EXPORT @interface RTCConfiguration : NSObject /** An array of Ice Servers available to be used by ICE. */ @property(nonatomic, copy) NSArray *iceServers; +/** An RTCCertificate for 're' use. */ +@property(nonatomic, nullable) RTCCertificate *certificate; + /** Which candidates the ICE agent is allowed to use. The W3C calls it * |iceTransportPolicy|, while in C++ it is called |type|. */ @property(nonatomic, assign) RTCIceTransportPolicy iceTransportPolicy; diff --git a/sdk/objc/Framework/UnitTests/RTCCertificateTest.mm b/sdk/objc/Framework/UnitTests/RTCCertificateTest.mm new file mode 100644 index 00000000000..64269f7b8c8 --- /dev/null +++ b/sdk/objc/Framework/UnitTests/RTCCertificateTest.mm @@ -0,0 +1,77 @@ +/* + * Copyright 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#import + +#include + +#include "rtc_base/gunit.h" + +#import "NSString+StdString.h" +#import "RTCConfiguration+Private.h" +#import "WebRTC/RTCConfiguration.h" +#import "WebRTC/RTCIceServer.h" +#import "WebRTC/RTCMediaConstraints.h" +#import "WebRTC/RTCPeerConnection.h" +#import "WebRTC/RTCPeerConnectionFactory.h" + +@interface RTCCertificateTest : NSObject +- (void)testCertificateIsUsedInConfig; +@end + +@implementation RTCCertificateTest + +- (void)testCertificateIsUsedInConfig { + RTCConfiguration *originalConfig = [[RTCConfiguration alloc] init]; + + NSArray *urlStrings = @[ @"stun:stun1.example.net" ]; + RTCIceServer *server = [[RTCIceServer alloc] initWithURLStrings:urlStrings]; + originalConfig.iceServers = @[ server ]; + + // Generate a new certificate. + RTCCertificate *originalCertificate = [RTCCertificate generateCertificateWithParams:@{ + @"expires" : @100000, + @"name" : @"RSASSA-PKCS1-v1_5" + }]; + + // Store certificate in configuration. + originalConfig.certificate = originalCertificate; + + RTCMediaConstraints *contraints = + [[RTCMediaConstraints alloc] initWithMandatoryConstraints:@{} optionalConstraints:nil]; + RTCPeerConnectionFactory *factory = [[RTCPeerConnectionFactory alloc] init]; + + // Create PeerConnection with this certificate. + RTCPeerConnection *peerConnection = + [factory peerConnectionWithConfiguration:originalConfig constraints:contraints delegate:nil]; + + // Retrieve certificate from the configuration. + RTCConfiguration *retrievedConfig = peerConnection.configuration; + + // Extract PEM strings from original certificate. + std::string originalPrivateKeyField = [[originalCertificate private_key] UTF8String]; + std::string originalCertificateField = [[originalCertificate certificate] UTF8String]; + + // Extract PEM strings from certificate retrieved from configuration. + RTCCertificate *retrievedCertificate = retrievedConfig.certificate; + std::string retrievedPrivateKeyField = [[retrievedCertificate private_key] UTF8String]; + std::string retrievedCertificateField = [[retrievedCertificate certificate] UTF8String]; + + // Check that the original certificate and retrieved certificate match. + EXPECT_EQ(originalPrivateKeyField, retrievedPrivateKeyField); + EXPECT_EQ(retrievedCertificateField, retrievedCertificateField); +} + +@end + +TEST(CertificateTest, CertificateIsUsedInConfig) { + RTCCertificateTest *test = [[RTCCertificateTest alloc] init]; + [test testCertificateIsUsedInConfig]; +}