Skip to content
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
180 changes: 177 additions & 3 deletions common/src/jni/main/cpp/conscrypt/native_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2565,6 +2565,42 @@ static void NativeCrypto_X25519_keypair(JNIEnv* env, jclass, jbyteArray outPubli
JNI_TRACE("X25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray);
}

static void NativeCrypto_ED25519_keypair(JNIEnv* env, jclass, jbyteArray outPublicArray,
jbyteArray outPrivateArray) {
CHECK_ERROR_QUEUE_ON_RETURN;
JNI_TRACE("ED25519_keypair(%p, %p)", outPublicArray, outPrivateArray);

ScopedByteArrayRW outPublic(env, outPublicArray);
if (outPublic.get() == nullptr) {
JNI_TRACE("ED25519_keypair(%p, %p) can't get output public key buffer", outPublicArray,
outPrivateArray);
return;
}

ScopedByteArrayRW outPrivate(env, outPrivateArray);
if (outPrivate.get() == nullptr) {
JNI_TRACE("ED25519_keypair(%p, %p) can't get output private key buffer", outPublicArray,
outPrivateArray);
return;
}

if (outPublic.size() != ED25519_PUBLIC_KEY_LEN) {
conscrypt::jniutil::throwIllegalArgumentException(env,
"Output public key array length != 32");
return;
}

if (outPrivate.size() != ED25519_PRIVATE_KEY_LEN) {
conscrypt::jniutil::throwIllegalArgumentException(env,
"Output private key array length != 64");
return;
}

ED25519_keypair(reinterpret_cast<uint8_t*>(outPublic.get()),
reinterpret_cast<uint8_t*>(outPrivate.get()));
JNI_TRACE("ED25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray);
}

static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) {
CHECK_ERROR_QUEUE_ON_RETURN;
JNI_TRACE_MD("EVP_MD_CTX_create()");
Expand Down Expand Up @@ -2763,7 +2799,9 @@ static jlong evpDigestSignVerifyInit(JNIEnv* env,
}
JNI_TRACE("%s(%p, %p, %p) <- ptr", jniName, mdCtx, md, pkey);

if (md == nullptr) {
// For ED25519, md must be null, see
// https://github.com/google/boringssl/blob/master/include/openssl/evp.h
if (md == nullptr && (EVP_PKEY_id(pkey) != EVP_PKEY_ED25519)) {
JNI_TRACE("ctx=%p %s => md == null", mdCtx, jniName);
Comment on lines +2804 to 2805
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n00b question: Why is this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For all other key types, md must not be null. But for Ed25519, it must always be null, see:

https://docs.openssl.org/3.0/man7/EVP_SIGNATURE-ED25519/#ed25519-and-ed448-signature-parameters
or
https://github.com/google/boringssl/blob/master/include/openssl/evp.h#L239

Maybe I should make this check stronger, and reject a non-null md for ed25519.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. Maybe just add a comment so the next person along understands :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also raises the question of what we do in SignatureSpi... Have update() cache the data and do a one-shot operation on sign() or verify(), I guess?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think in SignatureSpi, we have to keep a copy somewhere. My plan is to do it in a similar way as in OpenSSLSignatureRawECDSA.java.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

conscrypt::jniutil::throwNullPointerException(env, "md == null");
return 0;
Expand Down Expand Up @@ -3037,6 +3075,137 @@ static jboolean NativeCrypto_EVP_DigestVerifyFinal(JNIEnv* env, jclass, jobject
return result;
}

static jbyteArray NativeCrypto_EVP_DigestSign(JNIEnv* env, jclass, jobject evpMdCtxRef,
jbyteArray inJavaBytes, jint inOffset,
jint inLength) {
CHECK_ERROR_QUEUE_ON_RETURN;

EVP_MD_CTX* mdCtx = fromContextObject<EVP_MD_CTX>(env, evpMdCtxRef);
JNI_TRACE_MD("%s(%p, %p, %d, %d)", "EVP_DigestSign", mdCtx, inJavaBytes, inOffset, inLength);

if (mdCtx == nullptr) {
return nullptr;
}

if (inJavaBytes == nullptr) {
conscrypt::jniutil::throwNullPointerException(env, "inBytes");
return nullptr;
}

size_t array_size = static_cast<size_t>(env->GetArrayLength(inJavaBytes));
if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) {
conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not this CL but we should add a method for this as it gets thrown in a many places so we might as well cache the class.

"inBytes");
return nullptr;
}

jint in_offset = inOffset;
jint in_size = inLength;

jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr);
if (array_elements == nullptr) {
conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes");
return nullptr;
}
const unsigned char* buf = reinterpret_cast<const unsigned char*>(array_elements);
const unsigned char* inStart = buf + in_offset;
size_t inLen = static_cast<size_t>(in_size);

size_t maxLen;
if (EVP_DigestSign(mdCtx, nullptr, &maxLen, inStart, inLen) != 1) {
JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx);
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign");
return nullptr;
}

std::unique_ptr<unsigned char[]> buffer(new unsigned char[maxLen]);
if (buffer.get() == nullptr) {
conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate signature buffer");
return nullptr;
}
size_t actualLen(maxLen);
if (EVP_DigestSign(mdCtx, buffer.get(), &actualLen, inStart, inLen) != 1) {
JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx);
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign");
return nullptr;
}
if (actualLen > maxLen) {
JNI_TRACE("ctx=%p EVP_DigestSign => signature too long: %zd vs %zd", mdCtx, actualLen,
maxLen);
conscrypt::jniutil::throwRuntimeException(env, "EVP_DigestSign signature too long");
return nullptr;
}

ScopedLocalRef<jbyteArray> sigJavaBytes(env, env->NewByteArray(static_cast<jint>(actualLen)));
if (sigJavaBytes.get() == nullptr) {
conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate signature byte[]");
return nullptr;
}
env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast<jint>(actualLen),
reinterpret_cast<jbyte*>(buffer.get()));

JNI_TRACE("EVP_DigestSign(%p) => %p", mdCtx, sigJavaBytes.get());
return sigJavaBytes.release();
}

static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMdCtxRef,
jbyteArray signature, jint sigOffset, jint sigLen,
jbyteArray data, jint dataOffset, jint dataLen) {
CHECK_ERROR_QUEUE_ON_RETURN;
EVP_MD_CTX* mdCtx = fromContextObject<EVP_MD_CTX>(env, evpMdCtxRef);
JNI_TRACE("EVP_DigestVerify(%p)", mdCtx);

if (mdCtx == nullptr) {
return 0;
}

ScopedByteArrayRO sigBytes(env, signature);
if (sigBytes.get() == nullptr) {
return 0;
}

if (ARRAY_OFFSET_LENGTH_INVALID(sigBytes, sigOffset, sigLen)) {
conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException",
"signature");
return 0;
}

ScopedByteArrayRO dataBytes(env, data);
if (dataBytes.get() == nullptr) {
return 0;
}

if (ARRAY_OFFSET_LENGTH_INVALID(dataBytes, dataOffset, dataLen)) {
conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "data");
return 0;
}

const unsigned char* sigBuf = reinterpret_cast<const unsigned char*>(sigBytes.get());
const unsigned char* dataBuf = reinterpret_cast<const unsigned char*>(dataBytes.get());
int err = EVP_DigestVerify(mdCtx, sigBuf + sigOffset, static_cast<size_t>(sigLen),
dataBuf + dataOffset, static_cast<size_t>(dataLen));
jboolean result;
if (err == 1) {
// Signature verified
result = 1;
} else if (err == 0) {
// Signature did not verify
result = 0;
} else {
// Error while verifying signature
JNI_TRACE("ctx=%p EVP_DigestVerify => threw exception", mdCtx);
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestVerify");
return 0;
}

// If the signature did not verify, BoringSSL error queue contains an error (BAD_SIGNATURE).
// Clear the error queue to prevent its state from affecting future operations.
ERR_clear_error();

JNI_TRACE("EVP_DigestVerify(%p) => %d", mdCtx, result);
return result;
}

static jint evpPkeyEncryptDecrypt(JNIEnv* env,
int (*encrypt_decrypt_func)(EVP_PKEY_CTX*, uint8_t*, size_t*,
const uint8_t*, size_t),
Expand Down Expand Up @@ -11122,6 +11291,7 @@ static JNINativeMethod sNativeCryptoMethods[] = {
CONSCRYPT_NATIVE_METHOD(ECDSA_verify, "([B[B" REF_EVP_PKEY ")I"),
CONSCRYPT_NATIVE_METHOD(X25519, "([B[B[B)Z"),
CONSCRYPT_NATIVE_METHOD(X25519_keypair, "([B[B)V"),
CONSCRYPT_NATIVE_METHOD(ED25519_keypair, "([B[B)V"),
CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_create, "()J"),
CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_cleanup, "(" REF_EVP_MD_CTX ")V"),
CONSCRYPT_NATIVE_METHOD(EVP_MD_CTX_destroy, "(J)V"),
Expand All @@ -11140,6 +11310,8 @@ static JNINativeMethod sNativeCryptoMethods[] = {
CONSCRYPT_NATIVE_METHOD(EVP_DigestVerifyUpdate, "(" REF_EVP_MD_CTX "[BII)V"),
CONSCRYPT_NATIVE_METHOD(EVP_DigestVerifyUpdateDirect, "(" REF_EVP_MD_CTX "JI)V"),
CONSCRYPT_NATIVE_METHOD(EVP_DigestVerifyFinal, "(" REF_EVP_MD_CTX "[BII)Z"),
CONSCRYPT_NATIVE_METHOD(EVP_DigestSign, "(" REF_EVP_MD_CTX "[BII)[B"),
CONSCRYPT_NATIVE_METHOD(EVP_DigestVerify, "(" REF_EVP_MD_CTX "[BII[BII)Z"),
CONSCRYPT_NATIVE_METHOD(EVP_PKEY_encrypt_init, "(" REF_EVP_PKEY ")J"),
CONSCRYPT_NATIVE_METHOD(EVP_PKEY_encrypt, "(" REF_EVP_PKEY_CTX "[BI[BII)I"),
CONSCRYPT_NATIVE_METHOD(EVP_PKEY_decrypt_init, "(" REF_EVP_PKEY ")J"),
Expand Down Expand Up @@ -11179,8 +11351,10 @@ static JNINativeMethod sNativeCryptoMethods[] = {
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_free, "(J)V"),
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_open, "(" REF_EVP_HPKE_CTX "[B[B)[B"),
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_seal, "(" REF_EVP_HPKE_CTX "[B[B)[B"),
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_recipient, "(III[B[B[B)Ljava/lang/Object;"),
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_sender, "(III[B[B)[Ljava/lang/Object;"),
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_recipient,
"(III[B[B[B)Ljava/lang/Object;"),
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_sender,
"(III[B[B)[Ljava/lang/Object;"),
CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing,
"(III[B[B[B)[Ljava/lang/Object;"),
CONSCRYPT_NATIVE_METHOD(HMAC_CTX_new, "()J"),
Expand Down
5 changes: 5 additions & 0 deletions common/src/jni/main/include/conscrypt/jniutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,11 @@ extern int throwNullPointerException(JNIEnv* env, const char* msg);
*/
extern int throwOutOfMemory(JNIEnv* env, const char* message);

/**
* Throws an IllegalArgumentException with the given string as a message.
*/
extern int throwIllegalArgumentException(JNIEnv* env, const char* message);

/**
* Throws a BadPaddingException with the given string as a message.
*/
Expand Down
8 changes: 8 additions & 0 deletions common/src/main/java/org/conscrypt/NativeCrypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ static native int ECDH_compute_key(byte[] out, int outOffset, NativeRef.EVP_PKEY

static native void X25519_keypair(byte[] outPublicKey, byte[] outPrivateKey);

static native void ED25519_keypair(byte[] outPublicKey, byte[] outPrivateKey);

// --- Message digest functions --------------

// These return const references
Expand Down Expand Up @@ -264,6 +266,12 @@ static native void EVP_DigestVerifyUpdate(
static native boolean EVP_DigestVerifyFinal(NativeRef.EVP_MD_CTX ctx, byte[] signature,
int offset, int length) throws IndexOutOfBoundsException;

static native byte[] EVP_DigestSign(
NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length);

static native boolean EVP_DigestVerify(NativeRef.EVP_MD_CTX ctx, byte[] sigBuffer,
int sigOffset, int sigLen, byte[] dataBuffer, int dataOffset, int dataLen);

static native long EVP_PKEY_encrypt_init(NativeRef.EVP_PKEY pkey) throws InvalidKeyException;

static native int EVP_PKEY_encrypt(NativeRef.EVP_PKEY_CTX ctx, byte[] out, int outOffset,
Expand Down
1 change: 1 addition & 0 deletions constants/src/gen/cpp/generate_constants.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ int main(int /* argc */, char ** /* argv */) {

CONST(EVP_PKEY_RSA);
CONST(EVP_PKEY_EC);
CONST(EVP_PKEY_ED25519);

CONST(RSA_PKCS1_PADDING);
CONST(RSA_NO_PADDING);
Expand Down
Loading
Loading