diff --git a/sdk/android/BUILD.gn b/sdk/android/BUILD.gn index 9a319aba75..9890c0cbb1 100644 --- a/sdk/android/BUILD.gn +++ b/sdk/android/BUILD.gn @@ -295,6 +295,7 @@ if (is_android) { "api/org/webrtc/PeerConnection.java", "api/org/webrtc/PeerConnectionDependencies.java", "api/org/webrtc/PeerConnectionFactory.java", + "api/org/webrtc/RtcCertificatePem.java", "api/org/webrtc/RTCStats.java", "api/org/webrtc/RTCStatsCollectorCallback.java", "api/org/webrtc/RTCStatsReport.java", @@ -625,6 +626,8 @@ if (is_android) { "src/jni/pc/peerconnection.h", "src/jni/pc/peerconnectionfactory.cc", "src/jni/pc/peerconnectionfactory.h", + "src/jni/pc/rtccertificate.cc", + "src/jni/pc/rtccertificate.h", "src/jni/pc/rtcstatscollectorcallbackwrapper.cc", "src/jni/pc/rtcstatscollectorcallbackwrapper.h", "src/jni/pc/rtpparameters.cc", @@ -1180,6 +1183,7 @@ if (is_android) { "api/org/webrtc/RTCStats.java", "api/org/webrtc/RTCStatsCollectorCallback.java", "api/org/webrtc/RTCStatsReport.java", + "api/org/webrtc/RtcCertificatePem.java", "api/org/webrtc/RtpParameters.java", "api/org/webrtc/RtpReceiver.java", "api/org/webrtc/RtpSender.java", @@ -1272,6 +1276,7 @@ if (is_android) { "instrumentationtests/src/org/webrtc/PeerConnectionFactoryTest.java", "instrumentationtests/src/org/webrtc/PeerConnectionTest.java", "instrumentationtests/src/org/webrtc/RendererCommonTest.java", + "instrumentationtests/src/org/webrtc/RtcCertificatePemTest.java", "instrumentationtests/src/org/webrtc/SurfaceTextureHelperTest.java", "instrumentationtests/src/org/webrtc/SurfaceViewRendererOnMeasureTest.java", "instrumentationtests/src/org/webrtc/TestConstants.java", @@ -1296,6 +1301,7 @@ if (is_android) { "//rtc_base:base_java", "//third_party/android_support_test_runner:rules_java", "//third_party/android_support_test_runner:runner_java", + "//third_party/google-truth:google_truth_java", "//third_party/junit", ] diff --git a/sdk/android/api/org/webrtc/PeerConnection.java b/sdk/android/api/org/webrtc/PeerConnection.java index 84d49ebb8a..a252f198f3 100644 --- a/sdk/android/api/org/webrtc/PeerConnection.java +++ b/sdk/android/api/org/webrtc/PeerConnection.java @@ -385,6 +385,7 @@ public class PeerConnection { public IceTransportsType iceTransportsType; public List iceServers; public BundlePolicy bundlePolicy; + @Nullable public RtcCertificatePem certificate; public RtcpMuxPolicy rtcpMuxPolicy; public TcpCandidatePolicy tcpCandidatePolicy; public CandidateNetworkPolicy candidateNetworkPolicy; @@ -517,6 +518,12 @@ public class PeerConnection { return bundlePolicy; } + @Nullable + @CalledByNative("RTCConfiguration") + RtcCertificatePem getCertificate() { + return certificate; + } + @CalledByNative("RTCConfiguration") RtcpMuxPolicy getRtcpMuxPolicy() { return rtcpMuxPolicy; @@ -721,6 +728,10 @@ public class PeerConnection { return nativeGetRemoteDescription(); } + public RtcCertificatePem getCertificate() { + return nativeGetCertificate(); + } + public DataChannel createDataChannel(String label, DataChannel.Init init) { return nativeCreateDataChannel(label, init); } @@ -1107,6 +1118,7 @@ public class PeerConnection { private native long nativeGetNativePeerConnection(); private native SessionDescription nativeGetLocalDescription(); private native SessionDescription nativeGetRemoteDescription(); + private native RtcCertificatePem nativeGetCertificate(); private native DataChannel nativeCreateDataChannel(String label, DataChannel.Init init); private native void nativeCreateOffer(SdpObserver observer, MediaConstraints constraints); private native void nativeCreateAnswer(SdpObserver observer, MediaConstraints constraints); diff --git a/sdk/android/api/org/webrtc/RtcCertificatePem.java b/sdk/android/api/org/webrtc/RtcCertificatePem.java new file mode 100644 index 0000000000..6856e4099d --- /dev/null +++ b/sdk/android/api/org/webrtc/RtcCertificatePem.java @@ -0,0 +1,73 @@ +/* + * 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. + */ + +package org.webrtc; + +/** + * Easily storable/serializable version of a native C++ RTCCertificatePEM. + */ +public class RtcCertificatePem { + /** PEM string representation of the private key. */ + public final String privateKey; + /** PEM string representation of the certificate. */ + public final String certificate; + /** Default expiration time of 30 days. */ + private static final long DEFAULT_EXPIRY = 60 * 60 * 24 * 30; + + /** Instantiate an RtcCertificatePem object from stored strings. */ + @CalledByNative + public RtcCertificatePem(String privateKey, String certificate) { + this.privateKey = privateKey; + this.certificate = certificate; + } + + @CalledByNative + String getPrivateKey() { + return privateKey; + } + + @CalledByNative + String getCertificate() { + return certificate; + } + + /** + * Generate a new RtcCertificatePem with the default settings of KeyType = ECDSA and + * expires = 30 days. + */ + public static RtcCertificatePem generateCertificate() { + return nativeGenerateCertificate(PeerConnection.KeyType.ECDSA, DEFAULT_EXPIRY); + } + + /** + * Generate a new RtcCertificatePem with a custom KeyType and the default setting of + * expires = 30 days. + */ + public static RtcCertificatePem generateCertificate(PeerConnection.KeyType keyType) { + return nativeGenerateCertificate(keyType, DEFAULT_EXPIRY); + } + + /** + * Generate a new RtcCertificatePem with a custom expires and the default setting of + * KeyType = ECDSA. + */ + public static RtcCertificatePem generateCertificate(long expires) { + return nativeGenerateCertificate(PeerConnection.KeyType.ECDSA, expires); + } + + /** Generate a new RtcCertificatePem with a custom KeyType and a custom expires. */ + public static RtcCertificatePem generateCertificate( + PeerConnection.KeyType keyType, long expires) { + return nativeGenerateCertificate(keyType, expires); + } + + private static native RtcCertificatePem nativeGenerateCertificate( + PeerConnection.KeyType keyType, long expires); +} diff --git a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java index 7d0edc598e..894e2bde3c 100644 --- a/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java +++ b/sdk/android/instrumentationtests/src/org/webrtc/PeerConnectionTest.java @@ -11,6 +11,7 @@ package org.webrtc; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -663,6 +664,24 @@ public class PeerConnectionTest { assertNotNull(offeringPC); } + @Test + @SmallTest + public void testCreationWithCertificate() throws Exception { + PeerConnectionFactory factory = PeerConnectionFactory.builder().createPeerConnectionFactory(); + PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(Arrays.asList()); + + // Test certificate. + RtcCertificatePem originalCert = RtcCertificatePem.generateCertificate(); + config.certificate = originalCert; + + ObserverExpectations offeringExpectations = new ObserverExpectations("PCTest:offerer"); + PeerConnection offeringPC = factory.createPeerConnection(config, offeringExpectations); + + RtcCertificatePem restoredCert = offeringPC.getCertificate(); + assertEquals(originalCert.privateKey, restoredCert.privateKey); + assertEquals(originalCert.certificate, restoredCert.certificate); + } + @Test @MediumTest public void testCompleteSession() throws Exception { diff --git a/sdk/android/instrumentationtests/src/org/webrtc/RtcCertificatePemTest.java b/sdk/android/instrumentationtests/src/org/webrtc/RtcCertificatePemTest.java new file mode 100644 index 0000000000..e1840c356d --- /dev/null +++ b/sdk/android/instrumentationtests/src/org/webrtc/RtcCertificatePemTest.java @@ -0,0 +1,73 @@ +/* + * 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. + */ + +package org.webrtc; + +import static com.google.common.truth.Truth.assertThat; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import android.support.test.filters.SmallTest; +import org.junit.runner.RunWith; +import org.junit.Before; +import org.junit.Test; +import org.webrtc.RtcCertificatePem; +import org.webrtc.PeerConnection.KeyType; + +/** Tests for RtcCertificatePem.java. */ +@RunWith(BaseJUnit4ClassRunner.class) +public class RtcCertificatePemTest { + @Before + public void setUp() { + System.loadLibrary(TestConstants.NATIVE_LIBRARY); + } + + @Test + @SmallTest + public void testConstructor() { + RtcCertificatePem original = RtcCertificatePem.generateCertificate(); + RtcCertificatePem recreated = new RtcCertificatePem(original.privateKey, original.certificate); + assertThat(original.privateKey).isEqualTo(recreated.privateKey); + assertThat(original.certificate).isEqualTo(recreated.certificate); + } + + @Test + @SmallTest + public void testGenerateCertificateDefaults() { + RtcCertificatePem rtcCertificate = RtcCertificatePem.generateCertificate(); + assertThat(rtcCertificate.privateKey).isNotEmpty(); + assertThat(rtcCertificate.certificate).isNotEmpty(); + } + + @Test + @SmallTest + public void testGenerateCertificateCustomKeyTypeDefaultExpires() { + RtcCertificatePem rtcCertificate = + RtcCertificatePem.generateCertificate(PeerConnection.KeyType.RSA); + assertThat(rtcCertificate.privateKey).isNotEmpty(); + assertThat(rtcCertificate.certificate).isNotEmpty(); + } + + @Test + @SmallTest + public void testGenerateCertificateCustomExpiresDefaultKeyType() { + RtcCertificatePem rtcCertificate = RtcCertificatePem.generateCertificate(60 * 60 * 24); + assertThat(rtcCertificate.privateKey).isNotEmpty(); + assertThat(rtcCertificate.certificate).isNotEmpty(); + } + + @Test + @SmallTest + public void testGenerateCertificateCustomKeyTypeAndExpires() { + RtcCertificatePem rtcCertificate = + RtcCertificatePem.generateCertificate(PeerConnection.KeyType.RSA, 60 * 60 * 24); + assertThat(rtcCertificate.privateKey).isNotEmpty(); + assertThat(rtcCertificate.certificate).isNotEmpty(); + } +} diff --git a/sdk/android/src/jni/pc/peerconnection.cc b/sdk/android/src/jni/pc/peerconnection.cc index 79da797e38..9ef126debd 100644 --- a/sdk/android/src/jni/pc/peerconnection.cc +++ b/sdk/android/src/jni/pc/peerconnection.cc @@ -47,6 +47,7 @@ #include "sdk/android/src/jni/pc/icecandidate.h" #include "sdk/android/src/jni/pc/mediaconstraints.h" #include "sdk/android/src/jni/pc/mediastreamtrack.h" +#include "sdk/android/src/jni/pc/rtccertificate.h" #include "sdk/android/src/jni/pc/rtcstatscollectorcallbackwrapper.h" #include "sdk/android/src/jni/pc/rtpsender.h" #include "sdk/android/src/jni/pc/sdpobserver.h" @@ -129,6 +130,8 @@ void JavaToNativeRTCConfiguration( Java_RTCConfiguration_getBundlePolicy(jni, j_rtc_config); ScopedJavaLocalRef j_rtcp_mux_policy = Java_RTCConfiguration_getRtcpMuxPolicy(jni, j_rtc_config); + ScopedJavaLocalRef j_rtc_certificate = + Java_RTCConfiguration_getCertificate(jni, j_rtc_config); ScopedJavaLocalRef j_tcp_candidate_policy = Java_RTCConfiguration_getTcpCandidatePolicy(jni, j_rtc_config); ScopedJavaLocalRef j_candidate_network_policy = @@ -148,6 +151,13 @@ void JavaToNativeRTCConfiguration( rtc_config->bundle_policy = JavaToNativeBundlePolicy(jni, j_bundle_policy); rtc_config->rtcp_mux_policy = JavaToNativeRtcpMuxPolicy(jni, j_rtcp_mux_policy); + if (!j_rtc_certificate.is_null()) { + rtc::scoped_refptr certificate = + rtc::RTCCertificate::FromPEM( + JavaToNativeRTCCertificatePEM(jni, j_rtc_certificate)); + RTC_CHECK(certificate != nullptr) << "supplied certificate is malformed."; + rtc_config->certificates.push_back(certificate); + } rtc_config->tcp_candidate_policy = JavaToNativeTcpCandidatePolicy(jni, j_tcp_candidate_policy); rtc_config->candidate_network_policy = @@ -429,6 +439,16 @@ static ScopedJavaLocalRef JNI_PeerConnection_GetRemoteDescription( return sdp ? NativeToJavaSessionDescription(jni, sdp) : nullptr; } +static ScopedJavaLocalRef JNI_PeerConnection_GetCertificate( + JNIEnv* jni, + const JavaParamRef& j_pc) { + const PeerConnectionInterface::RTCConfiguration rtc_config = + ExtractNativePC(jni, j_pc)->GetConfiguration(); + rtc::scoped_refptr certificate = + rtc_config.certificates[0]; + return NativeToJavaRTCCertificatePEM(jni, certificate->ToPEM()); +} + static ScopedJavaLocalRef JNI_PeerConnection_CreateDataChannel( JNIEnv* jni, const JavaParamRef& j_pc, diff --git a/sdk/android/src/jni/pc/peerconnectionfactory.cc b/sdk/android/src/jni/pc/peerconnectionfactory.cc index 3de79555a6..01c1accaa3 100644 --- a/sdk/android/src/jni/pc/peerconnectionfactory.cc +++ b/sdk/android/src/jni/pc/peerconnectionfactory.cc @@ -396,18 +396,20 @@ static jlong JNI_PeerConnectionFactory_CreatePeerConnection( PeerConnectionInterface::RTCConfigurationType::kAggressive); JavaToNativeRTCConfiguration(jni, j_rtc_config, &rtc_config); - // Generate non-default certificate. - rtc::KeyType key_type = GetRtcConfigKeyType(jni, j_rtc_config); - if (key_type != rtc::KT_DEFAULT) { - rtc::scoped_refptr certificate = - rtc::RTCCertificateGenerator::GenerateCertificate( - rtc::KeyParams(key_type), absl::nullopt); - if (!certificate) { - RTC_LOG(LS_ERROR) << "Failed to generate certificate. KeyType: " - << key_type; - return 0; + if (rtc_config.certificates.empty()) { + // Generate non-default certificate. + rtc::KeyType key_type = GetRtcConfigKeyType(jni, j_rtc_config); + if (key_type != rtc::KT_DEFAULT) { + rtc::scoped_refptr certificate = + rtc::RTCCertificateGenerator::GenerateCertificate( + rtc::KeyParams(key_type), absl::nullopt); + if (!certificate) { + RTC_LOG(LS_ERROR) << "Failed to generate certificate. KeyType: " + << key_type; + return 0; + } + rtc_config.certificates.push_back(certificate); } - rtc_config.certificates.push_back(certificate); } std::unique_ptr constraints; diff --git a/sdk/android/src/jni/pc/rtccertificate.cc b/sdk/android/src/jni/pc/rtccertificate.cc new file mode 100644 index 0000000000..8c598cce09 --- /dev/null +++ b/sdk/android/src/jni/pc/rtccertificate.cc @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#include "sdk/android/src/jni/pc/rtccertificate.h" +#include "sdk/android/src/jni/pc/icecandidate.h" + +#include "rtc_base/refcount.h" +#include "rtc_base/rtccertificate.h" +#include "rtc_base/rtccertificategenerator.h" +#include "sdk/android/generated_peerconnection_jni/jni/RtcCertificatePem_jni.h" +#include "sdk/android/native_api/jni/java_types.h" +#include "sdk/android/src/jni/jni_helpers.h" + +namespace webrtc { +namespace jni { + +rtc::RTCCertificatePEM JavaToNativeRTCCertificatePEM( + JNIEnv* jni, + const JavaRef& j_rtc_certificate) { + ScopedJavaLocalRef privatekey_field = + Java_RtcCertificatePem_getPrivateKey(jni, j_rtc_certificate); + ScopedJavaLocalRef certificate_field = + Java_RtcCertificatePem_getCertificate(jni, j_rtc_certificate); + return rtc::RTCCertificatePEM(JavaToNativeString(jni, privatekey_field), + JavaToNativeString(jni, certificate_field)); +} + +ScopedJavaLocalRef NativeToJavaRTCCertificatePEM( + JNIEnv* jni, + const rtc::RTCCertificatePEM& certificate) { + return Java_RtcCertificatePem_Constructor( + jni, NativeToJavaString(jni, certificate.private_key()), + NativeToJavaString(jni, certificate.certificate())); +} + +static ScopedJavaLocalRef JNI_RtcCertificatePem_GenerateCertificate( + JNIEnv* jni, + const JavaParamRef&, + const JavaParamRef& j_key_type, + jlong j_expires) { + rtc::KeyType key_type = JavaToNativeKeyType(jni, j_key_type); + uint64_t expires = (uint64_t)j_expires; + rtc::scoped_refptr certificate = + rtc::RTCCertificateGenerator::GenerateCertificate( + rtc::KeyParams(key_type), expires); + rtc::RTCCertificatePEM pem = certificate->ToPEM(); + return Java_RtcCertificatePem_Constructor( + jni, NativeToJavaString(jni, pem.private_key()), + NativeToJavaString(jni, pem.certificate())); +} + +} // namespace jni +} // namespace webrtc diff --git a/sdk/android/src/jni/pc/rtccertificate.h b/sdk/android/src/jni/pc/rtccertificate.h new file mode 100644 index 0000000000..f7d1f75d13 --- /dev/null +++ b/sdk/android/src/jni/pc/rtccertificate.h @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#ifndef SDK_ANDROID_SRC_JNI_PC_RTCCERTIFICATE_H_ +#define SDK_ANDROID_SRC_JNI_PC_RTCCERTIFICATE_H_ + +#include "rtc_base/refcount.h" +#include "rtc_base/rtccertificate.h" +#include "sdk/android/native_api/jni/java_types.h" +#include "sdk/android/src/jni/jni_helpers.h" + +namespace webrtc { +namespace jni { + +rtc::RTCCertificatePEM JavaToNativeRTCCertificatePEM( + JNIEnv* jni, + const JavaRef& j_rtc_certificate); + +ScopedJavaLocalRef NativeToJavaRTCCertificatePEM( + JNIEnv* env, + const rtc::RTCCertificatePEM& certificate); + +} // namespace jni +} // namespace webrtc + +#endif // SDK_ANDROID_SRC_JNI_PC_RTCCERTIFICATE_H_