Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PakeOption 'client-handshake-limit' and 'server-handshake-l… #1316

Merged
merged 2 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions common/src/jni/main/cpp/conscrypt/native_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11324,20 +11324,18 @@ static jbyteArray NativeCrypto_Scrypt_generate_key(JNIEnv* env, jclass, jbyteArr
#define SPAKE2PLUS_PW_VERIFIER_SIZE 32
#define SPAKE2PLUS_REGISTRATION_RECORD_SIZE 65

static void NativeCrypto_SSL_CTX_set_spake_credential(JNIEnv* env, jclass, jbyteArray context,
jbyteArray pw_array,
jbyteArray id_prover_array,
jbyteArray id_verifier_array,
jboolean is_client, jlong ssl_ctx_address,
CONSCRYPT_UNUSED jobject holder) {
static void NativeCrypto_SSL_CTX_set_spake_credential(
JNIEnv* env, jclass, jbyteArray context, jbyteArray pw_array, jbyteArray id_prover_array,
jbyteArray id_verifier_array, jboolean is_client, jint handshake_limit,
jlong ssl_ctx_address, CONSCRYPT_UNUSED jobject holder) {
CHECK_ERROR_QUEUE_ON_RETURN;
JNI_TRACE("SSL_CTX_set_spake_credential(%p, %p, %p, %p, %d, %ld)", context, pw_array,
id_prover_array, id_verifier_array, is_client, ssl_ctx_address);
JNI_TRACE("SSL_CTX_set_spake_credential(%p, %p, %p, %p, %d, %d, %ld)", context, pw_array,
id_prover_array, id_verifier_array, is_client, handshake_limit, ssl_ctx_address);

SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);

JNI_TRACE("SSL_CTX_set_spake_credential(%p, %p, %p, %p, %d, %p)", context, pw_array,
id_prover_array, id_verifier_array, is_client, ssl_ctx);
JNI_TRACE("SSL_CTX_set_spake_credential(%p, %p, %p, %p, %d, %d, %p)", context, pw_array,
id_prover_array, id_verifier_array, is_client, handshake_limit, ssl_ctx);

if (context == nullptr || pw_array == nullptr || id_prover_array == nullptr ||
id_verifier_array == nullptr) {
Expand Down Expand Up @@ -11402,7 +11400,7 @@ static void NativeCrypto_SSL_CTX_set_spake_credential(JNIEnv* env, jclass, jbyte
/* client_identity_len= */ id_prover_bytes.size(),
/* server_identity= */ reinterpret_cast<const uint8_t*>(id_verifier_bytes.get()),
/* server_identity_len= */ id_verifier_bytes.size(),
/* attempts= */ 1,
/* attempts= */ handshake_limit,
/* w0= */ pw_verifier_w0,
/* w0_len= */ sizeof(pw_verifier_w0),
/* w1= */ pw_verifier_w1,
Expand All @@ -11416,7 +11414,7 @@ static void NativeCrypto_SSL_CTX_set_spake_credential(JNIEnv* env, jclass, jbyte
/* client_identity_len= */ id_prover_bytes.size(),
/* server_identity= */ reinterpret_cast<const uint8_t*>(id_verifier_bytes.get()),
/* server_identity_len= */ id_verifier_bytes.size(),
/* attempts= */ 1,
/* attempts= */ handshake_limit,
/* w0= */ pw_verifier_w0,
/* w0_len= */ sizeof(pw_verifier_w0),
/* registration_record= */ registration_record,
Expand All @@ -11432,8 +11430,8 @@ static void NativeCrypto_SSL_CTX_set_spake_credential(JNIEnv* env, jclass, jbyte
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SSL_CTX_add1_credential failed");
return;
}
JNI_TRACE("SSL_CTX_set_spake_credential (%p, %p, %p, %p, %d, %p) => %p", context, pw_array,
id_prover_array, id_verifier_array, is_client, ssl_ctx, creds.get());
JNI_TRACE("SSL_CTX_set_spake_credential (%p, %p, %p, %p, %d, %d, %p) => %p", context, pw_array,
id_prover_array, id_verifier_array, is_client, handshake_limit, ssl_ctx, creds.get());
return;
}

Expand Down Expand Up @@ -11926,7 +11924,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, "([B[B[B[BZJ" REF_SSL_CTX ")V"),
CONSCRYPT_NATIVE_METHOD(SSL_CTX_set_spake_credential, "([B[B[B[BZIJ" REF_SSL_CTX ")V"),

// Used for testing only.
CONSCRYPT_NATIVE_METHOD(BIO_read, "(J[B)I"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,12 @@ void initSpake(SSLParametersImpl parameters) throws SSLException {
byte[] idVerifierArray = spakeKeyManager.getIdVerifier();
byte[] pwArray = spakeKeyManager.getPassword();
boolean isClient = spakeKeyManager.isClient();
int handshakeLimit = spakeKeyManager.getHandshakeLimit();
lock.writeLock().lock();
try {
if (isValid()) {
NativeCrypto.SSL_CTX_set_spake_credential(context, pwArray, idProverArray,
idVerifierArray, isClient, sslCtxNativePointer, this);
idVerifierArray, isClient, handshakeLimit, sslCtxNativePointer, this);
}
} finally {
lock.writeLock().unlock();
Expand Down
4 changes: 2 additions & 2 deletions common/src/main/java/org/conscrypt/NativeCrypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,8 @@ static native long X509_CRL_get_nextUpdate(long x509CrlCtx, OpenSSLX509CRL holde
* Used for both client and server.
*/
static native void SSL_CTX_set_spake_credential(byte[] context, byte[] pw_array,
byte[] id_prover_array, byte[] id_verifier_array, boolean is_client, long ssl_ctx,
AbstractSessionContext holder) throws SSLException;
byte[] id_prover_array, byte[] id_verifier_array, boolean is_client,
int handshake_limit, long ssl_ctx, AbstractSessionContext holder) throws SSLException;

// --- ASN1_TIME -----------------------------------------------------------

Expand Down
10 changes: 8 additions & 2 deletions common/src/main/java/org/conscrypt/Spake2PlusKeyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ public class Spake2PlusKeyManager implements KeyManager {
private final byte[] idProver;
private final byte[] idVerifier;
private final boolean isClient;
private final int handshakeLimit;

Spake2PlusKeyManager(
byte[] context, byte[] password, byte[] idProver, byte[] idVerifier, boolean isClient) {
Spake2PlusKeyManager(byte[] context, byte[] password, byte[] idProver, byte[] idVerifier,
boolean isClient, int handshakeLimit) {
this.context = context == null ? new byte[0] : context;
this.password = password;
this.idProver = idProver == null ? new byte[0] : idProver;
this.idVerifier = idVerifier == null ? new byte[0] : idVerifier;
this.isClient = isClient;
this.handshakeLimit = handshakeLimit;
}

public String chooseEngineAlias(String keyType, Principal[] issuers, SSLEngine engine) {
Expand Down Expand Up @@ -69,4 +71,8 @@ public byte[] getIdVerifier() {
public boolean isClient() {
return isClient;
}

public int getHandshakeLimit() {
return handshakeLimit;
}
}
8 changes: 4 additions & 4 deletions common/src/test/java/org/conscrypt/NativeCryptoArgTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,13 @@ public void x509Methods() throws Throwable {
@Test
public void spake2Methods() throws Throwable {
expectNPE("SSL_CTX_set_spake_credential", null, new byte[0], new byte[0], new byte[0],
false, NOT_NULL, null);
false, 1, NOT_NULL, null);
expectNPE("SSL_CTX_set_spake_credential", new byte[0], null, new byte[0], new byte[0],
false, NOT_NULL, null);
false, 1, NOT_NULL, null);
expectNPE("SSL_CTX_set_spake_credential", new byte[0], new byte[0], null, new byte[0],
false, NOT_NULL, null);
false, 1, NOT_NULL, null);
expectNPE("SSL_CTX_set_spake_credential", new byte[0], new byte[0], new byte[0], null,
false, NOT_NULL, null);
false, 1, NOT_NULL, null);
}

private void testMethods(MethodFilter filter, Class<? extends Throwable> exceptionClass)
Expand Down
22 changes: 19 additions & 3 deletions platform/src/main/java/org/conscrypt/PakeKeyManagerFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
public class PakeKeyManagerFactory extends KeyManagerFactorySpi {
PakeClientKeyManagerParameters clientParams;
PakeServerKeyManagerParameters serverParams;
private static final int MAX_HANDSHAKE_LIMIT = 24;

/**
* @see KeyManagerFactorySpi#engineInit(KeyStore ks, char[] password)
Expand Down Expand Up @@ -96,6 +97,19 @@ public KeyManager[] engineGetKeyManagers() {
}
}

private static int getHandshakeLimit(PakeOption option, String limitName) {
byte[] limit = option.getMessageComponent(limitName);
if (limit == null) {
return 1;
}
int handshakeLimit = limit[0];
// This should never happen, but just in case, we set the limit to 1.
if (handshakeLimit < 1 || handshakeLimit > MAX_HANDSHAKE_LIMIT) {
return 1;
}
return handshakeLimit;
}

private KeyManager[] initClient() {
List<PakeOption> options = clientParams.getOptions();
for (PakeOption option : options) {
Expand All @@ -106,9 +120,10 @@ private KeyManager[] initClient() {
byte[] idVerifier = clientParams.getServerId();
byte[] context = option.getMessageComponent("context");
byte[] password = option.getMessageComponent("password");
int clientHandshakeLimit = getHandshakeLimit(option, "client-handshake-limit");
if (password != null) {
return new KeyManager[] {
new Spake2PlusKeyManager(context, password, idProver, idVerifier, true)};
return new KeyManager[] {new Spake2PlusKeyManager(
context, password, idProver, idVerifier, true, clientHandshakeLimit)};
}
break;
}
Expand All @@ -127,9 +142,10 @@ private KeyManager[] initServer() {
byte[] idVerifier = link.getServerId();
byte[] context = option.getMessageComponent("context");
byte[] password = option.getMessageComponent("password");
int serverHandshakeLimit = getHandshakeLimit(option, "server-handshake-limit");
if (password != null) {
return new KeyManager[] {new Spake2PlusKeyManager(
context, password, idProver, idVerifier, false)};
context, password, idProver, idVerifier, false, serverHandshakeLimit)};
}
break;
}
Expand Down
112 changes: 96 additions & 16 deletions platform/src/test/java/org/conscrypt/PakeKeyManagerFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,43 +22,123 @@

import android.net.ssl.PakeClientKeyManagerParameters;
import android.net.ssl.PakeOption;
import android.net.ssl.PakeServerKeyManagerParameters;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.security.KeyStoreException;
import java.util.Arrays;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;

@RunWith(JUnit4.class)
public class PakeKeyManagerFactoryTest {
private static final byte[] PASSWORD = new byte[] {1, 2, 3};
private static final byte[] CLIENT_ID = new byte[] {2, 3, 4};
private static final byte[] SERVER_ID = new byte[] {4, 5, 6};

@Test
public void pakeKeyManagerFactoryTest() throws Exception {
KeyManagerFactory kmf = KeyManagerFactory.getInstance("PAKE");
assertThrows(KeyStoreException.class, () -> kmf.init(null, null));
byte[] password = new byte[] {1, 2, 3};
byte[] clientId = new byte[] {2, 3, 4};
byte[] serverId = new byte[] {4, 5, 6};
PakeOption option =
new PakeOption.Builder("SPAKE2PLUS_PRERELEASE")
.addMessageComponent("password", password)
.build();
PakeOption option = new PakeOption.Builder("SPAKE2PLUS_PRERELEASE")
.addMessageComponent("password", PASSWORD)
.build();

PakeClientKeyManagerParameters params =
new PakeClientKeyManagerParameters.Builder()
.setClientId(clientId.clone())
.setServerId(serverId.clone())
.addOption(option)
.build();
PakeClientKeyManagerParameters params = new PakeClientKeyManagerParameters.Builder()
.setClientId(CLIENT_ID.clone())
.setServerId(SERVER_ID.clone())
.addOption(option)
.build();
kmf.init(params);

KeyManager[] keyManagers = kmf.getKeyManagers();
assertEquals(1, keyManagers.length);

Spake2PlusKeyManager keyManager = (Spake2PlusKeyManager) keyManagers[0];
assertArrayEquals(password, keyManager.getPassword());
assertArrayEquals(clientId, keyManager.getIdProver());
assertArrayEquals(serverId, keyManager.getIdVerifier());
assertArrayEquals(PASSWORD, keyManager.getPassword());
assertArrayEquals(CLIENT_ID, keyManager.getIdProver());
assertArrayEquals(SERVER_ID, keyManager.getIdVerifier());
}

@Test
public void pakeKeyManagerFactoryTestHanshakeLimitClient() throws Exception {
PakeOption option = new PakeOption.Builder("SPAKE2PLUS_PRERELEASE")
.addMessageComponent("password", PASSWORD)
.addMessageComponent("client-handshake-limit", new byte[] {16})
.build();

// Client
PakeClientKeyManagerParameters paramsClient = new PakeClientKeyManagerParameters.Builder()
.setClientId(CLIENT_ID.clone())
.setServerId(SERVER_ID.clone())
.addOption(option)
.build();
KeyManagerFactory kmfClient = KeyManagerFactory.getInstance("PAKE");
kmfClient.init(paramsClient);

Spake2PlusKeyManager keyManagerClient =
(Spake2PlusKeyManager) kmfClient.getKeyManagers()[0];
assertArrayEquals(PASSWORD, keyManagerClient.getPassword());
assertArrayEquals(CLIENT_ID, keyManagerClient.getIdProver());
assertArrayEquals(SERVER_ID, keyManagerClient.getIdVerifier());
assertEquals(16, keyManagerClient.getHandshakeLimit());

// Server
PakeServerKeyManagerParameters paramsServer =
new PakeServerKeyManagerParameters.Builder()
.setOptions(CLIENT_ID.clone(), SERVER_ID.clone(), Arrays.asList(option))
.build();
KeyManagerFactory kmfServer = KeyManagerFactory.getInstance("PAKE");
kmfServer.init(paramsServer);

Spake2PlusKeyManager keyManagerServer =
(Spake2PlusKeyManager) kmfServer.getKeyManagers()[0];
assertArrayEquals(PASSWORD, keyManagerServer.getPassword());
assertArrayEquals(CLIENT_ID, keyManagerServer.getIdProver());
assertArrayEquals(SERVER_ID, keyManagerServer.getIdVerifier());
assertEquals(1, keyManagerServer.getHandshakeLimit());
}

@Test
public void pakeKeyManagerFactoryTestHanshakeLimitServer() throws Exception {
PakeOption option = new PakeOption.Builder("SPAKE2PLUS_PRERELEASE")
.addMessageComponent("password", PASSWORD)
.addMessageComponent("server-handshake-limit", new byte[] {16})
.build();

// Client
PakeClientKeyManagerParameters paramsClient = new PakeClientKeyManagerParameters.Builder()
.setClientId(CLIENT_ID.clone())
.setServerId(SERVER_ID.clone())
.addOption(option)
.build();
KeyManagerFactory kmfClient = KeyManagerFactory.getInstance("PAKE");
kmfClient.init(paramsClient);

Spake2PlusKeyManager keyManagerClient =
(Spake2PlusKeyManager) kmfClient.getKeyManagers()[0];
assertArrayEquals(PASSWORD, keyManagerClient.getPassword());
assertArrayEquals(CLIENT_ID, keyManagerClient.getIdProver());
assertArrayEquals(SERVER_ID, keyManagerClient.getIdVerifier());
assertEquals(1, keyManagerClient.getHandshakeLimit());

// Server
PakeServerKeyManagerParameters paramsServer =
new PakeServerKeyManagerParameters.Builder()
.setOptions(CLIENT_ID.clone(), SERVER_ID.clone(), Arrays.asList(option))
.build();
KeyManagerFactory kmfServer = KeyManagerFactory.getInstance("PAKE");
kmfServer.init(paramsServer);

Spake2PlusKeyManager keyManagerServer =
(Spake2PlusKeyManager) kmfServer.getKeyManagers()[0];
assertArrayEquals(PASSWORD, keyManagerServer.getPassword());
assertArrayEquals(CLIENT_ID, keyManagerServer.getIdProver());
assertArrayEquals(SERVER_ID, keyManagerServer.getIdVerifier());
assertEquals(16, keyManagerServer.getHandshakeLimit());
}
}
Loading