diff --git a/android-stub/src/main/java/android/crypto/spake/SpakeKeyManagerParameters.java b/android-stub/src/main/java/android/crypto/spake/SpakeKeyManagerParameters.java new file mode 100644 index 000000000..de40106ee --- /dev/null +++ b/android-stub/src/main/java/android/crypto/spake/SpakeKeyManagerParameters.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ssl; + +public class SpakeKeyManagerParameters { + + private SpakeKeyManagerParameters() {} + + public byte[] getPassword() { + return null; + } + + public byte[] getIdProver() { + return null; + } + + public byte[] getIdVerifier() { + return null; + } + + public byte[] getContext() { + return null; + } +} + diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java index cc1e10148..dc114543a 100644 --- a/android/src/main/java/org/conscrypt/Platform.java +++ b/android/src/main/java/org/conscrypt/Platform.java @@ -965,4 +965,8 @@ public static boolean isTlsV1Filtered() { public static boolean isTlsV1Supported() { return false; } + + public static boolean isSpake2Supported() { + return false; + } } diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index d1bd6b089..10c98839a 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -530,8 +530,7 @@ static jbyteArray ecSignDigestWithPrivateKey(JNIEnv* env, jobject privateKey, co return reinterpret_cast(env->CallStaticObjectMethod( conscrypt::jniutil::cryptoUpcallsClass, - conscrypt::jniutil::cryptoUpcallsClass_rawSignMethod, - privateKey, messageArray.get())); + conscrypt::jniutil::cryptoUpcallsClass_rawSignMethod, privateKey, messageArray.get())); } static jbyteArray rsaSignDigestWithPrivateKey(JNIEnv* env, jobject privateKey, jint padding, @@ -558,10 +557,9 @@ static jbyteArray rsaSignDigestWithPrivateKey(JNIEnv* env, jobject privateKey, j } return reinterpret_cast( - env->CallStaticObjectMethod( - conscrypt::jniutil::cryptoUpcallsClass, - conscrypt::jniutil::cryptoUpcallsClass_rsaSignMethod, - privateKey, padding, messageArray.get())); + env->CallStaticObjectMethod(conscrypt::jniutil::cryptoUpcallsClass, + conscrypt::jniutil::cryptoUpcallsClass_rsaSignMethod, + privateKey, padding, messageArray.get())); } // rsaDecryptWithPrivateKey uses privateKey to decrypt |ciphertext_len| bytes @@ -592,10 +590,9 @@ static jbyteArray rsaDecryptWithPrivateKey(JNIEnv* env, jobject privateKey, jint } return reinterpret_cast( - env->CallStaticObjectMethod( - conscrypt::jniutil::cryptoUpcallsClass, - conscrypt::jniutil::cryptoUpcallsClass_rsaDecryptMethod, - privateKey, padding, ciphertextArray.get())); + env->CallStaticObjectMethod(conscrypt::jniutil::cryptoUpcallsClass, + conscrypt::jniutil::cryptoUpcallsClass_rsaDecryptMethod, + privateKey, padding, ciphertextArray.get())); } // ********************************************* @@ -7266,7 +7263,7 @@ static void info_callback(const SSL* ssl, int type, int value) { JNI_TRACE("ssl=%p info_callback calling onSSLStateChange", ssl); env->CallVoidMethod(sslHandshakeCallbacks, - conscrypt::jniutil::sslHandshakeCallbacks_onSSLStateChange, type, value); + conscrypt::jniutil::sslHandshakeCallbacks_onSSLStateChange, type, value); if (env->ExceptionCheck()) { JNI_TRACE("ssl=%p info_callback exception", ssl); @@ -10891,6 +10888,24 @@ static jbyteArray NativeCrypto_Scrypt_generate_key(JNIEnv* env, jclass, jbyteArr return key_bytes; } + +#define SPAKE2PLUS_PW_VERIFIER_SIZE 32 +#define SPAKE2PLUS_REGISTRATION_RECORD_SIZE 65 + +static void NativeCrypto_SSL_CTX_set_spake_credential( + JNIEnv* env, jclass, jobject sslCtx, jbyteArray context, jint contextLen, + jbyteArray pwArray, jint pwLen, jbyteArray idProverArray, jint idProverLen, + jbyteArray idVerifierArray, jint idVerifierLen, jboolean isClient) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE( + "SSL_CTX_set_spake_credential(%p, %p, %d, %p, %d, %p, %d, %p, " + "%d, %d)", + sslCtx, context, contextLen, pwArray, pwLen, idProverArray, idProverLen, + idVerifierArray, idVerifierLen, isClient); + + return; +} + // TESTING METHODS BEGIN static int NativeCrypto_BIO_read(JNIEnv* env, jclass, jlong bioRef, jbyteArray outputJavaBytes) { @@ -11369,6 +11384,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_shutdown, "(J" REF_SSL SSL_CALLBACKS ")V"), CONSCRYPT_NATIVE_METHOD(usesBoringSsl_FIPS_mode, "()Z"), CONSCRYPT_NATIVE_METHOD(Scrypt_generate_key, "([B[BIIII)[B"), + CONSCRYPT_NATIVE_METHOD(SSL_CTX_set_spake_credential, "(J[BI[BI[BI[BIZ)V"), // Used for testing only. CONSCRYPT_NATIVE_METHOD(BIO_read, "(J[B)I"), diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index 445ac0de8..d66f777d2 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -639,6 +639,13 @@ static native long X509_CRL_get_nextUpdate(long x509CrlCtx, OpenSSLX509CRL holde static native int X509_supported_extension(long x509ExtensionRef); + // --- SPAKE --------------------------------------------------------------- + + static native void SSL_CTX_set_spake_credential( + Object sslCtx, byte[] context, int contextLen, + byte[] pwArray, int pwLen, byte[] idProverArray, int idProverLen, + byte[] idVerifierArray, int idVerifierLen, boolean isClient); + // --- ASN1_TIME ----------------------------------------------------------- static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) throws ParsingException; @@ -963,6 +970,11 @@ static String cipherSuiteFromJava(String javaCipherSuite) { "TLS_PSK_WITH_AES_256_CBC_SHA", }; + /** TLS-SPAKE */ + static final String[] DEFAULT_SPAKE_CIPHER_SUITES = new String[] { + "TLS1_3_NAMED_PAKE_SPAKE2PLUSV1", + }; + static String[] getSupportedCipherSuites() { return SSLUtils.concat(SUPPORTED_TLS_1_3_CIPHER_SUITES, SUPPORTED_TLS_1_2_CIPHER_SUITES.clone()); } diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java index 51ae84561..1a65fdeab 100644 --- a/common/src/main/java/org/conscrypt/NativeSsl.java +++ b/common/src/main/java/org/conscrypt/NativeSsl.java @@ -60,6 +60,7 @@ final class NativeSsl { private X509Certificate[] localCertificates; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private volatile long ssl; + private final boolean isSpake; private NativeSsl(long ssl, SSLParametersImpl parameters, SSLHandshakeCallbacks handshakeCallbacks, AliasChooser aliasChooser, @@ -86,6 +87,19 @@ BioWrapper newBio() { } } + void initSpake() { + SpakeKeyManager spakeKeyManager = parameters.getSpakeKeyManager(); + byte[] context = spakeKeyManager.getContext(); + byte[] pwArray = spakeKeyManager.getPassword(); + byte[] idProverArray = spakeKeyManager.getIdProver(); + byte[] idVerifierArray = spakeKeyManager.getIdVerifier(); + + long sslCtx = NativeCrypto.SSL_CTX_new(); + NativeCrypto.SSL_CTX_set_spake_credential( + sslCtx, context, context.length, pwArray, pwArray.length, idProverArray, + idProverArray.length, idVerifierArray, idVerifierArray.length, isClient()); + } + void offerToResumeSession(long sslSessionNativePointer) throws SSLException { NativeCrypto.SSL_set_session(ssl, this, sslSessionNativePointer); } @@ -273,6 +287,11 @@ byte[] getTlsChannelId() throws SSLException { } void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOException { + this.isSpake = parameters.isSpake(); + if (this.isSpake) { + initSpake(); + } + boolean enableSessionCreation = parameters.getEnableSessionCreation(); if (!enableSessionCreation) { NativeCrypto.SSL_set_session_creation_enabled(ssl, this, false); @@ -349,7 +368,9 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept // with TLSv1 and SSLv3). NativeCrypto.SSL_set_mode(ssl, this, SSL_MODE_CBC_RECORD_SPLITTING); - setCertificateValidation(); + if (!isSpake) { + setCertificateValidation(); + } setTlsChannelId(channelIdPrivateKey); } diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index d0e7fd5c5..63d5fc45b 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -535,6 +535,12 @@ public OpenSSLProvider(String providerName) { baseClass + "$X25519_CHACHA20"); put("Alg.Alias.ConscryptHpke.DHKEM_X25519_HKDF_SHA256_HKDF_SHA256_GhpkeCHACHA20POLY1305", "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305"); + + /* === SPAKE2+ - Conscrypt internal only === */ + if (Platform.isSpake2Supported()) { + put("TrustManagerFactory.SPAKE2+", "SpakeTrustManagerFactory"); + put("KeyManagerFactory.SPAKE2+", "SpakeKeyManagerFactory"); + } } private boolean classExists(String classname) { diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java index 6c5bde587..79acb3944 100644 --- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java +++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java @@ -69,6 +69,10 @@ final class SSLParametersImpl implements Cloneable { private final PSKKeyManager pskKeyManager; // source of X.509 certificate based authentication trust decisions or null if not provided private final X509TrustManager x509TrustManager; + // source of Spake trust or null if not provided + private final SpakeTrustManager spakeTrustManager; + // source of Spake authentication or null if not provided + private final SpakeKeyManager spakeKeyManager; // protocols enabled for SSL connection String[] enabledProtocols; @@ -126,6 +130,20 @@ final class SSLParametersImpl implements Cloneable { this.serverSessionContext = serverSessionContext; this.clientSessionContext = clientSessionContext; + spakeKeyManager = findFirstSpakeKeyManager(kms); + spakeTrustManager = findFirstSpakeTrustManager(tms); + if (spakeKeyManager != null ^ spakeTrustManager != null) { + throw new IllegalArgumentException("SpakeKeyManager and SpakeTrustManager must be set together"); + } + if (spakeKeyManager != null) { + x509KeyManager = findFirstX509KeyManager(kms); + pskKeyManager = findFirstPSKKeyManager(kms); + x509TrustManager = findFirstX509TrustManager(tms); + if (x509KeyManager != null || pskKeyManager != null || x509TrustManager != null) { + throw new IllegalArgumentException("SpakeManagers should not be set with X509KeyManager, x509TrustManager or PSKKeyManager"); + } + } + // initialize key managers if (kms == null) { x509KeyManager = getDefaultX509KeyManager(); @@ -161,7 +179,8 @@ final class SSLParametersImpl implements Cloneable { boolean x509CipherSuitesNeeded = (x509KeyManager != null) || (x509TrustManager != null); boolean pskCipherSuitesNeeded = pskKeyManager != null; enabledCipherSuites = getDefaultCipherSuites( - x509CipherSuitesNeeded, pskCipherSuitesNeeded); + x509CipherSuitesNeeded, pskCipherSuitesNeeded, isSpake()); + // We ignore the SecureRandom passed in by the caller. The native code below // directly accesses /dev/urandom, which makes it irrelevant. @@ -180,6 +199,8 @@ private SSLParametersImpl(ClientSessionContext clientSessionContext, this.x509KeyManager = x509KeyManager; this.pskKeyManager = pskKeyManager; this.x509TrustManager = x509TrustManager; + this.spakeKeyManager = null; + this.spakeTrustManager = null; this.enabledProtocols = (sslParams.enabledProtocols == null) ? null : sslParams.enabledProtocols.clone(); @@ -248,6 +269,14 @@ PSKKeyManager getPSKKeyManager() { return pskKeyManager; } + /* + * Returns Spake key manager or null for none. + */ + SpakeKeyManager getSpakeKeyManager() { + return spakeKeyManager; + } + + /* * Returns X.509 trust manager or null for none. */ @@ -596,6 +625,18 @@ private static PSKKeyManager findFirstPSKKeyManager(KeyManager[] kms) { return null; } + /* + * Returns the first SpakeKeyManager element in the provided array. + */ + private static SpakeKeyManager findFirstSpakeKeyManager(KeyManager[] kms) { + for (KeyManager km : kms) { + if (km instanceof SpakeKeyManager) { + return (SpakeKeyManager)km; + } + } + return null; + } + /* * Returns the default X.509 trust manager. */ @@ -642,6 +683,18 @@ private static X509TrustManager findFirstX509TrustManager(TrustManager[] tms) { return null; } + /* + * Returns the first SpakeTrustManager element in the provided array. + */ + private static SpakeTrustManager findFirstSpakeTrustManager(TrustManager[] tms) { + for (TrustManager tm : tms) { + if (tm instanceof SpakeTrustManager) { + return (SpakeTrustManager) tm; + } + } + return null; + } + String getEndpointIdentificationAlgorithm() { return endpointIdentificationAlgorithm; } @@ -679,7 +732,11 @@ void setUseCipherSuitesOrder(boolean useCipherSuitesOrder) { private static String[] getDefaultCipherSuites( boolean x509CipherSuitesNeeded, - boolean pskCipherSuitesNeeded) { + boolean pskCipherSuitesNeeded, + boolean spakeCipherSuitesNeeded) { + if (spakeCipherSuitesNeeded) { + return NativeCrypto.DEFAULT_SPAKE_CIPHER_SUITES; + } if (x509CipherSuitesNeeded) { // X.509 based cipher suites need to be listed. if (pskCipherSuitesNeeded) { @@ -724,4 +781,8 @@ boolean isCTVerificationEnabled(String hostname) { } return Platform.isCTVerificationRequired(hostname); } + + boolean isSpake() { + return spakeKeyManager != null; + } } diff --git a/common/src/main/java/org/conscrypt/SpakeKeyManager.java b/common/src/main/java/org/conscrypt/SpakeKeyManager.java new file mode 100644 index 000000000..308d458d2 --- /dev/null +++ b/common/src/main/java/org/conscrypt/SpakeKeyManager.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.KeyManager; +import java.security.Principal; + +/** + * @hide This class is not part of the Android public SDK API + */ +@Internal +public class SpakeKeyManager implements KeyManager { + byte[] password; + byte[] idProver; + byte[] idVerifier; + byte[] context; + + SpakeKeyManager(byte[] password, byte[] idProver, + byte[] idVerifier, byte[] context) { + this.password = password; + this.idProver = idProver; + this.idVerifier = idVerifier; + this.context = context; + } + + public String chooseEngineAlias(String keyType, + Principal[] issuers, SSLEngine engine) { + throw new UnsupportedOperationException("Not implemented"); + } + + public String chooseEngineClientAlias(String[] keyType, + Principal[] issuers, SSLEngine engine) { + throw new UnsupportedOperationException("Not implemented"); + } + + public byte[] getContext() { + return context; + } + + public byte[] getPassword() { + return password; + } + + public byte[] getIdProver() { + return idProver; + } + + public byte[] getIdVerifier() { + return idVerifier; + } +} \ No newline at end of file diff --git a/common/src/main/java/org/conscrypt/SpakeTrustManager.java b/common/src/main/java/org/conscrypt/SpakeTrustManager.java new file mode 100644 index 000000000..53f814683 --- /dev/null +++ b/common/src/main/java/org/conscrypt/SpakeTrustManager.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import javax.net.ssl.TrustManager; + +/** + * @hide This class is not part of the Android public SDK API + */ +@Internal +public class SpakeTrustManager implements TrustManager { + + SpakeTrustManager() {} + + public void checkClientTrusted() {} + + public void checkServerTrusted() {} +} \ No newline at end of file diff --git a/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java new file mode 100644 index 000000000..73ea27341 --- /dev/null +++ b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.conscrypt.metrics; + +import org.conscrypt.Internal; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.lang.Thread.UncaughtExceptionHandler; + +/** + * Reimplement with reflection calls the logging class, + * generated by frameworks/statsd. + *

+ * In case atom is changed, generate new wrapper with stats-log-api-gen + * tool as shown below and add corresponding methods to ReflexiveStatsEvent's + * newEvent() method. + *

+ * $ stats-log-api-gen \ + * --java "common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java" \ + * --module conscrypt \ + * --javaPackage org.conscrypt.metrics \ + * --javaClass ConscryptStatsLog + **/ +@Internal +public final class ConscryptStatsLog { + public static final int TLS_HANDSHAKE_REPORTED = 317; + + private ConscryptStatsLog() {} + + public static void write(int atomId, boolean success, int protocol, int cipherSuite, + int duration, Source source) { + ReflexiveStatsEvent event = ReflexiveStatsEvent.buildEvent( + atomId, success, protocol, cipherSuite, duration, source.ordinal()); + + ReflexiveStatsLog.write(event); + } + + public static void write( + int atomId, boolean success, int protocol, int cipherSuite, int duration, Source source, + int uids[]) { + ReflexiveStatsEvent event = ReflexiveStatsEvent.buildEvent( + atomId, success, protocol, cipherSuite, duration, source.ordinal(), uids); + + ReflexiveStatsLog.write(event); + } + + public static void write(int atomId, int status, int loadedCompatVersion, + int minCompatVersionAvailable, int majorVersion, int minorVersion) { + ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder(); + builder.setAtomId(atomId); + builder.writeInt(status); + builder.writeInt(loadedCompatVersion); + builder.writeInt(minCompatVersionAvailable); + builder.writeInt(majorVersion); + builder.writeInt(minorVersion); + builder.usePooledBuffer(); + ReflexiveStatsLog.write(builder.build()); + } +} diff --git a/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java b/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java index a47bac9d6..0cc6dd99c 100644 --- a/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java +++ b/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java @@ -85,7 +85,7 @@ public void countTlsHandshake( CipherSuite suite = CipherSuite.forName(cipherSuite); write(TLS_HANDSHAKE_REPORTED, success, proto.getId(), suite.getId(), (int) duration, - Platform.getStatsSource().ordinal(), Platform.getUids()); + Platform.getStatsSource(), Platform.getUids()); } private static int logStoreStateToMetricsState(LogStore.State state) { @@ -123,14 +123,12 @@ public void updateCTLogListStatusChanged(LogStore logStore) { } private void write(int atomId, boolean success, int protocol, int cipherSuite, int duration, - int source, int[] uids) { + org.conscrypt.metrics.Source source, int[] uids) { e.execute(new Runnable() { @Override public void run() { - ReflexiveStatsEvent event = ReflexiveStatsEvent.buildEvent( + ConscryptStatsLog.write( atomId, success, protocol, cipherSuite, duration, source, uids); - - ReflexiveStatsLog.write(event); } }); } @@ -140,15 +138,8 @@ private void write(int atomId, int status, int loadedCompatVersion, e.execute(new Runnable() { @Override public void run() { - ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder(); - builder.setAtomId(atomId); - builder.writeInt(status); - builder.writeInt(loadedCompatVersion); - builder.writeInt(minCompatVersionAvailable); - builder.writeInt(majorVersion); - builder.writeInt(minorVersion); - builder.usePooledBuffer(); - ReflexiveStatsLog.write(builder.build()); + ConscryptStatsLog.write(atomId, status, loadedCompatVersion, + minCompatVersionAvailable, majorVersion, minorVersion); } }); } diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java index c45766076..37f2eb254 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java @@ -485,10 +485,6 @@ public void checkServerTrusted(X509Certificate[] x509Certificates, String s, // By the point of the handshake where we're validating certificates, // the hostname is known and the cipher suite should be agreed assertEquals(referenceContext.host.getHostName(), session.getPeerHost()); - - // The negotiated cipher suite should be one of the enabled ones, but - // BoringSSL may have reordered them based on things like hardware support, - // so we don't know which one may have been negotiated. String sessionSuite = session.getCipherSuite(); List enabledSuites = Arrays.asList(referenceClientSocket.getEnabledCipherSuites()); @@ -1080,6 +1076,27 @@ public void writeFromHandshakeListener() throws Exception { assertArrayEquals(ping, buffer); } + @Test + public void testSpake() { + byte[] password = "password".getBytes(); + byte[] context = "osmosis_test".getBytes(); + Socket plainSocketC; + Socket plainSocketS; + InetAddress hostC = TestUtils.getLoopbackAddress(); + InetAddress hostS = TestUtils.getLoopbackAddress(); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SPAKE2+"); + assertThrows(NoSuchAlgorithmException.class, () -> tmf.init(null)); + + SpakeClientKeyManagerParameters kmfParamsClient = new SpakeClientKeyManagerParameters.Builder + .setClientPassword(password) + .setContext(context) + .build(); + + KeyManagerFactory kmfClient = KeyManagerFactory.getInstance("SPAKE2+"); + assertThrows(NoSuchAlgorithmException.class, () -> kmfClient.init(kmfParamsClient)); + } + private void socketClose(Socket socket) { try { socket.close(); diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java index a651d4788..42dced1b3 100644 --- a/openjdk/src/main/java/org/conscrypt/Platform.java +++ b/openjdk/src/main/java/org/conscrypt/Platform.java @@ -849,4 +849,8 @@ public static boolean isTlsV1Filtered() { public static boolean isTlsV1Supported() { return true; } + + public static boolean isSpake2Supported() { + return false; + } } diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java index 9691c3de9..8e751538c 100644 --- a/platform/src/main/java/org/conscrypt/Platform.java +++ b/platform/src/main/java/org/conscrypt/Platform.java @@ -566,6 +566,10 @@ public static boolean isTlsV1Supported() { return false; } + public static boolean isSpake2Supported() { + return true; + } + static Object getTargetSdkVersion() { try { Class vmRuntime = Class.forName("dalvik.system.VMRuntime"); diff --git a/platform/src/main/java/org/conscrypt/SpakeKeyManagerFactory.java b/platform/src/main/java/org/conscrypt/SpakeKeyManagerFactory.java new file mode 100644 index 000000000..56e39a290 --- /dev/null +++ b/platform/src/main/java/org/conscrypt/SpakeKeyManagerFactory.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import static java.util.Objects.requireNonNull; + +import android.net.ssl.SpakeKeyManagerParameters; + +import com.android.org.conscrypt.io.IoUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactorySpi; +import javax.net.ssl.ManagerFactoryParameters; + +/** + * SpakeKeyManagerFactory implementation. + * @see KeyManagerFactorySpi + * @hide This class is not part of the Android public SDK API + */ +@Internal +public class SpakeKeyManagerFactory extends KeyManagerFactorySpi { + SpakeKeyManagerParameters params; + + /** + * @see KeyManagerFactorySpi#engineInit(KeyStore ks, char[] password) + */ + @Override + protected void engineInit(KeyStore ks, char[] password) + throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { + throw new KeyStoreException("KeyStore not supported"); + } + + /** + * @see KeyManagerFactorySpi#engineInit(ManagerFactoryParameters spec) + */ + @Override + protected void engineInit(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException { + if (params != null) { + throw new IllegalStateException("SpakeKeyManagerFactory is already initialized"); + } + requireNonNull(spec); + if (spec instanceof SpakeKeyManagerParameters) { + params = (SpakeKeyManagerParameters) spec; + } else { + throw new InvalidAlgorithmParameterException("ManagerFactoryParameters not supported"); + } + } + + /** + * @see KeyManagerFactorySpi#engineGetKeyManagers() + */ + @Override + protected KeyManager[] engineGetKeyManagers() { + if (params != null) { + return new KeyManager[] { new SpakeKeyManager(params.getPassword(), + params.getIdProver(), params.getIdVerifier(), + params.getContext()) }; + } else { + throw new IllegalStateException("SpakeKeyManagerFactory is not initialized"); + } + } +} diff --git a/platform/src/main/java/org/conscrypt/SpakeTrustManagerFactory.java b/platform/src/main/java/org/conscrypt/SpakeTrustManagerFactory.java new file mode 100644 index 000000000..2e7d1e56c --- /dev/null +++ b/platform/src/main/java/org/conscrypt/SpakeTrustManagerFactory.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import static java.util.Objects.requireNonNull; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.security.cert.CertificateException; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.TrustManagerFactorySpi; + +/** + * A factory for creating {@link SpakeTrustManager} instances that use SPAKE2. + * @hide This class is not part of the Android public SDK API + */ +@Internal +public class SpakeTrustManagerFactory extends TrustManagerFactorySpi { + /** + * @see javax.net.ssl.TrustManagerFactorySpi#engineInit(KeyStore) + */ + @Override + public void engineInit(KeyStore ks) throws KeyStoreException { + if (ks != null) { + throw new KeyStoreException("KeyStore not supported"); + } + } + + /** + * @see javax.net.ssl#engineInit(ManagerFactoryParameters) + */ + @Override + public void engineInit(ManagerFactoryParameters spec) + throws InvalidAlgorithmParameterException { + if (spec != null) { + throw new InvalidAlgorithmParameterException("ManagerFactoryParameters not supported"); + } + } + + /** + * @see javax.net.ssl#engineGetTrustManagers() + */ + @Override + public TrustManager[] engineGetTrustManagers() { + return new TrustManager[] { new SpakeTrustManager() }; + } +}