From 5daf26490e69482c3277a0cb82f47ec59817232a Mon Sep 17 00:00:00 2001 From: Pete Bentley <44170157+prbprbprb@users.noreply.github.com> Date: Sun, 22 Oct 2023 11:08:19 +0100 Subject: [PATCH 1/3] Don't create Date or Calendar objects for ASN.1 dates unless needed. (#1176) * Don't create Date or Calendar objects for ASN.1 dates unless needed. Continues to parse Cert/CRL dates on creation in order to detect errors, but stores them as longs since the epoch rather than Dates and lazily creates Dates directly from them rather than requiring a Calendar. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 82 +++++-------------- .../main/java/org/conscrypt/NativeCrypto.java | 4 - .../java/org/conscrypt/OpenSSLX509CRL.java | 20 ++--- .../org/conscrypt/OpenSSLX509CRLEntry.java | 6 +- .../org/conscrypt/OpenSSLX509Certificate.java | 30 ++----- .../org/conscrypt/HostnameVerifierTest.java | 2 +- openjdk/build.gradle | 1 + 7 files changed, 38 insertions(+), 107 deletions(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 78951bdad..e2dc9bdce 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -4861,6 +4861,20 @@ static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass return joa.release(); } +/* + * Converts an ASN1_TIME to epoch time in milliseconds. + */ +static jlong ASN1_TIME_convert_to_posix(JNIEnv* env, const ASN1_TIME* time) { + int64_t retval; + if (!ASN1_TIME_to_posix(time, &retval)) { + JNI_TRACE("ASN1_TIME_convert_to_posix(%p) => Invalid date value", time); + conscrypt::jniutil::throwParsingException(env, "Invalid date value"); + return 0; + } + // ASN1_TIME_to_posix can only return years from 0000 to 9999, so this won't overflow. + return static_cast(retval * 1000); +} + static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref, CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; @@ -4875,7 +4889,7 @@ static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref, ASN1_TIME* notBefore = X509_get_notBefore(x509); JNI_TRACE("X509_get_notBefore(%p) => %p", x509, notBefore); - return reinterpret_cast(notBefore); + return ASN1_TIME_convert_to_posix(env, notBefore); } static jlong NativeCrypto_X509_get_notAfter(JNIEnv* env, jclass, jlong x509Ref, @@ -4892,7 +4906,7 @@ static jlong NativeCrypto_X509_get_notAfter(JNIEnv* env, jclass, jlong x509Ref, ASN1_TIME* notAfter = X509_get_notAfter(x509); JNI_TRACE("X509_get_notAfter(%p) => %p", x509, notAfter); - return reinterpret_cast(notAfter); + return ASN1_TIME_convert_to_posix(env, notAfter); } // NOLINTNEXTLINE(runtime/int) @@ -5528,7 +5542,7 @@ static jlong NativeCrypto_get_X509_REVOKED_revocationDate(JNIEnv* env, jclass, JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => %p", revoked, X509_REVOKED_get0_revocationDate(revoked)); - return reinterpret_cast(X509_REVOKED_get0_revocationDate(revoked)); + return ASN1_TIME_convert_to_posix(env, X509_REVOKED_get0_revocationDate(revoked)); } #ifdef __GNUC__ @@ -5622,7 +5636,7 @@ static jlong NativeCrypto_X509_CRL_get_lastUpdate(JNIEnv* env, jclass, jlong x50 ASN1_TIME* lastUpdate = X509_CRL_get_lastUpdate(crl); JNI_TRACE("X509_CRL_get_lastUpdate(%p) => %p", crl, lastUpdate); - return reinterpret_cast(lastUpdate); + return ASN1_TIME_convert_to_posix(env, lastUpdate); } static jlong NativeCrypto_X509_CRL_get_nextUpdate(JNIEnv* env, jclass, jlong x509CrlRef, @@ -5639,7 +5653,7 @@ static jlong NativeCrypto_X509_CRL_get_nextUpdate(JNIEnv* env, jclass, jlong x50 ASN1_TIME* nextUpdate = X509_CRL_get_nextUpdate(crl); JNI_TRACE("X509_CRL_get_nextUpdate(%p) => %p", crl, nextUpdate); - return reinterpret_cast(nextUpdate); + return ASN1_TIME_convert_to_posix(env, nextUpdate); } static jbyteArray NativeCrypto_i2d_X509_REVOKED(JNIEnv* env, jclass, jlong x509RevokedRef) { @@ -5663,63 +5677,6 @@ static jint NativeCrypto_X509_supported_extension(JNIEnv* env, jclass, jlong x50 return X509_supported_extension(ext); } -static inline bool decimal_to_integer(const char* data, size_t len, int* out) { - int ret = 0; - for (size_t i = 0; i < len; i++) { - ret *= 10; - if (data[i] < '0' || data[i] > '9') { - return false; - } - ret += data[i] - '0'; - } - *out = ret; - return true; -} - -static void NativeCrypto_ASN1_TIME_to_Calendar(JNIEnv* env, jclass, jlong asn1TimeRef, - jobject calendar) { - CHECK_ERROR_QUEUE_ON_RETURN; - ASN1_TIME* asn1Time = reinterpret_cast(static_cast(asn1TimeRef)); - JNI_TRACE("ASN1_TIME_to_Calendar(%p, %p)", asn1Time, calendar); - - if (asn1Time == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "asn1Time == null"); - return; - } - - if (!ASN1_TIME_check(asn1Time)) { - conscrypt::jniutil::throwParsingException(env, "Invalid date format"); - return; - } - - bssl::UniquePtr gen(ASN1_TIME_to_generalizedtime(asn1Time, nullptr)); - if (gen.get() == nullptr) { - conscrypt::jniutil::throwParsingException(env, - "ASN1_TIME_to_generalizedtime returned null"); - return; - } - - if (ASN1_STRING_length(gen.get()) < 14 || ASN1_STRING_get0_data(gen.get()) == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "gen->length < 14 || gen->data == null"); - return; - } - - int year, mon, mday, hour, min, sec; - const char* data = reinterpret_cast(ASN1_STRING_get0_data(gen.get())); - if (!decimal_to_integer(data, 4, &year) || - !decimal_to_integer(data + 4, 2, &mon) || - !decimal_to_integer(data + 6, 2, &mday) || - !decimal_to_integer(data + 8, 2, &hour) || - !decimal_to_integer(data + 10, 2, &min) || - !decimal_to_integer(data + 12, 2, &sec)) { - conscrypt::jniutil::throwParsingException(env, "Invalid date format"); - return; - } - - env->CallVoidMethod(calendar, conscrypt::jniutil::calendar_setMethod, year, mon - 1, mday, hour, - min, sec); -} - // A CbsHandle is a structure used to manage resources allocated by asn1_read-* // functions so that they can be freed properly when finished. This struct owns // all objects pointed to by its members. @@ -11218,7 +11175,6 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(X509_REVOKED_dup, "(J)J"), CONSCRYPT_NATIVE_METHOD(i2d_X509_REVOKED, "(J)[B"), CONSCRYPT_NATIVE_METHOD(X509_supported_extension, "(J)I"), - CONSCRYPT_NATIVE_METHOD(ASN1_TIME_to_Calendar, "(JLjava/util/Calendar;)V"), CONSCRYPT_NATIVE_METHOD(asn1_read_init, "([B)J"), CONSCRYPT_NATIVE_METHOD(asn1_read_sequence, "(J)J"), CONSCRYPT_NATIVE_METHOD(asn1_read_next_tag_is, "(JI)Z"), diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index 16b93f787..e42b166fb 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -611,10 +611,6 @@ static native void X509_CRL_verify(long x509CrlCtx, OpenSSLX509CRL holder, static native int X509_supported_extension(long x509ExtensionRef); - // --- ASN1_TIME ----------------------------------------------------------- - - static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) throws ParsingException; - // --- ASN1 Encoding ------------------------------------------------------- /** diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java b/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java index d3983cdbb..9461faa28 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX509CRL.java @@ -50,23 +50,15 @@ */ final class OpenSSLX509CRL extends X509CRL { private volatile long mContext; - private final Date thisUpdate; - private final Date nextUpdate; + private final long thisUpdate; + private final long nextUpdate; private OpenSSLX509CRL(long ctx) throws ParsingException { mContext = ctx; // The legacy X509 OpenSSL APIs don't validate ASN1_TIME structures until access, so // parse them here because this is the only time we're allowed to throw ParsingException - thisUpdate = toDate(NativeCrypto.X509_CRL_get_lastUpdate(mContext, this)); - nextUpdate = toDate(NativeCrypto.X509_CRL_get_nextUpdate(mContext, this)); - } - - // Package-visible because it's also used by OpenSSLX509CRLEntry - static Date toDate(long asn1time) throws ParsingException { - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.set(Calendar.MILLISECOND, 0); - NativeCrypto.ASN1_TIME_to_Calendar(asn1time, calendar); - return calendar.getTime(); + thisUpdate = NativeCrypto.X509_CRL_get_lastUpdate(mContext, this); + nextUpdate = NativeCrypto.X509_CRL_get_nextUpdate(mContext, this); } static OpenSSLX509CRL fromX509DerInputStream(InputStream is) throws ParsingException { @@ -278,12 +270,12 @@ public X500Principal getIssuerX500Principal() { @Override public Date getThisUpdate() { - return (Date) thisUpdate.clone(); + return new Date(thisUpdate); } @Override public Date getNextUpdate() { - return (Date) nextUpdate.clone(); + return new Date(nextUpdate); } @Override diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java b/common/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java index 9a3db6245..2d4846cf8 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX509CRLEntry.java @@ -31,13 +31,13 @@ */ final class OpenSSLX509CRLEntry extends X509CRLEntry { private final long mContext; - private final Date revocationDate; + private final long revocationDate; OpenSSLX509CRLEntry(long ctx) throws ParsingException { mContext = ctx; // The legacy X509 OpenSSL APIs don't validate ASN1_TIME structures until access, so // parse them here because this is the only time we're allowed to throw ParsingException - revocationDate = OpenSSLX509CRL.toDate(NativeCrypto.get_X509_REVOKED_revocationDate(mContext)); + revocationDate = NativeCrypto.get_X509_REVOKED_revocationDate(mContext); } @Override @@ -112,7 +112,7 @@ public BigInteger getSerialNumber() { @Override public Date getRevocationDate() { - return (Date) revocationDate.clone(); + return new Date(revocationDate); } @Override diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java b/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java index f5e5c5f53..925d60ddd 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX509Certificate.java @@ -61,29 +61,15 @@ public final class OpenSSLX509Certificate extends X509Certificate { private transient volatile long mContext; private transient Integer mHashCode; - private final Date notBefore; - private final Date notAfter; + private final long notBefore; + private final long notAfter; OpenSSLX509Certificate(long ctx) throws ParsingException { mContext = ctx; // The legacy X509 OpenSSL APIs don't validate ASN1_TIME structures until access, so // parse them here because this is the only time we're allowed to throw ParsingException - notBefore = toDate(NativeCrypto.X509_get_notBefore(mContext, this)); - notAfter = toDate(NativeCrypto.X509_get_notAfter(mContext, this)); - } - - // A non-throwing constructor used when we have already parsed the dates - private OpenSSLX509Certificate(long ctx, Date notBefore, Date notAfter) { - mContext = ctx; - this.notBefore = notBefore; - this.notAfter = notAfter; - } - - private static Date toDate(long asn1time) throws ParsingException { - Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - calendar.set(Calendar.MILLISECOND, 0); - NativeCrypto.ASN1_TIME_to_Calendar(asn1time, calendar); - return calendar.getTime(); + notBefore = NativeCrypto.X509_get_notBefore(mContext, this); + notAfter = NativeCrypto.X509_get_notAfter(mContext, this); } public static OpenSSLX509Certificate fromX509DerInputStream(InputStream is) @@ -260,12 +246,12 @@ public void checkValidity(Date date) throws CertificateExpiredException, CertificateNotYetValidException { if (getNotBefore().compareTo(date) > 0) { throw new CertificateNotYetValidException("Certificate not valid until " - + getNotBefore().toString() + " (compared to " + date.toString() + ")"); + + getNotBefore() + " (compared to " + date + ")"); } if (getNotAfter().compareTo(date) < 0) { throw new CertificateExpiredException("Certificate expired at " - + getNotAfter().toString() + " (compared to " + date.toString() + ")"); + + getNotAfter() + " (compared to " + date + ")"); } } @@ -291,12 +277,12 @@ public Principal getSubjectDN() { @Override public Date getNotBefore() { - return (Date) notBefore.clone(); + return new Date(notBefore); } @Override public Date getNotAfter() { - return (Date) notAfter.clone(); + return new Date(notAfter); } @Override diff --git a/common/src/test/java/org/conscrypt/HostnameVerifierTest.java b/common/src/test/java/org/conscrypt/HostnameVerifierTest.java index a369ecd1e..487a42d1f 100644 --- a/common/src/test/java/org/conscrypt/HostnameVerifierTest.java +++ b/common/src/test/java/org/conscrypt/HostnameVerifierTest.java @@ -598,7 +598,7 @@ public void subjectAltNameWithToplevelWildcard() throws Exception { // // Certificate generated using:- // openssl req -x509 -nodes -days 36500 -subj "/CN=Google Inc" \ - // -addext "subjectAltName=DNS:*.com" -newkey rsa:512 + // -addext "subjectAltName=DNS:*.com" - SSLSession session = session("" + "-----BEGIN CERTIFICATE-----\n" + "MIIBlTCCAT+gAwIBAgIUe1RB6C61ZW/SEQpKiywSEJOEOUMwDQYJKoZIhvcNAQEL\n" diff --git a/openjdk/build.gradle b/openjdk/build.gradle index 2c0adb18e..0e6357cd4 100644 --- a/openjdk/build.gradle +++ b/openjdk/build.gradle @@ -407,6 +407,7 @@ model { if (toolChain in Clang || toolChain in Gcc) { cppCompiler.args "-Wall", + "-Werror", "-fPIC", "-O3", "-std=c++17", From deb0c22138832d5bf2caffc3138aac6b9c45b018 Mon Sep 17 00:00:00 2001 From: Pete Bentley <44170157+prbprbprb@users.noreply.github.com> Date: Tue, 24 Oct 2023 16:19:54 +0100 Subject: [PATCH 2/3] Rationalise test suite classes. (#1177) ConscryptJava7Suite is obsolete, and we no longer use suites for anything but OpenJDK so merge ConscryptSuite into ConscryptOpenJdkSuite. Ultimately I aim to stop using suites. This is part of #1155 but splitting it out here to simplify that change. --- .../org/conscrypt/ConscryptJava7Suite.java | 137 ------------- .../java/org/conscrypt/ConscryptSuite.java | 167 ---------------- openjdk/build.gradle | 20 +- .../org/conscrypt/ConscryptOpenJdkSuite.java | 181 +++++++++++++++--- 4 files changed, 161 insertions(+), 344 deletions(-) delete mode 100644 common/src/test/java/org/conscrypt/ConscryptJava7Suite.java delete mode 100644 common/src/test/java/org/conscrypt/ConscryptSuite.java diff --git a/common/src/test/java/org/conscrypt/ConscryptJava7Suite.java b/common/src/test/java/org/conscrypt/ConscryptJava7Suite.java deleted file mode 100644 index a400599c2..000000000 --- a/common/src/test/java/org/conscrypt/ConscryptJava7Suite.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2018 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 org.conscrypt.TestUtils.installConscryptAsDefaultProvider; - -import org.conscrypt.ct.CTVerifierTest; -import org.conscrypt.ct.SerializationTest; -import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDH; -import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDSA; -import org.conscrypt.java.security.AlgorithmParametersPSSTest; -import org.conscrypt.java.security.AlgorithmParametersTestAES; -import org.conscrypt.java.security.AlgorithmParametersTestDES; -import org.conscrypt.java.security.AlgorithmParametersTestDESede; -import org.conscrypt.java.security.AlgorithmParametersTestDH; -import org.conscrypt.java.security.AlgorithmParametersTestDSA; -import org.conscrypt.java.security.AlgorithmParametersTestEC; -import org.conscrypt.java.security.AlgorithmParametersTestGCM; -import org.conscrypt.java.security.AlgorithmParametersTestOAEP; -import org.conscrypt.java.security.KeyFactoryTestDH; -import org.conscrypt.java.security.KeyFactoryTestDSA; -import org.conscrypt.java.security.KeyFactoryTestEC; -import org.conscrypt.java.security.KeyFactoryTestRSA; -import org.conscrypt.java.security.KeyPairGeneratorTest; -import org.conscrypt.java.security.KeyPairGeneratorTestDH; -import org.conscrypt.java.security.KeyPairGeneratorTestDSA; -import org.conscrypt.java.security.KeyPairGeneratorTestRSA; -import org.conscrypt.java.security.MessageDigestTest; -import org.conscrypt.java.security.SignatureTest; -import org.conscrypt.java.security.cert.CertificateFactoryTest; -import org.conscrypt.java.security.cert.X509CRLTest; -import org.conscrypt.java.security.cert.X509CertificateTest; -import org.conscrypt.javax.crypto.AeadCipherTest; -import org.conscrypt.javax.crypto.CipherBasicsTest; -import org.conscrypt.javax.crypto.KeyGeneratorTest; -import org.conscrypt.javax.net.ssl.HttpsURLConnectionTest; -import org.conscrypt.javax.net.ssl.KeyManagerFactoryTest; -import org.conscrypt.javax.net.ssl.KeyStoreBuilderParametersTest; -import org.conscrypt.javax.net.ssl.SNIHostNameTest; -import org.conscrypt.javax.net.ssl.SSLContextTest; -import org.conscrypt.javax.net.ssl.SSLEngineTest; -import org.conscrypt.javax.net.ssl.SSLEngineVersionCompatibilityTest; -import org.conscrypt.javax.net.ssl.SSLParametersTest; -import org.conscrypt.javax.net.ssl.SSLServerSocketFactoryTest; -import org.conscrypt.javax.net.ssl.SSLServerSocketTest; -import org.conscrypt.javax.net.ssl.SSLSessionContextTest; -import org.conscrypt.javax.net.ssl.SSLSessionTest; -import org.conscrypt.javax.net.ssl.SSLSocketFactoryTest; -import org.conscrypt.javax.net.ssl.SSLSocketTest; -import org.conscrypt.javax.net.ssl.SSLSocketVersionCompatibilityTest; -import org.conscrypt.javax.net.ssl.TrustManagerFactoryTest; -import org.conscrypt.javax.net.ssl.X509KeyManagerTest; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) -@Suite.SuiteClasses({ - // org.conscrypt tests - CertPinManagerTest.class, - ChainStrengthAnalyzerTest.class, - TrustManagerImplTest.class, - // org.conscrypt.ct tests - CTVerifierTest.class, - SerializationTest.class, - // java.security tests - CertificateFactoryTest.class, - X509CertificateTest.class, - X509CRLTest.class, - AlgorithmParameterGeneratorTestDH.class, - AlgorithmParameterGeneratorTestDSA.class, - AlgorithmParametersPSSTest.class, - AlgorithmParametersTestAES.class, - AlgorithmParametersTestDES.class, - AlgorithmParametersTestDESede.class, - AlgorithmParametersTestDH.class, - AlgorithmParametersTestDSA.class, - AlgorithmParametersTestEC.class, - AlgorithmParametersTestGCM.class, - AlgorithmParametersTestOAEP.class, - KeyFactoryTestDH.class, - KeyFactoryTestDSA.class, - KeyFactoryTestEC.class, - KeyFactoryTestRSA.class, - KeyPairGeneratorTest.class, - KeyPairGeneratorTestDH.class, - KeyPairGeneratorTestDSA.class, - KeyPairGeneratorTestRSA.class, - MessageDigestTest.class, - SignatureTest.class, - // javax.crypto tests - AeadCipherTest.class, - CipherBasicsTest.class, - // CipherTest.class, // Lots of weird, broken behaviors in Sun* providers on OpenJDK 7 - // ECDHKeyAgreementTest.class, // EC keys are broken on OpenJDK 7 - KeyGeneratorTest.class, - // javax.net.ssl tests - HttpsURLConnectionTest.class, - KeyManagerFactoryTest.class, - KeyStoreBuilderParametersTest.class, - SNIHostNameTest.class, - SSLContextTest.class, - SSLEngineTest.class, - SSLEngineVersionCompatibilityTest.class, - SSLParametersTest.class, - SSLServerSocketFactoryTest.class, - SSLServerSocketTest.class, - SSLSessionContextTest.class, - SSLSessionTest.class, - SSLSocketFactoryTest.class, - SSLSocketTest.class, - SSLSocketVersionCompatibilityTest.class, - TrustManagerFactoryTest.class, - X509KeyManagerTest.class, -}) -public class ConscryptJava7Suite { - - @BeforeClass - public static void setupStatic() { - installConscryptAsDefaultProvider(); - } - -} diff --git a/common/src/test/java/org/conscrypt/ConscryptSuite.java b/common/src/test/java/org/conscrypt/ConscryptSuite.java deleted file mode 100644 index 2bcb85612..000000000 --- a/common/src/test/java/org/conscrypt/ConscryptSuite.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2017 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 org.conscrypt.TestUtils.installConscryptAsDefaultProvider; - -import org.conscrypt.ct.CTVerifierTest; -import org.conscrypt.ct.SerializationTest; -import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDH; -import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDSA; -import org.conscrypt.java.security.AlgorithmParametersPSSTest; -import org.conscrypt.java.security.AlgorithmParametersTestAES; -import org.conscrypt.java.security.AlgorithmParametersTestDES; -import org.conscrypt.java.security.AlgorithmParametersTestDESede; -import org.conscrypt.java.security.AlgorithmParametersTestDH; -import org.conscrypt.java.security.AlgorithmParametersTestDSA; -import org.conscrypt.java.security.AlgorithmParametersTestEC; -import org.conscrypt.java.security.AlgorithmParametersTestGCM; -import org.conscrypt.java.security.AlgorithmParametersTestOAEP; -import org.conscrypt.java.security.KeyFactoryTestDH; -import org.conscrypt.java.security.KeyFactoryTestDSA; -import org.conscrypt.java.security.KeyFactoryTestEC; -import org.conscrypt.java.security.KeyFactoryTestRSA; -import org.conscrypt.java.security.KeyFactoryTestRSACrt; -import org.conscrypt.java.security.KeyFactoryTestRSACustom; -import org.conscrypt.java.security.KeyFactoryTestXDH; -import org.conscrypt.java.security.KeyPairGeneratorTest; -import org.conscrypt.java.security.KeyPairGeneratorTestDH; -import org.conscrypt.java.security.KeyPairGeneratorTestDSA; -import org.conscrypt.java.security.KeyPairGeneratorTestRSA; -import org.conscrypt.java.security.KeyPairGeneratorTestXDH; -import org.conscrypt.java.security.MessageDigestTest; -import org.conscrypt.java.security.SignatureTest; -import org.conscrypt.java.security.cert.CertificateFactoryTest; -import org.conscrypt.java.security.cert.X509CRLTest; -import org.conscrypt.java.security.cert.X509CertificateTest; -import org.conscrypt.javax.crypto.AeadCipherTest; -import org.conscrypt.javax.crypto.CipherBasicsTest; -import org.conscrypt.javax.crypto.CipherTest; -import org.conscrypt.javax.crypto.ECDHKeyAgreementTest; -import org.conscrypt.javax.crypto.KeyGeneratorTest; -import org.conscrypt.javax.crypto.ScryptTest; -import org.conscrypt.javax.crypto.XDHKeyAgreementTest; -import org.conscrypt.javax.net.ssl.HttpsURLConnectionTest; -import org.conscrypt.javax.net.ssl.KeyManagerFactoryTest; -import org.conscrypt.javax.net.ssl.KeyStoreBuilderParametersTest; -import org.conscrypt.javax.net.ssl.SNIHostNameTest; -import org.conscrypt.javax.net.ssl.SSLContextTest; -import org.conscrypt.javax.net.ssl.SSLEngineTest; -import org.conscrypt.javax.net.ssl.SSLEngineVersionCompatibilityTest; -import org.conscrypt.javax.net.ssl.SSLParametersTest; -import org.conscrypt.javax.net.ssl.SSLServerSocketFactoryTest; -import org.conscrypt.javax.net.ssl.SSLServerSocketTest; -import org.conscrypt.javax.net.ssl.SSLSessionContextTest; -import org.conscrypt.javax.net.ssl.SSLSessionTest; -import org.conscrypt.javax.net.ssl.SSLSocketFactoryTest; -import org.conscrypt.javax.net.ssl.SSLSocketTest; -import org.conscrypt.javax.net.ssl.SSLSocketVersionCompatibilityTest; -import org.conscrypt.javax.net.ssl.TrustManagerFactoryTest; -import org.conscrypt.javax.net.ssl.X509KeyManagerTest; -import org.conscrypt.metrics.CipherSuiteTest; -import org.conscrypt.metrics.OptionalMethodTest; -import org.conscrypt.metrics.ProtocolTest; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) -@Suite.SuiteClasses({ - // org.conscrypt tests - CertPinManagerTest.class, - ChainStrengthAnalyzerTest.class, - HostnameVerifierTest.class, - HpkeContextRecipientTest.class, - HpkeContextSenderTest.class, - HpkeContextTest.class, - HpkeSuiteTest.class, - HpkeTestVectorsTest.class, - NativeCryptoArgTest.class, - TrustManagerImplTest.class, - // org.conscrypt.ct tests - CTVerifierTest.class, - SerializationTest.class, - // java.security tests - CertificateFactoryTest.class, - X509CertificateTest.class, - X509CRLTest.class, - AlgorithmParameterGeneratorTestDH.class, - AlgorithmParameterGeneratorTestDSA.class, - AlgorithmParametersPSSTest.class, - AlgorithmParametersTestAES.class, - AlgorithmParametersTestDES.class, - AlgorithmParametersTestDESede.class, - AlgorithmParametersTestDH.class, - AlgorithmParametersTestDSA.class, - AlgorithmParametersTestEC.class, - AlgorithmParametersTestGCM.class, - AlgorithmParametersTestOAEP.class, - BufferUtilsTest.class, - CipherSuiteTest.class, - KeyFactoryTestDH.class, - KeyFactoryTestDSA.class, - KeyFactoryTestEC.class, - KeyFactoryTestRSA.class, - KeyFactoryTestRSACrt.class, - KeyFactoryTestRSACustom.class, - KeyFactoryTestXDH.class, - KeyPairGeneratorTest.class, - KeyPairGeneratorTestDH.class, - KeyPairGeneratorTestDSA.class, - KeyPairGeneratorTestRSA.class, - KeyPairGeneratorTestXDH.class, - MessageDigestTest.class, - SignatureTest.class, - // javax.crypto tests - AeadCipherTest.class, - CipherBasicsTest.class, - CipherTest.class, - MacTest.class, - ECDHKeyAgreementTest.class, - KeyGeneratorTest.class, - XDHKeyAgreementTest.class, - // javax.net.ssl tests - HttpsURLConnectionTest.class, - KeyManagerFactoryTest.class, - KeyStoreBuilderParametersTest.class, - OptionalMethodTest.class, - ProtocolTest.class, - ScryptTest.class, - SNIHostNameTest.class, - SSLContextTest.class, - SSLEngineTest.class, - SSLEngineVersionCompatibilityTest.class, - SSLParametersTest.class, - SSLServerSocketFactoryTest.class, - SSLServerSocketTest.class, - SSLSessionContextTest.class, - SSLSessionTest.class, - SSLSocketFactoryTest.class, - SSLSocketTest.class, - SSLSocketVersionCompatibilityTest.class, - TrustManagerFactoryTest.class, - VeryBasicHttpServerTest.class, - X509KeyManagerTest.class, -}) -public class ConscryptSuite { - - @BeforeClass - public static void setupStatic() { - installConscryptAsDefaultProvider(); - } - -} diff --git a/openjdk/build.gradle b/openjdk/build.gradle index 0e6357cd4..cda93c34d 100644 --- a/openjdk/build.gradle +++ b/openjdk/build.gradle @@ -315,28 +315,12 @@ def addNativeJar(NativeBuildInfo nativeBuild) { publishing.publications.maven.artifact jarTask.get() } - -// TODO(prb) Still provide a mechanism for testing on Java 7? -// Check which version -//def javaError = new ByteArrayOutputStream() -//exec { -// executable test.executable -// System.out.println("Running tests with java executable: " + test.executable + ".") -// args = ['-version'] -// ignoreExitValue true -// errorOutput = javaError -//} -// -//def suiteClass = (javaError.toString() =~ /"1[.]7[.].*"/) ? -// "org/conscrypt/ConscryptJava7Suite.class" : "org/conscrypt/ConscryptSuite.class"; -def suiteClass = "org/conscrypt/ConscryptSuite.class"; - test { - include suiteClass, "org/conscrypt/ConscryptOpenJdkSuite.class" + include "org/conscrypt/ConscryptOpenJdkSuite.class" } def testFdSocket = tasks.register("testFdSocket", Test) { - include suiteClass, "org/conscrypt/ConscryptOpenJdkSuite.class" + include "org/conscrypt/ConscryptOpenJdkSuite.class" InvokerHelper.setProperties(testLogging, test.testLogging.properties) systemProperties = test.systemProperties systemProperty "org.conscrypt.useEngineSocketByDefault", false diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java index 813eaac75..977b5f063 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java @@ -1,35 +1,172 @@ +/* + * Copyright (C) 2023 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 org.conscrypt.TestUtils.installConscryptAsDefaultProvider; +import org.conscrypt.ct.CTVerifierTest; +import org.conscrypt.ct.SerializationTest; +import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDH; +import org.conscrypt.java.security.AlgorithmParameterGeneratorTestDSA; +import org.conscrypt.java.security.AlgorithmParametersPSSTest; +import org.conscrypt.java.security.AlgorithmParametersTestAES; +import org.conscrypt.java.security.AlgorithmParametersTestDES; +import org.conscrypt.java.security.AlgorithmParametersTestDESede; +import org.conscrypt.java.security.AlgorithmParametersTestDH; +import org.conscrypt.java.security.AlgorithmParametersTestDSA; +import org.conscrypt.java.security.AlgorithmParametersTestEC; +import org.conscrypt.java.security.AlgorithmParametersTestGCM; +import org.conscrypt.java.security.AlgorithmParametersTestOAEP; +import org.conscrypt.java.security.KeyFactoryTestDH; +import org.conscrypt.java.security.KeyFactoryTestDSA; +import org.conscrypt.java.security.KeyFactoryTestEC; +import org.conscrypt.java.security.KeyFactoryTestRSA; +import org.conscrypt.java.security.KeyFactoryTestRSACrt; +import org.conscrypt.java.security.KeyPairGeneratorTest; +import org.conscrypt.java.security.KeyPairGeneratorTestDH; +import org.conscrypt.java.security.KeyPairGeneratorTestDSA; +import org.conscrypt.java.security.KeyPairGeneratorTestRSA; +import org.conscrypt.java.security.KeyPairGeneratorTestXDH; +import org.conscrypt.java.security.MessageDigestTest; +import org.conscrypt.java.security.SignatureTest; +import org.conscrypt.java.security.cert.CertificateFactoryTest; +import org.conscrypt.java.security.cert.X509CRLTest; +import org.conscrypt.java.security.cert.X509CertificateTest; +import org.conscrypt.javax.crypto.AeadCipherTest; +import org.conscrypt.javax.crypto.CipherBasicsTest; +import org.conscrypt.javax.crypto.CipherTest; +import org.conscrypt.javax.crypto.ECDHKeyAgreementTest; +import org.conscrypt.javax.crypto.KeyGeneratorTest; +import org.conscrypt.javax.crypto.ScryptTest; +import org.conscrypt.javax.crypto.XDHKeyAgreementTest; +import org.conscrypt.javax.net.ssl.HttpsURLConnectionTest; +import org.conscrypt.javax.net.ssl.KeyManagerFactoryTest; +import org.conscrypt.javax.net.ssl.KeyStoreBuilderParametersTest; +import org.conscrypt.javax.net.ssl.SNIHostNameTest; +import org.conscrypt.javax.net.ssl.SSLContextTest; +import org.conscrypt.javax.net.ssl.SSLEngineTest; +import org.conscrypt.javax.net.ssl.SSLEngineVersionCompatibilityTest; +import org.conscrypt.javax.net.ssl.SSLParametersTest; +import org.conscrypt.javax.net.ssl.SSLServerSocketFactoryTest; +import org.conscrypt.javax.net.ssl.SSLServerSocketTest; +import org.conscrypt.javax.net.ssl.SSLSessionContextTest; +import org.conscrypt.javax.net.ssl.SSLSessionTest; +import org.conscrypt.javax.net.ssl.SSLSocketFactoryTest; +import org.conscrypt.javax.net.ssl.SSLSocketTest; +import org.conscrypt.javax.net.ssl.SSLSocketVersionCompatibilityTest; +import org.conscrypt.javax.net.ssl.TrustManagerFactoryTest; +import org.conscrypt.javax.net.ssl.X509KeyManagerTest; +import org.conscrypt.metrics.CipherSuiteTest; +import org.conscrypt.metrics.OptionalMethodTest; +import org.conscrypt.metrics.ProtocolTest; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.Suite; @RunWith(Suite.class) @Suite.SuiteClasses({ - AddressUtilsTest.class, - ApplicationProtocolSelectorAdapterTest.class, - ClientSessionContextTest.class, - ConscryptSocketTest.class, - ConscryptTest.class, - DuckTypedPSKKeyManagerTest.class, - FileClientSessionCacheTest.class, - NativeCryptoTest.class, - NativeRefTest.class, - NativeSslSessionTest.class, - OpenSSLKeyTest.class, - OpenSSLX509CertificateTest.class, - PlatformTest.class, - ServerSessionContextTest.class, - SSLUtilsTest.class, - TestSessionBuilderTest.class, + // org.conscrypt tests + AddressUtilsTest.class, + ApplicationProtocolSelectorAdapterTest.class, + CertPinManagerTest.class, + ChainStrengthAnalyzerTest.class, + ClientSessionContextTest.class, + ConscryptSocketTest.class, + ConscryptTest.class, + DuckTypedPSKKeyManagerTest.class, + FileClientSessionCacheTest.class, + HostnameVerifierTest.class, + NativeCryptoArgTest.class, + NativeCryptoTest.class, + NativeRefTest.class, + NativeSslSessionTest.class, + OpenSSLKeyTest.class, + OpenSSLX509CertificateTest.class, + PlatformTest.class, + SSLUtilsTest.class, + ServerSessionContextTest.class, + TestSessionBuilderTest.class, + TrustManagerImplTest.class, + // org.conscrypt.ct tests + CTVerifierTest.class, + SerializationTest.class, + // java.security tests + CertificateFactoryTest.class, + X509CertificateTest.class, + X509CRLTest.class, + AlgorithmParameterGeneratorTestDH.class, + AlgorithmParameterGeneratorTestDSA.class, + AlgorithmParametersPSSTest.class, + AlgorithmParametersTestAES.class, + AlgorithmParametersTestDES.class, + AlgorithmParametersTestDESede.class, + AlgorithmParametersTestDH.class, + AlgorithmParametersTestDSA.class, + AlgorithmParametersTestEC.class, + AlgorithmParametersTestGCM.class, + AlgorithmParametersTestOAEP.class, + BufferUtilsTest.class, + CipherSuiteTest.class, + KeyFactoryTestDH.class, + KeyFactoryTestDSA.class, + KeyFactoryTestEC.class, + KeyFactoryTestRSA.class, + KeyFactoryTestRSACrt.class, + KeyPairGeneratorTest.class, + KeyPairGeneratorTestDH.class, + KeyPairGeneratorTestDSA.class, + KeyPairGeneratorTestRSA.class, + KeyPairGeneratorTestXDH.class, + MessageDigestTest.class, + SignatureTest.class, + // javax.crypto tests + AeadCipherTest.class, + CipherBasicsTest.class, + CipherTest.class, + MacTest.class, + ECDHKeyAgreementTest.class, + KeyGeneratorTest.class, + XDHKeyAgreementTest.class, + // javax.net.ssl tests + HttpsURLConnectionTest.class, + KeyManagerFactoryTest.class, + KeyStoreBuilderParametersTest.class, + OptionalMethodTest.class, + ProtocolTest.class, + ScryptTest.class, + SNIHostNameTest.class, + SSLContextTest.class, + SSLEngineTest.class, + SSLEngineVersionCompatibilityTest.class, + SSLParametersTest.class, + SSLServerSocketFactoryTest.class, + SSLServerSocketTest.class, + SSLSessionContextTest.class, + SSLSessionTest.class, + SSLSocketFactoryTest.class, + SSLSocketTest.class, + SSLSocketVersionCompatibilityTest.class, + TrustManagerFactoryTest.class, + VeryBasicHttpServerTest.class, + X509KeyManagerTest.class, }) public class ConscryptOpenJdkSuite { - - @BeforeClass - public static void setupStatic() { - installConscryptAsDefaultProvider(); - } - + @BeforeClass + public static void setupStatic() { + installConscryptAsDefaultProvider(); + } } From bc1a34b5e199df1d09fe5735d287f18c5e79bdd7 Mon Sep 17 00:00:00 2001 From: Pete Bentley <44170157+prbprbprb@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:41:24 +0100 Subject: [PATCH 3/3] Rework HPKE into JCA Provider model. (#1174) Decouples API from implementation. Client API finds implementation instances using JCA Provider model and uses them via astable SPI using only primitives and classes available since Android API level 19, possibly via duck typing reflection if they are in different packages. Verified that this works end-to-end on Android where the API from a standalone Conscrypt library (org.conscrypt) could find and use the implementation in the platform (com.android.org.conscrypt). Also re-aligns client API and thrown exceptions to more closely match other JCA services, e.g. Cipher --- common/src/jni/main/cpp/conscrypt/jniutil.cc | 11 + .../jni/main/cpp/conscrypt/native_crypto.cc | 28 +- .../java/org/conscrypt/DuckTypedHpkeSpi.java | 125 ++++++++ .../main/java/org/conscrypt/HpkeContext.java | 119 ++++++++ .../org/conscrypt/HpkeContextRecipient.java | 187 ++++++++---- .../java/org/conscrypt/HpkeContextSender.java | 209 +++++++++---- .../conscrypt/HpkeContextSenderHelper.java | 40 --- .../org/conscrypt/HpkeDecryptException.java | 11 + .../src/main/java/org/conscrypt/HpkeImpl.java | 172 +++++++++++ .../src/main/java/org/conscrypt/HpkeSpi.java | 127 ++++++++ .../main/java/org/conscrypt/HpkeSuite.java | 66 +++-- .../main/java/org/conscrypt/NativeCrypto.java | 28 +- .../java/org/conscrypt/OpenSSLProvider.java | 9 +- .../org/conscrypt/DuckTypedHpkeSpiTest.java | 277 ++++++++++++++++++ .../conscrypt/HpkeContextRecipientTest.java | 130 ++++---- .../org/conscrypt/HpkeContextSenderTest.java | 108 +++++-- .../java/org/conscrypt/HpkeContextTest.java | 45 +-- .../test/java/org/conscrypt/HpkeFixture.java | 72 +++-- .../org/conscrypt/HpkeTestVectorsTest.java | 57 ++-- .../HpkeTestingContextSenderHelper.java | 50 ---- .../org/conscrypt/NativeCryptoArgTest.java | 6 +- .../org/conscrypt/ConscryptOpenJdkSuite.java | 1 + 22 files changed, 1471 insertions(+), 407 deletions(-) create mode 100644 common/src/main/java/org/conscrypt/DuckTypedHpkeSpi.java create mode 100644 common/src/main/java/org/conscrypt/HpkeContext.java delete mode 100644 common/src/main/java/org/conscrypt/HpkeContextSenderHelper.java create mode 100644 common/src/main/java/org/conscrypt/HpkeDecryptException.java create mode 100644 common/src/main/java/org/conscrypt/HpkeImpl.java create mode 100644 common/src/main/java/org/conscrypt/HpkeSpi.java create mode 100644 common/src/test/java/org/conscrypt/DuckTypedHpkeSpiTest.java delete mode 100644 common/src/test/java/org/conscrypt/HpkeTestingContextSenderHelper.java diff --git a/common/src/jni/main/cpp/conscrypt/jniutil.cc b/common/src/jni/main/cpp/conscrypt/jniutil.cc index 945ac8424..ba696969d 100644 --- a/common/src/jni/main/cpp/conscrypt/jniutil.cc +++ b/common/src/jni/main/cpp/conscrypt/jniutil.cc @@ -254,6 +254,12 @@ int throwInvalidKeyException(JNIEnv* env, const char* message) { return conscrypt::jniutil::throwException(env, "java/security/InvalidKeyException", message); } +int throwIllegalArgumentException(JNIEnv* env, const char* message) { + JNI_TRACE("throwIllegalArgumentException %s", message); + return conscrypt::jniutil::throwException( + env, "java/lang/IllegalArgumentException", message); +} + int throwIllegalBlockSizeException(JNIEnv* env, const char* message) { JNI_TRACE("throwIllegalBlockSizeException %s", message); return conscrypt::jniutil::throwException( @@ -344,11 +350,16 @@ int throwForEvpError(JNIEnv* env, int reason, const char* message, int (*defaultThrow)(JNIEnv*, const char*)) { switch (reason) { case EVP_R_MISSING_PARAMETERS: + case EVP_R_INVALID_PEER_KEY: + case EVP_R_DECODE_ERROR: return throwInvalidKeyException(env, message); break; case EVP_R_UNSUPPORTED_ALGORITHM: return throwNoSuchAlgorithmException(env, message); break; + case EVP_R_INVALID_BUFFER_SIZE: + return throwIllegalArgumentException(env, message); + break; default: return defaultThrow(env, message); break; diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index e2dc9bdce..ebbd644a6 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -4007,11 +4007,12 @@ const EVP_HPKE_KEM* getHpkeKem(JNIEnv* env, jint kemValue) { } } -static jobject NativeCrypto_EVP_HPKE_CTX_setup_recipient(JNIEnv* env, jclass, jint kemValue, - jint kdfValue, jint aeadValue, - jbyteArray privateKeyArray, - jbyteArray encArray, - jbyteArray infoArray) { +static jobject NativeCrypto_EVP_HPKE_CTX_setup_base_mode_recipient(JNIEnv* env, jclass, + jint kemValue,jint kdfValue, + jint aeadValue, + jbyteArray privateKeyArray, + jbyteArray encArray, + jbyteArray infoArray) { CHECK_ERROR_QUEUE_ON_RETURN; JNI_TRACE("EVP_HPKE_CTX_setup_recipient(%d, %d, %d, %p, %p, %p)", kemValue, kdfValue, aeadValue, privateKeyArray, encArray, infoArray); @@ -4082,10 +4083,11 @@ static jobject NativeCrypto_EVP_HPKE_CTX_setup_recipient(JNIEnv* env, jclass, ji return ctxObject.release(); } -static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_sender(JNIEnv* env, jclass, jint kemValue, - jint kdfValue, jint aeadValue, - jbyteArray publicKeyArray, - jbyteArray infoArray) { +static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender(JNIEnv* env, jclass, + jint kemValue,jint kdfValue, + jint aeadValue, + jbyteArray publicKeyArray, + jbyteArray infoArray) { CHECK_ERROR_QUEUE_ON_RETURN; JNI_TRACE("EVP_HPKE_CTX_setup_sender(%d, %d, %d, %p, %p)", kemValue, kdfValue, aeadValue, publicKeyArray, infoArray); @@ -4165,7 +4167,7 @@ static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_sender(JNIEnv* env, jclass, return result.release(); } -static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_sender_with_seed_for_testing( +static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing( JNIEnv* env, jclass, jint kemValue, jint kdfValue, jint aeadValue, jbyteArray publicKeyArray, jbyteArray infoArray, jbyteArray seedArray) { CHECK_ERROR_QUEUE_ON_RETURN; @@ -11092,9 +11094,9 @@ 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_recipient, "(III[B[B[B)Ljava/lang/Object;"), - CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_sender, "(III[B[B)[Ljava/lang/Object;"), - CONSCRYPT_NATIVE_METHOD(EVP_HPKE_CTX_setup_sender_with_seed_for_testing, + 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"), CONSCRYPT_NATIVE_METHOD(HMAC_CTX_free, "(J)V"), diff --git a/common/src/main/java/org/conscrypt/DuckTypedHpkeSpi.java b/common/src/main/java/org/conscrypt/DuckTypedHpkeSpi.java new file mode 100644 index 000000000..514d0bad4 --- /dev/null +++ b/common/src/main/java/org/conscrypt/DuckTypedHpkeSpi.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 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 java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.HashMap; +import java.util.Map; + +/** + * Duck typed implementation of {@link HpkeSpi}. + *

+ * Will wrap any Object which implements all of the methods in HpkeSpi and delegate to them + * by reflection. + */ +@Internal +public class DuckTypedHpkeSpi implements HpkeSpi { + private final Object delegate; + private final Map methods = new HashMap<>(); + + private DuckTypedHpkeSpi(Object delegate) throws NoSuchMethodException { + this.delegate = delegate; + + Class sourceClass = delegate.getClass(); + for (Method targetMethod : HpkeSpi.class.getMethods()) { + if (targetMethod.isSynthetic()) { + continue; + } + + Method sourceMethod = + sourceClass.getMethod(targetMethod.getName(), targetMethod.getParameterTypes()); + // Check that the return types match too. + Class sourceReturnType = sourceMethod.getReturnType(); + Class targetReturnType = targetMethod.getReturnType(); + if (!targetReturnType.isAssignableFrom(sourceReturnType)) { + throw new NoSuchMethodException(sourceMethod + " return value (" + sourceReturnType + + ") incompatible with target return value (" + targetReturnType + ")"); + } + methods.put(sourceMethod.getName(), sourceMethod); + } + } + + public static DuckTypedHpkeSpi newInstance(Object delegate) { + try { + return new DuckTypedHpkeSpi(delegate); + } catch (Exception ignored) { + return null; + } + } + + private Object invoke(String methodName, Object... args) { + Method method = methods.get(methodName); + if (method == null) { + throw new IllegalStateException("DuckTypedHpkSpi internal error"); + } + try { + return method.invoke(delegate, args); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException("DuckTypedHpkSpi internal error", e); + } + } + + // Visible for testing + public Object getDelegate() { + return delegate; + } + + @Override + public void engineInitSender( + PublicKey recipientKey, byte[] info, PrivateKey senderKey, byte[] psk, byte[] psk_id) + throws InvalidKeyException { + invoke("engineInitSender", recipientKey, info, senderKey, psk, psk_id); + } + + @Override + public void engineInitSenderForTesting(PublicKey recipientKey, byte[] info, PrivateKey senderKey, + byte[] psk, byte[] psk_id, byte[] sKe) throws InvalidKeyException { + invoke("engineInitSenderForTesting", + recipientKey, info, senderKey, psk, psk_id, sKe); + } + + @Override + public void engineInitRecipient(byte[] encapsulated, PrivateKey key, byte[] info, + PublicKey senderKey, byte[] psk, byte[] psk_id) throws InvalidKeyException { + invoke("engineInitRecipient", encapsulated, key, info, senderKey, psk, psk_id); + } + + @Override + public byte[] engineSeal(byte[] plaintext, byte[] aad) { + return (byte[]) invoke("engineSeal", plaintext, aad); + } + + @Override + public byte[] engineExport(int length, byte[] exporterContext) { + return (byte[]) invoke("engineExport", length, exporterContext); + } + + @Override + public byte[] engineOpen(byte[] ciphertext, byte[] aad) throws GeneralSecurityException { + return (byte[]) invoke("engineOpen", ciphertext, aad); + } + + @Override + public byte[] getEncapsulated() { + return (byte[]) invoke("getEncapsulated"); + } +} diff --git a/common/src/main/java/org/conscrypt/HpkeContext.java b/common/src/main/java/org/conscrypt/HpkeContext.java new file mode 100644 index 000000000..4317796e9 --- /dev/null +++ b/common/src/main/java/org/conscrypt/HpkeContext.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2023 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 java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; +import java.security.Security; + +/** + * Hybrid Public Key Encryption (HPKE) sender APIs. + * + * @see HPKE RFC 9180 + *

+ * Base class for HPKE sender and recipient contexts. + *

+ * This is the client API for HPKE usage, all operations are delegated to an implementation + * class implementing {@link HpkeSpi} which is located using the JCA {@link Provider} + * mechanism. + *

+ * The implementation maintains the context for an HPKE exchange, including the key schedule + * to use for seal and open operations. + * + * Secret key material based on the context may also be generated and exported as per RFC 9180. + */ +public abstract class HpkeContext { + protected final HpkeSpi spi; + + protected HpkeContext(HpkeSpi spi) { + this.spi = spi; + } + + /** + * Exports secret key material from this HpkeContext as described in RFC 9180. + * + * @param length expected output length + * @param context optional context string, may be null or empty + * @return exported value + * @throws IllegalArgumentException if the length is not valid for the KDF in use + * @throws IllegalStateException if this HpkeContext has not been initialised + * + */ + public byte[] export(int length, byte[] context) { + return spi.engineExport(length, context); + } + + /** + * Returns the {@link HpkeSpi} being used by this HpkeContext. + * + * @return the SPI + */ + public HpkeSpi getSpi() { + return spi; + } + + protected static HpkeSpi findSpi(String algorithm) throws NoSuchAlgorithmException { + if (algorithm == null) { + // Same behaviour as Cipher.getInstance + throw new NoSuchAlgorithmException("null algorithm"); + } + return findSpi(algorithm, findFirstProvider(algorithm)); + } + + private static Provider findFirstProvider(String algorithm) throws NoSuchAlgorithmException { + for (Provider p : Security.getProviders()) { + Provider.Service service = p.getService("ConscryptHpke", algorithm); + if (service != null) { + return service.getProvider(); + } + } + throw new NoSuchAlgorithmException("No Provider found for: " + algorithm); + } + + protected static HpkeSpi findSpi(String algorithm, String providerName) throws + NoSuchAlgorithmException, IllegalArgumentException, NoSuchProviderException { + if (providerName == null || providerName.isEmpty()) { + // Same behaviour as Cipher.getInstance + throw new IllegalArgumentException("Invalid provider name"); + } + Provider provider = Security.getProvider(providerName); + if (provider == null) { + throw new NoSuchProviderException("Unknown Provider: " + providerName); + } + return findSpi(algorithm, provider); + } + + protected static HpkeSpi findSpi(String algorithm, Provider provider) throws + NoSuchAlgorithmException, IllegalArgumentException { + if (provider == null) { + throw new IllegalArgumentException("null Provider"); + } + Provider.Service service = provider.getService("ConscryptHpke", algorithm); + if (service == null) { + throw new NoSuchAlgorithmException("Unknown algorithm"); + } + Object instance = service.newInstance(null); + HpkeSpi spi = (instance instanceof HpkeSpi) ? (HpkeSpi) instance + : DuckTypedHpkeSpi.newInstance(instance); + if (spi != null) { + return spi; + } + throw new IllegalStateException( + String.format("Provider %s is providing incorrect instances", provider.getName())); + } +} diff --git a/common/src/main/java/org/conscrypt/HpkeContextRecipient.java b/common/src/main/java/org/conscrypt/HpkeContextRecipient.java index 97c8068f3..29cdde956 100644 --- a/common/src/main/java/org/conscrypt/HpkeContextRecipient.java +++ b/common/src/main/java/org/conscrypt/HpkeContextRecipient.java @@ -16,85 +16,158 @@ package org.conscrypt; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.PrivateKey; -import org.conscrypt.NativeRef.EVP_HPKE_CTX; +import java.security.Provider; +import java.security.PublicKey; /** - * Hybrid Public Key Encryption (HPKE) Recipient APIs. + * Hybrid Public Key Encryption (HPKE) recipient APIs. * * @see HPKE RFC 9180 + * + * Recipient subclass of HpkeContext. See base class for details. */ -public class HpkeContextRecipient { - private final HpkeSuite hpkeSuite; - private EVP_HPKE_CTX ctx; +public class HpkeContextRecipient extends HpkeContext { + private HpkeContextRecipient(HpkeSpi spi) { + super(spi); + } - private HpkeContextRecipient(HpkeSuite hpkeSuite) { - this.hpkeSuite = hpkeSuite; + /** + * Opens a message, using the internal key schedule maintained by this HpkeContextRecipient. + * + * @param ciphertext the ciphertext + * @param aad optional associated data, may be null or empty + * @return the plaintext + * @throws IllegalStateException if this HpkeContextRecipient has not been initialised + * @throws GeneralSecurityException on decryption failures + */ + public byte[] open(byte[] ciphertext, byte[] aad) throws GeneralSecurityException { + return spi.engineOpen(ciphertext, aad); } /** - * Initializes the internal HPKE context for the recipient using BASE (0x00) mode. + * Returns an uninitialised HpkeContextRecipient. * - * @param enc encapsulated key matching the KEM encapsulated key size - * @param privateKey private key (secret key) matching the KEM private key size - * @param info optional application-supplied information - * @throws IllegalArgumentException if providing an encapsulated key (enc) or private key that - * does not match the size expectation - * @throws IllegalStateException if an issue is encountered while setting up the recipient - * (an issue could occur most likely if the keys configured are - * not valid) + * @param suite the HPKE suite to use. @see {@link HpkeSuite} for details. + * @return an uninitialised HpkeContextRecipient for the requested suite + * @throws NoSuchAlgorithmException if no implementation could be found */ - public static HpkeContextRecipient setupBase( - HpkeSuite hpkeSuite, byte[] enc, PrivateKey privateKey, byte[] info) { - Preconditions.checkNotNull(hpkeSuite, "hpkeSuite"); - hpkeSuite.getKem().validateEncLength(enc); - final byte[] sk = hpkeSuite.getKem().validatePrivateKeyTypeAndGetRawKey(privateKey); - try { - final HpkeContextRecipient ctxRecipient = new HpkeContextRecipient(hpkeSuite); - ctxRecipient.ctx = (EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_recipient( - hpkeSuite.getKem().getId(), hpkeSuite.getKdf().getId(), - hpkeSuite.getAead().getId(), sk, enc, info); - return ctxRecipient; - } catch (Exception e) { - throw new IllegalStateException( - "Error while setting up base recipient with the keys provided", e); - } + public static HpkeContextRecipient getInstance(String suite) throws NoSuchAlgorithmException { + return new HpkeContextRecipient(findSpi(suite)); + } + + /** + * Returns an uninitialised HpkeContextRecipient from a specific {@link Provider} + * + * @param suite the HPKE suite to use. @see {@link HpkeSuite} for details. + * @param providerName the name of the Provider to use + * @return an uninitialised HpkeContextRecipient for the requested suite + * @throws NoSuchAlgorithmException if no implementation could be found + * @throws NoSuchProviderException if providerName is null or no such Provider exists + */ + public static HpkeContextRecipient getInstance(String suite, String providerName) + throws NoSuchAlgorithmException, NoSuchProviderException { + return new HpkeContextRecipient(findSpi(suite, providerName)); } /** - * Hybrid Public Key Encryption (HPKE) decryption. - *

- * Note: This API keeps track of its state. It maintains an internal HPKE context. As a result, - * to decrypt multiple messages that were encrypted using the same context, one must call this - * method for each message in the same order as they were encrypted. + * Returns an uninitialised HpkeContextRecipient from a specific {@link Provider} * - * @param ciphertext contains the encrypted plaintext - * @param aad optional associated data - * @return plaintext - * @throws IllegalStateException if an issue is encountered while decrypting (an issue could - * occur - * most likely if the keys configured are not valid) + * @param suite the HPKE suite to use. @see {@link HpkeSuite} for details. + * @param provider the Provider to use + * @return an uninitialised HpkeContextRecipient for the requested suite + * @throws NoSuchAlgorithmException if no implementation could be found + * @throws NoSuchProviderException if providerName is null or no such Provider exists */ - public byte[] open(byte[] ciphertext, byte[] aad) { - Preconditions.checkNotNull(ciphertext, "ciphertext"); - try { - return NativeCrypto.EVP_HPKE_CTX_open(ctx, ciphertext, aad); - } catch (Exception e) { - throw new IllegalStateException( - "Error while decrypting with the keys provided during setup recipient", e); + public static HpkeContextRecipient getInstance(String suite, Provider provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + return new HpkeContextRecipient(findSpi(suite, provider)); + } + + /** + * Initialises this HpkeContextRecipient in BASE mode, i.e. no sender authentication. + * + * @param encapsulated encapsulated ephemeral key from an {@link HpkeContextSender} + * @param recipientKey private key of the recipient + * @param info application-supplied information, may be null or empty + * @throws InvalidKeyException if recipientKey is null or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextRecipient has already been initialised + */ + public void init(byte[] encapsulated, PrivateKey recipientKey, byte[] info) + throws InvalidKeyException { + spi.engineInitRecipient(encapsulated, recipientKey, info, null, + HpkeSpi.DEFAULT_PSK, HpkeSpi.DEFAULT_PSK_ID); + } + + /** + * Initialises this HpkeContextRecipient in AUTH mode, i.e. messages are authenticated using + * the sender's public key. + * + * @param encapsulated encapsulated ephemeral key from an {@link HpkeContextSender} + * @param recipientKey private key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey the public key of the sender + * @throws InvalidKeyException if either recipientKey or senderKey are null + * or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextRecipient has already been initialised + */ + public void init(byte[] encapsulated, PrivateKey recipientKey, byte[] info, PublicKey senderKey) + throws InvalidKeyException { + if (senderKey == null) { + throw new InvalidKeyException("null sender key"); } + // Remaining argument checks are performed by the SPI + spi.engineInitRecipient(encapsulated, recipientKey, info, senderKey, + HpkeSpi.DEFAULT_PSK, HpkeSpi.DEFAULT_PSK_ID); } /** - * Hybrid Public Key Encryption (HPKE) secret export. + * Initialises this HpkeContextRecipient in PSK_AUTH mode, i.e. messages are authenticated using + * a pre-shared secret key. * - * @param length expected output length - * @param exporterContext optional exporter context - * @return exported value - * @throws IllegalArgumentException if the length is not valid based on the KDF spec + * @param encapsulated encapsulated ephemeral key from an {@link HpkeContextSender} + * @param recipientKey private key of the recipient + * @param info application-supplied information, may be null or empty + * @param psk the a pre-shared secret key + * @param psk_id the id of the pre-shared secret key + * @throws NullPointerException if psk or psk_id are null + * @throws InvalidKeyException if recipientKey is null or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextRecipient has already been initialised */ - public byte[] export(int length, byte[] exporterContext) { - hpkeSuite.getKdf().validateExportLength(length); - return NativeCrypto.EVP_HPKE_CTX_export(ctx, exporterContext, length); + public void init(byte[] encapsulated, PrivateKey recipientKey, byte[] info, + byte[] psk, byte[] psk_id) throws InvalidKeyException { + spi.engineInitRecipient(encapsulated, recipientKey, info, null, psk, psk_id); + } + + /** + * Initialises this HpkeContextRecipient in PSK_AUTH mode, i.e. messages are authenticated using + * both the sender's public key and a pre-shared secret key. + * + * @param encapsulated encapsulated ephemeral key from an {@link HpkeContextSender} + * @param recipientKey private key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey the public key of the sender + * @param psk the a pre-shared secret key + * @param psk_id the id of the pre-shared secret key + * @throws NullPointerException if psk or psk_id are null + * @throws InvalidKeyException if either recipientKey or senderKey are null + * or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextRecipient has already been initialised + */ + public void init(byte[] encapsulated, PrivateKey recipientKey, byte[] info, PublicKey senderKey, + byte[] psk, byte[] psk_id) throws InvalidKeyException { + if (senderKey == null) { + throw new InvalidKeyException("null sender key"); + } + // Remaining argument checks are performed by the SPI + spi.engineInitRecipient(encapsulated, recipientKey, info, senderKey, psk, psk_id); } } diff --git a/common/src/main/java/org/conscrypt/HpkeContextSender.java b/common/src/main/java/org/conscrypt/HpkeContextSender.java index e3cca2ecc..abde31d03 100644 --- a/common/src/main/java/org/conscrypt/HpkeContextSender.java +++ b/common/src/main/java/org/conscrypt/HpkeContextSender.java @@ -16,100 +16,183 @@ package org.conscrypt; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.Provider; import java.security.PublicKey; -import org.conscrypt.NativeRef.EVP_HPKE_CTX; /** - * Hybrid Public Key Encryption (HPKE) Sender APIs. + * Hybrid Public Key Encryption (HPKE) sender APIs. * * @see HPKE RFC 9180 + * + * Sender subclass of HpkeContext. See base class for details. */ -public class HpkeContextSender { - private final HpkeSuite hpkeSuite; - private EVP_HPKE_CTX ctx; - private byte[] enc; +public class HpkeContextSender extends HpkeContext { + private HpkeContextSender(HpkeSpi spi) { + super(spi); + } - private HpkeContextSender(HpkeSuite hpkeSuite) { - this.hpkeSuite = hpkeSuite; + /** + * Returns the encapsulated key created for this HpkeContextSender. + * + * @return the encapsulated key + * @throws IllegalStateException if this HpkeContextSender has not been initialised. + */ + public byte[] getEncapsulated() { + return spi.getEncapsulated(); } /** - * Initializes the internal HPKE context for the sender using BASE (0x00) mode. Once initialized - * the encapsulated key is created which can be accessed through the {@link #getEnc()} method. + * Seals a message, using the internal key schedule maintained by this HpkeContextSender. * - * @param publicKey public key matching the KEM public key size - * @param info optional application-supplied information - * @return encapsulated key - * @throws IllegalArgumentException if providing a public key that does not match the size - * expectation - * @throws IllegalStateException if an issue is encountered while setting up the sender - * (an issue could occur most likely if the keys configured are - * not valid) + * @param plaintext the plaintext + * @param aad optional associated data, may be null or empty + * @return the ciphertext + * @throws NullPointerException if the plaintext is null + * @throws IllegalStateException if this HpkeContextSender has not been initialised */ - public static HpkeContextSender setupBase( - HpkeSuite hpkeSuite, PublicKey publicKey, byte[] info) { - return setupBase(new HpkeContextSenderHelper(), hpkeSuite, publicKey, info); + public byte[] seal(byte[] plaintext, byte[] aad) { + return spi.engineSeal(plaintext, aad); } - @Internal - static HpkeContextSender setupBaseForTesting(HpkeContextSenderHelper contextHelper, - HpkeSuite hpkeSuite, PublicKey publicKey, byte[] info) { - Preconditions.checkNotNull(contextHelper, "hpkeContextSenderHelper"); - return setupBase(contextHelper, hpkeSuite, publicKey, info); + /** + * Returns an uninitialised HpkeContextSender. + * + * @param suite the HPKE suite to use. @see {@link HpkeSuite} for details. + * @return an uninitialised HpkeContextSender for the requested suite + * @throws NoSuchAlgorithmException if no implementation could be found + */ + public static HpkeContextSender getInstance(String suite) throws NoSuchAlgorithmException { + return new HpkeContextSender(findSpi(suite)); + } + + /** + * Returns an uninitialised HpkeContextSender from a specific {@link Provider} + * + * @param suite the HPKE suite to use. @see {@link HpkeSuite} for details. + * @param providerName the name of the Provider to use + * @return an uninitialised HpkeContextSender for the requested suite + * @throws NoSuchAlgorithmException if no implementation could be found + * @throws NoSuchProviderException if providerName is null or no such Provider exists + */ + public static HpkeContextSender getInstance(String suite, String providerName) + throws NoSuchAlgorithmException, NoSuchProviderException { + return new HpkeContextSender(findSpi(suite, providerName)); + } + + /** + * Returns an uninitialised HpkeContextSender from a specific {@link Provider} + * + * @param suite the HPKE suite to use. @see {@link HpkeSuite} for details. + * @param provider the Provider to use + * @return an uninitialised HpkeContextSender for the requested suite + * @throws NoSuchAlgorithmException if no implementation could be found + * @throws NoSuchProviderException if provider is null + */ + public static HpkeContextSender getInstance(String suite, Provider provider) + throws NoSuchAlgorithmException, NoSuchProviderException { + return new HpkeContextSender(findSpi(suite, provider)); + } + + /** + * Initialises this HpkeContextSender in BASE mode, i.e. with no sender authentication. + * + * @param recipientKey public key of the recipient + * @param info additional application-supplied information, may be null or empty + * @throws InvalidKeyException if recipientKey is null or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextSender has already been initialised + */ + public void init(PublicKey recipientKey, byte[] info) throws InvalidKeyException { + spi.engineInitSender( + recipientKey, info, null, HpkeSpi.DEFAULT_PSK, HpkeSpi.DEFAULT_PSK_ID); } - private static HpkeContextSender setupBase(HpkeContextSenderHelper contextHelper, - HpkeSuite hpkeSuite, PublicKey publicKey, byte[] info) { - Preconditions.checkNotNull(hpkeSuite, "hpkeSuite"); - final byte[] pk = hpkeSuite.getKem().validatePublicKeyTypeAndGetRawKey(publicKey); - try { - final Object[] result = contextHelper.setupBase(hpkeSuite.getKem().getId(), - hpkeSuite.getKdf().getId(), hpkeSuite.getAead().getId(), pk, info); - final HpkeContextSender ctxSender = new HpkeContextSender(hpkeSuite); - ctxSender.ctx = (EVP_HPKE_CTX) result[0]; - ctxSender.enc = (byte[]) result[1]; - return ctxSender; - } catch (Exception e) { - throw new IllegalStateException( - "Error while setting up base sender with the keys provided", e); + /** + * Initialises this HpkeContextSender in AUTH mode, i.e. messages are authenticated using + * the sender's public key. + * + * @param recipientKey public key of the recipient + * @param info additional application-supplied information, may be null or empty + * @param senderKey private key of the sender + * @throws InvalidKeyException if either recipientKey or senderKey are null + * or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextSender has already been initialised + */ + public void init(PublicKey recipientKey, byte[] info, PrivateKey senderKey) + throws InvalidKeyException { + if (senderKey == null) { + throw new InvalidKeyException("Sender private key is null"); } + // Remaining argument checks are performed by the SPI + spi.engineInitSender( + recipientKey, info, senderKey, HpkeSpi.DEFAULT_PSK, HpkeSpi.DEFAULT_PSK_ID); } /** - * Returns the enc (encapsulated key) that was created during the initialization/setup phase. + * Initialises this HpkeContextSender in PSK mode, i.e. messages are authenticated using + * a pre-shared secret key. * - * @return enc (encapsulated key) + * @param recipientKey public key of the recipient + * @param info additional application-supplied information, may be null or empty + * @param psk the a pre-shared secret key + * @param psk_id the id of the pre-shared secret key + * @throws NullPointerException if psk or psk_id are null + * @throws InvalidKeyException if recipientKey is null or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextSender has already been initialised */ - public byte[] getEnc() { - return enc; + public void init(PublicKey recipientKey, byte[] info, byte[] psk, byte[] psk_id) + throws InvalidKeyException { + spi.engineInitSender(recipientKey, info, null, psk, psk_id); } /** - * Hybrid Public Key Encryption (HPKE) encryption. - *

- * Note: This API keeps track of its state. It maintains an internal HPKE context. As a result, - * to encrypt multiple messages that are expected to be decrypted using the same context, one - * must call this method for each of the messages. + * Initialises this HpkeContextSender in PSK_AUTH mode, i.e. messages are authenticated using + * both the sender's public key and a pre-shared secret key. * - * @param plaintext message that will be encrypted - * @param aad optional associated data - * @return ciphertext + * @param recipientKey public key of the recipient + * @param info additional application-supplied information, may be null or empty + * @param senderKey private key of the sender + * @param psk the a pre-shared secret key + * @param psk_id the id of the pre-shared secret key + * @throws NullPointerException if psk or psk_id are null + * @throws InvalidKeyException if either recipientKey or senderKey are null + * or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextSender has already been initialised */ - public byte[] seal(byte[] plaintext, byte[] aad) { - Preconditions.checkNotNull(plaintext, "plaintext"); - return NativeCrypto.EVP_HPKE_CTX_seal(ctx, plaintext, aad); + public void init(PublicKey recipientKey, byte[] info, PrivateKey senderKey, + byte[] psk, byte[] psk_id) throws InvalidKeyException { + if (senderKey == null) { + throw new InvalidKeyException("Sender private key is null"); + } + // Remaining argument checks are performed by the SPI + spi.engineInitSender(recipientKey, info, senderKey, psk, psk_id); } /** - * Hybrid Public Key Encryption (HPKE) secret export. + * Initialises this HpkeContextSender for testing in BASE mode ONLY. * - * @param length expected output length - * @param exporterContext optional exporter context - * @return exported value - * @throws IllegalArgumentException if the length is not valid based on the KDF spec + * @param recipientKey public key of the recipient + * @param info additional application-supplied information, may be null or empty + * @param sKe random seed to use during testing + * @throws InvalidKeyException if recipientKey is null or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this HpkeContextSender has already been initialised + * @throws IllegalArgumentException if sKe is null */ - public byte[] export(int length, byte[] exporterContext) { - hpkeSuite.getKdf().validateExportLength(length); - return NativeCrypto.EVP_HPKE_CTX_export(ctx, exporterContext, length); + @Internal + public void initForTesting(PublicKey recipientKey, byte[] info, byte[] sKe) + throws InvalidKeyException { + if (sKe == null) { + throw new IllegalArgumentException("null seed"); + } + spi.engineInitSenderForTesting( + recipientKey, info, null, HpkeSpi.DEFAULT_PSK, HpkeSpi.DEFAULT_PSK, sKe); } } diff --git a/common/src/main/java/org/conscrypt/HpkeContextSenderHelper.java b/common/src/main/java/org/conscrypt/HpkeContextSenderHelper.java deleted file mode 100644 index 6f7058cc7..000000000 --- a/common/src/main/java/org/conscrypt/HpkeContextSenderHelper.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2023 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; - -/** - * Separating {@link HpkeContextSender} logic for testing purposes. Testing could override these - * functions to call different implementations meant for testing as well. - */ -@Internal -public class HpkeContextSenderHelper { - /** - * Setup sender with base mode. Delegates the setup work to BoringSSL. - * - * @param kem kem decimal value representation, {@link HpkeSuite.KEM#getId()} - * @param kdf kdf decimal value representation, {@link HpkeSuite.KDF#getId()} - * @param aead aead decimal value representation, {@link HpkeSuite.AEAD#getId()} - * @param publicKey encoded public key value - * @param info optional application-supplied information - * @return object array with 2 elements, the HPKE context [0] and the encapsulated key [1] - */ - @Internal - public Object[] setupBase(int kem, int kdf, int aead, byte[] publicKey, byte[] info) { - Preconditions.checkNotNull(publicKey, "publicKey"); - return NativeCrypto.EVP_HPKE_CTX_setup_sender(kem, kdf, aead, publicKey, info); - } -} diff --git a/common/src/main/java/org/conscrypt/HpkeDecryptException.java b/common/src/main/java/org/conscrypt/HpkeDecryptException.java new file mode 100644 index 000000000..7e9784cd5 --- /dev/null +++ b/common/src/main/java/org/conscrypt/HpkeDecryptException.java @@ -0,0 +1,11 @@ +package org.conscrypt; + +import java.security.GeneralSecurityException; + +public class HpkeDecryptException extends GeneralSecurityException { + private static final long serialVersionUID = 5903211285098828754L; + + public HpkeDecryptException(String msg) { + super(msg); + } +} diff --git a/common/src/main/java/org/conscrypt/HpkeImpl.java b/common/src/main/java/org/conscrypt/HpkeImpl.java new file mode 100644 index 000000000..406324836 --- /dev/null +++ b/common/src/main/java/org/conscrypt/HpkeImpl.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2023 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 org.conscrypt.HpkeSuite.AEAD_AES_128_GCM; +import static org.conscrypt.HpkeSuite.AEAD_AES_256_GCM; +import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305; +import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256; +import static org.conscrypt.HpkeSuite.KEM_DHKEM_X25519_HKDF_SHA256; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Objects; + +import javax.crypto.BadPaddingException; + +/** + * Implementation of {@link HpkeSpi}. Should not be used directly, but rather by one + * of the subclasses of {@link HpkeContext}. + */ +public class HpkeImpl implements HpkeSpi { + private final HpkeSuite hpkeSuite; + + private NativeRef.EVP_HPKE_CTX ctx; + private byte[] encapsulated = null; + + public HpkeImpl(HpkeSuite hpkeSuite) { + this.hpkeSuite = hpkeSuite; + } + + @Override + public void engineInitSender(PublicKey recipientKey, byte[] info, PrivateKey senderKey, + byte[] psk, byte[] psk_id) throws InvalidKeyException { + checkNotInitialised(); + checkArgumentsForBaseModeOnly(senderKey, psk, psk_id); + final byte[] pk = hpkeSuite.getKem().validatePublicKeyTypeAndGetRawKey(recipientKey); + + final Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender( + hpkeSuite, pk, info); + ctx = (NativeRef.EVP_HPKE_CTX) result[0]; + encapsulated = (byte[]) result[1]; + } + + @Override + public void engineInitSenderForTesting(PublicKey recipientKey, byte[] info, + PrivateKey senderKey, byte[] psk, byte[] psk_id, byte[] sKe) throws InvalidKeyException { + checkNotInitialised(); + Objects.requireNonNull(sKe); + checkArgumentsForBaseModeOnly(senderKey, psk, psk_id); + final byte[] pk = hpkeSuite.getKem().validatePublicKeyTypeAndGetRawKey(recipientKey); + + final Object[] result = NativeCrypto.EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing( + hpkeSuite, pk, info, sKe); + ctx = (NativeRef.EVP_HPKE_CTX) result[0]; + encapsulated = (byte[]) result[1]; + } + + @Override + public void engineInitRecipient(byte[] encapsulated, PrivateKey recipientKey, + byte[] info, PublicKey senderKey, byte[] psk, byte[] psk_id) throws InvalidKeyException { + checkNotInitialised(); + checkArgumentsForBaseModeOnly(senderKey, psk, psk_id); + hpkeSuite.getKem().validateEncapsulatedLength(encapsulated); + final byte[] sk = hpkeSuite.getKem().validatePrivateKeyTypeAndGetRawKey(recipientKey); + + ctx = (NativeRef.EVP_HPKE_CTX) NativeCrypto.EVP_HPKE_CTX_setup_base_mode_recipient( + hpkeSuite, sk, encapsulated, info); + } + + private void checkArgumentsForBaseModeOnly(Key senderKey, byte[] psk, byte[] psk_id) { + if (senderKey != null) { + throw new UnsupportedOperationException("Asymmetric authentication not supported"); + } + // PSK args can only be null if the application passed them in. + Objects.requireNonNull(psk); + Objects.requireNonNull(psk_id); + if (psk.length > 0 || psk_id.length > 0) { + throw new UnsupportedOperationException("PSK authentication not supported"); + } + } + + @Override + public byte[] engineSeal(byte[] plaintext, byte[] aad) { + checkIsSender(); + Preconditions.checkNotNull(plaintext, "null plaintext"); + return NativeCrypto.EVP_HPKE_CTX_seal(ctx, plaintext, aad); + } + + @Override + public byte[] engineExport(int length, byte[] exporterContext) { + checkInitialised(); + hpkeSuite.getKdf().validateExportLength(length); + return NativeCrypto.EVP_HPKE_CTX_export(ctx, exporterContext, length); + } + + @Override + public byte[] engineOpen(byte[] ciphertext, byte[] aad) throws GeneralSecurityException { + checkIsRecipient(); + Preconditions.checkNotNull(ciphertext, "null ciphertext"); + try { + return NativeCrypto.EVP_HPKE_CTX_open(ctx, ciphertext, aad); + } catch (BadPaddingException e) { + throw new HpkeDecryptException(e.getMessage()); + } + } + + private void checkInitialised() { + if (ctx == null) { + throw new IllegalStateException("Not initialised"); + } + } + + private void checkNotInitialised() { + if (ctx != null) { + throw new IllegalStateException("Already initialised"); + } + } + + private void checkIsSender() { + checkInitialised(); + if (encapsulated == null) { + throw new IllegalStateException("Internal error"); + } + } + + private void checkIsRecipient() { + checkInitialised(); + if (encapsulated != null) { + throw new IllegalStateException("Internal error"); + } + } + + @Override + public byte[] getEncapsulated() { + checkIsSender(); + return encapsulated; + } + + public static class X25519_AES_128 extends HpkeImpl { + public X25519_AES_128() { + super(new HpkeSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_128_GCM)); + } + } + + public static class X25519_AES_256 extends HpkeImpl { + public X25519_AES_256() { + super(new HpkeSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_256_GCM)); + } + } + + public static class X25519_CHACHA20 extends HpkeImpl { + public X25519_CHACHA20() { + super(new HpkeSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_CHACHA20POLY1305)); + } + } +} diff --git a/common/src/main/java/org/conscrypt/HpkeSpi.java b/common/src/main/java/org/conscrypt/HpkeSpi.java new file mode 100644 index 000000000..f257ec63c --- /dev/null +++ b/common/src/main/java/org/conscrypt/HpkeSpi.java @@ -0,0 +1,127 @@ +package org.conscrypt; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * SPI for HPKE clients to communicate with implementations. The client API can use any + * implementation which implements this interface, by duck-typing if necessary. + */ +public interface HpkeSpi { + byte[] DEFAULT_PSK = new byte[0]; + byte[] DEFAULT_PSK_ID = DEFAULT_PSK; + + /** + * Initialises an HPKE sender SPI. + * + * @param recipientKey public key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey private key of the sender, for symmetric auth modes only, else null + * @param psk pre-shared key, for PSK auth modes only, else null + * @param psk_id pre-shared key ID, for PSK auth modes only, else null + * @throws InvalidKeyException if recipientKey is null or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this SPI has already been initialised + */ + void engineInitSender( + PublicKey recipientKey, + byte[] info, + PrivateKey senderKey, + byte[] psk, + byte[] psk_id) + throws InvalidKeyException; + + /** + * Initialises an HPKE sender SPI. + * + * @param recipientKey public key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey private key of the sender, for symmetric auth modes only, else null + * @param psk pre-shared key, for PSK auth modes only, else null + * @param psk_id pre-shared key ID, for PSK auth modes only, else null + * @param sKe optional random seed, should be null for all uses except for validation against + * known test vectors + * @throws InvalidKeyException if recipientKey is null or an unsupported key format or senderKey + * is an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this SPI has already been initialised + */ + void engineInitSenderForTesting( + PublicKey recipientKey, + byte[] info, + PrivateKey senderKey, + byte[] psk, + byte[] psk_id, + byte[] sKe) + throws InvalidKeyException; + + /** + * Initialises an HPKE recipient SPI. + * + * @param encapsulated encapsulated ephemeral key from a sender + * @param recipientKey private key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey public key of sender, for asymmetric auth modes only, else null + * @param psk pre-shared key, for PSK auth modes only, else null + * @param psk_id pre-shared key ID, for PSK auth modes only, else null + * @throws InvalidKeyException if recipientKey is null or an unsupported key format or senderKey + * is an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this SPI has already been initialised + */ + void engineInitRecipient( + byte[] encapsulated, + PrivateKey recipientKey, + byte[] info, + PublicKey senderKey, + byte[] psk, + byte[] psk_id) + throws InvalidKeyException; + + /** + * Seals a message, using the internal key schedule maintained by an HPKE sender. + * + * @param plaintext the plaintext + * @param aad optional associated data, may be null or empty + * @return the ciphertext + * @throws NullPointerException if the plaintext is null + * @throws IllegalStateException if this SPI has not been initialised or if it was initialised + * as a recipient + */ + byte[] engineSeal(byte[] plaintext, byte[] aad); + + /** + * Opens a message, using the internal key schedule maintained by an HPKE recipient. + * + * @param ciphertext the ciphertext + * @param aad optional associated data, may be null or empty + * @return the plaintext + * @throws IllegalStateException if this SPI has not been initialised or if it was initialised + * as a sender + * @throws GeneralSecurityException on decryption failures + */ + byte[] engineOpen(byte[] ciphertext, byte[] aad) throws GeneralSecurityException; + + /** + * Exports secret key material from this SPI as described in RFC 9180. + * + * @param length expected output length + * @param context optional context string, may be null or empty + * @return exported value + * @throws IllegalArgumentException if the length is not valid for the KDF in use + * @throws IllegalStateException if this SPI has not been initialised + * + */ + byte[] engineExport(int length, byte[] context); + + /** + * Returns the encapsulated key material for an HPKE sender. + * + * @return the key material + * @throws IllegalStateException if this SPI has not been initialised or if it was initialised + * as a recipient + */ + byte[] getEncapsulated(); +} diff --git a/common/src/main/java/org/conscrypt/HpkeSuite.java b/common/src/main/java/org/conscrypt/HpkeSuite.java index 4fa0abc70..a2d8c6660 100644 --- a/common/src/main/java/org/conscrypt/HpkeSuite.java +++ b/common/src/main/java/org/conscrypt/HpkeSuite.java @@ -16,6 +16,7 @@ package org.conscrypt; +import java.security.InvalidKeyException; import java.security.PrivateKey; import java.security.PublicKey; @@ -68,6 +69,18 @@ public HpkeSuite(int kem, int kdf, int aead) { mAead = convertAead(aead); } + public HpkeSuite(KEM kem, KDF kdf, AEAD aead) { + mKem = kem; + mKdf = kdf; + mAead = aead; + } + + + public String name() { + return String.format("%s/%s/%s", + mKem.name(), mKdf.name(), mAead.name()); + } + /** * KEM configured while creating an instance of {@link HpkeSuite} * @@ -173,19 +186,19 @@ int getEncLength() { } /** - * Validates the enc size in bytes matches the {@link KEM} spec. + * Validates the encapsulated size in bytes matches the {@link KEM} spec. * - * @param enc encapsulated key produced by the kem - * @see expected - * enc size + * @param encapsulated encapsulated key produced by the kem + * @see + * expected enc size */ - void validateEncLength(byte[] enc) { - Preconditions.checkNotNull(enc, "enc"); + void validateEncapsulatedLength(byte[] encapsulated) throws InvalidKeyException { + Preconditions.checkNotNull(encapsulated, "encapsulated"); final int expectedLength = this.getEncLength(); - if (enc.length != expectedLength) { - throw new IllegalArgumentException( - "Expected enc length of " + expectedLength + ", but was " + enc.length); + if (encapsulated.length != expectedLength) { + throw new InvalidKeyException( + "Expected encapsulated length of " + expectedLength + ", but was " + + encapsulated.length); } } @@ -198,14 +211,16 @@ void validateEncLength(byte[] enc) { * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-algorithm-identifiers">expected * pk size */ - byte[] validatePublicKeyTypeAndGetRawKey(PublicKey publicKey) { - Preconditions.checkNotNull(publicKey, "publicKey"); - if (!(publicKey instanceof OpenSSLX25519PublicKey)) { - throw new IllegalArgumentException( - "Public key algorithm " + publicKey.getAlgorithm() + " is not supported"); + byte[] validatePublicKeyTypeAndGetRawKey(PublicKey publicKey) throws InvalidKeyException { + String error; + if (publicKey == null) { + error = "null public key"; + } else if (!(publicKey instanceof OpenSSLX25519PublicKey)) { + error = "Public key algorithm " + publicKey.getAlgorithm() + " is not supported"; + } else { + return ((OpenSSLX25519PublicKey) publicKey).getU(); } - - return ((OpenSSLX25519PublicKey) publicKey).getU(); + throw new InvalidKeyException(error); } /** @@ -217,14 +232,17 @@ byte[] validatePublicKeyTypeAndGetRawKey(PublicKey publicKey) { * href="https://www.rfc-editor.org/rfc/rfc9180.html#name-algorithm-identifiers">expected * sk size */ - byte[] validatePrivateKeyTypeAndGetRawKey(PrivateKey privateKey) { - Preconditions.checkNotNull(privateKey, "privateKey"); - if (!(privateKey instanceof OpenSSLX25519PrivateKey)) { - throw new IllegalArgumentException( - "Private key algorithm " + privateKey.getAlgorithm() + " is not supported"); + byte[] validatePrivateKeyTypeAndGetRawKey(PrivateKey privateKey) + throws InvalidKeyException { + String error; + if (privateKey == null) { + error = "null private key"; + } else if (!(privateKey instanceof OpenSSLX25519PrivateKey)) { + error = "Private key algorithm " + privateKey.getAlgorithm() + " is not supported"; + } else { + return ((OpenSSLX25519PrivateKey) privateKey).getU(); } - - return ((OpenSSLX25519PrivateKey) privateKey).getU(); + throw new InvalidKeyException(error); } } diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index e42b166fb..5f933893c 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -391,20 +391,40 @@ static native byte[] EVP_HPKE_CTX_export( static native void EVP_HPKE_CTX_free(long ctx); static native byte[] EVP_HPKE_CTX_open( - NativeRef.EVP_HPKE_CTX ctx, byte[] ciphertext, byte[] aad); + NativeRef.EVP_HPKE_CTX ctx, byte[] ciphertext, byte[] aad) throws BadPaddingException; static native byte[] EVP_HPKE_CTX_seal( NativeRef.EVP_HPKE_CTX ctx, byte[] plaintext, byte[] aad); - static native Object EVP_HPKE_CTX_setup_recipient( + static native Object EVP_HPKE_CTX_setup_base_mode_recipient( int kem, int kdf, int aead, byte[] privateKey, byte[] enc, byte[] info); - static native Object[] EVP_HPKE_CTX_setup_sender( + static Object EVP_HPKE_CTX_setup_base_mode_recipient( + HpkeSuite suite, byte[] privateKey, byte[] enc, byte[] info) { + return EVP_HPKE_CTX_setup_base_mode_recipient( + suite.getKem().getId(), suite.getKdf().getId(), suite.getAead().getId(), + privateKey, enc, info); + } + + static native Object[] EVP_HPKE_CTX_setup_base_mode_sender( int kem, int kdf, int aead, byte[] publicKey, byte[] info); - static native Object[] EVP_HPKE_CTX_setup_sender_with_seed_for_testing( + static Object[] EVP_HPKE_CTX_setup_base_mode_sender( + HpkeSuite suite, byte[] publicKey, byte[] info) { + return EVP_HPKE_CTX_setup_base_mode_sender( + suite.getKem().getId(), suite.getKdf().getId(), suite.getAead().getId(), + publicKey, info); + } + static native Object[] EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing( int kem, int kdf, int aead, byte[] publicKey, byte[] info, byte[] seed); + static Object[] EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing( + HpkeSuite suite, byte[] publicKey, byte[] info, byte[] seed) { + return EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing( + suite.getKem().getId(), suite.getKdf().getId(), suite.getAead().getId(), + publicKey, info, seed); + } + // --- RAND ---------------------------------------------------------------- static native void RAND_bytes(byte[] output); diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index bb02f94be..6fc30ff9c 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -515,9 +515,16 @@ public OpenSSLProvider(String providerName) { putMacImplClass("AESCMAC", "OpenSSLMac$AesCmac"); /* === Certificate === */ - put("CertificateFactory.X509", PREFIX + "OpenSSLX509CertificateFactory"); put("Alg.Alias.CertificateFactory.X.509", "X509"); + + /* === HPKE - Conscrypt internal only === */ + put("ConscryptHpke.DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM", + PREFIX + "HpkeImpl$X25519_AES_128"); + put("ConscryptHpke.DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_256_GCM", + PREFIX + "HpkeImpl$X25519_AES_256"); + put("ConscryptHpke.DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305", + PREFIX + "HpkeImpl$X25519_CHACHA20"); } private void putMacImplClass(String algorithm, String className) { diff --git a/common/src/test/java/org/conscrypt/DuckTypedHpkeSpiTest.java b/common/src/test/java/org/conscrypt/DuckTypedHpkeSpiTest.java new file mode 100644 index 000000000..6ab538bd0 --- /dev/null +++ b/common/src/test/java/org/conscrypt/DuckTypedHpkeSpiTest.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2023 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 org.conscrypt.HpkeFixture.DEFAULT_ENC; +import static org.conscrypt.HpkeFixture.DEFAULT_EXPORTER_CONTEXT; +import static org.conscrypt.HpkeFixture.DEFAULT_EXPORTER_LENGTH; +import static org.conscrypt.HpkeFixture.DEFAULT_PT; +import static org.conscrypt.HpkeFixture.createDefaultHpkeContextRecipient; +import static org.conscrypt.HpkeFixture.createDefaultHpkeContextSender; +import static org.conscrypt.HpkeTestVectorsTest.getHpkeEncryptionRecords; +import static org.conscrypt.TestUtils.encodeHex; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Security; +import java.util.List; + +import org.conscrypt.HpkeTestVectorsTest.HpkeData; +import org.conscrypt.HpkeTestVectorsTest.HpkeEncryptionData; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Tests for DuckTypedHpkeSpiTest. Essentially the same as the tests for HpkeContext but + * with a "foreign" HPKE Provider inserted ahead of Conscrypt. That is, one which returns + * SPI instances with all the correct methods but which don't inherit directly from "our" + * HpkeSpi. + */ +@RunWith(JUnit4.class) +public class DuckTypedHpkeSpiTest { + private static final Provider conscryptProvider = TestUtils.getConscryptProvider(); + + @Before + public void before() { + Security.insertProviderAt(new ForeignHpkeProvider(), 1); + } + + @After + public void after() { + Security.removeProvider(ForeignHpkeProvider.NAME); + } + + // Copied from HpkeContextTest but with extra checks to ensure we are operating on + // duck typed instances. + @Test + public void sealOpen() throws Exception { + final HpkeContextSender ctxSender1 = createDefaultHpkeContextSender(); + assertForeign(ctxSender1); + final byte[] enc1 = ctxSender1.getEncapsulated(); + final byte[] ciphertext1 = ctxSender1.seal(DEFAULT_PT, /* aad= */ null); + + final HpkeContextSender ctxSender2 = createDefaultHpkeContextSender(); + assertForeign(ctxSender2); + final byte[] enc2 = ctxSender2.getEncapsulated(); + final byte[] ciphertext2 = ctxSender2.seal(DEFAULT_PT, /* aad= */ null); + + assertNotNull(enc1); + assertNotNull(ciphertext1); + assertNotNull(enc2); + assertNotNull(ciphertext2); + assertNotEquals(encodeHex(enc1), encodeHex(enc2)); + assertNotEquals(encodeHex(DEFAULT_PT), encodeHex(ciphertext1)); + assertNotEquals(encodeHex(ciphertext1), encodeHex(ciphertext2)); + + final HpkeContextRecipient ctxRecipient1 = createDefaultHpkeContextRecipient(enc1); + assertForeign(ctxRecipient1); + byte[] plaintext1 = ctxRecipient1.open(ciphertext1, /* aad= */ null); + + final HpkeContextRecipient ctxRecipient2 = createDefaultHpkeContextRecipient(enc2); + assertForeign(ctxRecipient2); + byte[] plaintext2 = ctxRecipient2.open(ciphertext2, /* aad= */ null); + + assertNotNull(plaintext1); + assertNotNull(plaintext2); + assertArrayEquals(DEFAULT_PT, plaintext1); + assertArrayEquals(DEFAULT_PT, plaintext2); + } + + // Copied from HpkeContextTest but with extra checks to ensure we are operating on + // duck typed instances. + @Test + public void export() throws Exception { + final HpkeContextSender ctxSender = createDefaultHpkeContextSender(); + assertForeign(ctxSender); + final byte[] enc = ctxSender.getEncapsulated(); + final byte[] export1 = ctxSender.export(DEFAULT_EXPORTER_LENGTH, DEFAULT_EXPORTER_CONTEXT); + + final HpkeContextRecipient ctxRecipient = createDefaultHpkeContextRecipient(DEFAULT_ENC); + assertForeign(ctxRecipient); + final byte[] export2 = + ctxRecipient.export(DEFAULT_EXPORTER_LENGTH, DEFAULT_EXPORTER_CONTEXT); + + assertNotNull(enc); + assertNotNull(export1); + assertEquals(DEFAULT_EXPORTER_LENGTH, export1.length); + assertNotNull(export2); + assertEquals(DEFAULT_EXPORTER_LENGTH, export2.length); + assertNotEquals(encodeHex(DEFAULT_ENC), encodeHex(enc)); + assertNotEquals(encodeHex(export1), encodeHex(export2)); + } + + @Test + public void vectors() throws Exception { + final List records = getHpkeEncryptionRecords(); + for (HpkeData record : records) { + testHpkeEncryption(record); + } + } + + // Copied from HpkeTestVectorsTest but with extra checks to ensure we are operating on + // duck typed instances. + private void testHpkeEncryption(HpkeData record) throws Exception { + final byte[] enc = record.pkEm; + + // Encryption + final HpkeContextSender contextSender = + setupBaseForTesting(record.hpkeSuite, record.pkRm, record.info, record.skEm); + assertForeign(contextSender); + final byte[] encResult = contextSender.getEncapsulated(); + assertArrayEquals("Failed encryption 'enc' " + encodeHex(enc), enc, encResult); + for (HpkeEncryptionData encryption : record.encryptions) { + final byte[] ciphertext = contextSender.seal(encryption.pt, encryption.aad); + assertArrayEquals("Failed encryption 'ciphertext' on data : " + encryption, + encryption.ct, ciphertext); + } + + // Decryption + final HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(record.hpkeSuite.name()); + assertForeign(contextRecipient); + contextRecipient.init(enc, record.skRm, record.info); + for (HpkeEncryptionData encryption : record.encryptions) { + final byte[] plaintext = contextRecipient.open(encryption.ct, encryption.aad); + assertArrayEquals( + "Failed decryption on data : " + encryption, encryption.pt, plaintext); + } + } + + private HpkeContextSender setupBaseForTesting( + HpkeSuite suite, PublicKey publicKey, byte[] info, byte[] sKem) throws Exception { + String algorithm = suite.name(); + HpkeContextSender sender = HpkeContextSender.getInstance(algorithm); + sender.initForTesting(publicKey, info, sKem); + return sender; + } + + // Asserts that an HpkeContext is duck-typed and configured as we expect it. + private static void assertForeign(HpkeContext context) { + // Context's SPI should be duck typed, because the foreign Provider returns instances + // which use HpkeForeignSpi which *doesn't* implement HpkeSpi. + assertTrue(context.getSpi() instanceof DuckTypedHpkeSpi); + DuckTypedHpkeSpi duckTyped = (DuckTypedHpkeSpi) context.getSpi(); + + // Verify the SPI is indeed foreign. + assertTrue(duckTyped.getDelegate() instanceof HpkeForeignSpi); + + // And that it is delegating to a real HpkeImpl, so we can test it. + HpkeForeignSpi foreign = (HpkeForeignSpi) duckTyped.getDelegate(); + assertTrue(foreign.realSpi instanceof HpkeImpl); + } + + // Provides HpkeContext instances that use a "foreign" SPI, that is one that isn't + // know to inherit HpkeSpi but implements the same methods and so can be used via + // duck typing. + // + // Some complexity here: We want to test end-to-end, so the foreign SPI delegates to a + // real Conscrypt SPI for its implementation so there are two levels of delegation. That is: + // * ForeignHpkeProvider provides instances of HpkeForeignSpi. This *doesn't* inherit from + // HpkeSpi and so is representative of the case where the SPI comes from a different + // Conscrypt variant or indeed some other provider. HpkeContext created a + // DuckTypedHpkeSpi to wrap this SPI and that is what we're testing above. + // * HpkeForeignSpi finds its equivalent SPI from the real Conscrypt Provider and + // delegates all operations to it (by direct method calls not duck typing). This is just + // a test setup quirk so we can test end-to-end. + private static class ForeignHpkeProvider extends Provider { + private static final String NAME = "Foreign_Hpke"; + + protected ForeignHpkeProvider() { + super( NAME, 1.0, "HPKE unit test usage only"); + put("ConscryptHpke.DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM", + HpkeForeignSpi.X25519_AES_128.class.getName()); + put("ConscryptHpke.DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_256_GCM", + HpkeForeignSpi.X25519_AES_256.class.getName()); + put("ConscryptHpke.DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305", + HpkeForeignSpi.X25519_CHACHA20.class.getName()); + } + } + public static class HpkeForeignSpi { + private final HpkeSpi realSpi; + + public HpkeForeignSpi(String hpkeSuite) throws NoSuchAlgorithmException { + + Provider.Service service = + conscryptProvider.getService("ConscryptHpke", hpkeSuite); + assertNotNull(service); + realSpi = (HpkeSpi) service.newInstance(null); + assertNotNull(realSpi); + } + + public void engineInitSender(PublicKey recipientKey, byte[] info, PrivateKey senderKey, + byte[] psk, byte[] psk_id) throws InvalidKeyException { + realSpi.engineInitSender(recipientKey, info, senderKey, psk, psk_id); + } + + public void engineInitSenderForTesting(PublicKey recipientKey, byte[] info, + PrivateKey senderKey, byte[] psk, byte[] psk_id, byte[] sKe) + throws InvalidKeyException { + realSpi.engineInitSenderForTesting(recipientKey, info, senderKey, psk, psk_id, sKe); + } + + public void engineInitRecipient(byte[] enc, PrivateKey recipientKey, byte[] info, + PublicKey senderKey, byte[] psk, byte[] psk_id) throws InvalidKeyException { + realSpi.engineInitRecipient(enc, recipientKey, info, senderKey, psk, psk_id); + } + + public byte[] engineSeal(byte[] plaintext, byte[] aad) { + return realSpi.engineSeal(plaintext, aad); + } + + public byte[] engineExport(int length, byte[] exporterContext) { + return realSpi.engineExport(length, exporterContext); + } + + public byte[] engineOpen(byte[] ciphertext, byte[] aad) throws GeneralSecurityException { + return realSpi.engineOpen(ciphertext, aad); + } + + public byte[] getEncapsulated() { + return realSpi.getEncapsulated(); + } + + public static class X25519_AES_128 extends HpkeForeignSpi { + public X25519_AES_128() throws NoSuchAlgorithmException { + super("DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM"); + } + } + + public static class X25519_AES_256 extends HpkeForeignSpi { + public X25519_AES_256() throws NoSuchAlgorithmException { + super("DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_256_GCM"); + } + } + + public static class X25519_CHACHA20 extends HpkeForeignSpi { + public X25519_CHACHA20() throws NoSuchAlgorithmException { + super("DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/CHACHA20POLY1305"); + } + } + } +} diff --git a/common/src/test/java/org/conscrypt/HpkeContextRecipientTest.java b/common/src/test/java/org/conscrypt/HpkeContextRecipientTest.java index 05a5a2593..b429bb416 100644 --- a/common/src/test/java/org/conscrypt/HpkeContextRecipientTest.java +++ b/common/src/test/java/org/conscrypt/HpkeContextRecipientTest.java @@ -22,10 +22,11 @@ import static org.conscrypt.HpkeFixture.DEFAULT_EXPORTER_CONTEXT; import static org.conscrypt.HpkeFixture.DEFAULT_EXPORTER_LENGTH; import static org.conscrypt.HpkeFixture.DEFAULT_INFO; +import static org.conscrypt.HpkeFixture.DEFAULT_PK; import static org.conscrypt.HpkeFixture.DEFAULT_PT; import static org.conscrypt.HpkeFixture.DEFAULT_SK; +import static org.conscrypt.HpkeFixture.DEFAULT_SUITE_NAME; import static org.conscrypt.HpkeFixture.createDefaultHpkeContextRecipient; -import static org.conscrypt.HpkeFixture.createDefaultHpkeSuite; import static org.conscrypt.HpkeFixture.createPrivateKey; import static org.conscrypt.TestUtils.decodeHex; import static org.junit.Assert.assertArrayEquals; @@ -33,7 +34,14 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.PrivateKey; +import java.security.Provider; + import org.conscrypt.java.security.DefaultKeys; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,57 +50,72 @@ @RunWith(JUnit4.class) public class HpkeContextRecipientTest { @Test - public void testSetupBase_missingSuiteParameter_throwNullException() { - assertThrows(NullPointerException.class, - () - -> HpkeContextRecipient.setupBase( - /* hpkeSuite= */ null, DEFAULT_ENC, createPrivateKey(DEFAULT_SK), - DEFAULT_INFO)); + public void testGetInstance() throws Exception { + assertThrows(NoSuchAlgorithmException.class, + () -> HpkeContextRecipient.getInstance(null)); + assertThrows(NoSuchAlgorithmException.class, + () -> HpkeContextRecipient.getInstance("No/Such/Thing")); + assertThrows(IllegalArgumentException.class, + () -> HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME, (String) null)); + assertThrows(IllegalArgumentException.class, + () -> HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME, (Provider) null)); + assertThrows(NoSuchProviderException.class, + () -> HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME, "NonsenseProviderName")); + HpkeContextRecipient recipient = HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + assertNotNull(recipient); } @Test - public void testSetupBase_missingEncParameter_throwNullException() { - assertThrows(NullPointerException.class, - () - -> HpkeContextRecipient.setupBase(createDefaultHpkeSuite(), /* enc= */ null, - createPrivateKey(DEFAULT_SK), DEFAULT_INFO)); - } + public void testInitBaseMode() throws Exception { + HpkeContextRecipient recipient = HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + final PrivateKey invalidKey = DefaultKeys.getPrivateKey("DH"); + - @Test - public void testSetupBase_missingSkParameter_throwNullException() { assertThrows(NullPointerException.class, - () - -> HpkeContextRecipient.setupBase(createDefaultHpkeSuite(), DEFAULT_ENC, - /* privateKey= */ null, DEFAULT_INFO)); - } + () -> recipient.init(/* enc= */ null, DEFAULT_SK, DEFAULT_INFO)); - @Test - public void testSetupBase_encLengthNotMatchingKemSpec_throwArgumentException() - throws Exception { - final PrivateKey privateKey = createPrivateKey(DEFAULT_SK); - final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () - -> HpkeContextRecipient.setupBase(createDefaultHpkeSuite(), - /* enc= */ new byte[1], privateKey, DEFAULT_INFO)); - assertEquals("Expected enc length of 32, but was 1", e.getMessage()); + assertThrows(InvalidKeyException.class, + () -> recipient.init(DEFAULT_ENC, /* privateKey= */ null, DEFAULT_INFO)); + + // Incorrect enc size + assertThrows(InvalidKeyException.class, + () -> recipient.init(new byte[1], DEFAULT_SK, DEFAULT_INFO)); + + assertThrows(InvalidKeyException.class, + () -> recipient.init(DEFAULT_ENC, invalidKey, DEFAULT_INFO)); + + // Should succeed + recipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO); + + // Can't initialise twice + assertThrows(IllegalStateException.class, + () -> recipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO)); + + HpkeContextRecipient recipient2 = HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + // null is explicitly allowed + recipient2.init(DEFAULT_ENC, DEFAULT_SK, /* info= */ null); } @Test - public void testSetupBase_keyAlgorithmNotSupported_throwArgumentException() throws Exception { - final PrivateKey privateKey = DefaultKeys.getPrivateKey("DH"); - final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () - -> HpkeContextRecipient.setupBase( - createDefaultHpkeSuite(), DEFAULT_ENC, privateKey, DEFAULT_INFO)); - assertEquals("Private key algorithm DH is not supported", e.getMessage()); + public void testInitUnsupportedModes() throws Exception { + HpkeContextRecipient recipient = HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + byte[] psk = "Shhh! Secret!".getBytes(StandardCharsets.UTF_8); + byte[] psk_id = "id".getBytes(StandardCharsets.UTF_8); + + assertThrows(UnsupportedOperationException.class, () -> + recipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO, DEFAULT_PK)); + assertThrows(UnsupportedOperationException.class, () -> + recipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO, psk, psk_id)); + assertThrows(UnsupportedOperationException.class, () -> + recipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO, DEFAULT_PK, psk, psk_id)); } @Test public void testOpen_successfully() throws Exception { final HpkeSuite suite = new HpkeSuite(HpkeSuite.KEM_DHKEM_X25519_HKDF_SHA256, HpkeSuite.KDF_HKDF_SHA256, HpkeSuite.AEAD_AES_128_GCM); - final HpkeContextRecipient ctxRecipient = HpkeContextRecipient.setupBase( - suite, DEFAULT_ENC, createPrivateKey(DEFAULT_SK), DEFAULT_INFO); + final HpkeContextRecipient ctxRecipient = HpkeContextRecipient.getInstance(suite.name()); + ctxRecipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO); byte[] plaintext = ctxRecipient.open(DEFAULT_CT, DEFAULT_AAD); assertNotNull(plaintext); assertArrayEquals(DEFAULT_PT, plaintext); @@ -100,9 +123,10 @@ public void testOpen_successfully() throws Exception { @Test public void testOpen_missingRequiredParameters_throwNullException() throws Exception { - final PrivateKey privateKey = createPrivateKey(DEFAULT_SK); - final HpkeContextRecipient ctxRecipient = HpkeContextRecipient.setupBase( - createDefaultHpkeSuite(), DEFAULT_ENC, privateKey, DEFAULT_INFO); + final HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + ctxRecipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO); + assertThrows(NullPointerException.class, () -> ctxRecipient.open(/* ciphertext= */ null, DEFAULT_AAD)); } @@ -111,27 +135,31 @@ public void testOpen_missingRequiredParameters_throwNullException() throws Excep public void testOpen_validSkButNotTheRightOne_throwStateException() throws Exception { final PrivateKey privateKey = createPrivateKey( decodeHex("497b4502664cfea5d5af0b39934dac72242a74f8480451e1aee7d6a53320333d")); - final HpkeContextRecipient ctxRecipient = HpkeContextRecipient.setupBase( - createDefaultHpkeSuite(), DEFAULT_ENC, privateKey, DEFAULT_INFO); - assertThrows(IllegalStateException.class, () -> ctxRecipient.open(DEFAULT_CT, DEFAULT_AAD)); + final HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + ctxRecipient.init(DEFAULT_ENC, privateKey, DEFAULT_INFO); + assertThrows(GeneralSecurityException.class, + () -> ctxRecipient.open(DEFAULT_CT, DEFAULT_AAD)); } @Test public void testOpen_validSkButWrongEnc_throwStateException() throws Exception { - final PrivateKey privateKey = createPrivateKey(DEFAULT_SK); final byte[] enc = decodeHex("6c93e09869df3402d7bf231bf540fadd35cd56be14f97178f0954db94b7fc256"); - final HpkeContextRecipient ctxRecipient = HpkeContextRecipient.setupBase( - createDefaultHpkeSuite(), enc, privateKey, DEFAULT_INFO); - assertThrows(IllegalStateException.class, () -> ctxRecipient.open(DEFAULT_CT, DEFAULT_AAD)); + final HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + ctxRecipient.init(enc, DEFAULT_SK, DEFAULT_INFO); + + assertThrows(GeneralSecurityException.class, () -> ctxRecipient.open(DEFAULT_CT, DEFAULT_AAD)); } @Test public void testOpen_invalidCiphertext_throwStateException() throws Exception { - final PrivateKey privateKey = createPrivateKey(DEFAULT_SK); - final HpkeContextRecipient ctxRecipient = HpkeContextRecipient.setupBase( - createDefaultHpkeSuite(), DEFAULT_ENC, privateKey, DEFAULT_INFO); - assertThrows(IllegalStateException.class, + final HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + ctxRecipient.init(DEFAULT_ENC, DEFAULT_SK, DEFAULT_INFO); + + assertThrows(GeneralSecurityException.class, () -> ctxRecipient.open(/* ct= */ new byte[32], DEFAULT_AAD)); } diff --git a/common/src/test/java/org/conscrypt/HpkeContextSenderTest.java b/common/src/test/java/org/conscrypt/HpkeContextSenderTest.java index 1cc27e233..55ebf6f9c 100644 --- a/common/src/test/java/org/conscrypt/HpkeContextSenderTest.java +++ b/common/src/test/java/org/conscrypt/HpkeContextSenderTest.java @@ -21,14 +21,20 @@ import static org.conscrypt.HpkeFixture.DEFAULT_EXPORTER_LENGTH; import static org.conscrypt.HpkeFixture.DEFAULT_INFO; import static org.conscrypt.HpkeFixture.DEFAULT_PK; +import static org.conscrypt.HpkeFixture.DEFAULT_SK; +import static org.conscrypt.HpkeFixture.DEFAULT_SUITE_NAME; import static org.conscrypt.HpkeFixture.createDefaultHpkeContextSender; -import static org.conscrypt.HpkeFixture.createDefaultHpkeSuite; -import static org.conscrypt.HpkeFixture.createPublicKey; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Provider; import java.security.PublicKey; + import org.conscrypt.java.security.DefaultKeys; import org.junit.Test; import org.junit.runner.RunWith; @@ -37,36 +43,77 @@ @RunWith(JUnit4.class) public class HpkeContextSenderTest { @Test - public void testSetupBase_missingSuiteParameter_throwNullException() { - assertThrows(NullPointerException.class, - () - -> HpkeContextSender.setupBase( - /* hpkeSuite= */ null, createPublicKey(DEFAULT_PK), DEFAULT_INFO)); + public void testGetInstance() throws Exception { + assertThrows(NoSuchAlgorithmException.class, + () -> HpkeContextSender.getInstance(null)); + assertThrows(NoSuchAlgorithmException.class, + () -> HpkeContextSender.getInstance("No/Such/Thing")); + assertThrows(IllegalArgumentException.class, + () -> HpkeContextSender.getInstance(DEFAULT_SUITE_NAME, (String) null)); + assertThrows(IllegalArgumentException.class, + () -> HpkeContextSender.getInstance(DEFAULT_SUITE_NAME, (Provider) null)); + assertThrows(NoSuchProviderException.class, + () -> HpkeContextSender.getInstance(DEFAULT_SUITE_NAME, "NonsenseProviderName")); + HpkeContextSender sender = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + assertNotNull(sender); } @Test - public void testSetupBase_missingPkParameter_throwNullException() { - assertThrows(NullPointerException.class, - () - -> HpkeContextSender.setupBase( - createDefaultHpkeSuite(), /* publicKey= */ null, DEFAULT_INFO)); + public void testInitBase() throws Exception { + HpkeContextSender sender = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + PublicKey dhKey = DefaultKeys.getPublicKey("DH"); + + assertThrows(InvalidKeyException.class, + () -> sender.init(null, null)); + assertThrows(InvalidKeyException.class, + () -> sender.init(null, DEFAULT_INFO)); + + // DH keys not supported + assertThrows(InvalidKeyException.class, + () -> sender.init(dhKey , DEFAULT_INFO)); + + assertThrows(IllegalArgumentException.class, + () -> sender.initForTesting(dhKey , DEFAULT_INFO, null)); + + // Should succeed + sender.init(DEFAULT_PK, DEFAULT_INFO); + + // Re-initialisation not supported + assertThrows(IllegalStateException.class, + () -> sender.init(DEFAULT_PK, null)); + + HpkeContextSender sender2 = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + // null info is explicitly allowed + sender2.init(DEFAULT_PK, null); } @Test - public void testSetupBase_keyAlgorithmNotSupported_throwArgumentException() throws Exception { - final PublicKey publicKey = DefaultKeys.getPublicKey("DH"); - final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, - () - -> HpkeContextSender.setupBase( - createDefaultHpkeSuite(), publicKey, DEFAULT_INFO)); - assertEquals("Public key algorithm DH is not supported", e.getMessage()); + public void testInitUnsupportedModes() throws Exception { + HpkeContextSender sender = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + byte[] psk = "Shhh! Secret!".getBytes(StandardCharsets.UTF_8); + byte[] psk_id = "id".getBytes(StandardCharsets.UTF_8); + + assertThrows(UnsupportedOperationException.class, () -> + sender.init(DEFAULT_PK, DEFAULT_INFO, DEFAULT_SK)); + assertThrows(UnsupportedOperationException.class, () -> + sender.init(DEFAULT_PK, DEFAULT_INFO, psk, psk_id)); + assertThrows(UnsupportedOperationException.class, () -> + sender.init(DEFAULT_PK, DEFAULT_INFO, DEFAULT_SK, psk, psk_id)); } + @Test + public void testUninitialised() throws Exception { + HpkeContextSender sender = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + + assertThrows(IllegalStateException.class, + () -> sender.seal(new byte[16], new byte[16])); + assertThrows(IllegalStateException.class, + () -> sender.export(16, new byte[16])); + } @Test public void testSeal_missingRequiredParameters_throwNullException() throws Exception { - final PublicKey publicKey = createPublicKey(DEFAULT_PK); - final HpkeContextSender ctxSender = - HpkeContextSender.setupBase(createDefaultHpkeSuite(), publicKey, DEFAULT_INFO); + HpkeContextSender ctxSender = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + ctxSender.init(DEFAULT_PK, DEFAULT_INFO); assertThrows(NullPointerException.class, () -> ctxSender.seal(/* plaintext= */ null, DEFAULT_AAD)); } @@ -74,7 +121,7 @@ public void testSeal_missingRequiredParameters_throwNullException() throws Excep @Test public void testExport_withNullValue() throws Exception { final HpkeContextSender ctxSender = createDefaultHpkeContextSender(); - final byte[] enc = ctxSender.getEnc(); + final byte[] enc = ctxSender.getEncapsulated(); final byte[] export = ctxSender.export(DEFAULT_EXPORTER_LENGTH, /* exporterContext= */ null); assertNotNull(enc); @@ -85,7 +132,7 @@ public void testExport_withNullValue() throws Exception { @Test public void testExport_verifyOutputLength() throws Exception { final HpkeContextSender ctxSender = createDefaultHpkeContextSender(); - final byte[] enc = ctxSender.getEnc(); + final byte[] enc = ctxSender.getEncapsulated(); for (int i = 0; i < 8_000; i += 500) { final byte[] export = ctxSender.export(i, DEFAULT_EXPORTER_CONTEXT); assertNotNull(enc); @@ -97,7 +144,7 @@ public void testExport_verifyOutputLength() throws Exception { @Test public void testExport_lowerEdgeLength() throws Exception { final HpkeContextSender ctxSender = createDefaultHpkeContextSender(); - final byte[] enc = ctxSender.getEnc(); + final byte[] enc = ctxSender.getEncapsulated(); final byte[] export = ctxSender.export(/* length= */ 0, DEFAULT_EXPORTER_CONTEXT); assertNotNull(enc); assertNotNull(export); @@ -105,4 +152,15 @@ public void testExport_lowerEdgeLength() throws Exception { () -> ctxSender.export(/* length= */ -1, DEFAULT_EXPORTER_CONTEXT)); assertEquals("Export length (L) must be between 0 and 8160, but was -1", e.getMessage()); } + + @Test + public void getInstance() throws Exception { + HpkeContextSender ctxSender = createDefaultHpkeContextSender(); + assertNotNull(ctxSender); + for (int i = 0; i < 8_000; i += 500) { + final byte[] export = ctxSender.export(i, DEFAULT_EXPORTER_CONTEXT); + assertNotNull(export); + assertEquals(i, export.length); + } + } } diff --git a/common/src/test/java/org/conscrypt/HpkeContextTest.java b/common/src/test/java/org/conscrypt/HpkeContextTest.java index 257aab3fb..2835b324f 100644 --- a/common/src/test/java/org/conscrypt/HpkeContextTest.java +++ b/common/src/test/java/org/conscrypt/HpkeContextTest.java @@ -21,12 +21,12 @@ import static org.conscrypt.HpkeFixture.DEFAULT_EXPORTER_CONTEXT; import static org.conscrypt.HpkeFixture.DEFAULT_EXPORTER_LENGTH; import static org.conscrypt.HpkeFixture.DEFAULT_INFO; -import static org.conscrypt.HpkeFixture.DEFAULT_PK; +import static org.conscrypt.HpkeFixture.DEFAULT_PK_BYTES; import static org.conscrypt.HpkeFixture.DEFAULT_PT; -import static org.conscrypt.HpkeFixture.DEFAULT_SK; +import static org.conscrypt.HpkeFixture.DEFAULT_SK_BYTES; +import static org.conscrypt.HpkeFixture.DEFAULT_SUITE_NAME; import static org.conscrypt.HpkeFixture.createDefaultHpkeContextRecipient; import static org.conscrypt.HpkeFixture.createDefaultHpkeContextSender; -import static org.conscrypt.HpkeFixture.createDefaultHpkeSuite; import static org.conscrypt.HpkeFixture.createPrivateKey; import static org.conscrypt.HpkeFixture.createPublicKey; import static org.conscrypt.TestUtils.encodeHex; @@ -36,6 +36,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; +import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.PublicKey; import org.junit.Test; @@ -47,11 +48,11 @@ public class HpkeContextTest { @Test public void testSealOpen_randomnessResult() throws Exception { final HpkeContextSender ctxSender1 = createDefaultHpkeContextSender(); - final byte[] enc1 = ctxSender1.getEnc(); + final byte[] enc1 = ctxSender1.getEncapsulated(); final byte[] ciphertext1 = ctxSender1.seal(DEFAULT_PT, /* aad= */ null); final HpkeContextSender ctxSender2 = createDefaultHpkeContextSender(); - final byte[] enc2 = ctxSender2.getEnc(); + final byte[] enc2 = ctxSender2.getEncapsulated(); final byte[] ciphertext2 = ctxSender2.seal(DEFAULT_PT, /* aad= */ null); assertNotNull(enc1); @@ -75,13 +76,13 @@ public void testSealOpen_randomnessResult() throws Exception { } @Test - public void testSealOpen_aadNullSameAsEmtpy() throws Exception { + public void testSealOpen_aadNullSameAsEmpty() throws Exception { final HpkeContextSender ctxSender1 = createDefaultHpkeContextSender(); - final byte[] enc1 = ctxSender1.getEnc(); + final byte[] enc1 = ctxSender1.getEncapsulated(); final byte[] ciphertext1 = ctxSender1.seal(DEFAULT_PT, /* aad= */ null); final HpkeContextSender ctxSender2 = createDefaultHpkeContextSender(); - final byte[] enc2 = ctxSender2.getEnc(); + final byte[] enc2 = ctxSender2.getEncapsulated(); final byte[] ciphertext2 = ctxSender2.seal(DEFAULT_PT, /* aad= */ new byte[0]); assertNotNull(enc1); @@ -105,14 +106,14 @@ public void testSealOpen_aadNullSameAsEmtpy() throws Exception { } @Test - public void testSealOpen_infoNullSameAsEmtpy() throws Exception { + public void testSealOpen_infoNullSameAsEmpty() throws Exception { final HpkeContextSender ctxSender1 = createDefaultHpkeContextSender(/* info= */ null); - final byte[] enc1 = ctxSender1.getEnc(); + final byte[] enc1 = ctxSender1.getEncapsulated(); final byte[] ciphertext1 = ctxSender1.seal(DEFAULT_PT, DEFAULT_AAD); final HpkeContextSender ctxSender2 = createDefaultHpkeContextSender(/* info= */ new byte[0]); - final byte[] enc2 = ctxSender2.getEnc(); + final byte[] enc2 = ctxSender2.getEncapsulated(); final byte[] ciphertext2 = ctxSender2.seal(DEFAULT_PT, DEFAULT_AAD); assertNotNull(enc1); @@ -138,24 +139,26 @@ public void testSealOpen_infoNullSameAsEmtpy() throws Exception { } @Test - public void testSealOpen_withKeysFlipped_throwStateException() throws Exception { - final PublicKey publicKey = createPublicKey(DEFAULT_SK); - final PrivateKey privateKey = createPrivateKey(DEFAULT_PK); + public void testSealOpen_withKeysFlipped_throwException() throws Exception { + final PublicKey publicKey = createPublicKey(DEFAULT_SK_BYTES); + final PrivateKey privateKey = createPrivateKey(DEFAULT_PK_BYTES); - final HpkeContextSender ctxSender = - HpkeContextSender.setupBase(createDefaultHpkeSuite(), publicKey, DEFAULT_INFO); - final byte[] enc = ctxSender.getEnc(); + final HpkeContextSender ctxSender = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + ctxSender.init(publicKey, DEFAULT_INFO); + + final byte[] enc = ctxSender.getEncapsulated(); final byte[] ciphertext = ctxSender.seal(DEFAULT_PT, DEFAULT_AAD); - final HpkeContextRecipient ctxRecipient = HpkeContextRecipient.setupBase( - createDefaultHpkeSuite(), enc, privateKey, DEFAULT_INFO); - assertThrows(IllegalStateException.class, () -> ctxRecipient.open(ciphertext, DEFAULT_AAD)); + final HpkeContextRecipient ctxRecipient = + HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + ctxRecipient.init(enc, privateKey, DEFAULT_INFO); + assertThrows(GeneralSecurityException.class, () -> ctxRecipient.open(ciphertext, DEFAULT_AAD)); } @Test public void testExportWithSetupSenderAndReceiver_randomnessResult() throws Exception { final HpkeContextSender ctxSender = createDefaultHpkeContextSender(); - final byte[] enc = ctxSender.getEnc(); + final byte[] enc = ctxSender.getEncapsulated(); final byte[] export1 = ctxSender.export(DEFAULT_EXPORTER_LENGTH, DEFAULT_EXPORTER_CONTEXT); final HpkeContextRecipient ctxRecipient = createDefaultHpkeContextRecipient(DEFAULT_ENC); diff --git a/common/src/test/java/org/conscrypt/HpkeFixture.java b/common/src/test/java/org/conscrypt/HpkeFixture.java index 2ab18f313..247e421b8 100644 --- a/common/src/test/java/org/conscrypt/HpkeFixture.java +++ b/common/src/test/java/org/conscrypt/HpkeFixture.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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 org.conscrypt.HpkeSuite.AEAD_AES_256_GCM; @@ -5,6 +21,7 @@ import static org.conscrypt.HpkeSuite.KEM_DHKEM_X25519_HKDF_SHA256; import static org.conscrypt.TestUtils.decodeHex; +import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; @@ -18,10 +35,12 @@ public class HpkeFixture { decodeHex("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"); static final byte[] DEFAULT_INFO = decodeHex("4f6465206f6e2061204772656369616e2055726e"); - static final byte[] DEFAULT_PK = + static final byte[] DEFAULT_PK_BYTES = decodeHex("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d"); - static final byte[] DEFAULT_SK = + static final PublicKey DEFAULT_PK = createPublicKey(DEFAULT_PK_BYTES); + static final byte[] DEFAULT_SK_BYTES = decodeHex("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8"); + static final PrivateKey DEFAULT_SK = createPrivateKey(DEFAULT_SK_BYTES); static final byte[] DEFAULT_PT = decodeHex("4265617574792069732074727574682c20747275746820626561757479"); @@ -31,43 +50,56 @@ public class HpkeFixture { static final int DEFAULT_EXPORTER_LENGTH = 32; static final byte[] DEFAULT_EXPORTER_CONTEXT = decodeHex("00"); + static final HpkeSuite DEFAULT_SUITE = createDefaultHpkeSuite(); + + static final String DEFAULT_SUITE_NAME = DEFAULT_SUITE.name(); + static HpkeContextRecipient createDefaultHpkeContextRecipient(byte[] enc) - throws NoSuchAlgorithmException, InvalidKeySpecException { + throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { return createDefaultHpkeContextRecipient(enc, DEFAULT_INFO); } static HpkeContextRecipient createDefaultHpkeContextRecipient(byte[] enc, byte[] info) - throws NoSuchAlgorithmException, InvalidKeySpecException { - final HpkeSuite suite = createDefaultHpkeSuite(); - return HpkeContextRecipient.setupBase(suite, enc, createPrivateKey(DEFAULT_SK), info); + throws NoSuchAlgorithmException, InvalidKeyException { + HpkeContextRecipient contextRecipient = + HpkeContextRecipient.getInstance(DEFAULT_SUITE_NAME); + contextRecipient.init(enc, DEFAULT_SK, info); + return contextRecipient; } static HpkeContextSender createDefaultHpkeContextSender() - throws NoSuchAlgorithmException, InvalidKeySpecException { + throws NoSuchAlgorithmException, InvalidKeyException { return createDefaultHpkeContextSender(DEFAULT_INFO); } static HpkeContextSender createDefaultHpkeContextSender(byte[] info) - throws NoSuchAlgorithmException, InvalidKeySpecException { - final HpkeSuite suite = createDefaultHpkeSuite(); - return HpkeContextSender.setupBase(suite, createPublicKey(DEFAULT_PK), info); + throws NoSuchAlgorithmException, InvalidKeyException { + HpkeContextSender hpke = HpkeContextSender.getInstance(DEFAULT_SUITE_NAME); + hpke.init(DEFAULT_PK, info); + return hpke; } static HpkeSuite createDefaultHpkeSuite() { return new HpkeSuite(KEM_DHKEM_X25519_HKDF_SHA256, KDF_HKDF_SHA256, AEAD_AES_256_GCM); } - static PublicKey createPublicKey(byte[] publicKey) - throws NoSuchAlgorithmException, InvalidKeySpecException { - final KeyFactory factory = KeyFactory.getInstance("XDH"); - final KeySpec spec = new XdhKeySpec(publicKey); - return factory.generatePublic(spec); + static PublicKey createPublicKey(byte[] publicKey) { + try { + final KeyFactory factory = KeyFactory.getInstance("XDH"); + final KeySpec spec = new XdhKeySpec(publicKey); + return factory.generatePublic(spec); + } catch (Exception e) { + throw new AssertionError(e); + } } - static PrivateKey createPrivateKey(byte[] privateKey) - throws NoSuchAlgorithmException, InvalidKeySpecException { - final KeyFactory factory = KeyFactory.getInstance("XDH"); - final KeySpec spec = new XdhKeySpec(privateKey); - return factory.generatePrivate(spec); + static PrivateKey createPrivateKey(byte[] privateKey) { + try { + final KeyFactory factory = KeyFactory.getInstance("XDH"); + final KeySpec spec = new XdhKeySpec(privateKey); + return factory.generatePrivate(spec); + } catch (Exception e) { + throw new AssertionError(e); + } } } diff --git a/common/src/test/java/org/conscrypt/HpkeTestVectorsTest.java b/common/src/test/java/org/conscrypt/HpkeTestVectorsTest.java index 2ff34dcf4..2c7fd0ab5 100644 --- a/common/src/test/java/org/conscrypt/HpkeTestVectorsTest.java +++ b/common/src/test/java/org/conscrypt/HpkeTestVectorsTest.java @@ -21,13 +21,11 @@ import static org.conscrypt.HpkeSuite.AEAD_CHACHA20POLY1305; import static org.conscrypt.HpkeSuite.KDF_HKDF_SHA256; import static org.conscrypt.HpkeSuite.KEM_DHKEM_X25519_HKDF_SHA256; -import static org.conscrypt.TestUtils.conscryptClass; import static org.conscrypt.TestUtils.decodeHex; import static org.conscrypt.TestUtils.encodeHex; import static org.junit.Assert.assertArrayEquals; import java.io.IOException; -import java.lang.reflect.Method; import java.security.PrivateKey; import java.security.PublicKey; import java.util.ArrayList; @@ -88,15 +86,13 @@ public void testHpkeBasicExport() throws Exception { } } - private void testHpkeEncryption(HpkeData record) { + private void testHpkeEncryption(HpkeData record) throws Exception { final byte[] enc = record.pkEm; - final HpkeContextSenderHelper contextHelper = - new HpkeTestingContextSenderHelper(record.skEm); // Encryption final HpkeContextSender contextSender = - setupBaseForTesting(contextHelper, record.hpkeSuite, record.pkRm, record.info); - final byte[] encResult = contextSender.getEnc(); + setupBaseForTesting(record.hpkeSuite, record.pkRm, record.info, record.skEm); + final byte[] encResult = contextSender.getEncapsulated(); assertArrayEquals("Failed encryption 'enc' " + encodeHex(enc), enc, encResult); for (HpkeEncryptionData encryption : record.encryptions) { final byte[] ciphertext = contextSender.seal(encryption.pt, encryption.aad); @@ -106,7 +102,8 @@ private void testHpkeEncryption(HpkeData record) { // Decryption final HpkeContextRecipient contextRecipient = - HpkeContextRecipient.setupBase(record.hpkeSuite, enc, record.skRm, record.info); + HpkeContextRecipient.getInstance(record.hpkeSuite.name()); + contextRecipient.init(enc, record.skRm, record.info); for (HpkeEncryptionData encryption : record.encryptions) { final byte[] plaintext = contextRecipient.open(encryption.ct, encryption.aad); assertArrayEquals( @@ -114,15 +111,13 @@ private void testHpkeEncryption(HpkeData record) { } } - private void testHpkeExport(HpkeData record) { + private void testHpkeExport(HpkeData record) throws Exception { final byte[] enc = record.pkEm; - final HpkeContextSenderHelper contextHelper = - new HpkeTestingContextSenderHelper(record.skEm); // Sender secret export final HpkeContextSender contextSender = - setupBaseForTesting(contextHelper, record.hpkeSuite, record.pkRm, record.info); - final byte[] encResult = contextSender.getEnc(); + setupBaseForTesting(record.hpkeSuite, record.pkRm, record.info, record.skEm); + final byte[] encResult = contextSender.getEncapsulated(); assertArrayEquals("Failed encryption 'enc' " + encodeHex(enc), enc, encResult); for (HpkeExporterData exporterData : record.exports) { final byte[] export = @@ -132,8 +127,9 @@ private void testHpkeExport(HpkeData record) { } // Recipient secret export - final HpkeContextRecipient contextRecipient = - HpkeContextRecipient.setupBase(record.hpkeSuite, enc, record.skRm, record.info); + final HpkeContextRecipient contextRecipient + = HpkeContextRecipient.getInstance(record.hpkeSuite.name()); + contextRecipient.init(enc, record.skRm, record.info); for (HpkeExporterData exporterData : record.exports) { final byte[] export = contextRecipient.export(exporterData.l, exporterData.exporterContext); @@ -142,7 +138,7 @@ private void testHpkeExport(HpkeData record) { } } - private List getHpkeEncryptionRecords() throws IOException { + static List getHpkeEncryptionRecords() throws IOException { final List records = new ArrayList<>(); final List data = TestUtils.readCsvResource(TEST_DATA_ENCRYPTION); @@ -211,7 +207,7 @@ private List getHpkeSecretExportRecords() throws IOException { return records; } - private HpkeSuite convertSuite(String kemId, String kdfId, String aeadId) { + private static HpkeSuite convertSuite(String kemId, String kdfId, String aeadId) { final String suite = String.join(":", kemId, kdfId, aeadId); if (SUPPORTED_HPKE_SUITES.containsKey(suite)) { @@ -221,24 +217,15 @@ private HpkeSuite convertSuite(String kemId, String kdfId, String aeadId) { throw new IllegalArgumentException("Invalid KEM, KDF, AEAD : " + suite); } - public static HpkeContextSender setupBaseForTesting( - HpkeContextSenderHelper helper, HpkeSuite suite, PublicKey publicKey, byte[] info) { - try { - final Class contextClass = conscryptClass("HpkeContextSender"); - final Method method = contextClass.getDeclaredMethod( - /* name = */ "setupBaseForTesting", - /* parameterType = */ HpkeContextSenderHelper.class, - /* parameterType = */ HpkeSuite.class, - /* parameterType = */ PublicKey.class, - /* parameterType = */ byte[].class); - method.setAccessible(true); - return (HpkeContextSender) method.invoke(contextClass, helper, suite, publicKey, info); - } catch (Exception e) { - throw new RuntimeException("Error while calling setupBaseForTesting", e); - } + private static HpkeContextSender setupBaseForTesting( + HpkeSuite suite, PublicKey publicKey, byte[] info, byte[] sKem) throws Exception { + String algorithm = suite.name(); + HpkeContextSender sender = HpkeContextSender.getInstance(algorithm); + sender.initForTesting(publicKey, info, sKem); + return sender; } - private static class HpkeData { + static class HpkeData { HpkeSuite hpkeSuite; byte[] info; @@ -260,7 +247,7 @@ public String toString() { } } - private static class HpkeEncryptionData { + static class HpkeEncryptionData { byte[] aad; byte[] ct; byte[] pt; @@ -285,4 +272,4 @@ public String toString() { + ", exported_value=" + TestUtils.encodeHex(exportedValue) + '}'; } } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/conscrypt/HpkeTestingContextSenderHelper.java b/common/src/test/java/org/conscrypt/HpkeTestingContextSenderHelper.java deleted file mode 100644 index a426ab49e..000000000 --- a/common/src/test/java/org/conscrypt/HpkeTestingContextSenderHelper.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2023 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 org.conscrypt.TestUtils.conscryptClass; - -import java.lang.reflect.Method; - -public class HpkeTestingContextSenderHelper extends HpkeContextSenderHelper { - private final byte[] skE; - - public HpkeTestingContextSenderHelper(byte[] skE) { - this.skE = skE; - } - - @Override - public Object[] setupBase(int kem, int kdf, int aead, byte[] encodedKey, byte[] info) { - try { - final Class nativeCryptoClass = conscryptClass("NativeCrypto"); - final Method method = nativeCryptoClass.getDeclaredMethod( - /* name = */ "EVP_HPKE_CTX_setup_sender_with_seed_for_testing", - /* parameterType = */ int.class, - /* parameterType = */ int.class, - /* parameterType = */ int.class, - /* parameterType = */ byte[].class, - /* parameterType = */ byte[].class, - /* parameterType = */ byte[].class); - method.setAccessible(true); - return (Object[]) method.invoke( - nativeCryptoClass, kem, kdf, aead, encodedKey, info, skE); - } catch (Exception e) { - throw new RuntimeException( - "Error while calling EVP_HPKE_CTX_setup_sender_with_seed_for_testing", e); - } - } -} diff --git a/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java index 2397069a4..1f032e951 100644 --- a/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java +++ b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java @@ -153,9 +153,9 @@ public void evpMethods() throws Throwable { String[] illegalArgMethods = new String[] { "EVP_AEAD_CTX_open_buf", "EVP_AEAD_CTX_seal_buf", - "EVP_HPKE_CTX_setup_recipient", - "EVP_HPKE_CTX_setup_sender", - "EVP_HPKE_CTX_setup_sender_with_seed_for_testing", + "EVP_HPKE_CTX_setup_base_mode_recipient", + "EVP_HPKE_CTX_setup_base_mode_sender", + "EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing", "EVP_PKEY_new_RSA" }; String[] nonThrowingMethods = new String[] { diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java index 977b5f063..b55ca1be1 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java @@ -87,6 +87,7 @@ ClientSessionContextTest.class, ConscryptSocketTest.class, ConscryptTest.class, + DuckTypedHpkeSpiTest.class, DuckTypedPSKKeyManagerTest.class, FileClientSessionCacheTest.class, HostnameVerifierTest.class,