Skip to content

Commit

Permalink
Introduce the ability to run key-signing in an fully async fashion. (#…
Browse files Browse the repository at this point in the history
…650)

* Introduce the ability to run key-signing in an fully async fashion.

Motivation:

Sometimes we may want to run tasks in a fully async fashion. For example if we need to do a network operation to full fill the task.

This commit only makes use of the real async support for key-signing but we could also do the same for other tasks.

Modifications:

- Add AsyncTask that allows to run a task in an async fashion and notify the callback once done.
- Add AsyncSSLPrivateKeyMethod that makes use of the AsyncTask and so allow for a fully async implementation for key signing.

Result:

More flexible

Co-authored-by: Trustin Lee <[email protected]>
  • Loading branch information
normanmaurer and trustin authored Jun 21, 2021
1 parent a1b0c7b commit 349eb1c
Show file tree
Hide file tree
Showing 13 changed files with 314 additions and 44 deletions.
43 changes: 33 additions & 10 deletions openssl-dynamic/src/main/c/sslcontext.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ static jmethodID sslPrivateKeyMethodSignTask_init;
static jclass sslPrivateKeyMethodDecryptTask_class;
static jmethodID sslPrivateKeyMethodDecryptTask_init;

static const char* staticPackagePrefix = NULL;

extern apr_pool_t *tcn_global_pool;

static apr_status_t ssl_context_cleanup(void *data)
Expand Down Expand Up @@ -2240,11 +2242,14 @@ const SSL_PRIVATE_KEY_METHOD private_key_method = {
#endif // OPENSSL_IS_BORINGSSL


TCN_IMPLEMENT_CALL(void, SSLContext, setPrivateKeyMethod)(TCN_STDARGS, jlong ctx, jobject method) {
TCN_IMPLEMENT_CALL(void, SSLContext, setPrivateKeyMethod0)(TCN_STDARGS, jlong ctx, jobject method) {
tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);

TCN_CHECK_NULL(c, ctx, /* void */);
#ifdef OPENSSL_IS_BORINGSSL
char* name = NULL;
char* combinedName = NULL;

jobject oldMethod = c->ssl_private_key_method;
if (method == NULL) {
c->ssl_private_key_method = NULL;
Expand All @@ -2259,13 +2264,21 @@ TCN_IMPLEMENT_CALL(void, SSLContext, setPrivateKeyMethod)(TCN_STDARGS, jlong ctx
return;
}

jmethodID signMethod = (*e)->GetMethodID(e, method_class, "sign", "(JI[B)[B");
NETTY_JNI_UTIL_PREPEND(staticPackagePrefix, "io/netty/internal/tcnative/ResultCallback;)V", name, error);
NETTY_JNI_UTIL_PREPEND("(JI[BL", name, combinedName, error);
TCN_REASSIGN(name, combinedName);

jmethodID signMethod = (*e)->GetMethodID(e, method_class, "sign", name);
if (signMethod == NULL) {
tcn_ThrowIllegalArgumentException(e, "Unable to retrieve sign method");
return;
}

jmethodID decryptMethod = (*e)->GetMethodID(e, method_class, "decrypt", "(J[B)[B");
NETTY_JNI_UTIL_PREPEND(staticPackagePrefix, "io/netty/internal/tcnative/ResultCallback;)V", name, error);
NETTY_JNI_UTIL_PREPEND("(J[BL", name, combinedName, error);
TCN_REASSIGN(name, combinedName);

jmethodID decryptMethod = (*e)->GetMethodID(e, method_class, "decrypt", name);
if (decryptMethod == NULL) {
tcn_ThrowIllegalArgumentException(e, "Unable to retrieve decrypt method");
return;
Expand All @@ -2284,7 +2297,11 @@ TCN_IMPLEMENT_CALL(void, SSLContext, setPrivateKeyMethod)(TCN_STDARGS, jlong ctx
}
if (oldMethod != NULL) {
(*e)->DeleteGlobalRef(e, oldMethod);
}
}

error:
free(name);
free(combinedName);
#else
tcn_ThrowException(e, "Requires BoringSSL");
#endif // OPENSSL_IS_BORINGSSL
Expand Down Expand Up @@ -2660,7 +2677,7 @@ static const JNINativeMethod fixed_method_table[] = {
// setCertRequestedCallback -> needs dynamic method table
// setCertificateCallback -> needs dynamic method table
// setSniHostnameMatcher -> needs dynamic method table
// setPrivateKeyMethod --> needs dynamic method table
// setPrivateKeyMethod0 --> needs dynamic method table
// setSSLSessionCache --> needs dynamic method table

{ TCN_METHOD_TABLE_ENTRY(setSessionIdContext, (J[B)Z, SSLContext) },
Expand Down Expand Up @@ -2718,11 +2735,11 @@ static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) {
dynamicMethod->fnPtr = (void *) TCN_FUNCTION_NAME(SSLContext, setSniHostnameMatcher);

dynamicMethod = &dynamicMethods[fixed_method_table_size + 4];
NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/SSLPrivateKeyMethod;)V", dynamicTypeName, error);
NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/AsyncSSLPrivateKeyMethod;)V", dynamicTypeName, error);
NETTY_JNI_UTIL_PREPEND("(JL", dynamicTypeName, dynamicMethod->signature, error);
netty_jni_util_free_dynamic_name(&dynamicTypeName);
dynamicMethod->name = "setPrivateKeyMethod";
dynamicMethod->fnPtr = (void *) TCN_FUNCTION_NAME(SSLContext, setPrivateKeyMethod);
dynamicMethod->name = "setPrivateKeyMethod0";
dynamicMethod->fnPtr = (void *) TCN_FUNCTION_NAME(SSLContext, setPrivateKeyMethod0);

dynamicMethod = &dynamicMethods[fixed_method_table_size + 5];
NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/SSLSessionCache;)V", dynamicTypeName, error);
Expand Down Expand Up @@ -2785,19 +2802,22 @@ jint netty_internal_tcnative_SSLContext_JNI_OnLoad(JNIEnv* env, const char* pack
NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/SSLPrivateKeyMethodSignTask", name, error);
NETTY_JNI_UTIL_LOAD_CLASS(env, sslPrivateKeyMethodSignTask_class, name, error);

NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/SSLPrivateKeyMethod;)V", name, error);
NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/AsyncSSLPrivateKeyMethod;)V", name, error);
NETTY_JNI_UTIL_PREPEND("(JI[BL", name, combinedName, error);
TCN_REASSIGN(name, combinedName);
NETTY_JNI_UTIL_GET_METHOD(env, sslPrivateKeyMethodSignTask_class, sslPrivateKeyMethodSignTask_init, "<init>", name, error);

NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/SSLPrivateKeyMethodDecryptTask", name, error);
NETTY_JNI_UTIL_LOAD_CLASS(env, sslPrivateKeyMethodDecryptTask_class, name, error);

NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/SSLPrivateKeyMethod;)V", name, error);
NETTY_JNI_UTIL_PREPEND(packagePrefix, "io/netty/internal/tcnative/AsyncSSLPrivateKeyMethod;)V", name, error);
NETTY_JNI_UTIL_PREPEND("(J[BL", name, combinedName, error);
TCN_REASSIGN(name, combinedName);
NETTY_JNI_UTIL_GET_METHOD(env, sslPrivateKeyMethodDecryptTask_class, sslPrivateKeyMethodDecryptTask_init, "<init>", name, error);

if (packagePrefix != NULL) {
staticPackagePrefix = strdup(packagePrefix);
}
return NETTY_JNI_UTIL_JNI_VERSION;
error:
free(name);
Expand All @@ -2815,5 +2835,8 @@ void netty_internal_tcnative_SSLContext_JNI_OnUnLoad(JNIEnv* env, const char* pa
NETTY_JNI_UTIL_UNLOAD_CLASS(env, sslPrivateKeyMethodSignTask_class);
NETTY_JNI_UTIL_UNLOAD_CLASS(env, sslPrivateKeyMethodDecryptTask_class);

free((void*) staticPackagePrefix);
staticPackagePrefix = NULL;

netty_jni_util_unregister_natives(env, packagePrefix, SSLCONTEXT_CLASSNAME);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.internal.tcnative;

/**
* Allows to customize private key signing / decrypt (when using RSA).
*/
public interface AsyncSSLPrivateKeyMethod {
int SSL_SIGN_RSA_PKCS1_SHA1 = NativeStaticallyReferencedJniMethods.sslSignRsaPkcsSha1();
int SSL_SIGN_RSA_PKCS1_SHA256 = NativeStaticallyReferencedJniMethods.sslSignRsaPkcsSha256();
int SSL_SIGN_RSA_PKCS1_SHA384 = NativeStaticallyReferencedJniMethods.sslSignRsaPkcsSha384();
int SSL_SIGN_RSA_PKCS1_SHA512 = NativeStaticallyReferencedJniMethods.sslSignRsaPkcsSha512();
int SSL_SIGN_ECDSA_SHA1 = NativeStaticallyReferencedJniMethods.sslSignEcdsaPkcsSha1();
int SSL_SIGN_ECDSA_SECP256R1_SHA256 = NativeStaticallyReferencedJniMethods.sslSignEcdsaSecp256r1Sha256();
int SSL_SIGN_ECDSA_SECP384R1_SHA384 = NativeStaticallyReferencedJniMethods.sslSignEcdsaSecp384r1Sha384();
int SSL_SIGN_ECDSA_SECP521R1_SHA512 = NativeStaticallyReferencedJniMethods.sslSignEcdsaSecp521r1Sha512();
int SSL_SIGN_RSA_PSS_RSAE_SHA256 = NativeStaticallyReferencedJniMethods.sslSignRsaPssRsaeSha256();
int SSL_SIGN_RSA_PSS_RSAE_SHA384 = NativeStaticallyReferencedJniMethods.sslSignRsaPssRsaeSha384();
int SSL_SIGN_RSA_PSS_RSAE_SHA512 = NativeStaticallyReferencedJniMethods.sslSignRsaPssRsaeSha512();
int SSL_SIGN_ED25519 = NativeStaticallyReferencedJniMethods.sslSignEd25519();
int SSL_SIGN_RSA_PKCS1_MD5_SHA1 = NativeStaticallyReferencedJniMethods.sslSignRsaPkcs1Md5Sha1();

/**
* Sign the input with given EC key and notify {@link ResultCallback} with the signed bytes.
*
* @param ssl the SSL instance
* @param signatureAlgorithm the algorithm to use for signing
* @param input the input itself
* @param resultCallback the callback that will be notified once the operation completes
*/
void sign(long ssl, int signatureAlgorithm, byte[] input, ResultCallback<byte[]> resultCallback);

/**
* Decrypts the input with the given RSA key and notify {@link ResultCallback} with the decrypted bytes.
*
* @param ssl the SSL instance
* @param input the input which should be decrypted
* @param resultCallback the callback that will be notified once the operation completes
*/
void decrypt(long ssl, byte[] input, ResultCallback<byte[]> resultCallback);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.internal.tcnative;

final class AsyncSSLPrivateKeyMethodAdapter implements AsyncSSLPrivateKeyMethod {
private final SSLPrivateKeyMethod method;

AsyncSSLPrivateKeyMethodAdapter(SSLPrivateKeyMethod method) {
if (method == null) {
throw new NullPointerException("method");
}
this.method = method;
}

@Override
public void sign(long ssl, int signatureAlgorithm, byte[] input, ResultCallback<byte[]> resultCallback) {
final byte[] result;
try {
result = method.sign(ssl, signatureAlgorithm, input);
} catch (Throwable cause) {
resultCallback.onError(ssl, cause);
return;
}
resultCallback.onSuccess(ssl, result);
}

@Override
public void decrypt(long ssl, byte[] input, ResultCallback<byte[]> resultCallback) {
final byte[] result;
try {
result = method.decrypt(ssl, input);
} catch (Throwable cause) {
resultCallback.onError(ssl, cause);
return;
}
resultCallback.onSuccess(ssl, result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.internal.tcnative;

public interface AsyncTask extends Runnable {

/**
* Run this {@link AsyncTask} in an async fashion. Which means it will be run and completed at some point.
* Once it is done the {@link Runnable} is called
*
* @param completeCallback The {@link Runnable} that is run once the task was run and completed.
*/
void runAsync(Runnable completeCallback);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ final class CertificateCallbackTask extends SSLTask {

// See https://www.openssl.org/docs/man1.0.2/man3/SSL_set_cert_cb.html.
@Override
protected int runTask(long ssl) {
protected void runTask(long ssl, TaskCallback taskCallback) {
try {
callback.handle(ssl, keyTypeBytes, asn1DerEncodedPrincipals);
return 1;
taskCallback.onResult(ssl, 1);
} catch (Exception e) {
// Just catch the exception and return 0 to fail the handshake.
// The problem is that rethrowing here is really "useless" as we will process it as part of an openssl
// c callback which needs to return 0 for an error to abort the handshake.
return 0;
taskCallback.onResult(ssl, 0);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ final class CertificateVerifierTask extends SSLTask {
}

@Override
protected int runTask(long ssl) {
return verifier.verify(ssl, x509, authAlgorithm);
protected void runTask(long ssl, TaskCallback callback) {
int result = verifier.verify(ssl, x509, authAlgorithm);
callback.onResult(ssl, result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2021 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.internal.tcnative;

/**
* Callback that is called once an operation completed.
*
* @param <T> The result type.
*/
public interface ResultCallback<T> {
/**
* Called when the operation completes with the given result.
*
* @param ssl the SSL instance (SSL *)
* @param result the result.
*/
void onSuccess(long ssl, T result);

/**
* Called when the operation completes with an error.
*
* @param ssl the SSL instance (SSL *)
* @param cause the error.
*/
void onError(long ssl, Throwable cause);
}
23 changes: 21 additions & 2 deletions openssl-dynamic/src/main/java/io/netty/internal/tcnative/SSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -835,14 +835,33 @@ public static void setKeyMaterialServerSide(long ssl, long chain, long key) thro
public static native byte[] getClientRandom(long ssl);

/**
* Return the {@link Runnable} thats needs to be run as an operation returned {@link #SSL_ERROR_WANT_X509_LOOKUP}.
* After the task was run we should retry the operations that returned {@link #SSL_ERROR_WANT_X509_LOOKUP}.
* Return the {@link Runnable} that needs to be run as an operation did signal that a task needs to be completed
* before we can retry the previous action.
*
* After the task was run we should retry the operation that did signal back that a task needed to be run.
*
*
* The {@link Runnable} may also implement {@link AsyncTask} which allows for fully asynchronous execution if
* {@link AsyncTask#runAsync(Runnable)} is used.
*
* @param ssl the SSL instance (SSL *)
* @return the task to run.
*/
public static native Runnable getTask(long ssl);

/**
* Return the {@link AsyncTask} that needs to be run as an operation did signal that a task needs to be completed
* before we can retry it.
*
* After the task was run we should retry the operation that did signal back that a task needed to be run.
*
* @param ssl the SSL instance (SSL *)
* @return the task to run.
*/
public static AsyncTask getAsyncTask(long ssl) {
return (AsyncTask) getTask(ssl);
}

/**
* Return {@code true} if the SSL_SESSION was reused.
* See <a href="https://www.openssl.org/docs/man1.1.0/man3/SSL_session_reused.html">SSL_session_reused</a>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,15 +649,33 @@ public static void setAlpnProtos(long ctx, String[] alpnProtos, int selectorFail
public static native void setUseTasks(long ctx, boolean useTasks);

/**
* Set the {@link SSLPrivateKeyMethod} to use for the given {@link SSLContext}. This allows to offload privatekey operations
* Set the {@link SSLPrivateKeyMethod} to use for the given {@link SSLContext}.
* This allows to offload private key operations
* if needed.
*
* This method is currently only supported when {@code BoringSSL} is used.
*
* @param ctx context to use
* @param method method to use for the given context.
*/
public static native void setPrivateKeyMethod(long ctx, SSLPrivateKeyMethod method);
public static void setPrivateKeyMethod(long ctx, final SSLPrivateKeyMethod method) {
setPrivateKeyMethod(ctx, new AsyncSSLPrivateKeyMethodAdapter(method));
}

/**
* Sets the {@link AsyncSSLPrivateKeyMethod} to use for the given {@link SSLContext}.
* This allows to offload private key operations if needed.
*
* This method is currently only supported when {@code BoringSSL} is used.
*
* @param ctx context to use
* @param method method to use for the given context.
*/
public static void setPrivateKeyMethod(long ctx, AsyncSSLPrivateKeyMethod method) {
setPrivateKeyMethod0(ctx, method);
}

private static native void setPrivateKeyMethod0(long ctx, AsyncSSLPrivateKeyMethod method);

/**
* Set the {@link SSLSessionCache} that will be used if session caching is enabled.
Expand Down
Loading

0 comments on commit 349eb1c

Please sign in to comment.