Skip to content

Commit

Permalink
Add certificate generate/set functionality to bring iOS closer to JS API
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Kári Helgason <[email protected]>
Reviewed-by: Steve Anton <[email protected]>
Cr-Commit-Position: refs/heads/master@{#24276}
  • Loading branch information
iedemam authored and Commit Bot committed Aug 13, 2018
1 parent b336c27 commit ccee56b
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 7 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Manish Jethani <[email protected]>
Martin Storsjo <[email protected]>
Matthias Liebig <[email protected]>
Maxim Potapov <[email protected]>
Michael Iedema <[email protected]>
Mike Gilbert <[email protected]>
Mo Zanaty <[email protected]>
Pali Rohar
Expand Down
6 changes: 6 additions & 0 deletions examples/objc/AppRTCMobile/ARDAppClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
3 changes: 3 additions & 0 deletions sdk/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
70 changes: 70 additions & 0 deletions sdk/objc/Framework/Classes/PeerConnection/RTCCertificate.mm
Original file line number Diff line number Diff line change
@@ -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<rtc::RTCCertificate> 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
37 changes: 31 additions & 6 deletions sdk/objc/Framework/Classes/PeerConnection/RTCConfiguration.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
@implementation RTCConfiguration

@synthesize iceServers = _iceServers;
@synthesize certificate = _certificate;
@synthesize iceTransportPolicy = _iceTransportPolicy;
@synthesize bundlePolicy = _bundlePolicy;
@synthesize rtcpMuxPolicy = _rtcpMuxPolicy;
Expand Down Expand Up @@ -63,6 +64,14 @@ - (instancetype)initWithNativeConfiguration:
[iceServers addObject:iceServer];
}
_iceServers = iceServers;
if (!config.certificates.empty()) {
rtc::scoped_refptr<rtc::RTCCertificate> 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 =
Expand Down Expand Up @@ -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<rtc::RTCCertificate> certificate =
rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(keyType),
absl::optional<uint64_t>());
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<rtc::RTCCertificate> 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<rtc::RTCCertificate> certificate =
rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(keyType),
absl::optional<uint64_t>());
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;
Expand Down
44 changes: 44 additions & 0 deletions sdk/objc/Framework/Headers/WebRTC/RTCCertificate.h
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

#import <WebRTC/RTCMacros.h>

NS_ASSUME_NONNULL_BEGIN

RTC_EXPORT
@interface RTCCertificate : NSObject <NSCopying>

/** 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
5 changes: 4 additions & 1 deletion sdk/objc/Framework/Headers/WebRTC/RTCConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#import <Foundation/Foundation.h>

#import <WebRTC/RTCCertificate.h>
#import <WebRTC/RTCMacros.h>

@class RTCIceServer;
Expand Down Expand Up @@ -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<RTCIceServer *> *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;
Expand Down
77 changes: 77 additions & 0 deletions sdk/objc/Framework/UnitTests/RTCCertificateTest.mm
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>

#include <vector>

#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];
}

0 comments on commit ccee56b

Please sign in to comment.