From e89ae39b869315ec9ddd414d7e97196428f1ff2c Mon Sep 17 00:00:00 2001 From: Miguel Aranda Date: Tue, 18 Aug 2026 13:00:48 +0000 Subject: [PATCH] Project import generated by Copybara. PiperOrigin-RevId: 966547131 --- .github/workflows/ci.yml | 6 +- AndroidManifest_instrumentation_test.xml | 17 + LICENSE | 54 + README.md | 10 +- android-stub/build.gradle | 7 + .../org/conscrypt/OpenSSLSocketImpl.java | 8 - android/build.gradle | 1 + android/lint.xml | 6 + .../ConscryptNetworkSecurityPolicy.java | 45 + ...itKatPlatformOpenSSLSocketImplAdapter.java | 15 - .../src/main/java/org/conscrypt/Platform.java | 145 +- common/src/jni/main/cpp/conscrypt/jniutil.cc | 45 +- .../jni/main/cpp/conscrypt/native_crypto.cc | 14424 ++++++++-------- .../conscrypt/AbstractConscryptEngine.java | 43 +- .../conscrypt/AbstractConscryptSocket.java | 35 +- .../main/java/org/conscrypt/AddressUtils.java | 25 +- .../main/java/org/conscrypt/ArrayUtils.java | 2 +- .../main/java/org/conscrypt/BufferUtils.java | 15 +- .../java/org/conscrypt/CertBlocklist.java | 3 + .../org/conscrypt/CertBlocklistEntry.java | 37 + .../CertificatePriorityComparator.java | 6 +- .../conscrypt/CompositeMlDsaAlgorithm.java | 215 + .../main/java/org/conscrypt/Conscrypt.java | 99 +- .../java/org/conscrypt/ConscryptEngine.java | 123 +- .../org/conscrypt/ConscryptEngineSocket.java | 43 +- .../ConscryptFileDescriptorSocket.java | 1246 -- .../org/conscrypt/ConscryptServerSocket.java | 27 +- .../conscrypt/ConscryptX509TrustManager.java | 77 + .../org/conscrypt/DefaultSSLContextImpl.java | 21 +- .../org/conscrypt/DomainEncryptionMode.java | 19 + .../main/java/org/conscrypt/EchOptions.java | 36 + .../org/conscrypt/EchRejectedException.java | 29 + .../org/conscrypt/Java8EngineWrapper.java | 20 +- .../conscrypt/Java8FileDescriptorSocket.java | 93 - .../org/conscrypt/KeyManagerFactoryImpl.java | 2 +- .../main/java/org/conscrypt/NativeCrypto.java | 676 +- .../main/java/org/conscrypt/NativeSsl.java | 103 +- .../org/conscrypt/NetworkSecurityPolicy.java | 34 + .../src/main/java/org/conscrypt/OidData.java | 8 + .../org/conscrypt/OkHostnameVerifier.java | 2 +- .../org/conscrypt/OpenSSLECPrivateKey.java | 2 +- .../java/org/conscrypt/OpenSSLEvpCipher.java | 99 +- .../java/org/conscrypt/OpenSSLProvider.java | 232 +- .../conscrypt/OpenSSLRSAPrivateCrtKey.java | 3 +- .../java/org/conscrypt/OpenSSLSignature.java | 5 +- .../conscrypt/OpenSSLSocketFactoryImpl.java | 75 +- .../java/org/conscrypt/OpenSSLSocketImpl.java | 6 +- .../conscrypt/OpenSSLX25519PrivateKey.java | 4 +- .../org/conscrypt/OpenSSLX25519PublicKey.java | 2 +- .../OpenSSLX509CertificateFactory.java | 83 +- .../OpenSslCompositeMlDsaKeyFactory.java | 307 + ...OpenSslCompositeMlDsaKeyPairGenerator.java | 239 + .../OpenSslCompositeMlDsaPrivateKey.java | 154 + .../OpenSslCompositeMlDsaPublicKey.java | 157 + .../org/conscrypt/OpenSslEdDsaPrivateKey.java | 6 +- .../org/conscrypt/OpenSslEdDsaPublicKey.java | 4 +- .../org/conscrypt/OpenSslMlDsaKeyFactory.java | 5 +- .../org/conscrypt/OpenSslMlDsaPrivateKey.java | 4 +- .../org/conscrypt/OpenSslMlDsaPublicKey.java | 2 +- .../org/conscrypt/OpenSslMlKemKeyFactory.java | 4 +- .../org/conscrypt/OpenSslMlKemPrivateKey.java | 4 +- .../org/conscrypt/OpenSslMlKemPublicKey.java | 4 +- .../OpenSslSignatureCompositeMlDsa.java | 474 + .../conscrypt/OpenSslSlhDsaKeyFactory.java | 4 +- .../conscrypt/OpenSslSlhDsaPrivateKey.java | 2 +- .../org/conscrypt/OpenSslSlhDsaPublicKey.java | 2 +- .../org/conscrypt/OpenSslXwingPrivateKey.java | 4 +- .../org/conscrypt/OpenSslXwingPublicKey.java | 4 +- .../java/org/conscrypt/SSLParametersImpl.java | 108 +- .../src/main/java/org/conscrypt/SSLUtils.java | 34 +- .../java/org/conscrypt/SessionSnapshot.java | 21 +- .../org/conscrypt/Spake2PlusKeyManager.java | 1 - .../org/conscrypt/Spake2PlusTrustManager.java | 1 - .../java/org/conscrypt/TrustManagerImpl.java | 93 +- .../org/conscrypt/ct/CertificateEntry.java | 13 + .../conscrypt/ct/CertificateTransparency.java | 34 +- .../main/java/org/conscrypt/ct/LogInfo.java | 56 +- .../main/java/org/conscrypt/ct/LogStore.java | 9 +- .../org/conscrypt/ct/PolicyCompliance.java | 9 +- .../ct/SignedCertificateTimestamp.java | 13 +- .../main/java/org/conscrypt/ct/Verifier.java | 12 +- ...ificateTransparencyVerificationReason.java | 7 +- .../CertificateValidationFailureReason.java | 39 + .../conscrypt/metrics/ConscryptStatsLog.java | 103 +- .../org/conscrypt/metrics/NoopStatsLog.java | 8 + .../org/conscrypt/metrics/OptionalMethod.java | 2 - .../metrics/ReflexiveStatsEvent.java | 7 + .../java/org/conscrypt/metrics/StatsLog.java | 8 + .../org/conscrypt/metrics/StatsLogImpl.java | 181 +- .../TlsEncryptedClientHelloHandshake.java | 296 + .../org/conscrypt/CompositeMlDsaTest.java | 375 + .../org/conscrypt/HostnameVerifierTest.java | 25 +- .../org/conscrypt/NativeCryptoArgTest.java | 9 - .../OpenSslCompositeMlDsaKeyFactoryTest.java | 577 + .../OpenSslCompositeMlDsaPrivateKeyTest.java | 115 + .../OpenSslCompositeMlDsaPublicKeyTest.java | 173 + .../conscrypt/SignatureThreadMisuseTest.java | 163 + .../org/conscrypt/TrustManagerImplTest.java | 10 + .../org/conscrypt/ct/SerializationTest.java | 9 + .../java/org/conscrypt/ct/VerifierTest.java | 14 +- .../security/AlgorithmParametersTestAES.java | 6 + .../AlgorithmParametersTestDESede.java | 6 + .../security/AlgorithmParametersTestEC.java | 6 + .../security/AlgorithmParametersTestGCM.java | 6 + .../security/AlgorithmParametersTestOAEP.java | 6 + .../java/security/KeyFactoryTestEC.java | 7 + .../java/security/KeyFactoryTestRSA.java | 6 + .../java/security/KeyPairGeneratorTest.java | 26 + .../java/security/MessageDigestTest.java | 6 + .../java/security/SecureRandomTest.java | 76 + .../java/security/SignatureTest.java | 21 +- .../security/cert/CertificateFactoryTest.java | 8 + .../java/security/cert/X509CRLTest.java | 6 + .../security/cert/X509CertificateTest.java | 166 +- .../javax/crypto/AeadCipherTest.java | 26 +- .../javax/crypto/CipherBasicsTest.java | 1 + .../conscrypt/javax/crypto/CipherTest.java | 98 +- .../javax/crypto/ECDHKeyAgreementTest.java | 21 + .../javax/crypto/KeyGeneratorTest.java | 6 + .../javax/crypto/XDHKeyAgreementTest.java | 1 + .../javax/net/ssl/KeyManagerFactoryTest.java | 18 +- .../javax/net/ssl/SSLContextTest.java | 20 + .../SSLEngineVersionCompatibilityTest.java | 107 +- .../javax/net/ssl/SSLSessionTest.java | 10 +- .../javax/net/ssl/SSLSocketTest.java | 135 +- .../test/resources/crypto/composite_mldsa.txt | 138 + constants/src/gen/cpp/generate_constants.cc | 11 +- constants/src/gen/java/README.md | 7 + .../gen/java/cert_verify_proc_blocklist.inc | 447 + constants/src/gen/java/generate_blocklist.awk | 106 + expectations/Android.bp | 37 + expectations/knownfailures.txt | 5 + expectations/skippedCtsTest.txt | 14 + expectations/skippedCtsTest_manual_base.txt | 8 + expectations/update_skippedCtsTest.sh | 23 + fix_format.py | 51 +- flatbuffers-jarjar-rules.txt | 2 + gradle/libs.versions.toml | 2 + openjdk/build.gradle | 3 +- .../ConscryptNetworkSecurityPolicy.java | 45 + .../src/main/java/org/conscrypt/Platform.java | 126 +- .../java/org/conscrypt/AddressUtilsTest.java | 478 +- .../org/conscrypt/ConscryptAndroidSuite.java | 1 + .../ConscryptAndroidWithoutTlsSuite.java | 1 + .../org/conscrypt/ConscryptEngineTest.java | 22 + .../org/conscrypt/ConscryptOpenJdkSuite.java | 1 + .../org/conscrypt/ConscryptSocketTest.java | 25 +- .../org/conscrypt/ConscryptTestListener.java | 45 + .../java/org/conscrypt/NativeCryptoTest.java | 1245 +- .../java/org/conscrypt/NativeSslTest.java | 4 +- .../test/java/org/conscrypt/SSLUtilsTest.java | 20 +- platform/src/main/ct_log_store.fbs | 38 + .../ConscryptNetworkSecurityPolicy.java | 104 + platform/src/main/java/org/conscrypt/Hex.java | 41 +- .../conscrypt/PakeTrustManagerFactory.java | 1 - .../src/main/java/org/conscrypt/Platform.java | 145 +- .../java/org/conscrypt/ct/LogStoreImpl.java | 296 - .../java/org/conscrypt/ct/LogStoreImplv3.java | 384 + .../java/org/conscrypt/ct/PolicyImpl.java | 2 +- .../java/org/conscrypt/CertBlocklistTest.java | 43 + .../conscrypt/PakeManagerFactoriesTest.java | 3 - .../org/conscrypt/TlsDeprecationTest.java | 2 + .../org/conscrypt/ct/LogStoreImplTest.java | 215 - .../org/conscrypt/ct/LogStoreImplv3Test.java | 311 + .../java/org/conscrypt/ct/PolicyImplTest.java | 72 +- .../org/conscrypt/metrics/MetricsTest.java | 47 + .../src/test/resources/ct_log_store_v3.ctfb | Bin 0 -> 1008 bytes .../src/test/resources/ct_log_store_v3.json | 418 + r8_include_list.txt | 1 + scripts/export_to_ag.py | 747 + settings.gradle | 2 - .../main/java/org/conscrypt/TestUtils.java | 32 +- .../org/conscrypt/VeryBasicHttpServer.java | 3 +- .../java/security/StandardNames.java | 51 +- .../main/java/tests/util/ServiceTester.java | 54 +- 175 files changed, 17419 insertions(+), 11493 deletions(-) create mode 100644 AndroidManifest_instrumentation_test.xml create mode 100644 android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java create mode 100644 common/src/main/java/org/conscrypt/CertBlocklistEntry.java create mode 100644 common/src/main/java/org/conscrypt/CompositeMlDsaAlgorithm.java delete mode 100644 common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java create mode 100644 common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java create mode 100644 common/src/main/java/org/conscrypt/DomainEncryptionMode.java create mode 100644 common/src/main/java/org/conscrypt/EchOptions.java create mode 100644 common/src/main/java/org/conscrypt/EchRejectedException.java delete mode 100644 common/src/main/java/org/conscrypt/Java8FileDescriptorSocket.java create mode 100644 common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactory.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyPairGenerator.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKey.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPublicKey.java create mode 100644 common/src/main/java/org/conscrypt/OpenSslSignatureCompositeMlDsa.java create mode 100644 common/src/main/java/org/conscrypt/metrics/CertificateValidationFailureReason.java create mode 100644 common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java create mode 100644 common/src/test/java/org/conscrypt/CompositeMlDsaTest.java create mode 100644 common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactoryTest.java create mode 100644 common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKeyTest.java create mode 100644 common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPublicKeyTest.java create mode 100644 common/src/test/java/org/conscrypt/SignatureThreadMisuseTest.java create mode 100644 common/src/test/java/org/conscrypt/java/security/SecureRandomTest.java create mode 100644 common/src/test/resources/crypto/composite_mldsa.txt create mode 100644 constants/src/gen/java/README.md create mode 100644 constants/src/gen/java/cert_verify_proc_blocklist.inc create mode 100644 constants/src/gen/java/generate_blocklist.awk create mode 100644 expectations/Android.bp create mode 100644 expectations/knownfailures.txt create mode 100644 expectations/skippedCtsTest.txt create mode 100644 expectations/skippedCtsTest_manual_base.txt create mode 100755 expectations/update_skippedCtsTest.sh create mode 100644 flatbuffers-jarjar-rules.txt create mode 100644 openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java create mode 100644 openjdk/src/test/java/org/conscrypt/ConscryptTestListener.java create mode 100644 platform/src/main/ct_log_store.fbs create mode 100644 platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java delete mode 100644 platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java create mode 100644 platform/src/main/java/org/conscrypt/ct/LogStoreImplv3.java delete mode 100644 platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java create mode 100644 platform/src/test/java/org/conscrypt/ct/LogStoreImplv3Test.java create mode 100644 platform/src/test/resources/ct_log_store_v3.ctfb create mode 100644 platform/src/test/resources/ct_log_store_v3.json create mode 100644 r8_include_list.txt create mode 100755 scripts/export_to_ag.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54f23fa01..3b1db4589 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -526,6 +526,10 @@ jobs: DIR="$(find m2/org/conscrypt/conscrypt-openjdk-uber -maxdepth 1 -mindepth 1 -type d -print)" VERSION="${DIR##*/}" TESTJAR="$(find testjar -name '*-tests.jar')" + EXTRA_FLAGS="" + if [ "${{ matrix.java }}" != "8" ]; then + EXTRA_FLAGS="--add-opens=java.base/java.net=ALL-UNNAMED" + fi # SIGTERM handler, e.g. for when tests hang and time out. # Send SIGQUIT to test process to get thread dump, give it # a few seconds to complete and then kill it. @@ -537,7 +541,7 @@ jobs: kill -KILL "$TESTPID" exit 1 } - java -jar junit-platform-console-standalone.jar execute -cp "$DIR/conscrypt-openjdk-uber-$VERSION.jar${{ matrix.separator }}$TESTJAR" -n='org.conscrypt.ConscryptOpenJdkSuite' --scan-classpath --reports-dir=results --fail-if-no-tests & + java $EXTRA_FLAGS -jar junit-platform-console-standalone.jar execute -cp "$DIR/conscrypt-openjdk-uber-$VERSION.jar${{ matrix.separator }}$TESTJAR" -n='org.conscrypt.ConscryptOpenJdkSuite' --scan-classpath --reports-dir=results --fail-if-no-tests & case $(uname -s) in Darwin|Linux) trap dump_threads SIGTERM SIGINT diff --git a/AndroidManifest_instrumentation_test.xml b/AndroidManifest_instrumentation_test.xml new file mode 100644 index 000000000..238c4f152 --- /dev/null +++ b/AndroidManifest_instrumentation_test.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/LICENSE b/LICENSE index d64569567..9f687a821 100644 --- a/LICENSE +++ b/LICENSE @@ -200,3 +200,57 @@ 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. + +=============================================================================== + +Path = constants/src/gen/java/cert_verify_proc_blocklist.inc + +=============================================================================== +// Copyright 2015 The Chromium Authors +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================== + +Path = common/src/test/resources/crypto/* + +=============================================================================== +Terms Of Use + +This software was developed by employees of the National Institute of Standards +and Technology (NIST), and others. This software has been contributed to the +public domain. Pursuant to title 15 Untied States Code Section 105, works of +NIST employees are not subject to copyright protection in the United States and +are considered to be in the public domain. As a result, a formal license is +not needed to use this software. + +This software is provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, +IMPLIED OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT AND DATA +ACCURACY. NIST does not warrant or make any representations regarding the use +of the software or the results thereof, including but not limited to the +correctness, accuracy, reliability or usefulness of this software. \ No newline at end of file diff --git a/README.md b/README.md index 7157e18a5..112edaf71 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ Use the [os-maven-plugin](https://github.com/trustin/os-maven-plugin) to add the org.conscrypt conscrypt-openjdk - 2.6.0 + 2.6.1 ${os.detected.classifier} ``` @@ -173,7 +173,7 @@ buildscript { apply plugin: "com.google.osdetector" dependencies { - compile 'org.conscrypt:conscrypt-openjdk:2.6.0:' + osdetector.classifier + compile 'org.conscrypt:conscrypt-openjdk:2.6.1:' + osdetector.classifier } ``` @@ -191,14 +191,14 @@ To depend on the uber jar, simply use the `conscrypt-openjdk-uber` artifacts. org.conscrypt conscrypt-openjdk-uber - 2.6.0 + 2.6.1 ``` ##### Gradle ```gradle dependencies { - compile 'org.conscrypt:conscrypt-openjdk-uber:2.6.0' + compile 'org.conscrypt:conscrypt-openjdk-uber:2.6.1' } ``` @@ -211,7 +211,7 @@ arm64-v8a. ```gradle dependencies { - implementation 'org.conscrypt:conscrypt-android:2.6.0' + implementation 'org.conscrypt:conscrypt-android:2.6.1' } ``` diff --git a/android-stub/build.gradle b/android-stub/build.gradle index 9037248c9..e9fb808c7 100644 --- a/android-stub/build.gradle +++ b/android-stub/build.gradle @@ -11,3 +11,10 @@ dependencies { // Disable the javadoc task. tasks.withType(Javadoc).configureEach { enabled = false } + +// The stubs are used to validate the build but are not expected to be called. +// Skip these lints. +tasks.withType(JavaCompile).configureEach { + options.errorprone.disable("unused") + options.errorprone.disable("DoNotCallSuggester") +} diff --git a/android-stub/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java b/android-stub/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java index a2be95473..66a5311f4 100644 --- a/android-stub/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java +++ b/android-stub/src/main/java/com/android/org/conscrypt/OpenSSLSocketImpl.java @@ -174,14 +174,6 @@ public void setHostname(String hostname) { throw new RuntimeException("Stub!"); } - public void setChannelIdEnabled(boolean enabled) { - throw new RuntimeException("Stub!"); - } - - public byte[] getChannelId() throws SSLException { - throw new RuntimeException("Stub!"); - } - public void setChannelIdPrivateKey(PrivateKey privateKey) { throw new RuntimeException("Stub!"); } diff --git a/android/build.gradle b/android/build.gradle index e8413a2b0..e5cf34de3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -129,6 +129,7 @@ dependencies { androidTestImplementation libs.bouncycastle.apis androidTestImplementation libs.bouncycastle.provider androidTestImplementation libs.mockito.android + androidTestImplementation libs.truth androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1', { exclude module: 'support-annotations' exclude module: 'support-v4' diff --git a/android/lint.xml b/android/lint.xml index 5d43d7ff6..8ad971986 100644 --- a/android/lint.xml +++ b/android/lint.xml @@ -28,4 +28,10 @@ + + + + + + diff --git a/android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java b/android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java new file mode 100644 index 000000000..4f5f0e6ad --- /dev/null +++ b/android/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 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 org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * A default NetworkSecurityPolicy for unbundled Android. + */ +@Internal +public class ConscryptNetworkSecurityPolicy implements NetworkSecurityPolicy { + public static ConscryptNetworkSecurityPolicy getDefault() { + return new ConscryptNetworkSecurityPolicy(); + } + + @Override + public boolean isCertificateTransparencyVerificationRequired(String hostname) { + return false; + } + + @Override + public CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname) { + return CertificateTransparencyVerificationReason.UNKNOWN; + } + + @Override + public DomainEncryptionMode getDomainEncryptionMode(String hostname) { + return DomainEncryptionMode.UNKNOWN; + } +} diff --git a/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java b/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java index 2f567ded5..de295f098 100644 --- a/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java +++ b/android/src/main/java/org/conscrypt/KitKatPlatformOpenSSLSocketImplAdapter.java @@ -402,21 +402,6 @@ public void setHostname(String hostname) { delegate.setHostname(hostname); } - @Override - public void setChannelIdEnabled(boolean enabled) { - delegate.setChannelIdEnabled(enabled); - } - - @Override - public byte[] getChannelId() throws SSLException { - return delegate.getChannelId(); - } - - @Override - public void setChannelIdPrivateKey(PrivateKey privateKey) { - delegate.setChannelIdPrivateKey(privateKey); - } - @Override public void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException { delegate.setSoWriteTimeout(writeTimeoutMilliseconds); diff --git a/android/src/main/java/org/conscrypt/Platform.java b/android/src/main/java/org/conscrypt/Platform.java index 552248eeb..5426bdd3b 100644 --- a/android/src/main/java/org/conscrypt/Platform.java +++ b/android/src/main/java/org/conscrypt/Platform.java @@ -59,11 +59,13 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import javax.net.ssl.SNIHostName; import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; @@ -258,7 +260,8 @@ private static void setSSLParametersOnImpl(SSLParameters params, SSLParametersIm try { Method getNamedGroupsMethod = params.getClass().getMethod("getNamedGroups"); impl.setNamedGroups((String[]) getNamedGroupsMethod.invoke(params)); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } } @@ -284,7 +287,9 @@ public static void setSSLParameters(SSLParameters params, SSLParametersImpl impl socket.setHostname(sniHostname); } } - } catch (NoSuchMethodException | IllegalAccessException ignored) { + } catch (NoSuchMethodException ignored) { + // Ignored + } catch (IllegalAccessException ignored) { // Ignored } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); @@ -302,7 +307,9 @@ public static void setSSLParameters(SSLParameters params, SSLParametersImpl impl engine.setHostname(sniHostname); } } - } catch (NoSuchMethodException | IllegalAccessException ignored) { + } catch (NoSuchMethodException ignored) { + // Ignored + } catch (IllegalAccessException ignored) { // Ignored } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); @@ -341,7 +348,8 @@ private static void getSSLParametersFromImpl(SSLParameters params, SSLParameters Method setNamedGroupsMethod = params.getClass().getMethod("setNamedGroups", String[].class); setNamedGroupsMethod.invoke(params, (Object) impl.getNamedGroups()); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } } @@ -364,7 +372,9 @@ public static void getSSLParameters(SSLParameters params, SSLParametersImpl impl if (Build.VERSION.SDK_INT >= 24) { setParametersSniHostname(params, impl, socket); } - } catch (NoSuchMethodException | IllegalAccessException ignored) { + } catch (NoSuchMethodException ignored) { + // Ignored + } catch (IllegalAccessException ignored) { // Ignored } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); @@ -391,7 +401,9 @@ public static void getSSLParameters(SSLParameters params, SSLParametersImpl impl if (Build.VERSION.SDK_INT >= 24) { setParametersSniHostname(params, impl, engine); } - } catch (NoSuchMethodException | IllegalAccessException ignored) { + } catch (NoSuchMethodException ignored) { + // Ignored + } catch (IllegalAccessException ignored) { // Ignored } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); @@ -571,68 +583,6 @@ static ConscryptEngineSocket createEngineSocket(Socket socket, String hostname, return new ConscryptEngineSocket(socket, hostname, port, autoClose, sslParameters); } - static ConscryptFileDescriptorSocket createFileDescriptorSocket(SSLParametersImpl sslParameters) - throws IOException { - if (Build.VERSION.SDK_INT >= 24) { - return new Java8FileDescriptorSocket(sslParameters); - } - return new ConscryptFileDescriptorSocket(sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port, - SSLParametersImpl sslParameters) - throws IOException { - if (Build.VERSION.SDK_INT >= 24) { - return new Java8FileDescriptorSocket(hostname, port, sslParameters); - } - return new ConscryptFileDescriptorSocket(hostname, port, sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port, - SSLParametersImpl sslParameters) - throws IOException { - if (Build.VERSION.SDK_INT >= 24) { - return new Java8FileDescriptorSocket(address, port, sslParameters); - } - return new ConscryptFileDescriptorSocket(address, port, sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port, - InetAddress clientAddress, - int clientPort, - SSLParametersImpl sslParameters) - throws IOException { - if (Build.VERSION.SDK_INT >= 24) { - return new Java8FileDescriptorSocket(hostname, port, clientAddress, clientPort, - sslParameters); - } - return new ConscryptFileDescriptorSocket(hostname, port, clientAddress, clientPort, - sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port, - InetAddress clientAddress, - int clientPort, - SSLParametersImpl sslParameters) - throws IOException { - if (Build.VERSION.SDK_INT >= 24) { - return new Java8FileDescriptorSocket(address, port, clientAddress, clientPort, - sslParameters); - } - return new ConscryptFileDescriptorSocket(address, port, clientAddress, clientPort, - sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(Socket socket, String hostname, - int port, boolean autoClose, - SSLParametersImpl sslParameters) - throws IOException { - if (Build.VERSION.SDK_INT >= 24) { - return new Java8FileDescriptorSocket(socket, hostname, port, autoClose, sslParameters); - } - return new ConscryptFileDescriptorSocket(socket, hostname, port, autoClose, sslParameters); - } - /** * Wrap the SocketFactory with the platform wrapper if needed for compatability. */ @@ -871,59 +821,13 @@ static boolean supportsX509ExtendedTrustManager() { return Build.VERSION.SDK_INT > 23; } - /** - * Check if SCT verification is required for a given hostname. - * - * SCT Verification is enabled using {@code Security} properties. - * The "conscrypt.ct.enable" property must be true, as well as a per domain property. - * The reverse notation of the domain name, prefixed with "conscrypt.ct.enforce." - * is used as the property name. - * Basic globbing is also supported. - * - * For example, for the domain foo.bar.com, the following properties will be - * looked up, in order of precedence. - * - conscrypt.ct.enforce.com.bar.foo - * - conscrypt.ct.enforce.com.bar.* - * - conscrypt.ct.enforce.com.* - * - conscrypt.ct.enforce.* - */ - public static boolean isCTVerificationRequired(String hostname) { - if (hostname == null) { - return false; - } - // TODO: Use the platform version on platforms that support it - - String property = Security.getProperty("conscrypt.ct.enable"); - if (property == null || !Boolean.parseBoolean(property)) { - return false; - } - - List parts = Arrays.asList(hostname.split("\\.")); - Collections.reverse(parts); - - boolean enable = false; - String propertyName = "conscrypt.ct.enforce"; - // The loop keeps going on even once we've found a match - // This allows for finer grained settings on subdomains - for (String part : parts) { - property = Security.getProperty(propertyName + ".*"); - if (property != null) { - enable = Boolean.parseBoolean(property); - } - - propertyName = propertyName + "." + part; - } - - property = Security.getProperty(propertyName); - if (property != null) { - enable = Boolean.parseBoolean(property); - } - return enable; + static SSLException wrapInvalidEchDataException(SSLException e) { + return e; } - public static CertificateTransparencyVerificationReason reasonCTVerificationRequired( - String hostname) { - return CertificateTransparencyVerificationReason.UNKNOWN; + static SSLException wrapEchRejectedException(EchRejectedException e, String hostname, + byte[] retryConfigs) { + return e; } static boolean supportsConscryptCertStore() { @@ -952,7 +856,8 @@ static CertBlocklist newDefaultBlocklist() { return null; } - static CertificateTransparency newDefaultCertificateTransparency() { + static CertificateTransparency newDefaultCertificateTransparency( + Supplier policySupplier) { return null; } diff --git a/common/src/jni/main/cpp/conscrypt/jniutil.cc b/common/src/jni/main/cpp/conscrypt/jniutil.cc index a57c6d810..f6b726e15 100644 --- a/common/src/jni/main/cpp/conscrypt/jniutil.cc +++ b/common/src/jni/main/cpp/conscrypt/jniutil.cc @@ -14,12 +14,14 @@ * limitations under the License. */ -#include +// clang-format off #include -#include -#include +#include +#include #include +#include +// clang-format on namespace conscrypt { namespace jniutil { @@ -93,10 +95,7 @@ void init(JavaVM* vm, JNIEnv* env) { openSslInputStreamClass = getGlobalRefToClass( env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/OpenSSLBIOInputStream"); sslHandshakeCallbacksClass = getGlobalRefToClass( - env, - TO_STRING( - JNI_JARJAR_PREFIX) "org/conscrypt/" - "NativeCrypto$SSLHandshakeCallbacks"); + env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/NativeCrypto$SSLHandshakeCallbacks"); nativeRef_address = getFieldRef(env, nativeRefClass, "address", "J"); #if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK) @@ -181,8 +180,8 @@ int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) { } extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer) { - // Some versions of ART do not check the buffer validity when handling - // GetDirectBufferAddress() and GetDirectBufferCapacity(). + // Some versions of ART do not check the buffer validity when handling GetDirectBufferAddress() + // and GetDirectBufferCapacity(). if (buffer == nullptr) { return false; } @@ -194,8 +193,7 @@ extern bool isDirectByteBufferInstance(JNIEnv* env, jobject buffer) { bool isGetByteArrayElementsLikelyToReturnACopy(size_t size) { #if defined(ANDROID) && !defined(CONSCRYPT_OPENJDK) - // ART's GetByteArrayElements creates copies only for arrays smaller than 12 - // kB. + // ART's GetByteArrayElements creates copies only for arrays smaller than 12 kB. return size <= 12 * 1024; #else (void)size; @@ -347,6 +345,9 @@ int throwForCipherError(JNIEnv* env, int reason, const char* message, case CIPHER_R_BUFFER_TOO_SMALL: return throwShortBufferException(env, message); break; + case ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED: + return throwIllegalStateException(env, message); + break; } return defaultThrow(env, message); } @@ -451,9 +452,8 @@ void throwExceptionFromBoringSSLError(JNIEnv* env, CONSCRYPT_UNUSED const char* return; } - // If there's an error from BoringSSL it may have been caused by an exception - // in Java code, so ensure there isn't a pending exception before we throw a - // new one. + // If there's an error from BoringSSL it may have been caused by an exception in Java code, so + // ensure there isn't a pending exception before we throw a new one. if (!env->ExceptionCheck()) { char message[256]; ERR_error_string_n(error, message, sizeof(message)); @@ -516,6 +516,12 @@ int throwSSLProtocolExceptionStr(JNIEnv* env, const char* message) { return conscrypt::jniutil::throwException(env, "javax/net/ssl/SSLProtocolException", message); } +int throwEchRejectedException(JNIEnv* env, const char* message) { + JNI_TRACE("throwEchRejectedException %s", message); + return conscrypt::jniutil::throwException( + env, TO_STRING(JNI_JARJAR_PREFIX) "org/conscrypt/EchRejectedException", message); +} + int throwSSLExceptionWithSslErrors(JNIEnv* env, SSL* ssl, int sslErrorCode, const char* message, int (*actualThrow)(JNIEnv*, const char*)) { if (message == nullptr) { @@ -571,6 +577,7 @@ int throwSSLExceptionWithSslErrors(JNIEnv* env, SSL* ssl, int sslErrorCode, cons } char* allocStr = str; + unsigned long first_err = 0; // For protocol errors, SSL might have more information. if (sslErrorCode == SSL_ERROR_NONE || sslErrorCode == SSL_ERROR_SSL) { @@ -583,6 +590,9 @@ int throwSSLExceptionWithSslErrors(JNIEnv* env, SSL* ssl, int sslErrorCode, cons int flags; // NOLINTNEXTLINE(runtime/int) unsigned long err = ERR_get_error_line_data(&file, &line, &data, &flags); + if (first_err == 0) { + first_err = err; + } if (err == 0) { break; } @@ -616,7 +626,12 @@ int throwSSLExceptionWithSslErrors(JNIEnv* env, SSL* ssl, int sslErrorCode, cons int ret; if (sslErrorCode == SSL_ERROR_SSL) { - ret = throwSSLProtocolExceptionStr(env, allocStr); + if (first_err != 0 && ERR_GET_LIB(first_err) == ERR_LIB_SSL && + ERR_GET_REASON(first_err) == SSL_R_ECH_REJECTED) { + ret = throwEchRejectedException(env, allocStr); + } else { + ret = throwSSLProtocolExceptionStr(env, allocStr); + } } else { ret = actualThrow(env, allocStr); } diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 9f8bf4ee3..276927ca5 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -823,747 +824,774 @@ void init_engine_globals() { #define THROW_SOCKETTIMEOUTEXCEPTION (-3) #define THROWN_EXCEPTION (-4) -/** - * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, - * byte[] p, byte[] q); - */ -static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jbyteArray e, - jbyteArray d, jbyteArray p, jbyteArray q, - jbyteArray dmp1, jbyteArray dmq1, jbyteArray iqmp) { +static jbyteArray NativeCrypto_wrap_EC_private_key_pkcs8(JNIEnv* env, jclass, + jbyteArray rawKeyBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE( - "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p, dmp1=%p, dmq1=%p, " - "iqmp=%p)", - n, e, d, p, q, dmp1, dmq1, iqmp); + JNI_TRACE("NativeCrypto_wrap_EC_private_key_pkcs8"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; + } - if (e == nullptr && d == nullptr) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "e == null && d == null"); - JNI_TRACE("NativeCrypto_EVP_PKEY_new_RSA => e == null && d == null"); - return 0; + ScopedByteArrayRO bytes(env, rawKeyBytes); + if (bytes.get() == nullptr) { + return nullptr; } - bssl::UniquePtr nBN, eBN, dBN, pBN, qBN, dmp1BN, dmq1BN, iqmpBN; - nBN = arrayToBignum(env, n); - if (!nBN) { - return 0; + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr ec_key(EC_KEY_parse_private_key(&cbs, nullptr)); + if (!ec_key || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "Error parsing EC private key", conscrypt::jniutil::throwParsingException); + return nullptr; } - if (e != nullptr) { - eBN = arrayToBignum(env, e); - if (!eBN) { - return 0; - } + + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (!pkey) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_new failed"); + return nullptr; } - if (d != nullptr) { - dBN = arrayToBignum(env, d); - if (!dBN) { - return 0; - } + + if (!EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_assign_EC_KEY"); + return nullptr; } - if (p != nullptr) { - pBN = arrayToBignum(env, p); - if (!pBN) { - return 0; - } + ec_key.release(); // pkey now owns the ec_key + + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 0)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + return nullptr; } - if (q != nullptr) { - qBN = arrayToBignum(env, q); - if (!qBN) { - return 0; - } + if (!EVP_marshal_private_key(cbb.get(), pkey.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_private_key"); + return nullptr; } - if (dmp1 != nullptr) { - dmp1BN = arrayToBignum(env, dmp1); - if (!dmp1BN) { - return 0; - } + + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(cbb.get(), &out_data, &out_len)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CBB_finish"); + return nullptr; } - if (dmq1 != nullptr) { - dmq1BN = arrayToBignum(env, dmq1); - if (!dmq1BN) { - return 0; - } + bssl::UniquePtr free_data(out_data); + + jbyteArray result = env->NewByteArray(out_len); + if (result != nullptr) { + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); } - if (iqmp != nullptr) { - iqmpBN = arrayToBignum(env, iqmp); - if (!iqmpBN) { - return 0; - } + return result; +} + +static jbyteArray NativeCrypto_wrap_RSA_private_key_pkcs8(JNIEnv* env, jclass, + jbyteArray rawKeyBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("NativeCrypto_wrap_RSA_private_key_pkcs8"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; } - // Determine what kind of key this is. - // - // TODO(davidben): The caller already knows what kind of key they expect. - // Ideally we would have separate APIs for the caller. However, we currently - // tolerate, say, an RSAPrivateCrtKeySpec where most fields are null and - // silently make a public key out of it. This is probably a mistake, but would - // need to be a breaking change. - bssl::UniquePtr rsa; - if (!dBN) { - rsa.reset(RSA_new_public_key(nBN.get(), eBN.get())); - } else if (!eBN) { - rsa.reset(RSA_new_private_key_no_e(nBN.get(), dBN.get())); - } else if (!pBN || !qBN || !dmp1BN || !dmq1BN || !iqmpBN) { - rsa.reset(RSA_new_private_key_no_crt(nBN.get(), eBN.get(), dBN.get())); - } else { - rsa.reset(RSA_new_private_key(nBN.get(), eBN.get(), dBN.get(), pBN.get(), qBN.get(), - dmp1BN.get(), dmq1BN.get(), iqmpBN.get())); + ScopedByteArrayRO bytes(env, rawKeyBytes); + if (bytes.get() == nullptr) { + return nullptr; } - if (rsa == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_new_RSA"); - return 0; + + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr rsa(RSA_parse_private_key(&cbs)); + if (!rsa || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwParsingException(env, "Error parsing RSA private key"); + ERR_clear_error(); + return nullptr; } bssl::UniquePtr pkey(EVP_PKEY_new()); - if (pkey.get() == nullptr) { - conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_new failed"); - return 0; - } - if (EVP_PKEY_assign_RSA(pkey.get(), rsa.get()) != 1) { - conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_new failed"); - ERR_clear_error(); - return 0; + if (!pkey) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_new failed"); + return nullptr; } - OWNERSHIP_TRANSFERRED(rsa); - JNI_TRACE( - "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p dmp1=%p, dmq1=%p, " - "iqmp=%p) => %p", - n, e, d, p, q, dmp1, dmq1, iqmp, pkey.get()); - return reinterpret_cast(pkey.release()); -} -static jlong NativeCrypto_EVP_PKEY_new_EC_KEY(JNIEnv* env, jclass, jobject groupRef, - jobject pubkeyRef, jbyteArray keyJavaBytes) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p)", groupRef, pubkeyRef, keyJavaBytes); - const EC_GROUP* group = fromContextObject(env, groupRef); - if (group == nullptr) { - return 0; + if (!EVP_PKEY_assign_RSA(pkey.get(), rsa.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_assign_RSA"); + return nullptr; } - const EC_POINT* pubkey = - pubkeyRef == nullptr ? nullptr : fromContextObject(env, pubkeyRef); - JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) <- ptr", group, pubkey, keyJavaBytes); + rsa.release(); // pkey now owns the rsa - bssl::UniquePtr key(nullptr); - if (keyJavaBytes != nullptr) { - key = arrayToBignum(env, keyJavaBytes); - if (!key) { - return 0; - } + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 0)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + return nullptr; + } + if (!EVP_marshal_private_key(cbb.get(), pkey.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_private_key"); + return nullptr; } - bssl::UniquePtr eckey(EC_KEY_new()); - if (eckey.get() == nullptr) { - conscrypt::jniutil::throwRuntimeException(env, "EC_KEY_new failed"); - return 0; + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(cbb.get(), &out_data, &out_len)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CBB_finish"); + return nullptr; } + bssl::UniquePtr free_data(out_data); - if (EC_KEY_set_group(eckey.get(), group) != 1) { - JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) > EC_KEY_set_group failed", group, pubkey, - keyJavaBytes); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); - return 0; + jbyteArray result = env->NewByteArray(out_len); + if (result == nullptr) { + return nullptr; } + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); + return result; +} - if (pubkey != nullptr) { - if (EC_KEY_set_public_key(eckey.get(), pubkey) != 1) { - JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => EC_KEY_set_private_key failed", group, - pubkey, keyJavaBytes); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_public_key"); - return 0; - } +static jbyteArray NativeCrypto_wrap_RSA_public_key_x509(JNIEnv* env, jclass, + jbyteArray rawKeyBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("NativeCrypto_wrap_RSA_public_key_x509"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; } - if (key.get() != nullptr) { - if (EC_KEY_set_private_key(eckey.get(), key.get()) != 1) { - JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => EC_KEY_set_private_key failed", group, - pubkey, keyJavaBytes); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_private_key"); - return 0; - } - if (pubkey == nullptr) { - bssl::UniquePtr calcPubkey(EC_POINT_new(group)); - if (!EC_POINT_mul(group, calcPubkey.get(), key.get(), nullptr, nullptr, nullptr)) { - JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => can't calculate public key", group, - pubkey, keyJavaBytes); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_private_key"); - return 0; - } - EC_KEY_set_public_key(eckey.get(), calcPubkey.get()); - } + ScopedByteArrayRO bytes(env, rawKeyBytes); + if (bytes.get() == nullptr) { + return nullptr; } - if (!EC_KEY_check_key(eckey.get())) { - JNI_TRACE("EVP_KEY_new_EC_KEY(%p, %p, %p) => invalid key created", group, pubkey, - keyJavaBytes); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_check_key"); - return 0; + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr rsa(RSA_parse_public_key(&cbs)); + if (!rsa || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwParsingException(env, "Error parsing RSA public key"); + ERR_clear_error(); + return nullptr; } bssl::UniquePtr pkey(EVP_PKEY_new()); - if (pkey.get() == nullptr) { - JNI_TRACE("EVP_PKEY_new_EC(%p, %p, %p) => threw error", group, pubkey, keyJavaBytes); + if (!pkey) { conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_new failed"); - return 0; - } - if (EVP_PKEY_assign_EC_KEY(pkey.get(), eckey.get()) != 1) { - JNI_TRACE("EVP_PKEY_new_EC(%p, %p, %p) => threw error", group, pubkey, keyJavaBytes); - conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_assign_EC_KEY failed"); - ERR_clear_error(); - return 0; + return nullptr; } - OWNERSHIP_TRANSFERRED(eckey); - JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => %p", group, pubkey, keyJavaBytes, pkey.get()); - return reinterpret_cast(pkey.release()); -} + if (!EVP_PKEY_assign_RSA(pkey.get(), rsa.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_assign_RSA"); + return nullptr; + } + rsa.release(); // pkey now owns the rsa -static int NativeCrypto_EVP_PKEY_type(JNIEnv* env, jclass, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("EVP_PKEY_type(%p)", pkey); + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 0)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + return nullptr; + } + if (!EVP_marshal_public_key(cbb.get(), pkey.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_public_key"); + return nullptr; + } - if (pkey == nullptr) { - return -1; + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(cbb.get(), &out_data, &out_len)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CBB_finish"); + return nullptr; } + bssl::UniquePtr free_data(out_data); - int result = EVP_PKEY_id(pkey); - JNI_TRACE("EVP_PKEY_type(%p) => %d", pkey, result); + jbyteArray result = env->NewByteArray(out_len); + if (result == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); return result; } -typedef int print_func(BIO*, const EVP_PKEY*, int, ASN1_PCTX*); - -static jstring evp_print_func(JNIEnv* env, jobject pkeyRef, print_func* func, - const char* debug_name) { - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("%s(%p)", debug_name, pkey); - - if (pkey == nullptr) { +static jbyteArray NativeCrypto_wrap_EC_public_key_x509(JNIEnv* env, jclass, jbyteArray rawKeyBytes, + jstring curveNameJava) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("NativeCrypto_wrap_EC_public_key_x509"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; + } + if (curveNameJava == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "curveName == null"); return nullptr; } - bssl::UniquePtr buffer(BIO_new(BIO_s_mem())); - if (buffer.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate BIO"); + ScopedUtfChars curveName(env, curveNameJava); + if (curveName.c_str() == nullptr) { return nullptr; } - if (func(buffer.get(), pkey, 0, nullptr) != 1) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, debug_name); + int nid = OBJ_sn2nid(curveName.c_str()); + if (nid == NID_undef) { + conscrypt::jniutil::throwParsingException( + env, "Error parsing EC public key: unknown curve name"); return nullptr; } - // Null terminate this - BIO_write(buffer.get(), "\0", 1); - - char* tmp; - BIO_get_mem_data(buffer.get(), &tmp); - jstring description = env->NewStringUTF(tmp); - JNI_TRACE("%s(%p) => \"%s\"", debug_name, pkey, tmp); - return description; -} + ScopedByteArrayRO bytes(env, rawKeyBytes); + if (bytes.get() == nullptr) { + return nullptr; + } -static jstring NativeCrypto_EVP_PKEY_print_public(JNIEnv* env, jclass, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - return evp_print_func(env, pkeyRef, EVP_PKEY_print_public, "EVP_PKEY_print_public"); -} + bssl::UniquePtr ec(EC_KEY_new_by_curve_name(nid)); + if (!ec) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, + "EC_KEY_new_by_curve_name failed"); + return nullptr; + } -static jstring NativeCrypto_EVP_PKEY_print_params(JNIEnv* env, jclass, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - return evp_print_func(env, pkeyRef, EVP_PKEY_print_params, "EVP_PKEY_print_params"); -} + const uint8_t* ptr = reinterpret_cast(bytes.get()); + EC_KEY* raw_ec = ec.get(); + if (!o2i_ECPublicKey(&raw_ec, &ptr, bytes.size())) { + conscrypt::jniutil::throwParsingException(env, "Error parsing EC public key"); + ERR_clear_error(); + return nullptr; + } -static void NativeCrypto_EVP_PKEY_free(JNIEnv* env, jclass, jlong pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = reinterpret_cast(pkeyRef); - JNI_TRACE("EVP_PKEY_free(%p)", pkey); + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (!pkey) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_new failed"); + return nullptr; + } - if (pkey != nullptr) { - EVP_PKEY_free(pkey); + if (!EVP_PKEY_assign_EC_KEY(pkey.get(), ec.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_assign_EC_KEY"); + return nullptr; } -} + ec.release(); // pkey now owns the ec_key -static jint NativeCrypto_EVP_PKEY_cmp(JNIEnv* env, jclass, jobject pkey1Ref, jobject pkey2Ref) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_cmp(%p, %p)", pkey1Ref, pkey2Ref); - EVP_PKEY* pkey1 = fromContextObject(env, pkey1Ref); - if (pkey1 == nullptr) { - JNI_TRACE("EVP_PKEY_cmp => pkey1 == null"); - return 0; + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 0)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + return nullptr; } - EVP_PKEY* pkey2 = fromContextObject(env, pkey2Ref); - if (pkey2 == nullptr) { - JNI_TRACE("EVP_PKEY_cmp => pkey2 == null"); - return 0; + if (!EVP_marshal_public_key(cbb.get(), pkey.get())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_public_key"); + return nullptr; } - JNI_TRACE("EVP_PKEY_cmp(%p, %p) <- ptr", pkey1, pkey2); - int result = EVP_PKEY_cmp(pkey1, pkey2); - JNI_TRACE("EVP_PKEY_cmp(%p, %p) => %d", pkey1, pkey2, result); + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(cbb.get(), &out_data, &out_len)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CBB_finish"); + return nullptr; + } + bssl::UniquePtr free_data(out_data); + + jbyteArray result = env->NewByteArray(out_len); + if (result == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); return result; } -/* - * static native byte[] EVP_marshal_private_key(long) - */ -static jbyteArray NativeCrypto_EVP_marshal_private_key(JNIEnv* env, jclass, jobject pkeyRef) { +static jbyteArray NativeCrypto_unwrap_RSA_private_key_pkcs8(JNIEnv* env, jclass, + jbyteArray rawKeyBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("EVP_marshal_private_key(%p)", pkey); + JNI_TRACE("NativeCrypto_unwrap_RSA_private_key_pkcs8"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; + } - if (pkey == nullptr) { + ScopedByteArrayRO bytes(env, rawKeyBytes); + if (bytes.get() == nullptr) { + return nullptr; + } + + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr pkey(EVP_parse_private_key(&cbs)); + if (!pkey || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwParsingException(env, "Error parsing PKCS#8 private key"); + ERR_clear_error(); + return nullptr; + } + + RSA* rsa = EVP_PKEY_get0_RSA(pkey.get()); + if (rsa == nullptr) { + conscrypt::jniutil::throwParsingException(env, "Key is not an RSA key"); + ERR_clear_error(); return nullptr; } bssl::ScopedCBB cbb; - if (!CBB_init(cbb.get(), 64)) { + if (!CBB_init(cbb.get(), 0)) { conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); - JNI_TRACE("CBB_init failed"); + return nullptr; + } + if (!RSA_marshal_private_key(cbb.get(), rsa)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "RSA_marshal_private_key"); return nullptr; } - if (!EVP_marshal_private_key(cbb.get(), pkey)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_private_key"); - JNI_TRACE("key=%p EVP_marshal_private_key => error", pkey); + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(cbb.get(), &out_data, &out_len)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CBB_finish"); return nullptr; } + bssl::UniquePtr free_data(out_data); - return CBBToByteArray(env, cbb.get()); + jbyteArray result = env->NewByteArray(out_len); + if (result == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); + return result; } -/* - * static native long EVP_parse_private_key(byte[]) - */ -static jlong NativeCrypto_EVP_parse_private_key(JNIEnv* env, jclass, jbyteArray keyJavaBytes) { +static jbyteArray NativeCrypto_unwrap_RSA_public_key_x509(JNIEnv* env, jclass, + jbyteArray rawKeyBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_parse_private_key(%p)", keyJavaBytes); + JNI_TRACE("NativeCrypto_unwrap_RSA_public_key_x509"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; + } - ScopedByteArrayRO bytes(env, keyJavaBytes); + ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - JNI_TRACE("bytes=%p EVP_parse_private_key => threw exception", keyJavaBytes); - return 0; + return nullptr; } CBS cbs; CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); - bssl::UniquePtr pkey(EVP_parse_private_key(&cbs)); - // We intentionally do not check that cbs is exhausted, as JCA providers - // typically allow parsing keys from buffers that are larger than the - // contained key structure so we do the same for compatibility. - if (!pkey) { - conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); + bssl::UniquePtr pkey(EVP_parse_public_key(&cbs)); + if (!pkey || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwParsingException(env, "Error parsing X.509 SPKI public key"); ERR_clear_error(); - JNI_TRACE("bytes=%p EVP_parse_private_key => threw exception", keyJavaBytes); - return 0; + return nullptr; } - JNI_TRACE("bytes=%p EVP_parse_private_key => %p", keyJavaBytes, pkey.get()); - return reinterpret_cast(pkey.release()); -} + RSA* rsa = EVP_PKEY_get0_RSA(pkey.get()); + if (rsa == nullptr) { + conscrypt::jniutil::throwParsingException(env, "Key is not an RSA key"); + ERR_clear_error(); + return nullptr; + } -static const EVP_PKEY_ALG* GetAlg(int pkeyType) { - switch (pkeyType) { - case EVP_PKEY_ED25519: - return EVP_pkey_ed25519(); - case EVP_PKEY_X25519: - return EVP_pkey_x25519(); - case EVP_PKEY_ML_DSA_44: - return EVP_pkey_ml_dsa_44(); - case EVP_PKEY_ML_DSA_65: - return EVP_pkey_ml_dsa_65(); - case EVP_PKEY_ML_DSA_87: - return EVP_pkey_ml_dsa_87(); - default: - return nullptr; + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 0)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + return nullptr; + } + if (!RSA_marshal_public_key(cbb.get(), rsa)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "RSA_marshal_public_key"); + return nullptr; + } + + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(cbb.get(), &out_data, &out_len)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CBB_finish"); + return nullptr; } + bssl::UniquePtr free_data(out_data); + + jbyteArray result = env->NewByteArray(out_len); + if (result == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); + return result; } -static jlong NativeCrypto_EVP_PKEY_from_private_key_info(JNIEnv* env, jclass, - jbyteArray key_java_bytes, - jintArray algs) { +static jbyteArray NativeCrypto_unwrap_EC_private_key_pkcs8(JNIEnv* env, jclass, + jbyteArray rawKeyBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_from_private_key_info(_, %p)", algs); - - if (key_java_bytes == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); - return 0; + JNI_TRACE("NativeCrypto_unwrap_EC_private_key_pkcs8"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; } - ScopedByteArrayRO bytes(env, key_java_bytes); + ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - JNI_TRACE("EVP_PKEY_from_private_key_info => threw exception"); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for bytes"); - return 0; + return nullptr; } - size_t num_algs = static_cast(env->GetArrayLength(algs)); - if (num_algs == 0) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "algs.length == 0"); - return 0; - } - ScopedIntArrayRO algs_ro(env, algs); - std::vector alg_pointers(num_algs); - for (size_t i = 0; i < num_algs; ++i) { - const EVP_PKEY_ALG* alg = GetAlg(algs_ro.get()[i]); - if (alg == nullptr) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "unsupported pkeyType"); - return 0; - } - alg_pointers[i] = alg; + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr pkey(EVP_parse_private_key(&cbs)); + if (!pkey || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwParsingException(env, "Error parsing PKCS#8 private key"); + ERR_clear_error(); + return nullptr; } - bssl::UniquePtr pkey( - EVP_PKEY_from_private_key_info(reinterpret_cast(bytes.get()), - bytes.size(), alg_pointers.data(), alg_pointers.size())); - if (pkey.get() == nullptr) { - conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); + EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get()); + if (ec == nullptr) { + conscrypt::jniutil::throwParsingException(env, "Key is not an EC key"); ERR_clear_error(); - JNI_TRACE("EVP_PKEY_from_private_key_info => threw exception"); - return 0; + return nullptr; } - return reinterpret_cast(pkey.release()); + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 0)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + return nullptr; + } + if (!EC_KEY_marshal_private_key(cbb.get(), ec, EC_PKEY_NO_PUBKEY)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_marshal_private_key"); + return nullptr; + } + + uint8_t* out_data; + size_t out_len; + if (!CBB_finish(cbb.get(), &out_data, &out_len)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CBB_finish"); + return nullptr; + } + bssl::UniquePtr free_data(out_data); + + jbyteArray result = env->NewByteArray(out_len); + if (result == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); + return result; } -static jlong NativeCrypto_EVP_PKEY_from_subject_public_key_info(JNIEnv* env, jclass, - jbyteArray key_java_bytes, - jintArray algs) { +static jbyteArray NativeCrypto_unwrap_EC_public_key_x509(JNIEnv* env, jclass, + jbyteArray rawKeyBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_from_subject_public_key_info(%p, %p)", key_java_bytes, algs); - - if (key_java_bytes == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); - return 0; + JNI_TRACE("NativeCrypto_unwrap_EC_public_key_x509"); + if (rawKeyBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "rawKeyBytes == null"); + return nullptr; } - ScopedByteArrayRO bytes(env, key_java_bytes); + ScopedByteArrayRO bytes(env, rawKeyBytes); if (bytes.get() == nullptr) { - JNI_TRACE("bytes=%p EVP_PKEY_from_subject_public_key_info => threw exception", - key_java_bytes); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for bytes"); - return 0; + return nullptr; } - size_t num_algs = static_cast(env->GetArrayLength(algs)); - if (num_algs == 0) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "algs.length == 0"); - return 0; - } - ScopedIntArrayRO algs_ro(env, algs); - std::vector alg_pointers(num_algs); - for (size_t i = 0; i < num_algs; ++i) { - const EVP_PKEY_ALG* alg = GetAlg(algs_ro.get()[i]); - if (alg == nullptr) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "unsupported pkeyType"); - return 0; - } - alg_pointers[i] = alg; + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr pkey(EVP_parse_public_key(&cbs)); + if (!pkey || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwParsingException(env, "Error parsing X.509 SPKI public key"); + ERR_clear_error(); + return nullptr; } - bssl::UniquePtr pkey(EVP_PKEY_from_subject_public_key_info( - reinterpret_cast(bytes.get()), bytes.size(), alg_pointers.data(), - alg_pointers.size())); - if (pkey.get() == nullptr) { - conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); + EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get()); + if (ec == nullptr) { + conscrypt::jniutil::throwParsingException(env, "Key is not an EC key"); ERR_clear_error(); - JNI_TRACE("bytes=%p EVP_PKEY_from_subject_public_key_info => threw exception", - key_java_bytes); - return 0; + return nullptr; } - JNI_TRACE("bytes=%p EVP_PKEY_from_subject_public_key_info => %p", key_java_bytes, pkey.get()); - return reinterpret_cast(pkey.release()); + uint8_t* out_data = nullptr; + int out_len = i2o_ECPublicKey(ec, &out_data); + if (out_len <= 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "i2o_ECPublicKey"); + return nullptr; + } + bssl::UniquePtr free_data(out_data); + + jbyteArray result = env->NewByteArray(out_len); + if (result == nullptr) { + return nullptr; + } + env->SetByteArrayRegion(result, 0, out_len, reinterpret_cast(out_data)); + return result; } -static jlong NativeCrypto_EVP_PKEY_from_raw_private_key(JNIEnv* env, jclass, jint pkey_type, - jbyteArray key_java_bytes) { +/** + * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, + * byte[] p, byte[] q); + */ +static jlong NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass, jbyteArray n, jbyteArray e, + jbyteArray d, jbyteArray p, jbyteArray q, + jbyteArray dmp1, jbyteArray dmq1, jbyteArray iqmp) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_from_raw_private_key(%p)", key_java_bytes); + JNI_TRACE( + "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p, dmp1=%p, dmq1=%p, " + "iqmp=%p)", + n, e, d, p, q, dmp1, dmq1, iqmp); - if (key_java_bytes == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); + if (e == nullptr && d == nullptr) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "e == null && d == null"); + JNI_TRACE("NativeCrypto_EVP_PKEY_new_RSA => e == null && d == null"); return 0; } - ScopedByteArrayRO bytes(env, key_java_bytes); - if (bytes.get() == nullptr) { - JNI_TRACE("key_java_bytes=%p EVP_PKEY_from_raw_private_key => threw exception", - key_java_bytes); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for bytes"); + bssl::UniquePtr nBN, eBN, dBN, pBN, qBN, dmp1BN, dmq1BN, iqmpBN; + nBN = arrayToBignum(env, n); + if (!nBN) { return 0; } + if (e != nullptr) { + eBN = arrayToBignum(env, e); + if (!eBN) { + return 0; + } + } + if (d != nullptr) { + dBN = arrayToBignum(env, d); + if (!dBN) { + return 0; + } + } + if (p != nullptr) { + pBN = arrayToBignum(env, p); + if (!pBN) { + return 0; + } + } + if (q != nullptr) { + qBN = arrayToBignum(env, q); + if (!qBN) { + return 0; + } + } + if (dmp1 != nullptr) { + dmp1BN = arrayToBignum(env, dmp1); + if (!dmp1BN) { + return 0; + } + } + if (dmq1 != nullptr) { + dmq1BN = arrayToBignum(env, dmq1); + if (!dmq1BN) { + return 0; + } + } + if (iqmp != nullptr) { + iqmpBN = arrayToBignum(env, iqmp); + if (!iqmpBN) { + return 0; + } + } - const EVP_PKEY_ALG* alg; - if (pkey_type == EVP_PKEY_ED25519) { - alg = EVP_pkey_ed25519(); - } else if (pkey_type == EVP_PKEY_X25519) { - alg = EVP_pkey_x25519(); + // Determine what kind of key this is. + // + // TODO(davidben): The caller already knows what kind of key they expect. + // Ideally we would have separate APIs for the caller. However, we currently + // tolerate, say, an RSAPrivateCrtKeySpec where most fields are null and + // silently make a public key out of it. This is probably a mistake, but would + // need to be a breaking change. + bssl::UniquePtr rsa; + if (!dBN) { + rsa.reset(RSA_new_public_key(nBN.get(), eBN.get())); + } else if (!eBN) { + rsa.reset(RSA_new_private_key_no_e(nBN.get(), dBN.get())); + } else if (!pBN || !qBN || !dmp1BN || !dmq1BN || !iqmpBN) { + rsa.reset(RSA_new_private_key_no_crt(nBN.get(), eBN.get(), dBN.get())); } else { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "unsupported pkey_type"); + rsa.reset(RSA_new_private_key(nBN.get(), eBN.get(), dBN.get(), pBN.get(), qBN.get(), + dmp1BN.get(), dmq1BN.get(), iqmpBN.get())); + } + if (rsa == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_new_RSA"); return 0; } - bssl::UniquePtr pkey(EVP_PKEY_from_raw_private_key( - alg, reinterpret_cast(bytes.get()), bytes.size())); - if (!pkey) { - conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (pkey.get() == nullptr) { + conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_new failed"); ERR_clear_error(); - JNI_TRACE("key_java_bytes=%p EVP_PKEY_from_raw_private_key => threw exception", - key_java_bytes); return 0; } - - JNI_TRACE("key_java_bytes=%p EVP_PKEY_from_raw_private_key => %p", key_java_bytes, pkey.get()); + if (EVP_PKEY_assign_RSA(pkey.get(), rsa.get()) != 1) { + conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_new failed"); + ERR_clear_error(); + return 0; + } + OWNERSHIP_TRANSFERRED(rsa); + JNI_TRACE( + "EVP_PKEY_new_RSA(n=%p, e=%p, d=%p, p=%p, q=%p dmp1=%p, dmq1=%p, " + "iqmp=%p) => %p", + n, e, d, p, q, dmp1, dmq1, iqmp, pkey.get()); return reinterpret_cast(pkey.release()); } -static jbyteArray NativeCrypto_EVP_PKEY_get_raw_private_key(JNIEnv* env, jclass, jobject pkey_ref) { +static jlong NativeCrypto_EVP_PKEY_new_EC_KEY(JNIEnv* env, jclass, jobject groupRef, + jobject pubkeyRef, jbyteArray keyJavaBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_get_raw_private_key(%p)", pkey_ref); - - EVP_PKEY* pkey = fromContextObject(env, pkey_ref); - if (pkey == nullptr) { - return nullptr; + JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p)", groupRef, pubkeyRef, keyJavaBytes); + const EC_GROUP* group = fromContextObject(env, groupRef); + if (group == nullptr) { + return 0; } + const EC_POINT* pubkey = + pubkeyRef == nullptr ? nullptr : fromContextObject(env, pubkeyRef); + JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) <- ptr", group, pubkey, keyJavaBytes); - size_t key_length = 0; - if (EVP_PKEY_get_raw_private_key(pkey, nullptr, &key_length) == 0) { - JNI_TRACE("key=%p EVP_PKEY_get_raw_private_key => error", pkey); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_private_key"); - return nullptr; + bssl::UniquePtr key(nullptr); + if (keyJavaBytes != nullptr) { + key = arrayToBignum(env, keyJavaBytes); + if (!key) { + return 0; + } } - ScopedLocalRef raw_key_array(env, - env->NewByteArray(static_cast(key_length))); - - if (raw_key_array.get() == nullptr) { - JNI_TRACE("key=%p EVP_PKEY_get_raw_private_key: creating byte array failed", pkey); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key_array"); - return nullptr; - } - ScopedByteArrayRW raw_key(env, raw_key_array.get()); - if (raw_key.get() == nullptr) { - JNI_TRACE("EVP_PKEY_get_raw_private_key: using byte array failed"); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key"); - return nullptr; + bssl::UniquePtr eckey(EC_KEY_new()); + if (eckey.get() == nullptr) { + conscrypt::jniutil::throwRuntimeException(env, "EC_KEY_new failed"); + return 0; } - if (EVP_PKEY_get_raw_private_key(pkey, reinterpret_cast(raw_key.get()), - &key_length) == 0) { - JNI_TRACE("key=%p EVP_PKEY_get_raw_private_key => error", pkey); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_private_key"); - return nullptr; + if (EC_KEY_set_group(eckey.get(), group) != 1) { + JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) > EC_KEY_set_group failed", group, pubkey, + keyJavaBytes); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); + return 0; } - return raw_key_array.release(); -} -static jlong NativeCrypto_EVP_PKEY_from_raw_public_key(JNIEnv* env, jclass, jint pkey_type, - jbyteArray key_java_bytes) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_from_raw_public_key(%p)", key_java_bytes); + if (pubkey != nullptr) { + if (EC_KEY_set_public_key(eckey.get(), pubkey) != 1) { + JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => EC_KEY_set_private_key failed", group, + pubkey, keyJavaBytes); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_public_key"); + return 0; + } + } - if (key_java_bytes == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); - return 0; + if (key.get() != nullptr) { + if (EC_KEY_set_private_key(eckey.get(), key.get()) != 1) { + JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => EC_KEY_set_private_key failed", group, + pubkey, keyJavaBytes); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_private_key"); + return 0; + } + if (pubkey == nullptr) { + bssl::UniquePtr calcPubkey(EC_POINT_new(group)); + if (!EC_POINT_mul(group, calcPubkey.get(), key.get(), nullptr, nullptr, nullptr)) { + JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => can't calculate public key", group, + pubkey, keyJavaBytes); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_private_key"); + return 0; + } + EC_KEY_set_public_key(eckey.get(), calcPubkey.get()); + } } - ScopedByteArrayRO bytes(env, key_java_bytes); - if (bytes.get() == nullptr) { - JNI_TRACE("bytes=%p EVP_PKEY_from_raw_public_key => threw exception", key_java_bytes); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for key_java_bytes"); + if (!EC_KEY_check_key(eckey.get())) { + JNI_TRACE("EVP_KEY_new_EC_KEY(%p, %p, %p) => invalid key created", group, pubkey, + keyJavaBytes); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_check_key"); return 0; } - const EVP_PKEY_ALG* alg = GetAlg(pkey_type); - if (alg == nullptr) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "unsupported pkeyType"); + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (pkey.get() == nullptr) { + JNI_TRACE("EVP_PKEY_new_EC(%p, %p, %p) => threw error", group, pubkey, keyJavaBytes); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_new failed"); return 0; } - - bssl::UniquePtr pkey(EVP_PKEY_from_raw_public_key( - alg, reinterpret_cast(bytes.get()), bytes.size())); - if (!pkey) { - conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); + if (EVP_PKEY_assign_EC_KEY(pkey.get(), eckey.get()) != 1) { + JNI_TRACE("EVP_PKEY_new_EC(%p, %p, %p) => threw error", group, pubkey, keyJavaBytes); + conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_assign_EC_KEY failed"); ERR_clear_error(); - JNI_TRACE("bytes=%p EVP_PKEY_from_raw_public_key => threw exception", key_java_bytes); return 0; } + OWNERSHIP_TRANSFERRED(eckey); - JNI_TRACE("bytes=%p EVP_PKEY_from_raw_public_key => %p", key_java_bytes, pkey.get()); + JNI_TRACE("EVP_PKEY_new_EC_KEY(%p, %p, %p) => %p", group, pubkey, keyJavaBytes, pkey.get()); return reinterpret_cast(pkey.release()); } -static jbyteArray NativeCrypto_EVP_PKEY_get_raw_public_key(JNIEnv* env, jclass, jobject pkey_ref) { +static int NativeCrypto_EVP_PKEY_type(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_get_raw_public_key(%p)", pkey_ref); + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("EVP_PKEY_type(%p)", pkey); - EVP_PKEY* pkey = fromContextObject(env, pkey_ref); if (pkey == nullptr) { - return nullptr; + return -1; } - size_t key_length = 0; - if (EVP_PKEY_get_raw_public_key(pkey, nullptr, &key_length) == 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_public_key"); - return nullptr; - } - - ScopedLocalRef raw_key_array(env, - env->NewByteArray(static_cast(key_length))); - if (raw_key_array.get() == nullptr) { - JNI_TRACE("EVP_PKEY_get_raw_public_key: creating byte array failed"); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key_array"); - return nullptr; - } - ScopedByteArrayRW raw_key(env, raw_key_array.get()); - if (raw_key.get() == nullptr) { - JNI_TRACE("EVP_PKEY_get_raw_public_key: using byte array failed"); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key"); - return nullptr; - } - - if (EVP_PKEY_get_raw_public_key(pkey, reinterpret_cast(raw_key.get()), &key_length) == - 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_public_key"); - return nullptr; - } - return raw_key_array.release(); -} - -static jlong NativeCrypto_EVP_PKEY_from_private_seed(JNIEnv* env, jclass, jint pkeyType, - jbyteArray javaSeedBytes) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_from_private_seed(%p)", javaSeedBytes); - - if (javaSeedBytes == nullptr) { - JNI_TRACE("EVP_PKEY_from_private_seed => threw exception"); - conscrypt::jniutil::throwNullPointerException(env, "javaSeedBytes == null"); - return 0; - } - - ScopedByteArrayRO seed(env, javaSeedBytes); - if (seed.get() == nullptr) { - JNI_TRACE("bytes=%p EVP_PKEY_from_private_seed => threw exception", javaSeedBytes); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for seed"); - return 0; - } - - const EVP_PKEY_ALG* alg; - if (pkeyType == EVP_PKEY_ML_DSA_44) { - alg = EVP_pkey_ml_dsa_44(); - } else if (pkeyType == EVP_PKEY_ML_DSA_65) { - alg = EVP_pkey_ml_dsa_65(); - } else if (pkeyType == EVP_PKEY_ML_DSA_87) { - alg = EVP_pkey_ml_dsa_87(); - } else { - conscrypt::jniutil::throwInvalidKeyException(env, "unsupported pkeyType"); - return 0; - } - - bssl::UniquePtr pkey(EVP_PKEY_from_private_seed( - alg, reinterpret_cast(seed.get()), seed.size())); - - if (!pkey) { - conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); - ERR_clear_error(); - JNI_TRACE("bytes=%p EVP_PKEY_from_private_seed => threw exception", javaSeedBytes); - return 0; - } - - JNI_TRACE("bytes=%p EVP_PKEY_from_private_seed => %p", javaSeedBytes, pkey.get()); - return reinterpret_cast(pkey.release()); + int result = EVP_PKEY_id(pkey); + JNI_TRACE("EVP_PKEY_type(%p) => %d", pkey, result); + return result; } -static jbyteArray NativeCrypto_EVP_PKEY_get_private_seed(JNIEnv* env, jclass, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_PKEY_get_private_seed(%p)", pkeyRef); +typedef int print_func(BIO*, const EVP_PKEY*, int, ASN1_PCTX*); +static jstring evp_print_func(JNIEnv* env, jobject pkeyRef, print_func* func, + const char* debug_name) { EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("%s(%p)", debug_name, pkey); + if (pkey == nullptr) { return nullptr; } - size_t seed_length = 0; - if (EVP_PKEY_get_private_seed(pkey, nullptr, &seed_length) == 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_private_seed"); + bssl::UniquePtr buffer(BIO_new(BIO_s_mem())); + if (buffer.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate BIO"); return nullptr; } - ScopedLocalRef seedArray(env, env->NewByteArray(static_cast(seed_length))); - if (seedArray.get() == nullptr) { - JNI_TRACE("EVP_PKEY_get_raw_private_key: creating byte array failed"); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for seedArray"); - return nullptr; - } - ScopedByteArrayRW seed(env, seedArray.get()); - if (seed.get() == nullptr) { - JNI_TRACE("EVP_PKEY_get_raw_private_key: using byte array failed"); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for seed"); + if (func(buffer.get(), pkey, 0, nullptr) != 1) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, debug_name); return nullptr; } + // Null terminate this + BIO_write(buffer.get(), "\0", 1); - if (EVP_PKEY_get_private_seed(pkey, reinterpret_cast(seed.get()), &seed_length) == - 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_private_seed"); - return nullptr; - } - return seedArray.release(); + char* tmp; + BIO_get_mem_data(buffer.get(), &tmp); + jstring description = env->NewStringUTF(tmp); + + JNI_TRACE("%s(%p) => \"%s\"", debug_name, pkey, tmp); + return description; } -static jbyteArray NativeCrypto_EVP_raw_X25519_private_key(JNIEnv* env, jclass cls, - jbyteArray keyJavaBytes) { +static jstring NativeCrypto_EVP_PKEY_print_public(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("NativeCrypto_EVP_raw_X25519_private_key(%p)", keyJavaBytes); + return evp_print_func(env, pkeyRef, EVP_PKEY_print_public, "EVP_PKEY_print_public"); +} - jlong key_ptr = NativeCrypto_EVP_parse_private_key(env, cls, keyJavaBytes); - if (key_ptr == 0) { - return nullptr; - } - bssl::UniquePtr pkey(reinterpret_cast(key_ptr)); - if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_X25519) { - conscrypt::jniutil::throwInvalidKeyException(env, "Invalid key type"); - return nullptr; - } +static jstring NativeCrypto_EVP_PKEY_print_params(JNIEnv* env, jclass, jobject pkeyRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + return evp_print_func(env, pkeyRef, EVP_PKEY_print_params, "EVP_PKEY_print_params"); +} - size_t key_length = X25519_PRIVATE_KEY_LEN; - ScopedLocalRef byteArray(env, env->NewByteArray(static_cast(key_length))); - if (byteArray.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Allocating byte[]"); - JNI_TRACE("NativeCrypto_EVP_raw_X25519_private_key: byte array creation failed"); - return nullptr; - } +static void NativeCrypto_EVP_PKEY_free(JNIEnv* env, jclass, jlong pkeyRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY* pkey = reinterpret_cast(pkeyRef); + JNI_TRACE("EVP_PKEY_free(%p)", pkey); - ScopedByteArrayRW bytes(env, byteArray.get()); - if (bytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Allocating scoped byte array"); - JNI_TRACE("NativeCrypto_EVP_raw_X25519_private_key: scoped byte array failed"); - return nullptr; + if (pkey != nullptr) { + EVP_PKEY_free(pkey); } +} - if (EVP_PKEY_get_raw_private_key(pkey.get(), reinterpret_cast(bytes.get()), - &key_length) == 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_private_key"); - return nullptr; +static jint NativeCrypto_EVP_PKEY_cmp(JNIEnv* env, jclass, jobject pkey1Ref, jobject pkey2Ref) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EVP_PKEY_cmp(%p, %p)", pkey1Ref, pkey2Ref); + EVP_PKEY* pkey1 = fromContextObject(env, pkey1Ref); + if (pkey1 == nullptr) { + JNI_TRACE("EVP_PKEY_cmp => pkey1 == null"); + return 0; } - jbyteArray result = byteArray.release(); - JNI_TRACE("bytes=%p NativeCrypto_EVP_raw_X25519_private_key => %p", keyJavaBytes, result); + EVP_PKEY* pkey2 = fromContextObject(env, pkey2Ref); + if (pkey2 == nullptr) { + JNI_TRACE("EVP_PKEY_cmp => pkey2 == null"); + return 0; + } + JNI_TRACE("EVP_PKEY_cmp(%p, %p) <- ptr", pkey1, pkey2); + + int result = EVP_PKEY_cmp(pkey1, pkey2); + JNI_TRACE("EVP_PKEY_cmp(%p, %p) => %d", pkey1, pkey2, result); return result; } /* - * static native byte[] EVP_marshal_public_key(long) + * static native byte[] EVP_marshal_private_key(long) */ -static jbyteArray NativeCrypto_EVP_marshal_public_key(JNIEnv* env, jclass, jobject pkeyRef) { +static jbyteArray NativeCrypto_EVP_marshal_private_key(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("EVP_marshal_public_key(%p)", pkey); + JNI_TRACE("EVP_marshal_private_key(%p)", pkey); if (pkey == nullptr) { return nullptr; @@ -1576,9 +1604,9 @@ static jbyteArray NativeCrypto_EVP_marshal_public_key(JNIEnv* env, jclass, jobje return nullptr; } - if (!EVP_marshal_public_key(cbb.get(), pkey)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_public_key"); - JNI_TRACE("key=%p EVP_marshal_public_key => error", pkey); + if (!EVP_marshal_private_key(cbb.get(), pkey)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_private_key"); + JNI_TRACE("key=%p EVP_marshal_private_key => error", pkey); return nullptr; } @@ -1586,1696 +1614,1752 @@ static jbyteArray NativeCrypto_EVP_marshal_public_key(JNIEnv* env, jclass, jobje } /* - * static native long EVP_parse_public_key(byte[]) + * static native long EVP_parse_private_key(byte[]) */ -static jlong NativeCrypto_EVP_parse_public_key(JNIEnv* env, jclass, jbyteArray keyJavaBytes) { +static jlong NativeCrypto_EVP_parse_private_key(JNIEnv* env, jclass, jbyteArray keyJavaBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_parse_public_key(%p)", keyJavaBytes); + JNI_TRACE("EVP_parse_private_key(%p)", keyJavaBytes); ScopedByteArrayRO bytes(env, keyJavaBytes); if (bytes.get() == nullptr) { - JNI_TRACE("bytes=%p EVP_parse_public_key => threw exception", keyJavaBytes); + JNI_TRACE("bytes=%p EVP_parse_private_key => threw exception", keyJavaBytes); return 0; } CBS cbs; CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); - bssl::UniquePtr pkey(EVP_parse_public_key(&cbs)); + bssl::UniquePtr pkey(EVP_parse_private_key(&cbs)); // We intentionally do not check that cbs is exhausted, as JCA providers // typically allow parsing keys from buffers that are larger than the // contained key structure so we do the same for compatibility. if (!pkey) { - conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); + conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); ERR_clear_error(); - JNI_TRACE("bytes=%p EVP_parse_public_key => threw exception", keyJavaBytes); + JNI_TRACE("bytes=%p EVP_parse_private_key => threw exception", keyJavaBytes); return 0; } - JNI_TRACE("bytes=%p EVP_parse_public_key => %p", keyJavaBytes, pkey.get()); + JNI_TRACE("bytes=%p EVP_parse_private_key => %p", keyJavaBytes, pkey.get()); return reinterpret_cast(pkey.release()); } -static jlong NativeCrypto_getRSAPrivateKeyWrapper(JNIEnv* env, jclass, jobject javaKey, - jbyteArray modulusBytes) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("getRSAPrivateKeyWrapper(%p, %p)", javaKey, modulusBytes); +static const EVP_PKEY_ALG* GetAlg(int pkeyType) { + switch (pkeyType) { + case EVP_PKEY_ED25519: + return EVP_pkey_ed25519(); + case EVP_PKEY_X25519: + return EVP_pkey_x25519(); + case EVP_PKEY_ML_DSA_44: + return EVP_pkey_ml_dsa_44(); + case EVP_PKEY_ML_DSA_65: + return EVP_pkey_ml_dsa_65(); + case EVP_PKEY_ML_DSA_87: + return EVP_pkey_ml_dsa_87(); + default: + return nullptr; + } +} - ensure_engine_globals(); +static jlong NativeCrypto_EVP_PKEY_from_private_key_info(JNIEnv* env, jclass, + jbyteArray key_java_bytes, + jintArray algs) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EVP_PKEY_from_private_key_info(_, %p)", algs); - // The PSS padding code needs access to the actual n, so set it even though we - // don't set any other parts of the key - bssl::UniquePtr n = arrayToBignum(env, modulusBytes); - if (n == nullptr) { + if (key_java_bytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); return 0; } - // TODO(crbug.com/boringssl/602): RSA_METHOD is not the ideal abstraction to - // use here. - bssl::UniquePtr rsa(RSA_new_method_no_e(g_engine, n.get())); - if (rsa == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); + ScopedByteArrayRO bytes(env, key_java_bytes); + if (bytes.get() == nullptr) { + JNI_TRACE("EVP_PKEY_from_private_key_info => threw exception"); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for bytes"); return 0; } - auto ex_data = new KeyExData; - ex_data->private_key = env->NewGlobalRef(javaKey); - RSA_set_ex_data(rsa.get(), g_rsa_exdata_index, ex_data); - - bssl::UniquePtr pkey(EVP_PKEY_new()); - if (pkey.get() == nullptr) { - JNI_TRACE("getRSAPrivateKeyWrapper failed"); - conscrypt::jniutil::throwRuntimeException(env, - "NativeCrypto_getRSAPrivateKeyWrapper failed"); - ERR_clear_error(); + size_t num_algs = static_cast(env->GetArrayLength(algs)); + if (num_algs == 0) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "algs.length == 0"); return 0; } - - if (EVP_PKEY_assign_RSA(pkey.get(), rsa.get()) != 1) { - conscrypt::jniutil::throwRuntimeException(env, "getRSAPrivateKeyWrapper failed"); + ScopedIntArrayRO algs_ro(env, algs); + std::vector alg_pointers(num_algs); + for (size_t i = 0; i < num_algs; ++i) { + const EVP_PKEY_ALG* alg = GetAlg(algs_ro.get()[i]); + if (alg == nullptr) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "unsupported pkeyType"); + return 0; + } + alg_pointers[i] = alg; + } + + bssl::UniquePtr pkey( + EVP_PKEY_from_private_key_info(reinterpret_cast(bytes.get()), + bytes.size(), alg_pointers.data(), alg_pointers.size())); + if (pkey.get() == nullptr) { + conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); ERR_clear_error(); + JNI_TRACE("EVP_PKEY_from_private_key_info => threw exception"); return 0; } - OWNERSHIP_TRANSFERRED(rsa); - JNI_TRACE("getRSAPrivateKeyWrapper(%p, %p) => %p", javaKey, modulusBytes, pkey.get()); + return reinterpret_cast(pkey.release()); } -static jlong NativeCrypto_getECPrivateKeyWrapper(JNIEnv* env, jclass, jobject javaKey, - jobject groupRef) { +static jlong NativeCrypto_EVP_PKEY_from_subject_public_key_info(JNIEnv* env, jclass, + jbyteArray key_java_bytes, + jintArray algs) { CHECK_ERROR_QUEUE_ON_RETURN; - EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("getECPrivateKeyWrapper(%p, %p)", javaKey, group); - if (group == nullptr) { - return 0; - } - - ensure_engine_globals(); + JNI_TRACE("EVP_PKEY_from_subject_public_key_info(%p, %p)", key_java_bytes, algs); - bssl::UniquePtr ecKey(EC_KEY_new_method(g_engine)); - if (ecKey.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate EC key"); + if (key_java_bytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); return 0; } - if (EC_KEY_set_group(ecKey.get(), group) != 1) { - JNI_TRACE("getECPrivateKeyWrapper(%p, %p) => EC_KEY_set_group error", javaKey, group); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); + ScopedByteArrayRO bytes(env, key_java_bytes); + if (bytes.get() == nullptr) { + JNI_TRACE("bytes=%p EVP_PKEY_from_subject_public_key_info => threw exception", + key_java_bytes); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for bytes"); return 0; } - auto ex_data = new KeyExData; - ex_data->private_key = env->NewGlobalRef(javaKey); - - if (!EC_KEY_set_ex_data(ecKey.get(), g_ecdsa_exdata_index, ex_data)) { - env->DeleteGlobalRef(ex_data->private_key); - delete ex_data; - conscrypt::jniutil::throwRuntimeException(env, "EC_KEY_set_ex_data"); - ERR_clear_error(); + size_t num_algs = static_cast(env->GetArrayLength(algs)); + if (num_algs == 0) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "algs.length == 0"); return 0; } + ScopedIntArrayRO algs_ro(env, algs); + std::vector alg_pointers(num_algs); + for (size_t i = 0; i < num_algs; ++i) { + const EVP_PKEY_ALG* alg = GetAlg(algs_ro.get()[i]); + if (alg == nullptr) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "unsupported pkeyType"); + return 0; + } + alg_pointers[i] = alg; + } - bssl::UniquePtr pkey(EVP_PKEY_new()); + bssl::UniquePtr pkey(EVP_PKEY_from_subject_public_key_info( + reinterpret_cast(bytes.get()), bytes.size(), alg_pointers.data(), + alg_pointers.size())); if (pkey.get() == nullptr) { - JNI_TRACE("getECPrivateKeyWrapper failed"); - conscrypt::jniutil::throwRuntimeException(env, - "NativeCrypto_getECPrivateKeyWrapper failed"); + conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); ERR_clear_error(); + JNI_TRACE("bytes=%p EVP_PKEY_from_subject_public_key_info => threw exception", + key_java_bytes); return 0; } - if (EVP_PKEY_assign_EC_KEY(pkey.get(), ecKey.get()) != 1) { - conscrypt::jniutil::throwRuntimeException(env, "getECPrivateKeyWrapper failed"); - ERR_clear_error(); - return 0; - } - OWNERSHIP_TRANSFERRED(ecKey); + JNI_TRACE("bytes=%p EVP_PKEY_from_subject_public_key_info => %p", key_java_bytes, pkey.get()); return reinterpret_cast(pkey.release()); } -/* - * public static native int RSA_generate_key(int modulusBits, byte[] - * publicExponent); - */ -static jlong NativeCrypto_RSA_generate_key_ex(JNIEnv* env, jclass, jint modulusBits, - jbyteArray publicExponent) { +static jlong NativeCrypto_EVP_PKEY_from_raw_private_key(JNIEnv* env, jclass, jint pkey_type, + jbyteArray key_java_bytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("RSA_generate_key_ex(%d, %p)", modulusBits, publicExponent); - - bssl::UniquePtr e = arrayToBignum(env, publicExponent); - if (e == nullptr) { - return 0; - } + JNI_TRACE("EVP_PKEY_from_raw_private_key(%p)", key_java_bytes); - bssl::UniquePtr rsa(RSA_new()); - if (rsa.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); + if (key_java_bytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); return 0; } - if (RSA_generate_key_ex(rsa.get(), modulusBits, e.get(), nullptr) != 1) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "RSA_generate_key_ex failed"); + ScopedByteArrayRO bytes(env, key_java_bytes); + if (bytes.get() == nullptr) { + JNI_TRACE("key_java_bytes=%p EVP_PKEY_from_raw_private_key => threw exception", + key_java_bytes); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for bytes"); return 0; } - bssl::UniquePtr pkey(EVP_PKEY_new()); - if (pkey.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); + const EVP_PKEY_ALG* alg; + if (pkey_type == EVP_PKEY_ED25519) { + alg = EVP_pkey_ed25519(); + } else if (pkey_type == EVP_PKEY_X25519) { + alg = EVP_pkey_x25519(); + } else { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "unsupported pkey_type"); return 0; } - if (EVP_PKEY_assign_RSA(pkey.get(), rsa.get()) != 1) { - conscrypt::jniutil::throwRuntimeException(env, "RSA_generate_key_ex failed"); + bssl::UniquePtr pkey(EVP_PKEY_from_raw_private_key( + alg, reinterpret_cast(bytes.get()), bytes.size())); + if (!pkey) { + conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); ERR_clear_error(); + JNI_TRACE("key_java_bytes=%p EVP_PKEY_from_raw_private_key => threw exception", + key_java_bytes); return 0; } - OWNERSHIP_TRANSFERRED(rsa); - JNI_TRACE("RSA_generate_key_ex(n=%d, e=%p) => %p", modulusBits, publicExponent, pkey.get()); + JNI_TRACE("key_java_bytes=%p EVP_PKEY_from_raw_private_key => %p", key_java_bytes, pkey.get()); return reinterpret_cast(pkey.release()); } -static jint NativeCrypto_RSA_size(JNIEnv* env, jclass, jobject pkeyRef) { +static jbyteArray NativeCrypto_EVP_PKEY_get_raw_private_key(JNIEnv* env, jclass, jobject pkey_ref) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("RSA_size(%p)", pkey); + JNI_TRACE("EVP_PKEY_get_raw_private_key(%p)", pkey_ref); + EVP_PKEY* pkey = fromContextObject(env, pkey_ref); if (pkey == nullptr) { + return nullptr; + } + + size_t key_length = 0; + if (EVP_PKEY_get_raw_private_key(pkey, nullptr, &key_length) == 0) { + JNI_TRACE("key=%p EVP_PKEY_get_raw_private_key => error", pkey); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_private_key"); + return nullptr; + } + + ScopedLocalRef raw_key_array(env, + env->NewByteArray(static_cast(key_length))); + + if (raw_key_array.get() == nullptr) { + JNI_TRACE("key=%p EVP_PKEY_get_raw_private_key: creating byte array failed", pkey); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key_array"); + return nullptr; + } + ScopedByteArrayRW raw_key(env, raw_key_array.get()); + if (raw_key.get() == nullptr) { + JNI_TRACE("EVP_PKEY_get_raw_private_key: using byte array failed"); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key"); + return nullptr; + } + + if (EVP_PKEY_get_raw_private_key(pkey, reinterpret_cast(raw_key.get()), + &key_length) == 0) { + JNI_TRACE("key=%p EVP_PKEY_get_raw_private_key => error", pkey); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_private_key"); + return nullptr; + } + return raw_key_array.release(); +} + +static jlong NativeCrypto_EVP_PKEY_from_raw_public_key(JNIEnv* env, jclass, jint pkey_type, + jbyteArray key_java_bytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EVP_PKEY_from_raw_public_key(%p)", key_java_bytes); + + if (key_java_bytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "key_java_bytes == null"); return 0; } - bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); - if (rsa.get() == nullptr) { - conscrypt::jniutil::throwRuntimeException(env, "RSA_size failed"); + ScopedByteArrayRO bytes(env, key_java_bytes); + if (bytes.get() == nullptr) { + JNI_TRACE("bytes=%p EVP_PKEY_from_raw_public_key => threw exception", key_java_bytes); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for key_java_bytes"); + return 0; + } + + const EVP_PKEY_ALG* alg = GetAlg(pkey_type); + if (alg == nullptr) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "unsupported pkeyType"); + return 0; + } + + bssl::UniquePtr pkey(EVP_PKEY_from_raw_public_key( + alg, reinterpret_cast(bytes.get()), bytes.size())); + if (!pkey) { + conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); ERR_clear_error(); + JNI_TRACE("bytes=%p EVP_PKEY_from_raw_public_key => threw exception", key_java_bytes); return 0; } - return static_cast(RSA_size(rsa.get())); + JNI_TRACE("bytes=%p EVP_PKEY_from_raw_public_key => %p", key_java_bytes, pkey.get()); + return reinterpret_cast(pkey.release()); } -typedef int RSACryptOperation(size_t flen, const unsigned char* from, unsigned char* to, RSA* rsa, - int padding); - -static jint RSA_crypt_operation(RSACryptOperation operation, const char* caller, JNIEnv* env, - jint flen, jbyteArray fromJavaBytes, jbyteArray toJavaBytes, - jobject pkeyRef, jint padding) { +static jbyteArray NativeCrypto_EVP_PKEY_get_raw_public_key(JNIEnv* env, jclass, jobject pkey_ref) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("%s(%d, %p, %p, %p)", caller, flen, fromJavaBytes, toJavaBytes, pkey); + JNI_TRACE("EVP_PKEY_get_raw_public_key(%p)", pkey_ref); + EVP_PKEY* pkey = fromContextObject(env, pkey_ref); if (pkey == nullptr) { - return -1; + return nullptr; } - bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); - if (rsa.get() == nullptr) { - return -1; + size_t key_length = 0; + if (EVP_PKEY_get_raw_public_key(pkey, nullptr, &key_length) == 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_public_key"); + return nullptr; } - ScopedByteArrayRO from(env, fromJavaBytes); - if (from.get() == nullptr) { - return -1; + ScopedLocalRef raw_key_array(env, + env->NewByteArray(static_cast(key_length))); + if (raw_key_array.get() == nullptr) { + JNI_TRACE("EVP_PKEY_get_raw_public_key: creating byte array failed"); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key_array"); + return nullptr; + } + ScopedByteArrayRW raw_key(env, raw_key_array.get()); + if (raw_key.get() == nullptr) { + JNI_TRACE("EVP_PKEY_get_raw_public_key: using byte array failed"); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for raw_key"); + return nullptr; } - ScopedByteArrayRW to(env, toJavaBytes); - if (to.get() == nullptr) { - return -1; + if (EVP_PKEY_get_raw_public_key(pkey, reinterpret_cast(raw_key.get()), &key_length) == + 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_public_key"); + return nullptr; } + return raw_key_array.release(); +} - int resultSize = - operation(static_cast(flen), reinterpret_cast(from.get()), - reinterpret_cast(to.get()), rsa.get(), padding); - if (resultSize == -1) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, caller, conscrypt::jniutil::throwBadPaddingException); - JNI_TRACE("%s => threw error", caller); - return -1; +static jlong NativeCrypto_EVP_PKEY_from_private_seed(JNIEnv* env, jclass, jint pkeyType, + jbyteArray javaSeedBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EVP_PKEY_from_private_seed(%p)", javaSeedBytes); + + if (javaSeedBytes == nullptr) { + JNI_TRACE("EVP_PKEY_from_private_seed => threw exception"); + conscrypt::jniutil::throwNullPointerException(env, "javaSeedBytes == null"); + return 0; } - JNI_TRACE("%s(%d, %p, %p, %p) => %d", caller, flen, fromJavaBytes, toJavaBytes, pkey, - resultSize); - return static_cast(resultSize); -} + ScopedByteArrayRO seed(env, javaSeedBytes); + if (seed.get() == nullptr) { + JNI_TRACE("bytes=%p EVP_PKEY_from_private_seed => threw exception", javaSeedBytes); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for seed"); + return 0; + } -static jint NativeCrypto_RSA_private_encrypt(JNIEnv* env, jclass, jint flen, - jbyteArray fromJavaBytes, jbyteArray toJavaBytes, - jobject pkeyRef, jint padding) { - CHECK_ERROR_QUEUE_ON_RETURN; - return RSA_crypt_operation(RSA_private_encrypt, __FUNCTION__, env, flen, fromJavaBytes, - toJavaBytes, pkeyRef, padding); -} -static jint NativeCrypto_RSA_public_decrypt(JNIEnv* env, jclass, jint flen, - jbyteArray fromJavaBytes, jbyteArray toJavaBytes, - jobject pkeyRef, jint padding) { - CHECK_ERROR_QUEUE_ON_RETURN; - return RSA_crypt_operation(RSA_public_decrypt, __FUNCTION__, env, flen, fromJavaBytes, - toJavaBytes, pkeyRef, padding); -} -static jint NativeCrypto_RSA_public_encrypt(JNIEnv* env, jclass, jint flen, - jbyteArray fromJavaBytes, jbyteArray toJavaBytes, - jobject pkeyRef, jint padding) { - CHECK_ERROR_QUEUE_ON_RETURN; - return RSA_crypt_operation(RSA_public_encrypt, __FUNCTION__, env, flen, fromJavaBytes, - toJavaBytes, pkeyRef, padding); -} -static jint NativeCrypto_RSA_private_decrypt(JNIEnv* env, jclass, jint flen, - jbyteArray fromJavaBytes, jbyteArray toJavaBytes, - jobject pkeyRef, jint padding) { - CHECK_ERROR_QUEUE_ON_RETURN; - return RSA_crypt_operation(RSA_private_decrypt, __FUNCTION__, env, flen, fromJavaBytes, - toJavaBytes, pkeyRef, padding); + const EVP_PKEY_ALG* alg; + if (pkeyType == EVP_PKEY_ML_DSA_44) { + alg = EVP_pkey_ml_dsa_44(); + } else if (pkeyType == EVP_PKEY_ML_DSA_65) { + alg = EVP_pkey_ml_dsa_65(); + } else if (pkeyType == EVP_PKEY_ML_DSA_87) { + alg = EVP_pkey_ml_dsa_87(); + } else { + conscrypt::jniutil::throwInvalidKeyException(env, "unsupported pkeyType"); + return 0; + } + + bssl::UniquePtr pkey(EVP_PKEY_from_private_seed( + alg, reinterpret_cast(seed.get()), seed.size())); + + if (!pkey) { + conscrypt::jniutil::throwParsingException(env, "Error parsing private key"); + ERR_clear_error(); + JNI_TRACE("bytes=%p EVP_PKEY_from_private_seed => threw exception", javaSeedBytes); + return 0; + } + + JNI_TRACE("bytes=%p EVP_PKEY_from_private_seed => %p", javaSeedBytes, pkey.get()); + return reinterpret_cast(pkey.release()); } -/* - * public static native byte[][] get_RSA_public_params(long); - */ -static jobjectArray NativeCrypto_get_RSA_public_params(JNIEnv* env, jclass, jobject pkeyRef) { +static jbyteArray NativeCrypto_EVP_PKEY_get_private_seed(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("get_RSA_public_params(%p)", pkey); + JNI_TRACE("EVP_PKEY_get_private_seed(%p)", pkeyRef); + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); if (pkey == nullptr) { return nullptr; } - bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); - if (rsa.get() == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "get_RSA_public_params failed"); + size_t seed_length = 0; + if (EVP_PKEY_get_private_seed(pkey, nullptr, &seed_length) == 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_private_seed"); return nullptr; } - jobjectArray joa = env->NewObjectArray(2, conscrypt::jniutil::byteArrayClass, nullptr); - if (joa == nullptr) { + ScopedLocalRef seedArray(env, env->NewByteArray(static_cast(seed_length))); + if (seedArray.get() == nullptr) { + JNI_TRACE("EVP_PKEY_get_raw_private_key: creating byte array failed"); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for seedArray"); return nullptr; } - - jbyteArray n = bignumToArray(env, RSA_get0_n(rsa.get()), "n"); - if (env->ExceptionCheck()) { + ScopedByteArrayRW seed(env, seedArray.get()); + if (seed.get() == nullptr) { + JNI_TRACE("EVP_PKEY_get_raw_private_key: using byte array failed"); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for seed"); return nullptr; } - env->SetObjectArrayElement(joa, 0, n); - jbyteArray e = bignumToArray(env, RSA_get0_e(rsa.get()), "e"); - if (env->ExceptionCheck()) { + if (EVP_PKEY_get_private_seed(pkey, reinterpret_cast(seed.get()), &seed_length) == + 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_private_seed"); return nullptr; } - env->SetObjectArrayElement(joa, 1, e); - - return joa; + return seedArray.release(); } -/* - * public static native byte[][] get_RSA_private_params(long); - */ -static jobjectArray NativeCrypto_get_RSA_private_params(JNIEnv* env, jclass, jobject pkeyRef) { +static jbyteArray NativeCrypto_EVP_raw_X25519_private_key(JNIEnv* env, jclass cls, + jbyteArray keyJavaBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("get_RSA_public_params(%p)", pkey); + JNI_TRACE("NativeCrypto_EVP_raw_X25519_private_key(%p)", keyJavaBytes); - if (pkey == nullptr) { + jlong key_ptr = NativeCrypto_EVP_parse_private_key(env, cls, keyJavaBytes); + if (key_ptr == 0) { return nullptr; } - - bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); - if (rsa.get() == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "get_RSA_public_params failed"); + bssl::UniquePtr pkey(reinterpret_cast(key_ptr)); + if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_X25519) { + conscrypt::jniutil::throwInvalidKeyException(env, "Invalid key type"); return nullptr; } - jobjectArray joa = env->NewObjectArray(8, conscrypt::jniutil::byteArrayClass, nullptr); - if (joa == nullptr) { + size_t key_length = X25519_PRIVATE_KEY_LEN; + ScopedLocalRef byteArray(env, env->NewByteArray(static_cast(key_length))); + if (byteArray.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Allocating byte[]"); + JNI_TRACE("NativeCrypto_EVP_raw_X25519_private_key: byte array creation failed"); return nullptr; } - jbyteArray n = bignumToArray(env, RSA_get0_n(rsa.get()), "n"); - if (env->ExceptionCheck()) { + ScopedByteArrayRW bytes(env, byteArray.get()); + if (bytes.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Allocating scoped byte array"); + JNI_TRACE("NativeCrypto_EVP_raw_X25519_private_key: scoped byte array failed"); return nullptr; } - env->SetObjectArrayElement(joa, 0, n); - if (RSA_get0_e(rsa.get()) != nullptr) { - jbyteArray e = bignumToArray(env, RSA_get0_e(rsa.get()), "e"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 1, e); + if (EVP_PKEY_get_raw_private_key(pkey.get(), reinterpret_cast(bytes.get()), + &key_length) == 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get_raw_private_key"); + return nullptr; } + jbyteArray result = byteArray.release(); + JNI_TRACE("bytes=%p NativeCrypto_EVP_raw_X25519_private_key => %p", keyJavaBytes, result); + return result; +} - if (RSA_get0_d(rsa.get()) != nullptr) { - jbyteArray d = bignumToArray(env, RSA_get0_d(rsa.get()), "d"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 2, d); - } +/* + * static native byte[] EVP_marshal_public_key(long) + */ +static jbyteArray NativeCrypto_EVP_marshal_public_key(JNIEnv* env, jclass, jobject pkeyRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("EVP_marshal_public_key(%p)", pkey); - if (RSA_get0_p(rsa.get()) != nullptr) { - jbyteArray p = bignumToArray(env, RSA_get0_p(rsa.get()), "p"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 3, p); + if (pkey == nullptr) { + return nullptr; } - if (RSA_get0_q(rsa.get()) != nullptr) { - jbyteArray q = bignumToArray(env, RSA_get0_q(rsa.get()), "q"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 4, q); + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 64)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + JNI_TRACE("CBB_init failed"); + return nullptr; } - if (RSA_get0_dmp1(rsa.get()) != nullptr) { - jbyteArray dmp1 = bignumToArray(env, RSA_get0_dmp1(rsa.get()), "dmp1"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 5, dmp1); + if (!EVP_marshal_public_key(cbb.get(), pkey)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_marshal_public_key"); + JNI_TRACE("key=%p EVP_marshal_public_key => error", pkey); + return nullptr; } - if (RSA_get0_dmq1(rsa.get()) != nullptr) { - jbyteArray dmq1 = bignumToArray(env, RSA_get0_dmq1(rsa.get()), "dmq1"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 6, dmq1); + return CBBToByteArray(env, cbb.get()); +} + +/* + * static native long EVP_parse_public_key(byte[]) + */ +static jlong NativeCrypto_EVP_parse_public_key(JNIEnv* env, jclass, jbyteArray keyJavaBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EVP_parse_public_key(%p)", keyJavaBytes); + + ScopedByteArrayRO bytes(env, keyJavaBytes); + if (bytes.get() == nullptr) { + JNI_TRACE("bytes=%p EVP_parse_public_key => threw exception", keyJavaBytes); + return 0; } - if (RSA_get0_iqmp(rsa.get()) != nullptr) { - jbyteArray iqmp = bignumToArray(env, RSA_get0_iqmp(rsa.get()), "iqmp"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 7, iqmp); + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr pkey(EVP_parse_public_key(&cbs)); + // We intentionally do not check that cbs is exhausted, as JCA providers + // typically allow parsing keys from buffers that are larger than the + // contained key structure so we do the same for compatibility. + if (!pkey) { + conscrypt::jniutil::throwParsingException(env, "Error parsing public key"); + ERR_clear_error(); + JNI_TRACE("bytes=%p EVP_parse_public_key => threw exception", keyJavaBytes); + return 0; } - return joa; + JNI_TRACE("bytes=%p EVP_parse_public_key => %p", keyJavaBytes, pkey.get()); + return reinterpret_cast(pkey.release()); } -static void NativeCrypto_chacha20_encrypt_decrypt(JNIEnv* env, jclass, jbyteArray inBytes, - jint inOffset, jbyteArray outBytes, - jint outOffset, jint length, jbyteArray keyBytes, - jbyteArray nonceBytes, jint blockCounter) { +static jlong NativeCrypto_getRSAPrivateKeyWrapper(JNIEnv* env, jclass, jobject javaKey, + jbyteArray modulusBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("chacha20_encrypt_decrypt"); - ScopedByteArrayRO in(env, inBytes); - if (in.get() == nullptr) { - JNI_TRACE( - "chacha20_encrypt_decrypt => threw exception: could not read input " - "bytes"); - return; - } - ScopedByteArrayRW out(env, outBytes); - if (out.get() == nullptr) { - JNI_TRACE( - "chacha20_encrypt_decrypt => threw exception: could not read output " - "bytes"); - return; - } - ScopedByteArrayRO key(env, keyBytes); - if (key.get() == nullptr) { - JNI_TRACE( - "chacha20_encrypt_decrypt => threw exception: could not read key " - "bytes"); - return; - } - ScopedByteArrayRO nonce(env, nonceBytes); - if (nonce.get() == nullptr) { - JNI_TRACE( - "chacha20_encrypt_decrypt => threw exception: could not read nonce " - "bytes"); - return; - } - - CRYPTO_chacha_20(reinterpret_cast(out.get()) + outOffset, - reinterpret_cast(in.get()) + inOffset, length, - reinterpret_cast(key.get()), - reinterpret_cast(nonce.get()), blockCounter); -} + JNI_TRACE("getRSAPrivateKeyWrapper(%p, %p)", javaKey, modulusBytes); -static jlong NativeCrypto_EC_GROUP_new_by_curve_name(JNIEnv* env, jclass, jstring curveNameJava) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EC_GROUP_new_by_curve_name(%p)", curveNameJava); + ensure_engine_globals(); - ScopedUtfChars curveName(env, curveNameJava); - if (curveName.c_str() == nullptr) { + // The PSS padding code needs access to the actual n, so set it even though we + // don't set any other parts of the key + bssl::UniquePtr n = arrayToBignum(env, modulusBytes); + if (n == nullptr) { return 0; } - JNI_TRACE("EC_GROUP_new_by_curve_name(%s)", curveName.c_str()); - int nid = OBJ_sn2nid(curveName.c_str()); - if (nid == NID_undef) { - JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => unknown NID name", curveName.c_str()); + // TODO(crbug.com/boringssl/602): RSA_METHOD is not the ideal abstraction to + // use here. + bssl::UniquePtr rsa(RSA_new_method_no_e(g_engine, n.get())); + if (rsa == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); return 0; } - EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); - if (group == nullptr) { - JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => unknown NID %d", curveName.c_str(), nid); + auto ex_data = new KeyExData; + ex_data->private_key = env->NewGlobalRef(javaKey); + RSA_set_ex_data(rsa.get(), g_rsa_exdata_index, ex_data); + + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (pkey.get() == nullptr) { + JNI_TRACE("getRSAPrivateKeyWrapper failed"); + conscrypt::jniutil::throwRuntimeException(env, + "NativeCrypto_getRSAPrivateKeyWrapper failed"); ERR_clear_error(); return 0; } - JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => %p", curveName.c_str(), group); - return reinterpret_cast(group); + if (EVP_PKEY_assign_RSA(pkey.get(), rsa.get()) != 1) { + conscrypt::jniutil::throwRuntimeException(env, "getRSAPrivateKeyWrapper failed"); + ERR_clear_error(); + return 0; + } + OWNERSHIP_TRANSFERRED(rsa); + JNI_TRACE("getRSAPrivateKeyWrapper(%p, %p) => %p", javaKey, modulusBytes, pkey.get()); + return reinterpret_cast(pkey.release()); } -static jlong NativeCrypto_EC_GROUP_new_arbitrary(JNIEnv* env, jclass, jbyteArray pBytes, - jbyteArray aBytes, jbyteArray bBytes, - jbyteArray xBytes, jbyteArray yBytes, - jbyteArray orderBytes, jint cofactorInt) { +static jlong NativeCrypto_getECPrivateKeyWrapper(JNIEnv* env, jclass, jobject javaKey, + jobject groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EC_GROUP_new_arbitrary"); - - if (cofactorInt < 1) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "cofactor < 1"); + EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("getECPrivateKeyWrapper(%p, %p)", javaKey, group); + if (group == nullptr) { return 0; } - bssl::UniquePtr p = arrayToBignum(env, pBytes); - if (p == nullptr) { - return 0; - } - bssl::UniquePtr a = arrayToBignum(env, aBytes); - if (a == nullptr) { + ensure_engine_globals(); + + bssl::UniquePtr ecKey(EC_KEY_new_method(g_engine)); + if (ecKey.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate EC key"); return 0; } - bssl::UniquePtr b = arrayToBignum(env, bBytes); - if (b == nullptr) { + + if (EC_KEY_set_group(ecKey.get(), group) != 1) { + JNI_TRACE("getECPrivateKeyWrapper(%p, %p) => EC_KEY_set_group error", javaKey, group); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); return 0; } - bssl::UniquePtr x = arrayToBignum(env, xBytes); - if (x == nullptr) { + + auto ex_data = new KeyExData; + ex_data->private_key = env->NewGlobalRef(javaKey); + + if (!EC_KEY_set_ex_data(ecKey.get(), g_ecdsa_exdata_index, ex_data)) { + env->DeleteGlobalRef(ex_data->private_key); + delete ex_data; + conscrypt::jniutil::throwRuntimeException(env, "EC_KEY_set_ex_data"); + ERR_clear_error(); return 0; } - bssl::UniquePtr y = arrayToBignum(env, yBytes); - if (y == nullptr) { + + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (pkey.get() == nullptr) { + JNI_TRACE("getECPrivateKeyWrapper failed"); + conscrypt::jniutil::throwRuntimeException(env, + "NativeCrypto_getECPrivateKeyWrapper failed"); + ERR_clear_error(); return 0; } - bssl::UniquePtr order = arrayToBignum(env, orderBytes); - if (order == nullptr) { + + if (EVP_PKEY_assign_EC_KEY(pkey.get(), ecKey.get()) != 1) { + conscrypt::jniutil::throwRuntimeException(env, "getECPrivateKeyWrapper failed"); + ERR_clear_error(); return 0; } - bssl::UniquePtr cofactor(BN_new()); - if (cofactor == nullptr || !BN_set_word(cofactor.get(), static_cast(cofactorInt))) { + OWNERSHIP_TRANSFERRED(ecKey); + return reinterpret_cast(pkey.release()); +} + +/* + * public static native int RSA_generate_key(int modulusBits, byte[] + * publicExponent); + */ +static jlong NativeCrypto_RSA_generate_key_ex(JNIEnv* env, jclass, jint modulusBits, + jbyteArray publicExponent) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("RSA_generate_key_ex(%d, %p)", modulusBits, publicExponent); + + bssl::UniquePtr e = arrayToBignum(env, publicExponent); + if (e == nullptr) { return 0; } - bssl::UniquePtr ctx(BN_CTX_new()); - bssl::UniquePtr group(EC_GROUP_new_curve_GFp(p.get(), a.get(), b.get(), ctx.get())); - if (group.get() == nullptr) { - JNI_TRACE("EC_GROUP_new_curve_GFp => null"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_new_curve_GFp"); + bssl::UniquePtr rsa(RSA_new()); + if (rsa.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); return 0; } - bssl::UniquePtr generator(EC_POINT_new(group.get())); - if (generator.get() == nullptr) { - JNI_TRACE("EC_POINT_new => null"); - ERR_clear_error(); + if (RSA_generate_key_ex(rsa.get(), modulusBits, e.get(), nullptr) != 1) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "RSA_generate_key_ex failed"); return 0; } - if (!EC_POINT_set_affine_coordinates_GFp(group.get(), generator.get(), x.get(), y.get(), - ctx.get())) { - JNI_TRACE("EC_POINT_set_affine_coordinates_GFp => error"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, - "EC_POINT_set_affine_coordinates_GFp"); + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (pkey.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate RSA key"); + ERR_clear_error(); return 0; } - if (!EC_GROUP_set_generator(group.get(), generator.get(), order.get(), cofactor.get())) { - JNI_TRACE("EC_GROUP_set_generator => error"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_set_generator"); + if (EVP_PKEY_assign_RSA(pkey.get(), rsa.get()) != 1) { + conscrypt::jniutil::throwRuntimeException(env, "RSA_generate_key_ex failed"); + ERR_clear_error(); return 0; } - JNI_TRACE("EC_GROUP_new_arbitrary => %p", group.get()); - return reinterpret_cast(group.release()); + OWNERSHIP_TRANSFERRED(rsa); + JNI_TRACE("RSA_generate_key_ex(n=%d, e=%p) => %p", modulusBits, publicExponent, pkey.get()); + return reinterpret_cast(pkey.release()); } -static jstring NativeCrypto_EC_GROUP_get_curve_name(JNIEnv* env, jclass, jobject groupRef) { +static jint NativeCrypto_RSA_size(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_GROUP_get_curve_name(%p)", group); + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("RSA_size(%p)", pkey); - if (group == nullptr) { - JNI_TRACE("EC_GROUP_get_curve_name => group == null"); - return nullptr; + if (pkey == nullptr) { + return 0; } - int nid = EC_GROUP_get_curve_name(group); - if (nid == NID_undef) { - JNI_TRACE("EC_GROUP_get_curve_name(%p) => unnamed curve", group); - return nullptr; + bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); + if (rsa.get() == nullptr) { + conscrypt::jniutil::throwRuntimeException(env, "RSA_size failed"); + ERR_clear_error(); + return 0; } - const char* shortName = OBJ_nid2sn(nid); - JNI_TRACE("EC_GROUP_get_curve_name(%p) => \"%s\"", group, shortName); - return env->NewStringUTF(shortName); + return static_cast(RSA_size(rsa.get())); } -static jobjectArray NativeCrypto_EC_GROUP_get_curve(JNIEnv* env, jclass, jobject groupRef) { +typedef int RSACryptOperation(size_t flen, const unsigned char* from, unsigned char* to, RSA* rsa, + int padding); + +static jint RSA_crypt_operation(RSACryptOperation operation, const char* caller, JNIEnv* env, + jint flen, jbyteArray fromJavaBytes, jbyteArray toJavaBytes, + jobject pkeyRef, jint padding) { CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_GROUP_get_curve(%p)", group); - if (group == nullptr) { - JNI_TRACE("EC_GROUP_get_curve => group == null"); - return nullptr; - } + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("%s(%d, %p, %p, %p)", caller, flen, fromJavaBytes, toJavaBytes, pkey); - bssl::UniquePtr p(BN_new()); - bssl::UniquePtr a(BN_new()); - bssl::UniquePtr b(BN_new()); + if (pkey == nullptr) { + return -1; + } - int ret = EC_GROUP_get_curve_GFp(group, p.get(), a.get(), b.get(), nullptr); - if (ret != 1) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_get_curve"); - return nullptr; + bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); + if (rsa.get() == nullptr) { + return -1; } - jobjectArray joa = env->NewObjectArray(3, conscrypt::jniutil::byteArrayClass, nullptr); - if (joa == nullptr) { - return nullptr; + ScopedByteArrayRO from(env, fromJavaBytes); + if (from.get() == nullptr) { + return -1; } - jbyteArray pArray = bignumToArray(env, p.get(), "p"); - if (env->ExceptionCheck()) { - return nullptr; + ScopedByteArrayRW to(env, toJavaBytes); + if (to.get() == nullptr) { + return -1; } - env->SetObjectArrayElement(joa, 0, pArray); - jbyteArray aArray = bignumToArray(env, a.get(), "a"); - if (env->ExceptionCheck()) { - return nullptr; + int resultSize = + operation(static_cast(flen), reinterpret_cast(from.get()), + reinterpret_cast(to.get()), rsa.get(), padding); + if (resultSize == -1) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, caller, conscrypt::jniutil::throwBadPaddingException); + JNI_TRACE("%s => threw error", caller); + return -1; } - env->SetObjectArrayElement(joa, 1, aArray); - jbyteArray bArray = bignumToArray(env, b.get(), "b"); - if (env->ExceptionCheck()) { - return nullptr; - } - env->SetObjectArrayElement(joa, 2, bArray); + JNI_TRACE("%s(%d, %p, %p, %p) => %d", caller, flen, fromJavaBytes, toJavaBytes, pkey, + resultSize); + return static_cast(resultSize); +} - JNI_TRACE("EC_GROUP_get_curve(%p) => %p", group, joa); - return joa; +static jint NativeCrypto_RSA_private_encrypt(JNIEnv* env, jclass, jint flen, + jbyteArray fromJavaBytes, jbyteArray toJavaBytes, + jobject pkeyRef, jint padding) { + CHECK_ERROR_QUEUE_ON_RETURN; + return RSA_crypt_operation(RSA_private_encrypt, __FUNCTION__, env, flen, fromJavaBytes, + toJavaBytes, pkeyRef, padding); +} +static jint NativeCrypto_RSA_public_decrypt(JNIEnv* env, jclass, jint flen, + jbyteArray fromJavaBytes, jbyteArray toJavaBytes, + jobject pkeyRef, jint padding) { + CHECK_ERROR_QUEUE_ON_RETURN; + return RSA_crypt_operation(RSA_public_decrypt, __FUNCTION__, env, flen, fromJavaBytes, + toJavaBytes, pkeyRef, padding); +} +static jint NativeCrypto_RSA_public_encrypt(JNIEnv* env, jclass, jint flen, + jbyteArray fromJavaBytes, jbyteArray toJavaBytes, + jobject pkeyRef, jint padding) { + CHECK_ERROR_QUEUE_ON_RETURN; + return RSA_crypt_operation(RSA_public_encrypt, __FUNCTION__, env, flen, fromJavaBytes, + toJavaBytes, pkeyRef, padding); +} +static jint NativeCrypto_RSA_private_decrypt(JNIEnv* env, jclass, jint flen, + jbyteArray fromJavaBytes, jbyteArray toJavaBytes, + jobject pkeyRef, jint padding) { + CHECK_ERROR_QUEUE_ON_RETURN; + return RSA_crypt_operation(RSA_private_decrypt, __FUNCTION__, env, flen, fromJavaBytes, + toJavaBytes, pkeyRef, padding); } -static jbyteArray NativeCrypto_EC_GROUP_get_order(JNIEnv* env, jclass, jobject groupRef) { +/* + * public static native byte[][] get_RSA_public_params(long); + */ +static jobjectArray NativeCrypto_get_RSA_public_params(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_GROUP_get_order(%p)", group); - if (group == nullptr) { + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("get_RSA_public_params(%p)", pkey); + + if (pkey == nullptr) { return nullptr; } - bssl::UniquePtr order(BN_new()); - if (order.get() == nullptr) { - JNI_TRACE("EC_GROUP_get_order(%p) => can't create BN", group); - conscrypt::jniutil::throwOutOfMemory(env, "BN_new"); + bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); + if (rsa.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "get_RSA_public_params failed"); return nullptr; } - if (EC_GROUP_get_order(group, order.get(), nullptr) != 1) { - JNI_TRACE("EC_GROUP_get_order(%p) => threw error", group); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_get_order"); + jobjectArray joa = env->NewObjectArray(2, conscrypt::jniutil::byteArrayClass, nullptr); + if (joa == nullptr) { return nullptr; } - jbyteArray orderArray = bignumToArray(env, order.get(), "order"); + jbyteArray n = bignumToArray(env, RSA_get0_n(rsa.get()), "n"); if (env->ExceptionCheck()) { return nullptr; } + env->SetObjectArrayElement(joa, 0, n); - JNI_TRACE("EC_GROUP_get_order(%p) => %p", group, orderArray); - return orderArray; -} - -static jint NativeCrypto_EC_GROUP_get_degree(JNIEnv* env, jclass, jobject groupRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_GROUP_get_degree(%p)", group); - if (group == nullptr) { - return 0; - } - - jint degree = static_cast(EC_GROUP_get_degree(group)); - if (degree == 0) { - JNI_TRACE("EC_GROUP_get_degree(%p) => unsupported", group); - conscrypt::jniutil::throwRuntimeException(env, "not supported"); - ERR_clear_error(); - return 0; + jbyteArray e = bignumToArray(env, RSA_get0_e(rsa.get()), "e"); + if (env->ExceptionCheck()) { + return nullptr; } + env->SetObjectArrayElement(joa, 1, e); - JNI_TRACE("EC_GROUP_get_degree(%p) => %d", group, degree); - return degree; + return joa; } -static jbyteArray NativeCrypto_EC_GROUP_get_cofactor(JNIEnv* env, jclass, jobject groupRef) { +/* + * public static native byte[][] get_RSA_private_params(long); + */ +static jobjectArray NativeCrypto_get_RSA_private_params(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_GROUP_get_cofactor(%p)", group); - if (group == nullptr) { + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("get_RSA_public_params(%p)", pkey); + + if (pkey == nullptr) { return nullptr; } - bssl::UniquePtr cofactor(BN_new()); - if (cofactor.get() == nullptr) { - JNI_TRACE("EC_GROUP_get_cofactor(%p) => can't create BN", group); - conscrypt::jniutil::throwOutOfMemory(env, "BN_new"); + bssl::UniquePtr rsa(EVP_PKEY_get1_RSA(pkey)); + if (rsa.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "get_RSA_public_params failed"); return nullptr; } - if (EC_GROUP_get_cofactor(group, cofactor.get(), nullptr) != 1) { - JNI_TRACE("EC_GROUP_get_cofactor(%p) => threw error", group); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_get_cofactor"); + jobjectArray joa = env->NewObjectArray(8, conscrypt::jniutil::byteArrayClass, nullptr); + if (joa == nullptr) { return nullptr; } - jbyteArray cofactorArray = bignumToArray(env, cofactor.get(), "cofactor"); + jbyteArray n = bignumToArray(env, RSA_get0_n(rsa.get()), "n"); if (env->ExceptionCheck()) { return nullptr; } + env->SetObjectArrayElement(joa, 0, n); - JNI_TRACE("EC_GROUP_get_cofactor(%p) => %p", group, cofactorArray); - return cofactorArray; -} - -static void NativeCrypto_EC_GROUP_clear_free(JNIEnv* env, jclass, jlong groupRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EC_GROUP* group = reinterpret_cast(groupRef); - JNI_TRACE("EC_GROUP_clear_free(%p)", group); - - if (group == nullptr) { - JNI_TRACE("EC_GROUP_clear_free => group == null"); - conscrypt::jniutil::throwNullPointerException(env, "group == null"); - return; + if (RSA_get0_e(rsa.get()) != nullptr) { + jbyteArray e = bignumToArray(env, RSA_get0_e(rsa.get()), "e"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 1, e); } - EC_GROUP_free(group); - JNI_TRACE("EC_GROUP_clear_free(%p) => success", group); -} - -static jlong NativeCrypto_EC_GROUP_get_generator(JNIEnv* env, jclass, jobject groupRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_GROUP_get_generator(%p)", group); - - if (group == nullptr) { - JNI_TRACE("EC_POINT_get_generator(%p) => group == null", group); - return 0; + if (RSA_get0_d(rsa.get()) != nullptr) { + jbyteArray d = bignumToArray(env, RSA_get0_d(rsa.get()), "d"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 2, d); } - const EC_POINT* generator = EC_GROUP_get0_generator(group); - - bssl::UniquePtr dup(EC_POINT_dup(generator, group)); - if (dup.get() == nullptr) { - JNI_TRACE("EC_GROUP_get_generator(%p) => oom error", group); - conscrypt::jniutil::throwOutOfMemory(env, "unable to dupe generator"); - return 0; + if (RSA_get0_p(rsa.get()) != nullptr) { + jbyteArray p = bignumToArray(env, RSA_get0_p(rsa.get()), "p"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 3, p); } - JNI_TRACE("EC_GROUP_get_generator(%p) => %p", group, dup.get()); - return reinterpret_cast(dup.release()); -} - -static jlong NativeCrypto_EC_POINT_new(JNIEnv* env, jclass, jobject groupRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_POINT_new(%p)", group); - - if (group == nullptr) { - JNI_TRACE("EC_POINT_new(%p) => group == null", group); - return 0; + if (RSA_get0_q(rsa.get()) != nullptr) { + jbyteArray q = bignumToArray(env, RSA_get0_q(rsa.get()), "q"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 4, q); } - EC_POINT* point = EC_POINT_new(group); - if (point == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable create an EC_POINT"); - return 0; + if (RSA_get0_dmp1(rsa.get()) != nullptr) { + jbyteArray dmp1 = bignumToArray(env, RSA_get0_dmp1(rsa.get()), "dmp1"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 5, dmp1); } - return reinterpret_cast(point); -} - -static void NativeCrypto_EC_POINT_clear_free(JNIEnv* env, jclass, jlong groupRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EC_POINT* group = reinterpret_cast(groupRef); - JNI_TRACE("EC_POINT_clear_free(%p)", group); + if (RSA_get0_dmq1(rsa.get()) != nullptr) { + jbyteArray dmq1 = bignumToArray(env, RSA_get0_dmq1(rsa.get()), "dmq1"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 6, dmq1); + } - if (group == nullptr) { - JNI_TRACE("EC_POINT_clear_free => group == null"); - conscrypt::jniutil::throwNullPointerException(env, "group == null"); - return; + if (RSA_get0_iqmp(rsa.get()) != nullptr) { + jbyteArray iqmp = bignumToArray(env, RSA_get0_iqmp(rsa.get()), "iqmp"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 7, iqmp); } - EC_POINT_free(group); - JNI_TRACE("EC_POINT_clear_free(%p) => success", group); + return joa; } -static void NativeCrypto_EC_POINT_set_affine_coordinates(JNIEnv* env, jclass, jobject groupRef, - jobject pointRef, jbyteArray xjavaBytes, - jbyteArray yjavaBytes) { +static void NativeCrypto_chacha20_encrypt_decrypt(JNIEnv* env, jclass, jbyteArray inBytes, + jint inOffset, jbyteArray outBytes, + jint outOffset, jint length, jbyteArray keyBytes, + jbyteArray nonceBytes, jint blockCounter) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p)", groupRef, pointRef, xjavaBytes, - yjavaBytes); - const EC_GROUP* group = fromContextObject(env, groupRef); - if (group == nullptr) { + JNI_TRACE("chacha20_encrypt_decrypt"); + ScopedByteArrayRO in(env, inBytes); + if (in.get() == nullptr) { + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read input " + "bytes"); return; } - EC_POINT* point = fromContextObject(env, pointRef); - if (point == nullptr) { + ScopedByteArrayRW out(env, outBytes); + if (out.get() == nullptr) { + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read output " + "bytes"); return; } - JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p) <- ptr", group, point, xjavaBytes, - yjavaBytes); - - bssl::UniquePtr x = arrayToBignum(env, xjavaBytes); - if (x == nullptr) { + ScopedByteArrayRO key(env, keyBytes); + if (key.get() == nullptr) { + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read key " + "bytes"); return; } - - bssl::UniquePtr y = arrayToBignum(env, yjavaBytes); - if (y == nullptr) { - return; - } - - int ret = EC_POINT_set_affine_coordinates_GFp(group, point, x.get(), y.get(), nullptr); - if (ret != 1) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, - "EC_POINT_set_affine_coordinates"); + ScopedByteArrayRO nonce(env, nonceBytes); + if (nonce.get() == nullptr) { + JNI_TRACE( + "chacha20_encrypt_decrypt => threw exception: could not read nonce " + "bytes"); return; } - JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p) => %d", group, point, xjavaBytes, - yjavaBytes, ret); + CRYPTO_chacha_20(reinterpret_cast(out.get()) + outOffset, + reinterpret_cast(in.get()) + inOffset, length, + reinterpret_cast(key.get()), + reinterpret_cast(nonce.get()), blockCounter); } -static jobjectArray NativeCrypto_EC_POINT_get_affine_coordinates(JNIEnv* env, jclass, - jobject groupRef, - jobject pointRef) { +static jlong NativeCrypto_EC_GROUP_new_by_curve_name(JNIEnv* env, jclass, jstring curveNameJava) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p)", groupRef, pointRef); - const EC_GROUP* group = fromContextObject(env, groupRef); - if (group == nullptr) { - return nullptr; - } - const EC_POINT* point = fromContextObject(env, pointRef); - if (point == nullptr) { - return nullptr; - } - JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p) <- ptr", group, point); - - bssl::UniquePtr x(BN_new()); - bssl::UniquePtr y(BN_new()); - - int ret = EC_POINT_get_affine_coordinates_GFp(group, point, x.get(), y.get(), nullptr); - if (ret != 1) { - JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p)", group, point); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, - "EC_POINT_get_affine_coordinates"); - return nullptr; - } + JNI_TRACE("EC_GROUP_new_by_curve_name(%p)", curveNameJava); - jobjectArray joa = env->NewObjectArray(2, conscrypt::jniutil::byteArrayClass, nullptr); - if (joa == nullptr) { - return nullptr; + ScopedUtfChars curveName(env, curveNameJava); + if (curveName.c_str() == nullptr) { + return 0; } + JNI_TRACE("EC_GROUP_new_by_curve_name(%s)", curveName.c_str()); - jbyteArray xBytes = bignumToArray(env, x.get(), "x"); - if (env->ExceptionCheck()) { - return nullptr; + int nid = OBJ_sn2nid(curveName.c_str()); + if (nid == NID_undef) { + JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => unknown NID name", curveName.c_str()); + return 0; } - env->SetObjectArrayElement(joa, 0, xBytes); - jbyteArray yBytes = bignumToArray(env, y.get(), "y"); - if (env->ExceptionCheck()) { - return nullptr; + EC_GROUP* group = EC_GROUP_new_by_curve_name(nid); + if (group == nullptr) { + JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => unknown NID %d", curveName.c_str(), nid); + ERR_clear_error(); + return 0; } - env->SetObjectArrayElement(joa, 1, yBytes); - JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p) => %p", group, point, joa); - return joa; + JNI_TRACE("EC_GROUP_new_by_curve_name(%s) => %p", curveName.c_str(), group); + return reinterpret_cast(group); } -static jlong NativeCrypto_EC_KEY_generate_key(JNIEnv* env, jclass, jobject groupRef) { +static jlong NativeCrypto_EC_GROUP_new_arbitrary(JNIEnv* env, jclass, jbyteArray pBytes, + jbyteArray aBytes, jbyteArray bBytes, + jbyteArray xBytes, jbyteArray yBytes, + jbyteArray orderBytes, jint cofactorInt) { CHECK_ERROR_QUEUE_ON_RETURN; - const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_KEY_generate_key(%p)", group); - if (group == nullptr) { + JNI_TRACE("EC_GROUP_new_arbitrary"); + + if (cofactorInt < 1) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "cofactor < 1"); return 0; } - bssl::UniquePtr eckey(EC_KEY_new()); - if (eckey.get() == nullptr) { - JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_new() oom", group); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to create an EC_KEY"); + bssl::UniquePtr p = arrayToBignum(env, pBytes); + if (p == nullptr) { return 0; } - - if (EC_KEY_set_group(eckey.get(), group) != 1) { - JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_set_group error", group); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); + bssl::UniquePtr a = arrayToBignum(env, aBytes); + if (a == nullptr) { return 0; } - - if (EC_KEY_generate_key(eckey.get()) != 1) { - JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_generate_key error", group); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); + bssl::UniquePtr b = arrayToBignum(env, bBytes); + if (b == nullptr) { return 0; } - - bssl::UniquePtr pkey(EVP_PKEY_new()); - if (pkey.get() == nullptr) { - JNI_TRACE("EC_KEY_generate_key(%p) => threw error", group); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_generate_key"); + bssl::UniquePtr x = arrayToBignum(env, xBytes); + if (x == nullptr) { return 0; } - if (EVP_PKEY_assign_EC_KEY(pkey.get(), eckey.get()) != 1) { - conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_assign_EC_KEY failed"); - ERR_clear_error(); + bssl::UniquePtr y = arrayToBignum(env, yBytes); + if (y == nullptr) { + return 0; + } + bssl::UniquePtr order = arrayToBignum(env, orderBytes); + if (order == nullptr) { + return 0; + } + bssl::UniquePtr cofactor(BN_new()); + if (cofactor == nullptr || !BN_set_word(cofactor.get(), static_cast(cofactorInt))) { return 0; } - OWNERSHIP_TRANSFERRED(eckey); - JNI_TRACE("EC_KEY_generate_key(%p) => %p", group, pkey.get()); - return reinterpret_cast(pkey.release()); -} + bssl::UniquePtr ctx(BN_CTX_new()); + bssl::UniquePtr group(EC_GROUP_new_curve_GFp(p.get(), a.get(), b.get(), ctx.get())); + if (group.get() == nullptr) { + JNI_TRACE("EC_GROUP_new_curve_GFp => null"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_new_curve_GFp"); + return 0; + } -static jlong NativeCrypto_EC_KEY_get1_group(JNIEnv* env, jclass, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("EC_KEY_get1_group(%p)", pkey); + bssl::UniquePtr generator(EC_POINT_new(group.get())); + if (generator.get() == nullptr) { + JNI_TRACE("EC_POINT_new => null"); + ERR_clear_error(); + return 0; + } - if (pkey == nullptr) { - JNI_TRACE("EC_KEY_get1_group(%p) => pkey == null", pkey); + if (!EC_POINT_set_affine_coordinates_GFp(group.get(), generator.get(), x.get(), y.get(), + ctx.get())) { + JNI_TRACE("EC_POINT_set_affine_coordinates_GFp => error"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, + "EC_POINT_set_affine_coordinates_GFp"); return 0; } - if (EVP_PKEY_id(pkey) != EVP_PKEY_EC) { - conscrypt::jniutil::throwRuntimeException(env, "not EC key"); - JNI_TRACE("EC_KEY_get1_group(%p) => not EC key (type == %d)", pkey, EVP_PKEY_id(pkey)); + if (!EC_GROUP_set_generator(group.get(), generator.get(), order.get(), cofactor.get())) { + JNI_TRACE("EC_GROUP_set_generator => error"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_set_generator"); return 0; } - EC_GROUP* group = EC_GROUP_dup(EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey))); - JNI_TRACE("EC_KEY_get1_group(%p) => %p", pkey, group); - return reinterpret_cast(group); + JNI_TRACE("EC_GROUP_new_arbitrary => %p", group.get()); + return reinterpret_cast(group.release()); } -static jbyteArray NativeCrypto_EC_KEY_get_private_key(JNIEnv* env, jclass, jobject pkeyRef) { +static jstring NativeCrypto_EC_GROUP_get_curve_name(JNIEnv* env, jclass, jobject groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("EC_KEY_get_private_key(%p)", pkey); + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_GROUP_get_curve_name(%p)", group); - if (pkey == nullptr) { - JNI_TRACE("EC_KEY_get_private_key => pkey == null"); + if (group == nullptr) { + JNI_TRACE("EC_GROUP_get_curve_name => group == null"); return nullptr; } - bssl::UniquePtr eckey(EVP_PKEY_get1_EC_KEY(pkey)); - if (eckey.get() == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get1_EC_KEY"); + int nid = EC_GROUP_get_curve_name(group); + if (nid == NID_undef) { + JNI_TRACE("EC_GROUP_get_curve_name(%p) => unnamed curve", group); return nullptr; } - const BIGNUM* privkey = EC_KEY_get0_private_key(eckey.get()); + const char* shortName = OBJ_nid2sn(nid); + JNI_TRACE("EC_GROUP_get_curve_name(%p) => \"%s\"", group, shortName); + return env->NewStringUTF(shortName); +} - jbyteArray privBytes = bignumToArray(env, privkey, "privkey"); - if (env->ExceptionCheck()) { - JNI_TRACE("EC_KEY_get_private_key(%p) => threw error", pkey); +static jobjectArray NativeCrypto_EC_GROUP_get_curve(JNIEnv* env, jclass, jobject groupRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_GROUP_get_curve(%p)", group); + if (group == nullptr) { + JNI_TRACE("EC_GROUP_get_curve => group == null"); return nullptr; } - JNI_TRACE("EC_KEY_get_private_key(%p) => %p", pkey, privBytes); - return privBytes; -} + bssl::UniquePtr p(BN_new()); + bssl::UniquePtr a(BN_new()); + bssl::UniquePtr b(BN_new()); -static jlong NativeCrypto_EC_KEY_get_public_key(JNIEnv* env, jclass, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("EC_KEY_get_public_key(%p)", pkey); + int ret = EC_GROUP_get_curve_GFp(group, p.get(), a.get(), b.get(), nullptr); + if (ret != 1) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_get_curve"); + return nullptr; + } - if (pkey == nullptr) { - JNI_TRACE("EC_KEY_get_public_key => pkey == null"); - return 0; + jobjectArray joa = env->NewObjectArray(3, conscrypt::jniutil::byteArrayClass, nullptr); + if (joa == nullptr) { + return nullptr; } - bssl::UniquePtr eckey(EVP_PKEY_get1_EC_KEY(pkey)); - if (eckey.get() == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get1_EC_KEY"); - return 0; + jbyteArray pArray = bignumToArray(env, p.get(), "p"); + if (env->ExceptionCheck()) { + return nullptr; } + env->SetObjectArrayElement(joa, 0, pArray); - bssl::UniquePtr dup( - EC_POINT_dup(EC_KEY_get0_public_key(eckey.get()), EC_KEY_get0_group(eckey.get()))); - if (dup.get() == nullptr) { - JNI_TRACE("EC_KEY_get_public_key(%p) => can't dup public key", pkey); - conscrypt::jniutil::throwRuntimeException(env, "EC_POINT_dup"); - ERR_clear_error(); - return 0; + jbyteArray aArray = bignumToArray(env, a.get(), "a"); + if (env->ExceptionCheck()) { + return nullptr; } + env->SetObjectArrayElement(joa, 1, aArray); - JNI_TRACE("EC_KEY_get_public_key(%p) => %p", pkey, dup.get()); - return reinterpret_cast(dup.release()); + jbyteArray bArray = bignumToArray(env, b.get(), "b"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 2, bArray); + + JNI_TRACE("EC_GROUP_get_curve(%p) => %p", group, joa); + return joa; } -static jbyteArray NativeCrypto_EC_KEY_marshal_curve_name(JNIEnv* env, jclass, jobject groupRef) { +static jbyteArray NativeCrypto_EC_GROUP_get_order(JNIEnv* env, jclass, jobject groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; const EC_GROUP* group = fromContextObject(env, groupRef); - JNI_TRACE("EC_KEY_marshal_curve_name(%p)", group); + JNI_TRACE("EC_GROUP_get_order(%p)", group); if (group == nullptr) { - env->ExceptionClear(); - conscrypt::jniutil::throwIOException(env, "Invalid group pointer"); - JNI_TRACE("group=%p EC_KEY_marshal_curve_name => Invalid group pointer", group); return nullptr; } - bssl::ScopedCBB cbb; - if (!CBB_init(cbb.get(), 64)) { - conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); - JNI_TRACE("CBB_init failed"); - return nullptr; - } + bssl::UniquePtr order(BN_new()); + if (order.get() == nullptr) { + JNI_TRACE("EC_GROUP_get_order(%p) => can't create BN", group); + conscrypt::jniutil::throwOutOfMemory(env, "BN_new"); + return nullptr; + } - if (!EC_KEY_marshal_curve_name(cbb.get(), group)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - ERR_clear_error(); - JNI_TRACE("group=%p EC_KEY_marshal_curve_name => error", group); + if (EC_GROUP_get_order(group, order.get(), nullptr) != 1) { + JNI_TRACE("EC_GROUP_get_order(%p) => threw error", group); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_get_order"); return nullptr; } - return CBBToByteArray(env, cbb.get()); + jbyteArray orderArray = bignumToArray(env, order.get(), "order"); + if (env->ExceptionCheck()) { + return nullptr; + } + + JNI_TRACE("EC_GROUP_get_order(%p) => %p", group, orderArray); + return orderArray; } -static jlong NativeCrypto_EC_KEY_parse_curve_name(JNIEnv* env, jclass, jbyteArray curveNameBytes) { +static jint NativeCrypto_EC_GROUP_get_degree(JNIEnv* env, jclass, jobject groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EC_KEY_parse_curve_name(%p)", curveNameBytes); - - ScopedByteArrayRO bytes(env, curveNameBytes); - if (bytes.get() == nullptr) { - env->ExceptionClear(); - conscrypt::jniutil::throwIOException(env, "Null EC curve name"); - JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => curveNameBytes == null ", curveNameBytes); + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_GROUP_get_degree(%p)", group); + if (group == nullptr) { return 0; } - CBS cbs; - CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); - bssl::UniquePtr group(EC_KEY_parse_curve_name(&cbs)); - if (!group || CBS_len(&cbs) != 0) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + jint degree = static_cast(EC_GROUP_get_degree(group)); + if (degree == 0) { + JNI_TRACE("EC_GROUP_get_degree(%p) => unsupported", group); + conscrypt::jniutil::throwRuntimeException(env, "not supported"); ERR_clear_error(); - JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => threw exception", curveNameBytes); return 0; } - JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => %p", curveNameBytes, group.get()); - return reinterpret_cast(group.release()); + JNI_TRACE("EC_GROUP_get_degree(%p) => %d", group, degree); + return degree; } -static jint NativeCrypto_ECDH_compute_key(JNIEnv* env, jclass, jbyteArray outArray, jint outOffset, - jobject pubkeyRef, jobject privkeyRef) { +static jbyteArray NativeCrypto_EC_GROUP_get_cofactor(JNIEnv* env, jclass, jobject groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p)", outArray, outOffset, pubkeyRef, privkeyRef); - EVP_PKEY* pubPkey = fromContextObject(env, pubkeyRef); - if (pubPkey == nullptr) { - JNI_TRACE("ECDH_compute_key => pubPkey == null"); - return -1; - } - EVP_PKEY* privPkey = fromContextObject(env, privkeyRef); - if (privPkey == nullptr) { - JNI_TRACE("ECDH_compute_key => privPkey == null"); - return -1; + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_GROUP_get_cofactor(%p)", group); + if (group == nullptr) { + return nullptr; } - JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) <- ptr", outArray, outOffset, pubPkey, privPkey); - ScopedByteArrayRW out(env, outArray); - if (out.get() == nullptr) { - JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) can't get output buffer", outArray, outOffset, - pubPkey, privPkey); - return -1; + bssl::UniquePtr cofactor(BN_new()); + if (cofactor.get() == nullptr) { + JNI_TRACE("EC_GROUP_get_cofactor(%p) => can't create BN", group); + conscrypt::jniutil::throwOutOfMemory(env, "BN_new"); + return nullptr; } - if (ARRAY_OFFSET_INVALID(out, outOffset)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - nullptr); - return -1; + if (EC_GROUP_get_cofactor(group, cofactor.get(), nullptr) != 1) { + JNI_TRACE("EC_GROUP_get_cofactor(%p) => threw error", group); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_GROUP_get_cofactor"); + return nullptr; } - if (pubPkey == nullptr) { - JNI_TRACE("ECDH_compute_key(%p) => pubPkey == null", pubPkey); - conscrypt::jniutil::throwNullPointerException(env, "pubPkey == null"); - return -1; + jbyteArray cofactorArray = bignumToArray(env, cofactor.get(), "cofactor"); + if (env->ExceptionCheck()) { + return nullptr; } - bssl::UniquePtr pubkey(EVP_PKEY_get1_EC_KEY(pubPkey)); - if (pubkey.get() == nullptr) { - JNI_TRACE("ECDH_compute_key(%p) => can't get public key", pubPkey); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_PKEY_get1_EC_KEY public", conscrypt::jniutil::throwInvalidKeyException); - return -1; - } + JNI_TRACE("EC_GROUP_get_cofactor(%p) => %p", group, cofactorArray); + return cofactorArray; +} - const EC_POINT* pubkeyPoint = EC_KEY_get0_public_key(pubkey.get()); - if (pubkeyPoint == nullptr) { - JNI_TRACE("ECDH_compute_key(%p) => can't get public key point", pubPkey); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_PKEY_get1_EC_KEY public", conscrypt::jniutil::throwInvalidKeyException); - return -1; - } +static void NativeCrypto_EC_GROUP_clear_free(JNIEnv* env, jclass, jlong groupRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EC_GROUP* group = reinterpret_cast(groupRef); + JNI_TRACE("EC_GROUP_clear_free(%p)", group); - if (privPkey == nullptr) { - JNI_TRACE("ECDH_compute_key(%p) => privKey == null", pubPkey); - conscrypt::jniutil::throwNullPointerException(env, "privPkey == null"); - return -1; + if (group == nullptr) { + JNI_TRACE("EC_GROUP_clear_free => group == null"); + conscrypt::jniutil::throwNullPointerException(env, "group == null"); + return; } - bssl::UniquePtr privkey(EVP_PKEY_get1_EC_KEY(privPkey)); - if (privkey.get() == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_PKEY_get1_EC_KEY private", conscrypt::jniutil::throwInvalidKeyException); - return -1; + EC_GROUP_free(group); + JNI_TRACE("EC_GROUP_clear_free(%p) => success", group); +} + +static jlong NativeCrypto_EC_GROUP_get_generator(JNIEnv* env, jclass, jobject groupRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_GROUP_get_generator(%p)", group); + + if (group == nullptr) { + JNI_TRACE("EC_POINT_get_generator(%p) => group == null", group); + return 0; } - std::size_t stdOutOffset = static_cast(outOffset); - int outputLength = ECDH_compute_key(&out[stdOutOffset], out.size() - stdOutOffset, pubkeyPoint, - privkey.get(), nullptr /* No KDF */); - if (outputLength == -1) { - JNI_TRACE("ECDH_compute_key(%p) => outputLength = -1", pubPkey); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "ECDH_compute_key", conscrypt::jniutil::throwInvalidKeyException); - return -1; + const EC_POINT* generator = EC_GROUP_get0_generator(group); + + bssl::UniquePtr dup(EC_POINT_dup(generator, group)); + if (dup.get() == nullptr) { + JNI_TRACE("EC_GROUP_get_generator(%p) => oom error", group); + conscrypt::jniutil::throwOutOfMemory(env, "unable to dupe generator"); + return 0; } - JNI_TRACE("ECDH_compute_key(%p) => outputLength=%d", pubPkey, outputLength); - return outputLength; + JNI_TRACE("EC_GROUP_get_generator(%p) => %p", group, dup.get()); + return reinterpret_cast(dup.release()); } -static jint NativeCrypto_ECDSA_size(JNIEnv* env, jclass, jobject pkeyRef) { +static jlong NativeCrypto_EC_POINT_new(JNIEnv* env, jclass, jobject groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("ECDSA_size(%p)", pkey); + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_POINT_new(%p)", group); - if (pkey == nullptr) { + if (group == nullptr) { + JNI_TRACE("EC_POINT_new(%p) => group == null", group); return 0; } - bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(pkey)); - if (ec_key.get() == nullptr) { - conscrypt::jniutil::throwRuntimeException(env, "ECDSA_size failed"); - ERR_clear_error(); + EC_POINT* point = EC_POINT_new(group); + if (point == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable create an EC_POINT"); return 0; } - size_t size = ECDSA_size(ec_key.get()); - - JNI_TRACE("ECDSA_size(%p) => %zu", pkey, size); - return static_cast(size); + return reinterpret_cast(point); } -static jint NativeCrypto_ECDSA_sign(JNIEnv* env, jclass, jbyteArray data, jint dataLen, - jbyteArray sig, jobject pkeyRef) { +static void NativeCrypto_EC_POINT_clear_free(JNIEnv* env, jclass, jlong groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("ECDSA_sign(%p, %d, %p, %p)", data, dataLen, sig, pkey); + EC_POINT* group = reinterpret_cast(groupRef); + JNI_TRACE("EC_POINT_clear_free(%p)", group); - if (pkey == nullptr) { - return -1; + if (group == nullptr) { + JNI_TRACE("EC_POINT_clear_free => group == null"); + conscrypt::jniutil::throwNullPointerException(env, "group == null"); + return; } - bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(pkey)); - if (ec_key.get() == nullptr) { - return -1; - } + EC_POINT_free(group); + JNI_TRACE("EC_POINT_clear_free(%p) => success", group); +} - ScopedByteArrayRO data_array(env, data); - if (data_array.get() == nullptr) { - return -1; +static void NativeCrypto_EC_POINT_set_affine_coordinates(JNIEnv* env, jclass, jobject groupRef, + jobject pointRef, jbyteArray xjavaBytes, + jbyteArray yjavaBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p)", groupRef, pointRef, xjavaBytes, + yjavaBytes); + const EC_GROUP* group = fromContextObject(env, groupRef); + if (group == nullptr) { + return; } + EC_POINT* point = fromContextObject(env, pointRef); + if (point == nullptr) { + return; + } + JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p) <- ptr", group, point, xjavaBytes, + yjavaBytes); - if (ARRAY_OFFSET_LENGTH_INVALID(data_array, 0, dataLen)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "dataLen"); - return -1; + bssl::UniquePtr x = arrayToBignum(env, xjavaBytes); + if (x == nullptr) { + return; } - ScopedByteArrayRW sig_array(env, sig); - if (sig_array.get() == nullptr) { - return -1; + bssl::UniquePtr y = arrayToBignum(env, yjavaBytes); + if (y == nullptr) { + return; } - unsigned int sig_size; - int result = - ECDSA_sign(0, reinterpret_cast(data_array.get()), dataLen, - reinterpret_cast(sig_array.get()), &sig_size, ec_key.get()); - if (result == 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "ECDSA_sign"); - JNI_TRACE("ECDSA_sign => threw error"); - return -1; + int ret = EC_POINT_set_affine_coordinates_GFp(group, point, x.get(), y.get(), nullptr); + if (ret != 1) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, + "EC_POINT_set_affine_coordinates"); + return; } - JNI_TRACE("ECDSA_sign(%p, %p, %p) => %d", data, sig, pkey, sig_size); - return static_cast(sig_size); + JNI_TRACE("EC_POINT_set_affine_coordinates(%p, %p, %p, %p) => %d", group, point, xjavaBytes, + yjavaBytes, ret); } -static jint NativeCrypto_ECDSA_verify(JNIEnv* env, jclass, jbyteArray data, jint dataLen, - jbyteArray sig, jobject pkeyRef) { +static jobjectArray NativeCrypto_EC_POINT_get_affine_coordinates(JNIEnv* env, jclass, + jobject groupRef, + jobject pointRef) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("ECDSA_verify(%p, %d, %p, %p)", data, dataLen, sig, pkey); - - if (pkey == nullptr) { - return -1; - } - - bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(pkey)); - if (ec_key.get() == nullptr) { - return -1; + JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p)", groupRef, pointRef); + const EC_GROUP* group = fromContextObject(env, groupRef); + if (group == nullptr) { + return nullptr; } - - ScopedByteArrayRO data_array(env, data); - if (data_array.get() == nullptr) { - return -1; + const EC_POINT* point = fromContextObject(env, pointRef); + if (point == nullptr) { + return nullptr; } + JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p) <- ptr", group, point); - if (ARRAY_OFFSET_LENGTH_INVALID(data_array, 0, dataLen)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "dataLen"); - return -1; + bssl::UniquePtr x(BN_new()); + bssl::UniquePtr y(BN_new()); + + int ret = EC_POINT_get_affine_coordinates_GFp(group, point, x.get(), y.get(), nullptr); + if (ret != 1) { + JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p)", group, point); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, + "EC_POINT_get_affine_coordinates"); + return nullptr; } - ScopedByteArrayRO sig_array(env, sig); - if (sig_array.get() == nullptr) { - return -1; + jobjectArray joa = env->NewObjectArray(2, conscrypt::jniutil::byteArrayClass, nullptr); + if (joa == nullptr) { + return nullptr; } - int result = ECDSA_verify(0, reinterpret_cast(data_array.get()), dataLen, - reinterpret_cast(sig_array.get()), - sig_array.size(), ec_key.get()); + jbyteArray xBytes = bignumToArray(env, x.get(), "x"); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(joa, 0, xBytes); - if (result == 0) { - // NOLINTNEXTLINE(runtime/int) - unsigned long error = ERR_peek_last_error(); - if ((ERR_GET_LIB(error) == ERR_LIB_ECDSA) && - (ERR_GET_REASON(error) == ECDSA_R_BAD_SIGNATURE)) { - // This error just means the signature didn't verify, so clear the error - // and return a failed verification - ERR_clear_error(); - JNI_TRACE("ECDSA_verify(%p, %d, %p, %p) => %d", data, dataLen, sig, pkey, result); - return 0; - } - if (error != 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "ECDSA_verify"); - JNI_TRACE("ECDSA_verify => threw error"); - return -1; - } - return 0; + jbyteArray yBytes = bignumToArray(env, y.get(), "y"); + if (env->ExceptionCheck()) { + return nullptr; } + env->SetObjectArrayElement(joa, 1, yBytes); - JNI_TRACE("ECDSA_verify(%p, %d, %p, %p) => %d", data, dataLen, sig, pkey, result); - return static_cast(result); + JNI_TRACE("EC_POINT_get_affine_coordinates(%p, %p) => %p", group, point, joa); + return joa; } -static jbyteArray NativeCrypto_MLDSA44_public_key_from_seed(JNIEnv* env, jclass, - jbyteArray privateKeySeed) { +static jlong NativeCrypto_EC_KEY_generate_key(JNIEnv* env, jclass, jobject groupRef) { CHECK_ERROR_QUEUE_ON_RETURN; - - ScopedByteArrayRO seedArray(env, privateKeySeed); - if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA44_public_key_from_seed => privateKeySeed == null"); - return nullptr; + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_KEY_generate_key(%p)", group); + if (group == nullptr) { + return 0; } - MLDSA44_private_key privateKey; - if (!MLDSA44_private_key_from_seed( - &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { - JNI_TRACE("MLDSA44_private_key_from_seed failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA44_private_key_from_seed"); - return nullptr; + bssl::UniquePtr eckey(EC_KEY_new()); + if (eckey.get() == nullptr) { + JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_new() oom", group); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to create an EC_KEY"); + return 0; } - MLDSA44_public_key publicKey; - if (!MLDSA44_public_from_private(&publicKey, &privateKey)) { - JNI_TRACE("MLDSA44_public_from_private failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA44_public_from_private"); - return nullptr; + if (EC_KEY_set_group(eckey.get(), group) != 1) { + JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_set_group error", group); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); + return 0; } - ScopedLocalRef publicKeyRef( - env, env->NewByteArray(static_cast(MLDSA44_PUBLIC_KEY_BYTES))); - if (publicKeyRef.get() == nullptr) { - return nullptr; + if (EC_KEY_generate_key(eckey.get()) != 1) { + JNI_TRACE("EC_KEY_generate_key(%p) => EC_KEY_generate_key error", group); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_set_group"); + return 0; } - ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); - if (publicKeyArray.get() == nullptr) { - return nullptr; + bssl::UniquePtr pkey(EVP_PKEY_new()); + if (pkey.get() == nullptr) { + JNI_TRACE("EC_KEY_generate_key(%p) => threw error", group); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EC_KEY_generate_key"); + return 0; } - - CBB cbb; - size_t size; - if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), - MLDSA44_PUBLIC_KEY_BYTES) || - !MLDSA44_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || - size != MLDSA44_PUBLIC_KEY_BYTES) { - JNI_TRACE("Failed to serialize ML-DSA public key."); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA44_marshal_public_key"); - return nullptr; + if (EVP_PKEY_assign_EC_KEY(pkey.get(), eckey.get()) != 1) { + conscrypt::jniutil::throwRuntimeException(env, "EVP_PKEY_assign_EC_KEY failed"); + ERR_clear_error(); + return 0; } - return publicKeyRef.release(); + OWNERSHIP_TRANSFERRED(eckey); + + JNI_TRACE("EC_KEY_generate_key(%p) => %p", group, pkey.get()); + return reinterpret_cast(pkey.release()); } -static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass, - jbyteArray privateKeySeed) { +static jlong NativeCrypto_EC_KEY_get1_group(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("EC_KEY_get1_group(%p)", pkey); - ScopedByteArrayRO seedArray(env, privateKeySeed); - if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA65_public_key_from_seed => privateKeySeed == null"); - return nullptr; + if (pkey == nullptr) { + JNI_TRACE("EC_KEY_get1_group(%p) => pkey == null", pkey); + return 0; } - MLDSA65_private_key privateKey; - if (!MLDSA65_private_key_from_seed( - &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { - JNI_TRACE("MLDSA65_private_key_from_seed failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_private_key_from_seed"); - return nullptr; + if (EVP_PKEY_id(pkey) != EVP_PKEY_EC) { + conscrypt::jniutil::throwRuntimeException(env, "not EC key"); + JNI_TRACE("EC_KEY_get1_group(%p) => not EC key (type == %d)", pkey, EVP_PKEY_id(pkey)); + return 0; } - MLDSA65_public_key publicKey; - if (!MLDSA65_public_from_private(&publicKey, &privateKey)) { - JNI_TRACE("MLDSA65_public_from_private failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_public_from_private"); - return nullptr; - } + EC_GROUP* group = EC_GROUP_dup(EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(pkey))); + JNI_TRACE("EC_KEY_get1_group(%p) => %p", pkey, group); + return reinterpret_cast(group); +} - ScopedLocalRef publicKeyRef( - env, env->NewByteArray(static_cast(MLDSA65_PUBLIC_KEY_BYTES))); - if (publicKeyRef.get() == nullptr) { +static jbyteArray NativeCrypto_EC_KEY_get_private_key(JNIEnv* env, jclass, jobject pkeyRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("EC_KEY_get_private_key(%p)", pkey); + + if (pkey == nullptr) { + JNI_TRACE("EC_KEY_get_private_key => pkey == null"); return nullptr; } - ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); - if (publicKeyArray.get() == nullptr) { + bssl::UniquePtr eckey(EVP_PKEY_get1_EC_KEY(pkey)); + if (eckey.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get1_EC_KEY"); return nullptr; } - CBB cbb; - size_t size; - if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), - MLDSA65_PUBLIC_KEY_BYTES) || - !MLDSA65_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || - size != MLDSA65_PUBLIC_KEY_BYTES) { - JNI_TRACE("Failed to serialize ML-DSA public key."); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_marshal_public_key"); + const BIGNUM* privkey = EC_KEY_get0_private_key(eckey.get()); + + jbyteArray privBytes = bignumToArray(env, privkey, "privkey"); + if (env->ExceptionCheck()) { + JNI_TRACE("EC_KEY_get_private_key(%p) => threw error", pkey); return nullptr; } - return publicKeyRef.release(); + JNI_TRACE("EC_KEY_get_private_key(%p) => %p", pkey, privBytes); + return privBytes; } -static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed(JNIEnv* env, jclass, - jbyteArray privateKeySeed) { +static jlong NativeCrypto_EC_KEY_get_public_key(JNIEnv* env, jclass, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("EC_KEY_get_public_key(%p)", pkey); - ScopedByteArrayRO seedArray(env, privateKeySeed); - if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_MLDSA87_public_key_from_seed => privateKeySeed == null"); - return nullptr; + if (pkey == nullptr) { + JNI_TRACE("EC_KEY_get_public_key => pkey == null"); + return 0; } - MLDSA87_private_key privateKey; - if (!MLDSA87_private_key_from_seed( - &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { - JNI_TRACE("MLDSA87_private_key_from_seed failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_private_key_from_seed"); - return nullptr; + bssl::UniquePtr eckey(EVP_PKEY_get1_EC_KEY(pkey)); + if (eckey.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_PKEY_get1_EC_KEY"); + return 0; } - MLDSA87_public_key publicKey; - if (!MLDSA87_public_from_private(&publicKey, &privateKey)) { - JNI_TRACE("MLDSA87_public_from_private failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_public_from_private"); - return nullptr; + bssl::UniquePtr dup( + EC_POINT_dup(EC_KEY_get0_public_key(eckey.get()), EC_KEY_get0_group(eckey.get()))); + if (dup.get() == nullptr) { + JNI_TRACE("EC_KEY_get_public_key(%p) => can't dup public key", pkey); + conscrypt::jniutil::throwRuntimeException(env, "EC_POINT_dup"); + ERR_clear_error(); + return 0; } - ScopedLocalRef publicKeyRef( - env, env->NewByteArray(static_cast(MLDSA87_PUBLIC_KEY_BYTES))); - if (publicKeyRef.get() == nullptr) { + JNI_TRACE("EC_KEY_get_public_key(%p) => %p", pkey, dup.get()); + return reinterpret_cast(dup.release()); +} + +static jbyteArray NativeCrypto_EC_KEY_marshal_curve_name(JNIEnv* env, jclass, jobject groupRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EC_GROUP* group = fromContextObject(env, groupRef); + JNI_TRACE("EC_KEY_marshal_curve_name(%p)", group); + if (group == nullptr) { + env->ExceptionClear(); + conscrypt::jniutil::throwIOException(env, "Invalid group pointer"); + JNI_TRACE("group=%p EC_KEY_marshal_curve_name => Invalid group pointer", group); return nullptr; } - ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); - if (publicKeyArray.get() == nullptr) { + bssl::ScopedCBB cbb; + if (!CBB_init(cbb.get(), 64)) { + conscrypt::jniutil::throwOutOfMemory(env, "CBB_init failed"); + JNI_TRACE("CBB_init failed"); return nullptr; } - CBB cbb; - size_t size; - if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), - MLDSA87_PUBLIC_KEY_BYTES) || - !MLDSA87_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || - size != MLDSA87_PUBLIC_KEY_BYTES) { - JNI_TRACE("Failed to serialize ML-DSA public key."); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_marshal_public_key"); + if (!EC_KEY_marshal_curve_name(cbb.get(), group)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + ERR_clear_error(); + JNI_TRACE("group=%p EC_KEY_marshal_curve_name => error", group); return nullptr; } - return publicKeyRef.release(); + + return CBBToByteArray(env, cbb.get()); } -static void NativeCrypto_SLHDSA_SHA2_128S_generate_key(JNIEnv* env, jclass, - jbyteArray outPublicArray, - jbyteArray outPrivateArray) { +static jlong NativeCrypto_EC_KEY_parse_curve_name(JNIEnv* env, jclass, jbyteArray curveNameBytes) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p)", outPublicArray, outPrivateArray); + JNI_TRACE("EC_KEY_parse_curve_name(%p)", curveNameBytes); - ScopedByteArrayRW outPublic(env, outPublicArray); - if (outPublic.get() == nullptr) { - JNI_TRACE( - "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output public key " - "buffer", - outPublicArray, outPrivateArray); - return; + ScopedByteArrayRO bytes(env, curveNameBytes); + if (bytes.get() == nullptr) { + env->ExceptionClear(); + conscrypt::jniutil::throwIOException(env, "Null EC curve name"); + JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => curveNameBytes == null ", curveNameBytes); + return 0; } - ScopedByteArrayRW outPrivate(env, outPrivateArray); - if (outPrivate.get() == nullptr) { - JNI_TRACE( - "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output private key " - "buffer", - outPublicArray, outPrivateArray); - return; + CBS cbs; + CBS_init(&cbs, reinterpret_cast(bytes.get()), bytes.size()); + bssl::UniquePtr group(EC_KEY_parse_curve_name(&cbs)); + if (!group || CBS_len(&cbs) != 0) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + ERR_clear_error(); + JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => threw exception", curveNameBytes); + return 0; } - if (outPublic.size() != SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES) { - conscrypt::jniutil::throwIllegalArgumentException(env, - "Output public key array length != 32"); - return; + JNI_TRACE("bytes=%p EC_KEY_parse_curve_name => %p", curveNameBytes, group.get()); + return reinterpret_cast(group.release()); +} + +static jint NativeCrypto_ECDH_compute_key(JNIEnv* env, jclass, jbyteArray outArray, jint outOffset, + jobject pubkeyRef, jobject privkeyRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p)", outArray, outOffset, pubkeyRef, privkeyRef); + EVP_PKEY* pubPkey = fromContextObject(env, pubkeyRef); + if (pubPkey == nullptr) { + JNI_TRACE("ECDH_compute_key => pubPkey == null"); + return -1; + } + EVP_PKEY* privPkey = fromContextObject(env, privkeyRef); + if (privPkey == nullptr) { + JNI_TRACE("ECDH_compute_key => privPkey == null"); + return -1; } + JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) <- ptr", outArray, outOffset, pubPkey, privPkey); - if (outPrivate.size() != SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES) { - conscrypt::jniutil::throwIllegalArgumentException(env, - "Output private key array length != 64"); - return; + ScopedByteArrayRW out(env, outArray); + if (out.get() == nullptr) { + JNI_TRACE("ECDH_compute_key(%p, %d, %p, %p) can't get output buffer", outArray, outOffset, + pubPkey, privPkey); + return -1; } - SLHDSA_SHA2_128S_generate_key(reinterpret_cast(outPublic.get()), - reinterpret_cast(outPrivate.get())); - JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p) => success", outPublicArray, outPrivateArray); -} + if (ARRAY_OFFSET_INVALID(out, outOffset)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + nullptr); + return -1; + } -static jbyteArray NativeCrypto_SLHDSA_SHA2_128S_sign(JNIEnv* env, jclass, jbyteArray data, - jint dataLen, jbyteArray privateKey) { - CHECK_ERROR_QUEUE_ON_RETURN; + if (pubPkey == nullptr) { + JNI_TRACE("ECDH_compute_key(%p) => pubPkey == null", pubPkey); + conscrypt::jniutil::throwNullPointerException(env, "pubPkey == null"); + return -1; + } - ScopedByteArrayRO privateKeyArray(env, privateKey); - if (privateKeyArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_SLHDSA_SHA2_128S_sign => privateKey == null"); - return nullptr; + bssl::UniquePtr pubkey(EVP_PKEY_get1_EC_KEY(pubPkey)); + if (pubkey.get() == nullptr) { + JNI_TRACE("ECDH_compute_key(%p) => can't get public key", pubPkey); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_PKEY_get1_EC_KEY public", conscrypt::jniutil::throwInvalidKeyException); + return -1; } - if (privateKeyArray.size() != SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Private key array length != 64"); - return nullptr; + const EC_POINT* pubkeyPoint = EC_KEY_get0_public_key(pubkey.get()); + if (pubkeyPoint == nullptr) { + JNI_TRACE("ECDH_compute_key(%p) => can't get public key point", pubPkey); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_PKEY_get1_EC_KEY public", conscrypt::jniutil::throwInvalidKeyException); + return -1; } - ScopedByteArrayRO dataArray(env, data); - if (dataArray.get() == nullptr) { - return nullptr; + if (privPkey == nullptr) { + JNI_TRACE("ECDH_compute_key(%p) => privKey == null", pubPkey); + conscrypt::jniutil::throwNullPointerException(env, "privPkey == null"); + return -1; } - if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "dataLen"); - return nullptr; + bssl::UniquePtr privkey(EVP_PKEY_get1_EC_KEY(privPkey)); + if (privkey.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_PKEY_get1_EC_KEY private", conscrypt::jniutil::throwInvalidKeyException); + return -1; } - ScopedLocalRef resultRef( - env, env->NewByteArray(static_cast(SLHDSA_SHA2_128S_SIGNATURE_BYTES))); - if (resultRef.get() == nullptr) { - return nullptr; + std::size_t stdOutOffset = static_cast(outOffset); + int outputLength = ECDH_compute_key(&out[stdOutOffset], out.size() - stdOutOffset, pubkeyPoint, + privkey.get(), nullptr /* No KDF */); + if (outputLength == -1) { + JNI_TRACE("ECDH_compute_key(%p) => outputLength = -1", pubPkey); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "ECDH_compute_key", conscrypt::jniutil::throwInvalidKeyException); + return -1; } - ScopedByteArrayRW resultArray(env, resultRef.get()); - if (resultArray.get() == nullptr) { - return nullptr; + JNI_TRACE("ECDH_compute_key(%p) => outputLength=%d", pubPkey, outputLength); + return outputLength; +} + +static jint NativeCrypto_ECDSA_size(JNIEnv* env, jclass, jobject pkeyRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("ECDSA_size(%p)", pkey); + + if (pkey == nullptr) { + return 0; } - if (!SLHDSA_SHA2_128S_sign(reinterpret_cast(resultArray.get()), - reinterpret_cast(privateKeyArray.get()), - reinterpret_cast(dataArray.get()), dataLen, - /* context */ NULL, /* context_len */ 0)) { - JNI_TRACE("SLHDSA_SHA2_128S_sign failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SLHDSA_SHA2_128S_sign"); - return nullptr; + bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(pkey)); + if (ec_key.get() == nullptr) { + conscrypt::jniutil::throwRuntimeException(env, "ECDSA_size failed"); + ERR_clear_error(); + return 0; } - return resultRef.release(); + size_t size = ECDSA_size(ec_key.get()); + + JNI_TRACE("ECDSA_size(%p) => %zu", pkey, size); + return static_cast(size); } -static jint NativeCrypto_SLHDSA_SHA2_128S_verify(JNIEnv* env, jclass, jbyteArray data, jint dataLen, - jbyteArray sig, jbyteArray publicKey) { +static jint NativeCrypto_ECDSA_sign(JNIEnv* env, jclass, jbyteArray data, jint dataLen, + jbyteArray sig, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("ECDSA_sign(%p, %d, %p, %p)", data, dataLen, sig, pkey); - ScopedByteArrayRO publicKeyArray(env, publicKey); - if (publicKeyArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_SLHDSA_SHA2_128S_verify => publicKey == null"); + if (pkey == nullptr) { return -1; } - if (publicKeyArray.size() != SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Public key array length != 32"); + bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(pkey)); + if (ec_key.get() == nullptr) { return -1; } - ScopedByteArrayRO dataArray(env, data); - if (dataArray.get() == nullptr) { + ScopedByteArrayRO data_array(env, data); + if (data_array.get() == nullptr) { return -1; } - if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { + if (ARRAY_OFFSET_LENGTH_INVALID(data_array, 0, dataLen)) { conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "dataLen"); return -1; } - ScopedByteArrayRO sigArray(env, sig); - if (sigArray.get() == nullptr) { + ScopedByteArrayRW sig_array(env, sig); + if (sig_array.get() == nullptr) { return -1; } - int result = SLHDSA_SHA2_128S_verify( - reinterpret_cast(sigArray.get()), sigArray.size(), - reinterpret_cast(publicKeyArray.get()), - reinterpret_cast(dataArray.get()), dataLen, - /*context=*/NULL, /*context_len=*/0); + unsigned int sig_size; + int result = + ECDSA_sign(0, reinterpret_cast(data_array.get()), dataLen, + reinterpret_cast(sig_array.get()), &sig_size, ec_key.get()); + if (result == 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "ECDSA_sign"); + JNI_TRACE("ECDSA_sign => threw error"); + return -1; + } - JNI_TRACE("NativeCrypto_SLHDSA_SHA2_128S_verify(%p, %p, %p) => %d", publicKey, sig, data, - result); - return static_cast(result); + JNI_TRACE("ECDSA_sign(%p, %p, %p) => %d", data, sig, pkey, sig_size); + return static_cast(sig_size); } -static jboolean NativeCrypto_X25519(JNIEnv* env, jclass, jbyteArray outArray, - jbyteArray privkeyArray, jbyteArray pubkeyArray) { +static jint NativeCrypto_ECDSA_verify(JNIEnv* env, jclass, jbyteArray data, jint dataLen, + jbyteArray sig, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("X25519(%p, %p, %p)", outArray, privkeyArray, pubkeyArray); + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("ECDSA_verify(%p, %d, %p, %p)", data, dataLen, sig, pkey); - ScopedByteArrayRW out(env, outArray); - if (out.get() == nullptr) { - JNI_TRACE("X25519(%p, %p, %p) can't get output buffer", outArray, privkeyArray, - pubkeyArray); - return JNI_FALSE; + if (pkey == nullptr) { + return -1; } - if (out.size() != 32) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Output array length != 32"); - return JNI_FALSE; + + bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(pkey)); + if (ec_key.get() == nullptr) { + return -1; } - ScopedByteArrayRO privkey(env, privkeyArray); - if (privkey.get() == nullptr) { - JNI_TRACE("X25519(%p) => privkey == null", outArray); - return JNI_FALSE; + ScopedByteArrayRO data_array(env, data); + if (data_array.get() == nullptr) { + return -1; } - if (privkey.size() != 32) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Private key array length != 32"); - return JNI_FALSE; + + if (ARRAY_OFFSET_LENGTH_INVALID(data_array, 0, dataLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "dataLen"); + return -1; } - ScopedByteArrayRO pubkey(env, pubkeyArray); - if (pubkey.get() == nullptr) { - JNI_TRACE("X25519(%p) => pubkey == null", outArray); - return JNI_FALSE; - } - if (pubkey.size() != 32) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Public key array length != 32"); - return JNI_FALSE; - } - - if (X25519(reinterpret_cast(out.get()), - reinterpret_cast(privkey.get()), - reinterpret_cast(pubkey.get())) != 1) { - JNI_TRACE("X25519(%p) => failure", outArray); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "X25519", conscrypt::jniutil::throwInvalidKeyException); - return JNI_FALSE; - } - - JNI_TRACE("X25519(%p) => success", outArray); - return JNI_TRUE; -} - -static void NativeCrypto_X25519_keypair(JNIEnv* env, jclass, jbyteArray outPublicArray, - jbyteArray outPrivateArray) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("X25519_keypair(%p, %p)", outPublicArray, outPrivateArray); - - ScopedByteArrayRW outPublic(env, outPublicArray); - if (outPublic.get() == nullptr) { - JNI_TRACE("X25519_keypair(%p, %p) can't get output public key buffer", outPublicArray, - outPrivateArray); - return; - } - - ScopedByteArrayRW outPrivate(env, outPrivateArray); - if (outPrivate.get() == nullptr) { - JNI_TRACE("X25519_keypair(%p, %p) can't get output private key buffer", outPublicArray, - outPrivateArray); - return; - } - - if (outPublic.size() != X25519_PUBLIC_VALUE_LEN || - outPrivate.size() != X25519_PRIVATE_KEY_LEN) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Output key array length != 32"); - return; - } - - X25519_keypair(reinterpret_cast(outPublic.get()), - reinterpret_cast(outPrivate.get())); - JNI_TRACE("X25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray); -} - -static void NativeCrypto_ED25519_keypair(JNIEnv* env, jclass, jbyteArray outPublicArray, - jbyteArray outPrivateArray) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("ED25519_keypair(%p, %p)", outPublicArray, outPrivateArray); - - ScopedByteArrayRW outPublic(env, outPublicArray); - if (outPublic.get() == nullptr) { - JNI_TRACE("ED25519_keypair(%p, %p) can't get output public key buffer", outPublicArray, - outPrivateArray); - return; - } - - ScopedByteArrayRW outPrivate(env, outPrivateArray); - if (outPrivate.get() == nullptr) { - JNI_TRACE("ED25519_keypair(%p, %p) can't get output private key buffer", outPublicArray, - outPrivateArray); - return; + ScopedByteArrayRO sig_array(env, sig); + if (sig_array.get() == nullptr) { + return -1; } - if (outPublic.size() != ED25519_PUBLIC_KEY_LEN) { - conscrypt::jniutil::throwIllegalArgumentException(env, - "Output public key array length != 32"); - return; - } + int result = ECDSA_verify(0, reinterpret_cast(data_array.get()), dataLen, + reinterpret_cast(sig_array.get()), + sig_array.size(), ec_key.get()); - if (outPrivate.size() != ED25519_PRIVATE_KEY_LEN) { - conscrypt::jniutil::throwIllegalArgumentException(env, - "Output private key array length != 64"); - return; + if (result == 0) { + // NOLINTNEXTLINE(runtime/int) + unsigned long error = ERR_peek_last_error(); + if ((ERR_GET_LIB(error) == ERR_LIB_ECDSA) && + (ERR_GET_REASON(error) == ECDSA_R_BAD_SIGNATURE)) { + // This error just means the signature didn't verify, so clear the error + // and return a failed verification + ERR_clear_error(); + JNI_TRACE("ECDSA_verify(%p, %d, %p, %p) => %d", data, dataLen, sig, pkey, result); + return 0; + } + if (error != 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "ECDSA_verify"); + JNI_TRACE("ECDSA_verify => threw error"); + return -1; + } + return 0; } - ED25519_keypair(reinterpret_cast(outPublic.get()), - reinterpret_cast(outPrivate.get())); - JNI_TRACE("ED25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray); + JNI_TRACE("ECDSA_verify(%p, %d, %p, %p) => %d", data, dataLen, sig, pkey, result); + return static_cast(result); } -static jbyteArray NativeCrypto_XWING_public_key_from_seed(JNIEnv* env, jclass, - jbyteArray privateKeySeed) { +static jbyteArray NativeCrypto_MLDSA44_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_XWING_public_key_from_seed => privateKeySeed == null"); + JNI_TRACE("NativeCrypto_MLDSA44_public_key_from_seed => privateKeySeed == null"); return nullptr; } - if (seedArray.size() != XWING_PRIVATE_KEY_BYTES) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "privateKeySeed length != 32"); + MLDSA44_private_key privateKey; + if (!MLDSA44_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLDSA44_private_key_from_seed failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA44_private_key_from_seed"); return nullptr; } - CBS cbs; - CBS_init(&cbs, reinterpret_cast(seedArray.get()), seedArray.size()); - XWING_private_key privateKey; - if (!XWING_parse_private_key(&privateKey, &cbs)) { - JNI_TRACE("XWING_parse_private_key failed"); - conscrypt::jniutil::throwIllegalArgumentException(env, "XWING_parse_private_key failed"); + MLDSA44_public_key publicKey; + if (!MLDSA44_public_from_private(&publicKey, &privateKey)) { + JNI_TRACE("MLDSA44_public_from_private failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA44_public_from_private"); return nullptr; } ScopedLocalRef publicKeyRef( - env, env->NewByteArray(static_cast(XWING_PUBLIC_KEY_BYTES))); + env, env->NewByteArray(static_cast(MLDSA44_PUBLIC_KEY_BYTES))); if (publicKeyRef.get() == nullptr) { return nullptr; } + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); if (publicKeyArray.get() == nullptr) { return nullptr; } - if (!XWING_public_from_private(reinterpret_cast(publicKeyArray.get()), &privateKey)) { - JNI_TRACE("XWING_public_from_private failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "XWING_public_from_private"); + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + MLDSA44_PUBLIC_KEY_BYTES) || + !MLDSA44_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLDSA44_PUBLIC_KEY_BYTES) { + JNI_TRACE("Failed to serialize ML-DSA public key."); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA44_marshal_public_key"); return nullptr; } - return publicKeyRef.release(); } -static jbyteArray NativeCrypto_MLKEM768_public_key_from_seed(JNIEnv* env, jclass, - jbyteArray privateKeySeed) { +static jbyteArray NativeCrypto_MLDSA65_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("MLKEM768_public_key_from_seed => privateKeySeed == null"); + JNI_TRACE("NativeCrypto_MLDSA65_public_key_from_seed => privateKeySeed == null"); return nullptr; } - if (seedArray.size() != MLKEM_SEED_BYTES) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "privateKeySeed length != 64"); + MLDSA65_private_key privateKey; + if (!MLDSA65_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLDSA65_private_key_from_seed failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_private_key_from_seed"); return nullptr; } - MLKEM768_private_key privateKey; - if (!MLKEM768_private_key_from_seed( - &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { - JNI_TRACE("MLKEM768_private_key_from_seed failed"); - conscrypt::jniutil::throwIllegalArgumentException(env, - "MLKEM768_private_key_from_seed failed"); + MLDSA65_public_key publicKey; + if (!MLDSA65_public_from_private(&publicKey, &privateKey)) { + JNI_TRACE("MLDSA65_public_from_private failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_public_from_private"); return nullptr; } - MLKEM768_public_key publicKey; - MLKEM768_public_from_private(&publicKey, &privateKey); - ScopedLocalRef publicKeyRef( - env, env->NewByteArray(static_cast(MLKEM768_PUBLIC_KEY_BYTES))); + env, env->NewByteArray(static_cast(MLDSA65_PUBLIC_KEY_BYTES))); if (publicKeyRef.get() == nullptr) { return nullptr; } + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); if (publicKeyArray.get() == nullptr) { return nullptr; @@ -3284,49 +3368,48 @@ static jbyteArray NativeCrypto_MLKEM768_public_key_from_seed(JNIEnv* env, jclass CBB cbb; size_t size; if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), - publicKeyArray.size()) || - !MLKEM768_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || - size != MLKEM768_PUBLIC_KEY_BYTES) { - JNI_TRACE("MLKEM768_marshal_public_key failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM768_marshal_public_key"); + MLDSA65_PUBLIC_KEY_BYTES) || + !MLDSA65_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLDSA65_PUBLIC_KEY_BYTES) { + JNI_TRACE("Failed to serialize ML-DSA public key."); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA65_marshal_public_key"); return nullptr; } + return publicKeyRef.release(); } -static jbyteArray NativeCrypto_MLKEM1024_public_key_from_seed(JNIEnv* env, jclass, - jbyteArray privateKeySeed) { +static jbyteArray NativeCrypto_MLDSA87_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; ScopedByteArrayRO seedArray(env, privateKeySeed); if (seedArray.get() == nullptr) { - JNI_TRACE("MLKEM1024_public_key_from_seed => privateKeySeed == null"); + JNI_TRACE("NativeCrypto_MLDSA87_public_key_from_seed => privateKeySeed == null"); return nullptr; } - if (seedArray.size() != MLKEM_SEED_BYTES) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "privateKeySeed length != 64"); + MLDSA87_private_key privateKey; + if (!MLDSA87_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLDSA87_private_key_from_seed failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_private_key_from_seed"); return nullptr; } - MLKEM1024_private_key privateKey; - if (!MLKEM1024_private_key_from_seed( - &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { - JNI_TRACE("MLKEM1024_private_key_from_seed failed"); - conscrypt::jniutil::throwIllegalArgumentException(env, - "MLKEM1024_private_key_from_seed failed"); + MLDSA87_public_key publicKey; + if (!MLDSA87_public_from_private(&publicKey, &privateKey)) { + JNI_TRACE("MLDSA87_public_from_private failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_public_from_private"); return nullptr; } - MLKEM1024_public_key publicKey; - MLKEM1024_public_from_private(&publicKey, &privateKey); - ScopedLocalRef publicKeyRef( - env, env->NewByteArray(static_cast(MLKEM1024_PUBLIC_KEY_BYTES))); + env, env->NewByteArray(static_cast(MLDSA87_PUBLIC_KEY_BYTES))); if (publicKeyRef.get() == nullptr) { return nullptr; } + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); if (publicKeyArray.get() == nullptr) { return nullptr; @@ -3335,7313 +3418,6910 @@ static jbyteArray NativeCrypto_MLKEM1024_public_key_from_seed(JNIEnv* env, jclas CBB cbb; size_t size; if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), - publicKeyArray.size()) || - !MLKEM1024_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || - size != MLKEM1024_PUBLIC_KEY_BYTES) { - JNI_TRACE("MLKEM1024_marshal_public_key failed"); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM1024_marshal_public_key"); + MLDSA87_PUBLIC_KEY_BYTES) || + !MLDSA87_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLDSA87_PUBLIC_KEY_BYTES) { + JNI_TRACE("Failed to serialize ML-DSA public key."); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLDSA87_marshal_public_key"); return nullptr; } return publicKeyRef.release(); } -static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) { +static void NativeCrypto_SLHDSA_SHA2_128S_generate_key(JNIEnv* env, jclass, + jbyteArray outPublicArray, + jbyteArray outPrivateArray) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE_MD("EVP_MD_CTX_create()"); + JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p)", outPublicArray, outPrivateArray); - bssl::UniquePtr ctx(EVP_MD_CTX_create()); - if (ctx.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable create a EVP_MD_CTX"); - return 0; + ScopedByteArrayRW outPublic(env, outPublicArray); + if (outPublic.get() == nullptr) { + JNI_TRACE( + "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output public key " + "buffer", + outPublicArray, outPrivateArray); + return; } - JNI_TRACE_MD("EVP_MD_CTX_create() => %p", ctx.get()); - return reinterpret_cast(ctx.release()); -} + ScopedByteArrayRW outPrivate(env, outPrivateArray); + if (outPrivate.get() == nullptr) { + JNI_TRACE( + "SLHDSA_SHA2_128S_generate_key(%p, %p) can't get output private key " + "buffer", + outPublicArray, outPrivateArray); + return; + } -static void NativeCrypto_EVP_MD_CTX_cleanup(JNIEnv* env, jclass, jobject ctxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE_MD("EVP_MD_CTX_cleanup(%p)", ctx); + if (outPublic.size() != SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES) { + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output public key array length != 32"); + return; + } - if (ctx != nullptr) { - EVP_MD_CTX_cleanup(ctx); + if (outPrivate.size() != SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES) { + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output private key array length != 64"); + return; } + + SLHDSA_SHA2_128S_generate_key(reinterpret_cast(outPublic.get()), + reinterpret_cast(outPrivate.get())); + JNI_TRACE("SLHDSA_SHA2_128S_generate_key(%p, %p) => success", outPublicArray, outPrivateArray); } -static void NativeCrypto_EVP_MD_CTX_destroy(JNIEnv* env, jclass, jlong ctxRef) { +static jbyteArray NativeCrypto_SLHDSA_SHA2_128S_sign(JNIEnv* env, jclass, jbyteArray data, + jint dataLen, jbyteArray privateKey) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* ctx = reinterpret_cast(ctxRef); - JNI_TRACE_MD("EVP_MD_CTX_destroy(%p)", ctx); - if (ctx != nullptr) { - EVP_MD_CTX_destroy(ctx); + ScopedByteArrayRO privateKeyArray(env, privateKey); + if (privateKeyArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_SLHDSA_SHA2_128S_sign => privateKey == null"); + return nullptr; } -} -static jint NativeCrypto_EVP_MD_CTX_copy_ex(JNIEnv* env, jclass, jobject dstCtxRef, - jobject srcCtxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE_MD("EVP_MD_CTX_copy_ex(%p. %p)", dstCtxRef, srcCtxRef); - EVP_MD_CTX* dst_ctx = fromContextObject(env, dstCtxRef); - if (dst_ctx == nullptr) { - JNI_TRACE_MD("EVP_MD_CTX_copy_ex => dst_ctx == null"); - return 0; + if (privateKeyArray.size() != SLHDSA_SHA2_128S_PRIVATE_KEY_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Private key array length != 64"); + return nullptr; } - const EVP_MD_CTX* src_ctx = fromContextObject(env, srcCtxRef); - if (src_ctx == nullptr) { - JNI_TRACE_MD("EVP_MD_CTX_copy_ex => src_ctx == null"); - return 0; + + ScopedByteArrayRO dataArray(env, data); + if (dataArray.get() == nullptr) { + return nullptr; } - JNI_TRACE_MD("EVP_MD_CTX_copy_ex(%p. %p) <- ptr", dst_ctx, src_ctx); - int result = EVP_MD_CTX_copy_ex(dst_ctx, src_ctx); - if (result == 0) { - conscrypt::jniutil::throwRuntimeException(env, "Unable to copy EVP_MD_CTX"); - ERR_clear_error(); + if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "dataLen"); + return nullptr; } - JNI_TRACE_MD("EVP_MD_CTX_copy_ex(%p, %p) => %d", dst_ctx, src_ctx, result); - return result; + ScopedLocalRef resultRef( + env, env->NewByteArray(static_cast(SLHDSA_SHA2_128S_SIGNATURE_BYTES))); + if (resultRef.get() == nullptr) { + return nullptr; + } + + ScopedByteArrayRW resultArray(env, resultRef.get()); + if (resultArray.get() == nullptr) { + return nullptr; + } + + if (!SLHDSA_SHA2_128S_sign(reinterpret_cast(resultArray.get()), + reinterpret_cast(privateKeyArray.get()), + reinterpret_cast(dataArray.get()), dataLen, + /* context */ NULL, /* context_len */ 0)) { + JNI_TRACE("SLHDSA_SHA2_128S_sign failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SLHDSA_SHA2_128S_sign"); + return nullptr; + } + + return resultRef.release(); } -/* - * public static native int EVP_DigestFinal_ex(long, byte[], int) - */ -static jint NativeCrypto_EVP_DigestFinal_ex(JNIEnv* env, jclass, jobject ctxRef, jbyteArray hash, - jint offset) { +static jint NativeCrypto_SLHDSA_SHA2_128S_verify(JNIEnv* env, jclass, jbyteArray data, jint dataLen, + jbyteArray sig, jbyteArray publicKey) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE_MD("EVP_DigestFinal_ex(%p, %p, %d)", ctx, hash, offset); - if (ctx == nullptr) { - JNI_TRACE("EVP_DigestFinal_ex => ctx == null"); - return -1; - } else if (hash == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "hash == null"); + ScopedByteArrayRO publicKeyArray(env, publicKey); + if (publicKeyArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_SLHDSA_SHA2_128S_verify => publicKey == null"); return -1; } - ScopedByteArrayRW hashBytes(env, hash); - if (hashBytes.get() == nullptr) { + if (publicKeyArray.size() != SLHDSA_SHA2_128S_PUBLIC_KEY_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Public key array length != 32"); return -1; } - unsigned int bytesWritten = static_cast(-1); - int ok = EVP_DigestFinal_ex(ctx, reinterpret_cast(hashBytes.get() + offset), - &bytesWritten); - if (ok == 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestFinal_ex"); + + ScopedByteArrayRO dataArray(env, data); + if (dataArray.get() == nullptr) { return -1; } - JNI_TRACE_MD("EVP_DigestFinal_ex(%p, %p, %d) => %d (%d)", ctx, hash, offset, bytesWritten, ok); - return static_cast(bytesWritten); -} - -static jint NativeCrypto_EVP_DigestInit_ex(JNIEnv* env, jclass, jobject evpMdCtxRef, - jlong evpMdRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* ctx = fromContextObject(env, evpMdCtxRef); - const EVP_MD* evp_md = reinterpret_cast(evpMdRef); - JNI_TRACE_MD("EVP_DigestInit_ex(%p, %p)", ctx, evp_md); - - if (ctx == nullptr) { - JNI_TRACE("EVP_DigestInit_ex(%p) => ctx == null", evp_md); - return 0; - } else if (evp_md == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "evp_md == null"); - return 0; + if (ARRAY_OFFSET_LENGTH_INVALID(dataArray, 0, dataLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "dataLen"); + return -1; } - int ok = EVP_DigestInit_ex(ctx, evp_md, nullptr); - if (ok == 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestInit_ex"); - JNI_TRACE("EVP_DigestInit_ex(%p) => threw exception", evp_md); - return 0; + ScopedByteArrayRO sigArray(env, sig); + if (sigArray.get() == nullptr) { + return -1; } - JNI_TRACE_MD("EVP_DigestInit_ex(%p, %p) => %d", ctx, evp_md, ok); - return ok; + + int result = SLHDSA_SHA2_128S_verify( + reinterpret_cast(sigArray.get()), sigArray.size(), + reinterpret_cast(publicKeyArray.get()), + reinterpret_cast(dataArray.get()), dataLen, + /*context=*/NULL, /*context_len=*/0); + + JNI_TRACE("NativeCrypto_SLHDSA_SHA2_128S_verify(%p, %p, %p) => %d", publicKey, sig, data, + result); + return static_cast(result); } -/* - * public static native int EVP_get_digestbyname(java.lang.String) - */ -static jlong NativeCrypto_EVP_get_digestbyname(JNIEnv* env, jclass, jstring algorithm) { +static jboolean NativeCrypto_X25519(JNIEnv* env, jclass, jbyteArray outArray, + jbyteArray privkeyArray, jbyteArray pubkeyArray) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%p)", algorithm); + JNI_TRACE("X25519(%p, %p, %p)", outArray, privkeyArray, pubkeyArray); - if (algorithm == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, nullptr); - return -1; + ScopedByteArrayRW out(env, outArray); + if (out.get() == nullptr) { + JNI_TRACE("X25519(%p, %p, %p) can't get output buffer", outArray, privkeyArray, + pubkeyArray); + return JNI_FALSE; + } + if (out.size() != 32) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Output array length != 32"); + return JNI_FALSE; } - ScopedUtfChars algorithmChars(env, algorithm); - if (algorithmChars.c_str() == nullptr) { - return 0; + ScopedByteArrayRO privkey(env, privkeyArray); + if (privkey.get() == nullptr) { + JNI_TRACE("X25519(%p) => privkey == null", outArray); + return JNI_FALSE; + } + if (privkey.size() != 32) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Private key array length != 32"); + return JNI_FALSE; } - JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%s)", algorithmChars.c_str()); - const char* alg = algorithmChars.c_str(); - const EVP_MD* md; + ScopedByteArrayRO pubkey(env, pubkeyArray); + if (pubkey.get() == nullptr) { + JNI_TRACE("X25519(%p) => pubkey == null", outArray); + return JNI_FALSE; + } + if (pubkey.size() != 32) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Public key array length != 32"); + return JNI_FALSE; + } - if (strcasecmp(alg, "md4") == 0) { - md = EVP_md4(); - } else if (strcasecmp(alg, "md5") == 0) { - md = EVP_md5(); - } else if (strcasecmp(alg, "sha1") == 0) { - md = EVP_sha1(); - } else if (strcasecmp(alg, "sha224") == 0) { - md = EVP_sha224(); - } else if (strcasecmp(alg, "sha256") == 0) { - md = EVP_sha256(); - } else if (strcasecmp(alg, "sha384") == 0) { - md = EVP_sha384(); - } else if (strcasecmp(alg, "sha512") == 0) { - md = EVP_sha512(); - } else { - JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%s) => error", alg); - conscrypt::jniutil::throwRuntimeException(env, "Hash algorithm not found"); - return 0; + if (X25519(reinterpret_cast(out.get()), + reinterpret_cast(privkey.get()), + reinterpret_cast(pubkey.get())) != 1) { + JNI_TRACE("X25519(%p) => failure", outArray); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "X25519", conscrypt::jniutil::throwInvalidKeyException); + return JNI_FALSE; } - return reinterpret_cast(md); + JNI_TRACE("X25519(%p) => success", outArray); + return JNI_TRUE; } -/* - * public static native int EVP_MD_size(long) - */ -static jint NativeCrypto_EVP_MD_size(JNIEnv* env, jclass, jlong evpMdRef) { +static void NativeCrypto_X25519_keypair(JNIEnv* env, jclass, jbyteArray outPublicArray, + jbyteArray outPrivateArray) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD* evp_md = reinterpret_cast(evpMdRef); - JNI_TRACE("NativeCrypto_EVP_MD_size(%p)", evp_md); + JNI_TRACE("X25519_keypair(%p, %p)", outPublicArray, outPrivateArray); - if (evp_md == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, nullptr); - return -1; + ScopedByteArrayRW outPublic(env, outPublicArray); + if (outPublic.get() == nullptr) { + JNI_TRACE("X25519_keypair(%p, %p) can't get output public key buffer", outPublicArray, + outPrivateArray); + return; } - jint result = static_cast(EVP_MD_size(evp_md)); - JNI_TRACE("NativeCrypto_EVP_MD_size(%p) => %d", evp_md, result); - return result; -} + ScopedByteArrayRW outPrivate(env, outPrivateArray); + if (outPrivate.get() == nullptr) { + JNI_TRACE("X25519_keypair(%p, %p) can't get output private key buffer", outPublicArray, + outPrivateArray); + return; + } -static jlong evpDigestSignVerifyInit(JNIEnv* env, - int (*init_func)(EVP_MD_CTX*, EVP_PKEY_CTX**, const EVP_MD*, - ENGINE*, EVP_PKEY*), - const char* jniName, jobject evpMdCtxRef, jlong evpMdRef, - jobject pkeyRef) { - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - if (mdCtx == nullptr) { - JNI_TRACE("%s => mdCtx == null", jniName); - return 0; - } - const EVP_MD* md = reinterpret_cast(evpMdRef); - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - if (pkey == nullptr) { - JNI_TRACE("ctx=%p %s => pkey == null", mdCtx, jniName); - return 0; - } - JNI_TRACE("%s(%p, %p, %p) <- ptr", jniName, mdCtx, md, pkey); - - // Allow md to be null for ED25519, ML_DSA_44, ML_DSA_65, and ML_DSA_87. See - // https://github.com/google/boringssl/blob/main/include/openssl/evp.h - int pkey_id = EVP_PKEY_id(pkey); - if (md == nullptr && (pkey_id != EVP_PKEY_ED25519 && pkey_id != EVP_PKEY_ML_DSA_44 && - pkey_id != EVP_PKEY_ML_DSA_65 && pkey_id != EVP_PKEY_ML_DSA_87)) { - JNI_TRACE("ctx=%p %s => md == null", mdCtx, jniName); - conscrypt::jniutil::throwNullPointerException(env, "md == null"); - return 0; - } - - EVP_PKEY_CTX* pctx = nullptr; - if (init_func(mdCtx, &pctx, md, nullptr, pkey) <= 0) { - JNI_TRACE("ctx=%p %s => threw exception", mdCtx, jniName); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, jniName); - return 0; + if (outPublic.size() != X25519_PUBLIC_VALUE_LEN || + outPrivate.size() != X25519_PRIVATE_KEY_LEN) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Output key array length != 32"); + return; } - JNI_TRACE("%s(%p, %p, %p) => success", jniName, mdCtx, md, pkey); - return reinterpret_cast(pctx); -} - -static jlong NativeCrypto_EVP_DigestSignInit(JNIEnv* env, jclass, jobject evpMdCtxRef, - const jlong evpMdRef, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - return evpDigestSignVerifyInit(env, EVP_DigestSignInit, "EVP_DigestSignInit", evpMdCtxRef, - evpMdRef, pkeyRef); + X25519_keypair(reinterpret_cast(outPublic.get()), + reinterpret_cast(outPrivate.get())); + JNI_TRACE("X25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray); } -static jlong NativeCrypto_EVP_DigestVerifyInit(JNIEnv* env, jclass, jobject evpMdCtxRef, - const jlong evpMdRef, jobject pkeyRef) { +static void NativeCrypto_ED25519_keypair(JNIEnv* env, jclass, jbyteArray outPublicArray, + jbyteArray outPrivateArray) { CHECK_ERROR_QUEUE_ON_RETURN; - return evpDigestSignVerifyInit(env, EVP_DigestVerifyInit, "EVP_DigestVerifyInit", evpMdCtxRef, - evpMdRef, pkeyRef); -} + JNI_TRACE("ED25519_keypair(%p, %p)", outPublicArray, outPrivateArray); -static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jlong inPtr, jint inLength, - const char* jniName, int (*update_func)(EVP_MD_CTX*, const void*, size_t)) { - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - const void* p = reinterpret_cast(inPtr); - JNI_TRACE_MD("%s(%p, %p, %d)", jniName, mdCtx, p, inLength); + ScopedByteArrayRW outPublic(env, outPublicArray); + if (outPublic.get() == nullptr) { + JNI_TRACE("ED25519_keypair(%p, %p) can't get output public key buffer", outPublicArray, + outPrivateArray); + return; + } - if (mdCtx == nullptr) { + ScopedByteArrayRW outPrivate(env, outPrivateArray); + if (outPrivate.get() == nullptr) { + JNI_TRACE("ED25519_keypair(%p, %p) can't get output private key buffer", outPublicArray, + outPrivateArray); return; } - if (p == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, nullptr); + if (outPublic.size() != ED25519_PUBLIC_KEY_LEN) { + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output public key array length != 32"); return; } - if (!update_func(mdCtx, p, static_cast(inLength))) { - JNI_TRACE("ctx=%p %s => threw exception", mdCtx, jniName); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, jniName); + if (outPrivate.size() != ED25519_PRIVATE_KEY_LEN) { + conscrypt::jniutil::throwIllegalArgumentException(env, + "Output private key array length != 64"); return; } - JNI_TRACE_MD("%s(%p, %p, %d) => success", jniName, mdCtx, p, inLength); + ED25519_keypair(reinterpret_cast(outPublic.get()), + reinterpret_cast(outPrivate.get())); + JNI_TRACE("ED25519_keypair(%p, %p) => success", outPublicArray, outPrivateArray); } -static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jbyteArray inJavaBytes, jint inOffset, - jint inLength, const char* jniName, - int (*update_func)(EVP_MD_CTX*, const void*, size_t)) { - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - JNI_TRACE_MD("%s(%p, %p, %d, %d)", jniName, mdCtx, inJavaBytes, inOffset, inLength); +static jbyteArray NativeCrypto_XWING_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { + CHECK_ERROR_QUEUE_ON_RETURN; - if (mdCtx == nullptr) { - return; + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_XWING_public_key_from_seed => privateKeySeed == null"); + return nullptr; } - if (inJavaBytes == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "inBytes"); - return; + if (seedArray.size() != XWING_PRIVATE_KEY_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "privateKeySeed length != 32"); + return nullptr; } - size_t array_size = static_cast(env->GetArrayLength(inJavaBytes)); - if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "inBytes"); - return; - } - if (inLength == 0) { - return; + CBS cbs; + CBS_init(&cbs, reinterpret_cast(seedArray.get()), seedArray.size()); + XWING_private_key privateKey; + if (!XWING_parse_private_key(&privateKey, &cbs)) { + JNI_TRACE("XWING_parse_private_key failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, "XWING_parse_private_key failed"); + return nullptr; } - jint in_offset = inOffset; - jint in_size = inLength; - int update_func_result = -1; - if (conscrypt::jniutil::isGetByteArrayElementsLikelyToReturnACopy(array_size)) { - // GetByteArrayElements is expected to return a copy. Use GetByteArrayRegion - // instead, to avoid copying the whole array. - if (in_size <= 1024) { - // For small chunk, it's more efficient to use a bit more space on the - // stack instead of allocating a new buffer. - jbyte buf[1024]; - env->GetByteArrayRegion(inJavaBytes, in_offset, in_size, buf); - update_func_result = update_func(mdCtx, reinterpret_cast(buf), - static_cast(in_size)); - } else { - // For large chunk, allocate a 64 kB buffer and stream the chunk into - // update_func through the buffer, stopping as soon as update_func fails. - jint remaining = in_size; - jint buf_size = (remaining >= 65536) ? 65536 : remaining; - std::unique_ptr buf(new jbyte[static_cast(buf_size)]); - if (buf.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate chunk buffer"); - return; - } - while (remaining > 0) { - jint chunk_size = (remaining >= buf_size) ? buf_size : remaining; - env->GetByteArrayRegion(inJavaBytes, in_offset, chunk_size, buf.get()); - update_func_result = - update_func(mdCtx, reinterpret_cast(buf.get()), - static_cast(chunk_size)); - if (!update_func_result) { - // update_func failed. This will be handled later in this method. - break; - } - in_offset += chunk_size; - remaining -= chunk_size; - } - } - } else { - // GetByteArrayElements is expected to not return a copy. Use - // GetByteArrayElements. We're not using ScopedByteArrayRO here because its - // an implementation detail whether it'll use GetByteArrayElements or - // another approach. - jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr); - if (array_elements == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes"); - return; - } - const unsigned char* buf = reinterpret_cast(array_elements); - update_func_result = update_func(mdCtx, buf + in_offset, static_cast(in_size)); - env->ReleaseByteArrayElements(inJavaBytes, array_elements, JNI_ABORT); + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(XWING_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; } - - if (!update_func_result) { - JNI_TRACE("ctx=%p %s => threw exception", mdCtx, jniName); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, jniName); - return; + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; } - JNI_TRACE_MD("%s(%p, %p, %d, %d) => success", jniName, mdCtx, inJavaBytes, inOffset, inLength); -} - -static void NativeCrypto_EVP_DigestUpdateDirect(JNIEnv* env, jclass, jobject evpMdCtxRef, - jlong inPtr, jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - evpUpdate(env, evpMdCtxRef, inPtr, inLength, "EVP_DigestUpdateDirect", EVP_DigestUpdate); -} - -static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef, - jbyteArray inJavaBytes, jint inOffset, jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestUpdate", - EVP_DigestUpdate); -} - -static void NativeCrypto_EVP_DigestSignUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef, - jbyteArray inJavaBytes, jint inOffset, - jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestSignUpdate", - EVP_DigestSignUpdate); -} - -static void NativeCrypto_EVP_DigestSignUpdateDirect(JNIEnv* env, jclass, jobject evpMdCtxRef, - jlong inPtr, jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - evpUpdate(env, evpMdCtxRef, inPtr, inLength, "EVP_DigestSignUpdateDirect", - EVP_DigestSignUpdate); -} - -static void NativeCrypto_EVP_DigestVerifyUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef, - jbyteArray inJavaBytes, jint inOffset, - jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestVerifyUpdate", - EVP_DigestVerifyUpdate); -} + if (!XWING_public_from_private(reinterpret_cast(publicKeyArray.get()), &privateKey)) { + JNI_TRACE("XWING_public_from_private failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "XWING_public_from_private"); + return nullptr; + } -static void NativeCrypto_EVP_DigestVerifyUpdateDirect(JNIEnv* env, jclass, jobject evpMdCtxRef, - jlong inPtr, jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - evpUpdate(env, evpMdCtxRef, inPtr, inLength, "EVP_DigestVerifyUpdateDirect", - EVP_DigestVerifyUpdate); + return publicKeyRef.release(); } -static jbyteArray NativeCrypto_EVP_DigestSignFinal(JNIEnv* env, jclass, jobject evpMdCtxRef) { +static jbyteArray NativeCrypto_MLKEM768_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - JNI_TRACE("EVP_DigestSignFinal(%p)", mdCtx); - if (mdCtx == nullptr) { + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("MLKEM768_public_key_from_seed => privateKeySeed == null"); return nullptr; } - size_t maxLen; - if (EVP_DigestSignFinal(mdCtx, nullptr, &maxLen) != 1) { - JNI_TRACE("ctx=%p EVP_DigestSignFinal => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSignFinal"); + if (seedArray.size() != MLKEM_SEED_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "privateKeySeed length != 64"); return nullptr; } - std::unique_ptr buffer(new unsigned char[maxLen]); - if (buffer.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate signature buffer"); - return nullptr; - } - size_t actualLen(maxLen); - if (EVP_DigestSignFinal(mdCtx, buffer.get(), &actualLen) != 1) { - JNI_TRACE("ctx=%p EVP_DigestSignFinal => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSignFinal"); - return nullptr; - } - if (actualLen > maxLen) { - JNI_TRACE("ctx=%p EVP_DigestSignFinal => signature too long: %zd vs %zd", mdCtx, actualLen, - maxLen); - conscrypt::jniutil::throwRuntimeException(env, "EVP_DigestSignFinal signature too long"); - return nullptr; - } - - ScopedLocalRef sigJavaBytes(env, env->NewByteArray(static_cast(actualLen))); - if (sigJavaBytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate signature byte[]"); + MLKEM768_private_key privateKey; + if (!MLKEM768_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLKEM768_private_key_from_seed failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLKEM768_private_key_from_seed failed"); return nullptr; } - env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast(actualLen), - reinterpret_cast(buffer.get())); - - JNI_TRACE("EVP_DigestSignFinal(%p) => %p", mdCtx, sigJavaBytes.get()); - return sigJavaBytes.release(); -} - -static jboolean NativeCrypto_EVP_DigestVerifyFinal(JNIEnv* env, jclass, jobject evpMdCtxRef, - jbyteArray signature, jint offset, jint len) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - JNI_TRACE("EVP_DigestVerifyFinal(%p)", mdCtx); - if (mdCtx == nullptr) { - return 0; - } + MLKEM768_public_key publicKey; + MLKEM768_public_from_private(&publicKey, &privateKey); - ScopedByteArrayRO sigBytes(env, signature); - if (sigBytes.get() == nullptr) { - return 0; + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLKEM768_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { + return nullptr; } - - if (ARRAY_OFFSET_LENGTH_INVALID(sigBytes, offset, len)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "signature"); - return 0; + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { + return nullptr; } - const unsigned char* sigBuf = reinterpret_cast(sigBytes.get()); - int err = EVP_DigestVerifyFinal(mdCtx, sigBuf + offset, static_cast(len)); - jboolean result; - if (err == 1) { - // Signature verified - result = 1; - } else if (err == 0) { - // Signature did not verify - result = 0; - } else { - // Error while verifying signature - JNI_TRACE("ctx=%p EVP_DigestVerifyFinal => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestVerifyFinal"); - return 0; + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + publicKeyArray.size()) || + !MLKEM768_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLKEM768_PUBLIC_KEY_BYTES) { + JNI_TRACE("MLKEM768_marshal_public_key failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM768_marshal_public_key"); + return nullptr; } - - // If the signature did not verify, BoringSSL error queue contains an error - // (BAD_SIGNATURE). Clear the error queue to prevent its state from affecting - // future operations. - ERR_clear_error(); - - JNI_TRACE("EVP_DigestVerifyFinal(%p) => %d", mdCtx, result); - return result; + return publicKeyRef.release(); } -static jbyteArray NativeCrypto_EVP_DigestSign(JNIEnv* env, jclass, jobject evpMdCtxRef, - jbyteArray inJavaBytes, jint inOffset, - jint inLength) { +static jbyteArray NativeCrypto_MLKEM1024_public_key_from_seed(JNIEnv* env, jclass, + jbyteArray privateKeySeed) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - JNI_TRACE_MD("%s(%p, %p, %d, %d)", "EVP_DigestSign", mdCtx, inJavaBytes, inOffset, inLength); - - if (mdCtx == nullptr) { + ScopedByteArrayRO seedArray(env, privateKeySeed); + if (seedArray.get() == nullptr) { + JNI_TRACE("MLKEM1024_public_key_from_seed => privateKeySeed == null"); return nullptr; } - if (inJavaBytes == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "inBytes"); + if (seedArray.size() != MLKEM_SEED_BYTES) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "privateKeySeed length != 64"); return nullptr; } - size_t array_size = static_cast(env->GetArrayLength(inJavaBytes)); - if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "inBytes"); + MLKEM1024_private_key privateKey; + if (!MLKEM1024_private_key_from_seed( + &privateKey, reinterpret_cast(seedArray.get()), seedArray.size())) { + JNI_TRACE("MLKEM1024_private_key_from_seed failed"); + conscrypt::jniutil::throwIllegalArgumentException(env, + "MLKEM1024_private_key_from_seed failed"); return nullptr; } - jint in_offset = inOffset; - jint in_size = inLength; + MLKEM1024_public_key publicKey; + MLKEM1024_public_from_private(&publicKey, &privateKey); - ScopedByteArrayRO array_elements(env, inJavaBytes); - if (array_elements.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes"); + ScopedLocalRef publicKeyRef( + env, env->NewByteArray(static_cast(MLKEM1024_PUBLIC_KEY_BYTES))); + if (publicKeyRef.get() == nullptr) { return nullptr; } - const unsigned char* buf = reinterpret_cast(array_elements.get()); - const unsigned char* inStart = buf + in_offset; - size_t inLen = static_cast(in_size); - - size_t maxLen; - if (EVP_DigestSign(mdCtx, nullptr, &maxLen, inStart, inLen) != 1) { - JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); + ScopedByteArrayRW publicKeyArray(env, publicKeyRef.get()); + if (publicKeyArray.get() == nullptr) { return nullptr; } - std::unique_ptr buffer(new unsigned char[maxLen]); - if (buffer.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate signature buffer"); - return nullptr; - } - size_t actualLen(maxLen); - if (EVP_DigestSign(mdCtx, buffer.get(), &actualLen, inStart, inLen) != 1) { - JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); - return nullptr; - } - if (actualLen > maxLen) { - JNI_TRACE("ctx=%p EVP_DigestSign => signature too long: %zd vs %zd", mdCtx, actualLen, - maxLen); - conscrypt::jniutil::throwRuntimeException(env, "EVP_DigestSign signature too long"); + CBB cbb; + size_t size; + if (!CBB_init_fixed(&cbb, reinterpret_cast(publicKeyArray.get()), + publicKeyArray.size()) || + !MLKEM1024_marshal_public_key(&cbb, &publicKey) || !CBB_finish(&cbb, nullptr, &size) || + size != MLKEM1024_PUBLIC_KEY_BYTES) { + JNI_TRACE("MLKEM1024_marshal_public_key failed"); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "MLKEM1024_marshal_public_key"); return nullptr; } + return publicKeyRef.release(); +} - ScopedLocalRef sigJavaBytes(env, env->NewByteArray(static_cast(actualLen))); - if (sigJavaBytes.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate signature byte[]"); - return nullptr; +static jlong NativeCrypto_EVP_MD_CTX_create(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE_MD("EVP_MD_CTX_create()"); + + bssl::UniquePtr ctx(EVP_MD_CTX_create()); + if (ctx.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable create a EVP_MD_CTX"); + return 0; } - env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast(actualLen), - reinterpret_cast(buffer.get())); - JNI_TRACE("EVP_DigestSign(%p) => %p", mdCtx, sigJavaBytes.get()); - return sigJavaBytes.release(); + JNI_TRACE_MD("EVP_MD_CTX_create() => %p", ctx.get()); + return reinterpret_cast(ctx.release()); } -static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMdCtxRef, - jbyteArray signature, jint sigOffset, jint sigLen, - jbyteArray data, jint dataOffset, jint dataLen) { +static void NativeCrypto_EVP_MD_CTX_cleanup(JNIEnv* env, jclass, jobject ctxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); - JNI_TRACE("EVP_DigestVerify(%p)", mdCtx); + EVP_MD_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE_MD("EVP_MD_CTX_cleanup(%p)", ctx); - if (mdCtx == nullptr) { - return 0; + if (ctx != nullptr) { + EVP_MD_CTX_cleanup(ctx); } +} - ScopedByteArrayRO sigBytes(env, signature); - if (sigBytes.get() == nullptr) { - return 0; - } +static void NativeCrypto_EVP_MD_CTX_destroy(JNIEnv* env, jclass, jlong ctxRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_MD_CTX* ctx = reinterpret_cast(ctxRef); + JNI_TRACE_MD("EVP_MD_CTX_destroy(%p)", ctx); - if (ARRAY_OFFSET_LENGTH_INVALID(sigBytes, sigOffset, sigLen)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "signature"); - return 0; + if (ctx != nullptr) { + EVP_MD_CTX_destroy(ctx); } +} - ScopedByteArrayRO dataBytes(env, data); - if (dataBytes.get() == nullptr) { +static jint NativeCrypto_EVP_MD_CTX_copy_ex(JNIEnv* env, jclass, jobject dstCtxRef, + jobject srcCtxRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE_MD("EVP_MD_CTX_copy_ex(%p. %p)", dstCtxRef, srcCtxRef); + EVP_MD_CTX* dst_ctx = fromContextObject(env, dstCtxRef); + if (dst_ctx == nullptr) { + JNI_TRACE_MD("EVP_MD_CTX_copy_ex => dst_ctx == null"); return 0; } - - if (ARRAY_OFFSET_LENGTH_INVALID(dataBytes, dataOffset, dataLen)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "data"); + const EVP_MD_CTX* src_ctx = fromContextObject(env, srcCtxRef); + if (src_ctx == nullptr) { + JNI_TRACE_MD("EVP_MD_CTX_copy_ex => src_ctx == null"); return 0; } + JNI_TRACE_MD("EVP_MD_CTX_copy_ex(%p. %p) <- ptr", dst_ctx, src_ctx); - const unsigned char* sigBuf = reinterpret_cast(sigBytes.get()); - const unsigned char* dataBuf = reinterpret_cast(dataBytes.get()); - int err = EVP_DigestVerify(mdCtx, sigBuf + sigOffset, static_cast(sigLen), - dataBuf + dataOffset, static_cast(dataLen)); - jboolean result; - if (err == 1) { - // Signature verified - result = 1; - } else if (err == 0) { - // Signature did not verify - result = 0; - } else { - // Error while verifying signature - JNI_TRACE("ctx=%p EVP_DigestVerify => threw exception", mdCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestVerify"); - return 0; + int result = EVP_MD_CTX_copy_ex(dst_ctx, src_ctx); + if (result == 0) { + conscrypt::jniutil::throwRuntimeException(env, "Unable to copy EVP_MD_CTX"); + ERR_clear_error(); } - // If the signature did not verify, BoringSSL error queue contains an error - // (BAD_SIGNATURE). Clear the error queue to prevent its state from affecting - // future operations. - ERR_clear_error(); - - JNI_TRACE("EVP_DigestVerify(%p) => %d", mdCtx, result); + JNI_TRACE_MD("EVP_MD_CTX_copy_ex(%p, %p) => %d", dst_ctx, src_ctx, result); return result; } -static jint evpPkeyEncryptDecrypt(JNIEnv* env, - int (*encrypt_decrypt_func)(EVP_PKEY_CTX*, uint8_t*, size_t*, - const uint8_t*, size_t), - const char* jniName, jobject evpPkeyCtxRef, - jbyteArray outJavaBytes, jint outOffset, jbyteArray inJavaBytes, - jint inOffset, jint inLength) { - EVP_PKEY_CTX* pkeyCtx = fromContextObject(env, evpPkeyCtxRef); - JNI_TRACE_MD("%s(%p, %p, %d, %p, %d, %d)", jniName, pkeyCtx, outJavaBytes, outOffset, - inJavaBytes, inOffset, inLength); - - if (pkeyCtx == nullptr) { - return 0; +/* + * public static native int EVP_DigestFinal_ex(long, byte[], int) + */ +static jint NativeCrypto_EVP_DigestFinal_ex(JNIEnv* env, jclass, jobject ctxRef, jbyteArray hash, + jint offset) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_MD_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE_MD("EVP_DigestFinal_ex(%p, %p, %d)", ctx, hash, offset); + + if (ctx == nullptr) { + JNI_TRACE("EVP_DigestFinal_ex => ctx == null"); + return -1; + } else if (hash == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "hash == null"); + return -1; } - ScopedByteArrayRW outBytes(env, outJavaBytes); - if (outBytes.get() == nullptr) { - return 0; + ScopedByteArrayRW hashBytes(env, hash); + if (hashBytes.get() == nullptr) { + return -1; + } + unsigned int bytesWritten = static_cast(-1); + int ok = EVP_DigestFinal_ex(ctx, reinterpret_cast(hashBytes.get() + offset), + &bytesWritten); + if (ok == 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestFinal_ex"); + return -1; } - ScopedByteArrayRO inBytes(env, inJavaBytes); - if (inBytes.get() == nullptr) { + JNI_TRACE_MD("EVP_DigestFinal_ex(%p, %p, %d) => %d (%d)", ctx, hash, offset, bytesWritten, ok); + return static_cast(bytesWritten); +} + +static jint NativeCrypto_EVP_DigestInit_ex(JNIEnv* env, jclass, jobject evpMdCtxRef, + jlong evpMdRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_MD_CTX* ctx = fromContextObject(env, evpMdCtxRef); + const EVP_MD* evp_md = reinterpret_cast(evpMdRef); + JNI_TRACE_MD("EVP_DigestInit_ex(%p, %p)", ctx, evp_md); + + if (ctx == nullptr) { + JNI_TRACE("EVP_DigestInit_ex(%p) => ctx == null", evp_md); + return 0; + } else if (evp_md == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "evp_md == null"); return 0; } - if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "outBytes"); + int ok = EVP_DigestInit_ex(ctx, evp_md, nullptr); + if (ok == 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestInit_ex"); + JNI_TRACE("EVP_DigestInit_ex(%p) => threw exception", evp_md); return 0; } + JNI_TRACE_MD("EVP_DigestInit_ex(%p, %p) => %d", ctx, evp_md, ok); + return ok; +} - if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "inBytes"); +/* + * public static native int EVP_get_digestbyname(java.lang.String) + */ +static jlong NativeCrypto_EVP_get_digestbyname(JNIEnv* env, jclass, jstring algorithm) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%p)", algorithm); + + if (algorithm == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, nullptr); + return -1; + } + + ScopedUtfChars algorithmChars(env, algorithm); + if (algorithmChars.c_str() == nullptr) { return 0; } + JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%s)", algorithmChars.c_str()); - uint8_t* outBuf = reinterpret_cast(outBytes.get()); - const uint8_t* inBuf = reinterpret_cast(inBytes.get()); - size_t outLength = outBytes.size() - outOffset; - if (!encrypt_decrypt_func(pkeyCtx, outBuf + outOffset, &outLength, inBuf + inOffset, - static_cast(inLength))) { - JNI_TRACE("ctx=%p %s => threw exception", pkeyCtx, jniName); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, jniName, conscrypt::jniutil::throwBadPaddingException); + const char* alg = algorithmChars.c_str(); + const EVP_MD* md; + + if (strcasecmp(alg, "md4") == 0) { + md = EVP_md4(); + } else if (strcasecmp(alg, "md5") == 0) { + md = EVP_md5(); + } else if (strcasecmp(alg, "sha1") == 0) { + md = EVP_sha1(); + } else if (strcasecmp(alg, "sha224") == 0) { + md = EVP_sha224(); + } else if (strcasecmp(alg, "sha256") == 0) { + md = EVP_sha256(); + } else if (strcasecmp(alg, "sha384") == 0) { + md = EVP_sha384(); + } else if (strcasecmp(alg, "sha512") == 0) { + md = EVP_sha512(); + } else { + JNI_TRACE("NativeCrypto_EVP_get_digestbyname(%s) => error", alg); + conscrypt::jniutil::throwRuntimeException(env, "Hash algorithm not found"); return 0; } - JNI_TRACE("%s(%p, %p, %d, %p, %d, %d) => success (%zd bytes)", jniName, pkeyCtx, outJavaBytes, - outOffset, inJavaBytes, inOffset, inLength, outLength); - return static_cast(outLength); + return reinterpret_cast(md); } -static jint NativeCrypto_EVP_PKEY_encrypt(JNIEnv* env, jclass, jobject evpPkeyCtxRef, - jbyteArray out, jint outOffset, jbyteArray inBytes, - jint inOffset, jint inLength) { +/* + * public static native int EVP_MD_size(long) + */ +static jint NativeCrypto_EVP_MD_size(JNIEnv* env, jclass, jlong evpMdRef) { CHECK_ERROR_QUEUE_ON_RETURN; - return evpPkeyEncryptDecrypt(env, EVP_PKEY_encrypt, "EVP_PKEY_encrypt", evpPkeyCtxRef, out, - outOffset, inBytes, inOffset, inLength); -} + EVP_MD* evp_md = reinterpret_cast(evpMdRef); + JNI_TRACE("NativeCrypto_EVP_MD_size(%p)", evp_md); -static jint NativeCrypto_EVP_PKEY_decrypt(JNIEnv* env, jclass, jobject evpPkeyCtxRef, - jbyteArray out, jint outOffset, jbyteArray inBytes, - jint inOffset, jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - return evpPkeyEncryptDecrypt(env, EVP_PKEY_decrypt, "EVP_PKEY_decrypt", evpPkeyCtxRef, out, - outOffset, inBytes, inOffset, inLength); + if (evp_md == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, nullptr); + return -1; + } + + jint result = static_cast(EVP_MD_size(evp_md)); + JNI_TRACE("NativeCrypto_EVP_MD_size(%p) => %d", evp_md, result); + return result; } -static jlong evpPkeyEcryptDecryptInit(JNIEnv* env, jobject evpPkeyRef, - int (*real_func)(EVP_PKEY_CTX*), const char* opType) { - EVP_PKEY* pkey = fromContextObject(env, evpPkeyRef); - JNI_TRACE("EVP_PKEY_%s_init(%p)", opType, pkey); +static jlong evpDigestSignVerifyInit(JNIEnv* env, + int (*init_func)(EVP_MD_CTX*, EVP_PKEY_CTX**, const EVP_MD*, + ENGINE*, EVP_PKEY*), + const char* jniName, jobject evpMdCtxRef, jlong evpMdRef, + jobject pkeyRef) { + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + if (mdCtx == nullptr) { + JNI_TRACE("%s => mdCtx == null", jniName); + return 0; + } + const EVP_MD* md = reinterpret_cast(evpMdRef); + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); if (pkey == nullptr) { - JNI_TRACE("EVP_PKEY_%s_init(%p) => pkey == null", opType, pkey); + JNI_TRACE("ctx=%p %s => pkey == null", mdCtx, jniName); return 0; } + JNI_TRACE("%s(%p, %p, %p) <- ptr", jniName, mdCtx, md, pkey); - bssl::UniquePtr pkeyCtx(EVP_PKEY_CTX_new(pkey, nullptr)); - if (pkeyCtx.get() == nullptr) { - JNI_TRACE("EVP_PKEY_%s_init(%p) => threw exception", opType, pkey); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_PKEY_CTX_new", conscrypt::jniutil::throwInvalidKeyException); + // Allow md to be null for ED25519, ML_DSA_44, ML_DSA_65, and ML_DSA_87. See + // https://github.com/google/boringssl/blob/main/include/openssl/evp.h + int pkey_id = EVP_PKEY_id(pkey); + if (md == nullptr && (pkey_id != EVP_PKEY_ED25519 && pkey_id != EVP_PKEY_ML_DSA_44 && + pkey_id != EVP_PKEY_ML_DSA_65 && pkey_id != EVP_PKEY_ML_DSA_87)) { + JNI_TRACE("ctx=%p %s => md == null", mdCtx, jniName); + conscrypt::jniutil::throwNullPointerException(env, "md == null"); return 0; } - if (!real_func(pkeyCtx.get())) { - JNI_TRACE("EVP_PKEY_%s_init(%p) => threw exception", opType, pkey); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, opType, conscrypt::jniutil::throwInvalidKeyException); + EVP_PKEY_CTX* pctx = nullptr; + if (init_func(mdCtx, &pctx, md, nullptr, pkey) <= 0) { + JNI_TRACE("ctx=%p %s => threw exception", mdCtx, jniName); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, jniName); return 0; } - JNI_TRACE("EVP_PKEY_%s_init(%p) => pkeyCtx=%p", opType, pkey, pkeyCtx.get()); - return reinterpret_cast(pkeyCtx.release()); + JNI_TRACE("%s(%p, %p, %p) => success", jniName, mdCtx, md, pkey); + return reinterpret_cast(pctx); } -static jlong NativeCrypto_EVP_PKEY_encrypt_init(JNIEnv* env, jclass, jobject evpPkeyRef) { +static jlong NativeCrypto_EVP_DigestSignInit(JNIEnv* env, jclass, jobject evpMdCtxRef, + const jlong evpMdRef, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - return evpPkeyEcryptDecryptInit(env, evpPkeyRef, EVP_PKEY_encrypt_init, "encrypt"); + return evpDigestSignVerifyInit(env, EVP_DigestSignInit, "EVP_DigestSignInit", evpMdCtxRef, + evpMdRef, pkeyRef); } -static jlong NativeCrypto_EVP_PKEY_decrypt_init(JNIEnv* env, jclass, jobject evpPkeyRef) { +static jlong NativeCrypto_EVP_DigestVerifyInit(JNIEnv* env, jclass, jobject evpMdCtxRef, + const jlong evpMdRef, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - return evpPkeyEcryptDecryptInit(env, evpPkeyRef, EVP_PKEY_decrypt_init, "decrypt"); + return evpDigestSignVerifyInit(env, EVP_DigestVerifyInit, "EVP_DigestVerifyInit", evpMdCtxRef, + evpMdRef, pkeyRef); } -static void NativeCrypto_EVP_PKEY_CTX_free(JNIEnv* env, jclass, jlong pkeyCtxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(pkeyCtxRef); - JNI_TRACE("EVP_PKEY_CTX_free(%p)", pkeyCtx); +static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jlong inPtr, jint inLength, + const char* jniName, int (*update_func)(EVP_MD_CTX*, const void*, size_t)) { + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + const void* p = reinterpret_cast(inPtr); + JNI_TRACE_MD("%s(%p, %p, %d)", jniName, mdCtx, p, inLength); - if (pkeyCtx != nullptr) { - EVP_PKEY_CTX_free(pkeyCtx); + if (mdCtx == nullptr) { + return; } -} -static void NativeCrypto_EVP_PKEY_CTX_set_rsa_padding(JNIEnv* env, jclass, jlong ctx, jint pad) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(ctx); - JNI_TRACE("EVP_PKEY_CTX_set_rsa_padding(%p, %d)", pkeyCtx, pad); - if (pkeyCtx == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "ctx == null"); + if (p == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, nullptr); return; } - int result = EVP_PKEY_CTX_set_rsa_padding(pkeyCtx, static_cast(pad)); - if (result <= 0) { - JNI_TRACE("ctx=%p EVP_PKEY_CTX_set_rsa_padding => threw exception", pkeyCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_PKEY_CTX_set_rsa_padding", - conscrypt::jniutil::throwInvalidAlgorithmParameterException); + if (!update_func(mdCtx, p, static_cast(inLength))) { + JNI_TRACE("ctx=%p %s => threw exception", mdCtx, jniName); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, jniName); return; } - JNI_TRACE("EVP_PKEY_CTX_set_rsa_padding(%p, %d) => success", pkeyCtx, pad); + JNI_TRACE_MD("%s(%p, %p, %d) => success", jniName, mdCtx, p, inLength); } -static void NativeCrypto_EVP_PKEY_CTX_set_rsa_pss_saltlen(JNIEnv* env, jclass, jlong ctx, - jint len) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(ctx); - JNI_TRACE("EVP_PKEY_CTX_set_rsa_pss_saltlen(%p, %d)", pkeyCtx, len); - if (pkeyCtx == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "ctx == null"); - return; - } +static void evpUpdate(JNIEnv* env, jobject evpMdCtxRef, jbyteArray inJavaBytes, jint inOffset, + jint inLength, const char* jniName, + int (*update_func)(EVP_MD_CTX*, const void*, size_t)) { + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE_MD("%s(%p, %p, %d, %d)", jniName, mdCtx, inJavaBytes, inOffset, inLength); - int result = EVP_PKEY_CTX_set_rsa_pss_saltlen(pkeyCtx, static_cast(len)); - if (result <= 0) { - JNI_TRACE("ctx=%p EVP_PKEY_CTX_set_rsa_pss_saltlen => threw exception", pkeyCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_PKEY_CTX_set_rsa_pss_saltlen", - conscrypt::jniutil::throwInvalidAlgorithmParameterException); + if (mdCtx == nullptr) { return; } - JNI_TRACE("EVP_PKEY_CTX_set_rsa_pss_saltlen(%p, %d) => success", pkeyCtx, len); -} - -static void evpPkeyCtxCtrlMdOp(JNIEnv* env, jlong pkeyCtxRef, jlong mdRef, const char* jniName, - int (*ctrl_func)(EVP_PKEY_CTX*, const EVP_MD*)) { - EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(pkeyCtxRef); - EVP_MD* md = reinterpret_cast(mdRef); - JNI_TRACE("%s(%p, %p)", jniName, pkeyCtx, md); - if (pkeyCtx == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "pkeyCtx == null"); + if (inJavaBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "inBytes"); return; } - if (md == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "md == null"); + + size_t array_size = static_cast(env->GetArrayLength(inJavaBytes)); + if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "inBytes"); + return; + } + if (inLength == 0) { return; } + jint in_offset = inOffset; + jint in_size = inLength; - int result = ctrl_func(pkeyCtx, md); - if (result <= 0) { - JNI_TRACE("ctx=%p %s => threw exception", pkeyCtx, jniName); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, jniName, conscrypt::jniutil::throwInvalidAlgorithmParameterException); + int update_func_result = -1; + if (conscrypt::jniutil::isGetByteArrayElementsLikelyToReturnACopy(array_size)) { + // GetByteArrayElements is expected to return a copy. Use GetByteArrayRegion + // instead, to avoid copying the whole array. + if (in_size <= 1024) { + // For small chunk, it's more efficient to use a bit more space on the + // stack instead of allocating a new buffer. + jbyte buf[1024]; + env->GetByteArrayRegion(inJavaBytes, in_offset, in_size, buf); + update_func_result = update_func(mdCtx, reinterpret_cast(buf), + static_cast(in_size)); + } else { + // For large chunk, allocate a 64 kB buffer and stream the chunk into + // update_func through the buffer, stopping as soon as update_func fails. + jint remaining = in_size; + jint buf_size = (remaining >= 65536) ? 65536 : remaining; + std::unique_ptr buf(new jbyte[static_cast(buf_size)]); + if (buf.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate chunk buffer"); + return; + } + while (remaining > 0) { + jint chunk_size = (remaining >= buf_size) ? buf_size : remaining; + env->GetByteArrayRegion(inJavaBytes, in_offset, chunk_size, buf.get()); + update_func_result = + update_func(mdCtx, reinterpret_cast(buf.get()), + static_cast(chunk_size)); + if (!update_func_result) { + // update_func failed. This will be handled later in this method. + break; + } + in_offset += chunk_size; + remaining -= chunk_size; + } + } + } else { + // GetByteArrayElements is expected to not return a copy. Use + // GetByteArrayElements. We're not using ScopedByteArrayRO here because its + // an implementation detail whether it'll use GetByteArrayElements or + // another approach. + jbyte* array_elements = env->GetByteArrayElements(inJavaBytes, nullptr); + if (array_elements == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes"); + return; + } + const unsigned char* buf = reinterpret_cast(array_elements); + update_func_result = update_func(mdCtx, buf + in_offset, static_cast(in_size)); + env->ReleaseByteArrayElements(inJavaBytes, array_elements, JNI_ABORT); + } + + if (!update_func_result) { + JNI_TRACE("ctx=%p %s => threw exception", mdCtx, jniName); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, jniName); return; } - JNI_TRACE("%s(%p, %p) => success", jniName, pkeyCtx, md); + JNI_TRACE_MD("%s(%p, %p, %d, %d) => success", jniName, mdCtx, inJavaBytes, inOffset, inLength); } -static void NativeCrypto_EVP_PKEY_CTX_set_rsa_mgf1_md(JNIEnv* env, jclass, jlong pkeyCtxRef, - jlong mdRef) { +static void NativeCrypto_EVP_DigestUpdateDirect(JNIEnv* env, jclass, jobject evpMdCtxRef, + jlong inPtr, jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - evpPkeyCtxCtrlMdOp(env, pkeyCtxRef, mdRef, "EVP_PKEY_CTX_set_rsa_mgf1_md", - EVP_PKEY_CTX_set_rsa_mgf1_md); + evpUpdate(env, evpMdCtxRef, inPtr, inLength, "EVP_DigestUpdateDirect", EVP_DigestUpdate); } -static void NativeCrypto_EVP_PKEY_CTX_set_rsa_oaep_md(JNIEnv* env, jclass, jlong pkeyCtxRef, - jlong mdRef) { +static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray inJavaBytes, jint inOffset, jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - evpPkeyCtxCtrlMdOp(env, pkeyCtxRef, mdRef, "EVP_PKEY_CTX_set_rsa_oaep_md", - EVP_PKEY_CTX_set_rsa_oaep_md); + evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestUpdate", + EVP_DigestUpdate); } -static void NativeCrypto_EVP_PKEY_CTX_set_rsa_oaep_label(JNIEnv* env, jclass, jlong pkeyCtxRef, - jbyteArray labelJava) { +static void NativeCrypto_EVP_DigestSignUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray inJavaBytes, jint inOffset, + jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(pkeyCtxRef); - JNI_TRACE("EVP_PKEY_CTX_set_rsa_oaep_label(%p, %p)", pkeyCtx, labelJava); - if (pkeyCtx == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "pkeyCtx == null"); - return; - } - - ScopedByteArrayRO labelBytes(env, labelJava); - if (labelBytes.get() == nullptr) { - return; - } + evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestSignUpdate", + EVP_DigestSignUpdate); +} - bssl::UniquePtr label(reinterpret_cast(OPENSSL_malloc(labelBytes.size()))); - memcpy(label.get(), labelBytes.get(), labelBytes.size()); +static void NativeCrypto_EVP_DigestSignUpdateDirect(JNIEnv* env, jclass, jobject evpMdCtxRef, + jlong inPtr, jint inLength) { + CHECK_ERROR_QUEUE_ON_RETURN; + evpUpdate(env, evpMdCtxRef, inPtr, inLength, "EVP_DigestSignUpdateDirect", + EVP_DigestSignUpdate); +} - int result = EVP_PKEY_CTX_set0_rsa_oaep_label(pkeyCtx, label.get(), labelBytes.size()); - if (result <= 0) { - JNI_TRACE("ctx=%p EVP_PKEY_CTX_set_rsa_oaep_label => threw exception", pkeyCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_PKEY_CTX_set_rsa_oaep_label", - conscrypt::jniutil::throwInvalidAlgorithmParameterException); - return; - } - OWNERSHIP_TRANSFERRED(label); +static void NativeCrypto_EVP_DigestVerifyUpdate(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray inJavaBytes, jint inOffset, + jint inLength) { + CHECK_ERROR_QUEUE_ON_RETURN; + evpUpdate(env, evpMdCtxRef, inJavaBytes, inOffset, inLength, "EVP_DigestVerifyUpdate", + EVP_DigestVerifyUpdate); +} - JNI_TRACE("EVP_PKEY_CTX_set_rsa_oaep_label(%p, %p) => success", pkeyCtx, labelJava); +static void NativeCrypto_EVP_DigestVerifyUpdateDirect(JNIEnv* env, jclass, jobject evpMdCtxRef, + jlong inPtr, jint inLength) { + CHECK_ERROR_QUEUE_ON_RETURN; + evpUpdate(env, evpMdCtxRef, inPtr, inLength, "EVP_DigestVerifyUpdateDirect", + EVP_DigestVerifyUpdate); } -static jlong NativeCrypto_EVP_get_cipherbyname(JNIEnv* env, jclass, jstring algorithm) { +static jbyteArray NativeCrypto_EVP_DigestSignFinal(JNIEnv* env, jclass, jobject evpMdCtxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_get_cipherbyname(%p)", algorithm); + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE("EVP_DigestSignFinal(%p)", mdCtx); - if (algorithm == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "algorithm == null"); - JNI_TRACE("EVP_get_cipherbyname(%p) => algorithm == null", algorithm); - return -1; + if (mdCtx == nullptr) { + return nullptr; } - ScopedUtfChars scoped_alg(env, algorithm); - const char* alg = scoped_alg.c_str(); - const EVP_CIPHER* cipher; + size_t maxLen; + if (EVP_DigestSignFinal(mdCtx, nullptr, &maxLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSignFinal => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSignFinal"); + return nullptr; + } - if (strcasecmp(alg, "rc4") == 0) { - cipher = EVP_rc4(); - } else if (strcasecmp(alg, "des-cbc") == 0) { - cipher = EVP_des_cbc(); - } else if (strcasecmp(alg, "des-ede-cbc") == 0) { - cipher = EVP_des_ede_cbc(); - } else if (strcasecmp(alg, "des-ede3-cbc") == 0) { - cipher = EVP_des_ede3_cbc(); - } else if (strcasecmp(alg, "aes-128-ecb") == 0) { - cipher = EVP_aes_128_ecb(); - } else if (strcasecmp(alg, "aes-128-cbc") == 0) { - cipher = EVP_aes_128_cbc(); - } else if (strcasecmp(alg, "aes-128-ctr") == 0) { - cipher = EVP_aes_128_ctr(); - } else if (strcasecmp(alg, "aes-128-gcm") == 0) { - cipher = EVP_aes_128_gcm(); - } else if (strcasecmp(alg, "aes-192-ecb") == 0) { - cipher = EVP_aes_192_ecb(); - } else if (strcasecmp(alg, "aes-192-cbc") == 0) { - cipher = EVP_aes_192_cbc(); - } else if (strcasecmp(alg, "aes-192-ctr") == 0) { - cipher = EVP_aes_192_ctr(); - } else if (strcasecmp(alg, "aes-192-gcm") == 0) { - cipher = EVP_aes_192_gcm(); - } else if (strcasecmp(alg, "aes-256-ecb") == 0) { - cipher = EVP_aes_256_ecb(); - } else if (strcasecmp(alg, "aes-256-cbc") == 0) { - cipher = EVP_aes_256_cbc(); - } else if (strcasecmp(alg, "aes-256-ctr") == 0) { - cipher = EVP_aes_256_ctr(); - } else if (strcasecmp(alg, "aes-256-gcm") == 0) { - cipher = EVP_aes_256_gcm(); - } else { - JNI_TRACE("NativeCrypto_EVP_get_cipherbyname(%s) => error", alg); - return 0; + std::unique_ptr buffer(new unsigned char[maxLen]); + if (buffer.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate signature buffer"); + return nullptr; + } + size_t actualLen(maxLen); + if (EVP_DigestSignFinal(mdCtx, buffer.get(), &actualLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSignFinal => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSignFinal"); + return nullptr; + } + if (actualLen > maxLen) { + JNI_TRACE("ctx=%p EVP_DigestSignFinal => signature too long: %zd vs %zd", mdCtx, actualLen, + maxLen); + conscrypt::jniutil::throwRuntimeException(env, "EVP_DigestSignFinal signature too long"); + return nullptr; } - return reinterpret_cast(cipher); + ScopedLocalRef sigJavaBytes(env, env->NewByteArray(static_cast(actualLen))); + if (sigJavaBytes.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate signature byte[]"); + return nullptr; + } + env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast(actualLen), + reinterpret_cast(buffer.get())); + + JNI_TRACE("EVP_DigestSignFinal(%p) => %p", mdCtx, sigJavaBytes.get()); + return sigJavaBytes.release(); } -static void NativeCrypto_EVP_CipherInit_ex(JNIEnv* env, jclass, jobject ctxRef, jlong evpCipherRef, - jbyteArray keyArray, jbyteArray ivArray, - jboolean encrypting) { +static jboolean NativeCrypto_EVP_DigestVerifyFinal(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray signature, jint offset, jint len) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - const EVP_CIPHER* evpCipher = reinterpret_cast(evpCipherRef); - JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %p, %d)", ctx, evpCipher, keyArray, ivArray, - encrypting ? 1 : 0); + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE("EVP_DigestVerifyFinal(%p)", mdCtx); - if (ctx == nullptr) { - JNI_TRACE("EVP_CipherInit_ex => ctx == null"); - return; + if (mdCtx == nullptr) { + return 0; } - // The key can be null if we need to set extra parameters. - std::unique_ptr keyPtr; - if (keyArray != nullptr) { - ScopedByteArrayRO keyBytes(env, keyArray); - if (keyBytes.get() == nullptr) { - return; - } + ScopedByteArrayRO sigBytes(env, signature); + if (sigBytes.get() == nullptr) { + return 0; + } - keyPtr.reset(new unsigned char[keyBytes.size()]); - memcpy(keyPtr.get(), keyBytes.get(), keyBytes.size()); + if (ARRAY_OFFSET_LENGTH_INVALID(sigBytes, offset, len)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "signature"); + return 0; } - // The IV can be null if we're using ECB. - std::unique_ptr ivPtr; - if (ivArray != nullptr) { - ScopedByteArrayRO ivBytes(env, ivArray); - if (ivBytes.get() == nullptr) { - return; - } - - ivPtr.reset(new unsigned char[ivBytes.size()]); - memcpy(ivPtr.get(), ivBytes.get(), ivBytes.size()); + const unsigned char* sigBuf = reinterpret_cast(sigBytes.get()); + int err = EVP_DigestVerifyFinal(mdCtx, sigBuf + offset, static_cast(len)); + jboolean result; + if (err == 1) { + // Signature verified + result = 1; + } else if (err == 0) { + // Signature did not verify + result = 0; + } else { + // Error while verifying signature + JNI_TRACE("ctx=%p EVP_DigestVerifyFinal => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestVerifyFinal"); + return 0; } - if (!EVP_CipherInit_ex(ctx, evpCipher, nullptr, keyPtr.get(), ivPtr.get(), - encrypting ? 1 : 0)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_CipherInit_ex"); - JNI_TRACE("EVP_CipherInit_ex => error initializing cipher"); - return; - } + // If the signature did not verify, BoringSSL error queue contains an error + // (BAD_SIGNATURE). Clear the error queue to prevent its state from affecting + // future operations. + ERR_clear_error(); - JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %p, %d) => success", ctx, evpCipher, keyArray, ivArray, - encrypting ? 1 : 0); + JNI_TRACE("EVP_DigestVerifyFinal(%p) => %d", mdCtx, result); + return result; } -/* - * public static native int EVP_CipherUpdate(long ctx, byte[] out, int - * outOffset, byte[] in, int inOffset, int inLength); - */ -static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jobject ctxRef, jbyteArray outArray, - jint outOffset, jbyteArray inArray, jint inOffset, - jint inLength) { +static jbyteArray NativeCrypto_EVP_DigestSign(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray inJavaBytes, jint inOffset, + jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE("EVP_CipherUpdate(%p, %p, %d, %p, %d)", ctx, outArray, outOffset, inArray, inOffset); - if (ctx == nullptr) { - JNI_TRACE("ctx=%p EVP_CipherUpdate => ctx == null", ctx); - return 0; + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE_MD("%s(%p, %p, %d, %d)", "EVP_DigestSign", mdCtx, inJavaBytes, inOffset, inLength); + + if (mdCtx == nullptr) { + return nullptr; } - ScopedByteArrayRO inBytes(env, inArray); - if (inBytes.get() == nullptr) { - return 0; + if (inJavaBytes == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "inBytes"); + return nullptr; } - if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { + + size_t array_size = static_cast(env->GetArrayLength(inJavaBytes)); + if (ARRAY_CHUNK_INVALID(array_size, inOffset, inLength)) { conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "inBytes"); - return 0; + return nullptr; } - ScopedByteArrayRW outBytes(env, outArray); - if (outBytes.get() == nullptr) { - return 0; - } - if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "outBytes"); - return 0; + jint in_offset = inOffset; + jint in_size = inLength; + + ScopedByteArrayRO array_elements(env, inJavaBytes); + if (array_elements.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to obtain elements of inBytes"); + return nullptr; } + const unsigned char* buf = reinterpret_cast(array_elements.get()); + const unsigned char* inStart = buf + in_offset; + size_t inLen = static_cast(in_size); - JNI_TRACE( - "ctx=%p EVP_CipherUpdate in=%p in.length=%zd inOffset=%d inLength=%d " - "out=%p " - "out.length=%zd outOffset=%d", - ctx, inBytes.get(), inBytes.size(), inOffset, inLength, outBytes.get(), outBytes.size(), - outOffset); + size_t maxLen; + if (EVP_DigestSign(mdCtx, nullptr, &maxLen, inStart, inLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); + return nullptr; + } - unsigned char* out = reinterpret_cast(outBytes.get()); - const unsigned char* in = reinterpret_cast(inBytes.get()); + std::unique_ptr buffer(new unsigned char[maxLen]); + if (buffer.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate signature buffer"); + return nullptr; + } + size_t actualLen(maxLen); + if (EVP_DigestSign(mdCtx, buffer.get(), &actualLen, inStart, inLen) != 1) { + JNI_TRACE("ctx=%p EVP_DigestSign => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestSign"); + return nullptr; + } + if (actualLen > maxLen) { + JNI_TRACE("ctx=%p EVP_DigestSign => signature too long: %zd vs %zd", mdCtx, actualLen, + maxLen); + conscrypt::jniutil::throwRuntimeException(env, "EVP_DigestSign signature too long"); + return nullptr; + } - size_t outl; - if (!EVP_CipherUpdate_ex(ctx, out + outOffset, &outl, outBytes.size() - outOffset, - in + inOffset, inLength)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_CipherUpdate_ex"); - JNI_TRACE("ctx=%p EVP_CipherUpdate => threw error", ctx); - return 0; + ScopedLocalRef sigJavaBytes(env, env->NewByteArray(static_cast(actualLen))); + if (sigJavaBytes.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Failed to allocate signature byte[]"); + return nullptr; } + env->SetByteArrayRegion(sigJavaBytes.get(), 0, static_cast(actualLen), + reinterpret_cast(buffer.get())); - JNI_TRACE("EVP_CipherUpdate(%p, %p, %d, %p, %d) => %zu", ctx, outArray, outOffset, inArray, - inOffset, outl); - // outl will fit in jint because EVP_CipherUpdate_ex will not output outBytes.size() bytes. - return static_cast(outl); + JNI_TRACE("EVP_DigestSign(%p) => %p", mdCtx, sigJavaBytes.get()); + return sigJavaBytes.release(); } -static jint NativeCrypto_EVP_CipherFinal_ex(JNIEnv* env, jclass, jobject ctxRef, - jbyteArray outArray, jint outOffset) { +static jboolean NativeCrypto_EVP_DigestVerify(JNIEnv* env, jclass, jobject evpMdCtxRef, + jbyteArray signature, jint sigOffset, jint sigLen, + jbyteArray data, jint dataOffset, jint dataLen) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE("EVP_CipherFinal_ex(%p, %p, %d)", ctx, outArray, outOffset); + EVP_MD_CTX* mdCtx = fromContextObject(env, evpMdCtxRef); + JNI_TRACE("EVP_DigestVerify(%p)", mdCtx); - if (ctx == nullptr) { - JNI_TRACE("ctx=%p EVP_CipherFinal_ex => ctx == null", ctx); + if (mdCtx == nullptr) { return 0; } - ScopedByteArrayRW outBytes(env, outArray); - if (outBytes.get() == nullptr) { + ScopedByteArrayRO sigBytes(env, signature); + if (sigBytes.get() == nullptr) { return 0; } - if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { + + if (ARRAY_OFFSET_LENGTH_INVALID(sigBytes, sigOffset, sigLen)) { conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "outBytes"); + "signature"); return 0; } - unsigned char* out = reinterpret_cast(outBytes.get()); - - size_t outl; - if (!EVP_CipherFinal_ex2(ctx, out + outOffset, &outl, outBytes.size() - outOffset)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_CipherFinal_ex2", conscrypt::jniutil::throwBadPaddingException); - JNI_TRACE("ctx=%p EVP_CipherFinal_ex => threw error", ctx); + ScopedByteArrayRO dataBytes(env, data); + if (dataBytes.get() == nullptr) { return 0; } - JNI_TRACE("EVP_CipherFinal(%p, %p, %d) => %zu", ctx, outArray, outOffset, outl); - // outl will fit in jint because EVP_CipherUpdate_ex will not output outBytes.size() bytes. - return static_cast(outl); -} - -static jint NativeCrypto_EVP_CIPHER_iv_length(JNIEnv* env, jclass, jlong evpCipherRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_CIPHER* evpCipher = reinterpret_cast(evpCipherRef); - JNI_TRACE("EVP_CIPHER_iv_length(%p)", evpCipher); + if (ARRAY_OFFSET_LENGTH_INVALID(dataBytes, dataOffset, dataLen)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "data"); + return 0; + } - if (evpCipher == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "evpCipher == null"); - JNI_TRACE("EVP_CIPHER_iv_length => evpCipher == null"); + const unsigned char* sigBuf = reinterpret_cast(sigBytes.get()); + const unsigned char* dataBuf = reinterpret_cast(dataBytes.get()); + int err = EVP_DigestVerify(mdCtx, sigBuf + sigOffset, static_cast(sigLen), + dataBuf + dataOffset, static_cast(dataLen)); + jboolean result; + if (err == 1) { + // Signature verified + result = 1; + } else if (err == 0) { + // Signature did not verify + result = 0; + } else { + // Error while verifying signature + JNI_TRACE("ctx=%p EVP_DigestVerify => threw exception", mdCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_DigestVerify"); return 0; } - jint ivLength = static_cast(EVP_CIPHER_iv_length(evpCipher)); - JNI_TRACE("EVP_CIPHER_iv_length(%p) => %d", evpCipher, ivLength); - return ivLength; + // If the signature did not verify, BoringSSL error queue contains an error + // (BAD_SIGNATURE). Clear the error queue to prevent its state from affecting + // future operations. + ERR_clear_error(); + + JNI_TRACE("EVP_DigestVerify(%p) => %d", mdCtx, result); + return result; } -static jlong NativeCrypto_EVP_CIPHER_CTX_new(JNIEnv* env, jclass) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_CIPHER_CTX_new()"); +static jint evpPkeyEncryptDecrypt(JNIEnv* env, + int (*encrypt_decrypt_func)(EVP_PKEY_CTX*, uint8_t*, size_t*, + const uint8_t*, size_t), + const char* jniName, jobject evpPkeyCtxRef, + jbyteArray outJavaBytes, jint outOffset, jbyteArray inJavaBytes, + jint inOffset, jint inLength) { + EVP_PKEY_CTX* pkeyCtx = fromContextObject(env, evpPkeyCtxRef); + JNI_TRACE_MD("%s(%p, %p, %d, %p, %d, %d)", jniName, pkeyCtx, outJavaBytes, outOffset, + inJavaBytes, inOffset, inLength); - bssl::UniquePtr ctx(EVP_CIPHER_CTX_new()); - if (ctx.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate cipher context"); - JNI_TRACE("EVP_CipherInit_ex => context allocation error"); + if (pkeyCtx == nullptr) { return 0; } - JNI_TRACE("EVP_CIPHER_CTX_new() => %p", ctx.get()); - return reinterpret_cast(ctx.release()); -} - -static jint NativeCrypto_EVP_CIPHER_CTX_block_size(JNIEnv* env, jclass, jobject ctxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE("EVP_CIPHER_CTX_block_size(%p)", ctx); - - if (ctx == nullptr) { - JNI_TRACE("ctx=%p EVP_CIPHER_CTX_block_size => ctx == null", ctx); + ScopedByteArrayRW outBytes(env, outJavaBytes); + if (outBytes.get() == nullptr) { return 0; } - jint blockSize = static_cast(EVP_CIPHER_CTX_block_size(ctx)); - JNI_TRACE("EVP_CIPHER_CTX_block_size(%p) => %d", ctx, blockSize); - return blockSize; -} + ScopedByteArrayRO inBytes(env, inJavaBytes); + if (inBytes.get() == nullptr) { + return 0; + } -static jint NativeCrypto_get_EVP_CIPHER_CTX_buf_len(JNIEnv* env, jclass, jobject ctxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE("get_EVP_CIPHER_CTX_buf_len(%p)", ctx); + if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "outBytes"); + return 0; + } - if (ctx == nullptr) { - JNI_TRACE("ctx=%p get_EVP_CIPHER_CTX_buf_len => ctx == null", ctx); + if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "inBytes"); return 0; } - int buf_len = ctx->buf_len; - JNI_TRACE("get_EVP_CIPHER_CTX_buf_len(%p) => %d", ctx, buf_len); - return buf_len; -} - -static jboolean NativeCrypto_get_EVP_CIPHER_CTX_final_used(JNIEnv* env, jclass, jobject ctxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE("get_EVP_CIPHER_CTX_final_used(%p)", ctx); - - if (ctx == nullptr) { - JNI_TRACE("ctx=%p get_EVP_CIPHER_CTX_final_used => ctx == null", ctx); + uint8_t* outBuf = reinterpret_cast(outBytes.get()); + const uint8_t* inBuf = reinterpret_cast(inBytes.get()); + size_t outLength = outBytes.size() - outOffset; + if (!encrypt_decrypt_func(pkeyCtx, outBuf + outOffset, &outLength, inBuf + inOffset, + static_cast(inLength))) { + JNI_TRACE("ctx=%p %s => threw exception", pkeyCtx, jniName); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, jniName, conscrypt::jniutil::throwBadPaddingException); return 0; } - bool final_used = ctx->final_used != 0; - JNI_TRACE("get_EVP_CIPHER_CTX_final_used(%p) => %d", ctx, final_used); - return static_cast(final_used); + JNI_TRACE("%s(%p, %p, %d, %p, %d, %d) => success (%zd bytes)", jniName, pkeyCtx, outJavaBytes, + outOffset, inJavaBytes, inOffset, inLength, outLength); + return static_cast(outLength); } -static void NativeCrypto_EVP_CIPHER_CTX_set_padding(JNIEnv* env, jclass, jobject ctxRef, - jboolean enablePaddingBool) { +static jint NativeCrypto_EVP_PKEY_encrypt(JNIEnv* env, jclass, jobject evpPkeyCtxRef, + jbyteArray out, jint outOffset, jbyteArray inBytes, + jint inOffset, jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - jint enablePadding = enablePaddingBool ? 1 : 0; - JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d)", ctx, enablePadding); - - if (ctx == nullptr) { - JNI_TRACE("ctx=%p EVP_CIPHER_CTX_set_padding => ctx == null", ctx); - return; - } - - EVP_CIPHER_CTX_set_padding(ctx, - enablePadding); // Not void, but always returns 1. - JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d) => success", ctx, enablePadding); + return evpPkeyEncryptDecrypt(env, EVP_PKEY_encrypt, "EVP_PKEY_encrypt", evpPkeyCtxRef, out, + outOffset, inBytes, inOffset, inLength); } -static void NativeCrypto_EVP_CIPHER_CTX_set_key_length(JNIEnv* env, jclass, jobject ctxRef, - jint keySizeBits) { +static jint NativeCrypto_EVP_PKEY_decrypt(JNIEnv* env, jclass, jobject evpPkeyCtxRef, + jbyteArray out, jint outOffset, jbyteArray inBytes, + jint inOffset, jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); - JNI_TRACE("EVP_CIPHER_CTX_set_key_length(%p, %d)", ctx, keySizeBits); + return evpPkeyEncryptDecrypt(env, EVP_PKEY_decrypt, "EVP_PKEY_decrypt", evpPkeyCtxRef, out, + outOffset, inBytes, inOffset, inLength); +} - if (ctx == nullptr) { - JNI_TRACE("ctx=%p EVP_CIPHER_CTX_set_key_length => ctx == null", ctx); - return; +static jlong evpPkeyEcryptDecryptInit(JNIEnv* env, jobject evpPkeyRef, + int (*real_func)(EVP_PKEY_CTX*), const char* opType) { + EVP_PKEY* pkey = fromContextObject(env, evpPkeyRef); + JNI_TRACE("EVP_PKEY_%s_init(%p)", opType, pkey); + if (pkey == nullptr) { + JNI_TRACE("EVP_PKEY_%s_init(%p) => pkey == null", opType, pkey); + return 0; } - if (!EVP_CIPHER_CTX_set_key_length(ctx, static_cast(keySizeBits))) { + bssl::UniquePtr pkeyCtx(EVP_PKEY_CTX_new(pkey, nullptr)); + if (pkeyCtx.get() == nullptr) { + JNI_TRACE("EVP_PKEY_%s_init(%p) => threw exception", opType, pkey); conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "NativeCrypto_EVP_CIPHER_CTX_set_key_length"); - JNI_TRACE("NativeCrypto_EVP_CIPHER_CTX_set_key_length => threw error"); - return; + env, "EVP_PKEY_CTX_new", conscrypt::jniutil::throwInvalidKeyException); + return 0; } - JNI_TRACE("EVP_CIPHER_CTX_set_key_length(%p, %d) => success", ctx, keySizeBits); -} -static void NativeCrypto_EVP_CIPHER_CTX_free(JNIEnv* env, jclass, jlong ctxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_CIPHER_CTX* ctx = reinterpret_cast(ctxRef); - JNI_TRACE("EVP_CIPHER_CTX_free(%p)", ctx); + if (!real_func(pkeyCtx.get())) { + JNI_TRACE("EVP_PKEY_%s_init(%p) => threw exception", opType, pkey); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, opType, conscrypt::jniutil::throwInvalidKeyException); + return 0; + } - EVP_CIPHER_CTX_free(ctx); + JNI_TRACE("EVP_PKEY_%s_init(%p) => pkeyCtx=%p", opType, pkey, pkeyCtx.get()); + return reinterpret_cast(pkeyCtx.release()); } -static jlong NativeCrypto_EVP_aead_aes_128_gcm(JNIEnv* env, jclass) { +static jlong NativeCrypto_EVP_PKEY_encrypt_init(JNIEnv* env, jclass, jobject evpPkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_AEAD* ctx = EVP_aead_aes_128_gcm(); - JNI_TRACE("EVP_aead_aes_128_gcm => ctx=%p", ctx); - return reinterpret_cast(ctx); + return evpPkeyEcryptDecryptInit(env, evpPkeyRef, EVP_PKEY_encrypt_init, "encrypt"); } -static jlong NativeCrypto_EVP_aead_aes_256_gcm(JNIEnv* env, jclass) { +static jlong NativeCrypto_EVP_PKEY_decrypt_init(JNIEnv* env, jclass, jobject evpPkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_AEAD* ctx = EVP_aead_aes_256_gcm(); - JNI_TRACE("EVP_aead_aes_256_gcm => ctx=%p", ctx); - return reinterpret_cast(ctx); + return evpPkeyEcryptDecryptInit(env, evpPkeyRef, EVP_PKEY_decrypt_init, "decrypt"); } -static jlong NativeCrypto_EVP_aead_chacha20_poly1305(JNIEnv* env, jclass) { +static void NativeCrypto_EVP_PKEY_CTX_free(JNIEnv* env, jclass, jlong pkeyCtxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_AEAD* ctx = EVP_aead_chacha20_poly1305(); - JNI_TRACE("EVP_aead_chacha20_poly1305 => ctx=%p", ctx); - return reinterpret_cast(ctx); -} + EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(pkeyCtxRef); + JNI_TRACE("EVP_PKEY_CTX_free(%p)", pkeyCtx); -static jlong NativeCrypto_EVP_aead_aes_128_gcm_siv(JNIEnv* env, jclass) { - CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_AEAD* ctx = EVP_aead_aes_128_gcm_siv(); - JNI_TRACE("EVP_aead_aes_128_gcm_siv => ctx=%p", ctx); - return reinterpret_cast(ctx); + if (pkeyCtx != nullptr) { + EVP_PKEY_CTX_free(pkeyCtx); + } } -static jlong NativeCrypto_EVP_aead_aes_256_gcm_siv(JNIEnv* env, jclass) { +static void NativeCrypto_EVP_PKEY_CTX_set_rsa_padding(JNIEnv* env, jclass, jlong ctx, jint pad) { CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_AEAD* ctx = EVP_aead_aes_256_gcm_siv(); - JNI_TRACE("EVP_aead_aes_256_gcm_siv => ctx=%p", ctx); - return reinterpret_cast(ctx); -} + EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(ctx); + JNI_TRACE("EVP_PKEY_CTX_set_rsa_padding(%p, %d)", pkeyCtx, pad); + if (pkeyCtx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "ctx == null"); + return; + } -static jint NativeCrypto_EVP_AEAD_max_overhead(JNIEnv* env, jclass, jlong evpAeadRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); - JNI_TRACE("EVP_AEAD_max_overhead(%p)", evpAead); - if (evpAead == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "evpAead == null"); - return 0; + int result = EVP_PKEY_CTX_set_rsa_padding(pkeyCtx, static_cast(pad)); + if (result <= 0) { + JNI_TRACE("ctx=%p EVP_PKEY_CTX_set_rsa_padding => threw exception", pkeyCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_PKEY_CTX_set_rsa_padding", + conscrypt::jniutil::throwInvalidAlgorithmParameterException); + return; } - jint maxOverhead = static_cast(EVP_AEAD_max_overhead(evpAead)); - JNI_TRACE("EVP_AEAD_max_overhead(%p) => %d", evpAead, maxOverhead); - return maxOverhead; + + JNI_TRACE("EVP_PKEY_CTX_set_rsa_padding(%p, %d) => success", pkeyCtx, pad); } -static jint NativeCrypto_EVP_AEAD_nonce_length(JNIEnv* env, jclass, jlong evpAeadRef) { +static void NativeCrypto_EVP_PKEY_CTX_set_rsa_pss_saltlen(JNIEnv* env, jclass, jlong ctx, + jint len) { CHECK_ERROR_QUEUE_ON_RETURN; - const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); - JNI_TRACE("EVP_AEAD_nonce_length(%p)", evpAead); - if (evpAead == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "evpAead == null"); - return 0; + EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(ctx); + JNI_TRACE("EVP_PKEY_CTX_set_rsa_pss_saltlen(%p, %d)", pkeyCtx, len); + if (pkeyCtx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "ctx == null"); + return; } - jint nonceLength = static_cast(EVP_AEAD_nonce_length(evpAead)); - JNI_TRACE("EVP_AEAD_nonce_length(%p) => %d", evpAead, nonceLength); - return nonceLength; -} - -typedef int (*evp_aead_ctx_op_func)(const EVP_AEAD_CTX* ctx, uint8_t* out, size_t* out_len, - size_t max_out_len, const uint8_t* nonce, size_t nonce_len, - const uint8_t* in, size_t in_len, const uint8_t* ad, - size_t ad_len); -static jint evp_aead_ctx_op_common(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen, - uint8_t* outBuf, jbyteArray nonceArray, const uint8_t* inBuf, - jbyteArray aadArray, evp_aead_ctx_op_func realFunc, - jobject inBuffer, jobject outBuffer, jint outRange, - jint inRange) { - const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); - - ScopedByteArrayRO keyBytes(env, keyArray); - if (keyBytes.get() == nullptr) { - return 0; + int result = EVP_PKEY_CTX_set_rsa_pss_saltlen(pkeyCtx, static_cast(len)); + if (result <= 0) { + JNI_TRACE("ctx=%p EVP_PKEY_CTX_set_rsa_pss_saltlen => threw exception", pkeyCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_PKEY_CTX_set_rsa_pss_saltlen", + conscrypt::jniutil::throwInvalidAlgorithmParameterException); + return; } - std::unique_ptr aad; - const uint8_t* aad_chars = nullptr; - size_t aad_chars_size = 0; - if (aadArray != nullptr) { - aad.reset(new ScopedByteArrayRO(env, aadArray)); - aad_chars = reinterpret_cast(aad->get()); - if (aad_chars == nullptr) { - return 0; - } - aad_chars_size = aad->size(); - } + JNI_TRACE("EVP_PKEY_CTX_set_rsa_pss_saltlen(%p, %d) => success", pkeyCtx, len); +} - ScopedByteArrayRO nonceBytes(env, nonceArray); - if (nonceBytes.get() == nullptr) { - return 0; +static void evpPkeyCtxCtrlMdOp(JNIEnv* env, jlong pkeyCtxRef, jlong mdRef, const char* jniName, + int (*ctrl_func)(EVP_PKEY_CTX*, const EVP_MD*)) { + EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(pkeyCtxRef); + EVP_MD* md = reinterpret_cast(mdRef); + JNI_TRACE("%s(%p, %p)", jniName, pkeyCtx, md); + if (pkeyCtx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "pkeyCtx == null"); + return; } - - bssl::ScopedEVP_AEAD_CTX aeadCtx; - const uint8_t* keyTmp = reinterpret_cast(keyBytes.get()); - if (!EVP_AEAD_CTX_init(aeadCtx.get(), evpAead, keyTmp, keyBytes.size(), - static_cast(tagLen), nullptr)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, - "failure initializing AEAD context"); - JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p) => fail EVP_AEAD_CTX_init", evpAead, - keyArray, tagLen, outBuffer, nonceArray, inBuffer, aadArray); - return 0; + if (md == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "md == null"); + return; } - const uint8_t* nonceTmp = reinterpret_cast(nonceBytes.get()); - size_t actualOutLength; - - if (!realFunc(aeadCtx.get(), outBuf, &actualOutLength, outRange, nonceTmp, nonceBytes.size(), - inBuf, static_cast(inRange), aad_chars, aad_chars_size)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "evp_aead_ctx_op"); - return 0; + int result = ctrl_func(pkeyCtx, md); + if (result <= 0) { + JNI_TRACE("ctx=%p %s => threw exception", pkeyCtx, jniName); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, jniName, conscrypt::jniutil::throwInvalidAlgorithmParameterException); + return; } - JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p) => success outlength=%zd", evpAead, - keyArray, tagLen, outBuffer, nonceArray, inBuffer, aadArray, actualOutLength); - return static_cast(actualOutLength); + JNI_TRACE("%s(%p, %p) => success", jniName, pkeyCtx, md); } -static jint evp_aead_ctx_op(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen, - jbyteArray outArray, jint outOffset, jbyteArray nonceArray, - jbyteArray inArray, jint inOffset, jint inLength, jbyteArray aadArray, - evp_aead_ctx_op_func realFunc) { - const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); - JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p)", evpAead, keyArray, tagLen, - outArray, outOffset, nonceArray, inArray, inOffset, inLength, aadArray); +static void NativeCrypto_EVP_PKEY_CTX_set_rsa_mgf1_md(JNIEnv* env, jclass, jlong pkeyCtxRef, + jlong mdRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + evpPkeyCtxCtrlMdOp(env, pkeyCtxRef, mdRef, "EVP_PKEY_CTX_set_rsa_mgf1_md", + EVP_PKEY_CTX_set_rsa_mgf1_md); +} - ScopedByteArrayRW outBytes(env, outArray); - if (outBytes.get() == nullptr) { - return 0; - } +static void NativeCrypto_EVP_PKEY_CTX_set_rsa_oaep_md(JNIEnv* env, jclass, jlong pkeyCtxRef, + jlong mdRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + evpPkeyCtxCtrlMdOp(env, pkeyCtxRef, mdRef, "EVP_PKEY_CTX_set_rsa_oaep_md", + EVP_PKEY_CTX_set_rsa_oaep_md); +} - if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { - JNI_TRACE( - "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => out offset " - "invalid", - evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, - inLength, aadArray); - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "out"); - return 0; +static void NativeCrypto_EVP_PKEY_CTX_set_rsa_oaep_label(JNIEnv* env, jclass, jlong pkeyCtxRef, + jbyteArray labelJava) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(pkeyCtxRef); + JNI_TRACE("EVP_PKEY_CTX_set_rsa_oaep_label(%p, %p)", pkeyCtx, labelJava); + if (pkeyCtx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "pkeyCtx == null"); + return; } - ScopedByteArrayRO inBytes(env, inArray); - if (inBytes.get() == nullptr) { - return 0; + ScopedByteArrayRO labelBytes(env, labelJava); + if (labelBytes.get() == nullptr) { + return; } - if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { - JNI_TRACE( - "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => in " - "offset/length " - "invalid", - evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, - inLength, aadArray); - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "in"); - return 0; - } + bssl::UniquePtr label(reinterpret_cast(OPENSSL_malloc(labelBytes.size()))); + memcpy(label.get(), labelBytes.get(), labelBytes.size()); - uint8_t* outTmp = reinterpret_cast(outBytes.get()); - const uint8_t* inTmp = reinterpret_cast(inBytes.get()); + int result = EVP_PKEY_CTX_set0_rsa_oaep_label(pkeyCtx, label.get(), labelBytes.size()); + if (result <= 0) { + JNI_TRACE("ctx=%p EVP_PKEY_CTX_set_rsa_oaep_label => threw exception", pkeyCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_PKEY_CTX_set_rsa_oaep_label", + conscrypt::jniutil::throwInvalidAlgorithmParameterException); + return; + } + OWNERSHIP_TRANSFERRED(label); - return evp_aead_ctx_op_common(env, evpAeadRef, keyArray, tagLen, outTmp + outOffset, nonceArray, - inTmp + inOffset, aadArray, realFunc, inArray, outArray, - outBytes.size() - outOffset, inLength); + JNI_TRACE("EVP_PKEY_CTX_set_rsa_oaep_label(%p, %p) => success", pkeyCtx, labelJava); } -static jint evp_aead_ctx_op_buf(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen, - jobject outBuffer, jbyteArray nonceArray, jobject inBuffer, - jbyteArray aadArray, evp_aead_ctx_op_func realFunc) { - const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); - JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p)", evpAead, keyArray, tagLen, outBuffer, - nonceArray, inBuffer, aadArray); - - if (!conscrypt::jniutil::isDirectByteBufferInstance(env, inBuffer)) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "inBuffer is not a direct ByteBuffer"); - return 0; +static void NativeCrypto_EVP_PKEY_CTX_set1_signature_context_string(JNIEnv* env, jclass, + jlong pkeyCtxRef, + jbyteArray contextJava) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_PKEY_CTX* pkeyCtx = reinterpret_cast(pkeyCtxRef); + JNI_TRACE("EVP_PKEY_CTX_set1_signature_context_string(%p, %p)", pkeyCtx, contextJava); + if (pkeyCtx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "pkeyCtx == null"); + return; } - if (!conscrypt::jniutil::isDirectByteBufferInstance(env, outBuffer)) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "outBuffer is not a direct ByteBuffer"); - return 0; + ScopedByteArrayRO contextBytes(env, contextJava); + if (contextBytes.get() == nullptr) { + return; } - uint8_t* inBuf; - jint in_limit; - jint in_position; - - inBuf = (uint8_t*)(env->GetDirectBufferAddress(inBuffer)); - // limit is the index of the first element that should not be read or written - in_limit = env->CallIntMethod(inBuffer, conscrypt::jniutil::buffer_limitMethod); - // position is the index of the next element to be read or written - in_position = env->CallIntMethod(inBuffer, conscrypt::jniutil::buffer_positionMethod); + if (EVP_PKEY_CTX_set1_signature_context_string( + pkeyCtx, reinterpret_cast(contextBytes.get()), + contextBytes.size()) <= 0) { + JNI_TRACE("ctx=%p EVP_PKEY_CTX_set1_signature_context_string => threw exception", pkeyCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_PKEY_CTX_set1_signature_context_string"); + return; + } - uint8_t* outBuf; - jint out_limit; - jint out_position; - outBuf = (uint8_t*)(env->GetDirectBufferAddress(outBuffer)); - // limit is the index of the first element that should not be read or written - out_limit = env->CallIntMethod(outBuffer, conscrypt::jniutil::buffer_limitMethod); - // position is the index of the next element to be read or written - out_position = env->CallIntMethod(outBuffer, conscrypt::jniutil::buffer_positionMethod); + JNI_TRACE("EVP_PKEY_CTX_set1_signature_context_string(%p, %p) => success", pkeyCtx, + contextJava); +} - // Shifting over of ByteBuffer address to start at true position - inBuf += in_position; - outBuf += out_position; +static jlong NativeCrypto_EVP_get_cipherbyname(JNIEnv* env, jclass, jstring algorithm) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EVP_get_cipherbyname(%p)", algorithm); - size_t inSize = in_limit - in_position; - uint8_t* outBufEnd = outBuf + out_limit - out_position; - uint8_t* inBufEnd = inBuf + inSize; - std::unique_ptr inCopy; - if (outBufEnd >= inBuf && inBufEnd >= outBuf) { // We have an overlap - inCopy.reset((new (std::nothrow) uint8_t[inSize])); - if (inCopy.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate new buffer for overlap"); - return 0; - } - memcpy(inCopy.get(), inBuf, inSize); - inBuf = inCopy.get(); + if (algorithm == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "algorithm == null"); + JNI_TRACE("EVP_get_cipherbyname(%p) => algorithm == null", algorithm); + return -1; } - return evp_aead_ctx_op_common(env, evpAeadRef, keyArray, tagLen, outBuf, nonceArray, inBuf, - aadArray, realFunc, inBuffer, outBuffer, out_limit - out_position, - in_limit - in_position); -} + ScopedUtfChars scoped_alg(env, algorithm); + const char* alg = scoped_alg.c_str(); + const EVP_CIPHER* cipher; -static jint NativeCrypto_EVP_AEAD_CTX_seal(JNIEnv* env, jclass, jlong evpAeadRef, - jbyteArray keyArray, jint tagLen, jbyteArray outArray, - jint outOffset, jbyteArray nonceArray, - jbyteArray inArray, jint inOffset, jint inLength, - jbyteArray aadArray) { - CHECK_ERROR_QUEUE_ON_RETURN; - return evp_aead_ctx_op(env, evpAeadRef, keyArray, tagLen, outArray, outOffset, nonceArray, - inArray, inOffset, inLength, aadArray, EVP_AEAD_CTX_seal); -} + if (strcasecmp(alg, "rc4") == 0) { + cipher = EVP_rc4(); + } else if (strcasecmp(alg, "des-cbc") == 0) { + cipher = EVP_des_cbc(); + } else if (strcasecmp(alg, "des-ede-cbc") == 0) { + cipher = EVP_des_ede_cbc(); + } else if (strcasecmp(alg, "des-ede3-cbc") == 0) { + cipher = EVP_des_ede3_cbc(); + } else if (strcasecmp(alg, "aes-128-ecb") == 0) { + cipher = EVP_aes_128_ecb(); + } else if (strcasecmp(alg, "aes-128-cbc") == 0) { + cipher = EVP_aes_128_cbc(); + } else if (strcasecmp(alg, "aes-128-ctr") == 0) { + cipher = EVP_aes_128_ctr(); + } else if (strcasecmp(alg, "aes-128-gcm") == 0) { + cipher = EVP_aes_128_gcm(); + } else if (strcasecmp(alg, "aes-192-ecb") == 0) { + cipher = EVP_aes_192_ecb(); + } else if (strcasecmp(alg, "aes-192-cbc") == 0) { + cipher = EVP_aes_192_cbc(); + } else if (strcasecmp(alg, "aes-192-ctr") == 0) { + cipher = EVP_aes_192_ctr(); + } else if (strcasecmp(alg, "aes-192-gcm") == 0) { + cipher = EVP_aes_192_gcm(); + } else if (strcasecmp(alg, "aes-256-ecb") == 0) { + cipher = EVP_aes_256_ecb(); + } else if (strcasecmp(alg, "aes-256-cbc") == 0) { + cipher = EVP_aes_256_cbc(); + } else if (strcasecmp(alg, "aes-256-ctr") == 0) { + cipher = EVP_aes_256_ctr(); + } else if (strcasecmp(alg, "aes-256-gcm") == 0) { + cipher = EVP_aes_256_gcm(); + } else { + JNI_TRACE("NativeCrypto_EVP_get_cipherbyname(%s) => error", alg); + return 0; + } -static jint NativeCrypto_EVP_AEAD_CTX_open(JNIEnv* env, jclass, jlong evpAeadRef, - jbyteArray keyArray, jint tagLen, jbyteArray outArray, - jint outOffset, jbyteArray nonceArray, - jbyteArray inArray, jint inOffset, jint inLength, - jbyteArray aadArray) { - CHECK_ERROR_QUEUE_ON_RETURN; - return evp_aead_ctx_op(env, evpAeadRef, keyArray, tagLen, outArray, outOffset, nonceArray, - inArray, inOffset, inLength, aadArray, EVP_AEAD_CTX_open); + return reinterpret_cast(cipher); } -static jint NativeCrypto_EVP_AEAD_CTX_seal_buf(JNIEnv* env, jclass, jlong evpAeadRef, - jbyteArray keyArray, jint tagLen, jobject outBuffer, - jbyteArray nonceArray, jobject inBuffer, - jbyteArray aadArray) { +static void NativeCrypto_EVP_CipherInit_ex(JNIEnv* env, jclass, jobject ctxRef, jlong evpCipherRef, + jbyteArray keyArray, jbyteArray ivArray, + jboolean encrypting) { CHECK_ERROR_QUEUE_ON_RETURN; - return evp_aead_ctx_op_buf(env, evpAeadRef, keyArray, tagLen, outBuffer, nonceArray, inBuffer, - aadArray, EVP_AEAD_CTX_seal); -} + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + const EVP_CIPHER* evpCipher = reinterpret_cast(evpCipherRef); + JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %p, %d)", ctx, evpCipher, keyArray, ivArray, + encrypting ? 1 : 0); -static jint NativeCrypto_EVP_AEAD_CTX_open_buf(JNIEnv* env, jclass, jlong evpAeadRef, - jbyteArray keyArray, jint tagLen, jobject outBuffer, - jbyteArray nonceArray, jobject inBuffer, - jbyteArray aadArray) { - CHECK_ERROR_QUEUE_ON_RETURN; - return evp_aead_ctx_op_buf(env, evpAeadRef, keyArray, tagLen, outBuffer, nonceArray, inBuffer, - aadArray, EVP_AEAD_CTX_open); -} + if (ctx == nullptr) { + JNI_TRACE("EVP_CipherInit_ex => ctx == null"); + return; + } -static jbyteArray NativeCrypto_EVP_HPKE_CTX_export(JNIEnv* env, jclass, jobject hpkeCtxRef, - jbyteArray exporterCtxArray, jint exportedLen) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_HPKE_CTX* hpkeCtx = fromContextObject(env, hpkeCtxRef); - JNI_TRACE("EVP_HPKE_CTX_export(%p, %p, %d)", hpkeCtx, exporterCtxArray, exportedLen); + // The key can be null if we need to set extra parameters. + std::unique_ptr keyPtr; + if (keyArray != nullptr) { + ScopedByteArrayRO keyBytes(env, keyArray); + if (keyBytes.get() == nullptr) { + return; + } - if (hpkeCtx == nullptr) { - // NullPointerException thrown while calling fromContextObject - return {}; + keyPtr.reset(new unsigned char[keyBytes.size()]); + memcpy(keyPtr.get(), keyBytes.get(), keyBytes.size()); } - std::optional optionalExporterCtx; - const uint8_t* exporterCtx = nullptr; - size_t exporterCtxLen = 0; - if (exporterCtxArray != nullptr) { - optionalExporterCtx.emplace(env, exporterCtxArray); - exporterCtx = reinterpret_cast(optionalExporterCtx->get()); - if (exporterCtx == nullptr) { - return {}; + // The IV can be null if we're using ECB. + std::unique_ptr ivPtr; + if (ivArray != nullptr) { + ScopedByteArrayRO ivBytes(env, ivArray); + if (ivBytes.get() == nullptr) { + return; } - exporterCtxLen = optionalExporterCtx->size(); + + ivPtr.reset(new unsigned char[ivBytes.size()]); + memcpy(ivPtr.get(), ivBytes.get(), ivBytes.size()); } - std::vector exported(exportedLen); - if (!EVP_HPKE_CTX_export(/* ctx= */ hpkeCtx, - /* out= */ exported.data(), - /* secret_len= */ exportedLen, - /* context= */ exporterCtx, - /* context_len= */ exporterCtxLen)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_export"); - return {}; + if (!EVP_CipherInit_ex(ctx, evpCipher, nullptr, keyPtr.get(), ivPtr.get(), + encrypting ? 1 : 0)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_CipherInit_ex"); + JNI_TRACE("EVP_CipherInit_ex => error initializing cipher"); + return; } - ScopedLocalRef exportedArray(env, - env->NewByteArray(static_cast(exportedLen))); - if (exportedArray.get() == nullptr) { - return {}; - } - ScopedByteArrayRW exportedBytes(env, exportedArray.get()); - if (exportedBytes.get() == nullptr) { - return {}; - } - memcpy(exportedBytes.get(), reinterpret_cast(exported.data()), exportedLen); - return exportedArray.release(); -} - -static void NativeCrypto_EVP_HPKE_CTX_free(JNIEnv* env, jclass, jlong hpkeCtxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - EVP_HPKE_CTX* ctx = reinterpret_cast(hpkeCtxRef); - JNI_TRACE("EVP_HPKE_CTX_free(%p)", ctx); - if (ctx == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "ctx == null"); - return; - } - EVP_HPKE_CTX_free(ctx); + JNI_TRACE("EVP_CipherInit_ex(%p, %p, %p, %p, %d) => success", ctx, evpCipher, keyArray, ivArray, + encrypting ? 1 : 0); } -static jbyteArray NativeCrypto_EVP_HPKE_CTX_open(JNIEnv* env, jclass, jobject recipientHpkeCtxRef, - jbyteArray ciphertextArray, jbyteArray aadArray) { +/* + * public static native int EVP_CipherUpdate(long ctx, byte[] out, int + * outOffset, byte[] in, int inOffset, int inLength); + */ +static jint NativeCrypto_EVP_CipherUpdate(JNIEnv* env, jclass, jobject ctxRef, jbyteArray outArray, + jint outOffset, jbyteArray inArray, jint inOffset, + jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_HPKE_CTX* ctx = fromContextObject(env, recipientHpkeCtxRef); - JNI_TRACE("EVP_HPKE_CTX_open(%p, %p, %p)", ctx, ciphertextArray, aadArray); + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE("EVP_CipherUpdate(%p, %p, %d, %p, %d)", ctx, outArray, outOffset, inArray, inOffset); if (ctx == nullptr) { - // NullPointerException thrown while calling fromContextObject - return {}; + JNI_TRACE("ctx=%p EVP_CipherUpdate => ctx == null", ctx); + return 0; } - if (ciphertextArray == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "ciphertextArray == null"); - return {}; + ScopedByteArrayRO inBytes(env, inArray); + if (inBytes.get() == nullptr) { + return 0; } - - ScopedByteArrayRO ciphertext(env, ciphertextArray); - if (ciphertext.get() == nullptr) { - return {}; + if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "inBytes"); + return 0; } - std::optional optionalAad; - const uint8_t* aad = nullptr; - size_t aadLen = 0; - if (aadArray != nullptr) { - optionalAad.emplace(env, aadArray); - aad = reinterpret_cast(optionalAad->get()); - if (aad == nullptr) { - return {}; - } - aadLen = optionalAad->size(); + ScopedByteArrayRW outBytes(env, outArray); + if (outBytes.get() == nullptr) { + return 0; } - - size_t plaintextLen; - std::vector plaintext(ciphertext.size()); - if (!EVP_HPKE_CTX_open( - /* ctx= */ ctx, - /* out= */ plaintext.data(), - /* out_len= */ &plaintextLen, - /* max_out_len= */ plaintext.size(), - /* in= */ reinterpret_cast(ciphertext.get()), - /* in_len= */ ciphertext.size(), - /* aad= */ aad, - /* aad_len= */ aadLen)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_open"); - return {}; + if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "outBytes"); + return 0; } - plaintext.resize(plaintextLen); - ScopedLocalRef plaintextArray(env, - env->NewByteArray(static_cast(plaintextLen))); - if (plaintextArray.get() == nullptr) { - return {}; - } - ScopedByteArrayRW plaintextBytes(env, plaintextArray.get()); - if (plaintextBytes.get() == nullptr) { - return {}; + JNI_TRACE( + "ctx=%p EVP_CipherUpdate in=%p in.length=%zd inOffset=%d inLength=%d " + "out=%p " + "out.length=%zd outOffset=%d", + ctx, inBytes.get(), inBytes.size(), inOffset, inLength, outBytes.get(), outBytes.size(), + outOffset); + + unsigned char* out = reinterpret_cast(outBytes.get()); + const unsigned char* in = reinterpret_cast(inBytes.get()); + + size_t outl; + if (!EVP_CipherUpdate_ex(ctx, out + outOffset, &outl, outBytes.size() - outOffset, + in + inOffset, inLength)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_CipherUpdate_ex"); + JNI_TRACE("ctx=%p EVP_CipherUpdate => threw error", ctx); + return 0; } - memcpy(plaintextBytes.get(), reinterpret_cast(plaintext.data()), plaintextLen); - return plaintextArray.release(); + + JNI_TRACE("EVP_CipherUpdate(%p, %p, %d, %p, %d) => %zu", ctx, outArray, outOffset, inArray, + inOffset, outl); + // outl will fit in jint because EVP_CipherUpdate_ex will not output outBytes.size() bytes. + return static_cast(outl); } -static jbyteArray NativeCrypto_EVP_HPKE_CTX_seal(JNIEnv* env, jclass, jobject senderHpkeCtxRef, - jbyteArray plaintextArray, jbyteArray aadArray) { +static jint NativeCrypto_EVP_CipherFinal_ex(JNIEnv* env, jclass, jobject ctxRef, + jbyteArray outArray, jint outOffset) { CHECK_ERROR_QUEUE_ON_RETURN; - EVP_HPKE_CTX* ctx = fromContextObject(env, senderHpkeCtxRef); - JNI_TRACE("EVP_HPKE_CTX_seal(%p, %p, %p)", ctx, plaintextArray, aadArray); + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE("EVP_CipherFinal_ex(%p, %p, %d)", ctx, outArray, outOffset); if (ctx == nullptr) { - // NullPointerException thrown while calling fromContextObject - return {}; + JNI_TRACE("ctx=%p EVP_CipherFinal_ex => ctx == null", ctx); + return 0; } - if (plaintextArray == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "plaintextArray == null"); - return {}; + ScopedByteArrayRW outBytes(env, outArray); + if (outBytes.get() == nullptr) { + return 0; } - - std::optional optionalAad; - const uint8_t* aad = nullptr; - size_t aadLen = 0; - if (aadArray != nullptr) { - optionalAad.emplace(env, aadArray); - aad = reinterpret_cast(optionalAad->get()); - if (aad == nullptr) { - return {}; - } - aadLen = optionalAad->size(); + if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "outBytes"); + return 0; } - ScopedByteArrayRO plaintext(env, plaintextArray); - std::vector encrypted(env->GetArrayLength(plaintextArray) + - EVP_HPKE_CTX_max_overhead(ctx)); - size_t encryptedLen; - if (!EVP_HPKE_CTX_seal( - /* ctx= */ ctx, - /* out= */ encrypted.data(), - /* out_len= */ &encryptedLen, - /* max_out_len= */ encrypted.size(), - /* in= */ reinterpret_cast(plaintext.get()), - /* in_len= */ plaintext.size(), - /* aad= */ aad, - /* aad_len= */ aadLen)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_seal"); - return {}; - } + unsigned char* out = reinterpret_cast(outBytes.get()); - ScopedLocalRef ciphertextArray(env, - env->NewByteArray(static_cast(encryptedLen))); - if (ciphertextArray.get() == nullptr) { - return {}; - } - ScopedByteArrayRW ciphertextBytes(env, ciphertextArray.get()); - if (ciphertextBytes.get() == nullptr) { - return {}; + size_t outl; + if (!EVP_CipherFinal_ex2(ctx, out + outOffset, &outl, outBytes.size() - outOffset)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_CipherFinal_ex2", conscrypt::jniutil::throwBadPaddingException); + JNI_TRACE("ctx=%p EVP_CipherFinal_ex => threw error", ctx); + return 0; } - memcpy(ciphertextBytes.get(), reinterpret_cast(encrypted.data()), encryptedLen); - return ciphertextArray.release(); -} -const EVP_HPKE_AEAD* getHpkeAead(JNIEnv* env, jint aeadValue) { - switch (aeadValue) { - case EVP_HPKE_AES_128_GCM: - return EVP_hpke_aes_128_gcm(); - case EVP_HPKE_AES_256_GCM: - return EVP_hpke_aes_256_gcm(); - case EVP_HPKE_CHACHA20_POLY1305: - return EVP_hpke_chacha20_poly1305(); - default: - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "AEAD is not supported"); - return nullptr; - } + JNI_TRACE("EVP_CipherFinal(%p, %p, %d) => %zu", ctx, outArray, outOffset, outl); + // outl will fit in jint because EVP_CipherUpdate_ex will not output outBytes.size() bytes. + return static_cast(outl); } -const EVP_HPKE_KDF* getHpkeKdf(JNIEnv* env, jint kdfValue) { - if (kdfValue == EVP_HPKE_HKDF_SHA256) { - return EVP_hpke_hkdf_sha256(); - } else { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "KDF is not supported"); - return nullptr; +static jint NativeCrypto_EVP_CIPHER_iv_length(JNIEnv* env, jclass, jlong evpCipherRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_CIPHER* evpCipher = reinterpret_cast(evpCipherRef); + JNI_TRACE("EVP_CIPHER_iv_length(%p)", evpCipher); + + if (evpCipher == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "evpCipher == null"); + JNI_TRACE("EVP_CIPHER_iv_length => evpCipher == null"); + return 0; } + + jint ivLength = static_cast(EVP_CIPHER_iv_length(evpCipher)); + JNI_TRACE("EVP_CIPHER_iv_length(%p) => %d", evpCipher, ivLength); + return ivLength; } -const EVP_HPKE_KEM* getHpkeKem(JNIEnv* env, jint kemValue) { - if (kemValue == EVP_HPKE_DHKEM_X25519_HKDF_SHA256) { - return EVP_hpke_x25519_hkdf_sha256(); - } else if (kemValue == EVP_HPKE_MLKEM768) { - return EVP_hpke_mlkem768(); - } else if (kemValue == EVP_HPKE_MLKEM1024) { - return EVP_hpke_mlkem1024(); - } else if (kemValue == EVP_HPKE_XWING) { - return EVP_hpke_xwing(); - } else { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "KEM is not supported"); - return nullptr; +static jlong NativeCrypto_EVP_CIPHER_CTX_new(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("EVP_CIPHER_CTX_new()"); + + bssl::UniquePtr ctx(EVP_CIPHER_CTX_new()); + if (ctx.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate cipher context"); + JNI_TRACE("EVP_CipherInit_ex => context allocation error"); + return 0; } + + JNI_TRACE("EVP_CIPHER_CTX_new() => %p", ctx.get()); + return reinterpret_cast(ctx.release()); } -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) { +static jint NativeCrypto_EVP_CIPHER_CTX_block_size(JNIEnv* env, jclass, jobject ctxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_HPKE_CTX_setup_recipient(%d, %d, %d, %p, %p, %p)", kemValue, kdfValue, aeadValue, - privateKeyArray, encArray, infoArray); + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE("EVP_CIPHER_CTX_block_size(%p)", ctx); - const EVP_HPKE_KEM* kem = getHpkeKem(env, kemValue); - if (kem == nullptr) { - return nullptr; - } - const EVP_HPKE_KDF* kdf = getHpkeKdf(env, kdfValue); - if (kdf == nullptr) { - return nullptr; - } - const EVP_HPKE_AEAD* aead = getHpkeAead(env, aeadValue); - if (aead == nullptr) { - return nullptr; - } - if (privateKeyArray == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "privateKeyArray == null"); - return nullptr; - } - if (encArray == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "encArray == null"); - return nullptr; + if (ctx == nullptr) { + JNI_TRACE("ctx=%p EVP_CIPHER_CTX_block_size => ctx == null", ctx); + return 0; } - ScopedByteArrayRO privateKey(env, privateKeyArray); + jint blockSize = static_cast(EVP_CIPHER_CTX_block_size(ctx)); + JNI_TRACE("EVP_CIPHER_CTX_block_size(%p) => %d", ctx, blockSize); + return blockSize; +} - bssl::ScopedEVP_HPKE_KEY key; +static jint NativeCrypto_get_EVP_CIPHER_CTX_buf_len(JNIEnv* env, jclass, jobject ctxRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE("get_EVP_CIPHER_CTX_buf_len(%p)", ctx); - if (!EVP_HPKE_KEY_init( - /* key= */ key.get(), - /* kem= */ kem, - /* priv_key= */ reinterpret_cast(privateKey.get()), - /* priv_key_len= */ privateKey.size())) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); - return nullptr; + if (ctx == nullptr) { + JNI_TRACE("ctx=%p get_EVP_CIPHER_CTX_buf_len => ctx == null", ctx); + return 0; } - std::optional optionalInfo; - const uint8_t* info = nullptr; - size_t infoLen = 0; - if (infoArray != nullptr) { - optionalInfo.emplace(env, infoArray); - info = reinterpret_cast(optionalInfo->get()); - if (info == nullptr) { - return {}; - } - infoLen = optionalInfo->size(); - } + int buf_len = ctx->buf_len; + JNI_TRACE("get_EVP_CIPHER_CTX_buf_len(%p) => %d", ctx, buf_len); + return buf_len; +} - bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); - ScopedByteArrayRO enc(env, encArray); - if (!EVP_HPKE_CTX_setup_recipient( - /* ctx= */ ctx.get(), - /* key= */ key.get(), - /* kdf= */ kdf, - /* aead= */ aead, - /* enc= */ reinterpret_cast(enc.get()), - /* enc_len= */ enc.size(), - /* info= */ info, - /* info_len= */ infoLen)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); - return nullptr; +static jboolean NativeCrypto_get_EVP_CIPHER_CTX_final_used(JNIEnv* env, jclass, jobject ctxRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE("get_EVP_CIPHER_CTX_final_used(%p)", ctx); + + if (ctx == nullptr) { + JNI_TRACE("ctx=%p get_EVP_CIPHER_CTX_final_used => ctx == null", ctx); + return 0; } - ScopedLocalRef ctxObject( - env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, - conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, - reinterpret_cast(ctx.release()))); - return ctxObject.release(); + bool final_used = ctx->final_used != 0; + JNI_TRACE("get_EVP_CIPHER_CTX_final_used(%p) => %d", ctx, final_used); + return static_cast(final_used); } -static jobjectArray NativeCrypto_EVP_HPKE_CTX_setup_base_mode_sender(JNIEnv* env, jclass, - jint kemValue, jint kdfValue, - jint aeadValue, - jbyteArray publicKeyArray, - jbyteArray infoArray) { +static void NativeCrypto_EVP_CIPHER_CTX_set_padding(JNIEnv* env, jclass, jobject ctxRef, + jboolean enablePaddingBool) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_HPKE_CTX_setup_sender(%d, %d, %d, %p, %p)", kemValue, kdfValue, aeadValue, - publicKeyArray, infoArray); - - const EVP_HPKE_KEM* kem = getHpkeKem(env, kemValue); - if (kem == nullptr) { - return nullptr; - } - const EVP_HPKE_KDF* kdf = getHpkeKdf(env, kdfValue); - if (kdf == nullptr) { - return nullptr; - } - const EVP_HPKE_AEAD* aead = getHpkeAead(env, aeadValue); - if (aead == nullptr) { - return nullptr; - } - if (publicKeyArray == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "publicKeyArray == null"); - return {}; - } + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + jint enablePadding = enablePaddingBool ? 1 : 0; + JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d)", ctx, enablePadding); - std::optional optionalInfo; - const uint8_t* info = nullptr; - size_t infoLen = 0; - if (infoArray != nullptr) { - optionalInfo.emplace(env, infoArray); - info = reinterpret_cast(optionalInfo->get()); - if (info == nullptr) { - return {}; - } - infoLen = optionalInfo->size(); + if (ctx == nullptr) { + JNI_TRACE("ctx=%p EVP_CIPHER_CTX_set_padding => ctx == null", ctx); + return; } - ScopedByteArrayRO peer_public_key(env, publicKeyArray); - - size_t encapsulatedSharedSecretLen; - uint8_t encapsulatedSharedSecret[EVP_HPKE_MAX_ENC_LENGTH]; + EVP_CIPHER_CTX_set_padding(ctx, + enablePadding); // Not void, but always returns 1. + JNI_TRACE("EVP_CIPHER_CTX_set_padding(%p, %d) => success", ctx, enablePadding); +} - bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); +static void NativeCrypto_EVP_CIPHER_CTX_set_key_length(JNIEnv* env, jclass, jobject ctxRef, + jint keySizeBits) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_CIPHER_CTX* ctx = fromContextObject(env, ctxRef); + JNI_TRACE("EVP_CIPHER_CTX_set_key_length(%p, %d)", ctx, keySizeBits); - if (!EVP_HPKE_CTX_setup_sender( - /* ctx= */ ctx.get(), - /* out_enc= */ encapsulatedSharedSecret, - /* out_enc_len= */ &encapsulatedSharedSecretLen, - /* max_enc= */ EVP_HPKE_MAX_ENC_LENGTH, - /* kem= */ kem, - /* kdf= */ kdf, - /* aead= */ aead, - /* peer_public_key= */ - reinterpret_cast(peer_public_key.get()), - /* peer_public_key_len= */ peer_public_key.size(), - /* info= */ info, - /* info_len= */ infoLen)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_sender"); - return {}; + if (ctx == nullptr) { + JNI_TRACE("ctx=%p EVP_CIPHER_CTX_set_key_length => ctx == null", ctx); + return; } - ScopedLocalRef encArray( - env, env->NewByteArray(static_cast(encapsulatedSharedSecretLen))); - if (encArray.get() == nullptr) { - return {}; - } - ScopedByteArrayRW encBytes(env, encArray.get()); - if (encBytes.get() == nullptr) { - return {}; + if (!EVP_CIPHER_CTX_set_key_length(ctx, static_cast(keySizeBits))) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "NativeCrypto_EVP_CIPHER_CTX_set_key_length"); + JNI_TRACE("NativeCrypto_EVP_CIPHER_CTX_set_key_length => threw error"); + return; } - memcpy(encBytes.get(), reinterpret_cast(encapsulatedSharedSecret), - encapsulatedSharedSecretLen); - - ScopedLocalRef result( - env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); - - ScopedLocalRef ctxObject( - env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, - conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, - reinterpret_cast(ctx.release()))); + JNI_TRACE("EVP_CIPHER_CTX_set_key_length(%p, %d) => success", ctx, keySizeBits); +} - env->SetObjectArrayElement(result.get(), 0, ctxObject.release()); - env->SetObjectArrayElement(result.get(), 1, encArray.release()); +static void NativeCrypto_EVP_CIPHER_CTX_free(JNIEnv* env, jclass, jlong ctxRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + EVP_CIPHER_CTX* ctx = reinterpret_cast(ctxRef); + JNI_TRACE("EVP_CIPHER_CTX_free(%p)", ctx); - return result.release(); + EVP_CIPHER_CTX_free(ctx); } -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) { +static jlong NativeCrypto_EVP_aead_aes_128_gcm(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("EVP_HPKE_CTX_setup_sender_with_seed_for_testing(%d, %d, %d, %p, %p, %p)", kemValue, - kdfValue, aeadValue, publicKeyArray, infoArray, seedArray); - - const EVP_HPKE_KEM* kem = getHpkeKem(env, kemValue); - if (kem == nullptr) { - return nullptr; - } - const EVP_HPKE_KDF* kdf = getHpkeKdf(env, kdfValue); - if (kdf == nullptr) { - return nullptr; - } - const EVP_HPKE_AEAD* aead = getHpkeAead(env, aeadValue); - if (aead == nullptr) { - return nullptr; - } - if (publicKeyArray == nullptr || seedArray == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "publicKeyArray or seedArray == null"); - return {}; - } + const EVP_AEAD* ctx = EVP_aead_aes_128_gcm(); + JNI_TRACE("EVP_aead_aes_128_gcm => ctx=%p", ctx); + return reinterpret_cast(ctx); +} - std::optional optionalInfo; - const uint8_t* info = nullptr; - size_t infoLen = 0; - if (infoArray != nullptr) { - optionalInfo.emplace(env, infoArray); - info = reinterpret_cast(optionalInfo->get()); - if (info == nullptr) { - return {}; - } - infoLen = optionalInfo->size(); - } +static jlong NativeCrypto_EVP_aead_aes_256_gcm(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_AEAD* ctx = EVP_aead_aes_256_gcm(); + JNI_TRACE("EVP_aead_aes_256_gcm => ctx=%p", ctx); + return reinterpret_cast(ctx); +} - ScopedByteArrayRO peer_public_key(env, publicKeyArray); +static jlong NativeCrypto_EVP_aead_chacha20_poly1305(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_AEAD* ctx = EVP_aead_chacha20_poly1305(); + JNI_TRACE("EVP_aead_chacha20_poly1305 => ctx=%p", ctx); + return reinterpret_cast(ctx); +} - ScopedByteArrayRO seed(env, seedArray); +static jlong NativeCrypto_EVP_aead_aes_128_gcm_siv(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_AEAD* ctx = EVP_aead_aes_128_gcm_siv(); + JNI_TRACE("EVP_aead_aes_128_gcm_siv => ctx=%p", ctx); + return reinterpret_cast(ctx); +} - size_t encapsulatedSharedSecretLen; - uint8_t encapsulatedSharedSecret[EVP_HPKE_MAX_ENC_LENGTH]; +static jlong NativeCrypto_EVP_aead_aes_256_gcm_siv(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + const EVP_AEAD* ctx = EVP_aead_aes_256_gcm_siv(); + JNI_TRACE("EVP_aead_aes_256_gcm_siv => ctx=%p", ctx); + return reinterpret_cast(ctx); +} - bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); - - if (!EVP_HPKE_CTX_setup_sender_with_seed_for_testing( - /* ctx= */ ctx.get(), - /* out_enc= */ encapsulatedSharedSecret, - /* out_enc_len= */ &encapsulatedSharedSecretLen, - /* max_enc= */ EVP_HPKE_MAX_ENC_LENGTH, - /* kem= */ kem, - /* kdf= */ kdf, - /* aead= */ aead, - /* peer_public_key= */ - reinterpret_cast(peer_public_key.get()), - /* peer_public_key_len= */ peer_public_key.size(), - /* info= */ info, - /* info_len= */ infoLen, - /* seed= */ reinterpret_cast(seed.get()), - /* seed_len= */ seed.size())) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "EVP_HPKE_CTX_setup_sender_with_seed_for_testing"); - return {}; - } - - ScopedLocalRef encArray( - env, env->NewByteArray(static_cast(encapsulatedSharedSecretLen))); - if (encArray.get() == nullptr) { - return {}; - } - ScopedByteArrayRW encBytes(env, encArray.get()); - if (encBytes.get() == nullptr) { - return {}; - } - memcpy(encBytes.get(), reinterpret_cast(encapsulatedSharedSecret), - encapsulatedSharedSecretLen); - - ScopedLocalRef result( - env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); - - ScopedLocalRef ctxObject( - env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, - conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, - reinterpret_cast(ctx.release()))); - - env->SetObjectArrayElement(result.get(), 0, ctxObject.release()); - env->SetObjectArrayElement(result.get(), 1, encArray.release()); - - return result.release(); -} - -static jlong NativeCrypto_CMAC_CTX_new(JNIEnv* env, jclass) { +static jint NativeCrypto_EVP_AEAD_max_overhead(JNIEnv* env, jclass, jlong evpAeadRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("CMAC_CTX_new"); - auto cmacCtx = CMAC_CTX_new(); - if (cmacCtx == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate CMAC_CTX"); + const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); + JNI_TRACE("EVP_AEAD_max_overhead(%p)", evpAead); + if (evpAead == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "evpAead == null"); return 0; } - - return reinterpret_cast(cmacCtx); + jint maxOverhead = static_cast(EVP_AEAD_max_overhead(evpAead)); + JNI_TRACE("EVP_AEAD_max_overhead(%p) => %d", evpAead, maxOverhead); + return maxOverhead; } -static void NativeCrypto_CMAC_CTX_free(JNIEnv* env, jclass, jlong cmacCtxRef) { +static jint NativeCrypto_EVP_AEAD_nonce_length(JNIEnv* env, jclass, jlong evpAeadRef) { CHECK_ERROR_QUEUE_ON_RETURN; - CMAC_CTX* cmacCtx = reinterpret_cast(cmacCtxRef); - JNI_TRACE("CMAC_CTX_free(%p)", cmacCtx); - if (cmacCtx == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "cmacCtx == null"); - return; + const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); + JNI_TRACE("EVP_AEAD_nonce_length(%p)", evpAead); + if (evpAead == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "evpAead == null"); + return 0; } - CMAC_CTX_free(cmacCtx); + jint nonceLength = static_cast(EVP_AEAD_nonce_length(evpAead)); + JNI_TRACE("EVP_AEAD_nonce_length(%p) => %d", evpAead, nonceLength); + return nonceLength; } -static void NativeCrypto_CMAC_Init(JNIEnv* env, jclass, jobject cmacCtxRef, jbyteArray keyArray) { - CHECK_ERROR_QUEUE_ON_RETURN; - CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); - JNI_TRACE("CMAC_Init(%p, %p)", cmacCtx, keyArray); - if (cmacCtx == nullptr) { - return; - } +typedef int (*evp_aead_ctx_op_func)(const EVP_AEAD_CTX* ctx, uint8_t* out, size_t* out_len, + size_t max_out_len, const uint8_t* nonce, size_t nonce_len, + const uint8_t* in, size_t in_len, const uint8_t* ad, + size_t ad_len); + +static jint evp_aead_ctx_op_common(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen, + uint8_t* outBuf, jbyteArray nonceArray, const uint8_t* inBuf, + jbyteArray aadArray, evp_aead_ctx_op_func realFunc, + jobject inBuffer, jobject outBuffer, jint outRange, + jint inRange) { + const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); + ScopedByteArrayRO keyBytes(env, keyArray); if (keyBytes.get() == nullptr) { - return; + return 0; } - const uint8_t* keyPtr = reinterpret_cast(keyBytes.get()); - - const EVP_CIPHER* cipher; - switch (keyBytes.size()) { - case 16: - cipher = EVP_aes_128_cbc(); - break; - case 24: - cipher = EVP_aes_192_cbc(); - break; - case 32: - cipher = EVP_aes_256_cbc(); - break; - default: - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "CMAC_Init: Unsupported key length"); - return; + std::unique_ptr aad; + const uint8_t* aad_chars = nullptr; + size_t aad_chars_size = 0; + if (aadArray != nullptr) { + aad.reset(new ScopedByteArrayRO(env, aadArray)); + aad_chars = reinterpret_cast(aad->get()); + if (aad_chars == nullptr) { + return 0; + } + aad_chars_size = aad->size(); } - if (!CMAC_Init(cmacCtx, keyPtr, keyBytes.size(), cipher, nullptr)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Init"); - JNI_TRACE("CMAC_Init(%p, %p) => fail CMAC_Init_ex", cmacCtx, keyArray); - return; + ScopedByteArrayRO nonceBytes(env, nonceArray); + if (nonceBytes.get() == nullptr) { + return 0; } -} - -static void NativeCrypto_CMAC_UpdateDirect(JNIEnv* env, jclass, jobject cmacCtxRef, jlong inPtr, - int inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); - const uint8_t* p = reinterpret_cast(inPtr); - JNI_TRACE("CMAC_UpdateDirect(%p, %p, %d)", cmacCtx, p, inLength); - if (cmacCtx == nullptr) { - return; + bssl::ScopedEVP_AEAD_CTX aeadCtx; + const uint8_t* keyTmp = reinterpret_cast(keyBytes.get()); + if (!EVP_AEAD_CTX_init(aeadCtx.get(), evpAead, keyTmp, keyBytes.size(), + static_cast(tagLen), nullptr)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, + "failure initializing AEAD context"); + JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p) => fail EVP_AEAD_CTX_init", evpAead, + keyArray, tagLen, outBuffer, nonceArray, inBuffer, aadArray); + return 0; } - if (p == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, nullptr); - return; - } + const uint8_t* nonceTmp = reinterpret_cast(nonceBytes.get()); + size_t actualOutLength; - if (!CMAC_Update(cmacCtx, p, static_cast(inLength))) { - JNI_TRACE("CMAC_UpdateDirect(%p, %p, %d) => threw exception", cmacCtx, p, inLength); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_UpdateDirect"); - return; + if (!realFunc(aeadCtx.get(), outBuf, &actualOutLength, outRange, nonceTmp, nonceBytes.size(), + inBuf, static_cast(inRange), aad_chars, aad_chars_size)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "evp_aead_ctx_op"); + return 0; } + + JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p) => success outlength=%zd", evpAead, + keyArray, tagLen, outBuffer, nonceArray, inBuffer, aadArray, actualOutLength); + return static_cast(actualOutLength); } -static void NativeCrypto_CMAC_Update(JNIEnv* env, jclass, jobject cmacCtxRef, jbyteArray inArray, - jint inOffset, jint inLength) { - CHECK_ERROR_QUEUE_ON_RETURN; - CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); - JNI_TRACE("CMAC_Update(%p, %p, %d, %d)", cmacCtx, inArray, inOffset, inLength); +static jint evp_aead_ctx_op(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen, + jbyteArray outArray, jint outOffset, jbyteArray nonceArray, + jbyteArray inArray, jint inOffset, jint inLength, jbyteArray aadArray, + evp_aead_ctx_op_func realFunc) { + const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); + JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p)", evpAead, keyArray, tagLen, + outArray, outOffset, nonceArray, inArray, inOffset, inLength, aadArray); - if (cmacCtx == nullptr) { - return; + ScopedByteArrayRW outBytes(env, outArray); + if (outBytes.get() == nullptr) { + return 0; + } + + if (ARRAY_OFFSET_INVALID(outBytes, outOffset)) { + JNI_TRACE( + "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => out offset " + "invalid", + evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, + inLength, aadArray); + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "out"); + return 0; } ScopedByteArrayRO inBytes(env, inArray); if (inBytes.get() == nullptr) { - return; + return 0; } if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "inBytes"); - return; + JNI_TRACE( + "evp_aead_ctx_op(%p, %p, %d, %p, %d, %p, %p, %d, %d, %p) => in " + "offset/length " + "invalid", + evpAead, keyArray, tagLen, outArray, outOffset, nonceArray, inArray, inOffset, + inLength, aadArray); + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "in"); + return 0; } - const uint8_t* inPtr = reinterpret_cast(inBytes.get()); + uint8_t* outTmp = reinterpret_cast(outBytes.get()); + const uint8_t* inTmp = reinterpret_cast(inBytes.get()); - if (!CMAC_Update(cmacCtx, inPtr + inOffset, static_cast(inLength))) { - JNI_TRACE("CMAC_Update(%p, %p, %d, %d) => threw exception", cmacCtx, inArray, inOffset, - inLength); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Update"); - return; - } + return evp_aead_ctx_op_common(env, evpAeadRef, keyArray, tagLen, outTmp + outOffset, nonceArray, + inTmp + inOffset, aadArray, realFunc, inArray, outArray, + outBytes.size() - outOffset, inLength); } -static jbyteArray NativeCrypto_CMAC_Final(JNIEnv* env, jclass, jobject cmacCtxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); - JNI_TRACE("CMAC_Final(%p)", cmacCtx); +static jint evp_aead_ctx_op_buf(JNIEnv* env, jlong evpAeadRef, jbyteArray keyArray, jint tagLen, + jobject outBuffer, jbyteArray nonceArray, jobject inBuffer, + jbyteArray aadArray, evp_aead_ctx_op_func realFunc) { + const EVP_AEAD* evpAead = reinterpret_cast(evpAeadRef); + JNI_TRACE("evp_aead_ctx_op(%p, %p, %d, %p, %p, %p, %p)", evpAead, keyArray, tagLen, outBuffer, + nonceArray, inBuffer, aadArray); - if (cmacCtx == nullptr) { - return nullptr; + if (!conscrypt::jniutil::isDirectByteBufferInstance(env, inBuffer)) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "inBuffer is not a direct ByteBuffer"); + return 0; } - uint8_t result[EVP_MAX_MD_SIZE]; - size_t len; - if (!CMAC_Final(cmacCtx, result, &len)) { - JNI_TRACE("CMAC_Final(%p) => threw exception", cmacCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Final"); - return nullptr; + if (!conscrypt::jniutil::isDirectByteBufferInstance(env, outBuffer)) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "outBuffer is not a direct ByteBuffer"); + return 0; } - ScopedLocalRef resultArray(env, env->NewByteArray(static_cast(len))); - if (resultArray.get() == nullptr) { - return nullptr; - } - ScopedByteArrayRW resultBytes(env, resultArray.get()); - if (resultBytes.get() == nullptr) { - return nullptr; - } - memcpy(resultBytes.get(), result, len); - return resultArray.release(); -} + uint8_t* inBuf; + jint in_limit; + jint in_position; -static void NativeCrypto_CMAC_Reset(JNIEnv* env, jclass, jobject cmacCtxRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); - JNI_TRACE("CMAC_Reset(%p)", cmacCtx); + inBuf = (uint8_t*)(env->GetDirectBufferAddress(inBuffer)); + // limit is the index of the first element that should not be read or written + in_limit = env->CallIntMethod(inBuffer, conscrypt::jniutil::buffer_limitMethod); + // position is the index of the next element to be read or written + in_position = env->CallIntMethod(inBuffer, conscrypt::jniutil::buffer_positionMethod); - if (cmacCtx == nullptr) { - return; - } + uint8_t* outBuf; + jint out_limit; + jint out_position; + outBuf = (uint8_t*)(env->GetDirectBufferAddress(outBuffer)); + // limit is the index of the first element that should not be read or written + out_limit = env->CallIntMethod(outBuffer, conscrypt::jniutil::buffer_limitMethod); + // position is the index of the next element to be read or written + out_position = env->CallIntMethod(outBuffer, conscrypt::jniutil::buffer_positionMethod); - if (!CMAC_Reset(cmacCtx)) { - JNI_TRACE("CMAC_Reset(%p) => threw exception", cmacCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Reset"); - return; - } -} + // Shifting over of ByteBuffer address to start at true position + inBuf += in_position; + outBuf += out_position; -static jlong NativeCrypto_HMAC_CTX_new(JNIEnv* env, jclass) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("HMAC_CTX_new"); - auto hmacCtx = HMAC_CTX_new(); - if (hmacCtx == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate HMAC_CTX"); - return 0; + size_t inSize = in_limit - in_position; + uint8_t* outBufEnd = outBuf + out_limit - out_position; + uint8_t* inBufEnd = inBuf + inSize; + std::unique_ptr inCopy; + if (outBufEnd >= inBuf && inBufEnd >= outBuf) { // We have an overlap + inCopy.reset((new (std::nothrow) uint8_t[inSize])); + if (inCopy.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate new buffer for overlap"); + return 0; + } + memcpy(inCopy.get(), inBuf, inSize); + inBuf = inCopy.get(); } - return reinterpret_cast(hmacCtx); + return evp_aead_ctx_op_common(env, evpAeadRef, keyArray, tagLen, outBuf, nonceArray, inBuf, + aadArray, realFunc, inBuffer, outBuffer, out_limit - out_position, + in_limit - in_position); } -static void NativeCrypto_HMAC_CTX_free(JNIEnv* env, jclass, jlong hmacCtxRef) { +static jint NativeCrypto_EVP_AEAD_CTX_seal(JNIEnv* env, jclass, jlong evpAeadRef, + jbyteArray keyArray, jint tagLen, jbyteArray outArray, + jint outOffset, jbyteArray nonceArray, + jbyteArray inArray, jint inOffset, jint inLength, + jbyteArray aadArray) { CHECK_ERROR_QUEUE_ON_RETURN; - HMAC_CTX* hmacCtx = reinterpret_cast(hmacCtxRef); - JNI_TRACE("HMAC_CTX_free(%p)", hmacCtx); - if (hmacCtx == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "hmacCtx == null"); - return; - } - HMAC_CTX_free(hmacCtx); + return evp_aead_ctx_op(env, evpAeadRef, keyArray, tagLen, outArray, outOffset, nonceArray, + inArray, inOffset, inLength, aadArray, EVP_AEAD_CTX_seal); } -static void NativeCrypto_HMAC_Init_ex(JNIEnv* env, jclass, jobject hmacCtxRef, jbyteArray keyArray, - jobject evpMdRef) { +static jint NativeCrypto_EVP_AEAD_CTX_open(JNIEnv* env, jclass, jlong evpAeadRef, + jbyteArray keyArray, jint tagLen, jbyteArray outArray, + jint outOffset, jbyteArray nonceArray, + jbyteArray inArray, jint inOffset, jint inLength, + jbyteArray aadArray) { CHECK_ERROR_QUEUE_ON_RETURN; - HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); - const EVP_MD* md = reinterpret_cast(evpMdRef); - JNI_TRACE("HMAC_Init_ex(%p, %p, %p)", hmacCtx, keyArray, md); - if (hmacCtx == nullptr) { - return; - } - ScopedByteArrayRO keyBytes(env, keyArray); - if (keyBytes.get() == nullptr) { - return; - } - - const uint8_t* keyPtr = reinterpret_cast(keyBytes.get()); - if (!HMAC_Init_ex(hmacCtx, keyPtr, keyBytes.size(), md, nullptr)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Init_ex"); - JNI_TRACE("HMAC_Init_ex(%p, %p, %p) => fail HMAC_Init_ex", hmacCtx, keyArray, md); - return; - } + return evp_aead_ctx_op(env, evpAeadRef, keyArray, tagLen, outArray, outOffset, nonceArray, + inArray, inOffset, inLength, aadArray, EVP_AEAD_CTX_open); } -static void NativeCrypto_HMAC_UpdateDirect(JNIEnv* env, jclass, jobject hmacCtxRef, jlong inPtr, - int inLength) { +static jint NativeCrypto_EVP_AEAD_CTX_seal_buf(JNIEnv* env, jclass, jlong evpAeadRef, + jbyteArray keyArray, jint tagLen, jobject outBuffer, + jbyteArray nonceArray, jobject inBuffer, + jbyteArray aadArray) { CHECK_ERROR_QUEUE_ON_RETURN; - HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); - const uint8_t* p = reinterpret_cast(inPtr); - JNI_TRACE("HMAC_UpdateDirect(%p, %p, %d)", hmacCtx, p, inLength); - - if (hmacCtx == nullptr) { - return; - } - - if (p == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, nullptr); - return; - } - - if (!HMAC_Update(hmacCtx, p, static_cast(inLength))) { - JNI_TRACE("HMAC_UpdateDirect(%p, %p, %d) => threw exception", hmacCtx, p, inLength); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_UpdateDirect"); - return; - } + return evp_aead_ctx_op_buf(env, evpAeadRef, keyArray, tagLen, outBuffer, nonceArray, inBuffer, + aadArray, EVP_AEAD_CTX_seal); } -static void NativeCrypto_HMAC_Update(JNIEnv* env, jclass, jobject hmacCtxRef, jbyteArray inArray, - jint inOffset, jint inLength) { +static jint NativeCrypto_EVP_AEAD_CTX_open_buf(JNIEnv* env, jclass, jlong evpAeadRef, + jbyteArray keyArray, jint tagLen, jobject outBuffer, + jbyteArray nonceArray, jobject inBuffer, + jbyteArray aadArray) { CHECK_ERROR_QUEUE_ON_RETURN; - HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); - JNI_TRACE("HMAC_Update(%p, %p, %d, %d)", hmacCtx, inArray, inOffset, inLength); - - if (hmacCtx == nullptr) { - return; - } - - ScopedByteArrayRO inBytes(env, inArray); - if (inBytes.get() == nullptr) { - return; - } - - if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", - "inBytes"); - return; - } - - const uint8_t* inPtr = reinterpret_cast(inBytes.get()); - if (!HMAC_Update(hmacCtx, inPtr + inOffset, static_cast(inLength))) { - JNI_TRACE("HMAC_Update(%p, %p, %d, %d) => threw exception", hmacCtx, inArray, inOffset, - inLength); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Update"); - return; - } + return evp_aead_ctx_op_buf(env, evpAeadRef, keyArray, tagLen, outBuffer, nonceArray, inBuffer, + aadArray, EVP_AEAD_CTX_open); } -static jbyteArray NativeCrypto_HMAC_Final(JNIEnv* env, jclass, jobject hmacCtxRef) { +static jbyteArray NativeCrypto_EVP_HPKE_CTX_export(JNIEnv* env, jclass, jobject hpkeCtxRef, + jbyteArray exporterCtxArray, jint exportedLen) { CHECK_ERROR_QUEUE_ON_RETURN; - HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); - JNI_TRACE("HMAC_Final(%p)", hmacCtx); + EVP_HPKE_CTX* hpkeCtx = fromContextObject(env, hpkeCtxRef); + JNI_TRACE("EVP_HPKE_CTX_export(%p, %p, %d)", hpkeCtx, exporterCtxArray, exportedLen); - if (hmacCtx == nullptr) { - return nullptr; + if (hpkeCtx == nullptr) { + // NullPointerException thrown while calling fromContextObject + return {}; } - uint8_t result[EVP_MAX_MD_SIZE]; - unsigned len; - if (!HMAC_Final(hmacCtx, result, &len)) { - JNI_TRACE("HMAC_Final(%p) => threw exception", hmacCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Final"); - return nullptr; + std::optional optionalExporterCtx; + const uint8_t* exporterCtx = nullptr; + size_t exporterCtxLen = 0; + if (exporterCtxArray != nullptr) { + optionalExporterCtx.emplace(env, exporterCtxArray); + exporterCtx = reinterpret_cast(optionalExporterCtx->get()); + if (exporterCtx == nullptr) { + return {}; + } + exporterCtxLen = optionalExporterCtx->size(); } - ScopedLocalRef resultArray(env, env->NewByteArray(static_cast(len))); - if (resultArray.get() == nullptr) { - return nullptr; + std::vector exported(exportedLen); + if (!EVP_HPKE_CTX_export(/* ctx= */ hpkeCtx, + /* out= */ exported.data(), + /* secret_len= */ exportedLen, + /* context= */ exporterCtx, + /* context_len= */ exporterCtxLen)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_export"); + return {}; } - ScopedByteArrayRW resultBytes(env, resultArray.get()); - if (resultBytes.get() == nullptr) { - return nullptr; + + ScopedLocalRef exportedArray(env, + env->NewByteArray(static_cast(exportedLen))); + if (exportedArray.get() == nullptr) { + return {}; } - memcpy(resultBytes.get(), result, len); - return resultArray.release(); + ScopedByteArrayRW exportedBytes(env, exportedArray.get()); + if (exportedBytes.get() == nullptr) { + return {}; + } + memcpy(exportedBytes.get(), reinterpret_cast(exported.data()), exportedLen); + return exportedArray.release(); } -static void NativeCrypto_HMAC_Reset(JNIEnv* env, jclass, jobject hmacCtxRef) { +static void NativeCrypto_EVP_HPKE_CTX_free(JNIEnv* env, jclass, jlong hpkeCtxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); - JNI_TRACE("HMAC_Reset(%p)", hmacCtx); - - if (hmacCtx == nullptr) { - return; - } - - // HMAC_Init_ex with all nulls will reuse the existing key. This is slightly - // more efficient than re-initializing the context with the key again. - if (!HMAC_Init_ex(hmacCtx, /*key=*/nullptr, /*key_len=*/0, /*md=*/nullptr, - /*impl=*/nullptr)) { - JNI_TRACE("HMAC_Reset(%p) => threw exception", hmacCtx); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Init_ex"); + EVP_HPKE_CTX* ctx = reinterpret_cast(hpkeCtxRef); + JNI_TRACE("EVP_HPKE_CTX_free(%p)", ctx); + if (ctx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "ctx == null"); return; } + EVP_HPKE_CTX_free(ctx); } -static void NativeCrypto_RAND_bytes(JNIEnv* env, jclass, jbyteArray output) { +static jbyteArray NativeCrypto_EVP_HPKE_CTX_open(JNIEnv* env, jclass, jobject recipientHpkeCtxRef, + jbyteArray ciphertextArray, jbyteArray aadArray) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("NativeCrypto_RAND_bytes(%p)", output); + EVP_HPKE_CTX* ctx = fromContextObject(env, recipientHpkeCtxRef); + JNI_TRACE("EVP_HPKE_CTX_open(%p, %p, %p)", ctx, ciphertextArray, aadArray); - ScopedByteArrayRW outputBytes(env, output); - if (outputBytes.get() == nullptr) { - return; + if (ctx == nullptr) { + // NullPointerException thrown while calling fromContextObject + return {}; } - unsigned char* tmp = reinterpret_cast(outputBytes.get()); - if (RAND_bytes(tmp, outputBytes.size()) <= 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "NativeCrypto_RAND_bytes"); - JNI_TRACE("tmp=%p NativeCrypto_RAND_bytes => threw error", tmp); - return; + if (ciphertextArray == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "ciphertextArray == null"); + return {}; } - JNI_TRACE("NativeCrypto_RAND_bytes(%p) => success", output); -} - -static jstring ASN1_OBJECT_to_OID_string(JNIEnv* env, const ASN1_OBJECT* obj) { - /* - * The OBJ_obj2txt API doesn't "measure" if you pass in nullptr as the buffer. - * Just make a buffer that's large enough here. The documentation recommends - * 80 characters. - */ - char output[128]; - int ret = OBJ_obj2txt(output, sizeof(output), obj, 1); - if (ret < 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "ASN1_OBJECT_to_OID_string"); - return nullptr; - } else if (size_t(ret) >= sizeof(output)) { - conscrypt::jniutil::throwRuntimeException(env, - "ASN1_OBJECT_to_OID_string buffer too small"); - return nullptr; + ScopedByteArrayRO ciphertext(env, ciphertextArray); + if (ciphertext.get() == nullptr) { + return {}; } - JNI_TRACE("ASN1_OBJECT_to_OID_string(%p) => %s", obj, output); - return env->NewStringUTF(output); -} - -static jlong NativeCrypto_create_BIO_InputStream(JNIEnv* env, jclass, jobject streamObj, - jboolean isFinite) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("create_BIO_InputStream(%p)", streamObj); + std::optional optionalAad; + const uint8_t* aad = nullptr; + size_t aadLen = 0; + if (aadArray != nullptr) { + optionalAad.emplace(env, aadArray); + aad = reinterpret_cast(optionalAad->get()); + if (aad == nullptr) { + return {}; + } + aadLen = optionalAad->size(); + } - if (streamObj == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "stream == null"); - return 0; + size_t plaintextLen; + std::vector plaintext(ciphertext.size()); + if (!EVP_HPKE_CTX_open( + /* ctx= */ ctx, + /* out= */ plaintext.data(), + /* out_len= */ &plaintextLen, + /* max_out_len= */ plaintext.size(), + /* in= */ reinterpret_cast(ciphertext.get()), + /* in_len= */ ciphertext.size(), + /* aad= */ aad, + /* aad_len= */ aadLen)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_open"); + return {}; } - const BIO_METHOD* method = stream_bio_method(); - if (!method) { - return 0; + plaintext.resize(plaintextLen); + ScopedLocalRef plaintextArray(env, + env->NewByteArray(static_cast(plaintextLen))); + if (plaintextArray.get() == nullptr) { + return {}; } - bssl::UniquePtr bio(BIO_new(method)); - if (bio.get() == nullptr) { - return 0; + ScopedByteArrayRW plaintextBytes(env, plaintextArray.get()); + if (plaintextBytes.get() == nullptr) { + return {}; } - - bio_stream_assign(bio.get(), new BioInputStream(streamObj, isFinite == JNI_TRUE)); - - JNI_TRACE("create_BIO_InputStream(%p) => %p", streamObj, bio.get()); - return static_cast(reinterpret_cast(bio.release())); + memcpy(plaintextBytes.get(), reinterpret_cast(plaintext.data()), plaintextLen); + return plaintextArray.release(); } -static jlong NativeCrypto_create_BIO_OutputStream(JNIEnv* env, jclass, jobject streamObj) { +static jbyteArray NativeCrypto_EVP_HPKE_CTX_seal(JNIEnv* env, jclass, jobject senderHpkeCtxRef, + jbyteArray plaintextArray, jbyteArray aadArray) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("create_BIO_OutputStream(%p)", streamObj); + EVP_HPKE_CTX* ctx = fromContextObject(env, senderHpkeCtxRef); + JNI_TRACE("EVP_HPKE_CTX_seal(%p, %p, %p)", ctx, plaintextArray, aadArray); - if (streamObj == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "stream == null"); - return 0; + if (ctx == nullptr) { + // NullPointerException thrown while calling fromContextObject + return {}; } - const BIO_METHOD* method = stream_bio_method(); - if (!method) { - return 0; + if (plaintextArray == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "plaintextArray == null"); + return {}; } - bssl::UniquePtr bio(BIO_new(method)); - if (bio.get() == nullptr) { - return 0; + + std::optional optionalAad; + const uint8_t* aad = nullptr; + size_t aadLen = 0; + if (aadArray != nullptr) { + optionalAad.emplace(env, aadArray); + aad = reinterpret_cast(optionalAad->get()); + if (aad == nullptr) { + return {}; + } + aadLen = optionalAad->size(); } - bio_stream_assign(bio.get(), new BioOutputStream(streamObj)); + ScopedByteArrayRO plaintext(env, plaintextArray); + std::vector encrypted(env->GetArrayLength(plaintextArray) + + EVP_HPKE_CTX_max_overhead(ctx)); + size_t encryptedLen; + if (!EVP_HPKE_CTX_seal( + /* ctx= */ ctx, + /* out= */ encrypted.data(), + /* out_len= */ &encryptedLen, + /* max_out_len= */ encrypted.size(), + /* in= */ reinterpret_cast(plaintext.get()), + /* in_len= */ plaintext.size(), + /* aad= */ aad, + /* aad_len= */ aadLen)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_seal"); + return {}; + } - JNI_TRACE("create_BIO_OutputStream(%p) => %p", streamObj, bio.get()); - return static_cast(reinterpret_cast(bio.release())); + ScopedLocalRef ciphertextArray(env, + env->NewByteArray(static_cast(encryptedLen))); + if (ciphertextArray.get() == nullptr) { + return {}; + } + ScopedByteArrayRW ciphertextBytes(env, ciphertextArray.get()); + if (ciphertextBytes.get() == nullptr) { + return {}; + } + memcpy(ciphertextBytes.get(), reinterpret_cast(encrypted.data()), encryptedLen); + return ciphertextArray.release(); } -static void NativeCrypto_BIO_free_all(JNIEnv* env, jclass, jlong bioRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - BIO* bio = to_BIO(env, bioRef); - JNI_TRACE("BIO_free_all(%p)", bio); +const EVP_HPKE_AEAD* getHpkeAead(JNIEnv* env, jint aeadValue) { + switch (aeadValue) { + case EVP_HPKE_AES_128_GCM: + return EVP_hpke_aes_128_gcm(); + case EVP_HPKE_AES_256_GCM: + return EVP_hpke_aes_256_gcm(); + case EVP_HPKE_CHACHA20_POLY1305: + return EVP_hpke_chacha20_poly1305(); + default: + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "AEAD is not supported"); + return nullptr; + } +} - if (bio == nullptr) { - return; +const EVP_HPKE_KDF* getHpkeKdf(JNIEnv* env, jint kdfValue) { + if (kdfValue == EVP_HPKE_HKDF_SHA256) { + return EVP_hpke_hkdf_sha256(); + } else { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "KDF is not supported"); + return nullptr; } +} - BIO_free_all(bio); +const EVP_HPKE_KEM* getHpkeKem(JNIEnv* env, jint kemValue) { + if (kemValue == EVP_HPKE_DHKEM_X25519_HKDF_SHA256) { + return EVP_hpke_x25519_hkdf_sha256(); + } else if (kemValue == EVP_HPKE_MLKEM768) { + return EVP_hpke_mlkem768(); + } else if (kemValue == EVP_HPKE_MLKEM1024) { + return EVP_hpke_mlkem1024(); + } else if (kemValue == EVP_HPKE_XWING) { + return EVP_hpke_xwing(); + } else { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "KEM is not supported"); + return nullptr; + } } -// NOLINTNEXTLINE(runtime/int) -static jstring X509_NAME_to_jstring(JNIEnv* env, X509_NAME* name, unsigned long flags) { - JNI_TRACE("X509_NAME_to_jstring(%p)", name); +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); - bssl::UniquePtr buffer(BIO_new(BIO_s_mem())); - if (buffer.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate BIO"); - JNI_TRACE("X509_NAME_to_jstring(%p) => threw error", name); + const EVP_HPKE_KEM* kem = getHpkeKem(env, kemValue); + if (kem == nullptr) { + return nullptr; + } + const EVP_HPKE_KDF* kdf = getHpkeKdf(env, kdfValue); + if (kdf == nullptr) { + return nullptr; + } + const EVP_HPKE_AEAD* aead = getHpkeAead(env, aeadValue); + if (aead == nullptr) { + return nullptr; + } + if (privateKeyArray == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKeyArray == null"); + return nullptr; + } + if (encArray == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "encArray == null"); return nullptr; } - /* Don't interpret the string. */ - flags &= ~(ASN1_STRFLGS_UTF8_CONVERT | ASN1_STRFLGS_ESC_MSB); + ScopedByteArrayRO privateKey(env, privateKeyArray); - /* Write in given format and null terminate. */ - X509_NAME_print_ex(buffer.get(), name, 0, flags); - BIO_write(buffer.get(), "\0", 1); + bssl::ScopedEVP_HPKE_KEY key; - char* tmp; - BIO_get_mem_data(buffer.get(), &tmp); - JNI_TRACE("X509_NAME_to_jstring(%p) => \"%s\"", name, tmp); - return env->NewStringUTF(tmp); -} + if (!EVP_HPKE_KEY_init( + /* key= */ key.get(), + /* kem= */ kem, + /* priv_key= */ reinterpret_cast(privateKey.get()), + /* priv_key_len= */ privateKey.size())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); + return nullptr; + } -/** - * Converts GENERAL_NAME items to the output format expected in - * X509Certificate#getSubjectAlternativeNames and - * X509Certificate#getIssuerAlternativeNames return. - */ -static jobject GENERAL_NAME_to_jobject(JNIEnv* env, GENERAL_NAME* gen) { - switch (gen->type) { - case GEN_EMAIL: - case GEN_DNS: - case GEN_URI: { - // This must be a valid IA5String and must not contain NULs. - // BoringSSL does not currently enforce the former (see - // https://crbug.com/boringssl/427). The latter was historically an - // issue for parsers that truncate at NUL. - const uint8_t* data = ASN1_STRING_get0_data(gen->d.ia5); - ssize_t len = ASN1_STRING_length(gen->d.ia5); - std::vector jchars; - jchars.reserve(len); - for (ssize_t i = 0; i < len; i++) { - if (data[i] == 0 || data[i] > 127) { - JNI_TRACE("GENERAL_NAME_to_jobject(%p) => Email/DNS/URI invalid", gen); - return nullptr; - } - // Converting ASCII to UTF-16 is the identity function. - jchars.push_back(data[i]); - } - JNI_TRACE("GENERAL_NAME_to_jobject(%p)=> Email/DNS/URI \"%.*s\"", gen, (int)len, data); - return env->NewString(jchars.data(), jchars.size()); + std::optional optionalInfo; + const uint8_t* info = nullptr; + size_t infoLen = 0; + if (infoArray != nullptr) { + optionalInfo.emplace(env, infoArray); + info = reinterpret_cast(optionalInfo->get()); + if (info == nullptr) { + return {}; } - case GEN_DIRNAME: - /* Write in RFC 2253 format */ - return X509_NAME_to_jstring(env, gen->d.directoryName, XN_FLAG_RFC2253); - case GEN_IPADD: { - const uint8_t* ip = ASN1_STRING_get0_data(gen->d.ip); - int ip_len = ASN1_STRING_length(gen->d.ip); - if (ip_len == 4) { - // IPv4 - std::unique_ptr buffer(new char[INET_ADDRSTRLEN]); - if (inet_ntop(AF_INET, ip, buffer.get(), INET_ADDRSTRLEN) != nullptr) { - JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv4 %s", gen, buffer.get()); - return env->NewStringUTF(buffer.get()); - } else { - JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv4 failed %s", gen, - strerror(errno)); - } - } else if (ip_len == 16) { - // IPv6 - std::unique_ptr buffer(new char[INET6_ADDRSTRLEN]); - if (inet_ntop(AF_INET6, ip, buffer.get(), INET6_ADDRSTRLEN) != nullptr) { - JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv6 %s", gen, buffer.get()); - return env->NewStringUTF(buffer.get()); - } else { - JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv6 failed %s", gen, - strerror(errno)); - } - } + infoLen = optionalInfo->size(); + } - /* Invalid IP encodings are pruned out without throwing an exception. */ - return nullptr; - } - case GEN_RID: - return ASN1_OBJECT_to_OID_string(env, gen->d.registeredID); - case GEN_OTHERNAME: - case GEN_X400: - default: - return ASN1ToByteArray(env, gen, i2d_GENERAL_NAME); + bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); + ScopedByteArrayRO enc(env, encArray); + if (!EVP_HPKE_CTX_setup_recipient( + /* ctx= */ ctx.get(), + /* key= */ key.get(), + /* kdf= */ kdf, + /* aead= */ aead, + /* enc= */ reinterpret_cast(enc.get()), + /* enc_len= */ enc.size(), + /* info= */ info, + /* info_len= */ infoLen)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_recipient"); + return nullptr; } - return nullptr; + ScopedLocalRef ctxObject( + env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, + conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, + reinterpret_cast(ctx.release()))); + return ctxObject.release(); } -#define GN_STACK_SUBJECT_ALT_NAME 1 -#define GN_STACK_ISSUER_ALT_NAME 2 - -static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder, - jint type) { +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; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d)", x509, type); + JNI_TRACE("EVP_HPKE_CTX_setup_sender(%d, %d, %d, %p, %p)", kemValue, kdfValue, aeadValue, + publicKeyArray, infoArray); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => x509 == null", x509, type); + const EVP_HPKE_KEM* kem = getHpkeKem(env, kemValue); + if (kem == nullptr) { return nullptr; } - - bssl::UniquePtr gn_stack; - if (type == GN_STACK_SUBJECT_ALT_NAME) { - gn_stack.reset(static_cast( - X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr))); - } else if (type == GN_STACK_ISSUER_ALT_NAME) { - gn_stack.reset(static_cast( - X509_get_ext_d2i(x509, NID_issuer_alt_name, nullptr, nullptr))); - } else { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => unknown type", x509, type); + const EVP_HPKE_KDF* kdf = getHpkeKdf(env, kdfValue); + if (kdf == nullptr) { return nullptr; } - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors, remove - // |ERR_clear_error|, and throw CertificateParsingException. - if (gn_stack == nullptr) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no extension or error)", x509, - type); - ERR_clear_error(); + const EVP_HPKE_AEAD* aead = getHpkeAead(env, aeadValue); + if (aead == nullptr) { return nullptr; } - - int count = static_cast(sk_GENERAL_NAME_num(gn_stack.get())); - if (count <= 0) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no entries)", x509, type); - return nullptr; + if (publicKeyArray == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "publicKeyArray == null"); + return {}; } - /* - * Keep track of how many originally so we can ignore any invalid - * values later. - */ - const int origCount = count; - - ScopedLocalRef joa( - env, env->NewObjectArray(count, conscrypt::jniutil::objectArrayClass, nullptr)); - for (int i = 0, j = 0; i < origCount; i++, j++) { - GENERAL_NAME* gen = sk_GENERAL_NAME_value(gn_stack.get(), static_cast(i)); - ScopedLocalRef val(env, GENERAL_NAME_to_jobject(env, gen)); - if (env->ExceptionCheck()) { - JNI_TRACE( - "get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen " - "name", - x509, type); - return nullptr; + std::optional optionalInfo; + const uint8_t* info = nullptr; + size_t infoLen = 0; + if (infoArray != nullptr) { + optionalInfo.emplace(env, infoArray); + info = reinterpret_cast(optionalInfo->get()); + if (info == nullptr) { + return {}; } + infoLen = optionalInfo->size(); + } - /* - * If it's nullptr, we'll have to skip this, reduce the number of total - * entries, and fix up the array later. - */ - if (val.get() == nullptr) { - j--; - count--; - continue; - } + ScopedByteArrayRO peer_public_key(env, publicKeyArray); - ScopedLocalRef item( - env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); + size_t encapsulatedSharedSecretLen; + uint8_t encapsulatedSharedSecret[EVP_HPKE_MAX_ENC_LENGTH]; - ScopedLocalRef parsedType( - env, - env->CallStaticObjectMethod(conscrypt::jniutil::integerClass, - conscrypt::jniutil::integer_valueOfMethod, gen->type)); - env->SetObjectArrayElement(item.get(), 0, parsedType.get()); - env->SetObjectArrayElement(item.get(), 1, val.get()); + bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); - env->SetObjectArrayElement(joa.get(), j, item.get()); + if (!EVP_HPKE_CTX_setup_sender( + /* ctx= */ ctx.get(), + /* out_enc= */ encapsulatedSharedSecret, + /* out_enc_len= */ &encapsulatedSharedSecretLen, + /* max_enc= */ EVP_HPKE_MAX_ENC_LENGTH, + /* kem= */ kem, + /* kdf= */ kdf, + /* aead= */ aead, + /* peer_public_key= */ + reinterpret_cast(peer_public_key.get()), + /* peer_public_key_len= */ peer_public_key.size(), + /* info= */ info, + /* info_len= */ infoLen)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "EVP_HPKE_CTX_setup_sender"); + return {}; } - if (count == 0) { - JNI_TRACE( - "get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to 0; returning " - "nullptr", - x509, type, origCount); - joa.reset(nullptr); - } else if (origCount != count) { - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to %d", x509, type, origCount, - count); - - ScopedLocalRef joa_copy( - env, env->NewObjectArray(count, conscrypt::jniutil::objectArrayClass, nullptr)); - - for (int i = 0; i < count; i++) { - ScopedLocalRef item(env, env->GetObjectArrayElement(joa.get(), i)); - env->SetObjectArrayElement(joa_copy.get(), i, item.get()); - } - - joa.reset(joa_copy.release()); + ScopedLocalRef encArray( + env, env->NewByteArray(static_cast(encapsulatedSharedSecretLen))); + if (encArray.get() == nullptr) { + return {}; + } + ScopedByteArrayRW encBytes(env, encArray.get()); + if (encBytes.get() == nullptr) { + return {}; } + memcpy(encBytes.get(), reinterpret_cast(encapsulatedSharedSecret), + encapsulatedSharedSecretLen); - JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => %d entries", x509, type, count); - return joa.release(); -} + ScopedLocalRef result( + env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); -static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_notBefore(%p)", x509); + ScopedLocalRef ctxObject( + env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, + conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, + reinterpret_cast(ctx.release()))); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_get_notBefore(%p) => x509 == null", x509); - return 0; - } + env->SetObjectArrayElement(result.get(), 0, ctxObject.release()); + env->SetObjectArrayElement(result.get(), 1, encArray.release()); - ASN1_TIME* notBefore = X509_get_notBefore(x509); - JNI_TRACE("X509_get_notBefore(%p) => %p", x509, notBefore); - return reinterpret_cast(notBefore); + return result.release(); } -static jlong NativeCrypto_X509_get_notAfter(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +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; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_notAfter(%p)", x509); + JNI_TRACE("EVP_HPKE_CTX_setup_sender_with_seed_for_testing(%d, %d, %d, %p, %p, %p)", kemValue, + kdfValue, aeadValue, publicKeyArray, infoArray, seedArray); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_get_notAfter(%p) => x509 == null", x509); - return 0; + const EVP_HPKE_KEM* kem = getHpkeKem(env, kemValue); + if (kem == nullptr) { + return nullptr; + } + const EVP_HPKE_KDF* kdf = getHpkeKdf(env, kdfValue); + if (kdf == nullptr) { + return nullptr; + } + const EVP_HPKE_AEAD* aead = getHpkeAead(env, aeadValue); + if (aead == nullptr) { + return nullptr; + } + if (publicKeyArray == nullptr || seedArray == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "publicKeyArray or seedArray == null"); + return {}; } - ASN1_TIME* notAfter = X509_get_notAfter(x509); - JNI_TRACE("X509_get_notAfter(%p) => %p", x509, notAfter); - return reinterpret_cast(notAfter); -} - -// NOLINTNEXTLINE(runtime/int) -static long NativeCrypto_X509_get_version(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_version(%p)", x509); - - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_get_version(%p) => x509 == null", x509); - return 0; + std::optional optionalInfo; + const uint8_t* info = nullptr; + size_t infoLen = 0; + if (infoArray != nullptr) { + optionalInfo.emplace(env, infoArray); + info = reinterpret_cast(optionalInfo->get()); + if (info == nullptr) { + return {}; + } + infoLen = optionalInfo->size(); } - // NOLINTNEXTLINE(runtime/int) - long version = X509_get_version(x509); - JNI_TRACE("X509_get_version(%p) => %ld", x509, version); - return version; -} + ScopedByteArrayRO peer_public_key(env, publicKeyArray); -template -static jbyteArray get_X509Type_serialNumber(JNIEnv* env, const T* x509Type, - const ASN1_INTEGER* (*get_serial_func)(const T*)) { - JNI_TRACE("get_X509Type_serialNumber(%p)", x509Type); + ScopedByteArrayRO seed(env, seedArray); - if (x509Type == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509Type == null"); - JNI_TRACE("get_X509Type_serialNumber(%p) => x509Type == null", x509Type); - return nullptr; - } + size_t encapsulatedSharedSecretLen; + uint8_t encapsulatedSharedSecret[EVP_HPKE_MAX_ENC_LENGTH]; - const ASN1_INTEGER* serialNumber = get_serial_func(x509Type); - bssl::UniquePtr serialBn(ASN1_INTEGER_to_BN(serialNumber, nullptr)); - if (serialBn.get() == nullptr) { - JNI_TRACE("X509_get_serialNumber(%p) => threw exception", x509Type); - return nullptr; + bssl::UniquePtr ctx(EVP_HPKE_CTX_new()); + + if (!EVP_HPKE_CTX_setup_sender_with_seed_for_testing( + /* ctx= */ ctx.get(), + /* out_enc= */ encapsulatedSharedSecret, + /* out_enc_len= */ &encapsulatedSharedSecretLen, + /* max_enc= */ EVP_HPKE_MAX_ENC_LENGTH, + /* kem= */ kem, + /* kdf= */ kdf, + /* aead= */ aead, + /* peer_public_key= */ + reinterpret_cast(peer_public_key.get()), + /* peer_public_key_len= */ peer_public_key.size(), + /* info= */ info, + /* info_len= */ infoLen, + /* seed= */ reinterpret_cast(seed.get()), + /* seed_len= */ seed.size())) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "EVP_HPKE_CTX_setup_sender_with_seed_for_testing"); + return {}; } - ScopedLocalRef serialArray(env, bignumToArray(env, serialBn.get(), "serialBn")); - if (env->ExceptionCheck()) { - JNI_TRACE("X509_get_serialNumber(%p) => threw exception", x509Type); - return nullptr; + ScopedLocalRef encArray( + env, env->NewByteArray(static_cast(encapsulatedSharedSecretLen))); + if (encArray.get() == nullptr) { + return {}; + } + ScopedByteArrayRW encBytes(env, encArray.get()); + if (encBytes.get() == nullptr) { + return {}; } + memcpy(encBytes.get(), reinterpret_cast(encapsulatedSharedSecret), + encapsulatedSharedSecretLen); - JNI_TRACE("X509_get_serialNumber(%p) => %p", x509Type, serialArray.get()); - return serialArray.release(); + ScopedLocalRef result( + env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); + + ScopedLocalRef ctxObject( + env, env->NewObject(conscrypt::jniutil::nativeRefHpkeCtxClass, + conscrypt::jniutil::nativeRefHpkeCtxClass_constructor, + reinterpret_cast(ctx.release()))); + + env->SetObjectArrayElement(result.get(), 0, ctxObject.release()); + env->SetObjectArrayElement(result.get(), 1, encArray.release()); + + return result.release(); } -static jbyteArray NativeCrypto_X509_get_serialNumber(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jlong NativeCrypto_CMAC_CTX_new(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_serialNumber(%p)", x509); + JNI_TRACE("CMAC_CTX_new"); + auto cmacCtx = CMAC_CTX_new(); + if (cmacCtx == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate CMAC_CTX"); + return 0; + } - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_get_serialNumber(%p) => x509 == null", x509); - return nullptr; + return reinterpret_cast(cmacCtx); +} + +static void NativeCrypto_CMAC_CTX_free(JNIEnv* env, jclass, jlong cmacCtxRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + CMAC_CTX* cmacCtx = reinterpret_cast(cmacCtxRef); + JNI_TRACE("CMAC_CTX_free(%p)", cmacCtx); + if (cmacCtx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "cmacCtx == null"); + return; } - return get_X509Type_serialNumber(env, x509, X509_get0_serialNumber); + CMAC_CTX_free(cmacCtx); } -static jbyteArray NativeCrypto_X509_REVOKED_get_serialNumber(JNIEnv* env, jclass, - jlong x509RevokedRef, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_CMAC_Init(JNIEnv* env, jclass, jobject cmacCtxRef, jbyteArray keyArray) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("X509_REVOKED_get_serialNumber(%p)", revoked); + CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); + JNI_TRACE("CMAC_Init(%p, %p)", cmacCtx, keyArray); + if (cmacCtx == nullptr) { + return; + } + ScopedByteArrayRO keyBytes(env, keyArray); + if (keyBytes.get() == nullptr) { + return; + } - if (revoked == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); - JNI_TRACE("X509_REVOKED_get_serialNumber(%p) => revoked == null", revoked); - return 0; + const uint8_t* keyPtr = reinterpret_cast(keyBytes.get()); + + const EVP_CIPHER* cipher; + switch (keyBytes.size()) { + case 16: + cipher = EVP_aes_128_cbc(); + break; + case 24: + cipher = EVP_aes_192_cbc(); + break; + case 32: + cipher = EVP_aes_256_cbc(); + break; + default: + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "CMAC_Init: Unsupported key length"); + return; + } + + if (!CMAC_Init(cmacCtx, keyPtr, keyBytes.size(), cipher, nullptr)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Init"); + JNI_TRACE("CMAC_Init(%p, %p) => fail CMAC_Init_ex", cmacCtx, keyArray); + return; } - return get_X509Type_serialNumber(env, revoked, X509_REVOKED_get0_serialNumber); } -static void NativeCrypto_X509_verify(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder, jobject pkeyRef) { +static void NativeCrypto_CMAC_UpdateDirect(JNIEnv* env, jclass, jobject cmacCtxRef, jlong inPtr, + int inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("X509_verify(%p, %p)", x509, pkey); + CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); + const uint8_t* p = reinterpret_cast(inPtr); + JNI_TRACE("CMAC_UpdateDirect(%p, %p, %d)", cmacCtx, p, inLength); - if (pkey == nullptr) { - JNI_TRACE("X509_verify(%p, %p) => pkey == null", x509, pkey); + if (cmacCtx == nullptr) { return; } - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_verify(%p, %p) => x509 == null", x509, pkey); + if (p == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, nullptr); return; } - if (X509_verify(x509, pkey) != 1) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "X509_verify", conscrypt::jniutil::throwCertificateException); - JNI_TRACE("X509_verify(%p, %p) => verify failure", x509, pkey); + if (!CMAC_Update(cmacCtx, p, static_cast(inLength))) { + JNI_TRACE("CMAC_UpdateDirect(%p, %p, %d) => threw exception", cmacCtx, p, inLength); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_UpdateDirect"); return; } - JNI_TRACE("X509_verify(%p, %p) => verify success", x509, pkey); } -static jbyteArray NativeCrypto_get_X509_tbs_cert(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_CMAC_Update(JNIEnv* env, jclass, jobject cmacCtxRef, jbyteArray inArray, + jint inOffset, jint inLength) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_tbs_cert(%p)", x509); - // Note |i2d_X509_tbs| preserves the original encoding of the TBSCertificate. - return ASN1ToByteArray(env, x509, i2d_X509_tbs); + CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); + JNI_TRACE("CMAC_Update(%p, %p, %d, %d)", cmacCtx, inArray, inOffset, inLength); + + if (cmacCtx == nullptr) { + return; + } + + ScopedByteArrayRO inBytes(env, inArray); + if (inBytes.get() == nullptr) { + return; + } + + if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "inBytes"); + return; + } + + const uint8_t* inPtr = reinterpret_cast(inBytes.get()); + + if (!CMAC_Update(cmacCtx, inPtr + inOffset, static_cast(inLength))) { + JNI_TRACE("CMAC_Update(%p, %p, %d, %d) => threw exception", cmacCtx, inArray, inOffset, + inLength); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Update"); + return; + } } -static jbyteArray NativeCrypto_get_X509_tbs_cert_without_ext(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder, - jstring oidString) { +static jbyteArray NativeCrypto_CMAC_Final(JNIEnv* env, jclass, jobject cmacCtxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p)", x509, oidString); + CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); + JNI_TRACE("CMAC_Final(%p)", cmacCtx); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => x509 == null", x509, oidString); + if (cmacCtx == nullptr) { return nullptr; } - bssl::UniquePtr copy(X509_dup(x509)); - if (copy == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_dup"); - JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => threw error", x509, oidString); + uint8_t result[EVP_MAX_MD_SIZE]; + size_t len; + if (!CMAC_Final(cmacCtx, result, &len)) { + JNI_TRACE("CMAC_Final(%p) => threw exception", cmacCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Final"); return nullptr; } - ScopedUtfChars oid(env, oidString); - if (oid.c_str() == nullptr) { - JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => oidString == null", x509, oidString); + ScopedLocalRef resultArray(env, env->NewByteArray(static_cast(len))); + if (resultArray.get() == nullptr) { return nullptr; } - - bssl::UniquePtr obj(OBJ_txt2obj(oid.c_str(), 1 /* allow numerical form only */)); - if (obj.get() == nullptr) { - JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %s) => oid conversion failed", x509, - oid.c_str()); - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Invalid OID."); - ERR_clear_error(); + ScopedByteArrayRW resultBytes(env, resultArray.get()); + if (resultBytes.get() == nullptr) { return nullptr; } + memcpy(resultBytes.get(), result, len); + return resultArray.release(); +} - int extIndex = X509_get_ext_by_OBJ(copy.get(), obj.get(), -1); - if (extIndex == -1) { - JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %s) => ext not found", x509, oid.c_str()); - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Extension not found."); - return nullptr; +static void NativeCrypto_CMAC_Reset(JNIEnv* env, jclass, jobject cmacCtxRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + CMAC_CTX* cmacCtx = fromContextObject(env, cmacCtxRef); + JNI_TRACE("CMAC_Reset(%p)", cmacCtx); + + if (cmacCtx == nullptr) { + return; } - // Remove the extension and re-encode the TBSCertificate. Note - // |i2d_re_X509_tbs| ignores the cached encoding. - X509_EXTENSION_free(X509_delete_ext(copy.get(), extIndex)); - return ASN1ToByteArray(env, copy.get(), i2d_re_X509_tbs); + if (!CMAC_Reset(cmacCtx)) { + JNI_TRACE("CMAC_Reset(%p) => threw exception", cmacCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "CMAC_Reset"); + return; + } } -static jint NativeCrypto_get_X509_ex_flags(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jlong NativeCrypto_HMAC_CTX_new(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_ex_flags(%p)", x509); - - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_ex_flags(%p) => x509 == null", x509); + JNI_TRACE("HMAC_CTX_new"); + auto hmacCtx = HMAC_CTX_new(); + if (hmacCtx == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate HMAC_CTX"); + ERR_clear_error(); return 0; } - uint32_t flags = X509_get_extension_flags(x509); - // X509_get_extension_flags sometimes leaves values in the error queue. See - // https://crbug.com/boringssl/382. - // - // TODO(https://github.com/google/conscrypt/issues/916): This function is used - // to check EXFLAG_CA, but does not check EXFLAG_INVALID. Fold the two JNI - // calls in getBasicConstraints() together and handle errors. (See also - // NativeCrypto_get_X509_ex_pathlen.) From there, limit this JNI call to - // EXFLAG_CRITICAL. - ERR_clear_error(); - return flags; + return reinterpret_cast(hmacCtx); } -static jint NativeCrypto_X509_check_issued(JNIEnv* env, jclass, jlong x509Ref1, - CONSCRYPT_UNUSED jobject holder, jlong x509Ref2, - CONSCRYPT_UNUSED jobject holder2) { +static void NativeCrypto_HMAC_CTX_free(JNIEnv* env, jclass, jlong hmacCtxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509_1 = reinterpret_cast(static_cast(x509Ref1)); - X509* x509_2 = reinterpret_cast(static_cast(x509Ref2)); - JNI_TRACE("X509_check_issued(%p, %p)", x509_1, x509_2); + HMAC_CTX* hmacCtx = reinterpret_cast(hmacCtxRef); + JNI_TRACE("HMAC_CTX_free(%p)", hmacCtx); + if (hmacCtx == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "hmacCtx == null"); + return; + } + HMAC_CTX_free(hmacCtx); +} - if (x509_1 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509Ref1 == null"); - JNI_TRACE("X509_check_issued(%p, %p) => x509_1 == null", x509_1, x509_2); - return 0; +static void NativeCrypto_HMAC_Init_ex(JNIEnv* env, jclass, jobject hmacCtxRef, jbyteArray keyArray, + jobject evpMdRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); + const EVP_MD* md = reinterpret_cast(evpMdRef); + JNI_TRACE("HMAC_Init_ex(%p, %p, %p)", hmacCtx, keyArray, md); + if (hmacCtx == nullptr) { + return; } - if (x509_2 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509Ref2 == null"); - JNI_TRACE("X509_check_issued(%p, %p) => x509_2 == null", x509_1, x509_2); - return 0; + ScopedByteArrayRO keyBytes(env, keyArray); + if (keyBytes.get() == nullptr) { + return; } - int ret = X509_check_issued(x509_1, x509_2); - JNI_TRACE("X509_check_issued(%p, %p) => %d", x509_1, x509_2, ret); - return ret; -} -static const ASN1_BIT_STRING* get_X509_signature(X509* x509) { - const ASN1_BIT_STRING* signature; - X509_get0_signature(&signature, nullptr, x509); - return signature; + const uint8_t* keyPtr = reinterpret_cast(keyBytes.get()); + if (!HMAC_Init_ex(hmacCtx, keyPtr, keyBytes.size(), md, nullptr)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Init_ex"); + JNI_TRACE("HMAC_Init_ex(%p, %p, %p) => fail HMAC_Init_ex", hmacCtx, keyArray, md); + return; + } } -static const ASN1_BIT_STRING* get_X509_CRL_signature(X509_CRL* crl) { - const ASN1_BIT_STRING* signature; - X509_CRL_get0_signature(crl, &signature, nullptr); - return signature; -} +static void NativeCrypto_HMAC_UpdateDirect(JNIEnv* env, jclass, jobject hmacCtxRef, jlong inPtr, + int inLength) { + CHECK_ERROR_QUEUE_ON_RETURN; + HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); + const uint8_t* p = reinterpret_cast(inPtr); + JNI_TRACE("HMAC_UpdateDirect(%p, %p, %d)", hmacCtx, p, inLength); -template -static jbyteArray get_X509Type_signature(JNIEnv* env, T* x509Type, - const ASN1_BIT_STRING* (*get_signature_func)(T*)) { - JNI_TRACE("get_X509Type_signature(%p)", x509Type); + if (hmacCtx == nullptr) { + return; + } - if (x509Type == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509Type == null"); - JNI_TRACE("get_X509Type_signature(%p) => x509Type == null", x509Type); - return nullptr; + if (p == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, nullptr); + return; } - const ASN1_BIT_STRING* signature = get_signature_func(x509Type); + if (!HMAC_Update(hmacCtx, p, static_cast(inLength))) { + JNI_TRACE("HMAC_UpdateDirect(%p, %p, %d) => threw exception", hmacCtx, p, inLength); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_UpdateDirect"); + return; + } +} - ScopedLocalRef signatureArray(env, - env->NewByteArray(ASN1_STRING_length(signature))); - if (env->ExceptionCheck()) { - JNI_TRACE("get_X509Type_signature(%p) => threw exception", x509Type); - return nullptr; +static void NativeCrypto_HMAC_Update(JNIEnv* env, jclass, jobject hmacCtxRef, jbyteArray inArray, + jint inOffset, jint inLength) { + CHECK_ERROR_QUEUE_ON_RETURN; + HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); + JNI_TRACE("HMAC_Update(%p, %p, %d, %d)", hmacCtx, inArray, inOffset, inLength); + + if (hmacCtx == nullptr) { + return; } - ScopedByteArrayRW signatureBytes(env, signatureArray.get()); - if (signatureBytes.get() == nullptr) { - JNI_TRACE("get_X509Type_signature(%p) => using byte array failed", x509Type); - return nullptr; + ScopedByteArrayRO inBytes(env, inArray); + if (inBytes.get() == nullptr) { + return; } - memcpy(signatureBytes.get(), ASN1_STRING_get0_data(signature), ASN1_STRING_length(signature)); + if (ARRAY_OFFSET_LENGTH_INVALID(inBytes, inOffset, inLength)) { + conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", + "inBytes"); + return; + } - JNI_TRACE("get_X509Type_signature(%p) => %p (%d bytes)", x509Type, signatureArray.get(), - ASN1_STRING_length(signature)); - return signatureArray.release(); + const uint8_t* inPtr = reinterpret_cast(inBytes.get()); + if (!HMAC_Update(hmacCtx, inPtr + inOffset, static_cast(inLength))) { + JNI_TRACE("HMAC_Update(%p, %p, %d, %d) => threw exception", hmacCtx, inArray, inOffset, + inLength); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Update"); + return; + } } -static jbyteArray NativeCrypto_get_X509_signature(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jbyteArray NativeCrypto_HMAC_Final(JNIEnv* env, jclass, jobject hmacCtxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_signature(%p)", x509); + HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); + JNI_TRACE("HMAC_Final(%p)", hmacCtx); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_signature(%p) => x509 == null", x509); + if (hmacCtx == nullptr) { return nullptr; } - return get_X509Type_signature(env, x509, get_X509_signature); -} -static jbyteArray NativeCrypto_get_X509_CRL_signature(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("get_X509_CRL_signature(%p)", crl); + uint8_t result[EVP_MAX_MD_SIZE]; + unsigned len; + if (!HMAC_Final(hmacCtx, result, &len)) { + JNI_TRACE("HMAC_Final(%p) => threw exception", hmacCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Final"); + return nullptr; + } - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_signature(%p) => crl == null", crl); + ScopedLocalRef resultArray(env, env->NewByteArray(static_cast(len))); + if (resultArray.get() == nullptr) { return nullptr; } - return get_X509Type_signature(env, crl, get_X509_CRL_signature); + ScopedByteArrayRW resultBytes(env, resultArray.get()); + if (resultBytes.get() == nullptr) { + return nullptr; + } + memcpy(resultBytes.get(), result, len); + return resultArray.release(); } -static jlong NativeCrypto_X509_CRL_get0_by_cert(JNIEnv* env, jclass, jlong x509crlRef, - CONSCRYPT_UNUSED jobject holder, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder2) { +static void NativeCrypto_HMAC_Reset(JNIEnv* env, jclass, jobject hmacCtxRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* x509crl = reinterpret_cast(static_cast(x509crlRef)); - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_CRL_get0_by_cert(%p, %p)", x509crl, x509); + HMAC_CTX* hmacCtx = fromContextObject(env, hmacCtxRef); + JNI_TRACE("HMAC_Reset(%p)", hmacCtx); - if (x509crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509crl == null"); - JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => x509crl == null", x509crl, x509); - return 0; - } else if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => x509 == null", x509crl, x509); - return 0; + if (hmacCtx == nullptr) { + return; } - X509_REVOKED* revoked = nullptr; - int ret = X509_CRL_get0_by_cert(x509crl, &revoked, x509); - if (ret == 0) { - JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => none", x509crl, x509); - return 0; + // HMAC_Init_ex with all nulls will reuse the existing key. This is slightly + // more efficient than re-initializing the context with the key again. + if (!HMAC_Init_ex(hmacCtx, /*key=*/nullptr, /*key_len=*/0, /*md=*/nullptr, + /*impl=*/nullptr)) { + JNI_TRACE("HMAC_Reset(%p) => threw exception", hmacCtx); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "HMAC_Init_ex"); + return; } - - JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => %p", x509crl, x509, revoked); - return reinterpret_cast(revoked); } -static jlong NativeCrypto_X509_CRL_get0_by_serial(JNIEnv* env, jclass, jlong x509crlRef, - CONSCRYPT_UNUSED jobject holder, - jbyteArray serialArray) { +static void NativeCrypto_RAND_bytes(JNIEnv* env, jclass, jbyteArray output) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* x509crl = reinterpret_cast(static_cast(x509crlRef)); - JNI_TRACE("X509_CRL_get0_by_serial(%p, %p)", x509crl, serialArray); - - if (x509crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509crl == null"); - JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => crl == null", x509crl, serialArray); - return 0; - } - - bssl::UniquePtr serialBn = arrayToBignum(env, serialArray); - if (serialBn == nullptr) { - if (!env->ExceptionCheck()) { - conscrypt::jniutil::throwNullPointerException(env, "serial == null"); - } - JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => BN conversion failed", x509crl, serialArray); - return 0; - } + JNI_TRACE("NativeCrypto_RAND_bytes(%p)", output); - bssl::UniquePtr serialInteger(BN_to_ASN1_INTEGER(serialBn.get(), nullptr)); - if (serialInteger.get() == nullptr) { - JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => BN conversion failed", x509crl, serialArray); - return 0; + ScopedByteArrayRW outputBytes(env, output); + if (outputBytes.get() == nullptr) { + return; } - X509_REVOKED* revoked = nullptr; - int ret = X509_CRL_get0_by_serial(x509crl, &revoked, serialInteger.get()); - if (ret == 0) { - JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => none", x509crl, serialArray); - return 0; + unsigned char* tmp = reinterpret_cast(outputBytes.get()); + if (RAND_bytes(tmp, outputBytes.size()) <= 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "NativeCrypto_RAND_bytes"); + JNI_TRACE("tmp=%p NativeCrypto_RAND_bytes => threw error", tmp); + return; } - JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => %p", x509crl, serialArray, revoked); - return reinterpret_cast(revoked); + JNI_TRACE("NativeCrypto_RAND_bytes(%p) => success", output); } -static jlongArray NativeCrypto_X509_CRL_get_REVOKED(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_get_REVOKED(%p)", crl); - - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_get_REVOKED(%p) => crl == null", crl); +static jstring ASN1_OBJECT_to_OID_string(JNIEnv* env, const ASN1_OBJECT* obj) { + /* + * The OBJ_obj2txt API doesn't "measure" if you pass in nullptr as the buffer. + * Just make a buffer that's large enough here. The documentation recommends + * 80 characters. + */ + char output[128]; + int ret = OBJ_obj2txt(output, sizeof(output), obj, 1); + if (ret < 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "ASN1_OBJECT_to_OID_string"); return nullptr; - } - - STACK_OF(X509_REVOKED)* stack = X509_CRL_get_REVOKED(crl); - if (stack == nullptr) { - JNI_TRACE("X509_CRL_get_REVOKED(%p) => stack is null", crl); + } else if (size_t(ret) >= sizeof(output)) { + conscrypt::jniutil::throwRuntimeException(env, + "ASN1_OBJECT_to_OID_string buffer too small"); return nullptr; } - size_t size = sk_X509_REVOKED_num(stack); - - ScopedLocalRef revokedArray(env, env->NewLongArray(static_cast(size))); - ScopedLongArrayRW revoked(env, revokedArray.get()); - for (size_t i = 0; i < size; i++) { - X509_REVOKED* item = reinterpret_cast(sk_X509_REVOKED_value(stack, i)); - revoked[i] = reinterpret_cast(X509_REVOKED_dup(item)); - } - - JNI_TRACE("X509_CRL_get_REVOKED(%p) => %p [size=%zd]", stack, revokedArray.get(), size); - return revokedArray.release(); + JNI_TRACE("ASN1_OBJECT_to_OID_string(%p) => %s", obj, output); + return env->NewStringUTF(output); } -static jbyteArray NativeCrypto_i2d_X509_CRL(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { +static jlong NativeCrypto_create_BIO_InputStream(JNIEnv* env, jclass, jobject streamObj, + jboolean isFinite) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("i2d_X509_CRL(%p)", crl); + JNI_TRACE("create_BIO_InputStream(%p)", streamObj); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("i2d_X509_CRL(%p) => crl == null", crl); - return nullptr; + if (streamObj == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "stream == null"); + return 0; } - return ASN1ToByteArray(env, crl, i2d_X509_CRL); -} - -static void NativeCrypto_X509_CRL_free(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_free(%p)", crl); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_free(%p) => crl == null", crl); - return; + const BIO_METHOD* method = stream_bio_method(); + if (!method) { + return 0; } - - X509_CRL_free(crl); -} - -static void NativeCrypto_X509_CRL_print(JNIEnv* env, jclass, jlong bioRef, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - BIO* bio = reinterpret_cast(static_cast(bioRef)); - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_print(%p, %p)", bio, crl); - - if (bio == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "bio == null"); - JNI_TRACE("X509_CRL_print(%p, %p) => bio == null", bio, crl); - return; + bssl::UniquePtr bio(BIO_new(method)); + if (bio.get() == nullptr) { + return 0; } - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_print(%p, %p) => crl == null", bio, crl); - return; - } + bio_stream_assign(bio.get(), new BioInputStream(streamObj, isFinite == JNI_TRUE)); - if (!X509_CRL_print(bio, crl)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_CRL_print"); - JNI_TRACE("X509_CRL_print(%p, %p) => threw error", bio, crl); - return; - } - JNI_TRACE("X509_CRL_print(%p, %p) => success", bio, crl); + JNI_TRACE("create_BIO_InputStream(%p) => %p", streamObj, bio.get()); + return static_cast(reinterpret_cast(bio.release())); } -static jstring NativeCrypto_get_X509_CRL_sig_alg_oid(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { +static jlong NativeCrypto_create_BIO_OutputStream(JNIEnv* env, jclass, jobject streamObj) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("get_X509_CRL_sig_alg_oid(%p)", crl); + JNI_TRACE("create_BIO_OutputStream(%p)", streamObj); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("get_X509_CRL_sig_alg_oid(%p) => crl == null", crl); - return nullptr; + if (streamObj == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "stream == null"); + return 0; } - const X509_ALGOR* sig_alg; - X509_CRL_get0_signature(crl, nullptr, &sig_alg); - const ASN1_OBJECT* oid; - X509_ALGOR_get0(&oid, nullptr, nullptr, sig_alg); - return ASN1_OBJECT_to_OID_string(env, oid); -} - -static jbyteArray get_X509_ALGOR_parameter(JNIEnv* env, const X509_ALGOR* algor) { - int param_type; - const void* param_value; - X509_ALGOR_get0(nullptr, ¶m_type, ¶m_value, algor); - - if (param_type == V_ASN1_UNDEF) { - JNI_TRACE("get_X509_ALGOR_parameter(%p) => no parameters", algor); - return nullptr; + const BIO_METHOD* method = stream_bio_method(); + if (!method) { + return 0; } - - // The OpenSSL 1.1.x API lacks a function to get the ASN1_TYPE out of - // X509_ALGOR directly, so recreate it from the returned components. - bssl::UniquePtr param(ASN1_TYPE_new()); - if (!param || !ASN1_TYPE_set1(param.get(), param_type, param_value)) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to serialize parameter"); - return nullptr; + bssl::UniquePtr bio(BIO_new(method)); + if (bio.get() == nullptr) { + return 0; } - return ASN1ToByteArray(env, param.get(), i2d_ASN1_TYPE); + bio_stream_assign(bio.get(), new BioOutputStream(streamObj)); + + JNI_TRACE("create_BIO_OutputStream(%p) => %p", streamObj, bio.get()); + return static_cast(reinterpret_cast(bio.release())); } -static jbyteArray NativeCrypto_get_X509_CRL_sig_alg_parameter(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_BIO_free_all(JNIEnv* env, jclass, jlong bioRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p)", crl); + BIO* bio = to_BIO(env, bioRef); + JNI_TRACE("BIO_free_all(%p)", bio); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p) => crl == null", crl); - return nullptr; + if (bio == nullptr) { + return; } - const X509_ALGOR* sig_alg; - X509_CRL_get0_signature(crl, nullptr, &sig_alg); - return get_X509_ALGOR_parameter(env, sig_alg); + BIO_free_all(bio); } -static jbyteArray NativeCrypto_X509_CRL_get_issuer_name(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); +// NOLINTNEXTLINE(runtime/int) +static jstring X509_NAME_to_jstring(JNIEnv* env, X509_NAME* name, unsigned long flags) { + JNI_TRACE("X509_NAME_to_jstring(%p)", name); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_get_issuer_name(%p) => crl == null", crl); + bssl::UniquePtr buffer(BIO_new(BIO_s_mem())); + if (buffer.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate BIO"); + ERR_clear_error(); + JNI_TRACE("X509_NAME_to_jstring(%p) => threw error", name); return nullptr; } - JNI_TRACE("X509_CRL_get_issuer_name(%p)", crl); - return ASN1ToByteArray(env, X509_CRL_get_issuer(crl), i2d_X509_NAME); -} - -// NOLINTNEXTLINE(runtime/int) -static long NativeCrypto_X509_CRL_get_version(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_get_version(%p)", crl); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_get_version(%p) => crl == null", crl); - return 0; - } - // NOLINTNEXTLINE(runtime/int) - long version = X509_CRL_get_version(crl); - JNI_TRACE("X509_CRL_get_version(%p) => %ld", crl, version); - return version; -} + /* Don't interpret the string. */ + flags &= ~(ASN1_STRFLGS_UTF8_CONVERT | ASN1_STRFLGS_ESC_MSB); -template -static X509_EXTENSION* X509Type_get_ext(JNIEnv* env, const T* x509Type, jstring oidString) { - JNI_TRACE("X509Type_get_ext(%p)", x509Type); + /* Write in given format and null terminate. */ + X509_NAME_print_ex(buffer.get(), name, 0, flags); + BIO_write(buffer.get(), "\0", 1); - if (x509Type == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - return nullptr; - } + char* tmp; + BIO_get_mem_data(buffer.get(), &tmp); + JNI_TRACE("X509_NAME_to_jstring(%p) => \"%s\"", name, tmp); + return env->NewStringUTF(tmp); +} - ScopedUtfChars oid(env, oidString); - if (oid.c_str() == nullptr) { - return nullptr; +/** + * Converts GENERAL_NAME items to the output format expected in + * X509Certificate#getSubjectAlternativeNames and + * X509Certificate#getIssuerAlternativeNames return. + */ +static jobject GENERAL_NAME_to_jobject(JNIEnv* env, GENERAL_NAME* gen) { + switch (gen->type) { + case GEN_EMAIL: + case GEN_DNS: + case GEN_URI: { + // This must be a valid IA5String and must not contain NULs. + // BoringSSL does not currently enforce the former (see + // https://crbug.com/boringssl/427). The latter was historically an + // issue for parsers that truncate at NUL. + const uint8_t* data = ASN1_STRING_get0_data(gen->d.ia5); + ssize_t len = ASN1_STRING_length(gen->d.ia5); + std::vector jchars; + jchars.reserve(len); + for (ssize_t i = 0; i < len; i++) { + if (data[i] == 0 || data[i] > 127) { + JNI_TRACE("GENERAL_NAME_to_jobject(%p) => Email/DNS/URI invalid", gen); + return nullptr; + } + // Converting ASCII to UTF-16 is the identity function. + jchars.push_back(data[i]); + } + JNI_TRACE("GENERAL_NAME_to_jobject(%p)=> Email/DNS/URI \"%.*s\"", gen, (int)len, data); + return env->NewString(jchars.data(), jchars.size()); + } + case GEN_DIRNAME: + /* Write in RFC 2253 format */ + return X509_NAME_to_jstring(env, gen->d.directoryName, XN_FLAG_RFC2253); + case GEN_IPADD: { + const uint8_t* ip = ASN1_STRING_get0_data(gen->d.ip); + int ip_len = ASN1_STRING_length(gen->d.ip); + if (ip_len == 4) { + // IPv4 + std::unique_ptr buffer(new char[INET_ADDRSTRLEN]); + if (inet_ntop(AF_INET, ip, buffer.get(), INET_ADDRSTRLEN) != nullptr) { + JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv4 %s", gen, buffer.get()); + return env->NewStringUTF(buffer.get()); + } else { + JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv4 failed %s", gen, + strerror(errno)); + } + } else if (ip_len == 16) { + // IPv6 + std::unique_ptr buffer(new char[INET6_ADDRSTRLEN]); + if (inet_ntop(AF_INET6, ip, buffer.get(), INET6_ADDRSTRLEN) != nullptr) { + JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv6 %s", gen, buffer.get()); + return env->NewStringUTF(buffer.get()); + } else { + JNI_TRACE("GENERAL_NAME_to_jobject(%p) => IPv6 failed %s", gen, + strerror(errno)); + } + } + + /* Invalid IP encodings are pruned out without throwing an exception. */ + return nullptr; + } + case GEN_RID: + return ASN1_OBJECT_to_OID_string(env, gen->d.registeredID); + case GEN_OTHERNAME: + case GEN_X400: + default: + return ASN1ToByteArray(env, gen, i2d_GENERAL_NAME); } - bssl::UniquePtr asn1(OBJ_txt2obj(oid.c_str(), 1)); - if (asn1.get() == nullptr) { - JNI_TRACE("X509Type_get_ext(%p, %s) => oid conversion failed", x509Type, oid.c_str()); - ERR_clear_error(); + return nullptr; +} + +#define GN_STACK_SUBJECT_ALT_NAME 1 +#define GN_STACK_ISSUER_ALT_NAME 2 + +static jobjectArray NativeCrypto_get_X509_GENERAL_NAME_stack(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder, + jint type) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d)", x509, type); + + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => x509 == null", x509, type); return nullptr; } - int extIndex = get_ext_by_OBJ_func(x509Type, asn1.get(), -1); - if (extIndex == -1) { - JNI_TRACE("X509Type_get_ext(%p, %s) => ext not found", x509Type, oid.c_str()); + bssl::UniquePtr gn_stack; + if (type == GN_STACK_SUBJECT_ALT_NAME) { + gn_stack.reset(static_cast( + X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr))); + } else if (type == GN_STACK_ISSUER_ALT_NAME) { + gn_stack.reset(static_cast( + X509_get_ext_d2i(x509, NID_issuer_alt_name, nullptr, nullptr))); + } else { + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => unknown type", x509, type); + return nullptr; + } + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors, remove + // |ERR_clear_error|, and throw CertificateParsingException. + if (gn_stack == nullptr) { + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no extension or error)", x509, + type); + ERR_clear_error(); return nullptr; } - X509_EXTENSION* ext = get_ext_func(x509Type, extIndex); - JNI_TRACE("X509Type_get_ext(%p, %s) => %p", x509Type, oid.c_str(), ext); - return ext; -} - -template -static jbyteArray X509Type_get_ext_oid(JNIEnv* env, const T* x509Type, jstring oidString) { - X509_EXTENSION* ext = - X509Type_get_ext(env, x509Type, oidString); - if (ext == nullptr) { - JNI_TRACE("X509Type_get_ext_oid(%p, %p) => fetching extension failed", x509Type, oidString); + int count = static_cast(sk_GENERAL_NAME_num(gn_stack.get())); + if (count <= 0) { + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => null (no entries)", x509, type); return nullptr; } - JNI_TRACE("X509Type_get_ext_oid(%p, %p) => %p", x509Type, oidString, - X509_EXTENSION_get_data(ext)); - return ASN1ToByteArray(env, X509_EXTENSION_get_data(ext), - i2d_ASN1_OCTET_STRING); -} + /* + * Keep track of how many originally so we can ignore any invalid + * values later. + */ + const int origCount = count; -static jlong NativeCrypto_X509_CRL_get_ext(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder, jstring oid) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_get_ext(%p, %p)", crl, oid); + ScopedLocalRef joa( + env, env->NewObjectArray(count, conscrypt::jniutil::objectArrayClass, nullptr)); + for (int i = 0, j = 0; i < origCount; i++, j++) { + GENERAL_NAME* gen = sk_GENERAL_NAME_value(gn_stack.get(), static_cast(i)); + ScopedLocalRef val(env, GENERAL_NAME_to_jobject(env, gen)); + if (env->ExceptionCheck()) { + JNI_TRACE( + "get_X509_GENERAL_NAME_stack(%p, %d) => threw exception parsing gen " + "name", + x509, type); + return nullptr; + } - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_get_ext(%p) => crl == null", crl); - return 0; - } - X509_EXTENSION* ext = - X509Type_get_ext(env, crl, oid); - JNI_TRACE("X509_CRL_get_ext(%p, %p) => %p", crl, oid, ext); - return reinterpret_cast(ext); -} + /* + * If it's nullptr, we'll have to skip this, reduce the number of total + * entries, and fix up the array later. + */ + if (val.get() == nullptr) { + j--; + count--; + continue; + } -static jlong NativeCrypto_X509_REVOKED_get_ext(JNIEnv* env, jclass, jlong x509RevokedRef, - jstring oid, CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("X509_REVOKED_get_ext(%p, %p)", revoked, oid); - X509_EXTENSION* ext = - X509Type_get_ext( - env, revoked, oid); - JNI_TRACE("X509_REVOKED_get_ext(%p, %p) => %p", revoked, oid, ext); - return reinterpret_cast(ext); -} + ScopedLocalRef item( + env, env->NewObjectArray(2, conscrypt::jniutil::objectClass, nullptr)); -static jlong NativeCrypto_X509_REVOKED_dup(JNIEnv* env, jclass, jlong x509RevokedRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("X509_REVOKED_dup(%p)", revoked); + ScopedLocalRef parsedType( + env, + env->CallStaticObjectMethod(conscrypt::jniutil::integerClass, + conscrypt::jniutil::integer_valueOfMethod, gen->type)); + env->SetObjectArrayElement(item.get(), 0, parsedType.get()); + env->SetObjectArrayElement(item.get(), 1, val.get()); - if (revoked == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); - JNI_TRACE("X509_REVOKED_dup(%p) => revoked == null", revoked); - return 0; + env->SetObjectArrayElement(joa.get(), j, item.get()); } - X509_REVOKED* dup = X509_REVOKED_dup(revoked); - JNI_TRACE("X509_REVOKED_dup(%p) => %p", revoked, dup); - return reinterpret_cast(dup); -} + if (count == 0) { + JNI_TRACE( + "get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to 0; returning " + "nullptr", + x509, type, origCount); + joa.reset(nullptr); + } else if (origCount != count) { + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) shrunk from %d to %d", x509, type, origCount, + count); -static void NativeCrypto_X509_REVOKED_free(JNIEnv* env, jclass, jlong x509RevokedRef, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("X509_REVOKED_free(%p)", revoked); + ScopedLocalRef joa_copy( + env, env->NewObjectArray(count, conscrypt::jniutil::objectArrayClass, nullptr)); - if (revoked == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); - JNI_TRACE("X509_REVOKED_free(%p) => revoked == null", revoked); - return; + for (int i = 0; i < count; i++) { + ScopedLocalRef item(env, env->GetObjectArrayElement(joa.get(), i)); + env->SetObjectArrayElement(joa_copy.get(), i, item.get()); + } + + joa.reset(joa_copy.release()); } - X509_REVOKED_free(revoked); + JNI_TRACE("get_X509_GENERAL_NAME_stack(%p, %d) => %d entries", x509, type, count); + return joa.release(); } -static jlong NativeCrypto_get_X509_REVOKED_revocationDate(JNIEnv* env, jclass, jlong x509RevokedRef, - CONSCRYPT_UNUSED jobject holder) { +static jlong NativeCrypto_X509_get_notBefore(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("get_X509_REVOKED_revocationDate(%p)", revoked); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_notBefore(%p)", x509); - if (revoked == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); - JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => revoked == null", revoked); + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_get_notBefore(%p) => x509 == null", x509); return 0; } - JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => %p", revoked, - X509_REVOKED_get0_revocationDate(revoked)); - return reinterpret_cast(X509_REVOKED_get0_revocationDate(revoked)); + ASN1_TIME* notBefore = X509_get_notBefore(x509); + JNI_TRACE("X509_get_notBefore(%p) => %p", x509, notBefore); + return reinterpret_cast(notBefore); } -#ifdef __GNUC__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wwrite-strings" -#endif -static void NativeCrypto_X509_REVOKED_print(JNIEnv* env, jclass, jlong bioRef, jlong x509RevokedRef, +static jlong NativeCrypto_X509_get_notAfter(JNIEnv* env, jclass, jlong x509Ref, CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - BIO* bio = reinterpret_cast(static_cast(bioRef)); - X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("X509_REVOKED_print(%p, %p)", bio, revoked); - - if (bio == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "bio == null"); - JNI_TRACE("X509_REVOKED_print(%p, %p) => bio == null", bio, revoked); - return; - } + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_notAfter(%p)", x509); - if (revoked == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); - JNI_TRACE("X509_REVOKED_print(%p, %p) => revoked == null", bio, revoked); - return; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_get_notAfter(%p) => x509 == null", x509); + return 0; } - BIO_printf(bio, "Serial Number: "); - i2a_ASN1_INTEGER(bio, X509_REVOKED_get0_serialNumber(revoked)); - BIO_printf(bio, "\nRevocation Date: "); - ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(revoked)); - BIO_printf(bio, "\n"); - // TODO(davidben): Should the flags parameter be |X509V3_EXT_DUMP_UNKNOWN| so - // we don't error on unknown extensions. Alternatively, maybe we can use a - // simpler toString() implementation. - X509V3_extensions_print(bio, "CRL entry extensions", X509_REVOKED_get0_extensions(revoked), 0, - 0); + ASN1_TIME* notAfter = X509_get_notAfter(x509); + JNI_TRACE("X509_get_notAfter(%p) => %p", x509, notAfter); + return reinterpret_cast(notAfter); } -#ifndef _WIN32 -#pragma GCC diagnostic pop -#endif -static jbyteArray NativeCrypto_get_X509_CRL_crl_enc(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { +// NOLINTNEXTLINE(runtime/int) +static long NativeCrypto_X509_get_version(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("get_X509_CRL_crl_enc(%p)", crl); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_version(%p)", x509); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("get_X509_CRL_crl_enc(%p) => crl == null", crl); - return nullptr; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_get_version(%p) => x509 == null", x509); + return 0; } - return ASN1ToByteArray(env, crl, i2d_X509_CRL_tbs); + + // NOLINTNEXTLINE(runtime/int) + long version = X509_get_version(x509); + JNI_TRACE("X509_get_version(%p) => %ld", x509, version); + return version; } -static void NativeCrypto_X509_CRL_verify(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder, jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - JNI_TRACE("X509_CRL_verify(%p, %p)", crl, pkey); +template +static jbyteArray get_X509Type_serialNumber(JNIEnv* env, const T* x509Type, + const ASN1_INTEGER* (*get_serial_func)(const T*)) { + JNI_TRACE("get_X509Type_serialNumber(%p)", x509Type); - if (pkey == nullptr) { - JNI_TRACE("X509_CRL_verify(%p, %p) => pkey == null", crl, pkey); - return; + if (x509Type == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509Type == null"); + JNI_TRACE("get_X509Type_serialNumber(%p) => x509Type == null", x509Type); + return nullptr; } - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_verify(%p, %p) => crl == null", crl, pkey); - return; + const ASN1_INTEGER* serialNumber = get_serial_func(x509Type); + bssl::UniquePtr serialBn(ASN1_INTEGER_to_BN(serialNumber, nullptr)); + if (serialBn.get() == nullptr) { + JNI_TRACE("X509_get_serialNumber(%p) => threw exception", x509Type); + return nullptr; } - if (X509_CRL_verify(crl, pkey) != 1) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_CRL_verify"); - JNI_TRACE("X509_CRL_verify(%p, %p) => verify failure", crl, pkey); - return; + ScopedLocalRef serialArray(env, bignumToArray(env, serialBn.get(), "serialBn")); + if (env->ExceptionCheck()) { + JNI_TRACE("X509_get_serialNumber(%p) => threw exception", x509Type); + return nullptr; } - JNI_TRACE("X509_CRL_verify(%p, %p) => verify success", crl, pkey); + + JNI_TRACE("X509_get_serialNumber(%p) => %p", x509Type, serialArray.get()); + return serialArray.release(); } -static jlong NativeCrypto_X509_CRL_get_lastUpdate(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { +static jbyteArray NativeCrypto_X509_get_serialNumber(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_get_lastUpdate(%p)", crl); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_serialNumber(%p)", x509); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_get_lastUpdate(%p) => crl == null", crl); - return 0; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_get_serialNumber(%p) => x509 == null", x509); + return nullptr; } - - ASN1_TIME* lastUpdate = X509_CRL_get_lastUpdate(crl); - JNI_TRACE("X509_CRL_get_lastUpdate(%p) => %p", crl, lastUpdate); - return reinterpret_cast(lastUpdate); + return get_X509Type_serialNumber(env, x509, X509_get0_serialNumber); } -static jlong NativeCrypto_X509_CRL_get_nextUpdate(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder) { +static jbyteArray NativeCrypto_X509_REVOKED_get_serialNumber(JNIEnv* env, jclass, + jlong x509RevokedRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_get_nextUpdate(%p)", crl); + X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("X509_REVOKED_get_serialNumber(%p)", revoked); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_get_nextUpdate(%p) => crl == null", crl); + if (revoked == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); + JNI_TRACE("X509_REVOKED_get_serialNumber(%p) => revoked == null", revoked); return 0; } - - ASN1_TIME* nextUpdate = X509_CRL_get_nextUpdate(crl); - JNI_TRACE("X509_CRL_get_nextUpdate(%p) => %p", crl, nextUpdate); - return reinterpret_cast(nextUpdate); + return get_X509Type_serialNumber(env, revoked, X509_REVOKED_get0_serialNumber); } -static jbyteArray NativeCrypto_i2d_X509_REVOKED(JNIEnv* env, jclass, jlong x509RevokedRef) { +static void NativeCrypto_X509_verify(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_REVOKED* x509Revoked = - reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("i2d_X509_REVOKED(%p)", x509Revoked); - return ASN1ToByteArray(env, x509Revoked, i2d_X509_REVOKED); -} + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("X509_verify(%p, %p)", x509, pkey); -static jint NativeCrypto_X509_supported_extension(JNIEnv* env, jclass, jlong x509ExtensionRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509_EXTENSION* ext = - reinterpret_cast(static_cast(x509ExtensionRef)); + if (pkey == nullptr) { + JNI_TRACE("X509_verify(%p, %p) => pkey == null", x509, pkey); + return; + } - if (ext == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "ext == null"); - return 0; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_verify(%p, %p) => x509 == null", x509, pkey); + return; } - return X509_supported_extension(ext); + if (X509_verify(x509, pkey) != 1) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "X509_verify", conscrypt::jniutil::throwCertificateException); + JNI_TRACE("X509_verify(%p, %p) => verify failure", x509, pkey); + return; + } + JNI_TRACE("X509_verify(%p, %p) => verify success", x509, pkey); } -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 jbyteArray NativeCrypto_get_X509_tbs_cert(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_tbs_cert(%p)", x509); + // Note |i2d_X509_tbs| preserves the original encoding of the TBSCertificate. + return ASN1ToByteArray(env, x509, i2d_X509_tbs); } -static void NativeCrypto_ASN1_TIME_to_Calendar(JNIEnv* env, jclass, jlong asn1TimeRef, - jobject calendar) { +static jbyteArray NativeCrypto_get_X509_tbs_cert_without_ext(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder, + jstring oidString) { CHECK_ERROR_QUEUE_ON_RETURN; - ASN1_TIME* asn1Time = reinterpret_cast(static_cast(asn1TimeRef)); - JNI_TRACE("ASN1_TIME_to_Calendar(%p, %p)", asn1Time, calendar); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p)", x509, oidString); - if (asn1Time == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "asn1Time == null"); - return; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => x509 == null", x509, oidString); + return nullptr; } - if (!ASN1_TIME_check(asn1Time)) { - conscrypt::jniutil::throwParsingException(env, "Invalid date format"); - return; + bssl::UniquePtr copy(X509_dup(x509)); + if (copy == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_dup"); + JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => threw error", x509, oidString); + return nullptr; } - bssl::UniquePtr gen(ASN1_TIME_to_generalizedtime(asn1Time, nullptr)); - if (gen.get() == nullptr) { - conscrypt::jniutil::throwParsingException(env, - "ASN1_TIME_to_generalizedtime returned null"); - return; + ScopedUtfChars oid(env, oidString); + if (oid.c_str() == nullptr) { + JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %p) => oidString == null", x509, oidString); + return nullptr; } - 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; + bssl::UniquePtr obj(OBJ_txt2obj(oid.c_str(), 1 /* allow numerical form only */)); + if (obj.get() == nullptr) { + JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %s) => oid conversion failed", x509, + oid.c_str()); + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Invalid OID."); + ERR_clear_error(); + return nullptr; } - 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; + int extIndex = X509_get_ext_by_OBJ(copy.get(), obj.get(), -1); + if (extIndex == -1) { + JNI_TRACE("get_X509_tbs_cert_without_ext(%p, %s) => ext not found", x509, oid.c_str()); + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Extension not found."); + return nullptr; } - env->CallVoidMethod(calendar, conscrypt::jniutil::calendar_setMethod, year, mon - 1, mday, hour, - min, sec); + // Remove the extension and re-encode the TBSCertificate. Note + // |i2d_re_X509_tbs| ignores the cached encoding. + X509_EXTENSION_free(X509_delete_ext(copy.get(), extIndex)); + return ASN1ToByteArray(env, copy.get(), i2d_re_X509_tbs); } -// 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. -struct CbsHandle { - // A pointer to the CBS. - std::unique_ptr cbs; - // A pointer to the data held by the CBS. If the data held by the CBS - // is owned by a different CbsHandle, data will be null. - std::unique_ptr data; -}; - -static jlong NativeCrypto_asn1_read_init(JNIEnv* env, jclass, jbyteArray data) { +static jint NativeCrypto_get_X509_ex_flags(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("asn1_read_init(%p)", data); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_ex_flags(%p)", x509); - ScopedByteArrayRO bytes(env, data); - if (bytes.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_ex_flags(%p) => x509 == null", x509); return 0; } - std::unique_ptr cbs(new CbsHandle()); - cbs->data.reset(new unsigned char[bytes.size()]); - memcpy(cbs->data.get(), bytes.get(), bytes.size()); - - cbs->cbs.reset(new CBS()); - CBS_init(cbs->cbs.get(), cbs->data.get(), bytes.size()); - JNI_TRACE("asn1_read_init(%p) => %p", data, cbs.get()); - return reinterpret_cast(cbs.release()); + uint32_t flags = X509_get_extension_flags(x509); + // X509_get_extension_flags sometimes leaves values in the error queue. See + // https://crbug.com/boringssl/382. + // + // TODO(https://github.com/google/conscrypt/issues/916): This function is used + // to check EXFLAG_CA, but does not check EXFLAG_INVALID. Fold the two JNI + // calls in getBasicConstraints() together and handle errors. (See also + // NativeCrypto_get_X509_ex_pathlen.) From there, limit this JNI call to + // EXFLAG_CRITICAL. + ERR_clear_error(); + return flags; } -static jlong NativeCrypto_asn1_read_sequence(JNIEnv* env, jclass, jlong cbsRef) { +static jint NativeCrypto_X509_check_issued(JNIEnv* env, jclass, jlong x509Ref1, + CONSCRYPT_UNUSED jobject holder, jlong x509Ref2, + CONSCRYPT_UNUSED jobject holder2) { CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_sequence(%p)", cbs); + X509* x509_1 = reinterpret_cast(static_cast(x509Ref1)); + X509* x509_2 = reinterpret_cast(static_cast(x509Ref2)); + JNI_TRACE("X509_check_issued(%p, %p)", x509_1, x509_2); - std::unique_ptr seq(new CbsHandle()); - seq->cbs.reset(new CBS()); - if (!CBS_get_asn1(cbs->cbs.get(), seq->cbs.get(), CBS_ASN1_SEQUENCE)) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + if (x509_1 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509Ref1 == null"); + JNI_TRACE("X509_check_issued(%p, %p) => x509_1 == null", x509_1, x509_2); return 0; } - JNI_TRACE("asn1_read_sequence(%p) => %p", cbs, seq.get()); - return reinterpret_cast(seq.release()); + if (x509_2 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509Ref2 == null"); + JNI_TRACE("X509_check_issued(%p, %p) => x509_2 == null", x509_1, x509_2); + return 0; + } + int ret = X509_check_issued(x509_1, x509_2); + JNI_TRACE("X509_check_issued(%p, %p) => %d", x509_1, x509_2, ret); + return ret; } -static jboolean NativeCrypto_asn1_read_next_tag_is(CONSCRYPT_UNUSED JNIEnv* env, jclass, - jlong cbsRef, jint tag) { - CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_next_tag_is(%p)", cbs); +static const ASN1_BIT_STRING* get_X509_signature(X509* x509) { + const ASN1_BIT_STRING* signature; + X509_get0_signature(&signature, nullptr, x509); + return signature; +} - int result = CBS_peek_asn1_tag(cbs->cbs.get(), - CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | tag); - JNI_TRACE("asn1_read_next_tag_is(%p) => %s", cbs, result ? "true" : "false"); - return result ? JNI_TRUE : JNI_FALSE; +static const ASN1_BIT_STRING* get_X509_CRL_signature(X509_CRL* crl) { + const ASN1_BIT_STRING* signature; + X509_CRL_get0_signature(crl, &signature, nullptr); + return signature; } -static jlong NativeCrypto_asn1_read_tagged(JNIEnv* env, jclass, jlong cbsRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_tagged(%p)", cbs); +template +static jbyteArray get_X509Type_signature(JNIEnv* env, T* x509Type, + const ASN1_BIT_STRING* (*get_signature_func)(T*)) { + JNI_TRACE("get_X509Type_signature(%p)", x509Type); - std::unique_ptr tag(new CbsHandle()); - tag->cbs.reset(new CBS()); - if (!CBS_get_any_asn1(cbs->cbs.get(), tag->cbs.get(), nullptr)) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); - return 0; + if (x509Type == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509Type == null"); + JNI_TRACE("get_X509Type_signature(%p) => x509Type == null", x509Type); + return nullptr; } - JNI_TRACE("asn1_read_tagged(%p) => %p", cbs, tag.get()); - return reinterpret_cast(tag.release()); -} -static jbyteArray NativeCrypto_asn1_read_octetstring(JNIEnv* env, jclass, jlong cbsRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_octetstring(%p)", cbs); + const ASN1_BIT_STRING* signature = get_signature_func(x509Type); - std::unique_ptr str(new CBS()); - if (!CBS_get_asn1(cbs->cbs.get(), str.get(), CBS_ASN1_OCTETSTRING)) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); - return 0; - } - ScopedLocalRef out(env, env->NewByteArray(static_cast(CBS_len(str.get())))); - if (out.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); - return 0; + ScopedLocalRef signatureArray(env, + env->NewByteArray(ASN1_STRING_length(signature))); + if (env->ExceptionCheck()) { + JNI_TRACE("get_X509Type_signature(%p) => threw exception", x509Type); + return nullptr; } - ScopedByteArrayRW outBytes(env, out.get()); - if (outBytes.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); - return 0; + + ScopedByteArrayRW signatureBytes(env, signatureArray.get()); + if (signatureBytes.get() == nullptr) { + JNI_TRACE("get_X509Type_signature(%p) => using byte array failed", x509Type); + return nullptr; } - memcpy(outBytes.get(), CBS_data(str.get()), CBS_len(str.get())); - JNI_TRACE("asn1_read_octetstring(%p) => %p", cbs, out.get()); - return out.release(); -} -static jlong NativeCrypto_asn1_read_uint64(JNIEnv* env, jclass, jlong cbsRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_uint64(%p)", cbs); + memcpy(signatureBytes.get(), ASN1_STRING_get0_data(signature), ASN1_STRING_length(signature)); - // NOLINTNEXTLINE(runtime/int) - uint64_t value; - if (!CBS_get_asn1_uint64(cbs->cbs.get(), &value)) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); - return 0; - } - return value; + JNI_TRACE("get_X509Type_signature(%p) => %p (%d bytes)", x509Type, signatureArray.get(), + ASN1_STRING_length(signature)); + return signatureArray.release(); } -static void NativeCrypto_asn1_read_null(JNIEnv* env, jclass, jlong cbsRef) { +static jbyteArray NativeCrypto_get_X509_signature(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_null(%p)", cbs); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_signature(%p)", x509); - CBS null_holder; - if (!CBS_get_asn1(cbs->cbs.get(), &null_holder, CBS_ASN1_NULL)) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_signature(%p) => x509 == null", x509); + return nullptr; } + return get_X509Type_signature(env, x509, get_X509_signature); } -static jstring NativeCrypto_asn1_read_oid(JNIEnv* env, jclass, jlong cbsRef) { +static jbyteArray NativeCrypto_get_X509_CRL_signature(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_oid(%p)", cbs); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("get_X509_CRL_signature(%p)", crl); - CBS oid_cbs; - if (!CBS_get_asn1(cbs->cbs.get(), &oid_cbs, CBS_ASN1_OBJECT)) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); - return nullptr; - } - int nid = OBJ_cbs2nid(&oid_cbs); - if (nid == NID_undef) { - conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding: OID not found"); - return nullptr; - } - const ASN1_OBJECT* obj(OBJ_nid2obj(nid)); - if (obj == nullptr) { - conscrypt::jniutil::throwIOException(env, - "Error reading ASN.1 encoding: " - "Could not find ASN1_OBJECT for NID"); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_signature(%p) => crl == null", crl); return nullptr; } - return ASN1_OBJECT_to_OID_string(env, obj); + return get_X509Type_signature(env, crl, get_X509_CRL_signature); } -static jboolean NativeCrypto_asn1_read_is_empty(CONSCRYPT_UNUSED JNIEnv* env, jclass, - jlong cbsRef) { +static jlong NativeCrypto_X509_CRL_get0_by_cert(JNIEnv* env, jclass, jlong x509crlRef, + CONSCRYPT_UNUSED jobject holder, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder2) { CHECK_ERROR_QUEUE_ON_RETURN; - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_is_empty(%p)", cbs); - - bool empty = (CBS_len(cbs->cbs.get()) == 0); - JNI_TRACE("asn1_read_is_empty(%p) => %s", cbs, empty ? "true" : "false"); - return empty; -} + X509_CRL* x509crl = reinterpret_cast(static_cast(x509crlRef)); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_CRL_get0_by_cert(%p, %p)", x509crl, x509); -static void NativeCrypto_asn1_read_free(CONSCRYPT_UNUSED JNIEnv* env, jclass, jlong cbsRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - if (cbsRef == 0) { - JNI_TRACE("asn1_read_free(0)"); - return; + if (x509crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509crl == null"); + JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => x509crl == null", x509crl, x509); + return 0; + } else if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => x509 == null", x509crl, x509); + return 0; } - CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); - JNI_TRACE("asn1_read_free(%p)", cbs); - delete cbs; -} -static jlong NativeCrypto_asn1_write_init(JNIEnv* env, jclass) { - CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("asn1_write_init"); - std::unique_ptr cbb(new CBB()); - if (!CBB_init(cbb.get(), 128)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + X509_REVOKED* revoked = nullptr; + int ret = X509_CRL_get0_by_cert(x509crl, &revoked, x509); + if (ret == 0) { + JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => none", x509crl, x509); return 0; } - JNI_TRACE("asn1_write_init => %p", cbb.get()); - return reinterpret_cast(cbb.release()); + + JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => %p", x509crl, x509, revoked); + return reinterpret_cast(revoked); } -static jlong NativeCrypto_asn1_write_sequence(JNIEnv* env, jclass, jlong cbbRef) { +static jlong NativeCrypto_X509_CRL_get0_by_serial(JNIEnv* env, jclass, jlong x509crlRef, + CONSCRYPT_UNUSED jobject holder, + jbyteArray serialArray) { CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_sequence(%p)", cbb); + X509_CRL* x509crl = reinterpret_cast(static_cast(x509crlRef)); + JNI_TRACE("X509_CRL_get0_by_serial(%p, %p)", x509crl, serialArray); - std::unique_ptr seq(new CBB()); - if (!CBB_add_asn1(cbb, seq.get(), CBS_ASN1_SEQUENCE)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + if (x509crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509crl == null"); + JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => crl == null", x509crl, serialArray); return 0; } - JNI_TRACE("asn1_write_sequence(%p) => %p", cbb, seq.get()); - return reinterpret_cast(seq.release()); -} -static jlong NativeCrypto_asn1_write_tag(JNIEnv* env, jclass, jlong cbbRef, jint tag) { - CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_tag(%p)", cbb); + bssl::UniquePtr serialBn = arrayToBignum(env, serialArray); + if (serialBn == nullptr) { + if (!env->ExceptionCheck()) { + conscrypt::jniutil::throwNullPointerException(env, "serial == null"); + } + JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => BN conversion failed", x509crl, serialArray); + return 0; + } - std::unique_ptr tag_holder(new CBB()); - if (!CBB_add_asn1(cbb, tag_holder.get(), - CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | tag)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + bssl::UniquePtr serialInteger(BN_to_ASN1_INTEGER(serialBn.get(), nullptr)); + if (serialInteger.get() == nullptr) { + JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => BN conversion failed", x509crl, serialArray); return 0; } - JNI_TRACE("asn1_write_tag(%p) => %p", cbb, tag_holder.get()); - return reinterpret_cast(tag_holder.release()); + + X509_REVOKED* revoked = nullptr; + int ret = X509_CRL_get0_by_serial(x509crl, &revoked, serialInteger.get()); + if (ret == 0) { + JNI_TRACE("X509_CRL_get0_by_serial(%p, %p) => none", x509crl, serialArray); + return 0; + } + + JNI_TRACE("X509_CRL_get0_by_cert(%p, %p) => %p", x509crl, serialArray, revoked); + return reinterpret_cast(revoked); } -static void NativeCrypto_asn1_write_octetstring(JNIEnv* env, jclass, jlong cbbRef, - jbyteArray data) { +static jlongArray NativeCrypto_X509_CRL_get_REVOKED(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_octetstring(%p, %p)", cbb, data); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_get_REVOKED(%p)", crl); - ScopedByteArrayRO bytes(env, data); - if (bytes.get() == nullptr) { - JNI_TRACE("asn1_write_octetstring(%p, %p) => using byte array failed", cbb, data); - return; + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_get_REVOKED(%p) => crl == null", crl); + return nullptr; } - std::unique_ptr octetstring(new CBB()); - if (!CBB_add_asn1(cbb, octetstring.get(), CBS_ASN1_OCTETSTRING)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return; - } - if (!CBB_add_bytes(octetstring.get(), reinterpret_cast(bytes.get()), - bytes.size())) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return; + STACK_OF(X509_REVOKED)* stack = X509_CRL_get_REVOKED(crl); + if (stack == nullptr) { + JNI_TRACE("X509_CRL_get_REVOKED(%p) => stack is null", crl); + return nullptr; } - if (!CBB_flush(cbb)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return; + + size_t size = sk_X509_REVOKED_num(stack); + + ScopedLocalRef revokedArray(env, env->NewLongArray(static_cast(size))); + ScopedLongArrayRW revoked(env, revokedArray.get()); + for (size_t i = 0; i < size; i++) { + X509_REVOKED* item = reinterpret_cast(sk_X509_REVOKED_value(stack, i)); + revoked[i] = reinterpret_cast(X509_REVOKED_dup(item)); } + + JNI_TRACE("X509_CRL_get_REVOKED(%p) => %p [size=%zd]", stack, revokedArray.get(), size); + return revokedArray.release(); } -static void NativeCrypto_asn1_write_uint64(JNIEnv* env, jclass, jlong cbbRef, jlong data) { +static jbyteArray NativeCrypto_i2d_X509_CRL(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_uint64(%p)", cbb); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("i2d_X509_CRL(%p)", crl); - if (!CBB_add_asn1_uint64(cbb, static_cast(data))) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return; + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("i2d_X509_CRL(%p) => crl == null", crl); + return nullptr; } + return ASN1ToByteArray(env, crl, i2d_X509_CRL); } -static void NativeCrypto_asn1_write_null(JNIEnv* env, jclass, jlong cbbRef) { +static void NativeCrypto_X509_CRL_free(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_null(%p)", cbb); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_free(%p)", crl); - CBB null_holder; - if (!CBB_add_asn1(cbb, &null_holder, CBS_ASN1_NULL)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return; - } - if (!CBB_flush(cbb)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_free(%p) => crl == null", crl); return; } + + X509_CRL_free(crl); } -static void NativeCrypto_asn1_write_oid(JNIEnv* env, jclass, jlong cbbRef, jstring oid) { +static void NativeCrypto_X509_CRL_print(JNIEnv* env, jclass, jlong bioRef, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_oid(%p)", cbb); + BIO* bio = reinterpret_cast(static_cast(bioRef)); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_print(%p, %p)", bio, crl); - ScopedUtfChars oid_chars(env, oid); - if (oid_chars.c_str() == nullptr) { + if (bio == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "bio == null"); + JNI_TRACE("X509_CRL_print(%p, %p) => bio == null", bio, crl); return; } - int nid = OBJ_txt2nid(oid_chars.c_str()); - if (nid == NID_undef) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_print(%p, %p) => crl == null", bio, crl); return; } - if (!OBJ_nid2cbb(cbb, nid)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + if (!X509_CRL_print(bio, crl)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_CRL_print"); + JNI_TRACE("X509_CRL_print(%p, %p) => threw error", bio, crl); return; } + JNI_TRACE("X509_CRL_print(%p, %p) => success", bio, crl); } -static void NativeCrypto_asn1_write_flush(JNIEnv* env, jclass, jlong cbbRef) { +static jstring NativeCrypto_get_X509_CRL_sig_alg_oid(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_flush(%p)", cbb); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("get_X509_CRL_sig_alg_oid(%p)", crl); - if (!CBB_flush(cbb)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return; + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("get_X509_CRL_sig_alg_oid(%p) => crl == null", crl); + return nullptr; } + + const X509_ALGOR* sig_alg; + X509_CRL_get0_signature(crl, nullptr, &sig_alg); + const ASN1_OBJECT* oid; + X509_ALGOR_get0(&oid, nullptr, nullptr, sig_alg); + return ASN1_OBJECT_to_OID_string(env, oid); } -static jbyteArray NativeCrypto_asn1_write_finish(JNIEnv* env, jclass, jlong cbbRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_finish(%p)", cbb); +static jbyteArray get_X509_ALGOR_parameter(JNIEnv* env, const X509_ALGOR* algor) { + int param_type; + const void* param_value; + X509_ALGOR_get0(nullptr, ¶m_type, ¶m_value, algor); - uint8_t* data; - size_t data_len; - if (!CBB_finish(cbb, &data, &data_len)) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return 0; - } - bssl::UniquePtr data_storage(data); - ScopedLocalRef out(env, env->NewByteArray(static_cast(data_len))); - if (out.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return 0; + if (param_type == V_ASN1_UNDEF) { + JNI_TRACE("get_X509_ALGOR_parameter(%p) => no parameters", algor); + return nullptr; } - ScopedByteArrayRW outBytes(env, out.get()); - if (outBytes.get() == nullptr) { - conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); - return 0; + + // The OpenSSL 1.1.x API lacks a function to get the ASN1_TYPE out of + // X509_ALGOR directly, so recreate it from the returned components. + bssl::UniquePtr param(ASN1_TYPE_new()); + if (!param || !ASN1_TYPE_set1(param.get(), param_type, param_value)) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to serialize parameter"); + ERR_clear_error(); + return nullptr; } - memcpy(outBytes.get(), data, data_len); - return out.release(); + + return ASN1ToByteArray(env, param.get(), i2d_ASN1_TYPE); } -static void NativeCrypto_asn1_write_cleanup(CONSCRYPT_UNUSED JNIEnv* env, jclass, jlong cbbRef) { +static jbyteArray NativeCrypto_get_X509_CRL_sig_alg_parameter(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_cleanup(%p)", cbb); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p)", crl); - CBB_cleanup(cbb); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("get_X509_CRL_sig_alg_parameter(%p) => crl == null", crl); + return nullptr; + } + + const X509_ALGOR* sig_alg; + X509_CRL_get0_signature(crl, nullptr, &sig_alg); + return get_X509_ALGOR_parameter(env, sig_alg); } -static void NativeCrypto_asn1_write_free(CONSCRYPT_UNUSED JNIEnv* env, jclass, jlong cbbRef) { +static jbyteArray NativeCrypto_X509_CRL_get_issuer_name(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - if (cbbRef == 0) { - JNI_TRACE("asn1_write_free(0)"); - return; + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_get_issuer_name(%p) => crl == null", crl); + return nullptr; } - CBB* cbb = reinterpret_cast(static_cast(cbbRef)); - JNI_TRACE("asn1_write_free(%p)", cbb); - delete cbb; + JNI_TRACE("X509_CRL_get_issuer_name(%p)", crl); + return ASN1ToByteArray(env, X509_CRL_get_issuer(crl), i2d_X509_NAME); } -template -static jlong d2i_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) { - BIO* bio = to_BIO(env, bioRef); - JNI_TRACE("d2i_ASN1Object_to_jlong(%p)", bio); - - if (bio == nullptr) { - return 0; - } +// NOLINTNEXTLINE(runtime/int) +static long NativeCrypto_X509_CRL_get_version(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_get_version(%p)", crl); - T* x = d2i_func(bio, nullptr); - if (x == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "d2i_ASN1Object_to_jlong"); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_get_version(%p) => crl == null", crl); return 0; } - - return reinterpret_cast(x); + // NOLINTNEXTLINE(runtime/int) + long version = X509_CRL_get_version(crl); + JNI_TRACE("X509_CRL_get_version(%p) => %ld", crl, version); + return version; } -static jlong NativeCrypto_d2i_X509_CRL_bio(JNIEnv* env, jclass, jlong bioRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - return d2i_ASN1Object_to_jlong(env, bioRef); -} +template +static X509_EXTENSION* X509Type_get_ext(JNIEnv* env, const T* x509Type, jstring oidString) { + JNI_TRACE("X509Type_get_ext(%p)", x509Type); -static jlong NativeCrypto_d2i_X509_bio(JNIEnv* env, jclass, jlong bioRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - return d2i_ASN1Object_to_jlong(env, bioRef); -} - -static jlong NativeCrypto_d2i_X509(JNIEnv* env, jclass, jbyteArray certBytes) { - CHECK_ERROR_QUEUE_ON_RETURN; - ScopedByteArrayRO bytes(env, certBytes); - if (bytes.get() == nullptr) { - JNI_TRACE("NativeCrypto_d2i_X509(%p) => using byte array failed", certBytes); - return 0; + if (x509Type == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + return nullptr; } - const unsigned char* tmp = reinterpret_cast(bytes.get()); - // NOLINTNEXTLINE(runtime/int) - X509* x = d2i_X509(nullptr, &tmp, static_cast(bytes.size())); - if (x == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "Error reading X.509 data", conscrypt::jniutil::throwParsingException); - return 0; + ScopedUtfChars oid(env, oidString); + if (oid.c_str() == nullptr) { + return nullptr; } - return reinterpret_cast(x); -} -static jbyteArray NativeCrypto_i2d_X509(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("i2d_X509(%p)", x509); + bssl::UniquePtr asn1(OBJ_txt2obj(oid.c_str(), 1)); + if (asn1.get() == nullptr) { + JNI_TRACE("X509Type_get_ext(%p, %s) => oid conversion failed", x509Type, oid.c_str()); + ERR_clear_error(); + return nullptr; + } - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("i2d_X509(%p) => x509 == null", x509); + int extIndex = get_ext_by_OBJ_func(x509Type, asn1.get(), -1); + if (extIndex == -1) { + JNI_TRACE("X509Type_get_ext(%p, %s) => ext not found", x509Type, oid.c_str()); return nullptr; } - return ASN1ToByteArray(env, x509, i2d_X509); -} -static jbyteArray NativeCrypto_i2d_X509_PUBKEY(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("i2d_X509_PUBKEY(%p)", x509); + X509_EXTENSION* ext = get_ext_func(x509Type, extIndex); + JNI_TRACE("X509Type_get_ext(%p, %s) => %p", x509Type, oid.c_str(), ext); + return ext; +} - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("i2d_X509_PUBKEY(%p) => x509 == null", x509); +template +static jbyteArray X509Type_get_ext_oid(JNIEnv* env, const T* x509Type, jstring oidString) { + X509_EXTENSION* ext = + X509Type_get_ext(env, x509Type, oidString); + if (ext == nullptr) { + JNI_TRACE("X509Type_get_ext_oid(%p, %p) => fetching extension failed", x509Type, oidString); return nullptr; } - return ASN1ToByteArray(env, X509_get_X509_PUBKEY(x509), i2d_X509_PUBKEY); -} -template -static jlong PEM_to_jlong(JNIEnv* env, jlong bioRef) { - BIO* bio = to_BIO(env, bioRef); - JNI_TRACE("PEM_to_jlong(%p)", bio); + JNI_TRACE("X509Type_get_ext_oid(%p, %p) => %p", x509Type, oidString, + X509_EXTENSION_get_data(ext)); + return ASN1ToByteArray(env, X509_EXTENSION_get_data(ext), + i2d_ASN1_OCTET_STRING); +} - if (bio == nullptr) { - JNI_TRACE("PEM_to_jlong(%p) => bio == null", bio); - return 0; - } +static jlong NativeCrypto_X509_CRL_get_ext(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder, jstring oid) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_get_ext(%p, %p)", crl, oid); - T* x = PEM_read_func(bio, nullptr, nullptr, nullptr); - if (x == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PEM_to_jlong"); - JNI_TRACE("PEM_to_jlong(%p) => threw exception", bio); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_get_ext(%p) => crl == null", crl); return 0; } - - JNI_TRACE("PEM_to_jlong(%p) => %p", bio, x); - return reinterpret_cast(x); + X509_EXTENSION* ext = + X509Type_get_ext(env, crl, oid); + JNI_TRACE("X509_CRL_get_ext(%p, %p) => %p", crl, oid, ext); + return reinterpret_cast(ext); } -static jlong NativeCrypto_PEM_read_bio_X509(JNIEnv* env, jclass, jlong bioRef) { +static jlong NativeCrypto_X509_REVOKED_get_ext(JNIEnv* env, jclass, jlong x509RevokedRef, + jstring oid, CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("PEM_read_bio_X509(0x%llx)", (long long)bioRef); - return PEM_to_jlong(env, bioRef); + X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("X509_REVOKED_get_ext(%p, %p)", revoked, oid); + X509_EXTENSION* ext = + X509Type_get_ext( + env, revoked, oid); + JNI_TRACE("X509_REVOKED_get_ext(%p, %p) => %p", revoked, oid, ext); + return reinterpret_cast(ext); } -static jlong NativeCrypto_PEM_read_bio_X509_CRL(JNIEnv* env, jclass, jlong bioRef) { +static jlong NativeCrypto_X509_REVOKED_dup(JNIEnv* env, jclass, jlong x509RevokedRef) { CHECK_ERROR_QUEUE_ON_RETURN; - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("PEM_read_bio_X509_CRL(0x%llx)", (long long)bioRef); - return PEM_to_jlong(env, bioRef); -} + X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("X509_REVOKED_dup(%p)", revoked); -static jlong NativeCrypto_PEM_read_bio_PUBKEY(JNIEnv* env, jclass, jlong bioRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("PEM_read_bio_PUBKEY(0x%llx)", (long long)bioRef); - return PEM_to_jlong(env, bioRef); -} + if (revoked == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); + JNI_TRACE("X509_REVOKED_dup(%p) => revoked == null", revoked); + return 0; + } -static jlong NativeCrypto_PEM_read_bio_PrivateKey(JNIEnv* env, jclass, jlong bioRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("PEM_read_bio_PrivateKey(0x%llx)", (long long)bioRef); - return PEM_to_jlong(env, bioRef); + X509_REVOKED* dup = X509_REVOKED_dup(revoked); + JNI_TRACE("X509_REVOKED_dup(%p) => %p", revoked, dup); + return reinterpret_cast(dup); } -static jlongArray X509s_to_ItemArray(JNIEnv* env, STACK_OF(X509)* certs) { - if (certs == nullptr) { - return nullptr; - } +static void NativeCrypto_X509_REVOKED_free(JNIEnv* env, jclass, jlong x509RevokedRef, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("X509_REVOKED_free(%p)", revoked); - ScopedLocalRef ref_array(env, nullptr); - size_t size = sk_X509_num(certs); - ref_array.reset(env->NewLongArray(size)); - ScopedLongArrayRW items(env, ref_array.get()); - for (size_t i = 0; i < size; i++) { - X509* cert = sk_X509_value(certs, i); - X509_up_ref(cert); - items[i] = reinterpret_cast(cert); + if (revoked == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); + JNI_TRACE("X509_REVOKED_free(%p) => revoked == null", revoked); + return; } - JNI_TRACE("X509s_to_ItemArray(%p) => %p [size=%zd]", certs, ref_array.get(), size); - return ref_array.release(); + X509_REVOKED_free(revoked); } -static jlongArray X509_CRLs_to_ItemArray(JNIEnv* env, STACK_OF(X509_CRL)* crls) { - if (crls == nullptr) { - return nullptr; - } +static jlong NativeCrypto_get_X509_REVOKED_revocationDate(JNIEnv* env, jclass, jlong x509RevokedRef, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("get_X509_REVOKED_revocationDate(%p)", revoked); - ScopedLocalRef ref_array(env, nullptr); - size_t size = sk_X509_CRL_num(crls); - ref_array.reset(env->NewLongArray(size)); - ScopedLongArrayRW items(env, ref_array.get()); - for (size_t i = 0; i < size; i++) { - X509_CRL* crl = sk_X509_CRL_value(crls, i); - X509_CRL_up_ref(crl); - items[i] = reinterpret_cast(crl); + if (revoked == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); + JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => revoked == null", revoked); + return 0; } - JNI_TRACE("X509_CRLs_to_ItemArray(%p) => %p [size=%zd]", crls, ref_array.get(), size); - return ref_array.release(); + JNI_TRACE("get_X509_REVOKED_revocationDate(%p) => %p", revoked, + X509_REVOKED_get0_revocationDate(revoked)); + return reinterpret_cast(X509_REVOKED_get0_revocationDate(revoked)); } -#define PKCS7_CERTS 1 -#define PKCS7_CRLS 2 - -static jbyteArray NativeCrypto_i2d_PKCS7(JNIEnv* env, jclass, jlongArray certsArray) { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wwrite-strings" +#endif +static void NativeCrypto_X509_REVOKED_print(JNIEnv* env, jclass, jlong bioRef, jlong x509RevokedRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - STACK_OF(X509)* stack = sk_X509_new_null(); + BIO* bio = reinterpret_cast(static_cast(bioRef)); + X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("X509_REVOKED_print(%p, %p)", bio, revoked); - ScopedLongArrayRO certs(env, certsArray); - for (size_t i = 0; i < certs.size(); i++) { - X509* item = reinterpret_cast(certs[i]); - if (sk_X509_push(stack, item) == 0) { - sk_X509_free(stack); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "sk_X509_push"); - return nullptr; - } + if (bio == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "bio == null"); + JNI_TRACE("X509_REVOKED_print(%p, %p) => bio == null", bio, revoked); + return; } - bssl::ScopedCBB out; - CBB_init(out.get(), 1024 * certs.size()); - if (!PKCS7_bundle_certificates(out.get(), stack)) { - sk_X509_free(stack); - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_bundle_certificates"); - return nullptr; + if (revoked == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); + JNI_TRACE("X509_REVOKED_print(%p, %p) => revoked == null", bio, revoked); + return; } - sk_X509_free(stack); - - return CBBToByteArray(env, out.get()); + BIO_printf(bio, "Serial Number: "); + i2a_ASN1_INTEGER(bio, X509_REVOKED_get0_serialNumber(revoked)); + BIO_printf(bio, "\nRevocation Date: "); + ASN1_TIME_print(bio, X509_REVOKED_get0_revocationDate(revoked)); + BIO_printf(bio, "\n"); + // TODO(davidben): Should the flags parameter be |X509V3_EXT_DUMP_UNKNOWN| so + // we don't error on unknown extensions. Alternatively, maybe we can use a + // simpler toString() implementation. + X509V3_extensions_print(bio, "CRL entry extensions", X509_REVOKED_get0_extensions(revoked), 0, + 0); } +#ifndef _WIN32 +#pragma GCC diagnostic pop +#endif -static jlongArray NativeCrypto_PEM_read_bio_PKCS7(JNIEnv* env, jclass, jlong bioRef, jint which) { +static jbyteArray NativeCrypto_get_X509_CRL_crl_enc(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - BIO* bio = to_BIO(env, bioRef); - JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p)", bio); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("get_X509_CRL_crl_enc(%p)", crl); - if (bio == nullptr) { - JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p) => bio == null", bio); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("get_X509_CRL_crl_enc(%p) => crl == null", crl); return nullptr; } + return ASN1ToByteArray(env, crl, i2d_X509_CRL_tbs); +} - if (which == PKCS7_CERTS) { - bssl::UniquePtr outCerts(sk_X509_new_null()); - if (!PKCS7_get_PEM_certificates(outCerts.get(), bio)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_get_PEM_certificates"); - return nullptr; - } - return X509s_to_ItemArray(env, outCerts.get()); - } else if (which == PKCS7_CRLS) { - bssl::UniquePtr outCRLs(sk_X509_CRL_new_null()); - if (!PKCS7_get_PEM_CRLs(outCRLs.get(), bio)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_get_PEM_CRLs"); - return nullptr; - } - return X509_CRLs_to_ItemArray(env, outCRLs.get()); - } else { - conscrypt::jniutil::throwRuntimeException(env, "unknown PKCS7 field"); - return nullptr; - } -} - -static jlongArray NativeCrypto_d2i_PKCS7_bio(JNIEnv* env, jclass, jlong bioRef, jint which) { +static void NativeCrypto_X509_CRL_verify(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder, jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; - BIO* bio = to_BIO(env, bioRef); - JNI_TRACE("d2i_PKCS7_bio(%p, %d)", bio, which); + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + JNI_TRACE("X509_CRL_verify(%p, %p)", crl, pkey); - if (bio == nullptr) { - JNI_TRACE("d2i_PKCS7_bio(%p, %d) => bio == null", bio, which); - return nullptr; + if (pkey == nullptr) { + JNI_TRACE("X509_CRL_verify(%p, %p) => pkey == null", crl, pkey); + return; } - uint8_t* data; - size_t len; - if (!BIO_read_asn1(bio, &data, &len, 256 * 1024 * 1024 /* max length, 256MB for sanity */)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "Error reading PKCS#7 data", conscrypt::jniutil::throwParsingException); - JNI_TRACE("d2i_PKCS7_bio(%p, %d) => error reading BIO", bio, which); - return nullptr; + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_verify(%p, %p) => crl == null", crl, pkey); + return; } - bssl::UniquePtr data_storage(data); - CBS cbs; - CBS_init(&cbs, data, len); - - if (which == PKCS7_CERTS) { - bssl::UniquePtr outCerts(sk_X509_new_null()); - if (!PKCS7_get_certificates(outCerts.get(), &cbs)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "PKCS7_get_certificates", conscrypt::jniutil::throwParsingException); - JNI_TRACE("d2i_PKCS7_bio(%p, %d) => error reading certs", bio, which); - return nullptr; - } - JNI_TRACE("d2i_PKCS7_bio(%p, %d) => success certs", bio, which); - return X509s_to_ItemArray(env, outCerts.get()); - } else if (which == PKCS7_CRLS) { - bssl::UniquePtr outCRLs(sk_X509_CRL_new_null()); - if (!PKCS7_get_CRLs(outCRLs.get(), &cbs)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "PKCS7_get_CRLs", conscrypt::jniutil::throwParsingException); - JNI_TRACE("d2i_PKCS7_bio(%p, %d) => error reading CRLs", bio, which); - return nullptr; - } - JNI_TRACE("d2i_PKCS7_bio(%p, %d) => success CRLs", bio, which); - return X509_CRLs_to_ItemArray(env, outCRLs.get()); - } else { - conscrypt::jniutil::throwRuntimeException(env, "unknown PKCS7 field"); - return nullptr; + if (X509_CRL_verify(crl, pkey) != 1) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_CRL_verify"); + JNI_TRACE("X509_CRL_verify(%p, %p) => verify failure", crl, pkey); + return; } + JNI_TRACE("X509_CRL_verify(%p, %p) => verify success", crl, pkey); } -static jlongArray NativeCrypto_ASN1_seq_unpack_X509_bio(JNIEnv* env, jclass, jlong bioRef) { +static jlong NativeCrypto_X509_CRL_get_lastUpdate(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - BIO* bio = to_BIO(env, bioRef); - JNI_TRACE("ASN1_seq_unpack_X509_bio(%p)", bio); - - if (bio == nullptr) { - JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => bio == null", bio); - return nullptr; - } - - uint8_t* data; - size_t len; - if (!BIO_read_asn1(bio, &data, &len, 256 * 1024 * 1024 /* max length, 256MB for sanity */)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "Error reading X.509 data", conscrypt::jniutil::throwParsingException); - JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => error reading BIO", bio); - return nullptr; - } - bssl::UniquePtr data_storage(data); - - bssl::UniquePtr path(sk_X509_new_null()); - if (path.get() == nullptr) { - JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => failed to make cert stack", bio); - return nullptr; - } + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_get_lastUpdate(%p)", crl); - CBS cbs, sequence; - CBS_init(&cbs, data, len); - if (!CBS_get_asn1(&cbs, &sequence, CBS_ASN1_SEQUENCE)) { - conscrypt::jniutil::throwParsingException(env, "Error reading X.509 data"); - ERR_clear_error(); - return nullptr; + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_get_lastUpdate(%p) => crl == null", crl); + return 0; } - while (CBS_len(&sequence) > 0) { - CBS child; - if (!CBS_get_asn1_element(&sequence, &child, CBS_ASN1_SEQUENCE)) { - conscrypt::jniutil::throwParsingException(env, "Error reading X.509 data"); - ERR_clear_error(); - return nullptr; - } + ASN1_TIME* lastUpdate = X509_CRL_get_lastUpdate(crl); + JNI_TRACE("X509_CRL_get_lastUpdate(%p) => %p", crl, lastUpdate); + return reinterpret_cast(lastUpdate); +} - const uint8_t* tmp = CBS_data(&child); - // NOLINTNEXTLINE(runtime/int) - bssl::UniquePtr cert(d2i_X509(nullptr, &tmp, static_cast(CBS_len(&child)))); - if (!cert || tmp != CBS_data(&child) + CBS_len(&child)) { - conscrypt::jniutil::throwParsingException(env, "Error reading X.509 data"); - ERR_clear_error(); - return nullptr; - } +static jlong NativeCrypto_X509_CRL_get_nextUpdate(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_get_nextUpdate(%p)", crl); - if (!sk_X509_push(path.get(), cert.get())) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to push local certificate"); - return nullptr; - } - OWNERSHIP_TRANSFERRED(cert); + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_get_nextUpdate(%p) => crl == null", crl); + return 0; } - size_t size = sk_X509_num(path.get()); - - ScopedLocalRef certArray(env, env->NewLongArray(static_cast(size))); - ScopedLongArrayRW certs(env, certArray.get()); - for (size_t i = 0; i < size; i++) { - X509* item = reinterpret_cast(sk_X509_shift(path.get())); - certs[i] = reinterpret_cast(item); - } + ASN1_TIME* nextUpdate = X509_CRL_get_nextUpdate(crl); + JNI_TRACE("X509_CRL_get_nextUpdate(%p) => %p", crl, nextUpdate); + return reinterpret_cast(nextUpdate); +} - JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => returns %zd items", bio, size); - return certArray.release(); +static jbyteArray NativeCrypto_i2d_X509_REVOKED(JNIEnv* env, jclass, jlong x509RevokedRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509_REVOKED* x509Revoked = + reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("i2d_X509_REVOKED(%p)", x509Revoked); + return ASN1ToByteArray(env, x509Revoked, i2d_X509_REVOKED); } -static jbyteArray NativeCrypto_ASN1_seq_pack_X509(JNIEnv* env, jclass, jlongArray certs) { +static jint NativeCrypto_X509_supported_extension(JNIEnv* env, jclass, jlong x509ExtensionRef) { CHECK_ERROR_QUEUE_ON_RETURN; - JNI_TRACE("ASN1_seq_pack_X509(%p)", certs); - ScopedLongArrayRO certsArray(env, certs); - if (certsArray.get() == nullptr) { - JNI_TRACE("ASN1_seq_pack_X509(%p) => failed to get certs array", certs); - return nullptr; - } + X509_EXTENSION* ext = + reinterpret_cast(static_cast(x509ExtensionRef)); - bssl::ScopedCBB result; - CBB seq_contents; - if (!CBB_init(result.get(), 2048 * certsArray.size())) { - JNI_TRACE("ASN1_seq_pack_X509(%p) => CBB_init failed", certs); - return nullptr; - } - if (!CBB_add_asn1(result.get(), &seq_contents, CBS_ASN1_SEQUENCE)) { - return nullptr; + if (ext == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "ext == null"); + return 0; } - for (size_t i = 0; i < certsArray.size(); i++) { - X509* x509 = reinterpret_cast(static_cast(certsArray[i])); - uint8_t* buf; - int len = i2d_X509(x509, nullptr); + return X509_supported_extension(ext); +} - if (len < 0 || !CBB_add_space(&seq_contents, &buf, static_cast(len)) || - i2d_X509(x509, &buf) < 0) { - return nullptr; +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'; } - - return CBBToByteArray(env, result.get()); + *out = ret; + return true; } -static void NativeCrypto_X509_free(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_ASN1_TIME_to_Calendar(JNIEnv* env, jclass, jlong asn1TimeRef, + jobject calendar) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_free(%p)", x509); + ASN1_TIME* asn1Time = reinterpret_cast(static_cast(asn1TimeRef)); + JNI_TRACE("ASN1_TIME_to_Calendar(%p, %p)", asn1Time, calendar); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_free(%p) => x509 == null", x509); + if (asn1Time == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "asn1Time == null"); return; } - X509_free(x509); -} - -static jint NativeCrypto_X509_cmp(JNIEnv* env, jclass, jlong x509Ref1, - CONSCRYPT_UNUSED jobject holder, jlong x509Ref2, - CONSCRYPT_UNUSED jobject holder2) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509_1 = reinterpret_cast(static_cast(x509Ref1)); - X509* x509_2 = reinterpret_cast(static_cast(x509Ref2)); - JNI_TRACE("X509_cmp(%p, %p)", x509_1, x509_2); - - if (x509_1 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509_1 == null"); - JNI_TRACE("X509_cmp(%p, %p) => x509_1 == null", x509_1, x509_2); - return -1; + if (!ASN1_TIME_check(asn1Time)) { + conscrypt::jniutil::throwParsingException(env, "Invalid date format"); + return; } - if (x509_2 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509_2 == null"); - JNI_TRACE("X509_cmp(%p, %p) => x509_2 == null", x509_1, x509_2); - return -1; + bssl::UniquePtr gen(ASN1_TIME_to_generalizedtime(asn1Time, nullptr)); + if (gen.get() == nullptr) { + conscrypt::jniutil::throwParsingException(env, + "ASN1_TIME_to_generalizedtime returned null"); + ERR_clear_error(); + return; } - int ret = X509_cmp(x509_1, x509_2); - JNI_TRACE("X509_cmp(%p, %p) => %d", x509_1, x509_2, ret); - return ret; -} + 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; + } -static void NativeCrypto_X509_print_ex(JNIEnv* env, jclass, jlong bioRef, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder, jlong nmflagJava, - jlong certflagJava) { - CHECK_ERROR_QUEUE_ON_RETURN; - BIO* bio = to_BIO(env, bioRef); - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - // NOLINTNEXTLINE(runtime/int) - unsigned long nmflag = static_cast(nmflagJava); - // NOLINTNEXTLINE(runtime/int) - unsigned long certflag = static_cast(certflagJava); - JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld)", bio, x509, nmflag, certflag); - - if (bio == nullptr) { - JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => bio == null", bio, x509, nmflag, certflag); - return; - } - - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => x509 == null", bio, x509, nmflag, certflag); + 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; } - if (!X509_print_ex(bio, x509, nmflag, certflag)) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_print_ex"); - JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => threw error", bio, x509, nmflag, certflag); - return; - } - JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => success", bio, x509, nmflag, certflag); + env->CallVoidMethod(calendar, conscrypt::jniutil::calendar_setMethod, year, mon - 1, mday, hour, + min, sec); } -static jlong NativeCrypto_X509_get_pubkey(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +// 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. +struct CbsHandle { + // A pointer to the CBS. + std::unique_ptr cbs; + // A pointer to the data held by the CBS. If the data held by the CBS + // is owned by a different CbsHandle, data will be null. + std::unique_ptr data; +}; + +static jlong NativeCrypto_asn1_read_init(JNIEnv* env, jclass, jbyteArray data) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_pubkey(%p)", x509); + JNI_TRACE("asn1_read_init(%p)", data); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_get_pubkey(%p) => x509 == null", x509); + ScopedByteArrayRO bytes(env, data); + if (bytes.get() == nullptr) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return 0; } - bssl::UniquePtr pkey(X509_get_pubkey(x509)); - if (pkey.get() == nullptr) { - const uint32_t last_error = ERR_peek_last_error(); - const uint32_t first_error = ERR_peek_error(); - if ((ERR_GET_LIB(last_error) == ERR_LIB_EVP && - ERR_GET_REASON(last_error) == EVP_R_UNKNOWN_PUBLIC_KEY_TYPE) || - (ERR_GET_LIB(first_error) == ERR_LIB_EC && - ERR_GET_REASON(first_error) == EC_R_UNKNOWN_GROUP)) { - ERR_clear_error(); - conscrypt::jniutil::throwNoSuchAlgorithmException(env, "X509_get_pubkey"); - return 0; - } + std::unique_ptr cbs(new CbsHandle()); + cbs->data.reset(new unsigned char[bytes.size()]); + memcpy(cbs->data.get(), bytes.get(), bytes.size()); - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "X509_get_pubkey", conscrypt::jniutil::throwInvalidKeyException); + cbs->cbs.reset(new CBS()); + CBS_init(cbs->cbs.get(), cbs->data.get(), bytes.size()); + JNI_TRACE("asn1_read_init(%p) => %p", data, cbs.get()); + return reinterpret_cast(cbs.release()); +} + +static jlong NativeCrypto_asn1_read_sequence(JNIEnv* env, jclass, jlong cbsRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_sequence(%p)", cbs); + + std::unique_ptr seq(new CbsHandle()); + seq->cbs.reset(new CBS()); + if (!CBS_get_asn1(cbs->cbs.get(), seq->cbs.get(), CBS_ASN1_SEQUENCE)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return 0; } - - JNI_TRACE("X509_get_pubkey(%p) => %p", x509, pkey.get()); - return reinterpret_cast(pkey.release()); + JNI_TRACE("asn1_read_sequence(%p) => %p", cbs, seq.get()); + return reinterpret_cast(seq.release()); } -static jbyteArray NativeCrypto_X509_get_issuer_name(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jboolean NativeCrypto_asn1_read_next_tag_is(CONSCRYPT_UNUSED JNIEnv* env, jclass, + jlong cbsRef, jint tag) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_issuer_name(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_next_tag_is(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_get_issuer_name(%p) => x509 == null", x509); - return nullptr; - } - return ASN1ToByteArray(env, X509_get_issuer_name(x509), i2d_X509_NAME); + int result = CBS_peek_asn1_tag(cbs->cbs.get(), + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | tag); + JNI_TRACE("asn1_read_next_tag_is(%p) => %s", cbs, result ? "true" : "false"); + return result ? JNI_TRUE : JNI_FALSE; } -static jbyteArray NativeCrypto_X509_get_subject_name(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jlong NativeCrypto_asn1_read_tagged(JNIEnv* env, jclass, jlong cbsRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_subject_name(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_tagged(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("X509_get_subject_name(%p) => x509 == null", x509); - return nullptr; + std::unique_ptr tag(new CbsHandle()); + tag->cbs.reset(new CBS()); + if (!CBS_get_any_asn1(cbs->cbs.get(), tag->cbs.get(), nullptr)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + return 0; } - return ASN1ToByteArray(env, X509_get_subject_name(x509), i2d_X509_NAME); + JNI_TRACE("asn1_read_tagged(%p) => %p", cbs, tag.get()); + return reinterpret_cast(tag.release()); } -static jstring NativeCrypto_get_X509_pubkey_oid(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jbyteArray NativeCrypto_asn1_read_octetstring(JNIEnv* env, jclass, jlong cbsRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_pubkey_oid(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_octetstring(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_pubkey_oid(%p) => x509 == null", x509); - return nullptr; + std::unique_ptr str(new CBS()); + if (!CBS_get_asn1(cbs->cbs.get(), str.get(), CBS_ASN1_OCTETSTRING)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + return 0; } - - X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(x509); - ASN1_OBJECT* algorithm; - X509_PUBKEY_get0_param(&algorithm, nullptr, nullptr, nullptr, pubkey); - return ASN1_OBJECT_to_OID_string(env, algorithm); + ScopedLocalRef out(env, env->NewByteArray(static_cast(CBS_len(str.get())))); + if (out.get() == nullptr) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + return 0; + } + ScopedByteArrayRW outBytes(env, out.get()); + if (outBytes.get() == nullptr) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + return 0; + } + memcpy(outBytes.get(), CBS_data(str.get()), CBS_len(str.get())); + JNI_TRACE("asn1_read_octetstring(%p) => %p", cbs, out.get()); + return out.release(); } -static jstring NativeCrypto_get_X509_sig_alg_oid(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jlong NativeCrypto_asn1_read_uint64(JNIEnv* env, jclass, jlong cbsRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_sig_alg_oid(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_uint64(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null || x509->sig_alg == null"); - JNI_TRACE("get_X509_sig_alg_oid(%p) => x509 == null", x509); - return nullptr; + // NOLINTNEXTLINE(runtime/int) + uint64_t value; + if (!CBS_get_asn1_uint64(cbs->cbs.get(), &value)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); + return 0; } - - const X509_ALGOR* sig_alg; - X509_get0_signature(nullptr, &sig_alg, x509); - const ASN1_OBJECT* oid; - X509_ALGOR_get0(&oid, nullptr, nullptr, sig_alg); - return ASN1_OBJECT_to_OID_string(env, oid); + return value; } -static jbyteArray NativeCrypto_get_X509_sig_alg_parameter(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_asn1_read_null(JNIEnv* env, jclass, jlong cbsRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_sig_alg_parameter(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_null(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_sig_alg_parameter(%p) => x509 == null", x509); - return nullptr; + CBS null_holder; + if (!CBS_get_asn1(cbs->cbs.get(), &null_holder, CBS_ASN1_NULL)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); } - - const X509_ALGOR* sig_alg; - X509_get0_signature(nullptr, &sig_alg, x509); - return get_X509_ALGOR_parameter(env, sig_alg); } -static jbooleanArray NativeCrypto_get_X509_issuerUID(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jstring NativeCrypto_asn1_read_oid(JNIEnv* env, jclass, jlong cbsRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_issuerUID(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_oid(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_issuerUID(%p) => x509 == null", x509); + CBS oid_cbs; + if (!CBS_get_asn1(cbs->cbs.get(), &oid_cbs, CBS_ASN1_OBJECT)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return nullptr; } - - const ASN1_BIT_STRING* issuer_uid; - X509_get0_uids(x509, &issuer_uid, /*out_subject_uid=*/nullptr); - if (issuer_uid == nullptr) { - JNI_TRACE("get_X509_issuerUID(%p) => null", x509); + int nid = OBJ_cbs2nid(&oid_cbs); + if (nid == NID_undef) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding: OID not found"); return nullptr; } - - return ASN1BitStringToBooleanArray(env, issuer_uid); + const ASN1_OBJECT* obj(OBJ_nid2obj(nid)); + if (obj == nullptr) { + conscrypt::jniutil::throwIOException(env, + "Error reading ASN.1 encoding: " + "Could not find ASN1_OBJECT for NID"); + return nullptr; + } + return ASN1_OBJECT_to_OID_string(env, obj); } -static jbooleanArray NativeCrypto_get_X509_subjectUID(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static jstring NativeCrypto_asn1_read_oid_raw(JNIEnv* env, jclass, jlong cbsRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_subjectUID(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_oid_raw(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_subjectUID(%p) => x509 == null", x509); + CBS oid_cbs; + if (!CBS_get_asn1(cbs->cbs.get(), &oid_cbs, CBS_ASN1_OBJECT)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return nullptr; } - - const ASN1_BIT_STRING* subject_uid; - X509_get0_uids(x509, /*out_issuer_uid=*/nullptr, &subject_uid); - if (subject_uid == nullptr) { - JNI_TRACE("get_X509_subjectUID(%p) => null", x509); + if (!CBS_is_valid_asn1_oid(&oid_cbs)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding: OID not valid"); return nullptr; } + char* oid_text = CBS_asn1_oid_to_text(&oid_cbs); + if (oid_text == nullptr) { + conscrypt::jniutil::throwIOException( + env, "Error reading ASN.1 encoding: could not convert OID to text"); + return nullptr; + } + jstring result = env->NewStringUTF(oid_text); + OPENSSL_free(oid_text); - return ASN1BitStringToBooleanArray(env, subject_uid); + return result; } -static jbooleanArray NativeCrypto_get_X509_ex_kusage(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +// This function reads the raw BITSTRING payload from the given CBS, and +// verifies that the padding byte matches the expected value. It returns the +// payload as a byte array. +static jbyteArray NativeCrypto_asn1_read_bitstring_payload(JNIEnv* env, jclass, jlong cbsRef, + jint expectedUnusedBits) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_ex_kusage(%p)", x509); + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_bitstring_payload(%p)", cbs); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_ex_kusage(%p) => x509 == null", x509); + CBS bitstring_cbs; + if (!CBS_get_asn1(cbs->cbs.get(), &bitstring_cbs, CBS_ASN1_BITSTRING)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return nullptr; } - - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and - // remove |ERR_clear_error|. Note X509Certificate.getKeyUsage() cannot throw - // CertificateParsingException, so this needs to be checked earlier, e.g. in - // the constructor. - bssl::UniquePtr bitStr( - static_cast(X509_get_ext_d2i(x509, NID_key_usage, nullptr, nullptr))); - if (bitStr.get() == nullptr) { - JNI_TRACE("get_X509_ex_kusage(%p) => null", x509); - ERR_clear_error(); + if (CBS_len(&bitstring_cbs) == 0) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding: empty bitstring"); return nullptr; } - - return ASN1BitStringToBooleanArray(env, bitStr.get()); -} - -static jobjectArray NativeCrypto_get_X509_ex_xkusage(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_ex_xkusage(%p)", x509); - - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_ex_xkusage(%p) => x509 == null", x509); + uint8_t padding; + if (!CBS_get_u8(&bitstring_cbs, &padding)) { + conscrypt::jniutil::throwIOException(env, "Error reading ASN.1 encoding"); return nullptr; } - - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors, remove - // |ERR_clear_error|, and throw CertificateParsingException. - bssl::UniquePtr objArray(static_cast( - X509_get_ext_d2i(x509, NID_ext_key_usage, nullptr, nullptr))); - if (objArray.get() == nullptr) { - JNI_TRACE("get_X509_ex_xkusage(%p) => null", x509); - ERR_clear_error(); + if (padding != static_cast(expectedUnusedBits)) { + conscrypt::jniutil::throwIOException( + env, "Error reading ASN.1 encoding: unexpected padding byte"); return nullptr; } - size_t size = sk_ASN1_OBJECT_num(objArray.get()); - ScopedLocalRef exKeyUsage( - env, env->NewObjectArray(static_cast(size), conscrypt::jniutil::stringClass, - nullptr)); - if (exKeyUsage.get() == nullptr) { + ScopedLocalRef out(env, + env->NewByteArray(static_cast(CBS_len(&bitstring_cbs)))); + if (out.get() == nullptr) { return nullptr; } - - for (size_t i = 0; i < size; i++) { - ScopedLocalRef oidStr( - env, ASN1_OBJECT_to_OID_string(env, sk_ASN1_OBJECT_value(objArray.get(), i))); - env->SetObjectArrayElement(exKeyUsage.get(), static_cast(i), oidStr.get()); + ScopedByteArrayRW outBytes(env, out.get()); + if (outBytes.get() == nullptr) { + return nullptr; } + memcpy(outBytes.get(), CBS_data(&bitstring_cbs), CBS_len(&bitstring_cbs)); + JNI_TRACE("asn1_read_bitstring_payload(%p) => %p", cbs, out.get()); + return out.release(); +} - JNI_TRACE("get_X509_ex_xkusage(%p) => success (%zd entries)", x509, size); - return exKeyUsage.release(); +static jboolean NativeCrypto_asn1_read_is_empty(CONSCRYPT_UNUSED JNIEnv* env, jclass, + jlong cbsRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_is_empty(%p)", cbs); + + bool empty = (CBS_len(cbs->cbs.get()) == 0); + JNI_TRACE("asn1_read_is_empty(%p) => %s", cbs, empty ? "true" : "false"); + return empty; } -static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_asn1_read_free(CONSCRYPT_UNUSED JNIEnv* env, jclass, jlong cbsRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509_ex_pathlen(%p)", x509); + if (cbsRef == 0) { + JNI_TRACE("asn1_read_free(0)"); + return; + } + CbsHandle* cbs = reinterpret_cast(static_cast(cbsRef)); + JNI_TRACE("asn1_read_free(%p)", cbs); + delete cbs; +} - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509_ex_pathlen(%p) => x509 == null", x509); +static jlong NativeCrypto_asn1_write_init(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("asn1_write_init"); + std::unique_ptr cbb(new CBB()); + if (!CBB_init(cbb.get(), 128)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); return 0; } + JNI_TRACE("asn1_write_init => %p", cbb.get()); + return reinterpret_cast(cbb.release()); +} - // Use |X509_get_ext_d2i| instead of |X509_get_pathlen| because the latter - // treats |EXFLAG_INVALID| as the error case. |EXFLAG_INVALID| is set if any - // built-in extension is invalid. For now, we preserve Conscrypt's historical - // behavior in accepting certificates in the constructor even if - // |EXFLAG_INVALID| is set. - // - // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and - // remove |ERR_clear_error|. Note X509Certificate.getBasicConstraints() cannot - // throw CertificateParsingException, so this needs to be checked earlier, - // e.g. in the constructor. - bssl::UniquePtr basic_constraints(static_cast( - X509_get_ext_d2i(x509, NID_basic_constraints, nullptr, nullptr))); - if (basic_constraints == nullptr) { - JNI_TRACE("get_X509_ex_path(%p) => -1 (no extension or error)", x509); - ERR_clear_error(); - return -1; - } +static jlong NativeCrypto_asn1_write_sequence(JNIEnv* env, jclass, jlong cbbRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_sequence(%p)", cbb); - if (basic_constraints->pathlen == nullptr) { - JNI_TRACE("get_X509_ex_path(%p) => -1 (no pathLenConstraint)", x509); - return -1; + std::unique_ptr seq(new CBB()); + if (!CBB_add_asn1(cbb, seq.get(), CBS_ASN1_SEQUENCE)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return 0; } + JNI_TRACE("asn1_write_sequence(%p) => %p", cbb, seq.get()); + return reinterpret_cast(seq.release()); +} - if (!basic_constraints->ca) { - // Path length constraints are only valid for CA certificates. - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an - // error condition. - JNI_TRACE("get_X509_ex_path(%p) => -1 (not a CA)", x509); - return -1; - } +static jlong NativeCrypto_asn1_write_tag(JNIEnv* env, jclass, jlong cbbRef, jint tag) { + CHECK_ERROR_QUEUE_ON_RETURN; + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_tag(%p)", cbb); - if (basic_constraints->pathlen->type == V_ASN1_NEG_INTEGER) { - // Path length constraints may not be negative. - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an - // error condition. - JNI_TRACE("get_X509_ex_path(%p) => -1 (negative)", x509); - return -1; + std::unique_ptr tag_holder(new CBB()); + if (!CBB_add_asn1(cbb, tag_holder.get(), + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | tag)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return 0; } + JNI_TRACE("asn1_write_tag(%p) => %p", cbb, tag_holder.get()); + return reinterpret_cast(tag_holder.release()); +} - long pathlen = ASN1_INTEGER_get(basic_constraints->pathlen); - if (pathlen == -1 || pathlen > INT_MAX) { - // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an - // error condition. If the integer overflows, the certificate is effectively - // unconstrained. Reporting no constraint is plausible, but Chromium rejects - // all values above 255. - JNI_TRACE("get_X509_ex_path(%p) => -1 (overflow)", x509); - return -1; +static void NativeCrypto_asn1_write_octetstring(JNIEnv* env, jclass, jlong cbbRef, + jbyteArray data) { + CHECK_ERROR_QUEUE_ON_RETURN; + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_octetstring(%p, %p)", cbb, data); + + ScopedByteArrayRO bytes(env, data); + if (bytes.get() == nullptr) { + JNI_TRACE("asn1_write_octetstring(%p, %p) => using byte array failed", cbb, data); + return; } - JNI_TRACE("get_X509_ex_path(%p) => %ld", x509, pathlen); - return pathlen; + std::unique_ptr octetstring(new CBB()); + if (!CBB_add_asn1(cbb, octetstring.get(), CBS_ASN1_OCTETSTRING)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; + } + if (!CBB_add_bytes(octetstring.get(), reinterpret_cast(bytes.get()), + bytes.size())) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; + } + if (!CBB_flush(cbb)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; + } } -static jbyteArray NativeCrypto_X509_get_ext_oid(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder, - jstring oidString) { +static void NativeCrypto_asn1_write_bitstring(JNIEnv* env, jclass, jlong cbbRef, jbyteArray data) { CHECK_ERROR_QUEUE_ON_RETURN; - X509* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("X509_get_ext_oid(%p, %p)", x509, oidString); - return X509Type_get_ext_oid(env, x509, oidString); + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_bitstring(%p, %p)", cbb, data); + + ScopedByteArrayRO bytes(env, data); + if (bytes.get() == nullptr) { + JNI_TRACE("asn1_write_bitstring(%p, %p) => using byte array failed", cbb, data); + return; + } + + std::unique_ptr bitstring(new CBB()); + if (!CBB_add_asn1(cbb, bitstring.get(), CBS_ASN1_BITSTRING) || + !CBB_add_u8(bitstring.get(), 0) || + !CBB_add_bytes(bitstring.get(), reinterpret_cast(bytes.get()), + bytes.size()) || + !CBB_flush(cbb)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; + } } -static jbyteArray NativeCrypto_X509_CRL_get_ext_oid(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder, - jstring oidString) { +static void NativeCrypto_asn1_write_uint64(JNIEnv* env, jclass, jlong cbbRef, jlong data) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); - JNI_TRACE("X509_CRL_get_ext_oid(%p, %p)", crl, oidString); + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_uint64(%p)", cbb); - if (crl == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "crl == null"); - JNI_TRACE("X509_CRL_get_ext_oid(%p) => crl == null", crl); - return nullptr; + if (!CBB_add_asn1_uint64(cbb, static_cast(data))) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; } - return X509Type_get_ext_oid(env, crl, - oidString); } -static jbyteArray NativeCrypto_X509_REVOKED_get_ext_oid(JNIEnv* env, jclass, jlong x509RevokedRef, - jstring oidString, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_asn1_write_null(JNIEnv* env, jclass, jlong cbbRef) { CHECK_ERROR_QUEUE_ON_RETURN; - X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); - JNI_TRACE("X509_REVOKED_get_ext_oid(%p, %p)", revoked, oidString); + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_null(%p)", cbb); - if (revoked == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); - JNI_TRACE("X509_REVOKED_get_ext_oid(%p) => revoked == null", revoked); - return nullptr; + CBB null_holder; + if (!CBB_add_asn1(cbb, &null_holder, CBS_ASN1_NULL)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; + } + if (!CBB_flush(cbb)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; } - return X509Type_get_ext_oid( - env, revoked, oidString); } -template -static jobjectArray get_X509Type_ext_oids(JNIEnv* env, jlong x509Ref, jint critical) { - T* x509 = reinterpret_cast(static_cast(x509Ref)); - JNI_TRACE("get_X509Type_ext_oids(%p, %d)", x509, critical); +static void NativeCrypto_asn1_write_oid(JNIEnv* env, jclass, jlong cbbRef, jstring oid) { + CHECK_ERROR_QUEUE_ON_RETURN; + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_oid(%p)", cbb); - if (x509 == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); - JNI_TRACE("get_X509Type_ext_oids(%p, %d) => x509 == null", x509, critical); - return nullptr; + ScopedUtfChars oid_chars(env, oid); + if (oid_chars.c_str() == nullptr) { + return; } - int lastPos = -1; - int count = 0; - while ((lastPos = get_ext_by_critical_func(x509, critical, lastPos)) != -1) { - count++; + int nid = OBJ_txt2nid(oid_chars.c_str()); + if (nid == NID_undef) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; } - JNI_TRACE("get_X509Type_ext_oids(%p, %d) has %d entries", x509, critical, count); - - ScopedLocalRef joa( - env, env->NewObjectArray(count, conscrypt::jniutil::stringClass, nullptr)); - if (joa.get() == nullptr) { - JNI_TRACE("get_X509Type_ext_oids(%p, %d) => fail to allocate result array", x509, critical); - return nullptr; + if (!OBJ_nid2cbb(cbb, nid)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; } +} - lastPos = -1; - count = 0; - while ((lastPos = get_ext_by_critical_func(x509, critical, lastPos)) != -1) { - X509_EXTENSION* ext = get_ext_func(x509, lastPos); - - ScopedLocalRef extOid( - env, ASN1_OBJECT_to_OID_string(env, X509_EXTENSION_get_object(ext))); - if (extOid.get() == nullptr) { - JNI_TRACE("get_X509Type_ext_oids(%p) => couldn't get OID", x509); - return nullptr; - } +static void NativeCrypto_asn1_write_oid_raw(JNIEnv* env, jclass, jlong cbbRef, jstring oid) { + CHECK_ERROR_QUEUE_ON_RETURN; + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_oid_raw(%p)", cbb); - env->SetObjectArrayElement(joa.get(), count++, extOid.get()); + ScopedUtfChars oid_chars(env, oid); + if (oid_chars.c_str() == nullptr) { + return; } - JNI_TRACE("get_X509Type_ext_oids(%p, %d) => success", x509, critical); - return joa.release(); + CBB child; + if (!CBB_add_asn1(cbb, &child, CBS_ASN1_OBJECT) || + !CBB_add_asn1_oid_from_text(&child, oid_chars.c_str(), strlen(oid_chars.c_str())) || + !CBB_flush(cbb)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; + } } -static jobjectArray NativeCrypto_get_X509_ext_oids(JNIEnv* env, jclass, jlong x509Ref, - CONSCRYPT_UNUSED jobject holder, jint critical) { +static void NativeCrypto_asn1_write_flush(JNIEnv* env, jclass, jlong cbbRef) { CHECK_ERROR_QUEUE_ON_RETURN; - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("get_X509_ext_oids(0x%llx, %d)", (long long)x509Ref, critical); - return get_X509Type_ext_oids(env, x509Ref, - critical); + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_flush(%p)", cbb); + + if (!CBB_flush(cbb)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return; + } } -static jobjectArray NativeCrypto_get_X509_CRL_ext_oids(JNIEnv* env, jclass, jlong x509CrlRef, - CONSCRYPT_UNUSED jobject holder, - jint critical) { +static jbyteArray NativeCrypto_asn1_write_finish(JNIEnv* env, jclass, jlong cbbRef) { CHECK_ERROR_QUEUE_ON_RETURN; - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", (long long)x509CrlRef, critical); - return get_X509Type_ext_oids( - env, x509CrlRef, critical); + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_finish(%p)", cbb); + + uint8_t* data; + size_t data_len; + if (!CBB_finish(cbb, &data, &data_len)) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return 0; + } + bssl::UniquePtr data_storage(data); + ScopedLocalRef out(env, env->NewByteArray(static_cast(data_len))); + if (out.get() == nullptr) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return 0; + } + ScopedByteArrayRW outBytes(env, out.get()); + if (outBytes.get() == nullptr) { + conscrypt::jniutil::throwIOException(env, "Error writing ASN.1 encoding"); + return 0; + } + memcpy(outBytes.get(), data, data_len); + return out.release(); } -static jobjectArray NativeCrypto_get_X509_REVOKED_ext_oids(JNIEnv* env, jclass, - jlong x509RevokedRef, jint critical, - CONSCRYPT_UNUSED jobject holder) { +static void NativeCrypto_asn1_write_cleanup(CONSCRYPT_UNUSED JNIEnv* env, jclass, jlong cbbRef) { CHECK_ERROR_QUEUE_ON_RETURN; - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", (long long)x509RevokedRef, critical); - return get_X509Type_ext_oids(env, x509RevokedRef, critical); -} + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_cleanup(%p)", cbb); -/** - * Based on example logging call back from SSL_CTX_set_info_callback man page - */ -static void info_callback_LOG(const SSL* s, int where, int ret) { - int w = where & ~SSL_ST_MASK; - const char* str; - if (w & SSL_ST_CONNECT) { - str = "SSL_connect"; - } else if (w & SSL_ST_ACCEPT) { - str = "SSL_accept"; - } else { - str = "undefined"; - } + CBB_cleanup(cbb); +} - if (where & SSL_CB_LOOP) { - JNI_TRACE("ssl=%p %s:%s %s", s, str, SSL_state_string(s), SSL_state_string_long(s)); - } else if (where & SSL_CB_ALERT) { - str = (where & SSL_CB_READ) ? "read" : "write"; - JNI_TRACE("ssl=%p SSL3 alert %s %s %s", s, str, SSL_alert_type_string_long(ret), - SSL_alert_desc_string_long(ret)); - } else if (where & SSL_CB_EXIT) { - if (ret == 0) { - JNI_TRACE("ssl=%p %s:failed exit in %s %s", s, str, SSL_state_string(s), - SSL_state_string_long(s)); - } else if (ret < 0) { - JNI_TRACE("ssl=%p %s:error exit in %s %s", s, str, SSL_state_string(s), - SSL_state_string_long(s)); - } else if (ret == 1) { - JNI_TRACE("ssl=%p %s:ok exit in %s %s", s, str, SSL_state_string(s), - SSL_state_string_long(s)); - } else { - JNI_TRACE("ssl=%p %s:unknown exit %d in %s %s", s, str, ret, SSL_state_string(s), - SSL_state_string_long(s)); - } - } else if (where & SSL_CB_HANDSHAKE_START) { - JNI_TRACE("ssl=%p handshake start in %s %s", s, SSL_state_string(s), - SSL_state_string_long(s)); - } else if (where & SSL_CB_HANDSHAKE_DONE) { - JNI_TRACE("ssl=%p handshake done in %s %s", s, SSL_state_string(s), - SSL_state_string_long(s)); - } else { - JNI_TRACE("ssl=%p %s:unknown where %d in %s %s", s, str, where, SSL_state_string(s), - SSL_state_string_long(s)); +static void NativeCrypto_asn1_write_free(CONSCRYPT_UNUSED JNIEnv* env, jclass, jlong cbbRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + if (cbbRef == 0) { + JNI_TRACE("asn1_write_free(0)"); + return; } + CBB* cbb = reinterpret_cast(static_cast(cbbRef)); + JNI_TRACE("asn1_write_free(%p)", cbb); + delete cbb; } -#ifdef _WIN32 - -/** - * Dark magic helper function that checks, for a given SSL session, whether it - * can SSL_read() or SSL_write() without blocking. Takes into account any - * concurrent attempts to close the SSLSocket from the Java side. This is - * needed to get rid of the hangs that occur when thread #1 closes the SSLSocket - * while thread #2 is sitting in a blocking read or write. The type argument - * specifies whether we are waiting for readability or writability. It expects - * to be passed either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, since we - * only need to wait in case one of these problems occurs. - * - * @param env - * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE - * @param fdObject The FileDescriptor, since appData->fileDescriptor should be - * NULL - * @param appData The application data structure with mutex info etc. - * @param timeout_millis The timeout value for select call, with the special - * value 0 meaning no timeout at all (wait indefinitely). Note: This is the Java - * semantics of the timeout value, not the usual select() semantics. - * @return THROWN_EXCEPTION on close socket, 0 on timeout, -1 on error, and 1 on - * success - */ -static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData, - int timeout_millis) { - int result = -1; +template +static jlong d2i_ASN1Object_to_jlong(JNIEnv* env, jlong bioRef) { + BIO* bio = to_BIO(env, bioRef); + JNI_TRACE("d2i_ASN1Object_to_jlong(%p)", bio); - NetFd fd(env, fdObject); - do { - if (fd.isClosed()) { - result = THROWN_EXCEPTION; - break; - } + if (bio == nullptr) { + return 0; + } - WSAEVENT events[2]; - events[0] = appData->interruptEvent; - events[1] = WSACreateEvent(); - if (events[1] == WSA_INVALID_EVENT) { - JNI_TRACE("sslSelect failure in WSACreateEvent: %d", WSAGetLastError()); - break; - } + T* x = d2i_func(bio, nullptr); + if (x == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "d2i_ASN1Object_to_jlong"); + return 0; + } - if (WSAEventSelect(fd.get(), events[1], - (type == SSL_ERROR_WANT_READ ? FD_READ : FD_WRITE) | FD_CLOSE) == - SOCKET_ERROR) { - JNI_TRACE("sslSelect failure in WSAEventSelect: %d", WSAGetLastError()); - break; - } + return reinterpret_cast(x); +} - JNI_TRACE("sslSelect type=%s fd=%d appData=%p timeout_millis=%d", - (type == SSL_ERROR_WANT_READ) ? "READ" : "WRITE", fd.get(), appData, - timeout_millis); - - int rc = WSAWaitForMultipleEvents( - 2, events, FALSE, timeout_millis == 0 ? WSA_INFINITE : timeout_millis, FALSE); - if (rc == WSA_WAIT_FAILED) { - JNI_TRACE("WSAWaitForMultipleEvents failed: %d", WSAGetLastError()); - result = -1; - } else if (rc == WSA_WAIT_TIMEOUT) { - result = 0; - } else { - result = 1; - } - WSACloseEvent(events[1]); - } while (0); +static jlong NativeCrypto_d2i_X509_CRL_bio(JNIEnv* env, jclass, jlong bioRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + return d2i_ASN1Object_to_jlong(env, bioRef); +} - JNI_TRACE("sslSelect type=%s fd=%d appData=%p timeout_millis=%d => %d", - (type == SSL_ERROR_WANT_READ) ? "READ" : "WRITE", fd.get(), appData, timeout_millis, - result); +static jlong NativeCrypto_d2i_X509_bio(JNIEnv* env, jclass, jlong bioRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + return d2i_ASN1Object_to_jlong(env, bioRef); +} - std::lock_guard appDataLock(appData->mutex); - appData->waitingThreads--; +static jlong NativeCrypto_d2i_X509(JNIEnv* env, jclass, jbyteArray certBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + ScopedByteArrayRO bytes(env, certBytes); + if (bytes.get() == nullptr) { + JNI_TRACE("NativeCrypto_d2i_X509(%p) => using byte array failed", certBytes); + return 0; + } - return result; + const unsigned char* tmp = reinterpret_cast(bytes.get()); + // NOLINTNEXTLINE(runtime/int) + X509* x = d2i_X509(nullptr, &tmp, static_cast(bytes.size())); + if (x == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "Error reading X.509 data", conscrypt::jniutil::throwParsingException); + return 0; + } + return reinterpret_cast(x); } -#else // !defined(_WIN32) +static jbyteArray NativeCrypto_i2d_X509(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("i2d_X509(%p)", x509); -/** - * Dark magic helper function that checks, for a given SSL session, whether it - * can SSL_read() or SSL_write() without blocking. Takes into account any - * concurrent attempts to close the SSLSocket from the Java side. This is - * needed to get rid of the hangs that occur when thread #1 closes the SSLSocket - * while thread #2 is sitting in a blocking read or write. The type argument - * specifies whether we are waiting for readability or writability. It expects - * to be passed either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, since we - * only need to wait in case one of these problems occurs. - * - * @param env - * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE - * @param fdObject The FileDescriptor, since appData->fileDescriptor should be - * nullptr - * @param appData The application data structure with mutex info etc. - * @param timeout_millis The timeout value for poll call, with the special value - * 0 meaning no timeout at all (wait indefinitely). Note: This is - * the Java semantics of the timeout value, not the usual - * poll() semantics. - * @return The result of the inner poll() call, - * THROW_SOCKETEXCEPTION if a SocketException was thrown, -1 on - * additional errors - */ -static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData, - int timeout_millis) { - // This loop is an expanded version of the NET_FAILURE_RETRY - // macro. It cannot simply be used in this case because poll - // cannot be restarted without recreating the pollfd structure. - int result; - struct pollfd fds[2]; - do { - NetFd fd(env, fdObject); - if (fd.isClosed()) { - result = THROWN_EXCEPTION; - break; - } - int intFd = fd.get(); - JNI_TRACE("sslSelect type=%s fd=%d appData=%p timeout_millis=%d", - (type == SSL_ERROR_WANT_READ) ? "READ" : "WRITE", intFd, appData, timeout_millis); - - memset(&fds, 0, sizeof(fds)); - fds[0].fd = intFd; - if (type == SSL_ERROR_WANT_READ) { - fds[0].events = POLLIN | POLLPRI; - } else { - fds[0].events = POLLOUT | POLLPRI; - } + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("i2d_X509(%p) => x509 == null", x509); + return nullptr; + } + return ASN1ToByteArray(env, x509, i2d_X509); +} - fds[1].fd = appData->fdsEmergency[0]; - fds[1].events = POLLIN | POLLPRI; +static jbyteArray NativeCrypto_i2d_X509_PUBKEY(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("i2d_X509_PUBKEY(%p)", x509); - // Converting from Java semantics to Posix semantics. - if (timeout_millis <= 0) { - timeout_millis = -1; - } + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("i2d_X509_PUBKEY(%p) => x509 == null", x509); + return nullptr; + } + return ASN1ToByteArray(env, X509_get_X509_PUBKEY(x509), i2d_X509_PUBKEY); +} - CompatibilityCloseMonitor monitor(intFd); +template +static jlong PEM_to_jlong(JNIEnv* env, jlong bioRef) { + BIO* bio = to_BIO(env, bioRef); + JNI_TRACE("PEM_to_jlong(%p)", bio); - result = poll(fds, sizeof(fds) / sizeof(fds[0]), timeout_millis); - JNI_TRACE("sslSelect %s fd=%d appData=%p timeout_millis=%d => %d", - (type == SSL_ERROR_WANT_READ) ? "READ" : "WRITE", fd.get(), appData, - timeout_millis, result); - if (result == -1) { - if (fd.isClosed()) { - result = THROWN_EXCEPTION; - break; - } - if (errno != EINTR) { - break; - } - } - } while (result == -1); - - std::lock_guard appDataLock(appData->mutex); - - if (result > 0) { - // We have been woken up by a token in the emergency pipe. We - // can't be sure the token is still in the pipe at this point - // because it could have already been read by the thread that - // originally wrote it if it entered sslSelect and acquired - // the mutex before we did. Thus we cannot safely read from - // the pipe in a blocking way (so we make the pipe - // non-blocking at creation). - if (fds[1].revents & POLLIN) { - char token; - do { - CONSCRYPT_UNUSED int n = read(appData->fdsEmergency[0], &token, 1); - } while (errno == EINTR); - } + if (bio == nullptr) { + JNI_TRACE("PEM_to_jlong(%p) => bio == null", bio); + return 0; } - // Tell the world that there is now one thread less waiting for the - // underlying network. - appData->waitingThreads--; + T* x = PEM_read_func(bio, nullptr, nullptr, nullptr); + if (x == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PEM_to_jlong"); + JNI_TRACE("PEM_to_jlong(%p) => threw exception", bio); + return 0; + } - return result; + JNI_TRACE("PEM_to_jlong(%p) => %p", bio, x); + return reinterpret_cast(x); } -#endif // !defined(_WIN32) -/** - * Helper function that wakes up a thread blocked in select(), in case there is - * one. Is being called by sslRead() and sslWrite() as well as by JNI glue - * before closing the connection. - * - * @param data The application data structure with mutex info etc. - */ -static void sslNotify(AppData* appData) { -#ifdef _WIN32 - SetEvent(appData->interruptEvent); -#else - // Write a byte to the emergency pipe, so a concurrent select() can return. - // Note we have to restore the errno of the original system call, since the - // caller relies on it for generating error messages. - int errnoBackup = errno; - char token = '*'; - do { - errno = 0; - CONSCRYPT_UNUSED int n = write(appData->fdsEmergency[1], &token, 1); - } while (errno == EINTR); - errno = errnoBackup; -#endif +static jlong NativeCrypto_PEM_read_bio_X509(JNIEnv* env, jclass, jlong bioRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("PEM_read_bio_X509(0x%llx)", (long long)bioRef); + return PEM_to_jlong(env, bioRef); } -static AppData* toAppData(const SSL* ssl) { - return reinterpret_cast(SSL_get_app_data(ssl)); +static jlong NativeCrypto_PEM_read_bio_X509_CRL(JNIEnv* env, jclass, jlong bioRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("PEM_read_bio_X509_CRL(0x%llx)", (long long)bioRef); + return PEM_to_jlong(env, bioRef); } -static ssl_verify_result_t cert_verify_callback(SSL* ssl, CONSCRYPT_UNUSED uint8_t* out_alert) { - JNI_TRACE("ssl=%p cert_verify_callback", ssl); +static jlong NativeCrypto_PEM_read_bio_PUBKEY(JNIEnv* env, jclass, jlong bioRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("PEM_read_bio_PUBKEY(0x%llx)", (long long)bioRef); + return PEM_to_jlong(env, bioRef); +} - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in cert_verify_callback"); - JNI_TRACE("ssl=%p cert_verify_callback => 0", ssl); - return ssl_verify_invalid; - } +static jlong NativeCrypto_PEM_read_bio_PrivateKey(JNIEnv* env, jclass, jlong bioRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("PEM_read_bio_PrivateKey(0x%llx)", (long long)bioRef); + return PEM_to_jlong(env, bioRef); +} - // Create the byte[][] array that holds all the certs - ScopedLocalRef array( - env, CryptoBuffersToObjectArray(env, SSL_get0_peer_certificates(ssl))); - if (array.get() == nullptr) { - return ssl_verify_invalid; +static jlongArray X509s_to_ItemArray(JNIEnv* env, STACK_OF(X509)* certs) { + if (certs == nullptr) { + return nullptr; } - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_verifyCertificateChain; - - const SSL_CIPHER* cipher = SSL_get_pending_cipher(ssl); - const char* authMethod = SSL_CIPHER_get_kx_name(cipher); - - JNI_TRACE( - "ssl=%p cert_verify_callback calling verifyCertificateChain " - "authMethod=%s", - ssl, authMethod); - ScopedLocalRef authMethodString(env, env->NewStringUTF(authMethod)); - env->CallVoidMethod(sslHandshakeCallbacks, methodID, array.get(), authMethodString.get()); + ScopedLocalRef ref_array(env, nullptr); + size_t size = sk_X509_num(certs); + ref_array.reset(env->NewLongArray(size)); + ScopedLongArrayRW items(env, ref_array.get()); + for (size_t i = 0; i < size; i++) { + X509* cert = sk_X509_value(certs, i); + X509_up_ref(cert); + items[i] = reinterpret_cast(cert); + } - ssl_verify_result_t result = env->ExceptionCheck() ? ssl_verify_invalid : ssl_verify_ok; - JNI_TRACE("ssl=%p cert_verify_callback => %d", ssl, result); - return result; + JNI_TRACE("X509s_to_ItemArray(%p) => %p [size=%zd]", certs, ref_array.get(), size); + return ref_array.release(); } -/** - * Call back to watch for handshake to be completed. This is necessary for - * False Start support, since SSL_do_handshake returns before the handshake is - * completed in this case. - */ -static void info_callback(const SSL* ssl, int type, int value) { - JNI_TRACE("ssl=%p info_callback type=0x%x value=%d", ssl, type, value); - if (conscrypt::trace::kWithJniTrace) { - info_callback_LOG(ssl, type, value); - } - if (!(type & SSL_CB_HANDSHAKE_DONE) && !(type & SSL_CB_HANDSHAKE_START)) { - JNI_TRACE("ssl=%p info_callback ignored", ssl); - return; +static jlongArray X509_CRLs_to_ItemArray(JNIEnv* env, STACK_OF(X509_CRL)* crls) { + if (crls == nullptr) { + return nullptr; } - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in info_callback"); - JNI_TRACE("ssl=%p info_callback env error", ssl); - return; - } - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p info_callback already pending exception", ssl); - return; + ScopedLocalRef ref_array(env, nullptr); + size_t size = sk_X509_CRL_num(crls); + ref_array.reset(env->NewLongArray(size)); + ScopedLongArrayRW items(env, ref_array.get()); + for (size_t i = 0; i < size; i++) { + X509_CRL* crl = sk_X509_CRL_value(crls, i); + X509_CRL_up_ref(crl); + items[i] = reinterpret_cast(crl); } - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - - JNI_TRACE("ssl=%p info_callback calling onSSLStateChange", ssl); - env->CallVoidMethod(sslHandshakeCallbacks, - conscrypt::jniutil::sslHandshakeCallbacks_onSSLStateChange, type, value); - - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p info_callback exception", ssl); - } - JNI_TRACE("ssl=%p info_callback completed", ssl); + JNI_TRACE("X509_CRLs_to_ItemArray(%p) => %p [size=%zd]", crls, ref_array.get(), size); + return ref_array.release(); } -/** - * Call back to ask for a certificate. There are three possible exit codes: - * - * 1 is success. - * 0 is error. - * -1 is to pause the handshake to continue from the same place later. - */ -static int cert_cb(SSL* ssl, CONSCRYPT_UNUSED void* arg) { - JNI_TRACE("ssl=%p cert_cb", ssl); +#define PKCS7_CERTS 1 +#define PKCS7_CRLS 2 - // cert_cb is called for both clients and servers, but we are only - // interested in client certificates. - if (SSL_is_server(ssl)) { - JNI_TRACE("ssl=%p cert_cb not a client => 1", ssl); - return 1; - } +static jbyteArray NativeCrypto_i2d_PKCS7(JNIEnv* env, jclass, jlongArray certsArray) { + CHECK_ERROR_QUEUE_ON_RETURN; + STACK_OF(X509)* stack = sk_X509_new_null(); - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in cert_cb"); - JNI_TRACE("ssl=%p cert_cb env error => 0", ssl); - return 0; + ScopedLongArrayRO certs(env, certsArray); + for (size_t i = 0; i < certs.size(); i++) { + X509* item = reinterpret_cast(certs[i]); + if (sk_X509_push(stack, item) == 0) { + sk_X509_free(stack); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "sk_X509_push"); + return nullptr; + } } - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p cert_cb already pending exception => 0", ssl); - return 0; + + bssl::ScopedCBB out; + CBB_init(out.get(), 1024 * certs.size()); + if (!PKCS7_bundle_certificates(out.get(), stack)) { + sk_X509_free(stack); + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_bundle_certificates"); + return nullptr; } - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_clientCertificateRequested; + sk_X509_free(stack); - // Call Java callback which can reconfigure the client certificate. - const uint8_t* ctype = nullptr; - size_t ctype_num = SSL_get0_certificate_types(ssl, &ctype); - const uint16_t* sigalgs = nullptr; - size_t sigalgs_num = SSL_get0_peer_verify_algorithms(ssl, &sigalgs); - ScopedLocalRef issuers( - env, CryptoBuffersToObjectArray(env, SSL_get0_server_requested_CAs(ssl))); - if (issuers.get() == nullptr) { - return 0; - } + return CBBToByteArray(env, out.get()); +} - if (conscrypt::trace::kWithJniTrace) { - for (size_t i = 0; i < ctype_num; i++) { - JNI_TRACE("ssl=%p clientCertificateRequested keyTypes[%zu]=%d", ssl, i, ctype[i]); - } - for (size_t i = 0; i < sigalgs_num; i++) { - JNI_TRACE("ssl=%p clientCertificateRequested sigAlgs[%zu]=%d", ssl, i, sigalgs[i]); - } - } +static jlongArray NativeCrypto_PEM_read_bio_PKCS7(JNIEnv* env, jclass, jlong bioRef, jint which) { + CHECK_ERROR_QUEUE_ON_RETURN; + BIO* bio = to_BIO(env, bioRef); + JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p)", bio); - jbyteArray keyTypes = env->NewByteArray(static_cast(ctype_num)); - if (keyTypes == nullptr) { - JNI_TRACE("ssl=%p cert_cb keyTypes == null => 0", ssl); - return 0; + if (bio == nullptr) { + JNI_TRACE("PEM_read_bio_PKCS7_CRLs(%p) => bio == null", bio); + return nullptr; } - env->SetByteArrayRegion(keyTypes, 0, static_cast(ctype_num), - reinterpret_cast(ctype)); - jintArray signatureAlgs = env->NewIntArray(static_cast(sigalgs_num)); - if (signatureAlgs == nullptr) { - JNI_TRACE("ssl=%p cert_cb signatureAlgs == null => 0", ssl); - return 0; - } - { - ScopedIntArrayRW sigAlgsRW(env, signatureAlgs); - for (size_t i = 0; i < sigalgs_num; i++) { - sigAlgsRW[i] = sigalgs[i]; + if (which == PKCS7_CERTS) { + bssl::UniquePtr outCerts(sk_X509_new_null()); + if (!PKCS7_get_PEM_certificates(outCerts.get(), bio)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_get_PEM_certificates"); + return nullptr; + } + return X509s_to_ItemArray(env, outCerts.get()); + } else if (which == PKCS7_CRLS) { + bssl::UniquePtr outCRLs(sk_X509_CRL_new_null()); + if (!PKCS7_get_PEM_CRLs(outCRLs.get(), bio)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "PKCS7_get_PEM_CRLs"); + return nullptr; } + return X509_CRLs_to_ItemArray(env, outCRLs.get()); + } else { + conscrypt::jniutil::throwRuntimeException(env, "unknown PKCS7 field"); + return nullptr; } +} - JNI_TRACE( - "ssl=%p clientCertificateRequested calling clientCertificateRequested " - "keyTypes=%p signatureAlgs=%p issuers=%p", - ssl, keyTypes, signatureAlgs, issuers.get()); - env->CallVoidMethod(sslHandshakeCallbacks, methodID, keyTypes, signatureAlgs, issuers.get()); +static jlongArray NativeCrypto_d2i_PKCS7_bio(JNIEnv* env, jclass, jlong bioRef, jint which) { + CHECK_ERROR_QUEUE_ON_RETURN; + BIO* bio = to_BIO(env, bioRef); + JNI_TRACE("d2i_PKCS7_bio(%p, %d)", bio, which); - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p cert_cb exception => 0", ssl); - return 0; + if (bio == nullptr) { + JNI_TRACE("d2i_PKCS7_bio(%p, %d) => bio == null", bio, which); + return nullptr; } - JNI_TRACE("ssl=%p cert_cb => 1", ssl); - return 1; -} + uint8_t* data; + size_t len; + if (!BIO_read_asn1(bio, &data, &len, 256 * 1024 * 1024 /* max length, 256MB for sanity */)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "Error reading PKCS#7 data", conscrypt::jniutil::throwParsingException); + JNI_TRACE("d2i_PKCS7_bio(%p, %d) => error reading BIO", bio, which); + return nullptr; + } + bssl::UniquePtr data_storage(data); -static enum ssl_select_cert_result_t select_certificate_cb(const SSL_CLIENT_HELLO* client_hello) { - SSL* ssl = client_hello->ssl; - JNI_TRACE("ssl=%p select_certificate_cb_callback", ssl); + CBS cbs; + CBS_init(&cbs, data, len); - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in select_certificate_cb"); - JNI_TRACE("ssl=%p select_certificate_cb env error", ssl); - return ssl_select_cert_error; - } - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p select_certificate_cb already pending exception", ssl); - return ssl_select_cert_error; + if (which == PKCS7_CERTS) { + bssl::UniquePtr outCerts(sk_X509_new_null()); + if (!PKCS7_get_certificates(outCerts.get(), &cbs)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "PKCS7_get_certificates", conscrypt::jniutil::throwParsingException); + JNI_TRACE("d2i_PKCS7_bio(%p, %d) => error reading certs", bio, which); + return nullptr; + } + JNI_TRACE("d2i_PKCS7_bio(%p, %d) => success certs", bio, which); + return X509s_to_ItemArray(env, outCerts.get()); + } else if (which == PKCS7_CRLS) { + bssl::UniquePtr outCRLs(sk_X509_CRL_new_null()); + if (!PKCS7_get_CRLs(outCRLs.get(), &cbs)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "PKCS7_get_CRLs", conscrypt::jniutil::throwParsingException); + JNI_TRACE("d2i_PKCS7_bio(%p, %d) => error reading CRLs", bio, which); + return nullptr; + } + JNI_TRACE("d2i_PKCS7_bio(%p, %d) => success CRLs", bio, which); + return X509_CRLs_to_ItemArray(env, outCRLs.get()); + } else { + conscrypt::jniutil::throwRuntimeException(env, "unknown PKCS7 field"); + return nullptr; } +} - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverCertificateRequested; +static jlongArray NativeCrypto_ASN1_seq_unpack_X509_bio(JNIEnv* env, jclass, jlong bioRef) { + CHECK_ERROR_QUEUE_ON_RETURN; + BIO* bio = to_BIO(env, bioRef); + JNI_TRACE("ASN1_seq_unpack_X509_bio(%p)", bio); - const uint16_t* sigalgs = nullptr; - size_t sigalgs_num = SSL_get0_peer_verify_algorithms(ssl, &sigalgs); + if (bio == nullptr) { + JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => bio == null", bio); + return nullptr; + } - if (sigalgs_num > static_cast(INT_MAX)) { - conscrypt::jniutil::throwRuntimeException(env, "Too many signature algorithms"); - return ssl_select_cert_error; + uint8_t* data; + size_t len; + if (!BIO_read_asn1(bio, &data, &len, 256 * 1024 * 1024 /* max length, 256MB for sanity */)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "Error reading X.509 data", conscrypt::jniutil::throwParsingException); + JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => error reading BIO", bio); + return nullptr; } - jintArray signatureAlgs = env->NewIntArray(static_cast(sigalgs_num)); - if (signatureAlgs == nullptr) { - return ssl_select_cert_error; + bssl::UniquePtr data_storage(data); + + bssl::UniquePtr path(sk_X509_new_null()); + if (path.get() == nullptr) { + JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => failed to make cert stack", bio); + return nullptr; } - { - ScopedIntArrayRW sigAlgsRW(env, signatureAlgs); - if (sigAlgsRW.get() == nullptr) { - return ssl_select_cert_error; + + CBS cbs, sequence; + CBS_init(&cbs, data, len); + if (!CBS_get_asn1(&cbs, &sequence, CBS_ASN1_SEQUENCE)) { + conscrypt::jniutil::throwParsingException(env, "Error reading X.509 data"); + ERR_clear_error(); + return nullptr; + } + + while (CBS_len(&sequence) > 0) { + CBS child; + if (!CBS_get_asn1_element(&sequence, &child, CBS_ASN1_SEQUENCE)) { + conscrypt::jniutil::throwParsingException(env, "Error reading X.509 data"); + ERR_clear_error(); + return nullptr; } - for (size_t i = 0; i < sigalgs_num; i++) { - sigAlgsRW[i] = sigalgs[i]; + + const uint8_t* tmp = CBS_data(&child); + // NOLINTNEXTLINE(runtime/int) + bssl::UniquePtr cert(d2i_X509(nullptr, &tmp, static_cast(CBS_len(&child)))); + if (!cert || tmp != CBS_data(&child) + CBS_len(&child)) { + conscrypt::jniutil::throwParsingException(env, "Error reading X.509 data"); + ERR_clear_error(); + return nullptr; + } + + if (!sk_X509_push(path.get(), cert.get())) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to push local certificate"); + return nullptr; } + OWNERSHIP_TRANSFERRED(cert); } - JNI_TRACE("ssl=%p select_certificate_cb calling serverCertificateRequested", ssl); - env->CallVoidMethod(sslHandshakeCallbacks, methodID, signatureAlgs); + size_t size = sk_X509_num(path.get()); - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p select_certificate_cb exception", ssl); - return ssl_select_cert_error; + ScopedLocalRef certArray(env, env->NewLongArray(static_cast(size))); + ScopedLongArrayRW certs(env, certArray.get()); + for (size_t i = 0; i < size; i++) { + X509* item = reinterpret_cast(sk_X509_shift(path.get())); + certs[i] = reinterpret_cast(item); } - JNI_TRACE("ssl=%p select_certificate_cb completed", ssl); - return ssl_select_cert_success; -} -/** - * Pre-Shared Key (PSK) client callback. - */ -static unsigned int psk_client_callback(SSL* ssl, const char* hint, char* identity, - unsigned int max_identity_len, unsigned char* psk, - unsigned int max_psk_len) { - JNI_TRACE("ssl=%p psk_client_callback", ssl); + JNI_TRACE("ASN1_seq_unpack_X509_bio(%p) => returns %zd items", bio, size); + return certArray.release(); +} - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in psk_client_callback"); - JNI_TRACE("ssl=%p psk_client_callback env error", ssl); - return 0; - } - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p psk_client_callback already pending exception", ssl); - return 0; +static jbyteArray NativeCrypto_ASN1_seq_pack_X509(JNIEnv* env, jclass, jlongArray certs) { + CHECK_ERROR_QUEUE_ON_RETURN; + JNI_TRACE("ASN1_seq_pack_X509(%p)", certs); + ScopedLongArrayRO certsArray(env, certs); + if (certsArray.get() == nullptr) { + JNI_TRACE("ASN1_seq_pack_X509(%p) => failed to get certs array", certs); + return nullptr; } - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_clientPSKKeyRequested; - JNI_TRACE("ssl=%p psk_client_callback calling clientPSKKeyRequested", ssl); - ScopedLocalRef identityHintJava(env, - (hint != nullptr) ? env->NewStringUTF(hint) : nullptr); - ScopedLocalRef identityJava( - env, env->NewByteArray(static_cast(max_identity_len))); - if (identityJava.get() == nullptr) { - JNI_TRACE("ssl=%p psk_client_callback failed to allocate identity bufffer", ssl); - return 0; - } - ScopedLocalRef keyJava(env, env->NewByteArray(static_cast(max_psk_len))); - if (keyJava.get() == nullptr) { - JNI_TRACE("ssl=%p psk_client_callback failed to allocate key bufffer", ssl); - return 0; - } - jint keyLen = env->CallIntMethod(sslHandshakeCallbacks, methodID, identityHintJava.get(), - identityJava.get(), keyJava.get()); - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p psk_client_callback exception", ssl); - return 0; - } - if (keyLen <= 0) { - JNI_TRACE("ssl=%p psk_client_callback failed to get key", ssl); - return 0; - } else if ((unsigned int)keyLen > max_psk_len) { - JNI_TRACE("ssl=%p psk_client_callback got key which is too long", ssl); - return 0; + bssl::ScopedCBB result; + CBB seq_contents; + if (!CBB_init(result.get(), 2048 * certsArray.size())) { + JNI_TRACE("ASN1_seq_pack_X509(%p) => CBB_init failed", certs); + return nullptr; } - ScopedByteArrayRO keyJavaRo(env, keyJava.get()); - if (keyJavaRo.get() == nullptr) { - JNI_TRACE("ssl=%p psk_client_callback failed to get key bytes", ssl); - return 0; + if (!CBB_add_asn1(result.get(), &seq_contents, CBS_ASN1_SEQUENCE)) { + return nullptr; } - memcpy(psk, keyJavaRo.get(), static_cast(keyLen)); - ScopedByteArrayRO identityJavaRo(env, identityJava.get()); - if (identityJavaRo.get() == nullptr) { - JNI_TRACE("ssl=%p psk_client_callback failed to get identity bytes", ssl); - return 0; + for (size_t i = 0; i < certsArray.size(); i++) { + X509* x509 = reinterpret_cast(static_cast(certsArray[i])); + uint8_t* buf; + int len = i2d_X509(x509, nullptr); + + if (len < 0 || !CBB_add_space(&seq_contents, &buf, static_cast(len)) || + i2d_X509(x509, &buf) < 0) { + return nullptr; + } } - memcpy(identity, identityJavaRo.get(), max_identity_len); - JNI_TRACE("ssl=%p psk_client_callback completed", ssl); - return static_cast(keyLen); + return CBBToByteArray(env, result.get()); } -/** - * Pre-Shared Key (PSK) server callback. - */ -static unsigned int psk_server_callback(SSL* ssl, const char* identity, unsigned char* psk, - unsigned int max_psk_len) { - JNI_TRACE("ssl=%p psk_server_callback", ssl); +static void NativeCrypto_X509_free(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_free(%p)", x509); - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in psk_server_callback"); - JNI_TRACE("ssl=%p psk_server_callback env error", ssl); - return 0; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_free(%p) => x509 == null", x509); + return; } - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p psk_server_callback already pending exception", ssl); - return 0; + + X509_free(x509); +} + +static jint NativeCrypto_X509_cmp(JNIEnv* env, jclass, jlong x509Ref1, + CONSCRYPT_UNUSED jobject holder, jlong x509Ref2, + CONSCRYPT_UNUSED jobject holder2) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509_1 = reinterpret_cast(static_cast(x509Ref1)); + X509* x509_2 = reinterpret_cast(static_cast(x509Ref2)); + JNI_TRACE("X509_cmp(%p, %p)", x509_1, x509_2); + + if (x509_1 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509_1 == null"); + JNI_TRACE("X509_cmp(%p, %p) => x509_1 == null", x509_1, x509_2); + return -1; } - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverPSKKeyRequested; - JNI_TRACE("ssl=%p psk_server_callback calling serverPSKKeyRequested", ssl); - const char* identityHint = SSL_get_psk_identity_hint(ssl); - ScopedLocalRef identityHintJava( - env, (identityHint != nullptr) ? env->NewStringUTF(identityHint) : nullptr); - ScopedLocalRef identityJava( - env, (identity != nullptr) ? env->NewStringUTF(identity) : nullptr); - ScopedLocalRef keyJava(env, env->NewByteArray(static_cast(max_psk_len))); - if (keyJava.get() == nullptr) { - JNI_TRACE("ssl=%p psk_server_callback failed to allocate key bufffer", ssl); - return 0; - } - jint keyLen = env->CallIntMethod(sslHandshakeCallbacks, methodID, identityHintJava.get(), - identityJava.get(), keyJava.get()); - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p psk_server_callback exception", ssl); - return 0; - } - if (keyLen <= 0) { - JNI_TRACE("ssl=%p psk_server_callback failed to get key", ssl); - return 0; - } else if ((unsigned int)keyLen > max_psk_len) { - JNI_TRACE("ssl=%p psk_server_callback got key which is too long", ssl); - return 0; - } - ScopedByteArrayRO keyJavaRo(env, keyJava.get()); - if (keyJavaRo.get() == nullptr) { - JNI_TRACE("ssl=%p psk_server_callback failed to get key bytes", ssl); - return 0; + if (x509_2 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509_2 == null"); + JNI_TRACE("X509_cmp(%p, %p) => x509_2 == null", x509_1, x509_2); + return -1; } - memcpy(psk, keyJavaRo.get(), static_cast(keyLen)); - JNI_TRACE("ssl=%p psk_server_callback completed", ssl); - return static_cast(keyLen); + int ret = X509_cmp(x509_1, x509_2); + JNI_TRACE("X509_cmp(%p, %p) => %d", x509_1, x509_2, ret); + return ret; } -static int new_session_callback(SSL* ssl, SSL_SESSION* session) { - JNI_TRACE("ssl=%p new_session_callback session=%p", ssl, session); +static void NativeCrypto_X509_print_ex(JNIEnv* env, jclass, jlong bioRef, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder, jlong nmflagJava, + jlong certflagJava) { + CHECK_ERROR_QUEUE_ON_RETURN; + BIO* bio = to_BIO(env, bioRef); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + // NOLINTNEXTLINE(runtime/int) + unsigned long nmflag = static_cast(nmflagJava); + // NOLINTNEXTLINE(runtime/int) + unsigned long certflag = static_cast(certflagJava); + JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld)", bio, x509, nmflag, certflag); - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in new_session_callback"); - JNI_TRACE("ssl=%p new_session_callback env error", ssl); - return 0; - } - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p new_session_callback already pending exception", ssl); - return 0; + if (bio == nullptr) { + JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => bio == null", bio, x509, nmflag, certflag); + return; } - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_onNewSessionEstablished; - JNI_TRACE("ssl=%p new_session_callback calling onNewSessionEstablished", ssl); - env->CallVoidMethod(sslHandshakeCallbacks, methodID, reinterpret_cast(session)); - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p new_session_callback exception cleared", ssl); - env->ExceptionClear(); + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => x509 == null", bio, x509, nmflag, certflag); + return; } - JNI_TRACE("ssl=%p new_session_callback completed", ssl); - // Always returning 0 (not taking ownership). The Java code is responsible for - // incrementing the reference count. - return 0; + if (!X509_print_ex(bio, x509, nmflag, certflag)) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "X509_print_ex"); + JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => threw error", bio, x509, nmflag, certflag); + return; + } + JNI_TRACE("X509_print_ex(%p, %p, %ld, %ld) => success", bio, x509, nmflag, certflag); } -static SSL_SESSION* server_session_requested_callback(SSL* ssl, const uint8_t* id, int id_len, - int* out_copy) { - JNI_TRACE("ssl=%p server_session_requested_callback", ssl); - - // Always set to out_copy to zero. The Java callback will be responsible for - // incrementing the reference count (and any required synchronization). - *out_copy = 0; +static jlong NativeCrypto_X509_get_pubkey(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_pubkey(%p)", x509); - AppData* appData = toAppData(ssl); - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in server_session_requested_callback"); - JNI_TRACE("ssl=%p server_session_requested_callback env error", ssl); - return 0; - } - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p server_session_requested_callback already pending exception", ssl); + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_get_pubkey(%p) => x509 == null", x509); return 0; } - // Copy the ID to a byte[]. - jbyteArray id_array = env->NewByteArray(static_cast(id_len)); - if (id_array == nullptr) { - JNI_TRACE("ssl=%p id_array bytes == null => 0", ssl); + bssl::UniquePtr pkey(X509_get_pubkey(x509)); + if (pkey.get() == nullptr) { + const uint32_t last_error = ERR_peek_last_error(); + const uint32_t first_error = ERR_peek_error(); + if ((ERR_GET_LIB(last_error) == ERR_LIB_EVP && + ERR_GET_REASON(last_error) == EVP_R_UNKNOWN_PUBLIC_KEY_TYPE) || + (ERR_GET_LIB(first_error) == ERR_LIB_EC && + ERR_GET_REASON(first_error) == EC_R_UNKNOWN_GROUP)) { + ERR_clear_error(); + conscrypt::jniutil::throwNoSuchAlgorithmException(env, "X509_get_pubkey"); + return 0; + } + + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "X509_get_pubkey", conscrypt::jniutil::throwInvalidKeyException); return 0; } - env->SetByteArrayRegion(id_array, 0, static_cast(id_len), - reinterpret_cast(id)); - jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverSessionRequested; - JNI_TRACE("ssl=%p server_session_requested_callback calling serverSessionRequested", ssl); - jlong ssl_session_address = env->CallLongMethod(sslHandshakeCallbacks, methodID, id_array); - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p server_session_requested_callback exception cleared", ssl); - env->ExceptionClear(); - } - SSL_SESSION* ssl_session_ptr = - reinterpret_cast(static_cast(ssl_session_address)); - JNI_TRACE("ssl=%p server_session_requested_callback completed => %p", ssl, ssl_session_ptr); - return ssl_session_ptr; + JNI_TRACE("X509_get_pubkey(%p) => %p", x509, pkey.get()); + return reinterpret_cast(pkey.release()); } -static jint NativeCrypto_EVP_has_aes_hardware(JNIEnv* env, jclass) { +static jbyteArray NativeCrypto_X509_get_issuer_name(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - int ret = 0; - ret = EVP_has_aes_hardware(); - JNI_TRACE("EVP_has_aes_hardware => %d", ret); - return ret; -} + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_issuer_name(%p)", x509); -static void debug_print_session_key(const SSL* ssl, const char* line) { - JNI_TRACE_KEYS("ssl=%p KEY_LINE: %s", ssl, line); + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_get_issuer_name(%p) => x509 == null", x509); + return nullptr; + } + return ASN1ToByteArray(env, X509_get_issuer_name(x509), i2d_X509_NAME); } -static void debug_print_packet_data(const SSL* ssl, char direction, const char* data, size_t len) { - static constexpr size_t kDataWidth = 16; +static jbyteArray NativeCrypto_X509_get_subject_name(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_subject_name(%p)", x509); - struct timeval tv; - if (gettimeofday(&tv, NULL)) { - CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", - "debug_print_packet_data: could not get time of day"); - return; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("X509_get_subject_name(%p) => x509 == null", x509); + return nullptr; } + return ASN1ToByteArray(env, X509_get_subject_name(x509), i2d_X509_NAME); +} - // Packet preamble for text2pcap - CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", "ssl=%p SSL_DATA: %c %ld.%06ld", ssl, direction, - static_cast(tv.tv_sec), - static_cast(tv.tv_usec)); // NOLINT(runtime/int) +static jstring NativeCrypto_get_X509_pubkey_oid(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_pubkey_oid(%p)", x509); - char out[kDataWidth * 3 + 1]; - for (size_t i = 0; i < len; i += kDataWidth) { - size_t n = len - i < kDataWidth ? len - i : kDataWidth; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_pubkey_oid(%p) => x509 == null", x509); + return nullptr; + } - for (size_t j = 0, offset = 0; j < n; j++, offset += 3) { - int ret = snprintf(out + offset, sizeof(out) - offset, "%02x ", data[i + j] & 0xFF); - if (ret < 0 || static_cast(ret) >= sizeof(out) - offset) { - CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", - "debug_print_packet_data failed to output %d", ret); - return; - } - } + X509_PUBKEY* pubkey = X509_get_X509_PUBKEY(x509); + ASN1_OBJECT* algorithm; + X509_PUBKEY_get0_param(&algorithm, nullptr, nullptr, nullptr, pubkey); + return ASN1_OBJECT_to_OID_string(env, algorithm); +} - // Print out packet data in format understood by text2pcap - CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", "ssl=%p SSL_DATA: %06zx %s", ssl, i, out); +static jstring NativeCrypto_get_X509_sig_alg_oid(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_sig_alg_oid(%p)", x509); + + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null || x509->sig_alg == null"); + JNI_TRACE("get_X509_sig_alg_oid(%p) => x509 == null", x509); + return nullptr; } - // Conclude the packet data - CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", "ssl=%p SSL_DATA: %06zx", ssl, len); + const X509_ALGOR* sig_alg; + X509_get0_signature(nullptr, &sig_alg, x509); + const ASN1_OBJECT* oid; + X509_ALGOR_get0(&oid, nullptr, nullptr, sig_alg); + return ASN1_OBJECT_to_OID_string(env, oid); } -/* - * public static native int SSL_CTX_new(); - */ -static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) { +static jbyteArray NativeCrypto_get_X509_sig_alg_parameter(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - bssl::UniquePtr sslCtx(SSL_CTX_new(TLS_with_buffers_method())); - if (sslCtx.get() == nullptr) { - conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SSL_CTX_new"); - return 0; - } - SSL_CTX_set_options( - sslCtx.get(), - SSL_OP_ALL - // We also disable session tickets for better compatibility b/2682876 - | SSL_OP_NO_TICKET - // We also disable compression for better compatibility b/2710492 - // b/2710497 - | SSL_OP_NO_COMPRESSION - // Generate a fresh ECDH keypair for each key exchange. - | SSL_OP_SINGLE_ECDH_USE); - SSL_CTX_set_min_proto_version(sslCtx.get(), TLS1_VERSION); - SSL_CTX_set_max_proto_version(sslCtx.get(), TLS1_2_VERSION); + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_sig_alg_parameter(%p)", x509); - uint32_t mode = SSL_CTX_get_mode(sslCtx.get()); - /* - * Turn on "partial write" mode. This means that SSL_write() will - * behave like Posix write() and possibly return after only - * writing a partial buffer. Note: The alternative, perhaps - * surprisingly, is not that SSL_write() always does full writes - * but that it will force you to retry write calls having - * preserved the full state of the original call. (This is icky - * and undesirable.) - */ - mode |= SSL_MODE_ENABLE_PARTIAL_WRITE; - - // Reuse empty buffers within the SSL_CTX to save memory - mode |= SSL_MODE_RELEASE_BUFFERS; - - // Enable False Start. - mode |= SSL_MODE_ENABLE_FALSE_START; - - // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address - // may change between calls to wrap(...). See - // https://github.com/netty/netty-tcnative/issues/100 - mode |= SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; - - SSL_CTX_set_mode(sslCtx.get(), mode); - - SSL_CTX_set_info_callback(sslCtx.get(), info_callback); - SSL_CTX_set_cert_cb(sslCtx.get(), cert_cb, nullptr); - SSL_CTX_set_select_certificate_cb(sslCtx.get(), select_certificate_cb); - if (conscrypt::trace::kWithJniTraceKeys) { - SSL_CTX_set_keylog_callback(sslCtx.get(), debug_print_session_key); + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_sig_alg_parameter(%p) => x509 == null", x509); + return nullptr; } - // By default BoringSSL will cache in server mode, but we want to get - // notified of new sessions being created in client mode. We set - // SSL_SESS_CACHE_BOTH in order to get the callback in client mode, but - // ignore it in server mode in favor of the internal cache. - SSL_CTX_set_session_cache_mode(sslCtx.get(), SSL_SESS_CACHE_BOTH); - SSL_CTX_sess_set_new_cb(sslCtx.get(), new_session_callback); - SSL_CTX_sess_set_get_cb(sslCtx.get(), server_session_requested_callback); - - JNI_TRACE("NativeCrypto_SSL_CTX_new => %p", sslCtx.get()); - return (jlong)sslCtx.release(); + const X509_ALGOR* sig_alg; + X509_get0_signature(nullptr, &sig_alg, x509); + return get_X509_ALGOR_parameter(env, sig_alg); } -/** - * public static native void SSL_CTX_free(long ssl_ctx) - */ -static void NativeCrypto_SSL_CTX_free(JNIEnv* env, jclass, jlong ssl_ctx_address, - CONSCRYPT_UNUSED jobject holder) { +static jbooleanArray NativeCrypto_get_X509_issuerUID(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_free", ssl_ctx); - if (ssl_ctx == nullptr) { - return; - } - SSL_CTX_free(ssl_ctx); -} + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_issuerUID(%p)", x509); -static void NativeCrypto_SSL_CTX_set_session_id_context(JNIEnv* env, jclass, jlong ssl_ctx_address, - CONSCRYPT_UNUSED jobject holder, - jbyteArray sid_ctx) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context sid_ctx=%p", ssl_ctx, - sid_ctx); - if (ssl_ctx == nullptr) { - return; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_issuerUID(%p) => x509 == null", x509); + return nullptr; } - ScopedByteArrayRO buf(env, sid_ctx); - if (buf.get() == nullptr) { - JNI_TRACE( - "ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => threw " - "exception", - ssl_ctx); - return; + const ASN1_BIT_STRING* issuer_uid; + X509_get0_uids(x509, &issuer_uid, /*out_subject_uid=*/nullptr); + if (issuer_uid == nullptr) { + JNI_TRACE("get_X509_issuerUID(%p) => null", x509); + return nullptr; } - unsigned int length = static_cast(buf.size()); - if (length > SSL_MAX_SSL_SESSION_ID_LENGTH) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "length > SSL_MAX_SSL_SESSION_ID_LENGTH"); - JNI_TRACE("NativeCrypto_SSL_CTX_set_session_id_context => length = %d", length); - return; - } - const unsigned char* bytes = reinterpret_cast(buf.get()); - int result = SSL_CTX_set_session_id_context(ssl_ctx, bytes, length); - if (result == 0) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "NativeCrypto_SSL_CTX_set_session_id_context"); - return; - } - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => ok", ssl_ctx); + return ASN1BitStringToBooleanArray(env, issuer_uid); } -static jlong NativeCrypto_SSL_CTX_set_timeout(JNIEnv* env, jclass, jlong ssl_ctx_address, - CONSCRYPT_UNUSED jobject holder, jlong seconds) { +static jbooleanArray NativeCrypto_get_X509_subjectUID(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_timeout seconds=%d", ssl_ctx, (int)seconds); - if (ssl_ctx == nullptr) { - return 0L; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_subjectUID(%p)", x509); + + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_subjectUID(%p) => x509 == null", x509); + return nullptr; } - return SSL_CTX_set_timeout(ssl_ctx, static_cast(seconds)); + const ASN1_BIT_STRING* subject_uid; + X509_get0_uids(x509, /*out_issuer_uid=*/nullptr, &subject_uid); + if (subject_uid == nullptr) { + JNI_TRACE("get_X509_subjectUID(%p) => null", x509); + return nullptr; + } + + return ASN1BitStringToBooleanArray(env, subject_uid); } -/** - * public static native int SSL_new(long ssl_ctx) throws SSLException; - */ -static jlong NativeCrypto_SSL_new(JNIEnv* env, jclass, jlong ssl_ctx_address, - CONSCRYPT_UNUSED jobject holder) { +static jbooleanArray NativeCrypto_get_X509_ex_kusage(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new", ssl_ctx); - if (ssl_ctx == nullptr) { - return 0; - } - bssl::UniquePtr ssl(SSL_new(ssl_ctx)); - if (ssl.get() == nullptr) { - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, nullptr, SSL_ERROR_NONE, - "Unable to create SSL structure"); - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => null", ssl_ctx); - return 0; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_ex_kusage(%p)", x509); + + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_ex_kusage(%p) => x509 == null", x509); + return nullptr; } - /* - * Create our special application data. - */ - AppData* appData = AppData::create(); - if (appData == nullptr) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to create application data"); + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and + // remove |ERR_clear_error|. Note X509Certificate.getKeyUsage() cannot throw + // CertificateParsingException, so this needs to be checked earlier, e.g. in + // the constructor. + bssl::UniquePtr bitStr( + static_cast(X509_get_ext_d2i(x509, NID_key_usage, nullptr, nullptr))); + if (bitStr.get() == nullptr) { + JNI_TRACE("get_X509_ex_kusage(%p) => null", x509); ERR_clear_error(); - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new appData => 0", ssl_ctx); - return 0; + return nullptr; } - SSL_set_app_data(ssl.get(), reinterpret_cast(appData)); - - SSL_set_custom_verify(ssl.get(), SSL_VERIFY_PEER, cert_verify_callback); - JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => ssl=%p appData=%p", ssl_ctx, ssl.get(), appData); - return (jlong)ssl.release(); + return ASN1BitStringToBooleanArray(env, bitStr.get()); } -static void NativeCrypto_SSL_enable_tls_channel_id(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { +static jobjectArray NativeCrypto_get_X509_ex_xkusage(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_tls_channel_id", ssl); - if (ssl == nullptr) { - return; - } + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_ex_xkusage(%p)", x509); - // NOLINTNEXTLINE(runtime/int) - long ret = SSL_enable_tls_channel_id(ssl); - if (ret != 1L) { - CONSCRYPT_LOG_ERROR("%s", ERR_error_string(ERR_peek_error(), nullptr)); - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, - "Error enabling Channel ID"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_tls_channel_id => error", ssl); - return; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_ex_xkusage(%p) => x509 == null", x509); + return nullptr; } -} -static jbyteArray NativeCrypto_SSL_get_tls_channel_id(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_tls_channel_id", ssl); - if (ssl == nullptr) { + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors, remove + // |ERR_clear_error|, and throw CertificateParsingException. + bssl::UniquePtr objArray(static_cast( + X509_get_ext_d2i(x509, NID_ext_key_usage, nullptr, nullptr))); + if (objArray.get() == nullptr) { + JNI_TRACE("get_X509_ex_xkusage(%p) => null", x509); + ERR_clear_error(); return nullptr; } - // Channel ID is 64 bytes long. Unfortunately, OpenSSL doesn't declare this - // length as a constant anywhere. - jbyteArray javaBytes = env->NewByteArray(64); - ScopedByteArrayRW bytes(env, javaBytes); - if (bytes.get() == nullptr) { - JNI_TRACE("NativeCrypto_SSL_get_tls_channel_id(%p) => null", ssl); + size_t size = sk_ASN1_OBJECT_num(objArray.get()); + ScopedLocalRef exKeyUsage( + env, env->NewObjectArray(static_cast(size), conscrypt::jniutil::stringClass, + nullptr)); + if (exKeyUsage.get() == nullptr) { return nullptr; } - unsigned char* tmp = reinterpret_cast(bytes.get()); - // Unfortunately, the SSL_get_tls_channel_id method below always returns 64 - // (upon success) regardless of the number of bytes copied into the output - // buffer "tmp". Thus, the correctness of this code currently relies on the - // "tmp" buffer being exactly 64 bytes long. - size_t ret = SSL_get_tls_channel_id(ssl, tmp, 64); - if (ret == 0) { - // Channel ID either not set or did not verify - JNI_TRACE("NativeCrypto_SSL_get_tls_channel_id(%p) => not available", ssl); - return nullptr; - } else if (ret != 64) { - CONSCRYPT_LOG_ERROR("%s", ERR_error_string(ERR_peek_error(), nullptr)); - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, - "Error getting Channel ID"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_tls_channel_id => error, returned %zd", ssl, ret); - return nullptr; + for (size_t i = 0; i < size; i++) { + ScopedLocalRef oidStr( + env, ASN1_OBJECT_to_OID_string(env, sk_ASN1_OBJECT_value(objArray.get(), i))); + env->SetObjectArrayElement(exKeyUsage.get(), static_cast(i), oidStr.get()); } - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_tls_channel_id() => %p", ssl, javaBytes); - return javaBytes; + JNI_TRACE("get_X509_ex_xkusage(%p) => success (%zd entries)", x509, size); + return exKeyUsage.release(); } -static void NativeCrypto_SSL_set1_tls_channel_id(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jobject pkeyRef) { +static jint NativeCrypto_get_X509_ex_pathlen(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p SSL_set1_tls_channel_id privatekey=%p", ssl, pkeyRef); - if (ssl == nullptr) { - return; - } + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509_ex_pathlen(%p)", x509); - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - if (pkey == nullptr) { - JNI_TRACE("ssl=%p SSL_set1_tls_channel_id => pkey == null", ssl); - return; + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509_ex_pathlen(%p) => x509 == null", x509); + return 0; } - // NOLINTNEXTLINE(runtime/int) - long ret = SSL_set1_tls_channel_id(ssl, pkey); - - if (ret != 1L) { - CONSCRYPT_LOG_ERROR("%s", ERR_error_string(ERR_peek_error(), nullptr)); - conscrypt::jniutil::throwSSLExceptionWithSslErrors( - env, ssl, SSL_ERROR_NONE, "Error setting private key for Channel ID"); - JNI_TRACE("ssl=%p SSL_set1_tls_channel_id => error", ssl); - return; + // Use |X509_get_ext_d2i| instead of |X509_get_pathlen| because the latter + // treats |EXFLAG_INVALID| as the error case. |EXFLAG_INVALID| is set if any + // built-in extension is invalid. For now, we preserve Conscrypt's historical + // behavior in accepting certificates in the constructor even if + // |EXFLAG_INVALID| is set. + // + // TODO(https://github.com/google/conscrypt/issues/916): Handle errors and + // remove |ERR_clear_error|. Note X509Certificate.getBasicConstraints() cannot + // throw CertificateParsingException, so this needs to be checked earlier, + // e.g. in the constructor. + bssl::UniquePtr basic_constraints(static_cast( + X509_get_ext_d2i(x509, NID_basic_constraints, nullptr, nullptr))); + if (basic_constraints == nullptr) { + JNI_TRACE("get_X509_ex_path(%p) => -1 (no extension or error)", x509); + ERR_clear_error(); + return -1; } - JNI_TRACE("ssl=%p SSL_set1_tls_channel_id => ok", ssl); -} - -static void NativeCrypto_setLocalCertsAndPrivateKey(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jobjectArray encodedCertificatesJava, - jobject pkeyRef) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_set_chain_and_key certificates=%p, " - "privateKey=%p", - ssl, encodedCertificatesJava, pkeyRef); - if (ssl == nullptr) { - return; - } - if (encodedCertificatesJava == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "certificates == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => certificates == null", ssl); - return; - } - size_t numCerts = static_cast(env->GetArrayLength(encodedCertificatesJava)); - if (numCerts == 0) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "certificates.length == 0"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => certificates.length == 0", ssl); - return; + if (basic_constraints->pathlen == nullptr) { + JNI_TRACE("get_X509_ex_path(%p) => -1 (no pathLenConstraint)", x509); + return -1; } - if (pkeyRef == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "privateKey == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => privateKey == null", ssl); - return; + + if (!basic_constraints->ca) { + // Path length constraints are only valid for CA certificates. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. + JNI_TRACE("get_X509_ex_path(%p) => -1 (not a CA)", x509); + return -1; } - // Get the private key. - EVP_PKEY* pkey = fromContextObject(env, pkeyRef); - if (pkey == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "pkey == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => pkey == null", ssl); - return; + if (basic_constraints->pathlen->type == V_ASN1_NEG_INTEGER) { + // Path length constraints may not be negative. + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. + JNI_TRACE("get_X509_ex_path(%p) => -1 (negative)", x509); + return -1; } - // Copy the certificates. - std::vector> certBufferRefs(numCerts); - std::vector certBuffers(numCerts); - for (size_t i = 0; i < numCerts; ++i) { - ScopedLocalRef certArray( - env, reinterpret_cast( - env->GetObjectArrayElement(encodedCertificatesJava, i))); - certBufferRefs[i] = ByteArrayToCryptoBuffer(env, certArray.get(), nullptr); - if (!certBufferRefs[i]) { - return; - } - certBuffers[i] = certBufferRefs[i].get(); + long pathlen = ASN1_INTEGER_get(basic_constraints->pathlen); + if (pathlen == -1 || pathlen > INT_MAX) { + // TODO(https://github.com/google/conscrypt/issues/916): Treat this as an + // error condition. If the integer overflows, the certificate is effectively + // unconstrained. Reporting no constraint is plausible, but Chromium rejects + // all values above 255. + JNI_TRACE("get_X509_ex_path(%p) => -1 (overflow)", x509); + return -1; } - if (!SSL_set_chain_and_key(ssl, certBuffers.data(), numCerts, pkey, nullptr)) { - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, - "Error configuring certificate"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => error", ssl); - return; + JNI_TRACE("get_X509_ex_path(%p) => %ld", x509, pathlen); + return pathlen; +} + +static jbyteArray NativeCrypto_X509_get_ext_oid(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder, + jstring oidString) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("X509_get_ext_oid(%p, %p)", x509, oidString); + return X509Type_get_ext_oid(env, x509, oidString); +} + +static jbyteArray NativeCrypto_X509_CRL_get_ext_oid(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder, + jstring oidString) { + CHECK_ERROR_QUEUE_ON_RETURN; + X509_CRL* crl = reinterpret_cast(static_cast(x509CrlRef)); + JNI_TRACE("X509_CRL_get_ext_oid(%p, %p)", crl, oidString); + + if (crl == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "crl == null"); + JNI_TRACE("X509_CRL_get_ext_oid(%p) => crl == null", crl); + return nullptr; } - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => ok", ssl); + return X509Type_get_ext_oid(env, crl, + oidString); } -static void NativeCrypto_SSL_set_client_CA_list(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jobjectArray principals) { +static jbyteArray NativeCrypto_X509_REVOKED_get_ext_oid(JNIEnv* env, jclass, jlong x509RevokedRef, + jstring oidString, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list principals=%p", ssl, principals); - if (ssl == nullptr) { - return; + X509_REVOKED* revoked = reinterpret_cast(static_cast(x509RevokedRef)); + JNI_TRACE("X509_REVOKED_get_ext_oid(%p, %p)", revoked, oidString); + + if (revoked == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "revoked == null"); + JNI_TRACE("X509_REVOKED_get_ext_oid(%p) => revoked == null", revoked); + return nullptr; } + return X509Type_get_ext_oid( + env, revoked, oidString); +} - if (principals == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "principals == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => principals == null", ssl); - return; +template +static jobjectArray get_X509Type_ext_oids(JNIEnv* env, jlong x509Ref, jint critical) { + T* x509 = reinterpret_cast(static_cast(x509Ref)); + JNI_TRACE("get_X509Type_ext_oids(%p, %d)", x509, critical); + + if (x509 == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "x509 == null"); + JNI_TRACE("get_X509Type_ext_oids(%p, %d) => x509 == null", x509, critical); + return nullptr; } - int length = env->GetArrayLength(principals); - if (length == 0) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "principals.length == 0"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => principals.length == 0", ssl); - return; + int lastPos = -1; + int count = 0; + while ((lastPos = get_ext_by_critical_func(x509, critical, lastPos)) != -1) { + count++; } - bssl::UniquePtr principalsStack(sk_CRYPTO_BUFFER_new_null()); - if (principalsStack.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate principal stack"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => stack allocation error", ssl); - return; + JNI_TRACE("get_X509Type_ext_oids(%p, %d) has %d entries", x509, critical, count); + + ScopedLocalRef joa( + env, env->NewObjectArray(count, conscrypt::jniutil::stringClass, nullptr)); + if (joa.get() == nullptr) { + JNI_TRACE("get_X509Type_ext_oids(%p, %d) => fail to allocate result array", x509, critical); + return nullptr; } - for (int i = 0; i < length; i++) { - ScopedLocalRef principal( - env, reinterpret_cast(env->GetObjectArrayElement(principals, i))); - bssl::UniquePtr buf = ByteArrayToCryptoBuffer(env, principal.get(), nullptr); - if (!buf) { - return; - } - if (!sk_CRYPTO_BUFFER_push(principalsStack.get(), buf.get())) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to push principal"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => principal push error", ssl); - return; + + lastPos = -1; + count = 0; + while ((lastPos = get_ext_by_critical_func(x509, critical, lastPos)) != -1) { + X509_EXTENSION* ext = get_ext_func(x509, lastPos); + + ScopedLocalRef extOid( + env, ASN1_OBJECT_to_OID_string(env, X509_EXTENSION_get_object(ext))); + if (extOid.get() == nullptr) { + JNI_TRACE("get_X509Type_ext_oids(%p) => couldn't get OID", x509); + return nullptr; } - OWNERSHIP_TRANSFERRED(buf); + + env->SetObjectArrayElement(joa.get(), count++, extOid.get()); } - SSL_set0_client_CAs(ssl, principalsStack.release()); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => ok", ssl); + JNI_TRACE("get_X509Type_ext_oids(%p, %d) => success", x509, critical); + return joa.release(); } -/** - * public static native long SSL_set_mode(long ssl, long mode); - */ -static jlong NativeCrypto_SSL_set_mode(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jlong mode) { +static jobjectArray NativeCrypto_get_X509_ext_oids(JNIEnv* env, jclass, jlong x509Ref, + CONSCRYPT_UNUSED jobject holder, jint critical) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_mode mode=0x%llx", ssl, (long long)mode); - if (ssl == nullptr) { - return 0; - } - jlong result = static_cast(SSL_set_mode(ssl, static_cast(mode))); // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_mode => 0x%lx", ssl, (long)result); - return result; + JNI_TRACE("get_X509_ext_oids(0x%llx, %d)", (long long)x509Ref, critical); + return get_X509Type_ext_oids(env, x509Ref, + critical); } -/** - * public static native long SSL_set_options(long ssl, long options); - */ -static jlong NativeCrypto_SSL_set_options(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jlong options) { +static jobjectArray NativeCrypto_get_X509_CRL_ext_oids(JNIEnv* env, jclass, jlong x509CrlRef, + CONSCRYPT_UNUSED jobject holder, + jint critical) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_options options=0x%llx", ssl, (long long)options); - if (ssl == nullptr) { - return 0; - } - jlong result = static_cast(SSL_set_options(ssl, static_cast(options))); // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_options => 0x%lx", ssl, (long)result); - return result; + JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", (long long)x509CrlRef, critical); + return get_X509Type_ext_oids( + env, x509CrlRef, critical); } -/** - * public static native long SSL_clear_options(long ssl, long options); - */ -static jlong NativeCrypto_SSL_clear_options(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jlong options) { +static jobjectArray NativeCrypto_get_X509_REVOKED_ext_oids(JNIEnv* env, jclass, + jlong x509RevokedRef, jint critical, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_options options=0x%llx", ssl, (long long)options); - if (ssl == nullptr) { - return 0; - } - jlong result = static_cast(SSL_clear_options(ssl, static_cast(options))); // NOLINTNEXTLINE(runtime/int) - JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_options => 0x%lx", ssl, (long)result); - return result; -} - -static jint NativeCrypto_SSL_set_protocol_versions(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jint min_version, jint max_version) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_protocol_versions min=0x%x max=0x%x", ssl, min_version, - max_version); - if (ssl == nullptr) { - return 0; - } - int min_result = SSL_set_min_proto_version(ssl, static_cast(min_version)); - int max_result = SSL_set_max_proto_version(ssl, static_cast(max_version)); - // Return failure if either call failed. - int result = 1; - if (!min_result || !max_result) { - result = 0; - // The only possible error is an invalid version, so we don't need the - // details. - ERR_clear_error(); - } - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_set_protocol_versions => (min: %d, max: %d) == " - "%d", - ssl, min_result, max_result, result); - return result; + JNI_TRACE("get_X509_CRL_ext_oids(0x%llx, %d)", (long long)x509RevokedRef, critical); + return get_X509Type_ext_oids(env, x509RevokedRef, critical); } /** - * public static native void SSL_enable_signed_cert_timestamps(long ssl); + * Based on example logging call back from SSL_CTX_set_info_callback man page */ -static void NativeCrypto_SSL_enable_signed_cert_timestamps(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_signed_cert_timestamps", ssl); - if (ssl == nullptr) { - return; +static void info_callback_LOG(const SSL* s, int where, int ret) { + int w = where & ~SSL_ST_MASK; + const char* str; + if (w & SSL_ST_CONNECT) { + str = "SSL_connect"; + } else if (w & SSL_ST_ACCEPT) { + str = "SSL_accept"; + } else { + str = "undefined"; } - SSL_enable_signed_cert_timestamps(ssl); + if (where & SSL_CB_LOOP) { + JNI_TRACE("ssl=%p %s:%s %s", s, str, SSL_state_string(s), SSL_state_string_long(s)); + } else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + JNI_TRACE("ssl=%p SSL3 alert %s %s %s", s, str, SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) { + JNI_TRACE("ssl=%p %s:failed exit in %s %s", s, str, SSL_state_string(s), + SSL_state_string_long(s)); + } else if (ret < 0) { + JNI_TRACE("ssl=%p %s:error exit in %s %s", s, str, SSL_state_string(s), + SSL_state_string_long(s)); + } else if (ret == 1) { + JNI_TRACE("ssl=%p %s:ok exit in %s %s", s, str, SSL_state_string(s), + SSL_state_string_long(s)); + } else { + JNI_TRACE("ssl=%p %s:unknown exit %d in %s %s", s, str, ret, SSL_state_string(s), + SSL_state_string_long(s)); + } + } else if (where & SSL_CB_HANDSHAKE_START) { + JNI_TRACE("ssl=%p handshake start in %s %s", s, SSL_state_string(s), + SSL_state_string_long(s)); + } else if (where & SSL_CB_HANDSHAKE_DONE) { + JNI_TRACE("ssl=%p handshake done in %s %s", s, SSL_state_string(s), + SSL_state_string_long(s)); + } else { + JNI_TRACE("ssl=%p %s:unknown where %d in %s %s", s, str, where, SSL_state_string(s), + SSL_state_string_long(s)); + } } /** - * public static native byte[] SSL_get_signed_cert_timestamp_list(long ssl); + * Helper function that wakes up a thread blocked in select(), in case there is + * one. Is being called by sslRead() and sslWrite() as well as by JNI glue + * before closing the connection. + * + * @param data The application data structure with mutex info etc. */ -static jbyteArray NativeCrypto_SSL_get_signed_cert_timestamp_list( - JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_signed_cert_timestamp_list", ssl); - if (ssl == nullptr) { - return nullptr; - } +static void sslNotify(AppData* appData) { +#ifdef _WIN32 + SetEvent(appData->interruptEvent); +#else + // Write a byte to the emergency pipe, so a concurrent select() can return. + // Note we have to restore the errno of the original system call, since the + // caller relies on it for generating error messages. + int errnoBackup = errno; + char token = '*'; + do { + errno = 0; + CONSCRYPT_UNUSED int n = write(appData->fdsEmergency[1], &token, 1); + } while (errno == EINTR); + errno = errnoBackup; +#endif +} - const uint8_t* data; - size_t data_len; - SSL_get0_signed_cert_timestamp_list(ssl, &data, &data_len); +static AppData* toAppData(const SSL* ssl) { + return reinterpret_cast(SSL_get_app_data(ssl)); +} - if (data_len == 0) { - JNI_TRACE("NativeCrypto_SSL_get_signed_cert_timestamp_list(%p) => null", ssl); - return nullptr; +static ssl_verify_result_t cert_verify_callback(SSL* ssl, CONSCRYPT_UNUSED uint8_t* out_alert) { + JNI_TRACE("ssl=%p cert_verify_callback", ssl); + + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in cert_verify_callback"); + JNI_TRACE("ssl=%p cert_verify_callback => 0", ssl); + return ssl_verify_invalid; } - jbyteArray result = env->NewByteArray(static_cast(data_len)); - if (result != nullptr) { - env->SetByteArrayRegion(result, 0, static_cast(data_len), (const jbyte*)data); + // Create the byte[][] array that holds all the certs + ScopedLocalRef array( + env, CryptoBuffersToObjectArray(env, SSL_get0_peer_certificates(ssl))); + if (array.get() == nullptr) { + return ssl_verify_invalid; } + + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_verifyCertificateChain; + + const SSL_CIPHER* cipher = SSL_get_pending_cipher(ssl); + const char* authMethod = SSL_CIPHER_get_kx_name(cipher); + + JNI_TRACE( + "ssl=%p cert_verify_callback calling verifyCertificateChain " + "authMethod=%s", + ssl, authMethod); + ScopedLocalRef authMethodString(env, env->NewStringUTF(authMethod)); + env->CallVoidMethod(sslHandshakeCallbacks, methodID, array.get(), authMethodString.get()); + + ssl_verify_result_t result = env->ExceptionCheck() ? ssl_verify_invalid : ssl_verify_ok; + JNI_TRACE("ssl=%p cert_verify_callback => %d", ssl, result); return result; } -/* - * public static native void SSL_set_signed_cert_timestamp_list(long ssl, byte[] - * response); +/** + * Call back to watch for handshake to be completed. This is necessary for + * False Start support, since SSL_do_handshake returns before the handshake is + * completed in this case. */ -static void NativeCrypto_SSL_set_signed_cert_timestamp_list(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jbyteArray list) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list", ssl); - if (ssl == nullptr) { - return; +static void info_callback(const SSL* ssl, int type, int value) { + JNI_TRACE("ssl=%p info_callback type=0x%x value=%d", ssl, type, value); + if (conscrypt::trace::kWithJniTrace) { + info_callback_LOG(ssl, type, value); } - - ScopedByteArrayRO listBytes(env, list); - if (listBytes.get() == nullptr) { - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => list == " - "null", - ssl); + if (!(type & SSL_CB_HANDSHAKE_DONE) && !(type & SSL_CB_HANDSHAKE_START)) { + JNI_TRACE("ssl=%p info_callback ignored", ssl); return; } - if (!SSL_set_signed_cert_timestamp_list(ssl, reinterpret_cast(listBytes.get()), - listBytes.size())) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => fail", ssl); - } else { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => ok", ssl); + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in info_callback"); + JNI_TRACE("ssl=%p info_callback env error", ssl); + return; } -} - -/* - * public static native void SSL_enable_ocsp_stapling(long ssl); - */ -static void NativeCrypto_SSL_enable_ocsp_stapling(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_ocsp_stapling", ssl); - if (ssl == nullptr) { + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p info_callback already pending exception", ssl); return; } - SSL_enable_ocsp_stapling(ssl); -} + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; -/* - * public static native byte[] SSL_get_ocsp_response(long ssl); - */ -static jbyteArray NativeCrypto_SSL_get_ocsp_response(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_ocsp_response", ssl); - if (ssl == nullptr) { - return nullptr; + JNI_TRACE("ssl=%p info_callback calling onSSLStateChange", ssl); + env->CallVoidMethod(sslHandshakeCallbacks, + conscrypt::jniutil::sslHandshakeCallbacks_onSSLStateChange, type, value); + + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p info_callback exception", ssl); } + JNI_TRACE("ssl=%p info_callback completed", ssl); +} - const uint8_t* data; - size_t data_len; - SSL_get0_ocsp_response(ssl, &data, &data_len); +/** + * Call back to ask for a certificate. There are three possible exit codes: + * + * 1 is success. + * 0 is error. + * -1 is to pause the handshake to continue from the same place later. + */ +static int cert_cb(SSL* ssl, CONSCRYPT_UNUSED void* arg) { + JNI_TRACE("ssl=%p cert_cb", ssl); - if (data_len == 0) { - JNI_TRACE("NativeCrypto_SSL_get_ocsp_response(%p) => null", ssl); - return nullptr; + // cert_cb is called for both clients and servers, but we are only + // interested in client certificates. + if (SSL_is_server(ssl)) { + JNI_TRACE("ssl=%p cert_cb not a client => 1", ssl); + return 1; } - ScopedLocalRef byteArray(env, env->NewByteArray(static_cast(data_len))); - if (byteArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_SSL_get_ocsp_response(%p) => creating byte array failed", ssl); - return nullptr; + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in cert_cb"); + JNI_TRACE("ssl=%p cert_cb env error => 0", ssl); + return 0; } + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p cert_cb already pending exception => 0", ssl); + return 0; + } + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; - env->SetByteArrayRegion(byteArray.get(), 0, static_cast(data_len), (const jbyte*)data); - JNI_TRACE("NativeCrypto_SSL_get_ocsp_response(%p) => %p [size=%zd]", ssl, byteArray.get(), - data_len); - - return byteArray.release(); -} + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_clientCertificateRequested; -/* - * public static native void SSL_set_ocsp_response(long ssl, byte[] response); - */ -static void NativeCrypto_SSL_set_ocsp_response(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jbyteArray response) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response", ssl); - if (ssl == nullptr) { - return; + // Call Java callback which can reconfigure the client certificate. + const uint8_t* ctype = nullptr; + size_t ctype_num = SSL_get0_certificate_types(ssl, &ctype); + const uint16_t* sigalgs = nullptr; + size_t sigalgs_num = SSL_get0_peer_verify_algorithms(ssl, &sigalgs); + ScopedLocalRef issuers( + env, CryptoBuffersToObjectArray(env, SSL_get0_server_requested_CAs(ssl))); + if (issuers.get() == nullptr) { + return 0; } - ScopedByteArrayRO responseBytes(env, response); - if (responseBytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response => response == null", ssl); - return; + if (conscrypt::trace::kWithJniTrace) { + for (size_t i = 0; i < ctype_num; i++) { + JNI_TRACE("ssl=%p clientCertificateRequested keyTypes[%zu]=%d", ssl, i, ctype[i]); + } + for (size_t i = 0; i < sigalgs_num; i++) { + JNI_TRACE("ssl=%p clientCertificateRequested sigAlgs[%zu]=%d", ssl, i, sigalgs[i]); + } } - if (!SSL_set_ocsp_response(ssl, reinterpret_cast(responseBytes.get()), - responseBytes.size())) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response => fail", ssl); - } else { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response => ok", ssl); + jbyteArray keyTypes = env->NewByteArray(static_cast(ctype_num)); + if (keyTypes == nullptr) { + JNI_TRACE("ssl=%p cert_cb keyTypes == null => 0", ssl); + return 0; } + env->SetByteArrayRegion(keyTypes, 0, static_cast(ctype_num), + reinterpret_cast(ctype)); + + jintArray signatureAlgs = env->NewIntArray(static_cast(sigalgs_num)); + if (signatureAlgs == nullptr) { + JNI_TRACE("ssl=%p cert_cb signatureAlgs == null => 0", ssl); + return 0; + } + { + ScopedIntArrayRW sigAlgsRW(env, signatureAlgs); + for (size_t i = 0; i < sigalgs_num; i++) { + sigAlgsRW[i] = sigalgs[i]; + } + } + + JNI_TRACE( + "ssl=%p clientCertificateRequested calling clientCertificateRequested " + "keyTypes=%p signatureAlgs=%p issuers=%p", + ssl, keyTypes, signatureAlgs, issuers.get()); + env->CallVoidMethod(sslHandshakeCallbacks, methodID, keyTypes, signatureAlgs, issuers.get()); + + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p cert_cb exception => 0", ssl); + return 0; + } + + JNI_TRACE("ssl=%p cert_cb => 1", ssl); + return 1; } -// All verify_data values are currently 12 bytes long, but cipher suites are -// allowed to customize the length of their verify_data (with a default of 12 -// bytes). We accept up to 16 bytes so that we can check that the results are -// actually 12 bytes long in tests and update this value if necessary. -const size_t MAX_TLS_UNIQUE_LENGTH = 16; +static enum ssl_select_cert_result_t select_certificate_cb(const SSL_CLIENT_HELLO* client_hello) { + SSL* ssl = client_hello->ssl; + JNI_TRACE("ssl=%p select_certificate_cb_callback", ssl); -static jbyteArray NativeCrypto_SSL_get_tls_unique(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_tls_unique", ssl); - if (ssl == nullptr) { - return nullptr; + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in select_certificate_cb"); + JNI_TRACE("ssl=%p select_certificate_cb env error", ssl); + return ssl_select_cert_error; + } + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p select_certificate_cb already pending exception", ssl); + return ssl_select_cert_error; } - uint8_t data[MAX_TLS_UNIQUE_LENGTH]; - size_t data_len; - int ret = SSL_get_tls_unique(ssl, data, &data_len, MAX_TLS_UNIQUE_LENGTH); + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverCertificateRequested; - if (!ret || data_len == 0) { - JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => null", ssl); - return nullptr; + const uint16_t* sigalgs = nullptr; + size_t sigalgs_num = SSL_get0_peer_verify_algorithms(ssl, &sigalgs); + + if (sigalgs_num > static_cast(INT_MAX)) { + conscrypt::jniutil::throwRuntimeException(env, "Too many signature algorithms"); + return ssl_select_cert_error; + } + jintArray signatureAlgs = env->NewIntArray(static_cast(sigalgs_num)); + if (signatureAlgs == nullptr) { + return ssl_select_cert_error; + } + { + ScopedIntArrayRW sigAlgsRW(env, signatureAlgs); + if (sigAlgsRW.get() == nullptr) { + return ssl_select_cert_error; + } + for (size_t i = 0; i < sigalgs_num; i++) { + sigAlgsRW[i] = sigalgs[i]; + } } - ScopedLocalRef byteArray(env, env->NewByteArray(static_cast(data_len))); - if (byteArray.get() == nullptr) { - JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => creating byte array failed", ssl); - return nullptr; + JNI_TRACE("ssl=%p select_certificate_cb calling serverCertificateRequested", ssl); + env->CallVoidMethod(sslHandshakeCallbacks, methodID, signatureAlgs); + + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p select_certificate_cb exception", ssl); + return ssl_select_cert_error; } + JNI_TRACE("ssl=%p select_certificate_cb completed", ssl); + return ssl_select_cert_success; +} - env->SetByteArrayRegion(byteArray.get(), 0, static_cast(data_len), (const jbyte*)data); - JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => %p [size=%zd]", ssl, byteArray.get(), - data_len); +/** + * Pre-Shared Key (PSK) client callback. + */ +static unsigned int psk_client_callback(SSL* ssl, const char* hint, char* identity, + unsigned int max_identity_len, unsigned char* psk, + unsigned int max_psk_len) { + JNI_TRACE("ssl=%p psk_client_callback", ssl); - return byteArray.release(); + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in psk_client_callback"); + JNI_TRACE("ssl=%p psk_client_callback env error", ssl); + return 0; + } + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p psk_client_callback already pending exception", ssl); + return 0; + } + + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_clientPSKKeyRequested; + JNI_TRACE("ssl=%p psk_client_callback calling clientPSKKeyRequested", ssl); + ScopedLocalRef identityHintJava(env, + (hint != nullptr) ? env->NewStringUTF(hint) : nullptr); + ScopedLocalRef identityJava( + env, env->NewByteArray(static_cast(max_identity_len))); + if (identityJava.get() == nullptr) { + JNI_TRACE("ssl=%p psk_client_callback failed to allocate identity bufffer", ssl); + return 0; + } + ScopedLocalRef keyJava(env, env->NewByteArray(static_cast(max_psk_len))); + if (keyJava.get() == nullptr) { + JNI_TRACE("ssl=%p psk_client_callback failed to allocate key bufffer", ssl); + return 0; + } + jint keyLen = env->CallIntMethod(sslHandshakeCallbacks, methodID, identityHintJava.get(), + identityJava.get(), keyJava.get()); + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p psk_client_callback exception", ssl); + return 0; + } + if (keyLen <= 0) { + JNI_TRACE("ssl=%p psk_client_callback failed to get key", ssl); + return 0; + } else if ((unsigned int)keyLen > max_psk_len) { + JNI_TRACE("ssl=%p psk_client_callback got key which is too long", ssl); + return 0; + } + ScopedByteArrayRO keyJavaRo(env, keyJava.get()); + if (keyJavaRo.get() == nullptr) { + JNI_TRACE("ssl=%p psk_client_callback failed to get key bytes", ssl); + return 0; + } + memcpy(psk, keyJavaRo.get(), static_cast(keyLen)); + + ScopedByteArrayRO identityJavaRo(env, identityJava.get()); + if (identityJavaRo.get() == nullptr) { + JNI_TRACE("ssl=%p psk_client_callback failed to get identity bytes", ssl); + return 0; + } + memcpy(identity, identityJavaRo.get(), max_identity_len); + + JNI_TRACE("ssl=%p psk_client_callback completed", ssl); + return static_cast(keyLen); } -static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jbyteArray label, jbyteArray context, - jint num_bytes) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material", ssl); - if (ssl == nullptr) { - return nullptr; +/** + * Pre-Shared Key (PSK) server callback. + */ +static unsigned int psk_server_callback(SSL* ssl, const char* identity, unsigned char* psk, + unsigned int max_psk_len) { + JNI_TRACE("ssl=%p psk_server_callback", ssl); + + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in psk_server_callback"); + JNI_TRACE("ssl=%p psk_server_callback env error", ssl); + return 0; } - ScopedByteArrayRO labelBytes(env, label); - if (labelBytes.get() == nullptr) { - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_export_keying_material label == null => " - "exception", - ssl); - return nullptr; + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p psk_server_callback already pending exception", ssl); + return 0; } - std::unique_ptr out(new uint8_t[num_bytes]); - int ret; - if (context == nullptr) { - ret = SSL_export_keying_material(ssl, out.get(), num_bytes, - reinterpret_cast(labelBytes.get()), - labelBytes.size(), nullptr, 0, 0); - } else { - ScopedByteArrayRO contextBytes(env, context); - if (contextBytes.get() == nullptr) { - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_export_keying_material context == null => " - "exception", - ssl); - return nullptr; - } - ret = SSL_export_keying_material( - ssl, out.get(), num_bytes, reinterpret_cast(labelBytes.get()), - labelBytes.size(), reinterpret_cast(contextBytes.get()), - contextBytes.size(), 1); + + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverPSKKeyRequested; + JNI_TRACE("ssl=%p psk_server_callback calling serverPSKKeyRequested", ssl); + const char* identityHint = SSL_get_psk_identity_hint(ssl); + ScopedLocalRef identityHintJava( + env, (identityHint != nullptr) ? env->NewStringUTF(identityHint) : nullptr); + ScopedLocalRef identityJava( + env, (identity != nullptr) ? env->NewStringUTF(identity) : nullptr); + ScopedLocalRef keyJava(env, env->NewByteArray(static_cast(max_psk_len))); + if (keyJava.get() == nullptr) { + JNI_TRACE("ssl=%p psk_server_callback failed to allocate key bufffer", ssl); + return 0; } - if (!ret) { - conscrypt::jniutil::throwExceptionFromBoringSSLError( - env, "SSL_export_keying_material", conscrypt::jniutil::throwSSLExceptionStr); - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material => exception", ssl); - return nullptr; + jint keyLen = env->CallIntMethod(sslHandshakeCallbacks, methodID, identityHintJava.get(), + identityJava.get(), keyJava.get()); + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p psk_server_callback exception", ssl); + return 0; } - jbyteArray result = env->NewByteArray(static_cast(num_bytes)); - if (result == nullptr) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Could not create result array"); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_export_keying_material => could not create " - "array", - ssl); - return nullptr; + if (keyLen <= 0) { + JNI_TRACE("ssl=%p psk_server_callback failed to get key", ssl); + return 0; + } else if ((unsigned int)keyLen > max_psk_len) { + JNI_TRACE("ssl=%p psk_server_callback got key which is too long", ssl); + return 0; + } + ScopedByteArrayRO keyJavaRo(env, keyJava.get()); + if (keyJavaRo.get() == nullptr) { + JNI_TRACE("ssl=%p psk_server_callback failed to get key bytes", ssl); + return 0; + } + memcpy(psk, keyJavaRo.get(), static_cast(keyLen)); + + JNI_TRACE("ssl=%p psk_server_callback completed", ssl); + return static_cast(keyLen); +} + +static int new_session_callback(SSL* ssl, SSL_SESSION* session) { + JNI_TRACE("ssl=%p new_session_callback session=%p", ssl, session); + + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in new_session_callback"); + JNI_TRACE("ssl=%p new_session_callback env error", ssl); + return 0; + } + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p new_session_callback already pending exception", ssl); + return 0; + } + + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_onNewSessionEstablished; + JNI_TRACE("ssl=%p new_session_callback calling onNewSessionEstablished", ssl); + env->CallVoidMethod(sslHandshakeCallbacks, methodID, reinterpret_cast(session)); + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p new_session_callback exception cleared", ssl); + env->ExceptionClear(); } - const jbyte* src = reinterpret_cast(out.get()); - env->SetByteArrayRegion(result, 0, static_cast(num_bytes), src); - JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material => success", ssl); - return result; + JNI_TRACE("ssl=%p new_session_callback completed", ssl); + + // Always returning 0 (not taking ownership). The Java code is responsible for + // incrementing the reference count. + return 0; } -static void NativeCrypto_SSL_use_psk_identity_hint(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jstring identityHintJava) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_use_psk_identity_hint identityHint=%p", ssl, - identityHintJava); - if (ssl == nullptr) { - return; +static SSL_SESSION* server_session_requested_callback(SSL* ssl, const uint8_t* id, int id_len, + int* out_copy) { + JNI_TRACE("ssl=%p server_session_requested_callback", ssl); + + // Always set to out_copy to zero. The Java callback will be responsible for + // incrementing the reference count (and any required synchronization). + *out_copy = 0; + + AppData* appData = toAppData(ssl); + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in server_session_requested_callback"); + JNI_TRACE("ssl=%p server_session_requested_callback env error", ssl); + return 0; + } + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p server_session_requested_callback already pending exception", ssl); + return 0; } - int ret; - if (identityHintJava == nullptr) { - ret = SSL_use_psk_identity_hint(ssl, nullptr); - } else { - ScopedUtfChars identityHint(env, identityHintJava); - if (identityHint.c_str() == nullptr) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Failed to obtain identityHint bytes"); - return; - } - ret = SSL_use_psk_identity_hint(ssl, identityHint.c_str()); + // Copy the ID to a byte[]. + jbyteArray id_array = env->NewByteArray(static_cast(id_len)); + if (id_array == nullptr) { + JNI_TRACE("ssl=%p id_array bytes == null => 0", ssl); + return 0; } + env->SetByteArrayRegion(id_array, 0, static_cast(id_len), + reinterpret_cast(id)); - if (ret != 1) { - int sslErrorCode = SSL_get_error(ssl, ret); - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, sslErrorCode, - "Failed to set PSK identity hint"); + jobject sslHandshakeCallbacks = appData->sslHandshakeCallbacks; + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_serverSessionRequested; + JNI_TRACE("ssl=%p server_session_requested_callback calling serverSessionRequested", ssl); + jlong ssl_session_address = env->CallLongMethod(sslHandshakeCallbacks, methodID, id_array); + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p server_session_requested_callback exception cleared", ssl); + env->ExceptionClear(); } + SSL_SESSION* ssl_session_ptr = + reinterpret_cast(static_cast(ssl_session_address)); + JNI_TRACE("ssl=%p server_session_requested_callback completed => %p", ssl, ssl_session_ptr); + return ssl_session_ptr; } -static void NativeCrypto_set_SSL_psk_client_callback_enabled(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jboolean enabled) { +static jint NativeCrypto_EVP_has_aes_hardware(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_set_SSL_psk_client_callback_enabled(%d)", ssl, enabled); - if (ssl == nullptr) { - return; - } + int ret = 0; + ret = EVP_has_aes_hardware(); + JNI_TRACE("EVP_has_aes_hardware => %d", ret); + return ret; +} - SSL_set_psk_client_callback(ssl, (enabled) ? psk_client_callback : nullptr); +static void debug_print_session_key(const SSL* ssl, const char* line) { + JNI_TRACE_KEYS("ssl=%p KEY_LINE: %s", ssl, line); } -static void NativeCrypto_set_SSL_psk_server_callback_enabled(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jboolean enabled) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_set_SSL_psk_server_callback_enabled(%d)", ssl, enabled); - if (ssl == nullptr) { +static void debug_print_packet_data(const SSL* ssl, char direction, const char* data, size_t len) { + static constexpr size_t kDataWidth = 16; + + struct timeval tv; + if (gettimeofday(&tv, NULL)) { + CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", + "debug_print_packet_data: could not get time of day"); return; } - SSL_set_psk_server_callback(ssl, (enabled) ? psk_server_callback : nullptr); -} + // Packet preamble for text2pcap + CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", "ssl=%p SSL_DATA: %c %ld.%06ld", ssl, direction, + static_cast(tv.tv_sec), + static_cast(tv.tv_usec)); // NOLINT(runtime/int) -static jlongArray NativeCrypto_SSL_get_ciphers(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_ciphers", ssl); - if (ssl == nullptr) { - return nullptr; - } + char out[kDataWidth * 3 + 1]; + for (size_t i = 0; i < len; i += kDataWidth) { + size_t n = len - i < kDataWidth ? len - i : kDataWidth; - STACK_OF(SSL_CIPHER)* cipherStack = SSL_get_ciphers(ssl); - size_t count = (cipherStack != nullptr) ? sk_SSL_CIPHER_num(cipherStack) : 0; - ScopedLocalRef ciphersArray(env, env->NewLongArray(static_cast(count))); - ScopedLongArrayRW ciphers(env, ciphersArray.get()); - for (size_t i = 0; i < count; i++) { - ciphers[i] = reinterpret_cast(sk_SSL_CIPHER_value(cipherStack, i)); + for (size_t j = 0, offset = 0; j < n; j++, offset += 3) { + int ret = snprintf(out + offset, sizeof(out) - offset, "%02x ", data[i + j] & 0xFF); + if (ret < 0 || static_cast(ret) >= sizeof(out) - offset) { + CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", + "debug_print_packet_data failed to output %d", ret); + return; + } + } + + // Print out packet data in format understood by text2pcap + CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", "ssl=%p SSL_DATA: %06zx %s", ssl, i, out); } - JNI_TRACE("NativeCrypto_SSL_get_ciphers(%p) => %p [size=%zu]", ssl, ciphersArray.get(), count); - return ciphersArray.release(); + // Conclude the packet data + CONSCRYPT_LOG(LOG_INFO, LOG_TAG "-jni", "ssl=%p SSL_DATA: %06zx", ssl, len); } -/** - * Sets the ciphers suites that are enabled in the SSL +/* + * public static native int SSL_CTX_new(); */ -static void NativeCrypto_SSL_set_cipher_lists(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jobjectArray cipherSuites) { +static jlong NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=%p", ssl, cipherSuites); - if (ssl == nullptr) { - return; - } - if (cipherSuites == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "cipherSuites == null"); - return; + bssl::UniquePtr sslCtx(SSL_CTX_new(TLS_with_buffers_method())); + if (sslCtx.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SSL_CTX_new"); + return 0; } + SSL_CTX_set_options( + sslCtx.get(), + SSL_OP_ALL + // We also disable session tickets for better compatibility b/2682876 + | SSL_OP_NO_TICKET + // We also disable compression for better compatibility b/2710492 + // b/2710497 + | SSL_OP_NO_COMPRESSION + // Generate a fresh ECDH keypair for each key exchange. + | SSL_OP_SINGLE_ECDH_USE); + SSL_CTX_set_min_proto_version(sslCtx.get(), TLS1_VERSION); + SSL_CTX_set_max_proto_version(sslCtx.get(), TLS1_2_VERSION); - int length = env->GetArrayLength(cipherSuites); - + uint32_t mode = SSL_CTX_get_mode(sslCtx.get()); /* - * Special case for empty cipher list. This is considered an error by the - * SSL_set_cipher_list API, but Java allows this silly configuration. - * However, the SSL cipher list is still set even when SSL_set_cipher_list - * returns 0 in this case. Just to make sure, we check the resulting cipher - * list to make sure it's zero length. + * Turn on "partial write" mode. This means that SSL_write() will + * behave like Posix write() and possibly return after only + * writing a partial buffer. Note: The alternative, perhaps + * surprisingly, is not that SSL_write() always does full writes + * but that it will force you to retry write calls having + * preserved the full state of the original call. (This is icky + * and undesirable.) */ - if (length == 0) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty", ssl); - SSL_set_cipher_list(ssl, ""); - ERR_clear_error(); - if (sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) != 0) { - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty => " - "error", - ssl); - conscrypt::jniutil::throwRuntimeException( - env, "SSL_set_cipher_list did not update ciphers!"); - ERR_clear_error(); - } - return; - } + mode |= SSL_MODE_ENABLE_PARTIAL_WRITE; - static const char noSSLv2[] = "!SSLv2"; - size_t cipherStringLen = strlen(noSSLv2); + // Reuse empty buffers within the SSL_CTX to save memory + mode |= SSL_MODE_RELEASE_BUFFERS; - for (int i = 0; i < length; i++) { - ScopedLocalRef cipherSuite( - env, reinterpret_cast(env->GetObjectArrayElement(cipherSuites, i))); - ScopedUtfChars c(env, cipherSuite.get()); - if (c.c_str() == nullptr) { - return; - } + // Enable False Start. + mode |= SSL_MODE_ENABLE_FALSE_START; - if (cipherStringLen + 1 < cipherStringLen) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Overflow in cipher suite strings"); - return; - } - cipherStringLen += 1; /* For the separating colon */ + // We need to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER as the memory address + // may change between calls to wrap(...). See + // https://github.com/netty/netty-tcnative/issues/100 + mode |= SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; - if (cipherStringLen + c.size() < cipherStringLen) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Overflow in cipher suite strings"); - return; - } - cipherStringLen += c.size(); + SSL_CTX_set_mode(sslCtx.get(), mode); + + SSL_CTX_set_info_callback(sslCtx.get(), info_callback); + SSL_CTX_set_cert_cb(sslCtx.get(), cert_cb, nullptr); + SSL_CTX_set_select_certificate_cb(sslCtx.get(), select_certificate_cb); + if (conscrypt::trace::kWithJniTraceKeys) { + SSL_CTX_set_keylog_callback(sslCtx.get(), debug_print_session_key); } - if (cipherStringLen + 1 < cipherStringLen) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Overflow in cipher suite strings"); + // By default BoringSSL will cache in server mode, but we want to get + // notified of new sessions being created in client mode. We set + // SSL_SESS_CACHE_BOTH in order to get the callback in client mode, but + // ignore it in server mode in favor of the internal cache. + SSL_CTX_set_session_cache_mode(sslCtx.get(), SSL_SESS_CACHE_BOTH); + SSL_CTX_sess_set_new_cb(sslCtx.get(), new_session_callback); + SSL_CTX_sess_set_get_cb(sslCtx.get(), server_session_requested_callback); + + JNI_TRACE("NativeCrypto_SSL_CTX_new => %p", sslCtx.get()); + return (jlong)sslCtx.release(); +} + +/** + * public static native void SSL_CTX_free(long ssl_ctx) + */ +static void NativeCrypto_SSL_CTX_free(JNIEnv* env, jclass, jlong ssl_ctx_address, + CONSCRYPT_UNUSED jobject holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_free", ssl_ctx); + if (ssl_ctx == nullptr) { return; } - cipherStringLen += 1; /* For final NUL. */ + SSL_CTX_free(ssl_ctx); +} - std::unique_ptr cipherString(new char[cipherStringLen]); - if (cipherString.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to alloc cipher string"); +static void NativeCrypto_SSL_CTX_set_session_id_context(JNIEnv* env, jclass, jlong ssl_ctx_address, + CONSCRYPT_UNUSED jobject holder, + jbyteArray sid_ctx) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context sid_ctx=%p", ssl_ctx, + sid_ctx); + if (ssl_ctx == nullptr) { return; } - memcpy(cipherString.get(), noSSLv2, strlen(noSSLv2)); - size_t j = strlen(noSSLv2); - - for (int i = 0; i < length; i++) { - ScopedLocalRef cipherSuite( - env, reinterpret_cast(env->GetObjectArrayElement(cipherSuites, i))); - ScopedUtfChars c(env, cipherSuite.get()); - - cipherString[j++] = ':'; - memcpy(&cipherString[j], c.c_str(), c.size()); - j += c.size(); - } - cipherString[j++] = 0; - if (j != cipherStringLen) { - conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Internal error"); + ScopedByteArrayRO buf(env, sid_ctx); + if (buf.get() == nullptr) { + JNI_TRACE( + "ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => threw " + "exception", + ssl_ctx); return; } - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=%s", ssl, cipherString.get()); - if (!SSL_set_cipher_list(ssl, cipherString.get())) { - ERR_clear_error(); + unsigned int length = static_cast(buf.size()); + if (length > SSL_MAX_SSL_SESSION_ID_LENGTH) { conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", - "Illegal cipher suite strings."); + "length > SSL_MAX_SSL_SESSION_ID_LENGTH"); + JNI_TRACE("NativeCrypto_SSL_CTX_set_session_id_context => length = %d", length); return; } -} - -static void NativeCrypto_SSL_set_accept_state(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_accept_state", ssl); - if (ssl == nullptr) { + const unsigned char* bytes = reinterpret_cast(buf.get()); + int result = SSL_CTX_set_session_id_context(ssl_ctx, bytes, length); + if (result == 0) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "NativeCrypto_SSL_CTX_set_session_id_context"); return; } - SSL_set_accept_state(ssl); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_session_id_context => ok", ssl_ctx); } -static void NativeCrypto_SSL_set_connect_state(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { +static jlong NativeCrypto_SSL_CTX_set_timeout(JNIEnv* env, jclass, jlong ssl_ctx_address, + CONSCRYPT_UNUSED jobject holder, jlong seconds) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_connect_state", ssl); - if (ssl == nullptr) { - return; + SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_set_timeout seconds=%d", ssl_ctx, (int)seconds); + if (ssl_ctx == nullptr) { + return 0L; } - SSL_set_connect_state(ssl); + + return SSL_CTX_set_timeout(ssl_ctx, static_cast(seconds)); } /** - * Sets certificate expectations, especially for server to request client auth + * public static native int SSL_new(long ssl_ctx) throws SSLException; */ -static void NativeCrypto_SSL_set_verify(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jint mode) { +static jlong NativeCrypto_SSL_new(JNIEnv* env, jclass, jlong ssl_ctx_address, + CONSCRYPT_UNUSED jobject holder) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_verify mode=%x", ssl, mode); - if (ssl == nullptr) { - return; + SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new", ssl_ctx); + if (ssl_ctx == nullptr) { + return 0; } - SSL_set_custom_verify(ssl, static_cast(mode), cert_verify_callback); + bssl::UniquePtr ssl(SSL_new(ssl_ctx)); + if (ssl.get() == nullptr) { + conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, nullptr, SSL_ERROR_NONE, + "Unable to create SSL structure"); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => null", ssl_ctx); + return 0; + } + + /* + * Create our special application data. + */ + AppData* appData = AppData::create(); + if (appData == nullptr) { + conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to create application data"); + ERR_clear_error(); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new appData => 0", ssl_ctx); + return 0; + } + SSL_set_app_data(ssl.get(), reinterpret_cast(appData)); + + SSL_set_custom_verify(ssl.get(), SSL_VERIFY_PEER, cert_verify_callback); + + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => ssl=%p appData=%p", ssl_ctx, ssl.get(), appData); + return (jlong)ssl.release(); } -/** - * Sets the ciphers suites that are enabled in the SSL - */ -static void NativeCrypto_SSL_set_session(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jlong ssl_session_address) { +static void NativeCrypto_setLocalCertsAndPrivateKey(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jobjectArray encodedCertificatesJava, + jobject pkeyRef) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_chain_and_key certificates=%p, " + "privateKey=%p", + ssl, encodedCertificatesJava, pkeyRef); if (ssl == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session => exception", ssl); + return; + } + if (encodedCertificatesJava == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "certificates == null"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => certificates == null", ssl); + return; + } + size_t numCerts = static_cast(env->GetArrayLength(encodedCertificatesJava)); + if (numCerts == 0) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "certificates.length == 0"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => certificates.length == 0", ssl); + return; + } + if (pkeyRef == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "privateKey == null"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => privateKey == null", ssl); return; } - SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, false); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session ssl_session=%p", ssl, ssl_session); - if (ssl_session == nullptr) { + // Get the private key. + EVP_PKEY* pkey = fromContextObject(env, pkeyRef); + if (pkey == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "pkey == null"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => pkey == null", ssl); return; } - int ret = SSL_set_session(ssl, ssl_session); - if (ret != 1) { - /* - * Translate the error, and throw if it turns out to be a real - * problem. - */ - int sslErrorCode = SSL_get_error(ssl, ret); - if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, sslErrorCode, - "SSL session set"); + // Copy the certificates. + std::vector> certBufferRefs(numCerts); + std::vector certBuffers(numCerts); + for (size_t i = 0; i < numCerts; ++i) { + ScopedLocalRef certArray( + env, reinterpret_cast( + env->GetObjectArrayElement(encodedCertificatesJava, i))); + certBufferRefs[i] = ByteArrayToCryptoBuffer(env, certArray.get(), nullptr); + if (!certBufferRefs[i]) { + return; } + certBuffers[i] = certBufferRefs[i].get(); } - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session ssl_session=%p => ret=%d", ssl, ssl_session, - ret); + + if (!SSL_set_chain_and_key(ssl, certBuffers.data(), numCerts, pkey, nullptr)) { + conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, + "Error configuring certificate"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => error", ssl); + return; + } + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_chain_and_key => ok", ssl); } -/** - * Sets the ciphers suites that are enabled in the SSL - */ -static void NativeCrypto_SSL_set_session_creation_enabled(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jboolean creation_enabled) { +static void NativeCrypto_SSL_set_client_CA_list(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jobjectArray principals) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_set_session_creation_enabled " - "creation_enabled=%d", - ssl, creation_enabled); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list principals=%p", ssl, principals); if (ssl == nullptr) { return; } - if (creation_enabled) { - SSL_clear_mode(ssl, SSL_MODE_NO_SESSION_CREATION); - } else { - SSL_set_mode(ssl, SSL_MODE_NO_SESSION_CREATION); + if (principals == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "principals == null"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => principals == null", ssl); + return; } -} -static jboolean NativeCrypto_SSL_session_reused(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_session_reused", ssl); - if (ssl == nullptr) { - return JNI_FALSE; + int length = env->GetArrayLength(principals); + if (length == 0) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "principals.length == 0"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => principals.length == 0", ssl); + return; } - int reused = SSL_session_reused(ssl); - JNI_TRACE("ssl=%p NativeCrypto_SSL_session_reused => %d", ssl, reused); - return static_cast(reused); -} - -static void NativeCrypto_SSL_accept_renegotiations(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_accept_renegotiations", ssl); - if (ssl == nullptr) { + bssl::UniquePtr principalsStack(sk_CRYPTO_BUFFER_new_null()); + if (principalsStack.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate principal stack"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => stack allocation error", ssl); return; } + for (int i = 0; i < length; i++) { + ScopedLocalRef principal( + env, reinterpret_cast(env->GetObjectArrayElement(principals, i))); + bssl::UniquePtr buf = ByteArrayToCryptoBuffer(env, principal.get(), nullptr); + if (!buf) { + return; + } + if (!sk_CRYPTO_BUFFER_push(principalsStack.get(), buf.get())) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to push principal"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => principal push error", ssl); + return; + } + OWNERSHIP_TRANSFERRED(buf); + } - SSL_set_renegotiate_mode(ssl, ssl_renegotiate_freely); + SSL_set0_client_CAs(ssl, principalsStack.release()); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_client_CA_list => ok", ssl); } -static void NativeCrypto_SSL_set_tlsext_host_name(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jstring hostname) { +/** + * public static native long SSL_set_mode(long ssl, long mode); + */ +static jlong NativeCrypto_SSL_set_mode(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, jlong mode) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name hostname=%p", ssl, hostname); + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_mode mode=0x%llx", ssl, (long long)mode); if (ssl == nullptr) { - return; - } - - ScopedUtfChars hostnameChars(env, hostname); - if (hostnameChars.c_str() == nullptr) { - return; - } - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name hostnameChars=%s", ssl, - hostnameChars.c_str()); - - int ret = SSL_set_tlsext_host_name(ssl, hostnameChars.c_str()); - if (ret != 1) { - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, - "Error setting host name"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name => error", ssl); - return; + return 0; } - JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name => ok", ssl); + jlong result = static_cast(SSL_set_mode(ssl, static_cast(mode))); + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_mode => 0x%lx", ssl, (long)result); + return result; } -static jstring NativeCrypto_SSL_get_servername(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { +/** + * public static native long SSL_set_options(long ssl, long options); + */ +static jlong NativeCrypto_SSL_set_options(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, jlong options) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_servername", ssl); + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_options options=0x%llx", ssl, (long long)options); if (ssl == nullptr) { - return nullptr; + return 0; } - const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_servername => %s", ssl, servername); - return env->NewStringUTF(servername); + jlong result = static_cast(SSL_set_options(ssl, static_cast(options))); + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_options => 0x%lx", ssl, (long)result); + return result; } /** - * Selects the ALPN protocol to use. The list of protocols in "primary" is - * considered the order which should take precedence. + * public static native long SSL_clear_options(long ssl, long options); */ -static int selectApplicationProtocol(SSL* ssl, unsigned char** out, unsigned char* outLength, - const unsigned char* primary, const unsigned int primaryLength, - const unsigned char* secondary, - const unsigned int secondaryLength) { - JNI_TRACE("primary=%p, length=%d", primary, primaryLength); - - int status = SSL_select_next_proto(out, outLength, primary, primaryLength, secondary, - secondaryLength); - switch (status) { - case OPENSSL_NPN_NEGOTIATED: - JNI_TRACE("ssl=%p selectApplicationProtocol ALPN negotiated", ssl); - return SSL_TLSEXT_ERR_OK; - break; - case OPENSSL_NPN_UNSUPPORTED: - JNI_TRACE("ssl=%p selectApplicationProtocol ALPN unsupported", ssl); - break; - case OPENSSL_NPN_NO_OVERLAP: - JNI_TRACE("ssl=%p selectApplicationProtocol ALPN no overlap", ssl); - break; +static jlong NativeCrypto_SSL_clear_options(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, jlong options) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_options options=0x%llx", ssl, (long long)options); + if (ssl == nullptr) { + return 0; } - return SSL_TLSEXT_ERR_NOACK; + jlong result = static_cast(SSL_clear_options(ssl, static_cast(options))); + // NOLINTNEXTLINE(runtime/int) + JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_options => 0x%lx", ssl, (long)result); + return result; } -/** - * Calls out to an application-provided selector to choose the ALPN protocol. - */ -static int selectApplicationProtocol(SSL* ssl, JNIEnv* env, jobject sslHandshakeCallbacks, - unsigned char** out, unsigned char* outLen, - const unsigned char* in, const unsigned int inLen) { - // Copy the input array. - ScopedLocalRef protocols(env, env->NewByteArray(static_cast(inLen))); - if (protocols.get() == nullptr) { - JNI_TRACE("ssl=%p selectApplicationProtocol failed allocating array", ssl); - return SSL_TLSEXT_ERR_NOACK; +static jint NativeCrypto_SSL_set_protocol_versions(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jint min_version, jint max_version) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_protocol_versions min=0x%x max=0x%x", ssl, min_version, + max_version); + if (ssl == nullptr) { + return 0; } - env->SetByteArrayRegion(protocols.get(), 0, (static_cast(inLen)), - reinterpret_cast(in)); - - // Invoke the selection method. - jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_selectApplicationProtocol; - jint offset = env->CallIntMethod(sslHandshakeCallbacks, methodID, protocols.get()); - - if (offset < 0) { - JNI_TRACE("ssl=%p selectApplicationProtocol selection failed", ssl); - return SSL_TLSEXT_ERR_NOACK; + int min_result = SSL_set_min_proto_version(ssl, static_cast(min_version)); + int max_result = SSL_set_max_proto_version(ssl, static_cast(max_version)); + // Return failure if either call failed. + int result = 1; + if (!min_result || !max_result) { + result = 0; + // The only possible error is an invalid version, so we don't need the + // details. + ERR_clear_error(); } - - // Point the output to the selected protocol. - *outLen = *(in + offset); - *out = const_cast(in + offset + 1); - - return SSL_TLSEXT_ERR_OK; + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_protocol_versions => (min: %d, max: %d) == " + "%d", + ssl, min_result, max_result, result); + return result; } /** - * Callback for the server to select an ALPN protocol. + * public static native void SSL_enable_signed_cert_timestamps(long ssl); */ -static int alpn_select_callback(SSL* ssl, const unsigned char** out, unsigned char* outLen, - const unsigned char* in, unsigned int inLen, void*) { - JNI_TRACE("ssl=%p alpn_select_callback in=%p inLen=%d", ssl, in, inLen); - - AppData* appData = toAppData(ssl); - if (appData == nullptr) { - JNI_TRACE("ssl=%p alpn_select_callback appData => 0", ssl); - return SSL_TLSEXT_ERR_NOACK; - } - JNIEnv* env = appData->env; - if (env == nullptr) { - CONSCRYPT_LOG_ERROR("AppData->env missing in alpn_select_callback"); - JNI_TRACE("ssl=%p alpn_select_callback => 0", ssl); - return SSL_TLSEXT_ERR_NOACK; - } - - if (in == nullptr || (appData->applicationProtocolsData == nullptr && - !appData->hasApplicationProtocolSelector)) { - if (out != nullptr && outLen != nullptr) { - *out = nullptr; - *outLen = 0; - } - JNI_TRACE("ssl=%p alpn_select_callback protocols => 0", ssl); - return SSL_TLSEXT_ERR_NOACK; - } - - if (appData->hasApplicationProtocolSelector) { - return selectApplicationProtocol(ssl, env, appData->sslHandshakeCallbacks, - const_cast(out), outLen, in, inLen); +static void NativeCrypto_SSL_enable_signed_cert_timestamps(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_signed_cert_timestamps", ssl); + if (ssl == nullptr) { + return; } - return selectApplicationProtocol( - ssl, const_cast(out), outLen, - reinterpret_cast(appData->applicationProtocolsData), - static_cast(appData->applicationProtocolsLength), in, inLen); + SSL_enable_signed_cert_timestamps(ssl); } -static jbyteArray NativeCrypto_getApplicationProtocol(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { +/** + * public static native byte[] SSL_get_signed_cert_timestamp_list(long ssl); + */ +static jbyteArray NativeCrypto_SSL_get_signed_cert_timestamp_list( + JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_getApplicationProtocol", ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_signed_cert_timestamp_list", ssl); if (ssl == nullptr) { return nullptr; } - const jbyte* protocol; - unsigned int protocolLength; - SSL_get0_alpn_selected(ssl, reinterpret_cast(&protocol), - &protocolLength); - if (protocolLength == 0) { + + const uint8_t* data; + size_t data_len; + SSL_get0_signed_cert_timestamp_list(ssl, &data, &data_len); + + if (data_len == 0) { + JNI_TRACE("NativeCrypto_SSL_get_signed_cert_timestamp_list(%p) => null", ssl); return nullptr; } - jbyteArray result = env->NewByteArray(static_cast(protocolLength)); + + jbyteArray result = env->NewByteArray(static_cast(data_len)); if (result != nullptr) { - env->SetByteArrayRegion(result, 0, (static_cast(protocolLength)), protocol); + env->SetByteArrayRegion(result, 0, static_cast(data_len), (const jbyte*)data); } return result; } -static void NativeCrypto_setApplicationProtocols(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jboolean client_mode, jbyteArray protocols) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, true); - if (ssl == nullptr) { - return; - } - AppData* appData = toAppData(ssl); - if (appData == nullptr) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to retrieve application data"); - JNI_TRACE("ssl=%p NativeCrypto_setApplicationProtocols appData => 0", ssl); - return; - } - - if (protocols != nullptr) { - if (client_mode) { - ScopedByteArrayRO protosBytes(env, protocols); - if (protosBytes.get() == nullptr) { - JNI_TRACE( - "ssl=%p NativeCrypto_setApplicationProtocols protocols=%p => " - "protosBytes == null", - ssl, protocols); - return; - } - - const unsigned char* tmp = reinterpret_cast(protosBytes.get()); - int ret = SSL_set_alpn_protos(ssl, tmp, static_cast(protosBytes.size())); - if (ret != 0) { - conscrypt::jniutil::throwSSLExceptionStr(env, - "Unable to set ALPN protocols for client"); - JNI_TRACE("ssl=%p NativeCrypto_setApplicationProtocols => exception", ssl); - return; - } - } else { - // Server mode - configure the ALPN protocol selection callback. - if (!appData->setApplicationProtocols(env, protocols)) { - conscrypt::jniutil::throwSSLExceptionStr(env, - "Unable to set ALPN protocols for server"); - JNI_TRACE("ssl=%p NativeCrypto_setApplicationProtocols => exception", ssl); - return; - } - SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(ssl), alpn_select_callback, nullptr); - } - } -} - -static void NativeCrypto_setHasApplicationProtocolSelector(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, - jboolean hasSelector) { +/* + * public static native void SSL_set_signed_cert_timestamp_list(long ssl, byte[] + * response); + */ +static void NativeCrypto_SSL_set_signed_cert_timestamp_list(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jbyteArray list) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_setHasApplicationProtocolSelector selector=%d", ssl, - hasSelector); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list", ssl); if (ssl == nullptr) { return; } - AppData* appData = toAppData(ssl); - if (appData == nullptr) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to retrieve application data"); - JNI_TRACE("ssl=%p NativeCrypto_setHasApplicationProtocolSelector appData => 0", ssl); + + ScopedByteArrayRO listBytes(env, list); + if (listBytes.get() == nullptr) { + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => list == " + "null", + ssl); return; } - appData->hasApplicationProtocolSelector = hasSelector; - if (hasSelector) { - SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(ssl), alpn_select_callback, nullptr); + if (!SSL_set_signed_cert_timestamp_list(ssl, reinterpret_cast(listBytes.get()), + listBytes.size())) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => fail", ssl); + } else { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_signed_cert_timestamp_list => ok", ssl); } } -/** - * Perform SSL handshake +/* + * public static native void SSL_enable_ocsp_stapling(long ssl); */ -static void NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jobject fdObject, - jobject shc, jint timeout_millis) { +static void NativeCrypto_SSL_enable_ocsp_stapling(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake fd=%p shc=%p timeout_millis=%d", ssl, fdObject, - shc, timeout_millis); + JNI_TRACE("ssl=%p NativeCrypto_SSL_enable_ocsp_stapling", ssl); if (ssl == nullptr) { return; } - if (fdObject == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "fd == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake fd == null => exception", ssl); - return; + + SSL_enable_ocsp_stapling(ssl); +} + +/* + * public static native byte[] SSL_get_ocsp_response(long ssl); + */ +static jbyteArray NativeCrypto_SSL_get_ocsp_response(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_ocsp_response", ssl); + if (ssl == nullptr) { + return nullptr; } - if (shc == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_do_handshake sslHandshakeCallbacks == null => " - "exception", - ssl); - return; + + const uint8_t* data; + size_t data_len; + SSL_get0_ocsp_response(ssl, &data, &data_len); + + if (data_len == 0) { + JNI_TRACE("NativeCrypto_SSL_get_ocsp_response(%p) => null", ssl); + return nullptr; } - NetFd fd(env, fdObject); - if (fd.isClosed()) { - // SocketException thrown by NetFd.isClosed - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake fd.isClosed() => exception", ssl); - return; + ScopedLocalRef byteArray(env, env->NewByteArray(static_cast(data_len))); + if (byteArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_SSL_get_ocsp_response(%p) => creating byte array failed", ssl); + return nullptr; } - int ret = SSL_set_fd(ssl, fd.get()); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake s=%d", ssl, fd.get()); + env->SetByteArrayRegion(byteArray.get(), 0, static_cast(data_len), (const jbyte*)data); + JNI_TRACE("NativeCrypto_SSL_get_ocsp_response(%p) => %p [size=%zd]", ssl, byteArray.get(), + data_len); + + return byteArray.release(); +} - if (ret != 1) { - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, - "Error setting the file descriptor"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake SSL_set_fd => exception", ssl); +/* + * public static native void SSL_set_ocsp_response(long ssl, byte[] response); + */ +static void NativeCrypto_SSL_set_ocsp_response(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jbyteArray response) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response", ssl); + if (ssl == nullptr) { return; } - /* - * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't - * hang forever and we can use select() to find out if the socket is ready. - */ - if (!conscrypt::netutil::setBlocking(fd.get(), false)) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to make socket non blocking"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake setBlocking => exception", ssl); + ScopedByteArrayRO responseBytes(env, response); + if (responseBytes.get() == nullptr) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response => response == null", ssl); return; } - AppData* appData = toAppData(ssl); - if (appData == nullptr) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to retrieve application data"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake appData => exception", ssl); - return; + if (!SSL_set_ocsp_response(ssl, reinterpret_cast(responseBytes.get()), + responseBytes.size())) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response => fail", ssl); + } else { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_ocsp_response => ok", ssl); } +} - ret = 0; - SslError sslError; - while (appData->aliveAndKicking) { - errno = 0; +// All verify_data values are currently 12 bytes long, but cipher suites are +// allowed to customize the length of their verify_data (with a default of 12 +// bytes). We accept up to 16 bytes so that we can check that the results are +// actually 12 bytes long in tests and update this value if necessary. +const size_t MAX_TLS_UNIQUE_LENGTH = 16; - if (!appData->setCallbackState(env, shc, fdObject)) { - // SocketException thrown by NetFd.isClosed - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake setCallbackState => exception", ssl); - return; - } - ret = SSL_do_handshake(ssl); - appData->clearCallbackState(); - // cert_verify_callback threw exception - if (env->ExceptionCheck()) { - ERR_clear_error(); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake exception => exception", ssl); - return; - } - // success case - if (ret == 1) { - break; - } - // retry case - if (errno == EINTR) { - continue; - } - // error case - sslError.reset(ssl, ret); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_do_handshake ret=%d errno=%d sslError=%d " - "timeout_millis=%d", - ssl, ret, errno, sslError.get(), timeout_millis); +static jbyteArray NativeCrypto_SSL_get_tls_unique(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_tls_unique", ssl); + if (ssl == nullptr) { + return nullptr; + } - /* - * If SSL_do_handshake doesn't succeed due to the socket being - * either unreadable or unwritable, we use sslSelect to - * wait for it to become ready. If that doesn't happen - * before the specified timeout or an error occurs, we - * cancel the handshake. Otherwise we try the SSL_connect - * again. - */ - if (sslError.get() == SSL_ERROR_WANT_READ || sslError.get() == SSL_ERROR_WANT_WRITE) { - appData->waitingThreads++; - int selectResult = sslSelect(env, sslError.get(), fdObject, appData, timeout_millis); + uint8_t data[MAX_TLS_UNIQUE_LENGTH]; + size_t data_len; + int ret = SSL_get_tls_unique(ssl, data, &data_len, MAX_TLS_UNIQUE_LENGTH); - if (selectResult == THROWN_EXCEPTION) { - // SocketException thrown by NetFd.isClosed - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake sslSelect => exception", ssl); - return; - } - if (selectResult == -1) { - conscrypt::jniutil::throwSSLExceptionWithSslErrors( - env, ssl, SSL_ERROR_SYSCALL, "handshake error", - conscrypt::jniutil::throwSSLHandshakeExceptionStr); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_do_handshake selectResult == -1 => " - "exception", - ssl); - return; - } - if (selectResult == 0) { - conscrypt::jniutil::throwSocketTimeoutException(env, "SSL handshake timed out"); - ERR_clear_error(); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_do_handshake selectResult == 0 => " - "exception", - ssl); - return; - } - } else { - // CONSCRYPT_LOG_ERROR("Unknown error %d during handshake", error); - break; - } + if (!ret || data_len == 0) { + JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => null", ssl); + return nullptr; } - // clean error. See SSL_do_handshake(3SSL) man page. - if (ret == 0) { - /* - * The other side closed the socket before the handshake could be - * completed, but everything is within the bounds of the TLS protocol. - * We still might want to find out the real reason of the failure. - */ - if (sslError.get() == SSL_ERROR_NONE || - (sslError.get() == SSL_ERROR_SYSCALL && errno == 0) || - (sslError.get() == SSL_ERROR_ZERO_RETURN)) { - conscrypt::jniutil::throwSSLHandshakeExceptionStr(env, "Connection closed by peer"); - } else { - conscrypt::jniutil::throwSSLExceptionWithSslErrors( - env, ssl, sslError.release(), "SSL handshake terminated", - conscrypt::jniutil::throwSSLHandshakeExceptionStr); - } - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake clean error => exception", ssl); - return; + ScopedLocalRef byteArray(env, env->NewByteArray(static_cast(data_len))); + if (byteArray.get() == nullptr) { + JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => creating byte array failed", ssl); + return nullptr; } - // unclean error. See SSL_do_handshake(3SSL) man page. - if (ret < 0) { - /* - * Translate the error and throw exception. We are sure it is an error - * at this point. - */ - conscrypt::jniutil::throwSSLExceptionWithSslErrors( - env, ssl, sslError.release(), "SSL handshake aborted", - conscrypt::jniutil::throwSSLHandshakeExceptionStr); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake unclean error => exception", ssl); - return; - } - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => success", ssl); + env->SetByteArrayRegion(byteArray.get(), 0, static_cast(data_len), (const jbyte*)data); + JNI_TRACE("NativeCrypto_SSL_get_tls_unique(%p) => %p [size=%zd]", ssl, byteArray.get(), + data_len); + + return byteArray.release(); } -static jstring NativeCrypto_SSL_get_current_cipher(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { +static jbyteArray NativeCrypto_SSL_export_keying_material(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jbyteArray label, jbyteArray context, + jint num_bytes) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher", ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material", ssl); if (ssl == nullptr) { return nullptr; } - const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl); - if (cipher == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher cipher => null", ssl); + ScopedByteArrayRO labelBytes(env, label); + if (labelBytes.get() == nullptr) { + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material label == null => " + "exception", + ssl); return nullptr; } - const char* name = SSL_CIPHER_standard_name(cipher); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher => %s", ssl, name); - return env->NewStringUTF(name); + std::unique_ptr out(new uint8_t[num_bytes]); + int ret; + if (context == nullptr) { + ret = SSL_export_keying_material(ssl, out.get(), num_bytes, + reinterpret_cast(labelBytes.get()), + labelBytes.size(), nullptr, 0, 0); + } else { + ScopedByteArrayRO contextBytes(env, context); + if (contextBytes.get() == nullptr) { + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material context == null => " + "exception", + ssl); + return nullptr; + } + ret = SSL_export_keying_material( + ssl, out.get(), num_bytes, reinterpret_cast(labelBytes.get()), + labelBytes.size(), reinterpret_cast(contextBytes.get()), + contextBytes.size(), 1); + } + if (!ret) { + conscrypt::jniutil::throwExceptionFromBoringSSLError( + env, "SSL_export_keying_material", conscrypt::jniutil::throwSSLExceptionStr); + JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material => exception", ssl); + return nullptr; + } + jbyteArray result = env->NewByteArray(static_cast(num_bytes)); + if (result == nullptr) { + conscrypt::jniutil::throwSSLExceptionStr(env, "Could not create result array"); + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_export_keying_material => could not create " + "array", + ssl); + return nullptr; + } + const jbyte* src = reinterpret_cast(out.get()); + env->SetByteArrayRegion(result, 0, static_cast(num_bytes), src); + JNI_TRACE("ssl=%p NativeCrypto_SSL_export_keying_material => success", ssl); + return result; } -static void NativeCrypto_SSL_set1_groups(JNIEnv* env, jclass, jlong sslAddress, - CONSCRYPT_UNUSED jobject sslHolder, jintArray groups) { +static void NativeCrypto_SSL_use_psk_identity_hint(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jstring identityHintJava) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, sslAddress, /* throwIfNull= */ true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_groups groups=%p", ssl, groups); - if (ssl == nullptr) { - // to_SSL already called conscrypt::jniutil::throwNullPointerException - return; - } - if (groups == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "groups == null"); - return; - } - ScopedIntArrayRO groups_ro(env, groups); - if (groups_ro.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_groups => threw exception", ssl); - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for groups"); + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_use_psk_identity_hint identityHint=%p", ssl, + identityHintJava); + if (ssl == nullptr) { return; } - std::vector groups_vector; - groups_vector.reserve(groups_ro.size()); - const jint* groups_ptr = groups_ro.get(); - for (size_t i = 0; i < groups_ro.size(); i++) { - groups_vector.push_back(groups_ptr[i]); + + int ret; + if (identityHintJava == nullptr) { + ret = SSL_use_psk_identity_hint(ssl, nullptr); + } else { + ScopedUtfChars identityHint(env, identityHintJava); + if (identityHint.c_str() == nullptr) { + conscrypt::jniutil::throwSSLExceptionStr(env, "Failed to obtain identityHint bytes"); + return; + } + ret = SSL_use_psk_identity_hint(ssl, identityHint.c_str()); } - if (!SSL_set1_groups(ssl, groups_vector.data(), groups_vector.size())) { - conscrypt::jniutil::throwSSLExceptionStr(env, "Error parsing groups"); - ERR_clear_error(); - return; + if (ret != 1) { + int sslErrorCode = SSL_get_error(ssl, ret); + conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, sslErrorCode, + "Failed to set PSK identity hint"); } } -static jstring NativeCrypto_SSL_get_curve_name(JNIEnv* env, jclass, jlong sslAddress, - CONSCRYPT_UNUSED jobject sslHolder) { +static void NativeCrypto_set_SSL_psk_client_callback_enabled(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jboolean enabled) { CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, sslAddress, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_curve_name", ssl); + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_set_SSL_psk_client_callback_enabled(%d)", ssl, enabled); if (ssl == nullptr) { - return nullptr; + return; } - uint16_t curve_id = SSL_get_curve_id(ssl); - const char* name = SSL_get_curve_name(curve_id); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_curve_name => %s", ssl, name); - return env->NewStringUTF(name); + + SSL_set_psk_client_callback(ssl, (enabled) ? psk_client_callback : nullptr); } -static jstring NativeCrypto_SSL_get_version(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { +static void NativeCrypto_set_SSL_psk_server_callback_enabled(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jboolean enabled) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_version", ssl); + JNI_TRACE("ssl=%p NativeCrypto_set_SSL_psk_server_callback_enabled(%d)", ssl, enabled); if (ssl == nullptr) { - return nullptr; + return; } - const char* protocol = SSL_get_version(ssl); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get_version => %s", ssl, protocol); - return env->NewStringUTF(protocol); + + SSL_set_psk_server_callback(ssl, (enabled) ? psk_server_callback : nullptr); } -static jobjectArray NativeCrypto_SSL_get0_peer_certificates(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder) { +static jlongArray NativeCrypto_SSL_get_ciphers(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_peer_certificates", ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_ciphers", ssl); if (ssl == nullptr) { return nullptr; } - const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl); - if (chain == nullptr) { - return nullptr; - } - - ScopedLocalRef array(env, CryptoBuffersToObjectArray(env, chain)); - if (array.get() == nullptr) { - return nullptr; + STACK_OF(SSL_CIPHER)* cipherStack = SSL_get_ciphers(ssl); + size_t count = (cipherStack != nullptr) ? sk_SSL_CIPHER_num(cipherStack) : 0; + ScopedLocalRef ciphersArray(env, env->NewLongArray(static_cast(count))); + ScopedLongArrayRW ciphers(env, ciphersArray.get()); + for (size_t i = 0; i < count; i++) { + ciphers[i] = reinterpret_cast(sk_SSL_CIPHER_value(cipherStack, i)); } - JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_peer_certificates => %p", ssl, array.get()); - return array.release(); + JNI_TRACE("NativeCrypto_SSL_get_ciphers(%p) => %p [size=%zu]", ssl, ciphersArray.get(), count); + return ciphersArray.release(); } -static int sslRead(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, char* buf, jint len, - SslError* sslError, int read_timeout_millis) { - JNI_TRACE("ssl=%p sslRead buf=%p len=%d", ssl, buf, len); - - if (len == 0) { - // Don't bother doing anything in this case. - return 0; +/** + * Sets the ciphers suites that are enabled in the SSL + */ +static void NativeCrypto_SSL_set_cipher_lists(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jobjectArray cipherSuites) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=%p", ssl, cipherSuites); + if (ssl == nullptr) { + return; } - - BIO* rbio = SSL_get_rbio(ssl); - BIO* wbio = SSL_get_wbio(ssl); - - AppData* appData = toAppData(ssl); - JNI_TRACE("ssl=%p sslRead appData=%p", ssl, appData); - if (appData == nullptr) { - return THROW_SSLEXCEPTION; + if (cipherSuites == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "cipherSuites == null"); + return; } - while (appData->aliveAndKicking) { - errno = 0; - - std::unique_lock appDataLock(appData->mutex); + int length = env->GetArrayLength(cipherSuites); - if (!SSL_is_init_finished(ssl) && !SSL_in_false_start(ssl) && - !SSL_renegotiate_pending(ssl)) { - JNI_TRACE("ssl=%p sslRead => init is not finished (state: %s)", ssl, - SSL_state_string_long(ssl)); - return THROW_SSLEXCEPTION; + /* + * Special case for empty cipher list. This is considered an error by the + * SSL_set_cipher_list API, but Java allows this silly configuration. + * However, the SSL cipher list is still set even when SSL_set_cipher_list + * returns 0 in this case. Just to make sure, we check the resulting cipher + * list to make sure it's zero length. + */ + if (length == 0) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty", ssl); + SSL_set_cipher_list(ssl, ""); + ERR_clear_error(); + if (sk_SSL_CIPHER_num(SSL_get_ciphers(ssl)) != 0) { + JNI_TRACE( + "ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=empty => " + "error", + ssl); + conscrypt::jniutil::throwRuntimeException( + env, "SSL_set_cipher_list did not update ciphers!"); + ERR_clear_error(); } + return; + } - size_t bytesMoved = BIO_number_read(rbio) + BIO_number_written(wbio); + static const char noSSLv2[] = "!SSLv2"; + size_t cipherStringLen = strlen(noSSLv2); - if (!appData->setCallbackState(env, shc, fdObject)) { - return THROWN_EXCEPTION; - } - int result = SSL_read(ssl, buf, len); - appData->clearCallbackState(); - // callbacks can happen if server requests renegotiation - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p sslRead => THROWN_EXCEPTION", ssl); - return THROWN_EXCEPTION; - } - sslError->reset(ssl, result); - - JNI_TRACE("ssl=%p sslRead SSL_read result=%d sslError=%d", ssl, result, sslError->get()); - if (conscrypt::trace::kWithJniTraceData) { - for (size_t i = 0; result > 0 && i < static_cast(result); - i += conscrypt::trace::kWithJniTraceDataChunkSize) { - size_t n = result - i; - if (n > conscrypt::trace::kWithJniTraceDataChunkSize) { - n = conscrypt::trace::kWithJniTraceDataChunkSize; - } - JNI_TRACE("ssl=%p sslRead data: %zu:\n%.*s", ssl, n, (int)n, buf + i); - } + for (int i = 0; i < length; i++) { + ScopedLocalRef cipherSuite( + env, reinterpret_cast(env->GetObjectArrayElement(cipherSuites, i))); + ScopedUtfChars c(env, cipherSuite.get()); + if (c.c_str() == nullptr) { + return; } - // If we have been successful in moving data around, check whether it - // might make sense to wake up other blocked threads, so they can give - // it a try, too. - if (BIO_number_read(rbio) + BIO_number_written(wbio) != bytesMoved && - appData->waitingThreads > 0) { - sslNotify(appData); + if (cipherStringLen + 1 < cipherStringLen) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Overflow in cipher suite strings"); + return; } + cipherStringLen += 1; /* For the separating colon */ - // If we are blocked by the underlying socket, tell the world that - // there will be one more waiting thread now. - if (sslError->get() == SSL_ERROR_WANT_READ || sslError->get() == SSL_ERROR_WANT_WRITE) { - appData->waitingThreads++; + if (cipherStringLen + c.size() < cipherStringLen) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Overflow in cipher suite strings"); + return; } + cipherStringLen += c.size(); + } - appDataLock.unlock(); + if (cipherStringLen + 1 < cipherStringLen) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Overflow in cipher suite strings"); + return; + } + cipherStringLen += 1; /* For final NUL. */ - switch (sslError->get()) { - // Successfully read at least one byte. - case SSL_ERROR_NONE: { - return result; - } + std::unique_ptr cipherString(new char[cipherStringLen]); + if (cipherString.get() == nullptr) { + conscrypt::jniutil::throwOutOfMemory(env, "Unable to alloc cipher string"); + return; + } + memcpy(cipherString.get(), noSSLv2, strlen(noSSLv2)); + size_t j = strlen(noSSLv2); - // Read zero bytes. End of stream reached. - case SSL_ERROR_ZERO_RETURN: { - return -1; - } + for (int i = 0; i < length; i++) { + ScopedLocalRef cipherSuite( + env, reinterpret_cast(env->GetObjectArrayElement(cipherSuites, i))); + ScopedUtfChars c(env, cipherSuite.get()); - // Need to wait for availability of underlying layer, then retry. - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: { - int selectResult = - sslSelect(env, sslError->get(), fdObject, appData, read_timeout_millis); - if (selectResult == THROWN_EXCEPTION) { - return THROWN_EXCEPTION; - } - if (selectResult == -1) { - return THROW_SSLEXCEPTION; - } - if (selectResult == 0) { - return THROW_SOCKETTIMEOUTEXCEPTION; - } + cipherString[j++] = ':'; + memcpy(&cipherString[j], c.c_str(), c.size()); + j += c.size(); + } - break; - } + cipherString[j++] = 0; + if (j != cipherStringLen) { + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Internal error"); + return; + } - // A problem occurred during a system call, but this is not - // necessarily an error. - case SSL_ERROR_SYSCALL: { - // Connection closed without proper shutdown. Tell caller we - // have reached end-of-stream. - if (result == 0) { - return -1; - } + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_lists cipherSuites=%s", ssl, cipherString.get()); + if (!SSL_set_cipher_list(ssl, cipherString.get())) { + ERR_clear_error(); + conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", + "Illegal cipher suite strings."); + return; + } +} - // System call has been interrupted. Simply retry. - if (errno == EINTR) { - break; - } +static void NativeCrypto_SSL_set_accept_state(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_accept_state", ssl); + if (ssl == nullptr) { + return; + } + SSL_set_accept_state(ssl); +} - // Note that for all other system call errors we fall through - // to the default case, which results in an Exception. - FALLTHROUGH_INTENDED; - } +static void NativeCrypto_SSL_set_connect_state(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_connect_state", ssl); + if (ssl == nullptr) { + return; + } + SSL_set_connect_state(ssl); +} - // Everything else is basically an error. - default: { - return THROW_SSLEXCEPTION; - } - } +/** + * Sets certificate expectations, especially for server to request client auth + */ +static void NativeCrypto_SSL_set_verify(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, jint mode) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_verify mode=%x", ssl, mode); + if (ssl == nullptr) { + return; + } + SSL_set_custom_verify(ssl, static_cast(mode), cert_verify_callback); +} + +/** + * Sets the ciphers suites that are enabled in the SSL + */ +static void NativeCrypto_SSL_set_session(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jlong ssl_session_address) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + if (ssl == nullptr) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session => exception", ssl); + return; + } + + SSL_SESSION* ssl_session = to_SSL_SESSION(env, ssl_session_address, false); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session ssl_session=%p", ssl, ssl_session); + if (ssl_session == nullptr) { + return; } - return -1; + int ret = SSL_set_session(ssl, ssl_session); + if (ret != 1) { + /* + * Translate the error, and throw if it turns out to be a real + * problem. + */ + int sslErrorCode = SSL_get_error(ssl, ret); + if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { + conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, sslErrorCode, + "SSL session set"); + } + } + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session ssl_session=%p => ret=%d", ssl, ssl_session, + ret); } /** - * OpenSSL read function (2): read into buffer at offset n chunks. - * Returns the number of bytes read (success) or value <= 0 (failure). + * Sets the ciphers suites that are enabled in the SSL */ -static jint NativeCrypto_SSL_read(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jobject fdObject, - jobject shc, jbyteArray b, jint offset, jint len, - jint read_timeout_millis) { +static void NativeCrypto_SSL_set_session_creation_enabled(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jboolean creation_enabled) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); JNI_TRACE( - "ssl=%p NativeCrypto_SSL_read fd=%p shc=%p b=%p offset=%d len=%d " - "read_timeout_millis=%d", - ssl, fdObject, shc, b, offset, len, read_timeout_millis); + "ssl=%p NativeCrypto_SSL_set_session_creation_enabled " + "creation_enabled=%d", + ssl, creation_enabled); if (ssl == nullptr) { - return 0; - } - if (fdObject == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "fd == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_read => fd == null", ssl); - return 0; + return; } - if (shc == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_read => sslHandshakeCallbacks == null", ssl); - return 0; + + if (creation_enabled) { + SSL_clear_mode(ssl, SSL_MODE_NO_SESSION_CREATION); + } else { + SSL_set_mode(ssl, SSL_MODE_NO_SESSION_CREATION); } - if (b == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "b == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_read => b == null", ssl); - return 0; +} + +static jboolean NativeCrypto_SSL_session_reused(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_session_reused", ssl); + if (ssl == nullptr) { + return JNI_FALSE; } - size_t array_size = static_cast(env->GetArrayLength(b)); - if (ARRAY_CHUNK_INVALID(array_size, offset, len)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "b"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_read => ArrayIndexOutOfBoundsException", ssl); - return 0; + int reused = SSL_session_reused(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_session_reused => %d", ssl, reused); + return static_cast(reused); +} + +static void NativeCrypto_SSL_accept_renegotiations(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_accept_renegotiations", ssl); + if (ssl == nullptr) { + return; } - SslError sslError; - int ret; - if (conscrypt::jniutil::isGetByteArrayElementsLikelyToReturnACopy(array_size)) { - if (len <= 1024) { - // Allocate small buffers on the stack for performance. - jbyte buf[1024]; - ret = sslRead(env, ssl, fdObject, shc, reinterpret_cast(&buf[0]), len, &sslError, - read_timeout_millis); - if (ret > 0) { - // Don't bother applying changes if issues were encountered. - env->SetByteArrayRegion(b, offset, ret, &buf[0]); - } - } else { - // Allocate larger buffers on the heap. - // ARRAY_CHUNK_INVALID above ensures that len >= 0. - jint remaining = len; - jint buf_size = (remaining >= 65536) ? 65536 : remaining; - std::unique_ptr buf(new jbyte[static_cast(buf_size)]); - // TODO(flooey): Use new(std::nothrow). - if (buf.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate chunk buffer"); - return 0; - } - // TODO(flooey): Fix cumulative read timeout? The effective timeout is the - // multiplied by the number of internal calls to sslRead() below. - ret = 0; - while (remaining > 0) { - jint temp_ret; - jint chunk_size = (remaining >= buf_size) ? buf_size : remaining; - temp_ret = sslRead(env, ssl, fdObject, shc, reinterpret_cast(buf.get()), - chunk_size, &sslError, read_timeout_millis); - if (temp_ret < 0) { - if (ret > 0) { - // We've already read some bytes; attempt to preserve them if this - // is an "expected" error. - if (temp_ret == -1) { - // EOF - break; - } else if (temp_ret == THROWN_EXCEPTION) { - // FD closed. Subsequent calls to sslRead should reproduce the - // exception. - env->ExceptionClear(); - break; - } - } - // An error was encountered. Handle below. - ret = temp_ret; - break; - } - env->SetByteArrayRegion(b, offset, temp_ret, buf.get()); - if (env->ExceptionCheck()) { - // Error committing changes to JVM. - return -1; - } - // Accumulate bytes read. - ret += temp_ret; - offset += temp_ret; - remaining -= temp_ret; - if (temp_ret < chunk_size) { - // sslRead isn't able to fulfill our request right now. - break; - } - } - } - } else { - ScopedByteArrayRW bytes(env, b); - if (bytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_read => threw exception", ssl); - return 0; - } + SSL_set_renegotiate_mode(ssl, ssl_renegotiate_freely); +} - ret = sslRead(env, ssl, fdObject, shc, reinterpret_cast(bytes.get() + offset), len, - &sslError, read_timeout_millis); +static void NativeCrypto_SSL_set_tlsext_host_name(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jstring hostname) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name hostname=%p", ssl, hostname); + if (ssl == nullptr) { + return; } - int result; - switch (ret) { - case THROW_SSLEXCEPTION: - // See sslRead() regarding improper failure to handle normal cases. - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, sslError.release(), - "Read error"); - result = -1; - break; - case THROW_SOCKETTIMEOUTEXCEPTION: - conscrypt::jniutil::throwSocketTimeoutException(env, "Read timed out"); - result = -1; - break; - case THROWN_EXCEPTION: - // SocketException thrown by NetFd.isClosed - // or RuntimeException thrown by callback - result = -1; - break; - default: - result = ret; - break; + ScopedUtfChars hostnameChars(env, hostname); + if (hostnameChars.c_str() == nullptr) { + return; } + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name hostnameChars=%s", ssl, + hostnameChars.c_str()); - JNI_TRACE("ssl=%p NativeCrypto_SSL_read => %d", ssl, result); - return result; + int ret = SSL_set_tlsext_host_name(ssl, hostnameChars.c_str()); + if (ret != 1) { + conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, SSL_ERROR_NONE, + "Error setting host name"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name => error", ssl); + return; + } + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_tlsext_host_name => ok", ssl); } -static int sslWrite(JNIEnv* env, SSL* ssl, jobject fdObject, jobject shc, const char* buf, jint len, - SslError* sslError, int write_timeout_millis) { - JNI_TRACE("ssl=%p sslWrite buf=%p len=%d write_timeout_millis=%d", ssl, buf, len, - write_timeout_millis); - - if (len == 0) { - // Don't bother doing anything in this case. - return 0; +static jstring NativeCrypto_SSL_get_servername(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_servername", ssl); + if (ssl == nullptr) { + return nullptr; } + const char* servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_servername => %s", ssl, servername); + return env->NewStringUTF(servername); +} - BIO* rbio = SSL_get_rbio(ssl); - BIO* wbio = SSL_get_wbio(ssl); +/** + * Selects the ALPN protocol to use. The list of protocols in "primary" is + * considered the order which should take precedence. + */ +static int selectApplicationProtocol(SSL* ssl, unsigned char** out, unsigned char* outLength, + const unsigned char* primary, const unsigned int primaryLength, + const unsigned char* secondary, + const unsigned int secondaryLength) { + JNI_TRACE("primary=%p, length=%d", primary, primaryLength); - AppData* appData = toAppData(ssl); - JNI_TRACE("ssl=%p sslWrite appData=%p", ssl, appData); - if (appData == nullptr) { - return THROW_SSLEXCEPTION; + int status = SSL_select_next_proto(out, outLength, primary, primaryLength, secondary, + secondaryLength); + switch (status) { + case OPENSSL_NPN_NEGOTIATED: + JNI_TRACE("ssl=%p selectApplicationProtocol ALPN negotiated", ssl); + return SSL_TLSEXT_ERR_OK; + break; + case OPENSSL_NPN_UNSUPPORTED: + JNI_TRACE("ssl=%p selectApplicationProtocol ALPN unsupported", ssl); + break; + case OPENSSL_NPN_NO_OVERLAP: + JNI_TRACE("ssl=%p selectApplicationProtocol ALPN no overlap", ssl); + break; } + return SSL_TLSEXT_ERR_NOACK; +} - int count = len; +/** + * Calls out to an application-provided selector to choose the ALPN protocol. + */ +static int selectApplicationProtocol(SSL* ssl, JNIEnv* env, jobject sslHandshakeCallbacks, + unsigned char** out, unsigned char* outLen, + const unsigned char* in, const unsigned int inLen) { + // Copy the input array. + ScopedLocalRef protocols(env, env->NewByteArray(static_cast(inLen))); + if (protocols.get() == nullptr) { + JNI_TRACE("ssl=%p selectApplicationProtocol failed allocating array", ssl); + return SSL_TLSEXT_ERR_NOACK; + } + env->SetByteArrayRegion(protocols.get(), 0, (static_cast(inLen)), + reinterpret_cast(in)); - while (appData->aliveAndKicking && len > 0) { - errno = 0; + // Invoke the selection method. + jmethodID methodID = conscrypt::jniutil::sslHandshakeCallbacks_selectApplicationProtocol; + jint offset = env->CallIntMethod(sslHandshakeCallbacks, methodID, protocols.get()); - std::unique_lock appDataLock(appData->mutex); + if (offset < 0) { + JNI_TRACE("ssl=%p selectApplicationProtocol selection failed", ssl); + return SSL_TLSEXT_ERR_NOACK; + } - if (!SSL_is_init_finished(ssl) && !SSL_in_false_start(ssl) && - !SSL_renegotiate_pending(ssl)) { - JNI_TRACE("ssl=%p sslWrite => init is not finished (state: %s)", ssl, - SSL_state_string_long(ssl)); - return THROW_SSLEXCEPTION; - } + // Point the output to the selected protocol. + *outLen = *(in + offset); + *out = const_cast(in + offset + 1); - size_t bytesMoved = BIO_number_read(rbio) + BIO_number_written(wbio); + return SSL_TLSEXT_ERR_OK; +} - if (!appData->setCallbackState(env, shc, fdObject)) { - return THROWN_EXCEPTION; - } - JNI_TRACE("ssl=%p sslWrite SSL_write len=%d", ssl, len); - int result = SSL_write(ssl, buf, len); - appData->clearCallbackState(); - // callbacks can happen if server requests renegotiation - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p sslWrite exception => THROWN_EXCEPTION", ssl); - return THROWN_EXCEPTION; - } - sslError->reset(ssl, result); - - JNI_TRACE("ssl=%p sslWrite SSL_write result=%d sslError=%d", ssl, result, sslError->get()); - if (conscrypt::trace::kWithJniTraceData) { - for (size_t i = 0; result > 0 && i < static_cast(result); - i += conscrypt::trace::kWithJniTraceDataChunkSize) { - size_t n = result - i; - if (n > conscrypt::trace::kWithJniTraceDataChunkSize) { - n = conscrypt::trace::kWithJniTraceDataChunkSize; - } - JNI_TRACE("ssl=%p sslWrite data: %zu:\n%.*s", ssl, n, (int)n, buf + i); - } - } +/** + * Callback for the server to select an ALPN protocol. + */ +static int alpn_select_callback(SSL* ssl, const unsigned char** out, unsigned char* outLen, + const unsigned char* in, unsigned int inLen, void*) { + JNI_TRACE("ssl=%p alpn_select_callback in=%p inLen=%d", ssl, in, inLen); - // If we have been successful in moving data around, check whether it - // might make sense to wake up other blocked threads, so they can give - // it a try, too. - if (BIO_number_read(rbio) + BIO_number_written(wbio) != bytesMoved && - appData->waitingThreads > 0) { - sslNotify(appData); - } + AppData* appData = toAppData(ssl); + if (appData == nullptr) { + JNI_TRACE("ssl=%p alpn_select_callback appData => 0", ssl); + return SSL_TLSEXT_ERR_NOACK; + } + JNIEnv* env = appData->env; + if (env == nullptr) { + CONSCRYPT_LOG_ERROR("AppData->env missing in alpn_select_callback"); + JNI_TRACE("ssl=%p alpn_select_callback => 0", ssl); + return SSL_TLSEXT_ERR_NOACK; + } - // If we are blocked by the underlying socket, tell the world that - // there will be one more waiting thread now. - if (sslError->get() == SSL_ERROR_WANT_READ || sslError->get() == SSL_ERROR_WANT_WRITE) { - appData->waitingThreads++; + if (in == nullptr || (appData->applicationProtocolsData == nullptr && + !appData->hasApplicationProtocolSelector)) { + if (out != nullptr && outLen != nullptr) { + *out = nullptr; + *outLen = 0; } + JNI_TRACE("ssl=%p alpn_select_callback protocols => 0", ssl); + return SSL_TLSEXT_ERR_NOACK; + } - appDataLock.unlock(); - - switch (sslError->get()) { - // Successfully wrote at least one byte. - case SSL_ERROR_NONE: { - buf += result; - len -= result; - break; - } - - // Wrote zero bytes. End of stream reached. - case SSL_ERROR_ZERO_RETURN: { - return -1; - } - - // Need to wait for availability of underlying layer, then retry. - // The concept of a write timeout doesn't really make sense, and - // it's also not standard Java behavior, so we wait forever here. - case SSL_ERROR_WANT_READ: - case SSL_ERROR_WANT_WRITE: { - int selectResult = - sslSelect(env, sslError->get(), fdObject, appData, write_timeout_millis); - if (selectResult == THROWN_EXCEPTION) { - return THROWN_EXCEPTION; - } - if (selectResult == -1) { - return THROW_SSLEXCEPTION; - } - if (selectResult == 0) { - return THROW_SOCKETTIMEOUTEXCEPTION; - } + if (appData->hasApplicationProtocolSelector) { + return selectApplicationProtocol(ssl, env, appData->sslHandshakeCallbacks, + const_cast(out), outLen, in, inLen); + } - break; - } + return selectApplicationProtocol( + ssl, const_cast(out), outLen, + reinterpret_cast(appData->applicationProtocolsData), + static_cast(appData->applicationProtocolsLength), in, inLen); +} - // A problem occurred during a system call, but this is not - // necessarily an error. - case SSL_ERROR_SYSCALL: { - // Connection closed without proper shutdown. Tell caller we - // have reached end-of-stream. - if (result == 0) { - return -1; - } +static jbyteArray NativeCrypto_getApplicationProtocol(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_getApplicationProtocol", ssl); + if (ssl == nullptr) { + return nullptr; + } + const jbyte* protocol; + unsigned int protocolLength; + SSL_get0_alpn_selected(ssl, reinterpret_cast(&protocol), + &protocolLength); + if (protocolLength == 0) { + return nullptr; + } + jbyteArray result = env->NewByteArray(static_cast(protocolLength)); + if (result != nullptr) { + env->SetByteArrayRegion(result, 0, (static_cast(protocolLength)), protocol); + } + return result; +} - // System call has been interrupted. Simply retry. - if (errno == EINTR) { - break; - } +static void NativeCrypto_setApplicationProtocols(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jboolean client_mode, jbyteArray protocols) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + if (ssl == nullptr) { + return; + } + AppData* appData = toAppData(ssl); + if (appData == nullptr) { + conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to retrieve application data"); + JNI_TRACE("ssl=%p NativeCrypto_setApplicationProtocols appData => 0", ssl); + return; + } - // Note that for all other system call errors we fall through - // to the default case, which results in an Exception. - FALLTHROUGH_INTENDED; + if (protocols != nullptr) { + if (client_mode) { + ScopedByteArrayRO protosBytes(env, protocols); + if (protosBytes.get() == nullptr) { + JNI_TRACE( + "ssl=%p NativeCrypto_setApplicationProtocols protocols=%p => " + "protosBytes == null", + ssl, protocols); + return; } - // Everything else is basically an error. - default: { - return THROW_SSLEXCEPTION; + const unsigned char* tmp = reinterpret_cast(protosBytes.get()); + int ret = SSL_set_alpn_protos(ssl, tmp, static_cast(protosBytes.size())); + if (ret != 0) { + conscrypt::jniutil::throwSSLExceptionStr(env, + "Unable to set ALPN protocols for client"); + ERR_clear_error(); + JNI_TRACE("ssl=%p NativeCrypto_setApplicationProtocols => exception", ssl); + return; + } + } else { + // Server mode - configure the ALPN protocol selection callback. + if (!appData->setApplicationProtocols(env, protocols)) { + conscrypt::jniutil::throwSSLExceptionStr(env, + "Unable to set ALPN protocols for server"); + JNI_TRACE("ssl=%p NativeCrypto_setApplicationProtocols => exception", ssl); + return; } + SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(ssl), alpn_select_callback, nullptr); } } - JNI_TRACE("ssl=%p sslWrite => count=%d", ssl, count); - - return count; } -/** - * OpenSSL write function (2): write into buffer at offset n chunks. - */ -static void NativeCrypto_SSL_write(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jobject fdObject, - jobject shc, jbyteArray b, jint offset, jint len, - jint write_timeout_millis) { +static void NativeCrypto_setHasApplicationProtocolSelector(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jboolean hasSelector) { CHECK_ERROR_QUEUE_ON_RETURN; SSL* ssl = to_SSL(env, ssl_address, true); - JNI_TRACE( - "ssl=%p NativeCrypto_SSL_write fd=%p shc=%p b=%p offset=%d len=%d " - "write_timeout_millis=%d", - ssl, fdObject, shc, b, offset, len, write_timeout_millis); + JNI_TRACE("ssl=%p NativeCrypto_setHasApplicationProtocolSelector selector=%d", ssl, + hasSelector); if (ssl == nullptr) { return; } - if (fdObject == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "fd == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_write => fd == null", ssl); + AppData* appData = toAppData(ssl); + if (appData == nullptr) { + conscrypt::jniutil::throwSSLExceptionStr(env, "Unable to retrieve application data"); + JNI_TRACE("ssl=%p NativeCrypto_setHasApplicationProtocolSelector appData => 0", ssl); return; } - if (shc == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_write => sslHandshakeCallbacks == null", ssl); + + appData->hasApplicationProtocolSelector = hasSelector; + if (hasSelector) { + SSL_CTX_set_alpn_select_cb(SSL_get_SSL_CTX(ssl), alpn_select_callback, nullptr); + } +} + +static jstring NativeCrypto_SSL_get_current_cipher(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher", ssl); + if (ssl == nullptr) { + return nullptr; + } + const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl); + if (cipher == nullptr) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher cipher => null", ssl); + return nullptr; + } + const char* name = SSL_CIPHER_standard_name(cipher); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_current_cipher => %s", ssl, name); + return env->NewStringUTF(name); +} + +static void NativeCrypto_SSL_set1_groups(JNIEnv* env, jclass, jlong sslAddress, + CONSCRYPT_UNUSED jobject sslHolder, jintArray groups) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, sslAddress, /* throwIfNull= */ true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_groups groups=%p", ssl, groups); + if (ssl == nullptr) { + // to_SSL already called conscrypt::jniutil::throwNullPointerException return; } - if (b == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "b == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_write => b == null", ssl); + if (groups == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "groups == null"); + return; + } + ScopedIntArrayRO groups_ro(env, groups); + if (groups_ro.get() == nullptr) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_groups => threw exception", ssl); + conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate buffer for groups"); return; } + std::vector groups_vector; + groups_vector.reserve(groups_ro.size()); + const jint* groups_ptr = groups_ro.get(); + for (size_t i = 0; i < groups_ro.size(); i++) { + groups_vector.push_back(groups_ptr[i]); + } - size_t array_size = static_cast(env->GetArrayLength(b)); - if (ARRAY_CHUNK_INVALID(array_size, offset, len)) { - conscrypt::jniutil::throwException(env, "java/lang/ArrayIndexOutOfBoundsException", "b"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_write => ArrayIndexOutOfBoundsException", ssl); + if (!SSL_set1_groups(ssl, groups_vector.data(), groups_vector.size())) { + conscrypt::jniutil::throwSSLExceptionStr(env, "Error parsing groups"); + ERR_clear_error(); return; } +} - SslError sslError; - int ret; - if (conscrypt::jniutil::isGetByteArrayElementsLikelyToReturnACopy(array_size)) { - if (len <= 1024) { - jbyte buf[1024]; - env->GetByteArrayRegion(b, offset, len, buf); - ret = sslWrite(env, ssl, fdObject, shc, reinterpret_cast(&buf[0]), len, - &sslError, write_timeout_millis); - } else { - // TODO(flooey): Similar safety concerns and questions here as in - // SSL_read. - jint remaining = len; - jint buf_size = (remaining >= 65536) ? 65536 : remaining; - std::unique_ptr buf(new jbyte[static_cast(buf_size)]); - if (buf.get() == nullptr) { - conscrypt::jniutil::throwOutOfMemory(env, "Unable to allocate chunk buffer"); - return; - } - while (remaining > 0) { - jint chunk_size = (remaining >= buf_size) ? buf_size : remaining; - env->GetByteArrayRegion(b, offset, chunk_size, buf.get()); - ret = sslWrite(env, ssl, fdObject, shc, reinterpret_cast(buf.get()), - chunk_size, &sslError, write_timeout_millis); - if (ret == THROW_SSLEXCEPTION || ret == THROW_SOCKETTIMEOUTEXCEPTION || - ret == THROWN_EXCEPTION) { - // Encountered an error. Terminate early and handle below. - break; - } - offset += ret; - remaining -= ret; - } - } - } else { - ScopedByteArrayRO bytes(env, b); - if (bytes.get() == nullptr) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_write => threw exception", ssl); - return; - } - ret = sslWrite(env, ssl, fdObject, shc, reinterpret_cast(bytes.get() + offset), - len, &sslError, write_timeout_millis); +static jstring NativeCrypto_SSL_get_curve_name(JNIEnv* env, jclass, jlong sslAddress, + CONSCRYPT_UNUSED jobject sslHolder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, sslAddress, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_curve_name", ssl); + if (ssl == nullptr) { + return nullptr; } + uint16_t curve_id = SSL_get_curve_id(ssl); + const char* name = SSL_get_curve_name(curve_id); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_curve_name => %s", ssl, name); + return env->NewStringUTF(name); +} - switch (ret) { - case THROW_SSLEXCEPTION: - // See sslWrite() regarding improper failure to handle normal cases. - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, sslError.release(), - "Write error"); - break; - case THROW_SOCKETTIMEOUTEXCEPTION: - conscrypt::jniutil::throwSocketTimeoutException(env, "Write timed out"); - break; - case THROWN_EXCEPTION: - // SocketException thrown by NetFd.isClosed - break; - default: - break; +static jstring NativeCrypto_SSL_get_version(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_version", ssl); + if (ssl == nullptr) { + return nullptr; + } + const char* protocol = SSL_get_version(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_version => %s", ssl, protocol); + return env->NewStringUTF(protocol); +} + +static jobjectArray NativeCrypto_SSL_get0_peer_certificates(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_peer_certificates", ssl); + if (ssl == nullptr) { + return nullptr; + } + + const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl); + if (chain == nullptr) { + return nullptr; + } + + ScopedLocalRef array(env, CryptoBuffersToObjectArray(env, chain)); + if (array.get() == nullptr) { + return nullptr; } + + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_peer_certificates => %p", ssl, array.get()); + return array.release(); } /** @@ -10670,86 +10350,6 @@ static void NativeCrypto_SSL_interrupt(JNIEnv* env, jclass, jlong ssl_address, } } -/** - * OpenSSL close SSL socket function. - */ -static void NativeCrypto_SSL_shutdown(JNIEnv* env, jclass, jlong ssl_address, - CONSCRYPT_UNUSED jobject ssl_holder, jobject fdObject, - jobject shc) { - CHECK_ERROR_QUEUE_ON_RETURN; - SSL* ssl = to_SSL(env, ssl_address, false); - JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown fd=%p shc=%p", ssl, fdObject, shc); - if (ssl == nullptr) { - return; - } - if (fdObject == nullptr) { - return; - } - if (shc == nullptr) { - conscrypt::jniutil::throwNullPointerException(env, "sslHandshakeCallbacks == null"); - JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown => sslHandshakeCallbacks == null", ssl); - return; - } - - AppData* appData = toAppData(ssl); - if (appData != nullptr) { - if (!appData->setCallbackState(env, shc, fdObject)) { - // SocketException thrown by NetFd.isClosed - ERR_clear_error(); - return; - } - - /* - * Try to make socket blocking again. OpenSSL literature recommends this. - */ - int fd = SSL_get_fd(ssl); - JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown s=%d", ssl, fd); -#ifndef _WIN32 - if (fd != -1) { - conscrypt::netutil::setBlocking(fd, true); - } -#endif - - int ret = SSL_shutdown(ssl); - appData->clearCallbackState(); - // callbacks can happen if server requests renegotiation - if (env->ExceptionCheck()) { - JNI_TRACE("ssl=%p NativeCrypto_SSL_shutdown => exception", ssl); - return; - } - switch (ret) { - case 0: - /* - * Shutdown was not successful (yet), but there also - * is no error. Since we can't know whether the remote - * server is actually still there, and we don't want to - * get stuck forever in a second SSL_shutdown() call, we - * simply return. This is not security a problem as long - * as we close the underlying socket, which we actually - * do, because that's where we are just coming from. - */ - break; - case 1: - /* - * Shutdown was successful. We can safely return. Hooray! - */ - break; - default: - /* - * Everything else is a real error condition. We should - * let the Java layer know about this by throwing an - * exception. - */ - int sslError = SSL_get_error(ssl, ret); - conscrypt::jniutil::throwSSLExceptionWithSslErrors(env, ssl, sslError, - "SSL shutdown failed"); - break; - } - } - - ERR_clear_error(); -} - static jint NativeCrypto_SSL_get_shutdown(JNIEnv* env, jclass, jlong ssl_address, CONSCRYPT_UNUSED jobject ssl_holder) { CHECK_ERROR_QUEUE_ON_RETURN; @@ -11094,11 +10694,22 @@ static jobjectArray NativeCrypto_get_cipher_names(JNIEnv* env, jclass, jstring s JNI_TRACE("NativeCrypto_get_cipher_names %s", selector.c_str()); bssl::UniquePtr sslCtx(SSL_CTX_new(TLS_with_buffers_method())); + if (sslCtx.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "Unable to allocate SSL_CTX", + conscrypt::jniutil::throwOutOfMemory); + return nullptr; + } bssl::UniquePtr ssl(SSL_new(sslCtx.get())); + if (ssl.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "Unable to allocate SSL", + conscrypt::jniutil::throwOutOfMemory); + return nullptr; + } if (!SSL_set_cipher_list(ssl.get(), selector.c_str())) { conscrypt::jniutil::throwException(env, "java/lang/IllegalArgumentException", "Unable to set SSL cipher list"); + ERR_clear_error(); return nullptr; } STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl.get()); @@ -11518,7 +11129,7 @@ static jint NativeCrypto_ENGINE_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_ env, ssl, sslError.release(), "SSL handshake terminated", conscrypt::jniutil::throwSSLHandshakeExceptionStr); } - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake clean error => exception", ssl); + JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_do_handshake clean error => exception", ssl); return code; } @@ -11530,7 +11141,7 @@ static jint NativeCrypto_ENGINE_SSL_do_handshake(JNIEnv* env, jclass, jlong ssl_ conscrypt::jniutil::throwSSLExceptionWithSslErrors( env, ssl, sslError.release(), "SSL handshake aborted", conscrypt::jniutil::throwSSLHandshakeExceptionStr); - JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake unclean error => exception", ssl); + JNI_TRACE("ssl=%p NativeCrypto_ENGINE_SSL_do_handshake unclean error => exception", ssl); return code; } @@ -12145,6 +11756,7 @@ static int NativeCrypto_BIO_read(JNIEnv* env, jclass, jlong bioRef, jbyteArray o int read = BIO_read(bio, buffer.get(), static_cast(outputSize)); if (read <= 0) { conscrypt::jniutil::throwIOException(env, "BIO_read"); + ERR_clear_error(); JNI_TRACE("BIO_read(%p, %p) => threw IO exception", bio, outputJavaBytes); return 0; } @@ -12492,6 +12104,14 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(CMAC_UpdateDirect, "(" REF_CMAC_CTX "JI)V"), CONSCRYPT_NATIVE_METHOD(CMAC_Final, "(" REF_CMAC_CTX ")[B"), CONSCRYPT_NATIVE_METHOD(CMAC_Reset, "(" REF_CMAC_CTX ")V"), + CONSCRYPT_NATIVE_METHOD(wrap_EC_private_key_pkcs8, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(wrap_RSA_private_key_pkcs8, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(wrap_RSA_public_key_x509, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(wrap_EC_public_key_x509, "([BLjava/lang/String;)[B"), + CONSCRYPT_NATIVE_METHOD(unwrap_RSA_private_key_pkcs8, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(unwrap_RSA_public_key_x509, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(unwrap_EC_private_key_pkcs8, "([B)[B"), + CONSCRYPT_NATIVE_METHOD(unwrap_EC_public_key_x509, "([B)[B"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_new_RSA, "([B[B[B[B[B[B[B[B)J"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_new_EC_KEY, "(" REF_EC_GROUP REF_EC_POINT "[B)J"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_type, "(" REF_EVP_PKEY ")I"), @@ -12593,6 +12213,7 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(EVP_PKEY_CTX_set_rsa_mgf1_md, "(JJ)V"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_CTX_set_rsa_oaep_md, "(JJ)V"), CONSCRYPT_NATIVE_METHOD(EVP_PKEY_CTX_set_rsa_oaep_label, "(J[B)V"), + CONSCRYPT_NATIVE_METHOD(EVP_PKEY_CTX_set1_signature_context_string, "(J[B)V"), CONSCRYPT_NATIVE_METHOD(EVP_get_cipherbyname, "(Ljava/lang/String;)J"), CONSCRYPT_NATIVE_METHOD(EVP_CipherInit_ex, "(" REF_EVP_CIPHER_CTX "J[B[BZ)V"), CONSCRYPT_NATIVE_METHOD(EVP_CipherUpdate, "(" REF_EVP_CIPHER_CTX "[BI[BII)I"), @@ -12719,15 +12340,19 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(asn1_read_uint64, "(J)J"), CONSCRYPT_NATIVE_METHOD(asn1_read_null, "(J)V"), CONSCRYPT_NATIVE_METHOD(asn1_read_oid, "(J)Ljava/lang/String;"), + CONSCRYPT_NATIVE_METHOD(asn1_read_oid_raw, "(J)Ljava/lang/String;"), + CONSCRYPT_NATIVE_METHOD(asn1_read_bitstring_payload, "(JI)[B"), CONSCRYPT_NATIVE_METHOD(asn1_read_is_empty, "(J)Z"), CONSCRYPT_NATIVE_METHOD(asn1_read_free, "(J)V"), CONSCRYPT_NATIVE_METHOD(asn1_write_init, "()J"), CONSCRYPT_NATIVE_METHOD(asn1_write_sequence, "(J)J"), CONSCRYPT_NATIVE_METHOD(asn1_write_tag, "(JI)J"), CONSCRYPT_NATIVE_METHOD(asn1_write_octetstring, "(J[B)V"), + CONSCRYPT_NATIVE_METHOD(asn1_write_bitstring, "(J[B)V"), CONSCRYPT_NATIVE_METHOD(asn1_write_uint64, "(JJ)V"), CONSCRYPT_NATIVE_METHOD(asn1_write_null, "(J)V"), CONSCRYPT_NATIVE_METHOD(asn1_write_oid, "(JLjava/lang/String;)V"), + CONSCRYPT_NATIVE_METHOD(asn1_write_oid_raw, "(JLjava/lang/String;)V"), CONSCRYPT_NATIVE_METHOD(asn1_write_flush, "(J)V"), CONSCRYPT_NATIVE_METHOD(asn1_write_cleanup, "(J)V"), CONSCRYPT_NATIVE_METHOD(asn1_write_finish, "(J)[B"), @@ -12738,9 +12363,6 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(SSL_CTX_set_session_id_context, "(J" REF_SSL_CTX "[B)V"), CONSCRYPT_NATIVE_METHOD(SSL_CTX_set_timeout, "(J" REF_SSL_CTX "J)J"), CONSCRYPT_NATIVE_METHOD(SSL_new, "(J" REF_SSL_CTX ")J"), - CONSCRYPT_NATIVE_METHOD(SSL_enable_tls_channel_id, "(J" REF_SSL ")V"), - CONSCRYPT_NATIVE_METHOD(SSL_get_tls_channel_id, "(J" REF_SSL ")[B"), - CONSCRYPT_NATIVE_METHOD(SSL_set1_tls_channel_id, "(J" REF_SSL REF_EVP_PKEY ")V"), CONSCRYPT_NATIVE_METHOD(setLocalCertsAndPrivateKey, "(J" REF_SSL "[[B" REF_EVP_PKEY ")V"), CONSCRYPT_NATIVE_METHOD(SSL_set_client_CA_list, "(J" REF_SSL "[[B)V"), CONSCRYPT_NATIVE_METHOD(SSL_set_mode, "(J" REF_SSL "J)J"), @@ -12769,16 +12391,12 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(SSL_accept_renegotiations, "(J" REF_SSL ")V"), CONSCRYPT_NATIVE_METHOD(SSL_set_tlsext_host_name, "(J" REF_SSL "Ljava/lang/String;)V"), CONSCRYPT_NATIVE_METHOD(SSL_get_servername, "(J" REF_SSL ")Ljava/lang/String;"), - CONSCRYPT_NATIVE_METHOD(SSL_do_handshake, "(J" REF_SSL FILE_DESCRIPTOR SSL_CALLBACKS "I)V"), CONSCRYPT_NATIVE_METHOD(SSL_get_current_cipher, "(J" REF_SSL ")Ljava/lang/String;"), CONSCRYPT_NATIVE_METHOD(SSL_set1_groups, "(J" REF_SSL "[I)V"), CONSCRYPT_NATIVE_METHOD(SSL_get_curve_name, "(J" REF_SSL ")Ljava/lang/String;"), CONSCRYPT_NATIVE_METHOD(SSL_get_version, "(J" REF_SSL ")Ljava/lang/String;"), CONSCRYPT_NATIVE_METHOD(SSL_get0_peer_certificates, "(J" REF_SSL ")[[B"), - CONSCRYPT_NATIVE_METHOD(SSL_read, "(J" REF_SSL FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)I"), - CONSCRYPT_NATIVE_METHOD(SSL_write, "(J" REF_SSL FILE_DESCRIPTOR SSL_CALLBACKS "[BIII)V"), CONSCRYPT_NATIVE_METHOD(SSL_interrupt, "(J" REF_SSL ")V"), - CONSCRYPT_NATIVE_METHOD(SSL_shutdown, "(J" REF_SSL FILE_DESCRIPTOR SSL_CALLBACKS ")V"), CONSCRYPT_NATIVE_METHOD(SSL_get_shutdown, "(J" REF_SSL ")I"), CONSCRYPT_NATIVE_METHOD(SSL_free, "(J" REF_SSL ")V"), CONSCRYPT_NATIVE_METHOD(SSL_SESSION_session_id, "(J)[B"), diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java index 3f0ac2914..0b588ac09 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java @@ -35,42 +35,6 @@ abstract class AbstractConscryptEngine extends SSLEngine { */ abstract int maxSealOverhead(); - /** - * Enables/disables TLS Channel ID for this server engine. - * - *

This method needs to be invoked before the handshake starts. - * - * @throws IllegalStateException if this is a client engine or if the handshake has already - * started. - */ - abstract void setChannelIdEnabled(boolean enabled); - - /** - * Gets the TLS Channel ID for this server engine. Channel ID is only available once the - * handshake completes. - * - * @return channel ID or {@code null} if not available. - * - * @throws IllegalStateException if this is a client engine or if the handshake has not yet - * completed. - * @throws SSLException if channel ID is available but could not be obtained. - */ - abstract byte[] getChannelId() throws SSLException; - - /** - * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client engine. - * - *

This method needs to be invoked before the handshake starts. - * - * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables - * TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST - * P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1). - * - * @throws IllegalStateException if this is a server engine or if the handshake has already - * started. - */ - abstract void setChannelIdPrivateKey(PrivateKey privateKey); - /** * Sets the listener for the completion of the TLS handshake. */ @@ -135,6 +99,13 @@ public abstract SSLEngineResult wrap(ByteBuffer[] srcs, int srcsOffset, int srcs */ abstract void setUseSessionTickets(boolean useSessionTickets); + /** + * This method sets the ECH config data to be used in the TLS handshake. + * + * @param echConfigList the ECH config data to be used in the TLS handshake + */ + abstract void setEchConfigList(byte[] echConfigList); + /** * Sets the list of ALPN protocols. * diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java index 8d21bea3f..27f8d99ce 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java @@ -630,40 +630,11 @@ private boolean isDelegating() { abstract void setUseSessionTickets(boolean useSessionTickets); /** - * Enables/disables TLS Channel ID for this server socket. + * This method sets the ECH config data to be used in the TLS handshake. * - *

This method needs to be invoked before the handshake starts. - * - * @throws IllegalStateException if this is a client socket or if the handshake has already - * started. - */ - abstract void setChannelIdEnabled(boolean enabled); - - /** - * Gets the TLS Channel ID for this server socket. Channel ID is only available once the - * handshake completes. - * - * @return channel ID or {@code null} if not available. - * - * @throws IllegalStateException if this is a client socket or if the handshake has not yet - * completed. - * @throws SSLException if channel ID is available but could not be obtained. - */ - abstract byte[] getChannelId() throws SSLException; - - /** - * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket. - * - *

This method needs to be invoked before the handshake starts. - * - * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables - * TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST - * P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1). - * - * @throws IllegalStateException if this is a server socket or if the handshake has already - * started. + * @param echConfigList the ECH config data to be used in the TLS handshake */ - abstract void setChannelIdPrivateKey(PrivateKey privateKey); + abstract void setEchConfigList(byte[] echConfigList); /** * Returns the protocol agreed upon by client and server, or {@code null} if diff --git a/common/src/main/java/org/conscrypt/AddressUtils.java b/common/src/main/java/org/conscrypt/AddressUtils.java index 8b7217254..1a46ccaab 100644 --- a/common/src/main/java/org/conscrypt/AddressUtils.java +++ b/common/src/main/java/org/conscrypt/AddressUtils.java @@ -18,7 +18,6 @@ /** Utilities to check whether IP addresses meet some criteria. */ final class AddressUtils { - private static final int IPV4_OCTET_COUNT = 4; private static final int MAX_IPV4_OCTET_VALUE = 255; private static final int MAX_IPV4_OCTET_DIGITS = 3; @@ -42,8 +41,7 @@ static boolean isValidSniHostname(String sniHostname) { // Must be a FQDN that does not have a trailing dot. return (asciiEqualsIgnoreCase(sniHostname, "localhost") || sniHostname.indexOf('.') != -1) - && !isLiteralIpAddress(sniHostname) - && !sniHostname.endsWith(".") + && !isLiteralIpAddress(sniHostname) && !sniHostname.endsWith(".") && sniHostname.indexOf('\0') == -1; } @@ -142,7 +140,7 @@ private static boolean isValidIPv6(String s) { } groupCount++; if (groupCount > IPV6_TOTAL_GROUPS - || (hasDoubleColon && groupCount >= IPV6_TOTAL_GROUPS)) { + || (hasDoubleColon && groupCount >= IPV6_TOTAL_GROUPS)) { return false; } groupLen = 0; @@ -219,13 +217,8 @@ private static boolean isValidZoneId(String s, int start) { // // We choose to reject them consistently on all platforms because they are control // characters and are not valid in a real network interface name (Zone ID). - if (c == '\n' - || c == '\r' - || c == '\u0085' - || c == '\u2028' - || c == '\u2029' - || c == '\u000B' - || c == '\u000C') { + if (c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029' + || c == '\u000B' || c == '\u000C') { return false; } } @@ -261,8 +254,16 @@ private static char toLowerCaseAscii(char c) { * {@link String#equalsIgnoreCase(String)} to: 1. Avoid dependency on Guava's Ascii class. 2. * Avoid locale-dependent behavior of String.equalsIgnoreCase (e.g. Turkish 'I' mapping), * ensuring strictly ASCII comparison. 3. Avoid any object allocations. + * + *

Both input strings are allowed to be null. */ - private static boolean asciiEqualsIgnoreCase(String s, String expected) { + static boolean asciiEqualsIgnoreCase(String s, String expected) { + if (s == null && expected == null) { + return true; + } + if (s == null || expected == null) { + return false; + } int len = s.length(); if (len != expected.length()) { return false; diff --git a/common/src/main/java/org/conscrypt/ArrayUtils.java b/common/src/main/java/org/conscrypt/ArrayUtils.java index df5ada006..a8189f987 100644 --- a/common/src/main/java/org/conscrypt/ArrayUtils.java +++ b/common/src/main/java/org/conscrypt/ArrayUtils.java @@ -29,7 +29,7 @@ private ArrayUtils() {} * Checks that the range described by {@code offset} and {@code count} * doesn't exceed {@code arrayLength}. */ - static void checkOffsetAndCount(int arrayLength, int offset, int count) { + public static void checkOffsetAndCount(int arrayLength, int offset, int count) { if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) { throw new ArrayIndexOutOfBoundsException("length=" + arrayLength + "; regionStart=" + offset + "; regionLength=" + count); diff --git a/common/src/main/java/org/conscrypt/BufferUtils.java b/common/src/main/java/org/conscrypt/BufferUtils.java index 3c5ee965a..28d7d66d7 100644 --- a/common/src/main/java/org/conscrypt/BufferUtils.java +++ b/common/src/main/java/org/conscrypt/BufferUtils.java @@ -22,13 +22,16 @@ import java.nio.ByteBuffer; -final class BufferUtils { +/** + * Utility methods for dealing with arrays of ByteBuffers. + */ +public final class BufferUtils { private BufferUtils() {} /** * Throws {@link IllegalArgumentException} if any of the buffers in the array are null. */ - static void checkNotNull(ByteBuffer[] buffers) { + public static void checkNotNull(ByteBuffer[] buffers) { for (ByteBuffer buffer : buffers) { if (buffer == null) { throw new IllegalArgumentException("Null buffer in array"); @@ -39,7 +42,7 @@ static void checkNotNull(ByteBuffer[] buffers) { /** * Returns the total number of bytes remaining in the buffer array. */ - static long remaining(ByteBuffer[] buffers) { + public static long remaining(ByteBuffer[] buffers) { long size = 0; for (ByteBuffer buffer : buffers) { size += buffer.remaining(); @@ -52,7 +55,7 @@ static long remaining(ByteBuffer[] buffers) { * * @throws IllegalArgumentException if there are fewer than {@code toConsume} bytes remaining */ - static void consume(ByteBuffer[] sourceBuffers, int toConsume) { + public static void consume(ByteBuffer[] sourceBuffers, int toConsume) { for (ByteBuffer sourceBuffer : sourceBuffers) { int amount = min(sourceBuffer.remaining(), toConsume); if (amount > 0) { @@ -72,7 +75,7 @@ static void consume(ByteBuffer[] sourceBuffers, int toConsume) { * Looks for a buffer in the buffer array which EITHER is larger than {@code minSize} AND * has no preceding non-empty buffers OR is the only non-empty buffer in the array. */ - static ByteBuffer getBufferLargerThan(ByteBuffer[] buffers, int minSize) { + public static ByteBuffer getBufferLargerThan(ByteBuffer[] buffers, int minSize) { int length = buffers.length; for (int i = 0; i < length; i++) { ByteBuffer buffer = buffers[i]; @@ -100,7 +103,7 @@ static ByteBuffer getBufferLargerThan(ByteBuffer[] buffers, int minSize) { * {@code consume()} method. * */ - static ByteBuffer copyNoConsume(ByteBuffer[] buffers, ByteBuffer destination, int maxAmount) { + public static ByteBuffer copyNoConsume(ByteBuffer[] buffers, ByteBuffer destination, int maxAmount) { checkArgument(destination.remaining() >= maxAmount, "Destination buffer too small"); int needed = maxAmount; for (ByteBuffer buffer : buffers) { diff --git a/common/src/main/java/org/conscrypt/CertBlocklist.java b/common/src/main/java/org/conscrypt/CertBlocklist.java index 0ed68b0ab..056718f02 100644 --- a/common/src/main/java/org/conscrypt/CertBlocklist.java +++ b/common/src/main/java/org/conscrypt/CertBlocklist.java @@ -16,12 +16,15 @@ package org.conscrypt; +import org.conscrypt.Internal; + import java.math.BigInteger; import java.security.PublicKey; /** * A set of certificates that are blacklisted from trust. */ +@Internal public interface CertBlocklist { /** * Returns whether the given public key is in the blacklist. diff --git a/common/src/main/java/org/conscrypt/CertBlocklistEntry.java b/common/src/main/java/org/conscrypt/CertBlocklistEntry.java new file mode 100644 index 000000000..950d568d5 --- /dev/null +++ b/common/src/main/java/org/conscrypt/CertBlocklistEntry.java @@ -0,0 +1,37 @@ +/* + * Copyright 2025 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 org.conscrypt.Internal; + +/** + * An entry in the blocklist, for the purpose of reporting. + */ +@Internal +public interface CertBlocklistEntry { + enum Origin { SHA1_TEST, SHA1_BUILT_IN, SHA1_FILE, SHA256_TEST, SHA256_BUILT_IN, SHA256_FILE } + + /** + * Returns the origin of this entry. + */ + Origin getOrigin(); + + /** + * Returns the index of this entry in its blocklist. + */ + int getIndex(); +} diff --git a/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java b/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java index c47c5f190..ef1cc75cc 100644 --- a/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java +++ b/common/src/main/java/org/conscrypt/CertificatePriorityComparator.java @@ -120,12 +120,12 @@ private int compareKeyAlgorithm(PublicKey lhs, PublicKey rhs) { String lhsAlgorithm = lhs.getAlgorithm(); String rhsAlgorithm = rhs.getAlgorithm(); - if (lhsAlgorithm.equalsIgnoreCase(rhsAlgorithm)) { + if (AddressUtils.asciiEqualsIgnoreCase(lhsAlgorithm, rhsAlgorithm)) { return 0; } // Prefer EC to RSA. - if ("EC".equalsIgnoreCase(lhsAlgorithm)) { + if (AddressUtils.asciiEqualsIgnoreCase(lhsAlgorithm, "EC")) { return 1; } else { return -1; @@ -135,7 +135,7 @@ private int compareKeyAlgorithm(PublicKey lhs, PublicKey rhs) { private int compareKeySize(PublicKey lhs, PublicKey rhs) { String lhsAlgorithm = lhs.getAlgorithm(); String rhsAlgorithm = rhs.getAlgorithm(); - if (!lhsAlgorithm.equalsIgnoreCase(rhsAlgorithm)) { + if (!AddressUtils.asciiEqualsIgnoreCase(lhsAlgorithm, rhsAlgorithm)) { throw new IllegalArgumentException("Keys are not of the same type"); } int lhsSize = getKeySize(lhs); diff --git a/common/src/main/java/org/conscrypt/CompositeMlDsaAlgorithm.java b/common/src/main/java/org/conscrypt/CompositeMlDsaAlgorithm.java new file mode 100644 index 000000000..364890882 --- /dev/null +++ b/common/src/main/java/org/conscrypt/CompositeMlDsaAlgorithm.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2026 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; + +/** + * Composite ML-DSA algorithm. Values from + * https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html#name-algorithm-identifiers-and-p. + */ +@Internal +public enum CompositeMlDsaAlgorithm { + MLDSA44_RSA2048_PSS_SHA256("MLDSA44-RSA2048-PSS-SHA256", "1.3.6.1.5.5.7.6.37", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x25}, + "COMPSIG-MLDSA44-RSA2048-PSS-SHA256", "SHA-256", + MlDsaAlgorithm.ML_DSA_44, "RSA", 2048, "SHA-256", true, null), + MLDSA44_RSA2048_PKCS15_SHA256("MLDSA44-RSA2048-PKCS15-SHA256", "1.3.6.1.5.5.7.6.38", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x26}, + "COMPSIG-MLDSA44-RSA2048-PKCS15-SHA256", "SHA-256", + MlDsaAlgorithm.ML_DSA_44, "RSA", 2048, "SHA-256", false, null), + MLDSA44_ED25519_SHA512("MLDSA44-Ed25519-SHA512", "1.3.6.1.5.5.7.6.39", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x27}, + "COMPSIG-MLDSA44-Ed25519-SHA512", "SHA-512", MlDsaAlgorithm.ML_DSA_44, + "Ed25519", null, null, null, null), + MLDSA44_ECDSA_P256_SHA256("MLDSA44-ECDSA-P256-SHA256", "1.3.6.1.5.5.7.6.40", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x28}, + "COMPSIG-MLDSA44-ECDSA-P256-SHA256", "SHA-256", + MlDsaAlgorithm.ML_DSA_44, "EC", 256, "SHA-256", null, "prime256v1"), + + MLDSA65_RSA3072_PSS_SHA512("MLDSA65-RSA3072-PSS-SHA512", "1.3.6.1.5.5.7.6.41", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x29}, + "COMPSIG-MLDSA65-RSA3072-PSS-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_65, "RSA", 3072, "SHA-256", true, null), + MLDSA65_RSA3072_PKCS15_SHA512("MLDSA65-RSA3072-PKCS15-SHA512", "1.3.6.1.5.5.7.6.42", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x2a}, + "COMPSIG-MLDSA65-RSA3072-PKCS15-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_65, "RSA", 3072, "SHA-256", false, null), + MLDSA65_RSA4096_PSS_SHA512("MLDSA65-RSA4096-PSS-SHA512", "1.3.6.1.5.5.7.6.43", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x2b}, + "COMPSIG-MLDSA65-RSA4096-PSS-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_65, "RSA", 4096, "SHA-384", true, null), + MLDSA65_RSA4096_PKCS15_SHA512("MLDSA65-RSA4096-PKCS15-SHA512", "1.3.6.1.5.5.7.6.44", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x2c}, + "COMPSIG-MLDSA65-RSA4096-PKCS15-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_65, "RSA", 4096, "SHA-384", false, null), + MLDSA65_ECDSA_P256_SHA512("MLDSA65-ECDSA-P256-SHA512", "1.3.6.1.5.5.7.6.45", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x2d}, + "COMPSIG-MLDSA65-ECDSA-P256-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_65, "EC", 256, "SHA-256", null, "prime256v1"), + MLDSA65_ECDSA_P384_SHA512("MLDSA65-ECDSA-P384-SHA512", "1.3.6.1.5.5.7.6.46", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x2e}, + "COMPSIG-MLDSA65-ECDSA-P384-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_65, "EC", 384, "SHA-384", null, "secp384r1"), + MLDSA65_ED25519_SHA512("MLDSA65-Ed25519-SHA512", "1.3.6.1.5.5.7.6.48", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x30}, + "COMPSIG-MLDSA65-Ed25519-SHA512", "SHA-512", MlDsaAlgorithm.ML_DSA_65, + "Ed25519", null, null, null, null), + + MLDSA87_ECDSA_P384_SHA512("MLDSA87-ECDSA-P384-SHA512", "1.3.6.1.5.5.7.6.49", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x31}, + "COMPSIG-MLDSA87-ECDSA-P384-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_87, "EC", 384, "SHA-384", null, "secp384r1"), + MLDSA87_RSA3072_PSS_SHA512("MLDSA87-RSA3072-PSS-SHA512", "1.3.6.1.5.5.7.6.52", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x34}, + "COMPSIG-MLDSA87-RSA3072-PSS-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_87, "RSA", 3072, "SHA-256", true, null), + MLDSA87_RSA4096_PSS_SHA512("MLDSA87-RSA4096-PSS-SHA512", "1.3.6.1.5.5.7.6.53", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x35}, + "COMPSIG-MLDSA87-RSA4096-PSS-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_87, "RSA", 4096, "SHA-384", true, null), + MLDSA87_ECDSA_P521_SHA512("MLDSA87-ECDSA-P521-SHA512", "1.3.6.1.5.5.7.6.54", + new byte[] {(byte) 0x06, (byte) 0x08, (byte) 0x2b, (byte) 0x06, + (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x07, + (byte) 0x06, (byte) 0x36}, + "COMPSIG-MLDSA87-ECDSA-P521-SHA512", "SHA-512", + MlDsaAlgorithm.ML_DSA_87, "EC", 521, "SHA-512", null, "secp521r1"); + + private final String name; + private final String oid; + @SuppressWarnings("Immutable") // We never change this byte array. + private final byte[] oidBytes; + private final String label; + private final String preHashAlgorithm; + private final MlDsaAlgorithm mlDsaAlgorithm; + private final String classicAlgorithm; + private final Integer classicRsaKeySize; + private final String classicSignatureDigest; + private final Boolean isRsaPss; + private final String ecCurve; + + private CompositeMlDsaAlgorithm(String name, String oid, byte[] oidBytes, String label, + String preHashAlgorithm, MlDsaAlgorithm mlDsaAlgorithm, + String classicAlgorithm, Integer classicKeySize, + String classicSignatureDigest, Boolean isRsaPss, + String ecCurve) { + this.name = name; + this.oid = oid; + this.oidBytes = oidBytes; + this.label = label; + this.preHashAlgorithm = preHashAlgorithm; + this.mlDsaAlgorithm = mlDsaAlgorithm; + this.classicAlgorithm = classicAlgorithm; + this.classicRsaKeySize = classicKeySize; + this.classicSignatureDigest = classicSignatureDigest; + this.isRsaPss = isRsaPss; + this.ecCurve = ecCurve; + } + + public String getName() { + return name; + } + + public String getOid() { + return oid; + } + + public byte[] getOidBytes() { + return oidBytes.clone(); + } + + public String getLabel() { + return label; + } + + public String getPreHashAlgorithm() { + return preHashAlgorithm; + } + + public MlDsaAlgorithm getMlDsaAlgorithm() { + return mlDsaAlgorithm; + } + + public String getClassicAlgorithm() { + return classicAlgorithm; + } + + public Integer getClassicKeySize() { + return classicRsaKeySize; + } + + public String getClassicSignatureDigest() { + return classicSignatureDigest; + } + + public boolean isPss() { + return isRsaPss != null && isRsaPss; + } + + public String getEcCurve() { + return ecCurve; + } + + @Override + public String toString() { + return name; + } + + public static CompositeMlDsaAlgorithm fromName(String name) { + for (CompositeMlDsaAlgorithm alg : values()) { + if (alg.getName().equals(name)) { + return alg; + } + } + throw new IllegalArgumentException("Unsupported algorithm: " + name); + } + + public static CompositeMlDsaAlgorithm fromOid(String oid) { + for (CompositeMlDsaAlgorithm alg : values()) { + if (alg.getOid().equals(oid)) { + return alg; + } + } + throw new IllegalArgumentException("Unsupported algorithm OID: " + oid); + } +} diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java index d86bdaa2a..f31227f11 100644 --- a/common/src/main/java/org/conscrypt/Conscrypt.java +++ b/common/src/main/java/org/conscrypt/Conscrypt.java @@ -421,49 +421,13 @@ public static void setUseSessionTickets(SSLSocket socket, boolean useSessionTick } /** - * Enables/disables TLS Channel ID for the given server-side socket. - * - *

This method needs to be invoked before the handshake starts. - * - * @param socket the socket - * @param enabled Whether to enable channel ID. - * @throws IllegalStateException if this is a client socket or if the handshake has already - * started. - */ - public static void setChannelIdEnabled(SSLSocket socket, boolean enabled) { - toConscrypt(socket).setChannelIdEnabled(enabled); - } - - /** - * Gets the TLS Channel ID for the given server-side socket. Channel ID is only available - * once the handshake completes. - * - * @param socket the socket - * @return channel ID or {@code null} if not available. - * @throws IllegalStateException if this is a client socket or if the handshake has not yet - * completed. - * @throws SSLException if channel ID is available but could not be obtained. - */ - public static byte[] getChannelId(SSLSocket socket) throws SSLException { - return toConscrypt(socket).getChannelId(); - } - - /** - * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket. - * - *

This method needs to be invoked before the handshake starts. + * This method sets the ECH config data to be used in the TLS handshake. * * @param socket the socket - * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key - * (disables TLS Channel ID). - * The private key must be an Elliptic Curve (EC) key based on the NIST P-256 curve (aka - * SECG secp256r1 or ANSI - * X9.62 prime256v1). - * @throws IllegalStateException if this is a server socket or if the handshake has already - * started. + * @param echConfigList the ECH config data to be used in the TLS handshake */ - public static void setChannelIdPrivateKey(SSLSocket socket, PrivateKey privateKey) { - toConscrypt(socket).setChannelIdPrivateKey(privateKey); + public static void setEchConfigList(SSLSocket socket, byte[] echConfigList) { + toConscrypt(socket).setEchConfigList(echConfigList); } /** @@ -629,51 +593,6 @@ public static void setHandshakeListener(SSLEngine engine, HandshakeListener hand toConscrypt(engine).setHandshakeListener(handshakeListener); } - /** - * Enables/disables TLS Channel ID for the given server-side engine. - * - *

This method needs to be invoked before the handshake starts. - * - * @param engine the engine - * @param enabled Whether to enable channel ID. - * @throws IllegalStateException if this is a client engine or if the handshake has already - * started. - */ - public static void setChannelIdEnabled(SSLEngine engine, boolean enabled) { - toConscrypt(engine).setChannelIdEnabled(enabled); - } - - /** - * Gets the TLS Channel ID for the given server-side engine. Channel ID is only available - * once the handshake completes. - * - * @param engine the engine - * @return channel ID or {@code null} if not available. - * @throws IllegalStateException if this is a client engine or if the handshake has not yet - * completed. - * @throws SSLException if channel ID is available but could not be obtained. - */ - public static byte[] getChannelId(SSLEngine engine) throws SSLException { - return toConscrypt(engine).getChannelId(); - } - - /** - * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client engine. - * - *

This method needs to be invoked before the handshake starts. - * - * @param engine the engine - * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key - * (disables TLS Channel ID). - * The private key must be an Elliptic Curve (EC) key based on the NIST P-256 curve (aka - * SECG secp256r1 or ANSI X9.62 prime256v1). - * @throws IllegalStateException if this is a server engine or if the handshake has already - * started. - */ - public static void setChannelIdPrivateKey(SSLEngine engine, PrivateKey privateKey) { - toConscrypt(engine).setChannelIdPrivateKey(privateKey); - } - /** * Extended unwrap method for multiple source and destination buffers. * @@ -719,6 +638,16 @@ public static void setUseSessionTickets(SSLEngine engine, boolean useSessionTick toConscrypt(engine).setUseSessionTickets(useSessionTickets); } + /** + * This method sets the ECH config data to be used in the TLS handshake. + * + * @param engine the engine + * @param echConfigList the ECH config data to be used in the TLS handshake + */ + public static void setEchConfigList(SSLEngine engine, byte[] echConfigList) { + toConscrypt(engine).setEchConfigList(echConfigList); + } + /** * Sets the application-layer protocols (ALPN) in prioritization order. * diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java index 16e7b5137..965a84b3e 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java @@ -70,6 +70,7 @@ import org.conscrypt.NativeRef.SSL_SESSION; import org.conscrypt.NativeSsl.BioWrapper; import org.conscrypt.SSLParametersImpl.AliasChooser; +import org.conscrypt.metrics.TlsEncryptedClientHelloHandshake; import java.io.IOException; import java.io.InterruptedIOException; @@ -167,12 +168,6 @@ public ConscryptSession provideSession() { } })); - /** - * Private key for the TLS Channel ID extension. This field is client-side only. Set during - * startHandshake. - */ - private OpenSSLKey channelIdPrivateKey; - private int maxSealOverhead; private HandshakeListener handshakeListener; @@ -255,102 +250,6 @@ int maxSealOverhead() { return maxSealOverhead; } - /** - * Enables/disables TLS Channel ID for this server engine. - * - *

This method needs to be invoked before the handshake starts. - * - * @throws IllegalStateException if this is a client engine or if the handshake has already - * started. - */ - @Override - void setChannelIdEnabled(boolean enabled) { - synchronized (ssl) { - if (getUseClientMode()) { - throw new IllegalStateException("Not allowed in client mode"); - } - if (isHandshakeStarted()) { - throw new IllegalStateException("Could not enable/disable Channel ID after the " - + "initial handshake has begun."); - } - sslParameters.channelIdEnabled = enabled; - } - } - - /** - * Gets the TLS Channel ID for this server engine. Channel ID is only available once the - * handshake completes. - * - * @return channel ID or {@code null} if not available. - * - * @throws IllegalStateException if this is a client engine or if the handshake has not yet - * completed. - * @throws SSLException if channel ID is available but could not be obtained. - */ - @Override - byte[] getChannelId() throws SSLException { - synchronized (ssl) { - if (getUseClientMode()) { - throw new IllegalStateException("Not allowed in client mode"); - } - - if (isHandshakeStarted()) { - throw new IllegalStateException( - "Channel ID is only available after handshake completes"); - } - return ssl.getTlsChannelId(); - } - } - - /** - * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client engine. - * - *

This method needs to be invoked before the handshake starts. - * - * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables - * TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST - * P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1). - * - * @throws IllegalStateException if this is a server engine or if the handshake has already - * started. - */ - @Override - void setChannelIdPrivateKey(PrivateKey privateKey) { - if (!getUseClientMode()) { - throw new IllegalStateException("Not allowed in server mode"); - } - - synchronized (ssl) { - if (isHandshakeStarted()) { - throw new IllegalStateException("Could not change Channel ID private key " - + "after the initial handshake has begun."); - } - - if (privateKey == null) { - sslParameters.channelIdEnabled = false; - channelIdPrivateKey = null; - return; - } - - sslParameters.channelIdEnabled = true; - try { - ECParameterSpec ecParams = null; - if (privateKey instanceof ECKey) { - ecParams = ((ECKey) privateKey).getParams(); - } - if (ecParams == null) { - // Assume this is a P-256 key, as specified in the contract of this method. - ecParams = - OpenSSLECGroupContext.getCurveByName("prime256v1").getECParameterSpec(); - } - channelIdPrivateKey = - OpenSSLKey.fromECPrivateKeyForTLSStackOnly(privateKey, ecParams); - } catch (InvalidKeyException e) { - // Will have error in startHandshake - } - } - } - /** * Sets the listener for the completion of the TLS handshake. */ @@ -436,7 +335,7 @@ private void beginHandshakeInternal() throws SSLException { boolean releaseResources = true; try { // Prepare the SSL object for the handshake. - ssl.initialize(getHostname(), channelIdPrivateKey); + ssl.initialize(getHostname()); // For clients, offer to resume a previously cached session to avoid the // full TLS handshake. @@ -1137,11 +1036,24 @@ private int readPlaintextDataHeap(ByteBuffer dst, int len) private SSLException convertException(Throwable e) { if (e instanceof SSLHandshakeException || !handshakeFinished) { + if (e instanceof EchRejectedException) { + byte[] retryConfigs = ssl.getEchRetryConfigs(); + ssl.getEchHandshakeMetricsBuilder().setRetryConfigs(retryConfigs); + return SSLUtils.toEchRejectedException(e, ssl.getEchNameOverride(), retryConfigs); + } return SSLUtils.toSSLHandshakeException(e); } return SSLUtils.toSSLException(e); } + TlsEncryptedClientHelloHandshake getEchHandshakeForMetrics( + boolean handshakeSuccess, int durationMillis) { + return ssl.getEchHandshakeMetricsBuilder() + .setHandshakeSuccess(handshakeSuccess) + .setHandshakeDurationMillis(durationMillis) + .build(); + } + /** * Write encrypted data to the OpenSSL network BIO. */ @@ -1753,6 +1665,11 @@ void setUseSessionTickets(boolean useSessionTickets) { sslParameters.setUseSessionTickets(useSessionTickets); } + @Override + void setEchConfigList(byte[] echConfigList) { + sslParameters.setEchConfigList(echConfigList); + } + @Override String[] getApplicationProtocols() { return sslParameters.getApplicationProtocols(); diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java index f6dcc713a..3eb64a2c9 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java @@ -27,6 +27,7 @@ import static javax.net.ssl.SSLEngineResult.Status.OK; import org.conscrypt.metrics.StatsLog; +import org.conscrypt.metrics.TlsEncryptedClientHelloHandshake; import java.io.EOFException; import java.io.IOException; @@ -312,10 +313,19 @@ private int transitionTo(int newState) { case STATE_READY_HANDSHAKE_CUT_THROUGH: if (handshakeStartedMillis > 0) { StatsLog statsLog = Platform.getStatsLog(); + long duration = + Platform.getMillisSinceBoot() - handshakeStartedMillis; statsLog.countTlsHandshake( true, engine.getSession().getProtocol(), - engine.getSession().getCipherSuite(), - Platform.getMillisSinceBoot() - handshakeStartedMillis); + engine.getSession().getCipherSuite(), duration); + if (getUseClientMode()) { + TlsEncryptedClientHelloHandshake echHandshake = + engine.getEchHandshakeForMetrics( + /* handshakeSuccess= */ true, (int) duration); + if (echHandshake.shouldReportEchHandshake()) { + statsLog.reportTlsEchHandshake(echHandshake); + } + } handshakeStartedMillis = 0; } notify = true; @@ -328,10 +338,19 @@ private int transitionTo(int newState) { case STATE_CLOSED: if (handshakeStartedMillis > 0) { StatsLog statsLog = Platform.getStatsLog(); + long duration = + Platform.getMillisSinceBoot() - handshakeStartedMillis; // Handshake was in progress and so must have failed. statsLog.countTlsHandshake( - false, "TLS_PROTO_FAILED", "TLS_CIPHER_FAILED", - Platform.getMillisSinceBoot() - handshakeStartedMillis); + false, "TLS_PROTO_FAILED", "TLS_CIPHER_FAILED", duration); + if (getUseClientMode()) { + TlsEncryptedClientHelloHandshake echHandshake = + engine.getEchHandshakeForMetrics( + /* handshakeSuccess= */ false, (int) duration); + if (echHandshake.shouldReportEchHandshake()) { + statsLog.reportTlsEchHandshake(echHandshake); + } + } handshakeStartedMillis = 0; } notify = true; @@ -469,19 +488,12 @@ public final void setUseSessionTickets(boolean useSessionTickets) { } @Override - public final void setChannelIdEnabled(boolean enabled) { - engine.setChannelIdEnabled(enabled); + public final void setEchConfigList(byte[] echConfigList) { + engine.setEchConfigList(echConfigList); } @Override - public final byte[] getChannelId() throws SSLException { - return engine.getChannelId(); - } - - @Override - public final void setChannelIdPrivateKey(PrivateKey privateKey) { - engine.setChannelIdPrivateKey(privateKey); - } + public final void setChannelIdPrivateKey(PrivateKey privateKey) {} @Override byte[] getTlsUnique() { @@ -920,6 +932,9 @@ private int processDataFromSocket(byte[] b, int off, int len) throws IOException switch (engineResult.getStatus()) { case BUFFER_UNDERFLOW: { + if (fromSocket.position() == fromSocket.capacity()) { + throw new SSLException("Encrypted packet exceeds buffer capacity"); + } if (engineResult.bytesProduced() == 0) { // Need to read more data from the socket. break; diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java deleted file mode 100644 index a2be687a2..000000000 --- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java +++ /dev/null @@ -1,1246 +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.SSLUtils.EngineStates.STATE_CLOSED; -import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_COMPLETED; -import static org.conscrypt.SSLUtils.EngineStates.STATE_HANDSHAKE_STARTED; -import static org.conscrypt.SSLUtils.EngineStates.STATE_NEW; -import static org.conscrypt.SSLUtils.EngineStates.STATE_READY; -import static org.conscrypt.SSLUtils.EngineStates.STATE_READY_HANDSHAKE_CUT_THROUGH; - -import org.conscrypt.ExternalSession.Provider; -import org.conscrypt.NativeRef.SSL_SESSION; -import org.conscrypt.metrics.StatsLog; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.SocketException; -import java.security.InvalidKeyException; -import java.security.PrivateKey; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.security.interfaces.ECKey; -import java.security.spec.ECParameterSpec; - -import javax.crypto.SecretKey; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLHandshakeException; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLProtocolException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.X509KeyManager; -import javax.net.ssl.X509TrustManager; -import javax.security.auth.x500.X500Principal; - -/** - * Implementation of the class OpenSSLSocketImpl based on OpenSSL. - *

- * Extensions to SSLSocket include: - *

    - *
  • handshake timeout - *
  • session tickets - *
  • Server Name Indication - *
- */ -class ConscryptFileDescriptorSocket extends OpenSSLSocketImpl - implements NativeCrypto.SSLHandshakeCallbacks, SSLParametersImpl.PSKCallbacks, - SSLParametersImpl.AliasChooser { - private static final boolean DBG_STATE = false; - - // @GuardedBy("ssl"); - private int state = STATE_NEW; - - /** - * Wrapper around the underlying SSL object. - */ - private final NativeSsl ssl; - - /** - * Protected by synchronizing on ssl. Starts as null, set by - * getInputStream. - */ - // @GuardedBy("ssl"); - private SSLInputStream is; - - /** - * Protected by synchronizing on ssl. Starts as null, set by - * getInputStream. - */ - // @GuardedBy("ssl"); - private SSLOutputStream os; - - private final SSLParametersImpl sslParameters; - - /* - * A CloseGuard object on Android. On other platforms, this is nothing. - */ - private final Object guard = Platform.closeGuardGet(); - - /** - * Private key for the TLS Channel ID extension. This field is client-side - * only. Set during startHandshake. - */ - private OpenSSLKey channelIdPrivateKey; - - private final ActiveSession activeSession; - /** - * A snapshot of the active session when the engine was closed. - */ - private SessionSnapshot closedSession; - /** - * The session object exposed externally from this class. - */ - private final SSLSession externalSession = - Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() { - @Override - public ConscryptSession provideSession() { - return ConscryptFileDescriptorSocket.this.provideSession(); - } - })); - - private int writeTimeoutMilliseconds = 0; - private int handshakeTimeoutMilliseconds = -1; // -1 = same as timeout; 0 = infinite - - private long handshakeStartedMillis = 0; - - // The constructors should not be called except from the Platform class, because we may - // want to construct a subclass instead. - ConscryptFileDescriptorSocket(SSLParametersImpl sslParameters) throws IOException { - this.sslParameters = sslParameters; - this.ssl = newSsl(sslParameters, this); - activeSession = new ActiveSession(ssl, sslParameters.getSessionContext()); - } - - ConscryptFileDescriptorSocket(String hostname, int port, SSLParametersImpl sslParameters) - throws IOException { - super(hostname, port); - this.sslParameters = sslParameters; - this.ssl = newSsl(sslParameters, this); - activeSession = new ActiveSession(ssl, sslParameters.getSessionContext()); - } - - ConscryptFileDescriptorSocket(InetAddress address, int port, SSLParametersImpl sslParameters) - throws IOException { - super(address, port); - this.sslParameters = sslParameters; - this.ssl = newSsl(sslParameters, this); - activeSession = new ActiveSession(ssl, sslParameters.getSessionContext()); - } - - ConscryptFileDescriptorSocket(String hostname, int port, InetAddress clientAddress, - int clientPort, SSLParametersImpl sslParameters) - throws IOException { - super(hostname, port, clientAddress, clientPort); - this.sslParameters = sslParameters; - this.ssl = newSsl(sslParameters, this); - activeSession = new ActiveSession(ssl, sslParameters.getSessionContext()); - } - - ConscryptFileDescriptorSocket(InetAddress address, int port, InetAddress clientAddress, - int clientPort, SSLParametersImpl sslParameters) - throws IOException { - super(address, port, clientAddress, clientPort); - this.sslParameters = sslParameters; - this.ssl = newSsl(sslParameters, this); - activeSession = new ActiveSession(ssl, sslParameters.getSessionContext()); - } - - ConscryptFileDescriptorSocket(Socket socket, String hostname, int port, boolean autoClose, - SSLParametersImpl sslParameters) throws IOException { - super(socket, hostname, port, autoClose); - this.sslParameters = sslParameters; - this.ssl = newSsl(sslParameters, this); - activeSession = new ActiveSession(ssl, sslParameters.getSessionContext()); - } - - private static NativeSsl newSsl(SSLParametersImpl sslParameters, - ConscryptFileDescriptorSocket engine) throws SSLException { - return NativeSsl.newInstance(sslParameters, engine, engine, engine); - } - - /** - * Starts a TLS/SSL handshake on this connection using some native methods - * from the OpenSSL library. It can negotiate new encryption keys, change - * cipher suites, or initiate a new session. The certificate chain is - * verified if the correspondent property in java.Security is set. All - * listeners are notified at the end of the TLS/SSL handshake. - */ - @Override - public final void startHandshake() throws IOException { - checkOpen(); - synchronized (ssl) { - if (state == STATE_NEW) { - transitionTo(STATE_HANDSHAKE_STARTED); - } else { - // We've either started the handshake already or have been closed. - // Do nothing in both cases. - return; - } - } - - boolean releaseResources = true; - try { - Platform.closeGuardOpen(guard, "close"); - - // Prepare the SSL object for the handshake. - ssl.initialize(getHostname(), channelIdPrivateKey); - - // For clients, offer to resume a previously cached session to avoid the - // full TLS handshake. - if (getUseClientMode()) { - NativeSslSession cachedSession = clientSessionContext().getCachedSession( - getHostnameOrIP(), getPort(), sslParameters); - if (cachedSession != null) { - cachedSession.offerToResume(ssl); - } - } - - // Temporarily use a different timeout for the handshake process - int savedReadTimeoutMilliseconds = getSoTimeout(); - int savedWriteTimeoutMilliseconds = getSoWriteTimeout(); - if (handshakeTimeoutMilliseconds >= 0) { - setSoTimeout(handshakeTimeoutMilliseconds); - setSoWriteTimeout(handshakeTimeoutMilliseconds); - } - - synchronized (ssl) { - if (state == STATE_CLOSED) { - return; - } - } - - try { - ssl.doHandshake(Platform.getFileDescriptor(socket), getSoTimeout()); - - // Update the session from the current state of the SSL object. - activeSession.onPeerCertificateAvailable(getHostnameOrIP(), getPort()); - } catch (CertificateException e) { - SSLHandshakeException wrapper = new SSLHandshakeException(e.getMessage()); - wrapper.initCause(e); - throw wrapper; - } catch (SSLException e) { - // Swallow this exception if it's thrown as the result of an interruption. - // - // TODO: SSL_read and SSL_write return -1 when interrupted, but SSL_do_handshake - // will throw the last sslError that it saw before sslSelect, usually SSL_WANT_READ - // (or WANT_WRITE). Catching that exception here doesn't seem much worse than - // changing the native code to return a "special" native pointer value when that - // happens. - synchronized (ssl) { - if (state == STATE_CLOSED) { - return; - } - } - throw e; - } - - synchronized (ssl) { - if (state == STATE_CLOSED) { - return; - } - } - - // Restore the original timeout now that the handshake is complete - if (handshakeTimeoutMilliseconds >= 0) { - setSoTimeout(savedReadTimeoutMilliseconds); - setSoWriteTimeout(savedWriteTimeoutMilliseconds); - } - - synchronized (ssl) { - releaseResources = (state == STATE_CLOSED); - - if (state == STATE_HANDSHAKE_STARTED) { - transitionTo(STATE_READY_HANDSHAKE_CUT_THROUGH); - } else { - transitionTo(STATE_READY); - } - - if (!releaseResources) { - // Unblock threads that are waiting for our state to transition - // into STATE_READY or STATE_READY_HANDSHAKE_CUT_THROUGH. - ssl.notifyAll(); - } - } - } catch (SSLProtocolException e) { - throw(SSLHandshakeException) new SSLHandshakeException("Handshake failed").initCause(e); - } finally { - // on exceptional exit, treat the socket as closed - if (releaseResources) { - synchronized (ssl) { - // Mark the socket as closed since we might have reached this as - // a result on an exception thrown by the handshake process. - // - // The state will already be set to closed if we reach this as a result of - // an early return or an interruption due to a concurrent call to close(). - transitionTo(STATE_CLOSED); - ssl.notifyAll(); - } - - try { - shutdownAndFreeSslNative(); - } catch (IOException ignored) { - // Ignored. - } - } - } - } - - @Override - @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / client_cert_cb - public final void clientCertificateRequested(byte[] keyTypeBytes, int[] signatureAlgs, - byte[][] asn1DerEncodedPrincipals) - throws CertificateEncodingException, SSLException { - String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); - activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); - ssl.chooseClientCertificate(keyTypeBytes, signatureAlgs, asn1DerEncodedPrincipals); - } - - @Override - @SuppressWarnings("unused") // used by native psk_client_callback - public final int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key) { - return ssl.clientPSKKeyRequested(identityHint, identity, key); - } - - @Override - @SuppressWarnings("unused") // used by native psk_server_callback - public final int serverPSKKeyRequested(String identityHint, String identity, byte[] key) { - return ssl.serverPSKKeyRequested(identityHint, identity, key); - } - - @Override - @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / info_callback - public final void onSSLStateChange(int type, int val) { - if (type != NativeConstants.SSL_CB_HANDSHAKE_DONE) { - // We only care about successful completion. - return; - } - - // First, update the state. - synchronized (ssl) { - if (state == STATE_CLOSED) { - // Someone called "close" but the handshake hasn't been interrupted yet. - return; - } - - // Now that we've fixed up our state, we can tell waiting threads that - // we're ready. - transitionTo(STATE_READY); - } - - // Let listeners know we are finally done - notifyHandshakeCompletedListeners(); - - synchronized (ssl) { - // Notify all threads waiting for the handshake to complete. - ssl.notifyAll(); - } - } - - @Override - @SuppressWarnings("unused") // used by NativeCrypto.SSLHandshakeCallbacks / new_session_callback - public final void onNewSessionEstablished(long sslSessionNativePtr) { - try { - // Increment the reference count to "take ownership" of the session resource. - NativeCrypto.SSL_SESSION_up_ref(sslSessionNativePtr); - - // Create a native reference which will release the SSL_SESSION in its finalizer. - // This constructor will only throw if the native pointer passed in is NULL, which - // BoringSSL guarantees will not happen. - NativeRef.SSL_SESSION ref = new SSL_SESSION(sslSessionNativePtr); - - NativeSslSession nativeSession = NativeSslSession.newInstance(ref, activeSession); - - // Cache the newly established session. - AbstractSessionContext ctx = sessionContext(); - ctx.cacheSession(nativeSession); - } catch (Exception ignored) { - // Ignore. - } - } - - @Override - public final long serverSessionRequested(byte[] id) { - // TODO(nathanmittler): Implement server-side caching for TLS < 1.3 - return 0; - } - - @Override - public final void serverCertificateRequested(int[] signatureAlgs) throws IOException { - synchronized (ssl) { - String[] jsseAlgs = SSLUtils.mapSignatureAlgorithms(signatureAlgs); - activeSession.onPeerSignatureAlgorithmsReceived(jsseAlgs); - ssl.configureServerCertificate(); - } - } - - @Override - public final void verifyCertificateChain(byte[][] certChain, String authMethod) - throws CertificateException { - try { - if (certChain == null || certChain.length == 0) { - throw new CertificateException("Peer sent no certificate"); - } - X509Certificate[] peerCertChain = SSLUtils.decodeX509CertificateChain(certChain); - - X509TrustManager x509tm = sslParameters.getX509TrustManager(); - if (x509tm == null) { - throw new CertificateException("No X.509 TrustManager"); - } - // Update the peer information on the session. - activeSession.onPeerCertificatesReceived(getHostnameOrIP(), getPort(), peerCertChain); - - if (getUseClientMode()) { - Platform.checkServerTrusted(x509tm, peerCertChain, authMethod, this); - } else { - String authType = peerCertChain[0].getPublicKey().getAlgorithm(); - Platform.checkClientTrusted(x509tm, peerCertChain, authType, this); - } - } catch (CertificateException e) { - throw e; - } catch (Exception e) { - throw new CertificateException(e); - } - } - - @Override - public final InputStream getInputStream() throws IOException { - checkOpen(); - - InputStream returnVal; - synchronized (ssl) { - if (state == STATE_CLOSED) { - throw new SocketException("Socket is closed."); - } - - if (is == null) { - is = new SSLInputStream(); - } - - returnVal = is; - } - - // Block waiting for a handshake without a lock held. It's possible that the socket - // is closed at this point. If that happens, we'll still return the input stream but - // all reads on it will throw. - waitForHandshake(); - return returnVal; - } - - @Override - public final OutputStream getOutputStream() throws IOException { - checkOpen(); - - OutputStream returnVal; - synchronized (ssl) { - if (state == STATE_CLOSED) { - throw new SocketException("Socket is closed."); - } - - if (os == null) { - os = new SSLOutputStream(); - } - - returnVal = os; - } - - // Block waiting for a handshake without a lock held. It's possible that the socket - // is closed at this point. If that happens, we'll still return the output stream but - // all writes on it will throw. - waitForHandshake(); - return returnVal; - } - - private void assertReadableOrWriteableState() { - if (state == STATE_READY || state == STATE_READY_HANDSHAKE_CUT_THROUGH) { - return; - } - - throw new AssertionError("Invalid state: " + state); - } - - private void waitForHandshake() throws IOException { - startHandshake(); - - synchronized (ssl) { - while (state != STATE_READY && state != STATE_READY_HANDSHAKE_CUT_THROUGH - && state != STATE_CLOSED) { - try { - ssl.wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IOException("Interrupted waiting for handshake", e); - } - } - - if (state == STATE_CLOSED) { - throw new SocketException("Socket is closed"); - } - } - } - - /** - * This inner class provides input data stream functionality - * for the OpenSSL native implementation. It is used to - * read data received via SSL protocol. - */ - private class SSLInputStream extends InputStream { - /** - * OpenSSL only lets one thread read at a time, so this is used to - * make sure we serialize callers of SSL_read. Thread is already - * expected to have completed handshaking. - */ - private final Object readLock = new Object(); - - SSLInputStream() {} - - /** - * Reads one byte. If there is no data in the underlying buffer, - * this operation can block until the data will be - * available. - */ - @Override - public int read() throws IOException { - byte[] buffer = new byte[1]; - int result = read(buffer, 0, 1); - return (result != -1) ? buffer[0] & 0xff : -1; - } - - /** - * Method acts as described in spec for superclass. - * @see java.io.InputStream#read(byte[],int,int) - */ - @Override - public int read(byte[] buf, int offset, int byteCount) throws IOException { - Platform.blockGuardOnNetwork(); - - checkOpen(); - ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount); - if (byteCount == 0) { - return 0; - } - - synchronized (readLock) { - synchronized (ssl) { - if (state == STATE_CLOSED) { - throw new SocketException("socket is closed"); - } - - if (DBG_STATE) { - assertReadableOrWriteableState(); - } - } - - int ret = ssl.read(Platform.getFileDescriptor(socket), buf, offset, byteCount, - getSoTimeout()); - if (ret == -1) { - synchronized (ssl) { - if (state == STATE_CLOSED) { - throw new SocketException("socket is closed"); - } - } - } - return ret; - } - } - - @Override - public int available() { - return ssl.getPendingReadableBytes(); - } - - void awaitPendingOps() { - if (DBG_STATE) { - synchronized (ssl) { - if (state != STATE_CLOSED) { - throw new AssertionError("State is: " + state); - } - } - } - - synchronized (readLock) {} - } - } - - /** - * This inner class provides output data stream functionality - * for the OpenSSL native implementation. It is used to - * write data according to the encryption parameters given in SSL context. - */ - private class SSLOutputStream extends OutputStream { - /** - * OpenSSL only lets one thread write at a time, so this is used - * to make sure we serialize callers of SSL_write. Thread is - * already expected to have completed handshaking. - */ - private final Object writeLock = new Object(); - - SSLOutputStream() {} - - /** - * Method acts as described in spec for superclass. - * @see java.io.OutputStream#write(int) - */ - @Override - public void write(int oneByte) throws IOException { - byte[] buffer = new byte[1]; - buffer[0] = (byte) (oneByte & 0xff); - write(buffer); - } - - /** - * Method acts as described in spec for superclass. - * @see java.io.OutputStream#write(byte[],int,int) - */ - @Override - public void write(byte[] buf, int offset, int byteCount) throws IOException { - Platform.blockGuardOnNetwork(); - checkOpen(); - ArrayUtils.checkOffsetAndCount(buf.length, offset, byteCount); - if (byteCount == 0) { - return; - } - - synchronized (writeLock) { - synchronized (ssl) { - if (state == STATE_CLOSED) { - throw new SocketException("socket is closed"); - } - - if (DBG_STATE) { - assertReadableOrWriteableState(); - } - } - - ssl.write(Platform.getFileDescriptor(socket), buf, offset, byteCount, - writeTimeoutMilliseconds); - - synchronized (ssl) { - if (state == STATE_CLOSED) { - throw new SocketException("socket is closed"); - } - } - } - } - - void awaitPendingOps() { - if (DBG_STATE) { - synchronized (ssl) { - if (state != STATE_CLOSED) { - throw new AssertionError("State is: " + state); - } - } - } - - synchronized (writeLock) {} - } - } - - @Override - public final SSLSession getSession() { - return externalSession; - } - - private ConscryptSession provideSession() { - boolean handshakeCompleted = false; - synchronized (ssl) { - if (state == STATE_CLOSED) { - return closedSession != null ? closedSession : SSLNullSession.getNullSession(); - } - - try { - handshakeCompleted = state >= STATE_READY; - if (!handshakeCompleted && isConnected()) { - waitForHandshake(); - handshakeCompleted = true; - } - } catch (IOException e) { - // Fall through. - } - } - - if (!handshakeCompleted) { - // return an invalid session with - // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL" - return SSLNullSession.getNullSession(); - } - - return activeSession; - } - - // After handshake has started, provide active session otherwise a null session, - // for code which needs to read session attributes without triggering the handshake. - private ConscryptSession provideAfterHandshakeSession() { - return (state < STATE_HANDSHAKE_STARTED) ? SSLNullSession.getNullSession() - : provideSession(); - } - - // If handshake is in progress, provide active session otherwise a null session. - private ConscryptSession provideHandshakeSession() { - synchronized (ssl) { - return state >= STATE_HANDSHAKE_STARTED && state < STATE_READY - ? activeSession - : SSLNullSession.getNullSession(); - } - } - - @Override - final SSLSession getActiveSession() { - return activeSession; - } - - @Override - public final SSLSession getHandshakeSession() { - synchronized (ssl) { - if (state >= STATE_HANDSHAKE_STARTED && state < STATE_READY) { - return Platform.wrapSSLSession(new ExternalSession(new ExternalSession.Provider() { - @Override - public ConscryptSession provideSession() { - return ConscryptFileDescriptorSocket.this.provideHandshakeSession(); - } - })); - } - return null; - } - } - - @Override - public final boolean getEnableSessionCreation() { - return sslParameters.getEnableSessionCreation(); - } - - @Override - public final void setEnableSessionCreation(boolean flag) { - sslParameters.setEnableSessionCreation(flag); - } - - @Override - public final String[] getSupportedCipherSuites() { - return NativeCrypto.getSupportedCipherSuites(); - } - - @Override - public final String[] getEnabledCipherSuites() { - return sslParameters.getEnabledCipherSuites(); - } - - @Override - public final void setEnabledCipherSuites(String[] suites) { - sslParameters.setEnabledCipherSuites(suites); - } - - @Override - public final String[] getSupportedProtocols() { - return NativeCrypto.getSupportedProtocols(); - } - - @Override - public final String[] getEnabledProtocols() { - return sslParameters.getEnabledProtocols(); - } - - @Override - public final void setEnabledProtocols(String[] protocols) { - sslParameters.setEnabledProtocols(protocols); - } - - @Override - public final void setNamedGroups(String[] namedGroups) { - sslParameters.setNamedGroups(namedGroups); - } - - @Override - public final String getCurveNameForTesting() { - return ssl.getCurveNameForTesting(); - } - - /** - * This method enables session ticket support. - * - * @param useSessionTickets True to enable session tickets - */ - @Override - public final void setUseSessionTickets(boolean useSessionTickets) { - sslParameters.setUseSessionTickets(useSessionTickets); - } - - /** - * This method enables Server Name Indication. If the hostname is not a valid SNI hostname, - * the SNI extension will be omitted from the handshake. - * - * @param hostname the desired SNI hostname, or null to disable - */ - @Override - public final void setHostname(String hostname) { - sslParameters.setUseSni(hostname != null); - super.setHostname(hostname); - } - - /** - * Enables/disables TLS Channel ID for this server socket. - * - *

This method needs to be invoked before the handshake starts. - * - * @throws IllegalStateException if this is a client socket or if the handshake has already - * started. - */ - @Override - public final void setChannelIdEnabled(boolean enabled) { - if (getUseClientMode()) { - throw new IllegalStateException("Client mode"); - } - - synchronized (ssl) { - if (state != STATE_NEW) { - throw new IllegalStateException( - "Could not enable/disable Channel ID after the initial handshake has" - + " begun."); - } - } - sslParameters.channelIdEnabled = enabled; - } - - /** - * Gets the TLS Channel ID for this server socket. Channel ID is only available once the - * handshake completes. - * - * @return channel ID or {@code null} if not available. - * - * @throws IllegalStateException if this is a client socket or if the handshake has not yet - * completed. - * @throws SSLException if channel ID is available but could not be obtained. - */ - @Override - public final byte[] getChannelId() throws SSLException { - if (getUseClientMode()) { - throw new IllegalStateException("Client mode"); - } - - synchronized (ssl) { - if (state != STATE_READY) { - throw new IllegalStateException( - "Channel ID is only available after handshake completes"); - } - } - return ssl.getTlsChannelId(); - } - - /** - * Sets the {@link PrivateKey} to be used for TLS Channel ID by this client socket. - * - *

This method needs to be invoked before the handshake starts. - * - * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables - * TLS Channel ID). The private key must be an Elliptic Curve (EC) key based on the NIST - * P-256 curve (aka SECG secp256r1 or ANSI X9.62 prime256v1). - * - * @throws IllegalStateException if this is a server socket or if the handshake has already - * started. - */ - @Override - public final void setChannelIdPrivateKey(PrivateKey privateKey) { - if (!getUseClientMode()) { - throw new IllegalStateException("Server mode"); - } - - synchronized (ssl) { - if (state != STATE_NEW) { - throw new IllegalStateException( - "Could not change Channel ID private key after the initial handshake has" - + " begun."); - } - } - - if (privateKey == null) { - sslParameters.channelIdEnabled = false; - channelIdPrivateKey = null; - } else { - sslParameters.channelIdEnabled = true; - try { - ECParameterSpec ecParams = null; - if (privateKey instanceof ECKey) { - ecParams = ((ECKey) privateKey).getParams(); - } - if (ecParams == null) { - // Assume this is a P-256 key, as specified in the contract of this method. - ecParams = - OpenSSLECGroupContext.getCurveByName("prime256v1").getECParameterSpec(); - } - channelIdPrivateKey = - OpenSSLKey.fromECPrivateKeyForTLSStackOnly(privateKey, ecParams); - } catch (InvalidKeyException e) { - // Will have error in startHandshake - } - } - } - - @Override - byte[] getTlsUnique() { - return ssl.getTlsUnique(); - } - - @Override - byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSLException { - synchronized (ssl) { - if (state < STATE_HANDSHAKE_COMPLETED || state == STATE_CLOSED) { - return null; - } - } - return ssl.exportKeyingMaterial(label, context, length); - } - - @Override - public final boolean getUseClientMode() { - return sslParameters.getUseClientMode(); - } - - @Override - public final void setUseClientMode(boolean mode) { - synchronized (ssl) { - if (state != STATE_NEW) { - throw new IllegalArgumentException( - "Could not change the mode after the initial handshake has begun."); - } - } - sslParameters.setUseClientMode(mode); - } - - @Override - public final boolean getWantClientAuth() { - return sslParameters.getWantClientAuth(); - } - - @Override - public final boolean getNeedClientAuth() { - return sslParameters.getNeedClientAuth(); - } - - @Override - public final void setNeedClientAuth(boolean need) { - sslParameters.setNeedClientAuth(need); - } - - @Override - public final void setWantClientAuth(boolean want) { - sslParameters.setWantClientAuth(want); - } - - /** - * Note write timeouts are not part of the javax.net.ssl.SSLSocket API - */ - @Override - public final void setSoWriteTimeout(int writeTimeoutMilliseconds) throws SocketException { - this.writeTimeoutMilliseconds = writeTimeoutMilliseconds; - - Platform.setSocketWriteTimeout(this, writeTimeoutMilliseconds); - } - - /** - * Note write timeouts are not part of the javax.net.ssl.SSLSocket API - */ - @Override - public final int getSoWriteTimeout() { - return writeTimeoutMilliseconds; - } - - /** - * Set the handshake timeout on this socket. This timeout is specified in - * milliseconds and will be used only during the handshake process. - */ - @Override - public final void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketException { - this.handshakeTimeoutMilliseconds = handshakeTimeoutMilliseconds; - } - - @Override - @SuppressWarnings("UnsynchronizedOverridesSynchronized") - public final void close() throws IOException { - // TODO: Close SSL sockets using a background thread so they close gracefully. - - SSLInputStream sslInputStream; - SSLOutputStream sslOutputStream; - - if (ssl == null) { - // close() has been called before we've initialized the socket, so just - // return. - return; - } - - synchronized (ssl) { - if (state == STATE_CLOSED) { - // close() has already been called, so do nothing and return. - return; - } - - int oldState = state; - transitionTo(STATE_CLOSED); - - if (oldState == STATE_NEW) { - // The handshake hasn't been started yet, so there's no OpenSSL related - // state to clean up. We still need to close the underlying socket if - // we're wrapping it and were asked to autoClose. - free(); - closeUnderlyingSocket(); - - ssl.notifyAll(); - return; - } - - if (oldState != STATE_READY && oldState != STATE_READY_HANDSHAKE_CUT_THROUGH) { - // If we're in these states, we still haven't returned from startHandshake. - // We call SSL_interrupt so that we can interrupt SSL_do_handshake and then - // set the state to STATE_CLOSED. startHandshake will handle all cleanup - // after SSL_do_handshake returns, so we don't have anything to do here. - ssl.interrupt(); - - ssl.notifyAll(); - return; - } - - ssl.notifyAll(); - // We've already returned from startHandshake, so we potentially have - // input and output streams to clean up. - sslInputStream = is; - sslOutputStream = os; - } - - // Don't bother interrupting unless we have something to interrupt. - if (sslInputStream != null || sslOutputStream != null) { - ssl.interrupt(); - } - - // Wait for the input and output streams to finish any reads they have in - // progress. If there are no reads in progress at this point, future reads will - // throw because state == STATE_CLOSED - if (sslInputStream != null) { - sslInputStream.awaitPendingOps(); - } - if (sslOutputStream != null) { - sslOutputStream.awaitPendingOps(); - } - - shutdownAndFreeSslNative(); - } - - private void shutdownAndFreeSslNative() throws IOException { - try { - Platform.blockGuardOnNetwork(); - ssl.shutdown(Platform.getFileDescriptor(socket)); - } catch (IOException ignored) { - /* - * Note that although close() can throw - * IOException, the RI does not throw if there - * is problem sending a "close notify" which - * can happen if the underlying socket is closed. - */ - } finally { - free(); - closeUnderlyingSocket(); - } - } - - private void closeUnderlyingSocket() throws IOException { - super.close(); - } - - private void free() { - if (!ssl.isClosed()) { - ssl.close(); - Platform.closeGuardClose(guard); - } - } - - @Override - @SuppressWarnings("Finalize") - protected final void finalize() throws Throwable { - try { - /* - * Just worry about our own state. Notably we do not try and - * close anything. The SocketImpl, either our own - * PlainSocketImpl, or the Socket we are wrapping, will do - * that. This might mean we do not properly SSL_shutdown, but - * if you want to do that, properly close the socket yourself. - * - * The reason why we don't try to SSL_shutdown, is that there - * can be a race between finalizers where the PlainSocketImpl - * finalizer runs first and closes the socket. However, in the - * meanwhile, the underlying file descriptor could be reused - * for another purpose. If we call SSL_shutdown, the - * underlying socket BIOs still have the old file descriptor - * and will write the close notify to some unsuspecting - * reader. - */ - if (guard != null) { - Platform.closeGuardWarnIfOpen(guard); - } - if (ssl != null) { - synchronized (ssl) { - transitionTo(STATE_CLOSED); - } - } - } finally { - super.finalize(); - } - } - - @Override - public final void setApplicationProtocolSelector(ApplicationProtocolSelector selector) { - setApplicationProtocolSelector( - selector == null ? null : new ApplicationProtocolSelectorAdapter(this, selector)); - } - - @Override - final void setApplicationProtocolSelector(ApplicationProtocolSelectorAdapter selector) { - sslParameters.setApplicationProtocolSelector(selector); - } - - @Override - public int selectApplicationProtocol(byte[] protocols) { - ApplicationProtocolSelectorAdapter adapter = sslParameters.getApplicationProtocolSelector(); - if (adapter == null) { - return NativeConstants.SSL_TLSEXT_ERR_NOACK; - } - return adapter.selectApplicationProtocol(protocols); - } - - @Override - final void setApplicationProtocols(String[] protocols) { - sslParameters.setApplicationProtocols(protocols); - } - - @Override - final String[] getApplicationProtocols() { - return sslParameters.getApplicationProtocols(); - } - - @Override - public final String getApplicationProtocol() { - return provideAfterHandshakeSession().getApplicationProtocol(); - } - - @Override - public final String getHandshakeApplicationProtocol() { - synchronized (ssl) { - return state >= STATE_HANDSHAKE_STARTED && state < STATE_READY - ? getApplicationProtocol() - : null; - } - } - - @Override - public final SSLParameters getSSLParameters() { - SSLParameters params = super.getSSLParameters(); - Platform.getSSLParameters(params, sslParameters, this); - return params; - } - - @Override - public final void setSSLParameters(SSLParameters p) { - super.setSSLParameters(p); - Platform.setSSLParameters(p, sslParameters, this); - } - - @Override - @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package - public final String chooseServerPSKIdentityHint(PSKKeyManager keyManager) { - return keyManager.chooseServerKeyIdentityHint(this); - } - - @Override - @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package - public final String chooseClientPSKIdentity(PSKKeyManager keyManager, String identityHint) { - return keyManager.chooseClientKeyIdentity(identityHint, this); - } - - @Override - @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package - public final SecretKey getPSKKey(PSKKeyManager keyManager, String identityHint, - String identity) { - return keyManager.getKey(identityHint, identity, this); - } - - @Override - public final String chooseServerAlias(X509KeyManager keyManager, String keyType) { - return keyManager.chooseServerAlias(keyType, null, this); - } - - @Override - public final String chooseClientAlias(X509KeyManager keyManager, X500Principal[] issuers, - String[] keyTypes) { - return keyManager.chooseClientAlias(keyTypes, issuers, this); - } - - private ClientSessionContext clientSessionContext() { - return sslParameters.getClientSessionContext(); - } - - private AbstractSessionContext sessionContext() { - return sslParameters.getSessionContext(); - } - - // All calls synchronized on this.ssl. - private void transitionTo(int newState) { - if (state == newState) { - return; - } - - switch (newState) { - case STATE_HANDSHAKE_STARTED: - handshakeStartedMillis = Platform.getMillisSinceBoot(); - break; - - case STATE_READY: - if (handshakeStartedMillis != 0) { - StatsLog statsLog = Platform.getStatsLog(); - if (statsLog != null) { - statsLog.countTlsHandshake( - true, activeSession.getProtocol(), activeSession.getCipherSuite(), - Platform.getMillisSinceBoot() - handshakeStartedMillis); - } - handshakeStartedMillis = 0; - } - break; - - case STATE_CLOSED: { - if (handshakeStartedMillis != 0) { - // Handshake was in progress so must have failed. - StatsLog statsLog = Platform.getStatsLog(); - if (statsLog != null) { - statsLog.countTlsHandshake( - false, "TLS_PROTO_FAILED", "TLS_CIPHER_FAILED", - Platform.getMillisSinceBoot() - handshakeStartedMillis); - } - handshakeStartedMillis = 0; - } - if (!ssl.isClosed() && state >= STATE_HANDSHAKE_STARTED && state < STATE_CLOSED) { - closedSession = new SessionSnapshot(activeSession); - } - break; - } - default: { - break; - } - } - - // Update the state - this.state = newState; - } -} diff --git a/common/src/main/java/org/conscrypt/ConscryptServerSocket.java b/common/src/main/java/org/conscrypt/ConscryptServerSocket.java index 7dcee7be1..239347dc8 100644 --- a/common/src/main/java/org/conscrypt/ConscryptServerSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptServerSocket.java @@ -28,8 +28,6 @@ */ final class ConscryptServerSocket extends SSLServerSocket { private final SSLParametersImpl sslParameters; - private boolean channelIdEnabled; - private boolean useEngineSocket; ConscryptServerSocket(SSLParametersImpl sslParameters) throws IOException { this.sslParameters = sslParameters; @@ -56,7 +54,7 @@ final class ConscryptServerSocket extends SSLServerSocket { * Configures the socket to be created for this instance. */ ConscryptServerSocket setUseEngineSocket(boolean useEngineSocket) { - this.useEngineSocket = useEngineSocket; + // No-op. Engine sockets are always used. return this; } @@ -127,20 +125,6 @@ public String[] getEnabledCipherSuites() { return sslParameters.getEnabledCipherSuites(); } - /** - * Enables/disables the TLS Channel ID extension for this server socket. - */ - void setChannelIdEnabled(boolean enabled) { - channelIdEnabled = enabled; - } - - /** - * Checks whether the TLS Channel ID extension is enabled for this server socket. - */ - boolean isChannelIdEnabled() { - return channelIdEnabled; - } - /** * This method enables the cipher suites listed by * getSupportedCipherSuites(). @@ -186,14 +170,7 @@ public boolean getUseClientMode() { @Override public Socket accept() throws IOException { - final AbstractConscryptSocket socket; - if (useEngineSocket) { - socket = Platform.createEngineSocket(sslParameters); - } else { - socket = Platform.createFileDescriptorSocket(sslParameters); - } - - socket.setChannelIdEnabled(channelIdEnabled); + final AbstractConscryptSocket socket = Platform.createEngineSocket(sslParameters); implAccept(socket); return socket; } diff --git a/common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java b/common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java new file mode 100644 index 000000000..7ee75e0e4 --- /dev/null +++ b/common/src/main/java/org/conscrypt/ConscryptX509TrustManager.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2025 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 org.conscrypt.ConscryptNetworkSecurityPolicy; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * Interface for TrustManager methods implemented in Conscrypt but not part of + * the standard X509TrustManager or X509ExtendedTrustManager. + * + * These methods can be called by the Android framework. Extend + * X509TrustManagerExtensions if these need to be visible to apps. + */ +@Internal +public interface ConscryptX509TrustManager { + /** + * Verifies the given certificate chain. + * + *

See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a + * description of the chain and authType parameters. The final parameter, host, should be the + * hostname of the server.

+ * + * @throws CertificateException if the chain does not verify correctly. + * @return the properly ordered chain used for verification as a list of X509Certificates. + */ + public List checkServerTrusted(X509Certificate[] chain, String authType, + String hostname) throws CertificateException; + + /** + * Verifies the given certificate chain. + * + *

See {@link X509TrustManager#checkServerTrusted(X509Certificate[], String)} for a + * description of the chain and authType parameters. The final parameter, host, should be the + * hostname of the server. + * + *

ocspData and tlsSctData may be provided to verify any Signed Certificate Timestamp (SCT) + * attached to the connection. These are ASN.1 octet strings (SignedCertificateTimestampList) as + * described in RFC 6962, Section 3.3. Note that SCTs embedded in the certificate chain will + * automatically be processed. + * + * @throws CertificateException if the chain does not verify correctly. + * @return the properly ordered chain used for verification as a list of X509Certificates. + */ + public List checkServerTrusted(X509Certificate[] chain, byte[] ocspData, + byte[] tlsSctData, String authType, + String hostname) throws CertificateException; + + /** + * Retrieve the ConscryptNetworkSecurityPolicy attached to this TrustManager. + */ + default public ConscryptNetworkSecurityPolicy getNetworkSecurityPolicy() { + return null; + } + + /** + * Attach a ConscryptNetworkSecurityPolicy to this TrustManager. + */ + default public void setNetworkSecurityPolicy(ConscryptNetworkSecurityPolicy policy) {} +} diff --git a/common/src/main/java/org/conscrypt/DefaultSSLContextImpl.java b/common/src/main/java/org/conscrypt/DefaultSSLContextImpl.java index 733735bd6..2bdaea6c9 100644 --- a/common/src/main/java/org/conscrypt/DefaultSSLContextImpl.java +++ b/common/src/main/java/org/conscrypt/DefaultSSLContextImpl.java @@ -24,7 +24,10 @@ import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.KeyStore; +import java.security.Provider; import java.security.SecureRandom; +import java.security.Security; +import java.util.Arrays; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -48,6 +51,9 @@ public class DefaultSSLContextImpl extends OpenSSLContextImpl { */ private static TrustManager[] TRUST_MANAGERS; + // Cache the list of providers to detect changes + private static Provider[] LAST_PROVIDERS; + /** * DefaultSSLContextImpl delegates the work to the super class since there * is no way to put a synchronized around both the call to super and the @@ -58,8 +64,19 @@ private DefaultSSLContextImpl(String[] protocols) throws GeneralSecurityExceptio super(protocols, true); } + private static synchronized void clearCacheIfProvidersChanged() { + Provider[] currentProviders = Security.getProviders(); + if (!Arrays.equals(LAST_PROVIDERS, currentProviders)) { + KEY_MANAGERS = null; + TRUST_MANAGERS = null; + LAST_PROVIDERS = currentProviders; + } + } + // TODO javax.net.ssl.keyStoreProvider system property KeyManager[] getKeyManagers() throws GeneralSecurityException, IOException { + clearCacheIfProvidersChanged(); + if (KEY_MANAGERS != null) { return KEY_MANAGERS; } @@ -85,6 +102,8 @@ KeyManager[] getKeyManagers() throws GeneralSecurityException, IOException { // TODO javax.net.ssl.trustStoreProvider system property TrustManager[] getTrustManagers() throws GeneralSecurityException, IOException { + clearCacheIfProvidersChanged(); + if (TRUST_MANAGERS != null) { return TRUST_MANAGERS; } @@ -126,4 +145,4 @@ public TLSv12() throws GeneralSecurityException, IOException { super(NativeCrypto.TLSV12_PROTOCOLS); } } -} +} \ No newline at end of file diff --git a/common/src/main/java/org/conscrypt/DomainEncryptionMode.java b/common/src/main/java/org/conscrypt/DomainEncryptionMode.java new file mode 100644 index 000000000..2f1386e82 --- /dev/null +++ b/common/src/main/java/org/conscrypt/DomainEncryptionMode.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2025 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; + +@Internal public enum DomainEncryptionMode { UNKNOWN, DISABLED, OPPORTUNISTIC, ENABLED, REQUIRED } diff --git a/common/src/main/java/org/conscrypt/EchOptions.java b/common/src/main/java/org/conscrypt/EchOptions.java new file mode 100644 index 000000000..6daca14f8 --- /dev/null +++ b/common/src/main/java/org/conscrypt/EchOptions.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2025 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; + +@Internal +public class EchOptions { + private final byte[] configList; + private final boolean enableGrease; + + EchOptions(byte[] configList, boolean enableGrease) { + this.configList = configList; + this.enableGrease = enableGrease; + } + + public byte[] getConfigList() { + return configList; + } + + public boolean isGreaseEnabled() { + return enableGrease; + } +} diff --git a/common/src/main/java/org/conscrypt/EchRejectedException.java b/common/src/main/java/org/conscrypt/EchRejectedException.java new file mode 100644 index 000000000..0b87850a1 --- /dev/null +++ b/common/src/main/java/org/conscrypt/EchRejectedException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import javax.net.ssl.SSLHandshakeException; + +/** + * Exception returned by the native code when ECH has been rejected. + */ +@Internal +public class EchRejectedException extends SSLHandshakeException { + public EchRejectedException(String message) { + super(message); + } +} diff --git a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java index 62f89d264..2b5eef18b 100644 --- a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java +++ b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java @@ -77,21 +77,6 @@ int maxSealOverhead() { return delegate.maxSealOverhead(); } - @Override - void setChannelIdEnabled(boolean enabled) { - delegate.setChannelIdEnabled(enabled); - } - - @Override - byte[] getChannelId() throws SSLException { - return delegate.getChannelId(); - } - - @Override - void setChannelIdPrivateKey(PrivateKey privateKey) { - delegate.setChannelIdPrivateKey(privateKey); - } - @Override void setHandshakeListener(HandshakeListener handshakeListener) { delegate.setHandshakeListener(handshakeListener); @@ -275,6 +260,11 @@ void setUseSessionTickets(boolean useSessionTickets) { delegate.setUseSessionTickets(useSessionTickets); } + @Override + void setEchConfigList(byte[] echConfigList) { + delegate.setEchConfigList(echConfigList); + } + @Override void setApplicationProtocols(String[] protocols) { delegate.setApplicationProtocols(protocols); diff --git a/common/src/main/java/org/conscrypt/Java8FileDescriptorSocket.java b/common/src/main/java/org/conscrypt/Java8FileDescriptorSocket.java deleted file mode 100644 index f69338fa2..000000000 --- a/common/src/main/java/org/conscrypt/Java8FileDescriptorSocket.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 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 java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.util.List; -import java.util.function.BiFunction; - -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLSocket; - -/** - * A version of ConscryptFileDescriptorSocket that includes the new Java 9 (and potentially later - * patches of 8) {@code setHandshakeApplicationProtocolSelector} API (which requires Java 8 for - * compilation, due to the use of {@link BiFunction}). - */ -final class Java8FileDescriptorSocket extends ConscryptFileDescriptorSocket { - private BiFunction, String> selector; - - Java8FileDescriptorSocket(SSLParametersImpl sslParameters) throws IOException { - super(sslParameters); - } - - Java8FileDescriptorSocket(String hostname, int port, SSLParametersImpl sslParameters) - throws IOException { - super(hostname, port, sslParameters); - } - - Java8FileDescriptorSocket(InetAddress address, int port, SSLParametersImpl sslParameters) - throws IOException { - super(address, port, sslParameters); - } - - Java8FileDescriptorSocket(String hostname, int port, InetAddress clientAddress, int clientPort, - SSLParametersImpl sslParameters) throws IOException { - super(hostname, port, clientAddress, clientPort, sslParameters); - } - - Java8FileDescriptorSocket(InetAddress address, int port, InetAddress clientAddress, - int clientPort, SSLParametersImpl sslParameters) throws IOException { - super(address, port, clientAddress, clientPort, sslParameters); - } - - Java8FileDescriptorSocket(Socket socket, String hostname, int port, boolean autoClose, - SSLParametersImpl sslParameters) throws IOException { - super(socket, hostname, port, autoClose, sslParameters); - } - - /* @Override */ - @SuppressWarnings("MissingOverride") // For compilation with Java < 9. - public void setHandshakeApplicationProtocolSelector( - final BiFunction, String> selector) { - this.selector = selector; - setApplicationProtocolSelector(toApplicationProtocolSelector(selector)); - } - - /* @Override */ - @SuppressWarnings("MissingOverride") // For compilation with Java < 9. - public BiFunction, String> getHandshakeApplicationProtocolSelector() { - return selector; - } - - private static ApplicationProtocolSelector toApplicationProtocolSelector( - final BiFunction, String> selector) { - return selector == null ? null : new ApplicationProtocolSelector() { - @Override - public String selectApplicationProtocol(SSLEngine socket, List protocols) { - throw new UnsupportedOperationException(); - } - - @Override - public String selectApplicationProtocol(SSLSocket socket, List protocols) { - return selector.apply(socket, protocols); - } - }; - } -} diff --git a/common/src/main/java/org/conscrypt/KeyManagerFactoryImpl.java b/common/src/main/java/org/conscrypt/KeyManagerFactoryImpl.java index a6ba3b8b3..cc8713ab9 100644 --- a/common/src/main/java/org/conscrypt/KeyManagerFactoryImpl.java +++ b/common/src/main/java/org/conscrypt/KeyManagerFactoryImpl.java @@ -62,7 +62,7 @@ protected void engineInit(KeyStore ks, char[] password) keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); String keyStoreName = System.getProperty("javax.net.ssl.keyStore"); String keyStorePwd = null; - if (keyStoreName == null || keyStoreName.equalsIgnoreCase("NONE") + if (keyStoreName == null || AddressUtils.asciiEqualsIgnoreCase(keyStoreName, "NONE") || keyStoreName.isEmpty()) { try { keyStore.load(null, null); diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index 8722dc9bd..a7aadc4b9 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -16,6 +16,8 @@ package org.conscrypt; +// android-add: import dalvik.annotation.optimization.CriticalNative; +// android-add: import dalvik.annotation.optimization.FastNative; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; import java.io.FileDescriptor; @@ -61,6 +63,7 @@ public final class NativeCrypto { // --- OpenSSL library initialization -------------------------------------- private static final UnsatisfiedLinkError loadError; + static { UnsatisfiedLinkError error = null; try { @@ -75,8 +78,8 @@ public final class NativeCrypto { } /** - * Checks to see whether or not the native library was successfully loaded. If not, throws - * the {@link UnsatisfiedLinkError} that was encountered while attempting to load the library. + * Checks to see whether or not the native library was successfully loaded. If not, throws the + * {@link UnsatisfiedLinkError} that was encountered while attempting to load the library. */ static void checkAvailability() { if (loadError != null) { @@ -86,23 +89,38 @@ static void checkAvailability() { // --- DSA/RSA public/private key handling functions ----------------------- + static native byte[] wrap_RSA_private_key_pkcs8(byte[] rawKeyBytes); + static native byte[] wrap_RSA_public_key_x509(byte[] rawKeyBytes); + + static native byte[] unwrap_RSA_private_key_pkcs8(byte[] rawKeyBytes); + static native byte[] unwrap_RSA_public_key_x509(byte[] rawKeyBytes); + + // android-add: @FastNative static native long EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q, byte[] dmp1, byte[] dmq1, byte[] iqmp); + // android-add: @FastNative static native int EVP_PKEY_type(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native String EVP_PKEY_print_public(NativeRef.EVP_PKEY pkeyRef); + // android-add: @FastNative static native String EVP_PKEY_print_params(NativeRef.EVP_PKEY pkeyRef); + // android-add: @FastNative static native void EVP_PKEY_free(long pkey); + // android-add: @FastNative static native int EVP_PKEY_cmp(NativeRef.EVP_PKEY pkey1, NativeRef.EVP_PKEY pkey2); + // android-add: @FastNative static native byte[] EVP_marshal_private_key(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native long EVP_parse_private_key(byte[] data) throws ParsingException; + // android-add: @FastNative static native byte[] EVP_marshal_public_key(NativeRef.EVP_PKEY pkey); static native long EVP_PKEY_from_private_key_info(byte[] data, int[] algs) @@ -123,33 +141,44 @@ static native long EVP_PKEY_from_subject_public_key_info(byte[] data, int[] algs static native byte[] EVP_PKEY_get_private_seed(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native byte[] EVP_raw_X25519_private_key(byte[] data) throws ParsingException, InvalidKeyException; + // android-add: @FastNative static native long EVP_parse_public_key(byte[] data) throws ParsingException; + // android-add: @FastNative static native long PEM_read_bio_PUBKEY(long bioCtx); + // android-add: @FastNative static native long PEM_read_bio_PrivateKey(long bioCtx); + // android-add: @FastNative static native long getRSAPrivateKeyWrapper(PrivateKey key, byte[] modulus); + // android-add: @FastNative static native long getECPrivateKeyWrapper(PrivateKey key, NativeRef.EC_GROUP ecGroupRef); static native long RSA_generate_key_ex(int modulusBits, byte[] publicExponent); + // android-add: @FastNative static native int RSA_size(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native int RSA_private_encrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding); + // android-add: @FastNative static native int RSA_public_decrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding) throws BadPaddingException, SignatureException; + // android-add: @FastNative static native int RSA_public_encrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding); + // android-add: @FastNative static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, NativeRef.EVP_PKEY pkey, int padding) throws BadPaddingException, SignatureException; @@ -157,11 +186,13 @@ static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, NativeRe /* * Returns array of {n, e} */ + // android-add: @FastNative static native byte[][] get_RSA_public_params(NativeRef.EVP_PKEY rsa); /* * Returns array of {n, e, d, p, q, dmp1, dmq1, iqmp} */ + // android-add: @FastNative static native byte[][] get_RSA_private_params(NativeRef.EVP_PKEY rsa); // --- ChaCha20 ----------------------- @@ -169,100 +200,140 @@ static native int RSA_private_decrypt(int flen, byte[] from, byte[] to, NativeRe /* * Returns the encrypted or decrypted version of the data. */ + // android-add: @FastNative static native void chacha20_encrypt_decrypt(byte[] in, int inOffset, byte[] out, int outOffset, int length, byte[] key, byte[] nonce, int blockCounter); // --- EC functions -------------------------- + static native byte[] wrap_EC_private_key_pkcs8(byte[] rawKeyBytes); + + static native byte[] wrap_EC_public_key_x509(byte[] rawKeyBytes, String curveName); + + static native byte[] unwrap_EC_private_key_pkcs8(byte[] rawKeyBytes); + + static native byte[] unwrap_EC_public_key_x509(byte[] rawKeyBytes); + + // android-add: @FastNative static native long EVP_PKEY_new_EC_KEY(NativeRef.EC_GROUP groupRef, NativeRef.EC_POINT pubkeyRef, byte[] privkey); + // android-add: @FastNative static native long EC_GROUP_new_by_curve_name(String curveName); + // android-add: @FastNative static native long EC_GROUP_new_arbitrary(byte[] p, byte[] a, byte[] b, byte[] x, byte[] y, byte[] order, int cofactor); + // android-add: @FastNative static native String EC_GROUP_get_curve_name(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native byte[][] EC_GROUP_get_curve(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native void EC_GROUP_clear_free(long groupRef); + // android-add: @FastNative static native long EC_GROUP_get_generator(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native byte[] EC_GROUP_get_order(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native int EC_GROUP_get_degree(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native byte[] EC_GROUP_get_cofactor(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native long EC_POINT_new(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native void EC_POINT_clear_free(long pointRef); + // android-add: @FastNative static native byte[][] EC_POINT_get_affine_coordinates(NativeRef.EC_GROUP groupRef, NativeRef.EC_POINT pointRef); + // android-add: @FastNative static native void EC_POINT_set_affine_coordinates(NativeRef.EC_GROUP groupRef, NativeRef.EC_POINT pointRef, byte[] x, byte[] y); static native long EC_KEY_generate_key(NativeRef.EC_GROUP groupRef); + // android-add: @FastNative static native long EC_KEY_get1_group(NativeRef.EVP_PKEY pkeyRef); + // android-add: @FastNative static native byte[] EC_KEY_get_private_key(NativeRef.EVP_PKEY keyRef); + // android-add: @FastNative static native long EC_KEY_get_public_key(NativeRef.EVP_PKEY keyRef); + // android-add: @FastNative static native byte[] EC_KEY_marshal_curve_name(NativeRef.EC_GROUP groupRef) throws IOException; + // android-add: @FastNative static native long EC_KEY_parse_curve_name(byte[] encoded) throws IOException; + // android-add: @FastNative static native int ECDH_compute_key(byte[] out, int outOffset, NativeRef.EVP_PKEY publicKeyRef, NativeRef.EVP_PKEY privateKeyRef) throws InvalidKeyException, IndexOutOfBoundsException; + // android-add: @FastNative static native int ECDSA_size(NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native int ECDSA_sign(byte[] data, int dataLen, byte[] sig, NativeRef.EVP_PKEY pkey); + // android-add: @FastNative static native int ECDSA_verify(byte[] data, int dataLen, byte[] sig, NativeRef.EVP_PKEY pkey); // --- MLDSA44 -------------------------------------------------------------- + // android-add: @FastNative static native byte[] MLDSA44_public_key_from_seed(byte[] privateKeySeed); // --- MLDSA65 -------------------------------------------------------------- + // android-add: @FastNative static native byte[] MLDSA65_public_key_from_seed(byte[] privateKeySeed); // --- MLDSA87 -------------------------------------------------------------- + // android-add: @FastNative static native byte[] MLDSA87_public_key_from_seed(byte[] privateKeySeed); // --- SLHDSA_SHA2_128S -------------------------------------------------------------- static native void SLHDSA_SHA2_128S_generate_key(byte[] outPublicKey, byte[] outPrivateKey); + // android-add: @FastNative static native byte[] SLHDSA_SHA2_128S_sign(byte[] data, int dataLen, byte[] privateKey); + // android-add: @FastNative static native int SLHDSA_SHA2_128S_verify(byte[] data, int dataLen, byte[] sig, byte[] publicKey); // --- Curve25519 -------------- + // android-add: @FastNative static native boolean X25519(byte[] out, byte[] privateKey, byte[] publicKey) throws InvalidKeyException; + // android-add: @FastNative static native void X25519_keypair(byte[] outPublicKey, byte[] outPrivateKey); + // android-add: @FastNative static native void ED25519_keypair(byte[] outPublicKey, byte[] outPrivateKey); // --- X-Wing -------------- - static native byte[] XWING_public_key_from_seed(byte[] privateKeySeed); + public static native byte[] XWING_public_key_from_seed(byte[] privateKeySeed); // --- ML-KEM -------------- @@ -272,154 +343,209 @@ static native boolean X25519(byte[] out, byte[] privateKey, byte[] publicKey) // --- Message digest functions -------------- // These return const references + // android-add: @FastNative static native long EVP_get_digestbyname(String name); + // android-add: @FastNative static native int EVP_MD_size(long evp_md_const); // --- Message digest context functions -------------- + // android-add: @FastNative static native long EVP_MD_CTX_create(); + // android-add: @FastNative static native void EVP_MD_CTX_cleanup(NativeRef.EVP_MD_CTX ctx); + // android-add: @FastNative static native void EVP_MD_CTX_destroy(long ctx); + // android-add: @FastNative static native int EVP_MD_CTX_copy_ex(NativeRef.EVP_MD_CTX dst_ctx, NativeRef.EVP_MD_CTX src_ctx); // --- Digest handling functions ------------------------------------------- + // android-add: @FastNative static native int EVP_DigestInit_ex(NativeRef.EVP_MD_CTX ctx, long evp_md); + // android-add: @FastNative static native void EVP_DigestUpdate(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native void EVP_DigestUpdateDirect(NativeRef.EVP_MD_CTX ctx, long ptr, int length); + // android-add: @FastNative static native int EVP_DigestFinal_ex(NativeRef.EVP_MD_CTX ctx, byte[] hash, int offset); // --- Signature handling functions ---------------------------------------- + // android-add: @FastNative static native long EVP_DigestSignInit(NativeRef.EVP_MD_CTX ctx, long evpMdRef, NativeRef.EVP_PKEY key); + // android-add: @FastNative static native long EVP_DigestVerifyInit(NativeRef.EVP_MD_CTX ctx, long evpMdRef, NativeRef.EVP_PKEY key); + // android-add: @FastNative static native void EVP_DigestSignUpdate(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native void EVP_DigestSignUpdateDirect(NativeRef.EVP_MD_CTX ctx, long ptr, int length); + // android-add: @FastNative static native void EVP_DigestVerifyUpdate(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native void EVP_DigestVerifyUpdateDirect(NativeRef.EVP_MD_CTX ctx, long ptr, int length); + // android-add: @FastNative static native byte[] EVP_DigestSignFinal(NativeRef.EVP_MD_CTX ctx); + // android-add: @FastNative static native boolean EVP_DigestVerifyFinal(NativeRef.EVP_MD_CTX ctx, byte[] signature, int offset, int length) throws IndexOutOfBoundsException; + // android-add: @FastNative static native byte[] EVP_DigestSign(NativeRef.EVP_MD_CTX ctx, byte[] buffer, int offset, int length); + // android-add: @FastNative static native boolean EVP_DigestVerify(NativeRef.EVP_MD_CTX ctx, byte[] sigBuffer, int sigOffset, int sigLen, byte[] dataBuffer, int dataOffset, int dataLen); + // android-add: @FastNative static native long EVP_PKEY_encrypt_init(NativeRef.EVP_PKEY pkey) throws InvalidKeyException; + // android-add: @FastNative static native int EVP_PKEY_encrypt(NativeRef.EVP_PKEY_CTX ctx, byte[] out, int outOffset, byte[] input, int inOffset, int inLength) throws IndexOutOfBoundsException, BadPaddingException; + // android-add: @FastNative static native long EVP_PKEY_decrypt_init(NativeRef.EVP_PKEY pkey) throws InvalidKeyException; + // android-add: @FastNative static native int EVP_PKEY_decrypt(NativeRef.EVP_PKEY_CTX ctx, byte[] out, int outOffset, byte[] input, int inOffset, int inLength) throws IndexOutOfBoundsException, BadPaddingException; + // android-add: @FastNative static native void EVP_PKEY_CTX_free(long pkeyCtx); + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_padding(long ctx, int pad) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_pss_saltlen(long ctx, int len) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_mgf1_md(long ctx, long evpMdRef) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_oaep_md(long ctx, long evpMdRef) throws InvalidAlgorithmParameterException; + // android-add: @FastNative static native void EVP_PKEY_CTX_set_rsa_oaep_label(long ctx, byte[] label) throws InvalidAlgorithmParameterException; + static native void EVP_PKEY_CTX_set1_signature_context_string(long ctx, byte[] contextString); + // --- Block ciphers ------------------------------------------------------- // These return const references + // android-add: @FastNative static native long EVP_get_cipherbyname(String string); + // android-add: @FastNative static native void EVP_CipherInit_ex(NativeRef.EVP_CIPHER_CTX ctx, long evpCipher, byte[] key, byte[] iv, boolean encrypting); + // android-add: @FastNative static native int EVP_CipherUpdate(NativeRef.EVP_CIPHER_CTX ctx, byte[] out, int outOffset, byte[] in, int inOffset, int inLength) throws IndexOutOfBoundsException; + // android-add: @FastNative static native int EVP_CipherFinal_ex(NativeRef.EVP_CIPHER_CTX ctx, byte[] out, int outOffset) throws BadPaddingException, IllegalBlockSizeException; + // android-add: @FastNative static native int EVP_CIPHER_iv_length(long evpCipher); + // android-add: @FastNative static native long EVP_CIPHER_CTX_new(); + // android-add: @FastNative static native int EVP_CIPHER_CTX_block_size(NativeRef.EVP_CIPHER_CTX ctx); + // android-add: @FastNative static native int get_EVP_CIPHER_CTX_buf_len(NativeRef.EVP_CIPHER_CTX ctx); + // android-add: @FastNative static native boolean get_EVP_CIPHER_CTX_final_used(NativeRef.EVP_CIPHER_CTX ctx); + // android-add: @FastNative static native void EVP_CIPHER_CTX_set_padding(NativeRef.EVP_CIPHER_CTX ctx, boolean enablePadding); + // android-add: @FastNative static native void EVP_CIPHER_CTX_set_key_length(NativeRef.EVP_CIPHER_CTX ctx, int keyBitSize); + // android-add: @FastNative static native void EVP_CIPHER_CTX_free(long ctx); // --- AEAD ---------------------------------------------------------------- + // android-add: @FastNative static native long EVP_aead_aes_128_gcm(); + // android-add: @FastNative static native long EVP_aead_aes_256_gcm(); + // android-add: @FastNative static native long EVP_aead_chacha20_poly1305(); + // android-add: @FastNative static native long EVP_aead_aes_128_gcm_siv(); + // android-add: @FastNative static native long EVP_aead_aes_256_gcm_siv(); + // android-add: @FastNative static native int EVP_AEAD_max_overhead(long evpAead); + // android-add: @FastNative static native int EVP_AEAD_nonce_length(long evpAead); + // android-add: @FastNative static native int EVP_AEAD_CTX_seal(long evpAead, byte[] key, int tagLengthInBytes, byte[] out, int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad) throws ShortBufferException, BadPaddingException; + // android-add: @FastNative static native int EVP_AEAD_CTX_seal_buf(long evpAead, byte[] key, int tagLengthInBytes, ByteBuffer out, byte[] nonce, ByteBuffer input, byte[] ad) throws ShortBufferException, BadPaddingException; + // android-add: @FastNative static native int EVP_AEAD_CTX_open(long evpAead, byte[] key, int tagLengthInBytes, byte[] out, int outOffset, byte[] nonce, byte[] in, int inOffset, int inLength, byte[] ad) throws ShortBufferException, BadPaddingException; + // android-add: @FastNative static native int EVP_AEAD_CTX_open_buf(long evpAead, byte[] key, int tagLengthInBytes, ByteBuffer out, byte[] nonce, ByteBuffer input, byte[] ad) @@ -427,48 +553,66 @@ static native int EVP_AEAD_CTX_open_buf(long evpAead, byte[] key, int tagLengthI // --- CMAC functions ------------------------------------------------------ + // android-add: @FastNative static native long CMAC_CTX_new(); + // android-add: @FastNative static native void CMAC_CTX_free(long ctx); + // android-add: @FastNative static native void CMAC_Init(NativeRef.CMAC_CTX ctx, byte[] key); + // android-add: @FastNative static native void CMAC_Update(NativeRef.CMAC_CTX ctx, byte[] in, int inOffset, int inLength); + // android-add: @FastNative static native void CMAC_UpdateDirect(NativeRef.CMAC_CTX ctx, long inPtr, int inLength); + // android-add: @FastNative static native byte[] CMAC_Final(NativeRef.CMAC_CTX ctx); + // android-add: @FastNative static native void CMAC_Reset(NativeRef.CMAC_CTX ctx); // --- HMAC functions ------------------------------------------------------ + // android-add: @FastNative static native long HMAC_CTX_new(); + // android-add: @FastNative static native void HMAC_CTX_free(long ctx); + // android-add: @FastNative static native void HMAC_Init_ex(NativeRef.HMAC_CTX ctx, byte[] key, long evp_md); + // android-add: @FastNative static native void HMAC_Update(NativeRef.HMAC_CTX ctx, byte[] in, int inOffset, int inLength); + // android-add: @FastNative static native void HMAC_UpdateDirect(NativeRef.HMAC_CTX ctx, long inPtr, int inLength); + // android-add: @FastNative static native byte[] HMAC_Final(NativeRef.HMAC_CTX ctx); + // android-add: @FastNative static native void HMAC_Reset(NativeRef.HMAC_CTX ctx); // --- HPKE functions ------------------------------------------------------ + // android-add: @FastNative static native byte[] EVP_HPKE_CTX_export(NativeRef.EVP_HPKE_CTX ctx, byte[] exporterCtx, int length); static native void EVP_HPKE_CTX_free(long ctx); + // android-add: @FastNative static native byte[] EVP_HPKE_CTX_open(NativeRef.EVP_HPKE_CTX ctx, byte[] ciphertext, byte[] aad) throws BadPaddingException; + // android-add: @FastNative static native byte[] EVP_HPKE_CTX_seal(NativeRef.EVP_HPKE_CTX ctx, byte[] plaintext, byte[] aad); + // android-add: @FastNative static native Object EVP_HPKE_CTX_setup_base_mode_recipient(int kem, int kdf, int aead, byte[] privateKey, byte[] enc, byte[] info); @@ -480,6 +624,7 @@ static Object EVP_HPKE_CTX_setup_base_mode_recipient(HpkeSuite suite, byte[] pri enc, info); } + // android-add: @FastNative static native Object[] EVP_HPKE_CTX_setup_base_mode_sender(int kem, int kdf, int aead, byte[] publicKey, byte[] info); @@ -488,6 +633,8 @@ static Object[] EVP_HPKE_CTX_setup_base_mode_sender(HpkeSuite suite, byte[] publ return EVP_HPKE_CTX_setup_base_mode_sender(suite.getKem().getId(), suite.getKdf().getId(), suite.getAead().getId(), publicKey, info); } + + // android-add: @FastNative 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); @@ -502,7 +649,7 @@ static Object[] EVP_HPKE_CTX_setup_base_mode_sender_with_seed_for_testing(HpkeSu // --- RAND ---------------------------------------------------------------- - static native void RAND_bytes(byte[] output); + public static native void RAND_bytes(byte[] output); // --- X509_NAME ----------------------------------------------------------- @@ -513,6 +660,7 @@ static int X509_NAME_hash(X500Principal principal) { public static int X509_NAME_hash_old(X500Principal principal) { return X509_NAME_hash(principal, "MD5"); } + private static int X509_NAME_hash(X500Principal principal, String algorithm) { try { byte[] digest = MessageDigest.getInstance(algorithm).digest(principal.getEncoded()); @@ -529,102 +677,128 @@ private static int X509_NAME_hash(X500Principal principal, String algorithm) { /** Used to request get_X509_GENERAL_NAME_stack get the "altname" field. */ static final int GN_STACK_SUBJECT_ALT_NAME = 1; - /** - * Used to request get_X509_GENERAL_NAME_stack get the issuerAlternativeName - * extension. - */ + /** Used to request get_X509_GENERAL_NAME_stack get the issuerAlternativeName extension. */ static final int GN_STACK_ISSUER_ALT_NAME = 2; - /** - * Used to request only non-critical types in get_X509*_ext_oids. - */ + /** Used to request only non-critical types in get_X509*_ext_oids. */ static final int EXTENSION_TYPE_NON_CRITICAL = 0; - /** - * Used to request only critical types in get_X509*_ext_oids. - */ + /** Used to request only critical types in get_X509*_ext_oids. */ static final int EXTENSION_TYPE_CRITICAL = 1; + // android-add: @FastNative static native long d2i_X509_bio(long bioCtx); + // android-add: @FastNative static native long d2i_X509(byte[] encoded) throws ParsingException; + // android-add: @FastNative static native long PEM_read_bio_X509(long bioCtx); + // android-add: @FastNative static native byte[] i2d_X509(long x509ctx, OpenSSLX509Certificate holder); /** Takes an X509 context not an X509_PUBKEY context. */ + // android-add: @FastNative static native byte[] i2d_X509_PUBKEY(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] ASN1_seq_pack_X509(long[] x509CertRefs); + // android-add: @FastNative static native long[] ASN1_seq_unpack_X509_bio(long bioRef) throws ParsingException; static native void X509_free(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native int X509_cmp(long x509ctx1, OpenSSLX509Certificate holder, long x509ctx2, OpenSSLX509Certificate holder2); + // android-add: @FastNative static native void X509_print_ex(long bioCtx, long x509ctx, OpenSSLX509Certificate holder, long nmflag, long certflag); + // android-add: @FastNative static native byte[] X509_get_issuer_name(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] X509_get_subject_name(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native String get_X509_sig_alg_oid(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] get_X509_sig_alg_parameter(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native boolean[] get_X509_issuerUID(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native boolean[] get_X509_subjectUID(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native long X509_get_pubkey(long x509ctx, OpenSSLX509Certificate holder) throws NoSuchAlgorithmException, InvalidKeyException; + // android-add: @FastNative static native String get_X509_pubkey_oid(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] X509_get_ext_oid(long x509ctx, OpenSSLX509Certificate holder, String oid); + // android-add: @FastNative static native String[] get_X509_ext_oids(long x509ctx, OpenSSLX509Certificate holder, int critical); + // android-add: @FastNative static native Object[][] get_X509_GENERAL_NAME_stack(long x509ctx, OpenSSLX509Certificate holder, int type) throws CertificateParsingException; + // android-add: @FastNative static native boolean[] get_X509_ex_kusage(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native String[] get_X509_ex_xkusage(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native int get_X509_ex_pathlen(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native long X509_get_notBefore(long x509ctx, OpenSSLX509Certificate holder) throws ParsingException; + // android-add: @FastNative static native long X509_get_notAfter(long x509ctx, OpenSSLX509Certificate holder) throws ParsingException; + // android-add: @FastNative static native long X509_get_version(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] X509_get_serialNumber(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native void X509_verify(long x509ctx, OpenSSLX509Certificate holder, NativeRef.EVP_PKEY pkeyCtx) throws BadPaddingException, IllegalBlockSizeException; + // android-add: @FastNative static native byte[] get_X509_tbs_cert(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native byte[] get_X509_tbs_cert_without_ext(long x509ctx, OpenSSLX509Certificate holder, String oid); + // android-add: @FastNative static native byte[] get_X509_signature(long x509ctx, OpenSSLX509Certificate holder); + // android-add: @FastNative static native int get_X509_ex_flags(long x509ctx, OpenSSLX509Certificate holder); // Used by Android platform TrustedCertificateStore. @SuppressWarnings("unused") + // android-add: @FastNative static native int X509_check_issued(long ctx, OpenSSLX509Certificate holder, long ctx2, OpenSSLX509Certificate holder2); @@ -637,104 +811,138 @@ static native int X509_check_issued(long ctx, OpenSSLX509Certificate holder, lon static final int PKCS7_CRLS = 2; /** Returns an array of X509 or X509_CRL pointers. */ + // android-add: @FastNative static native long[] d2i_PKCS7_bio(long bioCtx, int which) throws ParsingException; /** Returns an array of X509 or X509_CRL pointers. */ + // android-add: @FastNative static native byte[] i2d_PKCS7(long[] certs); /** Returns an array of X509 or X509_CRL pointers. */ + // android-add: @FastNative static native long[] PEM_read_bio_PKCS7(long bioCtx, int which); // --- X509_CRL ------------------------------------------------------------ + // android-add: @FastNative static native long d2i_X509_CRL_bio(long bioCtx); + // android-add: @FastNative static native long PEM_read_bio_X509_CRL(long bioCtx); + // android-add: @FastNative static native byte[] i2d_X509_CRL(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native void X509_CRL_free(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native void X509_CRL_print(long bioCtx, long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native String get_X509_CRL_sig_alg_oid(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native byte[] get_X509_CRL_sig_alg_parameter(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native byte[] X509_CRL_get_issuer_name(long x509CrlCtx, OpenSSLX509CRL holder); /** Returns X509_REVOKED reference that is not duplicated! */ + // android-add: @FastNative static native long X509_CRL_get0_by_cert(long x509CrlCtx, OpenSSLX509CRL holder, long x509Ctx, OpenSSLX509Certificate holder2); /** Returns X509_REVOKED reference that is not duplicated! */ + // android-add: @FastNative static native long X509_CRL_get0_by_serial(long x509CrlCtx, OpenSSLX509CRL holder, byte[] serial); /** Returns an array of X509_REVOKED that are owned by the caller. */ + // android-add: @FastNative static native long[] X509_CRL_get_REVOKED(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native String[] get_X509_CRL_ext_oids(long x509Crlctx, OpenSSLX509CRL holder, int critical); + // android-add: @FastNative static native byte[] X509_CRL_get_ext_oid(long x509CrlCtx, OpenSSLX509CRL holder, String oid); + // android-add: @FastNative static native long X509_CRL_get_version(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native long X509_CRL_get_ext(long x509CrlCtx, OpenSSLX509CRL holder, String oid); + // android-add: @FastNative static native byte[] get_X509_CRL_signature(long x509ctx, OpenSSLX509CRL holder); + // android-add: @FastNative static native void X509_CRL_verify(long x509CrlCtx, OpenSSLX509CRL holder, NativeRef.EVP_PKEY pkeyCtx) throws BadPaddingException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException; + // android-add: @FastNative static native byte[] get_X509_CRL_crl_enc(long x509CrlCtx, OpenSSLX509CRL holder); + // android-add: @FastNative static native long X509_CRL_get_lastUpdate(long x509CrlCtx, OpenSSLX509CRL holder) throws ParsingException; + // android-add: @FastNative static native long X509_CRL_get_nextUpdate(long x509CrlCtx, OpenSSLX509CRL holder) throws ParsingException; // --- X509_REVOKED -------------------------------------------------------- + // android-add: @FastNative static native long X509_REVOKED_dup(long x509RevokedCtx); + // android-add: @FastNative static native byte[] i2d_X509_REVOKED(long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native String[] get_X509_REVOKED_ext_oids(long x509ctx, int critical, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native byte[] X509_REVOKED_get_ext_oid(long x509RevokedCtx, String oid, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native byte[] X509_REVOKED_get_serialNumber(long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native long X509_REVOKED_get_ext(long x509RevokedCtx, String oid, OpenSSLX509CRLEntry holder); /** Returns ASN1_TIME reference. */ + // android-add: @FastNative static native long get_X509_REVOKED_revocationDate(long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native void X509_REVOKED_print(long bioRef, long x509RevokedCtx, OpenSSLX509CRLEntry holder); + // android-add: @FastNative static native void X509_REVOKED_free(long x509RevokedCtx, OpenSSLX509CRLEntry holder); // --- X509_EXTENSION ------------------------------------------------------ + // android-add: @FastNative static native int X509_supported_extension(long x509ExtensionRef); // --- SPAKE --------------------------------------------------------------- /** - * Sets the SPAKE credential for the given SSL context using a password. - * Used for both client and server. + * Sets the SPAKE credential for the given SSL context using a password. Used for both client + * and server. */ + // android-add: @FastNative static native void SSL_CTX_set_spake_credential(byte[] context, byte[] pw_array, byte[] id_prover_array, byte[] id_verifier_array, boolean is_client, @@ -744,6 +952,7 @@ static native void SSL_CTX_set_spake_credential(byte[] context, byte[] pw_array, // --- ASN1_TIME ----------------------------------------------------------- + // android-add: @FastNative static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) throws ParsingException; @@ -751,144 +960,179 @@ static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_read_* functions to read the ASN.1-encoded data in val. The returned object must - * be freed after use by calling asn1_read_free. + * asn1_read_* functions to read the ASN.1-encoded data in val. The returned object must be + * freed after use by calling asn1_read_free. */ + // android-add: @FastNative static native long asn1_read_init(byte[] val) throws IOException; /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_read_* functions to read the ASN.1 sequence pointed to by cbsRef. The returned - * object must be freed after use by calling asn1_read_free. + * asn1_read_* functions to read the ASN.1 sequence pointed to by cbsRef. The returned object + * must be freed after use by calling asn1_read_free. */ + // android-add: @FastNative static native long asn1_read_sequence(long cbsRef) throws IOException; /** - * Returns whether the next object in the given reference is explicitly tagged with the - * given tag number. + * Returns whether the next object in the given reference is explicitly tagged with the given + * tag number. */ + // android-add: @FastNative static native boolean asn1_read_next_tag_is(long cbsRef, int tag) throws IOException; /** - * Allocates and returns an opaque reference to an object that can be used with - * other asn1_read_* functions to read the ASN.1 data pointed to by cbsRef. The returned - * object must be freed after use by calling asn1_read_free. + * Allocates and returns an opaque reference to an object that can be used with other + * asn1_read_* functions to read the ASN.1 data pointed to by cbsRef. The returned object must + * be freed after use by calling asn1_read_free. */ + // android-add: @FastNative static native long asn1_read_tagged(long cbsRef) throws IOException; - /** - * Returns the contents of an ASN.1 octet string from the given reference. - */ + /** Returns the contents of an ASN.1 octet string from the given reference. */ + // android-add: @FastNative static native byte[] asn1_read_octetstring(long cbsRef) throws IOException; /** - * Returns an ASN.1 integer from the given reference. If the integer doesn't fit - * in a uint64, this method will throw an IOException. + * Returns an ASN.1 integer from the given reference. If the integer doesn't fit in a uint64, + * this method will throw an IOException. */ + // android-add: @FastNative static native long asn1_read_uint64(long cbsRef) throws IOException; - /** - * Consumes an ASN.1 NULL from the given reference. - */ + /** Consumes an ASN.1 NULL from the given reference. */ + // android-add: @FastNative static native void asn1_read_null(long cbsRef) throws IOException; /** * Returns an ASN.1 OID in dotted-decimal notation (eg, "1.3.14.3.2.26" for SHA-1) from the * given reference. */ + // android-add: @FastNative static native String asn1_read_oid(long cbsRef) throws IOException; + /** + * Returns an ASN.1 OID in dotted-decimal notation (eg, "1.3.14.3.2.26" for SHA-1) from the + * given reference even if it's not registered in BoringSSL. + */ + static native String asn1_read_oid_raw(long cbsRef) throws IOException; + + /** + * Parses an ASN.1 BIT STRING and returns its payload checking that the unused bits + * padding byte matches the expected value. Used for Composite ML-DSA keys. + */ + static native byte[] asn1_read_bitstring_payload(long cbsRef, int expectedUnusedBits) + throws IOException; + /** * Returns whether or not the given reference has been read completely. */ + // android-add: @FastNative static native boolean asn1_read_is_empty(long cbsRef); /** - * Frees any resources associated with the given reference. After calling, the reference - * must not be used again. This may be called with a zero reference, in which case nothing - * will be done. + * Frees any resources associated with the given reference. After calling, the reference must + * not be used again. This may be called with a zero reference, in which case nothing will be + * done. */ + // android-add: @FastNative static native void asn1_read_free(long cbsRef); /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_write_* functions to write ASN.1-encoded data. The returned object must be finalized - * after use by calling either asn1_write_finish or asn1_write_cleanup, and its resources - * must be freed by calling asn1_write_free. + * asn1_write_* functions to write ASN.1-encoded data. The returned object must be finalized + * after use by calling either asn1_write_finish or asn1_write_cleanup, and its resources must + * be freed by calling asn1_write_free. */ + // android-add: @FastNative static native long asn1_write_init() throws IOException; /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_write_* functions to write an ASN.1 sequence into the given reference. The returned - * reference may only be used until the next call on the parent reference. The returned - * object must be freed after use by calling asn1_write_free. + * asn1_write_* functions to write an ASN.1 sequence into the given reference. The returned + * reference may only be used until the next call on the parent reference. The returned object + * must be freed after use by calling asn1_write_free. */ + // android-add: @FastNative static native long asn1_write_sequence(long cbbRef) throws IOException; /** * Allocates and returns an opaque reference to an object that can be used with other - * asn1_write_* functions to write a explicitly-tagged ASN.1 object with the given tag - * into the given reference. The returned reference may only be used until the next - * call on the parent reference. The returned object must be freed after use by - * calling asn1_write_free. + * asn1_write_* functions to write a explicitly-tagged ASN.1 object with the given tag into the + * given reference. The returned reference may only be used until the next call on the parent + * reference. The returned object must be freed after use by calling asn1_write_free. */ + // android-add: @FastNative static native long asn1_write_tag(long cbbRef, int tag) throws IOException; + /** Writes the given data into the given reference as an ASN.1-encoded octet string. */ + // android-add: @FastNative + static native void asn1_write_octetstring(long cbbRef, byte[] data) throws IOException; + /** - * Writes the given data into the given reference as an ASN.1-encoded octet string. + * Writes the given data into the given reference as an ASN.1-encoded bit string with 0 padding. */ - static native void asn1_write_octetstring(long cbbRef, byte[] data) throws IOException; + static native void asn1_write_bitstring(long cbbRef, byte[] data) throws IOException; /** * Writes the given value into the given reference as an ASN.1-encoded integer. */ + // android-add: @FastNative static native void asn1_write_uint64(long cbbRef, long value) throws IOException; - /** - * Writes a NULL value into the given reference. - */ + /** Writes a NULL value into the given reference. */ + // android-add: @FastNative static native void asn1_write_null(long cbbRef) throws IOException; + /** Writes the given OID (which must be in dotted-decimal notation) into the given reference. */ + // android-add: @FastNative + static native void asn1_write_oid(long cbbRef, String oid) throws IOException; + /** - * Writes the given OID (which must be in dotted-decimal notation) into the given reference. + * Writes the given OID (which must be in dotted-decimal notation) into the given reference + * even if it's not registered in BoringSSL. */ - static native void asn1_write_oid(long cbbRef, String oid) throws IOException; + static native void asn1_write_oid_raw(long cbbRef, String oid) throws IOException; /** * Flushes the given reference, invalidating any child references and completing their - * operations. This must be called if the child references are to be freed before - * asn1_write_finish is called on the ultimate parent. The child references must still - * be freed. + * operations. This must be called if the child references are to be freed before + * asn1_write_finish is called on the ultimate parent. The child references must still be freed. */ + // android-add: @FastNative static native void asn1_write_flush(long cbbRef) throws IOException; /** - * Completes any in-progress operations and returns the ASN.1-encoded data. Either this - * or asn1_write_cleanup must be called on any reference returned from asn1_write_init - * before it is freed. + * Completes any in-progress operations and returns the ASN.1-encoded data. Either this or + * asn1_write_cleanup must be called on any reference returned from asn1_write_init before it is + * freed. */ + // android-add: @FastNative static native byte[] asn1_write_finish(long cbbRef) throws IOException; /** - * Cleans up intermediate state in the given reference. Either this or asn1_write_finish - * must be called on any reference returned from asn1_write_init before it is freed. + * Cleans up intermediate state in the given reference. Either this or asn1_write_finish must be + * called on any reference returned from asn1_write_init before it is freed. */ + // android-add: @FastNative static native void asn1_write_cleanup(long cbbRef); /** - * Frees resources associated with the given reference. After calling, the reference - * must not be used again. This may be called with a zero reference, in which case nothing - * will be done. + * Frees resources associated with the given reference. After calling, the reference must not be + * used again. This may be called with a zero reference, in which case nothing will be done. */ + // android-add: @FastNative static native void asn1_write_free(long cbbRef); // --- BIO stream creation ------------------------------------------------- + // android-add: @FastNative static native long create_BIO_InputStream(OpenSSLBIOInputStream is, boolean isFinite); + // android-add: @FastNative static native long create_BIO_OutputStream(OutputStream os); + // android-add: @FastNative static native void BIO_free_all(long bioRef); // --- SSL handling -------------------------------------------------------- @@ -918,23 +1162,18 @@ static native void ASN1_TIME_to_Calendar(long asn1TimeCtx, Calendar cal) new HashSet(Arrays.asList(SUPPORTED_TLS_1_3_CIPHER_SUITES)); /** - * TLS_EMPTY_RENEGOTIATION_INFO_SCSV is RFC 5746's renegotiation - * indication signaling cipher suite value. It is not a real - * cipher suite. It is just an indication in the default and - * supported cipher suite lists indicates that the implementation - * supports secure renegotiation. - *

- * In the RI, its presence means that the SCSV is sent in the - * cipher suite list to indicate secure renegotiation support and - * its absense means to send an empty TLS renegotiation info + * TLS_EMPTY_RENEGOTIATION_INFO_SCSV is RFC 5746's renegotiation indication signaling cipher + * suite value. It is not a real cipher suite. It is just an indication in the default and + * supported cipher suite lists indicates that the implementation supports secure renegotiation. + * + *

In the RI, its presence means that the SCSV is sent in the cipher suite list to indicate + * secure renegotiation support and its absense means to send an empty TLS renegotiation info * extension instead. - *

- * However, OpenSSL doesn't provide an API to give this level of - * control, instead always sending the SCSV and always including - * the empty renegotiation info if TLS is used (as opposed to - * SSL). So we simply allow TLS_EMPTY_RENEGOTIATION_INFO_SCSV to - * be passed for compatibility as to provide the hint that we - * support secure renegotiation. + * + *

However, OpenSSL doesn't provide an API to give this level of control, instead always + * sending the SCSV and always including the empty renegotiation info if TLS is used (as opposed + * to SSL). So we simply allow TLS_EMPTY_RENEGOTIATION_INFO_SCSV to be passed for compatibility + * as to provide the hint that we support secure renegotiation. */ static final String TLS_EMPTY_RENEGOTIATION_INFO_SCSV = "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"; @@ -954,15 +1193,14 @@ static String cipherSuiteFromJava(String javaCipherSuite) { } /** - * TLS_FALLBACK_SCSV is from - * https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 - * to indicate to the server that this is a fallback protocol - * request. + * TLS_FALLBACK_SCSV is from https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00 to + * indicate to the server that this is a fallback protocol request. */ private static final String TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"; private static final boolean HAS_AES_HARDWARE; private static final String[] SUPPORTED_TLS_1_2_CIPHER_SUITES; + static { if (loadError == null) { // If loadError is not null, it means the native code was not loaded, so @@ -996,11 +1234,13 @@ static String cipherSuiteFromJava(String javaCipherSuite) { } /** - * Returns 1 if the BoringSSL believes the CPU has AES accelerated hardware - * instructions. Used to determine cipher suite ordering. + * Returns 1 if the BoringSSL believes the CPU has AES accelerated hardware instructions. Used + * to determine cipher suite ordering. */ + // android-add: @CriticalNative static native int EVP_has_aes_hardware(); + // android-add: @FastNative static native long SSL_CTX_new(); // IMPLEMENTATION NOTE: The default list of cipher suites is a trade-off between what we'd like @@ -1022,39 +1262,40 @@ static String cipherSuiteFromJava(String javaCipherSuite) { // prevent apps from connecting to servers they were previously able to connect to. /** X.509 based cipher suites enabled by default (if requested), in preference order. */ - static final String[] DEFAULT_X509_CIPHER_SUITES = HAS_AES_HARDWARE ? - new String[] { - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - } : - new String[] { - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_256_GCM_SHA384", - "TLS_RSA_WITH_AES_128_CBC_SHA", - "TLS_RSA_WITH_AES_256_CBC_SHA", - }; + static final String[] DEFAULT_X509_CIPHER_SUITES = + HAS_AES_HARDWARE + ? new String[] { + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + } + : new String[] { + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + }; /** TLS-PSK cipher suites enabled by default (if requested), in preference order. */ static final String[] DEFAULT_PSK_CIPHER_SUITES = new String[] { @@ -1075,24 +1316,20 @@ static String[] getSupportedCipherSuites() { SUPPORTED_TLS_1_2_CIPHER_SUITES.clone()); } + // android-add: @FastNative static native void SSL_CTX_free(long ssl_ctx, AbstractSessionContext holder); + // android-add: @FastNative static native void SSL_CTX_set_session_id_context(long ssl_ctx, AbstractSessionContext holder, byte[] sid_ctx); + // android-add: @FastNative static native long SSL_CTX_set_timeout(long ssl_ctx, AbstractSessionContext holder, long seconds); + // android-add: @FastNative static native long SSL_new(long ssl_ctx, AbstractSessionContext holder) throws SSLException; - static native void SSL_enable_tls_channel_id(long ssl, NativeSsl ssl_holder) - throws SSLException; - - static native byte[] SSL_get_tls_channel_id(long ssl, NativeSsl ssl_holder) throws SSLException; - - static native void SSL_set1_tls_channel_id(long ssl, NativeSsl ssl_holder, - NativeRef.EVP_PKEY pkey); - /** * Sets the local certificates and private key. * @@ -1101,48 +1338,65 @@ static native void SSL_set1_tls_channel_id(long ssl, NativeSsl ssl_holder, * @param pkey a reference to the private key. * @throws SSLException if a problem occurs setting the cert/key. */ + // android-add: @FastNative static native void setLocalCertsAndPrivateKey(long ssl, NativeSsl ssl_holder, byte[][] encodedCertificates, NativeRef.EVP_PKEY pkey) throws SSLException; + // android-add: @FastNative static native void SSL_set_client_CA_list(long ssl, NativeSsl ssl_holder, byte[][] asn1DerEncodedX500Principals) throws SSLException; + // android-add: @FastNative static native long SSL_set_mode(long ssl, NativeSsl ssl_holder, long mode); + // android-add: @FastNative static native long SSL_set_options(long ssl, NativeSsl ssl_holder, long options); + // android-add: @FastNative static native long SSL_clear_options(long ssl, NativeSsl ssl_holder, long options); + // android-add: @FastNative static native int SSL_set_protocol_versions(long ssl, NativeSsl ssl_holder, int min_version, int max_version); + // android-add: @FastNative static native void SSL_enable_signed_cert_timestamps(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_get_signed_cert_timestamp_list(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_signed_cert_timestamp_list(long ssl, NativeSsl ssl_holder, byte[] list); + // android-add: @FastNative static native void SSL_enable_ocsp_stapling(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_get_ocsp_response(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_ocsp_response(long ssl, NativeSsl ssl_holder, byte[] response); + // android-add: @FastNative static native byte[] SSL_get_tls_unique(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_export_keying_material(long ssl, NativeSsl ssl_holder, byte[] label, byte[] context, int num_bytes) throws SSLException; + // android-add: @FastNative static native void SSL_use_psk_identity_hint(long ssl, NativeSsl ssl_holder, String identityHint) throws SSLException; + // android-add: @FastNative static native void set_SSL_psk_client_callback_enabled(long ssl, NativeSsl ssl_holder, boolean enabled); + // android-add: @FastNative static native void set_SSL_psk_server_callback_enabled(long ssl, NativeSsl ssl_holder, boolean enabled); @@ -1214,6 +1468,7 @@ static String[] getSupportedProtocols() { private static class Range { public final String min; public final String max; + public Range(String min, String max) { this.min = min; this.max = max; @@ -1282,6 +1537,7 @@ static String[] checkEnabledProtocols(String[] protocols) { return protocols; } + // android-add: @FastNative static native void SSL_set_cipher_lists(long ssl, NativeSsl ssl_holder, String[] ciphers); /** @@ -1289,6 +1545,7 @@ static String[] checkEnabledProtocols(String[] protocols) { * * @return array of {@code SSL_CIPHER} references. */ + // android-add: @FastNative static native long[] SSL_get_ciphers(long ssl, NativeSsl ssl_holder); static void setEnabledCipherSuites(long ssl, NativeSsl ssl_holder, String[] cipherSuites, @@ -1350,101 +1607,111 @@ static String[] checkEnabledCipherSuites(String[] cipherSuites) { return cipherSuites; } + // android-add: @FastNative static native void SSL_set_accept_state(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_connect_state(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_set_verify(long ssl, NativeSsl ssl_holder, int mode); + // android-add: @FastNative static native void SSL_set_session(long ssl, NativeSsl ssl_holder, long sslSessionNativePointer) throws SSLException; + // android-add: @FastNative static native void SSL_set_session_creation_enabled(long ssl, NativeSsl ssl_holder, boolean creationEnabled) throws SSLException; + // android-add: @FastNative static native boolean SSL_session_reused(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_accept_renegotiations(long ssl, NativeSsl ssl_holder) throws SSLException; + // android-add: @FastNative static native void SSL_set_tlsext_host_name(long ssl, NativeSsl ssl_holder, String hostname) throws SSLException; - static native String SSL_get_servername(long ssl, NativeSsl ssl_holder); - static native void SSL_do_handshake(long ssl, NativeSsl ssl_holder, FileDescriptor fd, - SSLHandshakeCallbacks shc, int timeoutMillis) - throws SSLException, SocketTimeoutException, CertificateException; + // android-add: @FastNative + static native String SSL_get_servername(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative public static native String SSL_get_current_cipher(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative + public static native String SSL_get_version(long ssl, NativeSsl ssl_holder); + public static native void SSL_set1_groups(long ssl, NativeSsl sslHolder, int[] groups); public static native String SSL_get_curve_name(long ssl, NativeSsl sslHolder); - public static native String SSL_get_version(long ssl, NativeSsl ssl_holder); - /** * Returns the peer certificate chain. */ + // android-add: @FastNative static native byte[][] SSL_get0_peer_certificates(long ssl, NativeSsl ssl_holder); - /** - * Reads with the native SSL_read function from the encrypted data stream - * @return -1 if error or the end of the stream is reached. - */ - static native int SSL_read(long ssl, NativeSsl ssl_holder, FileDescriptor fd, - SSLHandshakeCallbacks shc, byte[] b, int off, int len, - int readTimeoutMillis) throws IOException; - - /** - * Writes with the native SSL_write function to the encrypted data stream. - */ - static native void SSL_write(long ssl, NativeSsl ssl_holder, FileDescriptor fd, - SSLHandshakeCallbacks shc, byte[] b, int off, int len, - int writeTimeoutMillis) throws IOException; - + // android-add: @FastNative static native void SSL_interrupt(long ssl, NativeSsl ssl_holder); - static native void SSL_shutdown(long ssl, NativeSsl ssl_holder, FileDescriptor fd, - SSLHandshakeCallbacks shc) throws IOException; + // android-add: @FastNative static native int SSL_get_shutdown(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native void SSL_free(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native long SSL_get_time(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native long SSL_set_timeout(long ssl, NativeSsl ssl_holder, long millis); + // android-add: @FastNative static native long SSL_get_timeout(long ssl, NativeSsl ssl_holder); + // android-add: @CriticalNative static native int SSL_get_signature_algorithm_key_type(int signatureAlg); + // android-add: @FastNative static native byte[] SSL_session_id(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native byte[] SSL_SESSION_session_id(long sslSessionNativePointer); + // android-add: @FastNative static native long SSL_SESSION_get_time(long sslSessionNativePointer); + // android-add: @FastNative static native long SSL_SESSION_get_timeout(long sslSessionNativePointer); + // android-add: @FastNative static native String SSL_SESSION_get_version(long sslSessionNativePointer); + // android-add: @FastNative static native String SSL_SESSION_cipher(long sslSessionNativePointer); + // android-add: @FastNative static native boolean SSL_SESSION_should_be_single_use(long sslSessionNativePointer); + // android-add: @FastNative static native void SSL_SESSION_up_ref(long sslSessionNativePointer); + // android-add: @FastNative static native void SSL_SESSION_free(long sslSessionNativePointer); + // android-add: @FastNative static native byte[] i2d_SSL_SESSION(long sslSessionNativePointer); + // android-add: @FastNative static native long d2i_SSL_SESSION(byte[] data) throws IOException; /** - * A collection of callbacks from the native OpenSSL code that are - * related to the SSL handshake initiated by SSL_do_handshake. + * A collection of callbacks from the native OpenSSL code that are related to the SSL handshake + * initiated by SSL_do_handshake. */ interface SSLHandshakeCallbacks { /** @@ -1452,7 +1719,6 @@ interface SSLHandshakeCallbacks { * * @param certificateChain chain of X.509 certificates in their encoded form * @param authMethod auth algorithm name - * * @throws CertificateException if the certificate is untrusted */ @SuppressWarnings("unused") @@ -1473,9 +1739,9 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, throws CertificateEncodingException, SSLException; /** - * Called when acting as a server during ClientHello processing before a decision - * to resume a session is made. This allows the selection of the correct server - * certificate based on things like Server Name Indication (SNI). + * Called when acting as a server during ClientHello processing before a decision to resume + * a session is made. This allows the selection of the correct server certificate based on + * things like Server Name Indication (SNI). * * @throws IOException if there was an error during certificate selection. */ @@ -1487,13 +1753,12 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, * exchange. * * @param identityHint PSK identity hint provided by the server or {@code null} if no hint - * provided. + * provided. * @param identity buffer to be populated with PSK identity (NULL-terminated modified UTF-8) - * by this method. This identity will be provided to the server. + * by this method. This identity will be provided to the server. * @param key buffer to be populated with key material by this method. - * * @return number of bytes this method stored in the {@code key} buffer or {@code 0} if an - * error occurred in which case the handshake will be aborted. + * error occurred in which case the handshake will be aborted. */ int clientPSKKeyRequested(String identityHint, byte[] identity, byte[] key); @@ -1501,33 +1766,30 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, * Gets the key to be used in server mode for this connection in Pre-Shared Key (PSK) key * exchange. * - * @param identityHint PSK identity hint provided by this server to the client or - * {@code null} if no hint was provided. + * @param identityHint PSK identity hint provided by this server to the client or {@code + * null} if no hint was provided. * @param identity PSK identity provided by the client. * @param key buffer to be populated with key material by this method. - * * @return number of bytes this method stored in the {@code key} buffer or {@code 0} if an - * error occurred in which case the handshake will be aborted. + * error occurred in which case the handshake will be aborted. */ int serverPSKKeyRequested(String identityHint, String identity, byte[] key); - /** - * Called when SSL state changes. This could be handshake completion. - */ + /** Called when SSL state changes. This could be handshake completion. */ @SuppressWarnings("unused") void onSSLStateChange(int type, int val); /** - * Called when a new session has been established and may be added to the session cache. - * The callee is responsible for incrementing the reference count on the returned session. + * Called when a new session has been established and may be added to the session cache. The + * callee is responsible for incrementing the reference count on the returned session. */ @SuppressWarnings("unused") void onNewSessionEstablished(long sslSessionNativePtr); /** - * Called for servers where TLS < 1.3 (TLS 1.3 uses session tickets rather than - * application session caches). + * Called for servers where TLS < 1.3 (TLS 1.3 uses session tickets rather than application + * session caches). * - *

Looks up the session by ID in the application's session cache. If a valid session - * is returned, this callback is responsible for incrementing the reference count (and any + *

Looks up the session by ID in the application's session cache. If a valid session is + * returned, this callback is responsible for incrementing the reference count (and any * required synchronization). * * @param id the ID of the session to find. @@ -1537,7 +1799,7 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, /** * Called when acting as a server, the socket has an {@link - * ApplicationProtocolSelectorAdapter} associated with it, and the application protocol + * ApplicationProtocolSelectorAdapter} associated with it, and the application protocol * needs to be selected. * * @param applicationProtocols list of application protocols in length-prefix format @@ -1546,10 +1808,13 @@ void clientCertificateRequested(byte[] keyTypes, int[] signatureAlgs, @SuppressWarnings("unused") int selectApplicationProtocol(byte[] applicationProtocols); } + // android-add: @FastNative static native String SSL_CIPHER_get_kx_name(long cipherAddress); + // android-add: @FastNative static native String[] get_cipher_names(String selection); + // android-add: @FastNative public static native byte[] get_ocsp_single_extension(byte[] ocspResponse, String oid, long x509Ref, OpenSSLX509Certificate holder, @@ -1557,59 +1822,67 @@ public static native byte[] get_ocsp_single_extension(byte[] ocspResponse, Strin OpenSSLX509Certificate holder2); /** - * Returns the starting address of the memory region referenced by the provided direct - * {@link Buffer} or {@code 0} if the provided buffer is not direct or if such access to direct - * buffers is not supported by the platform. + * Returns the starting address of the memory region referenced by the provided direct {@link + * Buffer} or {@code 0} if the provided buffer is not direct or if such access to direct buffers + * is not supported by the platform. * *

NOTE: This method ignores the buffer's current {@code position}. */ + // android-add: @FastNative static native long getDirectBufferAddress(Buffer buf); + // android-add: @FastNative static native long SSL_BIO_new(long ssl, NativeSsl ssl_holder) throws SSLException; + // android-add: @FastNative static native int SSL_get_error(long ssl, NativeSsl ssl_holder, int ret); + // android-add: @CriticalNative static native void SSL_clear_error(); + // android-add: @FastNative static native int SSL_pending_readable_bytes(long ssl, NativeSsl ssl_holder); + // android-add: @FastNative static native int SSL_pending_written_bytes_in_BIO(long bio); - /** - * Returns the maximum overhead, in bytes, of sealing a record with SSL. - */ + /** Returns the maximum overhead, in bytes, of sealing a record with SSL. */ + // android-add: @FastNative static native int SSL_max_seal_overhead(long ssl, NativeSsl ssl_holder); /** * Enables ALPN for this TLS endpoint and sets the list of supported ALPN protocols in * wire-format (length-prefixed 8-bit strings). */ + // android-add: @FastNative static native void setApplicationProtocols(long ssl, NativeSsl ssl_holder, boolean client, byte[] protocols) throws IOException; /** * Called for a server endpoint only. Enables ALPN and indicates that the {@link - * SSLHandshakeCallbacks#selectApplicationProtocol} will be called to select the - * correct protocol during a handshake. Calling this method overrides - * {@link #setApplicationProtocols(long, NativeSsl, boolean, byte[])}. + * SSLHandshakeCallbacks#selectApplicationProtocol} will be called to select the correct + * protocol during a handshake. Calling this method overrides {@link + * #setApplicationProtocols(long, NativeSsl, boolean, byte[])}. */ + // android-add: @FastNative static native void setHasApplicationProtocolSelector(long ssl, NativeSsl ssl_holder, boolean hasSelector) throws IOException; /** - * Returns the selected ALPN protocol. If the server did not select a - * protocol, {@code null} will be returned. + * Returns the selected ALPN protocol. If the server did not select a protocol, {@code null} + * will be returned. */ + // android-add: @FastNative static native byte[] getApplicationProtocol(long ssl, NativeSsl ssl_holder); /** * Variant of the {@link #SSL_do_handshake} used by {@link ConscryptEngine}. This differs - * slightly from the raw BoringSSL API in that it returns the SSL error code from the - * operation, rather than the return value from {@code SSL_do_handshake}. This is done in - * order to allow to properly handle SSL errors and propagate useful exceptions. + * slightly from the raw BoringSSL API in that it returns the SSL error code from the operation, + * rather than the return value from {@code SSL_do_handshake}. This is done in order to allow to + * properly handle SSL errors and propagate useful exceptions. * * @return Returns the SSL error code for the operation when the error was {@code - * SSL_ERROR_NONE}, {@code SSL_ERROR_WANT_READ}, or {@code SSL_ERROR_WANT_WRITE}. + * SSL_ERROR_NONE}, {@code SSL_ERROR_WANT_READ}, or {@code SSL_ERROR_WANT_WRITE}. * @throws IOException when the error code is anything except those returned by this method. */ static native int ENGINE_SSL_do_handshake(long ssl, NativeSsl ssl_holder, @@ -1619,14 +1892,13 @@ static native int ENGINE_SSL_do_handshake(long ssl, NativeSsl ssl_holder, * Variant of the {@link #SSL_read} for a direct {@link java.nio.ByteBuffer} used by {@link * ConscryptEngine}. * - * @return if positive, represents the number of bytes read into the given buffer. - * Returns {@code -SSL_ERROR_WANT_READ} if more data is needed. Returns - * {@code -SSL_ERROR_WANT_WRITE} if data needs to be written out to flush the BIO. - * + * @return if positive, represents the number of bytes read into the given buffer. Returns + * {@code -SSL_ERROR_WANT_READ} if more data is needed. Returns {@code + * -SSL_ERROR_WANT_WRITE} if data needs to be written out to flush the BIO. * @throws java.io.InterruptedIOException if the read was interrupted. * @throws java.io.EOFException if the end of stream has been reached. * @throws CertificateException if the application's certificate verification callback failed. - * Only occurs during handshake processing. + * Only occurs during handshake processing. * @throws SSLException if any other error occurs. */ static native int ENGINE_SSL_read_direct(long ssl, NativeSsl ssl_holder, long address, @@ -1674,9 +1946,8 @@ static native void ENGINE_SSL_shutdown(long ssl, NativeSsl ssl_holder, static native byte[] Scrypt_generate_key(byte[] password, byte[] salt, int n, int r, int p, int key_len); - /** - * Return {@code true} if BoringSSL has been built in FIPS mode. - */ + /** Return {@code true} if BoringSSL has been built in FIPS mode. */ + // android-add: @CriticalNative static native boolean usesBoringSsl_FIPS_mode(); /* ECH */ @@ -1708,11 +1979,22 @@ static native boolean SSL_CTX_ech_enable_server(long sslCtx, AbstractSessionCont /** * Used for testing only. */ + // android-add: @FastNative static native int BIO_read(long bioRef, byte[] buffer) throws IOException; + + // android-add: @FastNative static native void BIO_write(long bioRef, byte[] buffer, int offset, int length) throws IOException, IndexOutOfBoundsException; + + // android-add: @FastNative static native long SSL_clear_mode(long ssl, NativeSsl ssl_holder, long mode); + + // android-add: @FastNative static native long SSL_get_mode(long ssl, NativeSsl ssl_holder); + + // android-add: @FastNative static native long SSL_get_options(long ssl, NativeSsl ssl_holder); + + // android-add: @FastNative static native long SSL_get1_session(long ssl, NativeSsl ssl_holder); } diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java index 31976fbc4..29e14c799 100644 --- a/common/src/main/java/org/conscrypt/NativeSsl.java +++ b/common/src/main/java/org/conscrypt/NativeSsl.java @@ -28,6 +28,7 @@ import org.conscrypt.NativeCrypto.SSLHandshakeCallbacks; import org.conscrypt.SSLParametersImpl.AliasChooser; import org.conscrypt.SSLParametersImpl.PSKCallbacks; +import org.conscrypt.metrics.TlsEncryptedClientHelloHandshake; import java.io.FileDescriptor; import java.io.IOException; @@ -63,6 +64,8 @@ final class NativeSsl { private X509Certificate[] localCertificates; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private volatile long ssl; + private final TlsEncryptedClientHelloHandshake.Builder echHandshakeBuilder = + new TlsEncryptedClientHelloHandshake.Builder(); private NativeSsl(long ssl, SSLParametersImpl parameters, SSLHandshakeCallbacks handshakeCallbacks, AliasChooser aliasChooser, @@ -275,10 +278,6 @@ String getRequestedServerName() { return NativeCrypto.SSL_get_servername(ssl, this); } - byte[] getTlsChannelId() throws SSLException { - return NativeCrypto.SSL_get_tls_channel_id(ssl, this); - } - private static int toBoringSslGroup(String javaNamedGroup) { switch (javaNamedGroup) { case "X25519": @@ -295,8 +294,6 @@ private static int toBoringSslGroup(String javaNamedGroup) { return NativeConstants.NID_secp521r1; case "X25519MLKEM768": return NativeConstants.NID_X25519MLKEM768; - case "X25519Kyber768Draft00": - return NativeConstants.NID_X25519Kyber768Draft00; case "MLKEM1024": return NativeConstants.NID_ML_KEM_1024; default: @@ -344,7 +341,7 @@ private static void setDefaultNamedGroups(long ssl, NativeSsl sslHolder) { NativeCrypto.SSL_set1_groups(ssl, sslHolder, new int[0]); } - void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOException { + void initialize(String hostname) throws IOException { boolean enableSessionCreation = parameters.getEnableSessionCreation(); if (!enableSessionCreation) { NativeCrypto.SSL_set_session_creation_enabled(ssl, this, false); @@ -363,6 +360,7 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept if (parameters.isCTVerificationEnabled(hostname)) { NativeCrypto.SSL_enable_signed_cert_timestamps(ssl, this); } + enableEchBasedOnPolicy(hostname); } else { NativeCrypto.SSL_set_accept_state(ssl, this); @@ -449,7 +447,6 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept if (!parameters.isSpake()) { setCertificateValidation(); } - setTlsChannelId(channelIdPrivateKey); } void configureServerCertificate() throws IOException { @@ -490,20 +487,6 @@ private Set getCipherKeyTypes() { return keyTypes; } - // TODO(nathanmittler): Remove once after we switch to the engine socket. - void doHandshake(FileDescriptor fd, int timeoutMillis) - throws CertificateException, IOException { - lock.readLock().lock(); - try { - if (isClosed() || fd == null || !fd.valid()) { - throw new SocketException("Socket is closed"); - } - NativeCrypto.SSL_do_handshake(ssl, this, fd, handshakeCallbacks, timeoutMillis); - } finally { - lock.readLock().unlock(); - } - } - int doHandshake() throws IOException { lock.readLock().lock(); try { @@ -513,36 +496,6 @@ int doHandshake() throws IOException { } } - // TODO(nathanmittler): Remove once after we switch to the engine socket. - int read(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis) - throws IOException { - lock.readLock().lock(); - try { - if (isClosed() || fd == null || !fd.valid()) { - throw new SocketException("Socket is closed"); - } - return NativeCrypto.SSL_read(ssl, this, fd, handshakeCallbacks, buf, offset, len, - timeoutMillis); - } finally { - lock.readLock().unlock(); - } - } - - // TODO(nathanmittler): Remove once after we switch to the engine socket. - void write(FileDescriptor fd, byte[] buf, int offset, int len, int timeoutMillis) - throws IOException { - lock.readLock().lock(); - try { - if (isClosed() || fd == null || !fd.valid()) { - throw new SocketException("Socket is closed"); - } - NativeCrypto.SSL_write(ssl, this, fd, handshakeCallbacks, buf, offset, len, - timeoutMillis); - } finally { - lock.readLock().unlock(); - } - } - @SuppressWarnings("deprecation") // PSKKeyManager is deprecated, but in our own package private void enablePSKKeyManagerIfRequested() throws SSLException { // Enable Pre-Shared Key (PSK) key exchange if requested @@ -567,21 +520,42 @@ private void enablePSKKeyManagerIfRequested() throws SSLException { } } - private void setTlsChannelId(OpenSSLKey channelIdPrivateKey) throws SSLException { - if (!parameters.channelIdEnabled) { + private void enableEchBasedOnPolicy(String hostname) throws SSLException { + EchOptions opts = parameters.getEchOptions(hostname); + echHandshakeBuilder.setEchOptions(opts).setHostname(hostname).setPolicy( + parameters.getPolicy()); + + if (opts == null) { return; } - if (parameters.getUseClientMode()) { - // Client-side TLS Channel ID - if (channelIdPrivateKey == null) { - throw new SSLHandshakeException("Invalid TLS channel ID key specified"); + byte[] configList = opts.getConfigList(); + if (configList != null) { + try { + NativeCrypto.SSL_set1_ech_config_list(ssl, this, configList); + } catch (SSLException e) { + echHandshakeBuilder.setFailureReason( + TlsEncryptedClientHelloHandshake.FailureReason.INVALID_CONFIG); + // The platform may provide a more specialized exception type for this error. + throw Platform.wrapInvalidEchDataException(e); } - NativeCrypto.SSL_set1_tls_channel_id(ssl, this, channelIdPrivateKey.getNativeRef()); - } else { - // Server-side TLS Channel ID - NativeCrypto.SSL_enable_tls_channel_id(ssl, this); } + + if (opts.isGreaseEnabled()) { + NativeCrypto.SSL_set_enable_ech_grease(ssl, this, /* enable= */ true); + } + } + + TlsEncryptedClientHelloHandshake.Builder getEchHandshakeMetricsBuilder() { + return echHandshakeBuilder; + } + + String getEchNameOverride() { + return NativeCrypto.SSL_get0_ech_name_override(ssl, this); + } + + byte[] getEchRetryConfigs() { + return NativeCrypto.SSL_get0_ech_retry_configs(ssl, this); } private void setCertificateValidation() throws SSLException { @@ -623,11 +597,6 @@ void interrupt() { NativeCrypto.SSL_interrupt(ssl, this); } - // TODO(nathanmittler): Remove once after we switch to the engine socket. - void shutdown(FileDescriptor fd) throws IOException { - NativeCrypto.SSL_shutdown(ssl, this, fd, handshakeCallbacks); - } - void shutdown() throws IOException { lock.readLock().lock(); try { diff --git a/common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java b/common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java new file mode 100644 index 000000000..499c5eda0 --- /dev/null +++ b/common/src/main/java/org/conscrypt/NetworkSecurityPolicy.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2025 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 org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * A policy provided by the platform to decide on the behaviour of TrustManagerImpl. + * + * See the platform-specific implementations in PlatformNetworkSecurityPolicy. + */ +@Internal +public interface NetworkSecurityPolicy { + boolean isCertificateTransparencyVerificationRequired(String hostname); + + CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname); + + DomainEncryptionMode getDomainEncryptionMode(String hostname); +} diff --git a/common/src/main/java/org/conscrypt/OidData.java b/common/src/main/java/org/conscrypt/OidData.java index 2e3010ddf..63a567931 100644 --- a/common/src/main/java/org/conscrypt/OidData.java +++ b/common/src/main/java/org/conscrypt/OidData.java @@ -54,6 +54,14 @@ private OidData() {} OID_TO_NAME_MAP.put("1.2.840.10045.4.3.2", "SHA256withECDSA"); OID_TO_NAME_MAP.put("1.2.840.10045.4.3.3", "SHA384withECDSA"); OID_TO_NAME_MAP.put("1.2.840.10045.4.3.4", "SHA512withECDSA"); + + // RFC 8410 + OID_TO_NAME_MAP.put("1.3.101.112", "Ed25519"); + + // RFC 9881 + OID_TO_NAME_MAP.put("2.16.840.1.101.3.4.3.17", "ML-DSA-44"); + OID_TO_NAME_MAP.put("2.16.840.1.101.3.4.3.18", "ML-DSA-65"); + OID_TO_NAME_MAP.put("2.16.840.1.101.3.4.3.19", "ML-DSA-87"); } public static String oidToAlgorithmName(String oid) { diff --git a/common/src/main/java/org/conscrypt/OkHostnameVerifier.java b/common/src/main/java/org/conscrypt/OkHostnameVerifier.java index 9b3979ec3..2155d565b 100644 --- a/common/src/main/java/org/conscrypt/OkHostnameVerifier.java +++ b/common/src/main/java/org/conscrypt/OkHostnameVerifier.java @@ -99,7 +99,7 @@ static boolean verifyAsIpAddress(String host) { private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) { List altNames = getSubjectAltNames(certificate, ALT_IPA_NAME); for (int i = 0, size = altNames.size(); i < size; i++) { - if (ipAddress.equalsIgnoreCase(altNames.get(i))) { + if (AddressUtils.asciiEqualsIgnoreCase(ipAddress, altNames.get(i))) { return true; } } diff --git a/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java index 313d46957..07356096e 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLECPrivateKey.java @@ -16,7 +16,6 @@ package org.conscrypt; -import java.security.MessageDigest; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; import java.io.IOException; @@ -26,6 +25,7 @@ import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.PrivateKey; import java.security.PublicKey; import java.security.interfaces.ECKey; diff --git a/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java b/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java index ebbb624d6..5873627c0 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java +++ b/common/src/main/java/org/conscrypt/OpenSSLEvpCipher.java @@ -158,67 +158,86 @@ int doFinalInternal(byte[] output, int outputOffset, int maximumLen) } outputOffset += writtenBytes; - reset(); - return outputOffset - initialOutputOffset; } @Override protected byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) throws IllegalBlockSizeException, BadPaddingException { - final int maximumLen = getOutputSizeForFinal(inputLen); - /* Assume that we'll output exactly on a byte boundary. */ - final byte[] output = new byte[maximumLen]; + try { + final int maximumLen = getOutputSizeForFinal(inputLen); + /* Assume that we'll output exactly on a byte boundary. */ + final byte[] output = new byte[maximumLen]; + + int bytesWritten; + if (inputLen > 0) { + try { + bytesWritten = updateInternal(input, inputOffset, inputLen, output, 0, maximumLen); + } catch (ShortBufferException e) { + /* This should not happen since we sized our own buffer. */ + throw new RuntimeException("our calculated buffer was too small", e); + } + } else { + bytesWritten = 0; + } - int bytesWritten; - if (inputLen > 0) { try { - bytesWritten = updateInternal(input, inputOffset, inputLen, output, 0, maximumLen); + bytesWritten += doFinalInternal(output, bytesWritten, maximumLen - bytesWritten); } catch (ShortBufferException e) { /* This should not happen since we sized our own buffer. */ throw new RuntimeException("our calculated buffer was too small", e); } - } else { - bytesWritten = 0; - } - try { - bytesWritten += doFinalInternal(output, bytesWritten, maximumLen - bytesWritten); - } catch (ShortBufferException e) { - /* This should not happen since we sized our own buffer. */ - throw new RuntimeException("our calculated buffer was too small", e); - } - - if (bytesWritten == output.length) { - return output; - } else if (bytesWritten == 0) { - return EmptyArray.BYTE; - } else { - return Arrays.copyOfRange(output, 0, bytesWritten); + if (bytesWritten == output.length) { + return output; + } else if (bytesWritten == 0) { + return EmptyArray.BYTE; + } else { + return Arrays.copyOfRange(output, 0, bytesWritten); + } + } finally { + // Always reset the context after a final is called. Note that this + // is only required on _success_ by javax.crypto.Cipher's + // documentation and otherwise optional, but it is less error-prone + // to always reset. + reset(); } } @Override protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, - int outputOffset) - throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { - if (output == null) { - throw new NullPointerException("output == null"); - } + int outputOffset) throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + // Assume we will need to reset unless we hit a ShortBufferException + boolean resetNeeded = true; + try { + if (output == null) { + throw new NullPointerException("output == null"); + } - int maximumLen = getOutputSizeForFinal(inputLen); + int maximumLen = getOutputSizeForFinal(inputLen); - final int bytesWritten; - if (inputLen > 0) { - bytesWritten = - updateInternal(input, inputOffset, inputLen, output, outputOffset, maximumLen); - outputOffset += bytesWritten; - maximumLen -= bytesWritten; - } else { - bytesWritten = 0; - } + final int bytesWritten; + if (inputLen > 0) { + bytesWritten = updateInternal(input, inputOffset, inputLen, output, outputOffset, + maximumLen); + outputOffset += bytesWritten; + maximumLen -= bytesWritten; + } else { + bytesWritten = 0; + } - return bytesWritten + doFinalInternal(output, outputOffset, maximumLen); + return bytesWritten + doFinalInternal(output, outputOffset, maximumLen); + } catch (ShortBufferException e) { + // State preserved so the caller can retry with a larger buffer + resetNeeded = false; + throw e; + } finally { + // Reset on success or FATAL errors (like BadPaddingException), but skip on short buffer + if (resetNeeded) { + reset(); + } + } } @Override diff --git a/common/src/main/java/org/conscrypt/OpenSSLProvider.java b/common/src/main/java/org/conscrypt/OpenSSLProvider.java index d7d1aea16..c0d94ac0a 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLProvider.java +++ b/common/src/main/java/org/conscrypt/OpenSSLProvider.java @@ -45,6 +45,35 @@ public final class OpenSSLProvider extends Provider { private static final String STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME = "java.security.interfaces.RSAPublicKey"; + private static final String OPENSSL_CONTEXT_IMPL_CLASS_NAME = PREFIX + "OpenSSLContextImpl"; + private static final String TLS1_SSL_CONTEXT_SUFFIX = "$TLSv1"; + private static final String TLS11_SSL_CONTEXT_SUFFIX = "$TLSv11"; + private static final String TLS12_SSL_CONTEXT_SUFFIX = "$TLSv12"; + private static final String TLS13_SSL_CONTEXT_SUFFIX = "$TLSv13"; + + private static final String KEY_HOLDER_CLASS_NAME = PREFIX + "OpenSSLKeyHolder"; + + private static final String RSA_KEY_CLASSES = PREFIX + "OpenSSLRSAPrivateKey" + + "|" + STANDARD_RSA_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + PREFIX + + "OpenSSLRSAPublicKey" + + "|" + STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME; + + private static final String SIGNATURE_KEY_CLASSES = KEY_HOLDER_CLASS_NAME + "|" + + STANDARD_RSA_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + + STANDARD_EC_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + + STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME; + + private static final String ECDH_KEY_CLASSES = + KEY_HOLDER_CLASS_NAME + "|" + STANDARD_EC_PRIVATE_KEY_INTERFACE_CLASS_NAME; + + private static final String XDH_KEY_CLASSES = KEY_HOLDER_CLASS_NAME + "|" + + STANDARD_XEC_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + PREFIX + + "OpenSSLX25519PrivateKey"; + + private static final String RAW_FORMAT = "RAW"; + private static final String PKCS8_FORMAT = "PKCS#8"; + private static final String SIGNATURE_KEY_FORMATS = "PKCS#8|X.509"; + public OpenSSLProvider() { this(Platform.getDefaultProviderName()); } @@ -74,28 +103,26 @@ public OpenSSLProvider(String providerName) { Platform.setup(deprecatedTlsV1, enabledTlsV1); /* === SSL Contexts === */ - String classOpenSSLContextImpl = PREFIX + "OpenSSLContextImpl"; - String tls12SSLContextSuffix = "$TLSv12"; - String tls13SSLContextSuffix = "$TLSv13"; String defaultSSLContextSuffix; switch (defaultTlsProtocol) { case "TLSv1.2": - defaultSSLContextSuffix = tls12SSLContextSuffix; + defaultSSLContextSuffix = TLS12_SSL_CONTEXT_SUFFIX; break; case "TLSv1.3": - defaultSSLContextSuffix = tls13SSLContextSuffix; + defaultSSLContextSuffix = TLS13_SSL_CONTEXT_SUFFIX; break; default: throw new IllegalArgumentException("Choice of default protocol is unsupported: " + defaultTlsProtocol); } // Keep SSL as an alias to TLS - put("SSLContext.SSL", classOpenSSLContextImpl + defaultSSLContextSuffix); - put("SSLContext.TLS", classOpenSSLContextImpl + defaultSSLContextSuffix); - put("SSLContext.TLSv1", classOpenSSLContextImpl + "$TLSv1"); - put("SSLContext.TLSv1.1", classOpenSSLContextImpl + "$TLSv11"); - put("SSLContext.TLSv1.2", classOpenSSLContextImpl + tls12SSLContextSuffix); - put("SSLContext.TLSv1.3", classOpenSSLContextImpl + tls13SSLContextSuffix); + String defaultSslContext = OPENSSL_CONTEXT_IMPL_CLASS_NAME + defaultSSLContextSuffix; + put("SSLContext.SSL", defaultSslContext); + put("SSLContext.TLS", defaultSslContext); + put("SSLContext.TLSv1", OPENSSL_CONTEXT_IMPL_CLASS_NAME + TLS1_SSL_CONTEXT_SUFFIX); + put("SSLContext.TLSv1.1", OPENSSL_CONTEXT_IMPL_CLASS_NAME + TLS11_SSL_CONTEXT_SUFFIX); + put("SSLContext.TLSv1.2", OPENSSL_CONTEXT_IMPL_CLASS_NAME + TLS12_SSL_CONTEXT_SUFFIX); + put("SSLContext.TLSv1.3", OPENSSL_CONTEXT_IMPL_CLASS_NAME + TLS13_SSL_CONTEXT_SUFFIX); put("SSLContext.Default", PREFIX + "DefaultSSLContextImpl" + defaultSSLContextSuffix); if (includeTrustManager) { @@ -225,6 +252,52 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.KeyPairGenerator.2.16.840.1.101.3.4.3.19", "ML-DSA-87"); put("Alg.Alias.KeyPairGenerator.OID.2.16.840.1.101.3.4.3.19", "ML-DSA-87"); + put("KeyPairGenerator.MLDSA44-RSA2048-PSS-SHA256", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa44Rsa2048PssSha256"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.37", "MLDSA44-RSA2048-PSS-SHA256"); + put("KeyPairGenerator.MLDSA44-RSA2048-PKCS15-SHA256", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa44Rsa2048Pkcs15Sha256"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.38", "MLDSA44-RSA2048-PKCS15-SHA256"); + put("KeyPairGenerator.MLDSA44-Ed25519-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa44Ed25519Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.39", "MLDSA44-Ed25519-SHA512"); + put("KeyPairGenerator.MLDSA44-ECDSA-P256-SHA256", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa44EcdsaP256Sha256"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.40", "MLDSA44-ECDSA-P256-SHA256"); + put("KeyPairGenerator.MLDSA65-Ed25519-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa65Ed25519Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.48", "MLDSA65-Ed25519-SHA512"); + put("KeyPairGenerator.MLDSA65-RSA3072-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa65Rsa3072PssSha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.41", "MLDSA65-RSA3072-PSS-SHA512"); + put("KeyPairGenerator.MLDSA65-RSA3072-PKCS15-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa65Rsa3072Pkcs15Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.42", "MLDSA65-RSA3072-PKCS15-SHA512"); + put("KeyPairGenerator.MLDSA65-RSA4096-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa65Rsa4096PssSha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.43", "MLDSA65-RSA4096-PSS-SHA512"); + put("KeyPairGenerator.MLDSA65-RSA4096-PKCS15-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa65Rsa4096Pkcs15Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.44", "MLDSA65-RSA4096-PKCS15-SHA512"); + put("KeyPairGenerator.MLDSA65-ECDSA-P256-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa65EcdsaP256Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.45", "MLDSA65-ECDSA-P256-SHA512"); + put("KeyPairGenerator.MLDSA65-ECDSA-P384-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa65EcdsaP384Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.46", "MLDSA65-ECDSA-P384-SHA512"); + put("KeyPairGenerator.MLDSA87-ECDSA-P384-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa87EcdsaP384Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.49", "MLDSA87-ECDSA-P384-SHA512"); + put("KeyPairGenerator.MLDSA87-RSA3072-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa87Rsa3072PssSha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.52", "MLDSA87-RSA3072-PSS-SHA512"); + put("KeyPairGenerator.MLDSA87-RSA4096-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa87Rsa4096PssSha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.53", "MLDSA87-RSA4096-PSS-SHA512"); + put("KeyPairGenerator.MLDSA87-ECDSA-P521-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyPairGenerator$Mldsa87EcdsaP521Sha512"); + put("Alg.Alias.KeyPairGenerator.1.3.6.1.5.5.7.6.54", "MLDSA87-ECDSA-P521-SHA512"); + // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyPairGenerator.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyPairGenerator"); @@ -263,6 +336,52 @@ public OpenSSLProvider(String providerName) { put("Alg.Alias.KeyFactory.2.16.840.1.101.3.4.3.19", "ML-DSA-87"); put("Alg.Alias.KeyFactory.OID.2.16.840.1.101.3.4.3.19", "ML-DSA-87"); + put("KeyFactory.MLDSA44-RSA2048-PSS-SHA256", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa44Rsa2048PssSha256"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.37", "MLDSA44-RSA2048-PSS-SHA256"); + put("KeyFactory.MLDSA44-RSA2048-PKCS15-SHA256", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa44Rsa2048Pkcs15Sha256"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.38", "MLDSA44-RSA2048-PKCS15-SHA256"); + put("KeyFactory.MLDSA44-Ed25519-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa44Ed25519Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.39", "MLDSA44-Ed25519-SHA512"); + put("KeyFactory.MLDSA44-ECDSA-P256-SHA256", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa44EcdsaP256Sha256"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.40", "MLDSA44-ECDSA-P256-SHA256"); + put("KeyFactory.MLDSA65-RSA3072-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa65Rsa3072PssSha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.41", "MLDSA65-RSA3072-PSS-SHA512"); + put("KeyFactory.MLDSA65-RSA3072-PKCS15-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa65Rsa3072Pkcs15Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.42", "MLDSA65-RSA3072-PKCS15-SHA512"); + put("KeyFactory.MLDSA65-RSA4096-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa65Rsa4096PssSha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.43", "MLDSA65-RSA4096-PSS-SHA512"); + put("KeyFactory.MLDSA65-RSA4096-PKCS15-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa65Rsa4096Pkcs15Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.44", "MLDSA65-RSA4096-PKCS15-SHA512"); + put("KeyFactory.MLDSA65-ECDSA-P256-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa65EcdsaP256Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.45", "MLDSA65-ECDSA-P256-SHA512"); + put("KeyFactory.MLDSA65-ECDSA-P384-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa65EcdsaP384Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.46", "MLDSA65-ECDSA-P384-SHA512"); + put("KeyFactory.MLDSA65-Ed25519-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa65Ed25519Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.48", "MLDSA65-Ed25519-SHA512"); + put("KeyFactory.MLDSA87-ECDSA-P384-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa87EcdsaP384Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.49", "MLDSA87-ECDSA-P384-SHA512"); + put("KeyFactory.MLDSA87-RSA3072-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa87Rsa3072PssSha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.52", "MLDSA87-RSA3072-PSS-SHA512"); + put("KeyFactory.MLDSA87-RSA4096-PSS-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa87Rsa4096PssSha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.53", "MLDSA87-RSA4096-PSS-SHA512"); + put("KeyFactory.MLDSA87-ECDSA-P521-SHA512", + PREFIX + "OpenSslCompositeMlDsaKeyFactory$Mldsa87EcdsaP521Sha512"); + put("Alg.Alias.KeyFactory.1.3.6.1.5.5.7.6.54", "MLDSA87-ECDSA-P521-SHA512"); + // We don't support SLH-DSA, because it's not clear which algorithm to use. put("KeyFactory.SLH-DSA-SHA2-128S", PREFIX + "OpenSslSlhDsaKeyFactory"); @@ -402,6 +521,51 @@ public OpenSSLProvider(String providerName) { putSignatureImplClass("ML-DSA-87", "OpenSslSignatureMlDsa$MlDsa87"); put("Alg.Alias.Signature.2.16.840.1.101.3.4.3.19", "ML-DSA-87"); put("Alg.Alias.Signature.OID.2.16.840.1.101.3.4.3.19", "ML-DSA-87"); + putSignatureImplClass("MLDSA44-RSA2048-PSS-SHA256", + "OpenSslSignatureCompositeMlDsa$Mldsa44Rsa2048PssSha256"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.37", "MLDSA44-RSA2048-PSS-SHA256"); + putSignatureImplClass("MLDSA44-RSA2048-PKCS15-SHA256", + "OpenSslSignatureCompositeMlDsa$Mldsa44Rsa2048Pkcs15Sha256"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.38", "MLDSA44-RSA2048-PKCS15-SHA256"); + putSignatureImplClass("MLDSA44-Ed25519-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa44Ed25519Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.39", "MLDSA44-Ed25519-SHA512"); + putSignatureImplClass("MLDSA44-ECDSA-P256-SHA256", + "OpenSslSignatureCompositeMlDsa$Mldsa44EcdsaP256Sha256"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.40", "MLDSA44-ECDSA-P256-SHA256"); + putSignatureImplClass("MLDSA65-RSA3072-PSS-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa65Rsa3072PssSha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.41", "MLDSA65-RSA3072-PSS-SHA512"); + putSignatureImplClass("MLDSA65-RSA3072-PKCS15-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa65Rsa3072Pkcs15Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.42", "MLDSA65-RSA3072-PKCS15-SHA512"); + putSignatureImplClass("MLDSA65-RSA4096-PSS-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa65Rsa4096PssSha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.43", "MLDSA65-RSA4096-PSS-SHA512"); + putSignatureImplClass("MLDSA65-RSA4096-PKCS15-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa65Rsa4096Pkcs15Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.44", "MLDSA65-RSA4096-PKCS15-SHA512"); + putSignatureImplClass("MLDSA65-ECDSA-P256-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa65EcdsaP256Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.45", "MLDSA65-ECDSA-P256-SHA512"); + putSignatureImplClass("MLDSA65-ECDSA-P384-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa65EcdsaP384Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.46", "MLDSA65-ECDSA-P384-SHA512"); + putSignatureImplClass("MLDSA65-Ed25519-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa65Ed25519Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.48", "MLDSA65-Ed25519-SHA512"); + putSignatureImplClass("MLDSA87-ECDSA-P384-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa87EcdsaP384Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.49", "MLDSA87-ECDSA-P384-SHA512"); + putSignatureImplClass("MLDSA87-RSA3072-PSS-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa87Rsa3072PssSha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.52", "MLDSA87-RSA3072-PSS-SHA512"); + putSignatureImplClass("MLDSA87-RSA4096-PSS-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa87Rsa4096PssSha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.53", "MLDSA87-RSA4096-PSS-SHA512"); + putSignatureImplClass("MLDSA87-ECDSA-P521-SHA512", + "OpenSslSignatureCompositeMlDsa$Mldsa87EcdsaP521Sha512"); + put("Alg.Alias.Signature.1.3.6.1.5.5.7.6.54", "MLDSA87-ECDSA-P521-SHA512"); // We don't support SLH-DSA, because it's not clear which algorithm to use. putSignatureImplClass("SLH-DSA-SHA2-128S", "OpenSslSignatureSlhDsa"); @@ -414,6 +578,7 @@ public OpenSSLProvider(String providerName) { */ put("SecureRandom.SHA1PRNG", PREFIX + "OpenSSLRandom"); put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); + put("SecureRandom.SHA1PRNG ThreadSafe", "true"); /* === Cipher === */ putRSACipherImplClass("RSA/ECB/NoPadding", "OpenSSLCipherRSA$Raw"); @@ -635,32 +800,25 @@ private void putMacImplClass(String algorithm, String className) { // Accept only keys for which any of the following is true: // * the key is from this provider (subclass of OpenSSLKeyHolder), // * the key provides its key material in "RAW" encoding via Key.getEncoded. - String supportedKeyClasses = PREFIX + "OpenSSLKeyHolder"; - String supportedKeyFormats = "RAW"; - putImplClassWithKeyConstraints("Mac." + algorithm, PREFIX + className, supportedKeyClasses, - supportedKeyFormats); + putImplClassWithKeyConstraints("Mac." + algorithm, PREFIX + className, + KEY_HOLDER_CLASS_NAME, RAW_FORMAT); } private void putSymmetricCipherImplClass(String transformation, String className) { // Accept only keys for which any of the following is true: // * the key provides its key material in "RAW" encoding via Key.getEncoded. String supportedKeyClasses = null; // ignored -- filtered based on encoding format only - String supportedKeyFormats = "RAW"; putImplClassWithKeyConstraints("Cipher." + transformation, PREFIX + className, - supportedKeyClasses, supportedKeyFormats); + supportedKeyClasses, RAW_FORMAT); } private void putRSACipherImplClass(String transformation, String className) { // Accept only keys for which any of the following is true: // * the key is instance of OpenSSLRSAPrivateKey, RSAPrivateKey, OpenSSLRSAPublicKey, or // RSAPublicKey. - String supportedKeyClasses = PREFIX + "OpenSSLRSAPrivateKey" - + "|" + STANDARD_RSA_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + PREFIX - + "OpenSSLRSAPublicKey" - + "|" + STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME; String supportedKeyFormats = null; // ignored -- filtered based on class only putImplClassWithKeyConstraints("Cipher." + transformation, PREFIX + className, - supportedKeyClasses, supportedKeyFormats); + RSA_KEY_CLASSES, supportedKeyFormats); } private void putSignatureImplClass(String algorithm, String className) { @@ -671,26 +829,17 @@ private void putSignatureImplClass(String algorithm, String className) { // some reason this provider's Signature implementation does not unconditionally accept // transparent public keys -- it only accepts them if they provide their key material in // encoded form (see above). - String supportedKeyClasses = PREFIX + "OpenSSLKeyHolder" - + "|" + STANDARD_RSA_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" - + STANDARD_EC_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" - + STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME; - String supportedKeyFormats = "PKCS#8|X.509"; putImplClassWithKeyConstraints("Signature." + algorithm, PREFIX + className, - supportedKeyClasses, supportedKeyFormats); + SIGNATURE_KEY_CLASSES, SIGNATURE_KEY_FORMATS); } private void putRAWRSASignatureImplClass(String className) { // Accept only keys for which any of the following is true: // * the key is instance of OpenSSLRSAPrivateKey, RSAPrivateKey, OpenSSLRSAPublicKey, or // RSAPublicKey. - String supportedKeyClasses = PREFIX + "OpenSSLRSAPrivateKey" - + "|" + STANDARD_RSA_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + PREFIX - + "OpenSSLRSAPublicKey" - + "|" + STANDARD_RSA_PUBLIC_KEY_INTERFACE_CLASS_NAME; String supportedKeyFormats = null; // ignored -- filtered based on class only - putImplClassWithKeyConstraints("Signature.NONEwithRSA", PREFIX + className, - supportedKeyClasses, supportedKeyFormats); + putImplClassWithKeyConstraints("Signature.NONEwithRSA", PREFIX + className, RSA_KEY_CLASSES, + supportedKeyFormats); } private void putECDHKeyAgreementImplClass(String className) { @@ -698,11 +847,8 @@ private void putECDHKeyAgreementImplClass(String className) { // * the key is from this provider (subclass of OpenSSLKeyHolder), // * the key provides its key material in "PKCS#8" encoding via Key.getEncoded. // * the key is a transparent EC private key (subclass of ECPrivateKey). - String supportedKeyClasses = PREFIX + "OpenSSLKeyHolder" - + "|" + STANDARD_EC_PRIVATE_KEY_INTERFACE_CLASS_NAME; - String supportedKeyFormats = "PKCS#8"; - putImplClassWithKeyConstraints("KeyAgreement.ECDH", PREFIX + className, supportedKeyClasses, - supportedKeyFormats); + putImplClassWithKeyConstraints("KeyAgreement.ECDH", PREFIX + className, ECDH_KEY_CLASSES, + PKCS8_FORMAT); } private void putXDHKeyAgreementImplClass(String className) { @@ -710,12 +856,8 @@ private void putXDHKeyAgreementImplClass(String className) { // * the key is from this provider (subclass of OpenSSLKeyHolder), // * the key provides its key material in "PKCS#8" encoding via Key.getEncoded. // * the key is a transparent XEC private key (subclass of XECPrivateKey). - String supportedKeyClasses = PREFIX + "OpenSSLKeyHolder" - + "|" + STANDARD_XEC_PRIVATE_KEY_INTERFACE_CLASS_NAME + "|" + PREFIX - + "OpenSSLX25519PrivateKey"; - String supportedKeyFormats = "PKCS#8"; - putImplClassWithKeyConstraints("KeyAgreement.XDH", PREFIX + className, supportedKeyClasses, - supportedKeyFormats); + putImplClassWithKeyConstraints("KeyAgreement.XDH", PREFIX + className, XDH_KEY_CLASSES, + PKCS8_FORMAT); put("Alg.Alias.KeyAgreement.X25519", "XDH"); } diff --git a/common/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java b/common/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java index 2a4f818e1..b3cd40f3f 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLRSAPrivateCrtKey.java @@ -223,7 +223,8 @@ public boolean equals(Object o) { return equalsBigInteger(getModulus(), other.getModulus()) && equalsBigInteger(publicExponent, other.getPublicExponent()) && equalsBigInteger(getPrivateExponent(), other.getPrivateExponent()) - && equalsBigInteger(primeP, other.getPrimeP()) && equalsBigInteger(primeQ, other.getPrimeQ()) + && equalsBigInteger(primeP, other.getPrimeP()) + && equalsBigInteger(primeQ, other.getPrimeQ()) && equalsBigInteger(primeExponentP, other.getPrimeExponentP()) && equalsBigInteger(primeExponentQ, other.getPrimeExponentQ()) && equalsBigInteger(crtCoefficient, other.getCrtCoefficient()); diff --git a/common/src/main/java/org/conscrypt/OpenSSLSignature.java b/common/src/main/java/org/conscrypt/OpenSSLSignature.java index 04d40c529..689447efe 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLSignature.java +++ b/common/src/main/java/org/conscrypt/OpenSSLSignature.java @@ -385,13 +385,14 @@ protected final void engineSetParameter(AlgorithmParameterSpec params) if (specContentDigest == null) { throw new InvalidAlgorithmParameterException( "Unsupported content digest algorithm: " + spec.getDigestAlgorithm()); - } else if (!contentDigestAlgorithm.equalsIgnoreCase(specContentDigest)) { + } else if (!AddressUtils.asciiEqualsIgnoreCase(contentDigestAlgorithm, + specContentDigest)) { throw new InvalidAlgorithmParameterException( "Changing content digest algorithm not supported"); } String specMgfAlgorithm = spec.getMGFAlgorithm(); - if (!EvpMdRef.MGF1_ALGORITHM_NAME.equalsIgnoreCase(specMgfAlgorithm) + if (!AddressUtils.asciiEqualsIgnoreCase(specMgfAlgorithm, EvpMdRef.MGF1_ALGORITHM_NAME) && !EvpMdRef.MGF1_OID.equals(specMgfAlgorithm)) { throw new InvalidAlgorithmParameterException( "Unsupported MGF algorithm: " + specMgfAlgorithm + ". Only " diff --git a/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java b/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java index 58f992836..7ccfdd243 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java +++ b/common/src/main/java/org/conscrypt/OpenSSLSocketFactoryImpl.java @@ -17,7 +17,6 @@ package org.conscrypt; import static org.conscrypt.Platform.createEngineSocket; -import static org.conscrypt.Platform.createFileDescriptorSocket; import java.io.IOException; import java.net.InetAddress; @@ -36,11 +35,8 @@ * core {@code ProviderInstallerImpl} */ final class OpenSSLSocketFactoryImpl extends SSLSocketFactory { - private static boolean useEngineSocketByDefault = SSLUtils.USE_ENGINE_SOCKET_BY_DEFAULT; - private final SSLParametersImpl sslParameters; private final IOException instantiationException; - private boolean useEngineSocket = useEngineSocketByDefault; OpenSSLSocketFactoryImpl() { SSLParametersImpl sslParametersLocal = null; @@ -63,20 +59,14 @@ final class OpenSSLSocketFactoryImpl extends SSLSocketFactory { * Configures the default socket type to be created for the default and all new instances. */ static void setUseEngineSocketByDefault(boolean useEngineSocket) { - useEngineSocketByDefault = useEngineSocket; - // The default SSLSocketFactory may already have been created, so also change its setting. - SocketFactory defaultFactory = SSLSocketFactory.getDefault(); - if (defaultFactory instanceof OpenSSLSocketFactoryImpl) { - ((OpenSSLSocketFactoryImpl) defaultFactory).setUseEngineSocket(useEngineSocket); - } + // No-op. Engine sockets are always used. } /** - * Configures the socket to be created for this instance. If not called, - * {@link #useEngineSocketByDefault} will be used. + * Configures the socket to be created for this instance. */ void setUseEngineSocket(boolean useEngineSocket) { - this.useEngineSocket = useEngineSocket; + // No-op. Engine sockets are always used. } void setNamedGroups(String[] namedGroups) { @@ -98,55 +88,31 @@ public Socket createSocket() throws IOException { if (instantiationException != null) { throw instantiationException; } - if (useEngineSocket) { - return createEngineSocket((SSLParametersImpl) sslParameters.clone()); - } else { - return createFileDescriptorSocket((SSLParametersImpl) sslParameters.clone()); - } + return createEngineSocket((SSLParametersImpl) sslParameters.clone()); } @Override public Socket createSocket(String hostname, int port) throws IOException, UnknownHostException { - if (useEngineSocket) { - return createEngineSocket(hostname, port, (SSLParametersImpl) sslParameters.clone()); - } else { - return createFileDescriptorSocket(hostname, port, - (SSLParametersImpl) sslParameters.clone()); - } + return createEngineSocket(hostname, port, (SSLParametersImpl) sslParameters.clone()); } @Override public Socket createSocket(String hostname, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { - if (useEngineSocket) { - return createEngineSocket(hostname, port, localHost, localPort, - (SSLParametersImpl) sslParameters.clone()); - } else { - return createFileDescriptorSocket(hostname, port, localHost, localPort, - (SSLParametersImpl) sslParameters.clone()); - } + return createEngineSocket(hostname, port, localHost, localPort, + (SSLParametersImpl) sslParameters.clone()); } @Override public Socket createSocket(InetAddress address, int port) throws IOException { - if (useEngineSocket) { - return createEngineSocket(address, port, (SSLParametersImpl) sslParameters.clone()); - } else { - return createFileDescriptorSocket(address, port, - (SSLParametersImpl) sslParameters.clone()); - } + return createEngineSocket(address, port, (SSLParametersImpl) sslParameters.clone()); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - if (useEngineSocket) { - return createEngineSocket(address, port, localAddress, localPort, - (SSLParametersImpl) sslParameters.clone()); - } else { - return createFileDescriptorSocket(address, port, localAddress, localPort, - (SSLParametersImpl) sslParameters.clone()); - } + return createEngineSocket(address, port, localAddress, localPort, + (SSLParametersImpl) sslParameters.clone()); } @Override @@ -156,24 +122,7 @@ public Socket createSocket(Socket socket, String hostname, int port, boolean aut if (!socket.isConnected()) { throw new SocketException("Socket is not connected."); } - - if (!useEngineSocket && hasFileDescriptor(socket)) { - return createFileDescriptorSocket(socket, hostname, port, autoClose, - (SSLParametersImpl) sslParameters.clone()); - } else { - return createEngineSocket(socket, hostname, port, autoClose, - (SSLParametersImpl) sslParameters.clone()); - } - } - - private boolean hasFileDescriptor(Socket s) { - try { - // If socket has a file descriptor we can use it directly - // otherwise we need to use the engine. - Platform.getFileDescriptor(s); - return true; - } catch (RuntimeException re) { - return false; - } + return createEngineSocket(socket, hostname, port, autoClose, + (SSLParametersImpl) sslParameters.clone()); } } diff --git a/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java b/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java index c24bdb8cb..d4caf673c 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java +++ b/common/src/main/java/org/conscrypt/OpenSSLSocketImpl.java @@ -96,11 +96,9 @@ public void setHandshakeTimeout(int handshakeTimeoutMilliseconds) throws SocketE @Override public abstract void setUseSessionTickets(boolean useSessionTickets); - @Override public abstract void setChannelIdEnabled(boolean enabled); + @Override public abstract void setEchConfigList(byte[] echConfigList); - @Override public abstract byte[] getChannelId() throws SSLException; - - @Override public abstract void setChannelIdPrivateKey(PrivateKey privateKey); + public abstract void setChannelIdPrivateKey(PrivateKey privateKey); /** * @deprecated NPN is not supported diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java index fcbebdb1e..6ed5da48c 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PrivateKey.java @@ -16,13 +16,13 @@ package org.conscrypt; -import java.security.MessageDigest; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.PrivateKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; @@ -54,7 +54,7 @@ public OpenSSLX25519PrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecExce } catch (InvalidKeyException | ParsingException e) { throw new InvalidKeySpecException(e); } - } else if ("raw".equalsIgnoreCase(keySpec.getFormat())) { + } else if (AddressUtils.asciiEqualsIgnoreCase(keySpec.getFormat(), "raw")) { uCoordinate = encoded; } else { throw new InvalidKeySpecException("Encoding must be in PKCS#8 or raw format"); diff --git a/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java index 0c09efe1b..c5483084f 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX25519PublicKey.java @@ -52,7 +52,7 @@ public OpenSSLX25519PublicKey(EncodedKeySpec keySpec) throws InvalidKeySpecExcep throw new InvalidKeySpecException("Invalid key size"); } uCoordinate = Arrays.copyOfRange(encoded, X509_PREAMBLE.length, totalLength); - } else if ("raw".equalsIgnoreCase(keySpec.getFormat())) { + } else if (AddressUtils.asciiEqualsIgnoreCase(keySpec.getFormat(), "raw")) { if (encoded.length != X25519_KEY_SIZE_BYTES) { throw new InvalidKeySpecException("Invalid key size"); } diff --git a/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java b/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java index 77f3e7f89..01fbb26de 100644 --- a/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java +++ b/common/src/main/java/org/conscrypt/OpenSSLX509CertificateFactory.java @@ -46,6 +46,7 @@ public class OpenSSLX509CertificateFactory extends CertificateFactorySpi { private static final int VALUE_0 = 0x30; // Value of '0' private static final int PUSHBACK_SIZE = 64; + private static final int SAFE_MARK_LIMIT = 256 * 1024; static class ParsingException extends Exception { private static final long serialVersionUID = 8390802697728301325L; @@ -97,6 +98,41 @@ private static boolean isMaybePkcs7(byte[] header) { return idx < header.length && header[idx] == 0x06; } + /** + * Returns true if the header looks like an X.509 Certificate or CRL, by checking that the + * first element of the structure is a SEQUENCE, which has tag 0x30 and that the length does + * not exceed what can be represented by 4 bytes. + */ + private static boolean isMaybeX509(byte[] header, int len) { + // The outer tag must be an ASN.1 SEQUENCE. + if (len < 2 || header[0] != VALUE_0) { + return false; + } + + // Bytes are signed in Java. + int lengthByte = header[1] & 0xff; + int idx = 2; + + // Skip the length prefix to find the tag of the first child. + if (lengthByte <= 0x80) { + // Short-form or indefinite length. + } else if (lengthByte == 0x81) { + idx += 1; + } else if (lengthByte == 0x82) { + idx += 2; + } else if (lengthByte == 0x83) { + idx += 3; + } else if (lengthByte == 0x84) { + idx += 4; + } else { + return false; + } + + // The first element of an X.509 Certificate (TBSCertificate) or + // CRL (TBSCertList) is always a SEQUENCE, which has tag 0x30. + return idx < len && header[idx] == VALUE_0; + } + /** * The code for X509 Certificates and CRL is pretty much the same. We use * this abstract class to share the code between them. This makes it ugly, @@ -110,18 +146,25 @@ T generateItem(InputStream inStream) throws ParsingException { final boolean markable = inStream.markSupported(); if (markable) { - inStream.mark(PKCS7_MARKER.length); + inStream.mark(SAFE_MARK_LIMIT); } final PushbackInputStream pbis = new PushbackInputStream(inStream, PUSHBACK_SIZE); try { byte[] buffer = new byte[PKCS7_MARKER.length]; + int len = 0; + while (len < buffer.length) { + int read = pbis.read(buffer, len, buffer.length - len); + if (read < 0) { + break; + } + len += read; + } - int len = pbis.read(buffer); - if (len < 0) { - /* No need to reset here. The stream was empty or EOF. */ + if (len == 0) { throw new ParsingException("inStream is empty"); } + pbis.unread(buffer, 0, len); if (buffer[0] == '-') { @@ -134,8 +177,20 @@ T generateItem(InputStream inStream) throws ParsingException { } return certs.get(0); } - if (buffer[0] == VALUE_0) { - return fromX509DerInputStream(pbis); + // GUARD: Only attempt greedy DER parsing if the header does not look like PEM and + // does look like X509 or PKCS#7. + if (isMaybeX509(buffer, len)) { + try { + return fromX509DerInputStream(pbis); + } catch (ParsingException e) { + if (markable) { + try { + inStream.reset(); + } catch (IOException ignored) { + // If resetting the stream fails, there's not much we can do + } + } + } } int value = 0; buffer = new byte[PEM_MARKER.length]; @@ -174,7 +229,7 @@ Collection generateItems(InputStream inStream) throws ParsingExcept final boolean markable = inStream.markSupported(); if (markable) { - inStream.mark(PUSHBACK_SIZE); + inStream.mark(SAFE_MARK_LIMIT); } /* Attempt to see if this is a PKCS#7 bag. */ @@ -182,8 +237,16 @@ Collection generateItems(InputStream inStream) throws ParsingExcept try { final byte[] buffer = new byte[PKCS7_MARKER.length]; - final int len = pbis.read(buffer); - if (len < 0) { + int len = 0; + while (len < buffer.length) { + int read = pbis.read(buffer, len, buffer.length - len); + if (read < 0) { + break; + } + len += read; + } + + if (len == 0) { // No need to reset here. The stream was empty or EOF so we return an empty // list, making it mutable for consistency with the other code paths. return new ArrayList<>(); @@ -220,7 +283,7 @@ Collection generateItems(InputStream inStream) throws ParsingExcept * there is an error during certificate generation. */ if (markable) { - inStream.mark(PUSHBACK_SIZE); + inStream.mark(SAFE_MARK_LIMIT); } try { diff --git a/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactory.java new file mode 100644 index 000000000..220fd496b --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactory.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2026 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.io.IOException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** An implementation of a {@link KeyFactorySpi} for composite MLDSA keys. */ +@Internal +public abstract class OpenSslCompositeMlDsaKeyFactory extends KeyFactorySpi { + private final CompositeMlDsaAlgorithm algorithm; + + private OpenSslCompositeMlDsaKeyFactory(CompositeMlDsaAlgorithm algorithm) { + this.algorithm = algorithm; + } + + /** Key factory for MLDSA44-RSA2048-PSS-SHA256. */ + public static class Mldsa44Rsa2048PssSha256 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa44Rsa2048PssSha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_RSA2048_PSS_SHA256); + } + } + + /** Key factory for MLDSA44-RSA2048-PKCS15-SHA256. */ + public static class Mldsa44Rsa2048Pkcs15Sha256 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa44Rsa2048Pkcs15Sha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_RSA2048_PKCS15_SHA256); + } + } + + /** Key factory for MLDSA44-Ed25519-SHA512. */ + public static class Mldsa44Ed25519Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa44Ed25519Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512); + } + } + + /** Key factory for MLDSA44-ECDSA-P256-SHA256. */ + public static class Mldsa44EcdsaP256Sha256 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa44EcdsaP256Sha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_ECDSA_P256_SHA256); + } + } + + /** Key factory for MLDSA65-RSA3072-PSS-SHA512. */ + public static class Mldsa65Rsa3072PssSha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa65Rsa3072PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA3072_PSS_SHA512); + } + } + + /** Key factory for MLDSA65-RSA3072-PKCS15-SHA512. */ + public static class Mldsa65Rsa3072Pkcs15Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa65Rsa3072Pkcs15Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA3072_PKCS15_SHA512); + } + } + + /** Key factory for MLDSA65-RSA4096-PSS-SHA512. */ + public static class Mldsa65Rsa4096PssSha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa65Rsa4096PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA4096_PSS_SHA512); + } + } + + /** Key factory for MLDSA65-RSA4096-PKCS15-SHA512. */ + public static class Mldsa65Rsa4096Pkcs15Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa65Rsa4096Pkcs15Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA4096_PKCS15_SHA512); + } + } + + /** Key factory for MLDSA65-ECDSA-P256-SHA512. */ + public static class Mldsa65EcdsaP256Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa65EcdsaP256Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ECDSA_P256_SHA512); + } + } + + /** Key factory for MLDSA65-ECDSA-P384-SHA512. */ + public static class Mldsa65EcdsaP384Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa65EcdsaP384Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ECDSA_P384_SHA512); + } + } + + /** Key factory for MLDSA65-Ed25519-SHA512. */ + public static class Mldsa65Ed25519Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa65Ed25519Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ED25519_SHA512); + } + } + + /** Key factory for MLDSA87-ECDSA-P384-SHA512. */ + public static class Mldsa87EcdsaP384Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa87EcdsaP384Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_ECDSA_P384_SHA512); + } + } + + /** Key factory for MLDSA87-RSA3072-PSS-SHA512. */ + public static class Mldsa87Rsa3072PssSha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa87Rsa3072PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_RSA3072_PSS_SHA512); + } + } + + /** Key factory for MLDSA87-RSA4096-PSS-SHA512. */ + public static class Mldsa87Rsa4096PssSha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa87Rsa4096PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_RSA4096_PSS_SHA512); + } + } + + /** Key factory for MLDSA87-ECDSA-P521-SHA512. */ + public static class Mldsa87EcdsaP521Sha512 extends OpenSslCompositeMlDsaKeyFactory { + public Mldsa87EcdsaP521Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_ECDSA_P521_SHA512); + } + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!(keySpec instanceof EncodedKeySpec)) { + throw new InvalidKeySpecException("Unsupported KeySpec"); + } + EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; + if (AddressUtils.asciiEqualsIgnoreCase(encodedKeySpec.getFormat(), "raw")) { + return new OpenSslCompositeMlDsaPublicKey(encodedKeySpec.getEncoded(), algorithm); + } + if (!encodedKeySpec.getFormat().equals("X.509")) { + throw new InvalidKeySpecException("Unsupported key format: " + + encodedKeySpec.getFormat() + + ". Only raw and X.509 keys are supported."); + } + byte[] rawKey = parseSubjectPublicKeyInfo(encodedKeySpec.getEncoded()); + return new OpenSslCompositeMlDsaPublicKey(rawKey, algorithm); + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec == null"); + } + if (!(keySpec instanceof EncodedKeySpec)) { + throw new InvalidKeySpecException("Unsupported KeySpec"); + } + EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; + if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + return new OpenSslCompositeMlDsaPrivateKey(encodedKeySpec.getEncoded(), algorithm); + } + if (!encodedKeySpec.getFormat().equals("PKCS#8")) { + throw new InvalidKeySpecException("Unsupported key format: " + + encodedKeySpec.getFormat() + + ". Only raw and PKCS#8 keys are supported."); + } + byte[] rawKey = parsePkcs8(encodedKeySpec.getEncoded()); + return new OpenSslCompositeMlDsaPrivateKey(rawKey, algorithm); + } + + @Override + protected T engineGetKeySpec(Key key, Class keySpec) + throws InvalidKeySpecException { + if (key == null) { + throw new InvalidKeySpecException("Key null"); + } + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec null"); + } + + if (key instanceof OpenSslCompositeMlDsaPublicKey) { + OpenSslCompositeMlDsaPublicKey conscryptKey = (OpenSslCompositeMlDsaPublicKey) key; + if (X509EncodedKeySpec.class.isAssignableFrom(keySpec)) { + @SuppressWarnings("unchecked") // Safe because keySpec is X509EncodedKeySpec or a superclass. + T result = (T) new X509EncodedKeySpec(conscryptKey.getEncoded()); + return result; + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec); + } + } else if (key instanceof OpenSslCompositeMlDsaPrivateKey) { + OpenSslCompositeMlDsaPrivateKey conscryptKey = (OpenSslCompositeMlDsaPrivateKey) key; + if (PKCS8EncodedKeySpec.class.isAssignableFrom(keySpec)) { + @SuppressWarnings("unchecked") // Safe because keySpec is PKCS8EncodedKeySpec or a superclass. + T result = (T) new PKCS8EncodedKeySpec(key.getEncoded()); + return result; + } else if (EncodedKeySpec.class.isAssignableFrom(keySpec)) { + return KeySpecUtil.makeRawKeySpec(conscryptKey.getRaw(), keySpec); + } + } + throw new InvalidKeySpecException("Unsupported keySpec: " + keySpec.getName()); + } + + @Override + @SuppressWarnings("CanIgnoreReturnValueSuggester") + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + if (key instanceof OpenSslCompositeMlDsaPublicKey + || key instanceof OpenSslCompositeMlDsaPrivateKey) { + return key; + } + throw new InvalidKeyException("Unsupported key"); + } + + public CompositeMlDsaAlgorithm getAlgorithm() { + return algorithm; + } + + /** + * Format described in + * https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html#name-encoding-to-der + */ + private byte[] parsePkcs8(byte[] encoded) throws InvalidKeySpecException { + byte[] rawKey = null; + long pkcs8Parser = 0; + long sequenceParser = 0; + long oidParser = 0; + + try { + pkcs8Parser = NativeCrypto.asn1_read_init(encoded); + sequenceParser = NativeCrypto.asn1_read_sequence(pkcs8Parser); + + long version = NativeCrypto.asn1_read_uint64(sequenceParser); + if (version != 0) { + throw new InvalidKeySpecException("Invalid PKCS#8 version: " + version); + } + + oidParser = NativeCrypto.asn1_read_sequence(sequenceParser); + String oid = NativeCrypto.asn1_read_oid_raw(oidParser); + String expectedOid = algorithm.getOid(); + if (!oid.equals(expectedOid)) { + throw new InvalidKeySpecException("Incorrect Algorithm OID"); + } + + rawKey = NativeCrypto.asn1_read_octetstring(sequenceParser); + if (rawKey == null || rawKey.length <= 32) { + throw new InvalidKeySpecException("Invalid PKCS#8 encoding: key too short"); + } + return rawKey; + } catch (IOException e) { + throw new InvalidKeySpecException("Failed to decode PKCS8 structure", e); + } finally { + NativeCrypto.asn1_read_free(pkcs8Parser); + NativeCrypto.asn1_read_free(sequenceParser); + NativeCrypto.asn1_read_free(oidParser); + } + } + + /** + * Format described in + * https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html#name-encoding-to-der + */ + private byte[] parseSubjectPublicKeyInfo(byte[] encoded) throws InvalidKeySpecException { + long spkiParser = 0; + long sequenceParser = 0; + long oidParser = 0; + + try { + spkiParser = NativeCrypto.asn1_read_init(encoded); + sequenceParser = NativeCrypto.asn1_read_sequence(spkiParser); + + oidParser = NativeCrypto.asn1_read_sequence(sequenceParser); + String oid = NativeCrypto.asn1_read_oid_raw(oidParser); + String expectedOid = algorithm.getOid(); + if (!oid.equals(expectedOid)) { + throw new InvalidKeySpecException("Incorrect Algorithm OID"); + } + + // Read the raw key payload and ensure that the padding bits are exactly 0. + byte[] rawKey = NativeCrypto.asn1_read_bitstring_payload(sequenceParser, 0); + if (rawKey == null || rawKey.length <= 1312) { // ML-DSA-44 public key size + throw new InvalidKeySpecException("Invalid X.509 SPKI encoding: key too short"); + } + return rawKey; + } catch (IOException e) { + throw new InvalidKeySpecException("Failed to decode X.509 structure", e); + } finally { + NativeCrypto.asn1_read_free(spkiParser); + NativeCrypto.asn1_read_free(sequenceParser); + NativeCrypto.asn1_read_free(oidParser); + } + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyPairGenerator.java b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyPairGenerator.java new file mode 100644 index 000000000..24966fd48 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaKeyPairGenerator.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2026 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.InvalidParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyPairGeneratorSpi; + +/** An implementation of a {@link KeyPairGenerator} for composite MLDSA keys. */ +@Internal +public abstract class OpenSslCompositeMlDsaKeyPairGenerator extends KeyPairGenerator { + private final CompositeMlDsaAlgorithm fullAlgorithm; + + private OpenSslCompositeMlDsaKeyPairGenerator(CompositeMlDsaAlgorithm algorithm) { + super(algorithm.getName()); + this.fullAlgorithm = algorithm; + } + + /** Key pair generator for MLDSA44-RSA2048-PSS-SHA256. */ + public static class Mldsa44Rsa2048PssSha256 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa44Rsa2048PssSha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_RSA2048_PSS_SHA256); + } + } + + /** Key pair generator for MLDSA44-RSA2048-PKCS15-SHA256. */ + public static class Mldsa44Rsa2048Pkcs15Sha256 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa44Rsa2048Pkcs15Sha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_RSA2048_PKCS15_SHA256); + } + } + + /** Key pair generator for MLDSA44-Ed25519-SHA512. */ + public static class Mldsa44Ed25519Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa44Ed25519Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512); + } + } + + /** Key pair generator for MLDSA44-ECDSA-P256-SHA256. */ + public static class Mldsa44EcdsaP256Sha256 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa44EcdsaP256Sha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_ECDSA_P256_SHA256); + } + } + + /** Key pair generator for MLDSA65-RSA3072-PSS-SHA512. */ + public static class Mldsa65Rsa3072PssSha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa65Rsa3072PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA3072_PSS_SHA512); + } + } + + /** Key pair generator for MLDSA65-RSA3072-PKCS15-SHA512. */ + public static class Mldsa65Rsa3072Pkcs15Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa65Rsa3072Pkcs15Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA3072_PKCS15_SHA512); + } + } + + /** Key pair generator for MLDSA65-RSA4096-PSS-SHA512. */ + public static class Mldsa65Rsa4096PssSha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa65Rsa4096PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA4096_PSS_SHA512); + } + } + + /** Key pair generator for MLDSA65-RSA4096-PKCS15-SHA512. */ + public static class Mldsa65Rsa4096Pkcs15Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa65Rsa4096Pkcs15Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA4096_PKCS15_SHA512); + } + } + + /** Key pair generator for MLDSA65-Ed25519-SHA512. */ + public static class Mldsa65Ed25519Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa65Ed25519Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ED25519_SHA512); + } + } + + /** Key pair generator for MLDSA65-ECDSA-P256-SHA512. */ + public static class Mldsa65EcdsaP256Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa65EcdsaP256Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ECDSA_P256_SHA512); + } + } + + /** Key pair generator for MLDSA65-ECDSA-P384-SHA512. */ + public static class Mldsa65EcdsaP384Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa65EcdsaP384Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ECDSA_P384_SHA512); + } + } + + /** Key pair generator for MLDSA87-RSA3072-PSS-SHA512. */ + public static class Mldsa87Rsa3072PssSha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa87Rsa3072PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_RSA3072_PSS_SHA512); + } + } + + /** Key pair generator for MLDSA87-RSA4096-PSS-SHA512. */ + public static class Mldsa87Rsa4096PssSha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa87Rsa4096PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_RSA4096_PSS_SHA512); + } + } + + /** Key pair generator for MLDSA87-ECDSA-P384-SHA512. */ + public static class Mldsa87EcdsaP384Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa87EcdsaP384Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_ECDSA_P384_SHA512); + } + } + + /** Key pair generator for MLDSA87-ECDSA-P521-SHA512. */ + public static class Mldsa87EcdsaP521Sha512 extends OpenSslCompositeMlDsaKeyPairGenerator { + public Mldsa87EcdsaP521Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_ECDSA_P521_SHA512); + } + } + + @Override + public void initialize(int bits) { + if (bits != -1) { + throw new InvalidParameterException("Composite ML-DSA only supports -1 for bits"); + } + } + + /** + * Following + * https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html#name-key-generation + */ + @Override + public KeyPair generateKeyPair() { + try { + // Generate ML-DSA key pair. + byte[] seed = new byte[32]; + NativeCrypto.RAND_bytes(seed); + byte[] pqRawPublicKeyBytes; + if (fullAlgorithm.getMlDsaAlgorithm().equals(MlDsaAlgorithm.ML_DSA_44)) { + pqRawPublicKeyBytes = NativeCrypto.MLDSA44_public_key_from_seed(seed); + } else if (fullAlgorithm.getMlDsaAlgorithm().equals(MlDsaAlgorithm.ML_DSA_65)) { + pqRawPublicKeyBytes = NativeCrypto.MLDSA65_public_key_from_seed(seed); + } else if (fullAlgorithm.getMlDsaAlgorithm().equals(MlDsaAlgorithm.ML_DSA_87)) { + pqRawPublicKeyBytes = NativeCrypto.MLDSA87_public_key_from_seed(seed); + } else { + // Should never happen. + throw new IllegalStateException("Unsupported ML-DSA algorithm: " + + fullAlgorithm.getMlDsaAlgorithm()); + } + + // Generate classic key pair. + KeyPairGeneratorSpi kpg; + if (fullAlgorithm.getClassicAlgorithm().equals("Ed25519")) { + kpg = new OpenSslEdDsaKeyPairGenerator(); + } else if (fullAlgorithm.getClassicAlgorithm().equals("RSA")) { + kpg = new OpenSSLRSAKeyPairGenerator(); + } else if (fullAlgorithm.getClassicAlgorithm().equals("EC")) { + kpg = new OpenSSLECKeyPairGenerator(); + } else { + throw new IllegalStateException("Unsupported classic algorithm: " + + fullAlgorithm.getClassicAlgorithm()); + } + + if (fullAlgorithm.getClassicKeySize() != null + && fullAlgorithm.getClassicKeySize() > 0) { + kpg.initialize(fullAlgorithm.getClassicKeySize(), null); + } + KeyPair classicKeyPair = kpg.generateKeyPair(); + + // Get classic public key bytes. + OpenSSLKey classicPublicKey = + ((OpenSSLKeyHolder) classicKeyPair.getPublic()).getOpenSSLKey(); + byte[] classicRawPublicKeyBytes; + if (fullAlgorithm.getClassicAlgorithm().equals("Ed25519")) { + classicRawPublicKeyBytes = + NativeCrypto.EVP_PKEY_get_raw_public_key(classicPublicKey.getNativeRef()); + } else if (fullAlgorithm.getClassicAlgorithm().equals("RSA")) { + classicRawPublicKeyBytes = NativeCrypto.unwrap_RSA_public_key_x509( + NativeCrypto.EVP_marshal_public_key(classicPublicKey.getNativeRef())); + } else if (fullAlgorithm.getClassicAlgorithm().equals("EC")) { + classicRawPublicKeyBytes = NativeCrypto.unwrap_EC_public_key_x509( + NativeCrypto.EVP_marshal_public_key(classicPublicKey.getNativeRef())); + } else { + throw new IllegalStateException("Unsupported classic algorithm: " + + fullAlgorithm.getClassicAlgorithm()); + } + + // Assemble composite public key bytes. + byte[] compositeRawPublicKeyBytes = + ArrayUtils.concat(pqRawPublicKeyBytes, classicRawPublicKeyBytes); + + // Get classic private key bytes. + OpenSSLKey classicPrivateKey = + ((OpenSSLKeyHolder) classicKeyPair.getPrivate()).getOpenSSLKey(); + byte[] classicRawSecretKeyBytes; + if (fullAlgorithm.getClassicAlgorithm().equals("Ed25519")) { + classicRawSecretKeyBytes = + NativeCrypto.EVP_PKEY_get_raw_private_key(classicPrivateKey.getNativeRef()); + } else if (fullAlgorithm.getClassicAlgorithm().equals("RSA")) { + classicRawSecretKeyBytes = NativeCrypto.unwrap_RSA_private_key_pkcs8( + NativeCrypto.EVP_marshal_private_key(classicPrivateKey.getNativeRef())); + } else if (fullAlgorithm.getClassicAlgorithm().equals("EC")) { + classicRawSecretKeyBytes = NativeCrypto.unwrap_EC_private_key_pkcs8( + NativeCrypto.EVP_marshal_private_key(classicPrivateKey.getNativeRef())); + } else { + throw new IllegalStateException("Unsupported classic algorithm: " + + fullAlgorithm.getClassicAlgorithm()); + } + + // Assemble composite private key bytes. + byte[] compositeRawSecretKeyBytes = ArrayUtils.concat(seed, classicRawSecretKeyBytes); + + // Assemble composite key pair. + return new KeyPair( + new OpenSslCompositeMlDsaPublicKey(compositeRawPublicKeyBytes, fullAlgorithm), + new OpenSslCompositeMlDsaPrivateKey(compositeRawSecretKeyBytes, fullAlgorithm)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKey.java new file mode 100644 index 000000000..f3a95ac7c --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKey.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2026 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.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; + +/** An implementation of a composite ML-DSA private key. */ +@Internal +public class OpenSslCompositeMlDsaPrivateKey implements PrivateKey { + private static final int ML_DSA_SEED_LENGTH = 32; + + private final CompositeMlDsaAlgorithm algorithm; + private final OpenSslMlDsaPrivateKey mlDsaPrivateKey; + private final PrivateKey classicPrivateKey; + // Needed to avoid re-creating the classic private key on every getRaw() call, since some of the + // classic PrivateKey implementations (like OpenSSLRSAPrivateKey) don't support unencoded keys + // (that is, to get raw bytes back from the PrivateKey object, we'd need to decode it from the + // PKCS#8 format first). + private final byte[] classicRawKey; + + public OpenSslCompositeMlDsaPrivateKey(byte[] rawKey, CompositeMlDsaAlgorithm algorithm) + throws InvalidKeySpecException { + this.algorithm = algorithm; + byte[] seed = Arrays.copyOf(rawKey, ML_DSA_SEED_LENGTH); + byte[] classicRaw = Arrays.copyOfRange(rawKey, ML_DSA_SEED_LENGTH, rawKey.length); + this.mlDsaPrivateKey = new OpenSslMlDsaPrivateKey(seed, algorithm.getMlDsaAlgorithm()); + this.classicPrivateKey = createClassicPrivateKey(this.algorithm, classicRaw); + this.classicRawKey = classicRaw; + } + + private static PrivateKey createClassicPrivateKey(CompositeMlDsaAlgorithm algorithm, + byte[] rawClassicKey) + throws InvalidKeySpecException { + if (algorithm.getClassicAlgorithm().equals("Ed25519")) { + if (rawClassicKey.length != 32) { + throw new InvalidKeySpecException("Invalid Ed25519 private key length: " + + rawClassicKey.length); + } + return new OpenSslEdDsaPrivateKey(rawClassicKey); + } + if (algorithm.getClassicAlgorithm().equals("RSA")) { + return OpenSSLKey.getPrivateKey( + new PKCS8EncodedKeySpec(NativeCrypto.wrap_RSA_private_key_pkcs8(rawClassicKey)), + NativeConstants.EVP_PKEY_RSA); + } + if (algorithm.getClassicAlgorithm().equals("EC")) { + return OpenSSLKey.getPrivateKey( + new PKCS8EncodedKeySpec(NativeCrypto.wrap_EC_private_key_pkcs8(rawClassicKey)), + NativeConstants.EVP_PKEY_EC); + } + throw new InvalidKeySpecException("Unsupported classic algorithm: " + + algorithm.getClassicAlgorithm()); + } + + public CompositeMlDsaAlgorithm getCompositeMlDsaAlgorithm() { + return algorithm; + } + + public OpenSslMlDsaPrivateKey getMlDsaPrivateKey() { + return mlDsaPrivateKey; + } + + public PrivateKey getClassicPrivateKey() { + return classicPrivateKey; + } + + public byte[] getRaw() { + byte[] seed = mlDsaPrivateKey.getSeed(); + byte[] result = new byte[seed.length + classicRawKey.length]; + System.arraycopy(seed, 0, result, 0, seed.length); + System.arraycopy(classicRawKey, 0, result, seed.length, classicRawKey.length); + return result; + } + + @Override + public String getAlgorithm() { + return algorithm.getName(); + } + + @Override + public String getFormat() { + return "PKCS#8"; + } + + @Override + public byte[] getEncoded() { + long cbb = 0; + long seqRef = 0; + long algRef = 0; + try { + cbb = NativeCrypto.asn1_write_init(); + seqRef = NativeCrypto.asn1_write_sequence(cbb); + NativeCrypto.asn1_write_uint64(seqRef, 0); // version + algRef = NativeCrypto.asn1_write_sequence(seqRef); + NativeCrypto.asn1_write_oid_raw(algRef, algorithm.getOid()); + NativeCrypto.asn1_write_flush(seqRef); + NativeCrypto.asn1_write_octetstring(seqRef, getRaw()); + return NativeCrypto.asn1_write_finish(cbb); + } catch (IOException e) { + NativeCrypto.asn1_write_cleanup(cbb); + throw new IllegalStateException("Failed to encode composite ML-DSA private key", e); + } finally { + NativeCrypto.asn1_write_free(algRef); + NativeCrypto.asn1_write_free(seqRef); + NativeCrypto.asn1_write_free(cbb); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof OpenSslCompositeMlDsaPrivateKey)) { + return false; + } + OpenSslCompositeMlDsaPrivateKey other = (OpenSslCompositeMlDsaPrivateKey) o; + return algorithm == other.algorithm && MessageDigest.isEqual(getRaw(), other.getRaw()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getRaw()); + } + + private void readObject(ObjectInputStream in) { + throw new UnsupportedOperationException("serialization not supported"); + } + + private void writeObject(ObjectOutputStream out) { + throw new UnsupportedOperationException("serialization not supported"); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPublicKey.java new file mode 100644 index 000000000..73e91b260 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslCompositeMlDsaPublicKey.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2026 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.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.security.MessageDigest; +import java.security.PublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +/** An implementation of a composite MLDSA public key. */ +@Internal +public class OpenSslCompositeMlDsaPublicKey implements PublicKey { + private final CompositeMlDsaAlgorithm algorithm; + private final OpenSslMlDsaPublicKey mlDsaPublicKey; + private final PublicKey classicPublicKey; + // Needed to avoid re-creating the classic public key on every getRaw() call, since some of the + // classic PublicKey implementations (like OpenSSLRSAPublicKey) don't support unencoded keys + // (that is, to get raw bytes back from the PublicKey object, we'd need to decode it from the + // X509 format first). + private final byte[] classicRawKey; + + public OpenSslCompositeMlDsaPublicKey(byte[] rawKey, CompositeMlDsaAlgorithm algorithm) + throws IllegalArgumentException, InvalidKeySpecException { + this.algorithm = algorithm; + int mlLen = algorithm.getMlDsaAlgorithm().publicKeySize(); + byte[] mlDsaRaw = Arrays.copyOf(rawKey, mlLen); + byte[] classicRaw = Arrays.copyOfRange(rawKey, mlLen, rawKey.length); + this.mlDsaPublicKey = new OpenSslMlDsaPublicKey(mlDsaRaw, algorithm.getMlDsaAlgorithm()); + this.classicPublicKey = createClassicPublicKey(this.algorithm, classicRaw); + this.classicRawKey = classicRaw; + } + + private static PublicKey createClassicPublicKey(CompositeMlDsaAlgorithm algorithm, + byte[] rawClassicKey) + throws IllegalArgumentException, InvalidKeySpecException { + if (algorithm.getClassicAlgorithm().equals("Ed25519")) { + if (rawClassicKey.length != 32) { + throw new IllegalArgumentException("Invalid Ed25519 public key length: " + + rawClassicKey.length); + } + return new OpenSslEdDsaPublicKey(rawClassicKey); + } else if (algorithm.getClassicAlgorithm().equals("RSA")) { + try { + return OpenSSLKey.getPublicKey( + new X509EncodedKeySpec( + NativeCrypto.wrap_RSA_public_key_x509(rawClassicKey)), + NativeConstants.EVP_PKEY_RSA); + } catch (InvalidKeySpecException e) { + throw new IllegalArgumentException(e); + } + } else if (algorithm.getClassicAlgorithm().equals("EC")) { + return OpenSSLKey.getPublicKey( + new X509EncodedKeySpec(NativeCrypto.wrap_EC_public_key_x509( + rawClassicKey, algorithm.getEcCurve())), + NativeConstants.EVP_PKEY_EC); + } else { + throw new IllegalArgumentException("Unsupported classic algorithm: " + + algorithm.getClassicAlgorithm()); + } + } + + public CompositeMlDsaAlgorithm getCompositeMlDsaAlgorithm() { + return algorithm; + } + + public OpenSslMlDsaPublicKey getMlDsaPublicKey() { + return mlDsaPublicKey; + } + + public PublicKey getClassicPublicKey() { + return classicPublicKey; + } + + public byte[] getRaw() { + byte[] mlDsaRaw = mlDsaPublicKey.getRaw(); + byte[] result = new byte[mlDsaRaw.length + classicRawKey.length]; + System.arraycopy(mlDsaRaw, 0, result, 0, mlDsaRaw.length); + System.arraycopy(classicRawKey, 0, result, mlDsaRaw.length, classicRawKey.length); + return result; + } + + @Override + public String getAlgorithm() { + return algorithm.getName(); + } + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + public byte[] getEncoded() { + long cbb = 0; + long seqRef = 0; + long algRef = 0; + try { + cbb = NativeCrypto.asn1_write_init(); + seqRef = NativeCrypto.asn1_write_sequence(cbb); + algRef = NativeCrypto.asn1_write_sequence(seqRef); + NativeCrypto.asn1_write_oid_raw(algRef, algorithm.getOid()); + NativeCrypto.asn1_write_flush(seqRef); + NativeCrypto.asn1_write_bitstring(seqRef, getRaw()); + return NativeCrypto.asn1_write_finish(cbb); + } catch (IOException e) { + NativeCrypto.asn1_write_cleanup(cbb); + throw new IllegalStateException("Failed to encode public key", e); + } finally { + NativeCrypto.asn1_write_free(algRef); + NativeCrypto.asn1_write_free(seqRef); + NativeCrypto.asn1_write_free(cbb); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof OpenSslCompositeMlDsaPublicKey)) { + return false; + } + OpenSslCompositeMlDsaPublicKey other = (OpenSslCompositeMlDsaPublicKey) o; + return algorithm == other.algorithm && MessageDigest.isEqual(getRaw(), other.getRaw()); + } + + @Override + public int hashCode() { + return Arrays.hashCode(getRaw()); + } + + private void readObject(ObjectInputStream in) { + throw new UnsupportedOperationException("serialization not supported"); + } + + private void writeObject(ObjectOutputStream out) { + throw new UnsupportedOperationException("serialization not supported"); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslEdDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslEdDsaPrivateKey.java index 4f29997da..99440e702 100644 --- a/common/src/main/java/org/conscrypt/OpenSslEdDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslEdDsaPrivateKey.java @@ -16,12 +16,12 @@ package org.conscrypt; -import java.security.MessageDigest; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.security.MessageDigest; import java.security.PrivateKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; @@ -46,7 +46,7 @@ private static OpenSSLKey getOpenSslKeyFromRaw(byte[] raw) throws ParsingExcepti public OpenSslEdDsaPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { try { - if (keySpec.getFormat().equalsIgnoreCase("raw")) { + if (AddressUtils.asciiEqualsIgnoreCase(keySpec.getFormat(), "raw")) { key = getOpenSslKeyFromRaw(keySpec.getEncoded()); } else if (keySpec.getFormat().equals("PKCS#8")) { key = getOpenSslKeyFromPkcS8(keySpec.getEncoded()); @@ -90,7 +90,7 @@ public byte[] getEncoded() { return NativeCrypto.EVP_marshal_private_key(key.getNativeRef()); } - byte[] getRaw() { + public byte[] getRaw() { if (key == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslEdDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslEdDsaPublicKey.java index dedb57947..8fb9c434f 100644 --- a/common/src/main/java/org/conscrypt/OpenSslEdDsaPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslEdDsaPublicKey.java @@ -45,7 +45,7 @@ private static OpenSSLKey getOpenSslKeyFromRaw(byte[] raw) throws ParsingExcepti public OpenSslEdDsaPublicKey(EncodedKeySpec keySpec) throws InvalidKeySpecException { try { - if (keySpec.getFormat().equalsIgnoreCase("raw")) { + if (AddressUtils.asciiEqualsIgnoreCase(keySpec.getFormat(), "raw")) { key = getOpenSslKeyFromRaw(keySpec.getEncoded()); } else if (keySpec.getFormat().equals("X.509")) { key = getOpenSslKeyFromX509(keySpec.getEncoded()); @@ -89,7 +89,7 @@ public byte[] getEncoded() { return NativeCrypto.EVP_marshal_public_key(key.getNativeRef()); } - byte[] getRaw() { + public byte[] getRaw() { if (key == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java index 31d4668db..186446da4 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaKeyFactory.java @@ -16,8 +16,6 @@ package org.conscrypt; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.security.InvalidKeyException; import java.security.Key; import java.security.KeyFactorySpi; @@ -28,7 +26,6 @@ import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; -import java.util.Arrays; /** An implementation of a {@link KeyFactorySpi} for MLDSA keys based on BoringSSL. */ @Internal @@ -149,7 +146,7 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecE + keySpec.getClass().getName()); } EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; - if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + if (AddressUtils.asciiEqualsIgnoreCase(encodedKeySpec.getFormat(), "raw")) { byte[] raw = encodedKeySpec.getEncoded(); return makePublicKeyFromRaw(raw, defaultAlgorithm); } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java index bb488705a..ecf26c7fc 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPrivateKey.java @@ -16,12 +16,12 @@ package org.conscrypt; -import java.security.MessageDigest; import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.security.MessageDigest; import java.security.PrivateKey; import java.util.Arrays; @@ -135,7 +135,7 @@ public byte[] getEncoded() { return NativeCrypto.EVP_marshal_private_key(key.getNativeRef()); } - byte[] getSeed() { + public byte[] getSeed() { if (key == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java index 0a5b8c58a..16cef3daf 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlDsaPublicKey.java @@ -100,7 +100,7 @@ public OpenSSLKey getOpenSSLKey() { return key; } - byte[] getRaw() { + public byte[] getRaw() { if (key == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java index c979811a4..54b1a484a 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemKeyFactory.java @@ -130,7 +130,7 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecE + keySpec.getClass().getName()); } EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; - if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + if (AddressUtils.asciiEqualsIgnoreCase(encodedKeySpec.getFormat(), "raw")) { byte[] raw = encodedKeySpec.getEncoded(); return makePublicKeyFromRaw(raw, defaultAlgorithm); } @@ -175,7 +175,7 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpe + keySpec.getClass().getName()); } EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; - if (encodedKeySpec.getFormat().equalsIgnoreCase("raw")) { + if (AddressUtils.asciiEqualsIgnoreCase(encodedKeySpec.getFormat(), "raw")) { byte[] raw = encodedKeySpec.getEncoded(); return makePrivateKeyFromSeed(raw, defaultAlgorithm); } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java index e55dd4568..ab334a361 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemPrivateKey.java @@ -44,7 +44,7 @@ public String getAlgorithm() { return "ML-KEM"; } - MlKemAlgorithm getMlKemAlgorithm() { + public MlKemAlgorithm getMlKemAlgorithm() { return algorithm; } @@ -69,7 +69,7 @@ public byte[] getEncoded() { return ArrayUtils.concat(getPkcs8Preamble(algorithm), seed); } - byte[] getSeed() { + public byte[] getSeed() { if (seed == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java index 373ec5eef..ac371f64f 100644 --- a/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslMlKemPublicKey.java @@ -45,7 +45,7 @@ public String getAlgorithm() { return "ML-KEM"; } - MlKemAlgorithm getMlKemAlgorithm() { + public MlKemAlgorithm getMlKemAlgorithm() { return algorithm; } @@ -70,7 +70,7 @@ public byte[] getEncoded() { return ArrayUtils.concat(getX509Preamble(algorithm), raw); } - byte[] getRaw() { + public byte[] getRaw() { if (raw == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslSignatureCompositeMlDsa.java b/common/src/main/java/org/conscrypt/OpenSslSignatureCompositeMlDsa.java new file mode 100644 index 000000000..69b5c52f5 --- /dev/null +++ b/common/src/main/java/org/conscrypt/OpenSslSignatureCompositeMlDsa.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt; + +import static java.nio.charset.StandardCharsets.US_ASCII; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.SignatureSpi; +import java.util.Arrays; + +/** An implementation of a {@link SignatureSpi} for Composite ML-DSA keys. */ +@Internal +public abstract class OpenSslSignatureCompositeMlDsa extends SignatureSpi { + private final CompositeMlDsaAlgorithm algorithm; + private OpenSSLMessageDigestJDK messageDigest; + private OpenSslCompositeMlDsaPrivateKey signKey; + private OpenSslCompositeMlDsaPublicKey verifyKey; + + private OpenSslSignatureCompositeMlDsa(CompositeMlDsaAlgorithm algorithm) { + this.algorithm = algorithm; + } + + /** Signature for MLDSA44-RSA2048-PSS-SHA256. */ + public static class Mldsa44Rsa2048PssSha256 extends OpenSslSignatureCompositeMlDsa { + public Mldsa44Rsa2048PssSha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_RSA2048_PSS_SHA256); + } + } + + /** Signature for MLDSA44-RSA2048-PKCS15-SHA256. */ + public static class Mldsa44Rsa2048Pkcs15Sha256 extends OpenSslSignatureCompositeMlDsa { + public Mldsa44Rsa2048Pkcs15Sha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_RSA2048_PKCS15_SHA256); + } + } + + /** Signature for MLDSA44-Ed25519-SHA512. */ + public static class Mldsa44Ed25519Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa44Ed25519Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512); + } + } + + /** Signature for MLDSA44-ECDSA-P256-SHA256. */ + public static class Mldsa44EcdsaP256Sha256 extends OpenSslSignatureCompositeMlDsa { + public Mldsa44EcdsaP256Sha256() { + super(CompositeMlDsaAlgorithm.MLDSA44_ECDSA_P256_SHA256); + } + } + + /** Signature for MLDSA65-RSA3072-PSS-SHA512. */ + public static class Mldsa65Rsa3072PssSha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa65Rsa3072PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA3072_PSS_SHA512); + } + } + + /** Signature for MLDSA65-RSA3072-PKCS15-SHA512. */ + public static class Mldsa65Rsa3072Pkcs15Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa65Rsa3072Pkcs15Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA3072_PKCS15_SHA512); + } + } + + /** Signature for MLDSA65-RSA4096-PSS-SHA512. */ + public static class Mldsa65Rsa4096PssSha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa65Rsa4096PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA4096_PSS_SHA512); + } + } + + /** Signature for MLDSA65-RSA4096-PKCS15-SHA512. */ + public static class Mldsa65Rsa4096Pkcs15Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa65Rsa4096Pkcs15Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_RSA4096_PKCS15_SHA512); + } + } + + /** Signature for MLDSA65-Ed25519-SHA512. */ + public static class Mldsa65Ed25519Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa65Ed25519Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ED25519_SHA512); + } + } + + /** Signature for MLDSA65-ECDSA-P256-SHA512. */ + public static class Mldsa65EcdsaP256Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa65EcdsaP256Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ECDSA_P256_SHA512); + } + } + + /** Signature for MLDSA65-ECDSA-P384-SHA512. */ + public static class Mldsa65EcdsaP384Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa65EcdsaP384Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA65_ECDSA_P384_SHA512); + } + } + + /** Signature for MLDSA87-RSA3072-PSS-SHA512. */ + public static class Mldsa87Rsa3072PssSha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa87Rsa3072PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_RSA3072_PSS_SHA512); + } + } + + /** Signature for MLDSA87-RSA4096-PSS-SHA512. */ + public static class Mldsa87Rsa4096PssSha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa87Rsa4096PssSha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_RSA4096_PSS_SHA512); + } + } + + /** Signature for MLDSA87-ECDSA-P384-SHA512. */ + public static class Mldsa87EcdsaP384Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa87EcdsaP384Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_ECDSA_P384_SHA512); + } + } + + /** Signature for MLDSA87-ECDSA-P521-SHA512. */ + public static class Mldsa87EcdsaP521Sha512 extends OpenSslSignatureCompositeMlDsa { + public Mldsa87EcdsaP521Sha512() { + super(CompositeMlDsaAlgorithm.MLDSA87_ECDSA_P521_SHA512); + } + } + + private void resetDigest() { + try { + switch (algorithm.getPreHashAlgorithm()) { + case "SHA-512": + messageDigest = new OpenSSLMessageDigestJDK.SHA512(); + break; + case "SHA-256": + messageDigest = new OpenSSLMessageDigestJDK.SHA256(); + break; + default: + throw new IllegalStateException("Unsupported pre-hash algorithm: " + + algorithm.getPreHashAlgorithm()); + } + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Failed to create message digest", e); + } + } + + @Override + protected void engineUpdate(byte b) throws SignatureException { + if (messageDigest == null) { + throw new SignatureException("Not initialized"); + } + messageDigest.engineUpdate(b); + } + + @Override + protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { + if (messageDigest == null) { + throw new SignatureException("Not initialized"); + } + messageDigest.engineUpdate(b, off, len); + } + + @Override + @SuppressWarnings("deprecation") // SignatureSpi requires implementing this deprecated method. + protected Object engineGetParameter(String param) { + return null; + } + + @Override + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + if (!(privateKey instanceof OpenSslCompositeMlDsaPrivateKey)) { + throw new InvalidKeyException("Require OpenSslCompositeMlDsaPrivateKey"); + } + if (((OpenSslCompositeMlDsaPrivateKey) privateKey).getCompositeMlDsaAlgorithm() + != algorithm) { + throw new InvalidKeyException( + "Algorithm mismatch: expected " + algorithm + ", got " + + ((OpenSslCompositeMlDsaPrivateKey) privateKey).getCompositeMlDsaAlgorithm()); + } + this.signKey = (OpenSslCompositeMlDsaPrivateKey) privateKey; + this.verifyKey = null; + resetDigest(); + } + + @Override + protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException { + if (!(publicKey instanceof OpenSslCompositeMlDsaPublicKey)) { + throw new InvalidKeyException("Require OpenSslCompositeMlDsaPublicKey"); + } + if (((OpenSslCompositeMlDsaPublicKey) publicKey).getCompositeMlDsaAlgorithm() + != algorithm) { + throw new InvalidKeyException( + "Algorithm mismatch: expected " + algorithm + ", got " + + ((OpenSslCompositeMlDsaPublicKey) publicKey).getCompositeMlDsaAlgorithm()); + } + this.verifyKey = (OpenSslCompositeMlDsaPublicKey) publicKey; + this.signKey = null; + resetDigest(); + } + + @Override + @SuppressWarnings("deprecation") // SignatureSpi requires implementing this deprecated method. + protected void engineSetParameter(String param, Object value) {} + + // https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html#name-pre-hashing + private byte[] createMessageRepresentative() throws SignatureException { + byte[] prefix = "CompositeAlgorithmSignatures2025".getBytes(US_ASCII); + byte[] label = algorithm.getLabel().getBytes(US_ASCII); + + if (messageDigest == null) { + throw new SignatureException("Not initialized"); + } + + byte[] messageHash = messageDigest.engineDigest(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + outputStream.write(prefix); + outputStream.write(label); + outputStream.write((byte) 0); // len(ctx) + outputStream.write(messageHash); + } catch (IOException e) { + throw new SignatureException("Failed to create message representative", e); + } + + return outputStream.toByteArray(); + } + + @Override + protected byte[] engineSign() throws SignatureException { + if (signKey == null) { + // Should never happen. + throw new SignatureException("No key provided"); + } + + // Calculate message representative. + byte[] messageRepresentative = createMessageRepresentative(); + + // Calculate ML-DSA signature with `ctx`. + // (`ctx` here means the signature context in Algorithm 2 in + // https://nvlpubs.nist.gov/nistpubs/fips/nist.fips.204.pdf) + NativeRef.EVP_MD_CTX ctxLocal = new NativeRef.EVP_MD_CTX(NativeCrypto.EVP_MD_CTX_create()); + long evpPkeyCtx = NativeCrypto.EVP_DigestSignInit( + ctxLocal, 0, signKey.getMlDsaPrivateKey().getOpenSSLKey().getNativeRef()); + byte[] mldsaCtx = algorithm.getLabel().getBytes(US_ASCII); + NativeCrypto.EVP_PKEY_CTX_set1_signature_context_string(evpPkeyCtx, mldsaCtx); + byte[] mldsaSig = NativeCrypto.EVP_DigestSign(ctxLocal, messageRepresentative, 0, + messageRepresentative.length); + + // Calculate traditional signature. + byte[] tradSig; + switch (algorithm.getClassicAlgorithm()) { + case "Ed25519": + OpenSslSignatureEdDsa edDsaSigner = new OpenSslSignatureEdDsa(); + try { + edDsaSigner.engineInitSign(signKey.getClassicPrivateKey()); + } catch (InvalidKeyException e) { + throw new SignatureException("Failed to initialize Ed25519 signature", e); + } + edDsaSigner.engineUpdate(messageRepresentative, 0, messageRepresentative.length); + tradSig = edDsaSigner.engineSign(); + break; + case "RSA": + OpenSSLSignature rsaSigner; + String classicSignatureDigest = algorithm.getClassicSignatureDigest(); + if (classicSignatureDigest == null) { + throw new IllegalStateException("Classic signature digest is null"); + } + if (algorithm.isPss()) { + if (classicSignatureDigest.equals("SHA-256")) { + rsaSigner = new OpenSSLSignature.SHA256RSAPSS(); + } else if (classicSignatureDigest.equals("SHA-384")) { + rsaSigner = new OpenSSLSignature.SHA384RSAPSS(); + } else if (classicSignatureDigest.equals("SHA-512")) { + rsaSigner = new OpenSSLSignature.SHA512RSAPSS(); + } else { + throw new IllegalStateException("Unsupported digest: " + + classicSignatureDigest); + } + } else { + // PKCS#1 v1.5 + if (classicSignatureDigest.equals("SHA-256")) { + rsaSigner = new OpenSSLSignature.SHA256RSA(); + } else if (classicSignatureDigest.equals("SHA-384")) { + rsaSigner = new OpenSSLSignature.SHA384RSA(); + } else if (classicSignatureDigest.equals("SHA-512")) { + rsaSigner = new OpenSSLSignature.SHA512RSA(); + } else { + throw new IllegalStateException("Unsupported digest: " + + classicSignatureDigest); + } + } + try { + rsaSigner.engineInitSign(signKey.getClassicPrivateKey()); + } catch (InvalidKeyException e) { + throw new SignatureException("Failed to initialize RSA signature", e); + } + rsaSigner.engineUpdate(messageRepresentative, 0, messageRepresentative.length); + tradSig = rsaSigner.engineSign(); + break; + case "EC": + OpenSSLSignature ecDsaSigner; + classicSignatureDigest = algorithm.getClassicSignatureDigest(); + if (classicSignatureDigest == null) { + throw new IllegalStateException("Classic signature digest is null"); + } + if (classicSignatureDigest.equals("SHA-256")) { + ecDsaSigner = new OpenSSLSignature.SHA256ECDSA(); + } else if (classicSignatureDigest.equals("SHA-384")) { + ecDsaSigner = new OpenSSLSignature.SHA384ECDSA(); + } else if (classicSignatureDigest.equals("SHA-512")) { + ecDsaSigner = new OpenSSLSignature.SHA512ECDSA(); + } else { + throw new IllegalStateException("Unsupported digest: " + + classicSignatureDigest); + } + try { + ecDsaSigner.engineInitSign(signKey.getClassicPrivateKey()); + } catch (InvalidKeyException e) { + throw new SignatureException("Failed to initialize EC signature", e); + } + ecDsaSigner.engineUpdate(messageRepresentative, 0, messageRepresentative.length); + tradSig = ecDsaSigner.engineSign(); + break; + default: + throw new IllegalStateException("Unsupported classic algorithm: " + + algorithm.getClassicAlgorithm()); + } + + byte[] sig = new byte[mldsaSig.length + tradSig.length]; + System.arraycopy(mldsaSig, 0, sig, 0, mldsaSig.length); + System.arraycopy(tradSig, 0, sig, mldsaSig.length, tradSig.length); + + return sig; + } + + @Override + protected boolean engineVerify(byte[] sigBytes) throws SignatureException { + if (verifyKey == null) { + // Should never happen. + throw new SignatureException("No verify key provided"); + } + + byte[] messageRepresentative = createMessageRepresentative(); + + int mldsaSigLength; + if (algorithm.getMlDsaAlgorithm().equals(MlDsaAlgorithm.ML_DSA_44)) { + mldsaSigLength = 2420; + } else if (algorithm.getMlDsaAlgorithm().equals(MlDsaAlgorithm.ML_DSA_65)) { + mldsaSigLength = 3309; + } else if (algorithm.getMlDsaAlgorithm().equals(MlDsaAlgorithm.ML_DSA_87)) { + mldsaSigLength = 4627; + } else { + throw new IllegalStateException("Unsupported ML-DSA algorithm: " + + algorithm.getMlDsaAlgorithm()); + } + + if (sigBytes.length <= mldsaSigLength) { + return false; + } + + byte[] mldsaSig = Arrays.copyOfRange(sigBytes, 0, mldsaSigLength); + byte[] tradSig = Arrays.copyOfRange(sigBytes, mldsaSigLength, sigBytes.length); + + // Verify ML-DSA signature with `ctx`. + NativeRef.EVP_MD_CTX ctxLocal = new NativeRef.EVP_MD_CTX(NativeCrypto.EVP_MD_CTX_create()); + long evpPkeyCtx = NativeCrypto.EVP_DigestVerifyInit( + ctxLocal, 0, verifyKey.getMlDsaPublicKey().getOpenSSLKey().getNativeRef()); + byte[] mldsaCtx = algorithm.getLabel().getBytes(US_ASCII); + NativeCrypto.EVP_PKEY_CTX_set1_signature_context_string(evpPkeyCtx, mldsaCtx); + boolean mldsaVerified = NativeCrypto.EVP_DigestVerify( + ctxLocal, mldsaSig, 0, mldsaSig.length, messageRepresentative, 0, + messageRepresentative.length); + + boolean tradVerified = false; + switch (algorithm.getClassicAlgorithm()) { + case "Ed25519": + OpenSslSignatureEdDsa edDsaVerifier = new OpenSslSignatureEdDsa(); + try { + edDsaVerifier.engineInitVerify(verifyKey.getClassicPublicKey()); + } catch (InvalidKeyException e) { + throw new SignatureException("Failed to initialize Ed25519 signature", e); + } + edDsaVerifier.engineUpdate(messageRepresentative, 0, messageRepresentative.length); + tradVerified = edDsaVerifier.engineVerify(tradSig); + break; + case "RSA": + OpenSSLSignature rsaVerifier; + String classicSignatureDigest = algorithm.getClassicSignatureDigest(); + if (classicSignatureDigest == null) { + throw new IllegalStateException("Classic signature digest is null"); + } + if (algorithm.isPss()) { + if (classicSignatureDigest.equals("SHA-256")) { + rsaVerifier = new OpenSSLSignature.SHA256RSAPSS(); + } else if (classicSignatureDigest.equals("SHA-384")) { + rsaVerifier = new OpenSSLSignature.SHA384RSAPSS(); + } else if (classicSignatureDigest.equals("SHA-512")) { + rsaVerifier = new OpenSSLSignature.SHA512RSAPSS(); + } else { + throw new IllegalStateException("Unsupported digest: " + + classicSignatureDigest); + } + } else { + // PKCS#1 v1.5 + if (classicSignatureDigest.equals("SHA-256")) { + rsaVerifier = new OpenSSLSignature.SHA256RSA(); + } else if (classicSignatureDigest.equals("SHA-384")) { + rsaVerifier = new OpenSSLSignature.SHA384RSA(); + } else if (classicSignatureDigest.equals("SHA-512")) { + rsaVerifier = new OpenSSLSignature.SHA512RSA(); + } else { + throw new IllegalStateException("Unsupported digest: " + + classicSignatureDigest); + } + } + try { + rsaVerifier.engineInitVerify(verifyKey.getClassicPublicKey()); + } catch (InvalidKeyException e) { + throw new SignatureException("Failed to initialize RSA signature", e); + } + rsaVerifier.engineUpdate(messageRepresentative, 0, messageRepresentative.length); + tradVerified = rsaVerifier.engineVerify(tradSig); + break; + case "EC": + OpenSSLSignature ecDsaVerifier; + classicSignatureDigest = algorithm.getClassicSignatureDigest(); + if (classicSignatureDigest == null) { + throw new IllegalStateException("Classic signature digest is null"); + } + if (classicSignatureDigest.equals("SHA-256")) { + ecDsaVerifier = new OpenSSLSignature.SHA256ECDSA(); + } else if (classicSignatureDigest.equals("SHA-384")) { + ecDsaVerifier = new OpenSSLSignature.SHA384ECDSA(); + } else if (classicSignatureDigest.equals("SHA-512")) { + ecDsaVerifier = new OpenSSLSignature.SHA512ECDSA(); + } else { + throw new IllegalStateException("Unsupported digest: " + + classicSignatureDigest); + } + try { + ecDsaVerifier.engineInitVerify(verifyKey.getClassicPublicKey()); + } catch (InvalidKeyException e) { + throw new SignatureException("Failed to initialize EC signature", e); + } + ecDsaVerifier.engineUpdate(messageRepresentative, 0, messageRepresentative.length); + tradVerified = ecDsaVerifier.engineVerify(tradSig); + break; + default: + throw new IllegalStateException("Unsupported classic algorithm: " + + algorithm.getClassicAlgorithm()); + } + return (mldsaVerified && tradVerified); + } +} diff --git a/common/src/main/java/org/conscrypt/OpenSslSlhDsaKeyFactory.java b/common/src/main/java/org/conscrypt/OpenSslSlhDsaKeyFactory.java index af498c8f1..88b77e2c3 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSlhDsaKeyFactory.java +++ b/common/src/main/java/org/conscrypt/OpenSslSlhDsaKeyFactory.java @@ -69,7 +69,7 @@ protected PublicKey engineGeneratePublic(KeySpec keySpec) throws InvalidKeySpecE + keySpec.getClass().getName()); } EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; - if ("raw".equalsIgnoreCase(encodedKeySpec.getFormat())) { + if (AddressUtils.asciiEqualsIgnoreCase(encodedKeySpec.getFormat(), "raw")) { byte[] raw = encodedKeySpec.getEncoded(); return makePublicKeyFromRaw(raw); } @@ -110,7 +110,7 @@ protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws InvalidKeySpe + keySpec.getClass().getName()); } EncodedKeySpec encodedKeySpec = (EncodedKeySpec) keySpec; - if ("raw".equalsIgnoreCase(encodedKeySpec.getFormat())) { + if (AddressUtils.asciiEqualsIgnoreCase(encodedKeySpec.getFormat(), "raw")) { byte[] raw = encodedKeySpec.getEncoded(); return makePrivateKeyFromRaw(raw); } diff --git a/common/src/main/java/org/conscrypt/OpenSslSlhDsaPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslSlhDsaPrivateKey.java index 20da4781f..8028acc02 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSlhDsaPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslSlhDsaPrivateKey.java @@ -53,7 +53,7 @@ public byte[] getEncoded() { return ArrayUtils.concat(OpenSslSlhDsaKeyFactory.pkcs8Preamble, raw); } - byte[] getRaw() { + public byte[] getRaw() { if (raw == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslSlhDsaPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslSlhDsaPublicKey.java index b7bb74d4d..70e255843 100644 --- a/common/src/main/java/org/conscrypt/OpenSslSlhDsaPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslSlhDsaPublicKey.java @@ -52,7 +52,7 @@ public byte[] getEncoded() { return ArrayUtils.concat(OpenSslSlhDsaKeyFactory.x509Preamble, raw); } - byte[] getRaw() { + public byte[] getRaw() { if (raw == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java b/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java index c2d76cd62..059a01052 100644 --- a/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslXwingPrivateKey.java @@ -50,7 +50,7 @@ public OpenSslXwingPrivateKey(EncodedKeySpec keySpec) throws InvalidKeySpecExcep if (raw.length != PRIVATE_KEY_SIZE_BYTES) { throw new InvalidKeySpecException("Invalid key size"); } - } else if (keySpec.getFormat().equalsIgnoreCase("raw")) { + } else if (AddressUtils.asciiEqualsIgnoreCase(keySpec.getFormat(), "raw")) { if (encoded.length != PRIVATE_KEY_SIZE_BYTES) { throw new InvalidKeySpecException("Invalid key size"); } @@ -85,7 +85,7 @@ public byte[] getEncoded() { return ArrayUtils.concat(pkcs8Preamble, raw); } - byte[] getRaw() { + public byte[] getRaw() { if (raw == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/OpenSslXwingPublicKey.java b/common/src/main/java/org/conscrypt/OpenSslXwingPublicKey.java index 1210a3c85..c1f93f8d6 100644 --- a/common/src/main/java/org/conscrypt/OpenSslXwingPublicKey.java +++ b/common/src/main/java/org/conscrypt/OpenSslXwingPublicKey.java @@ -50,7 +50,7 @@ public OpenSslXwingPublicKey(EncodedKeySpec keySpec) throws InvalidKeySpecExcept if (raw.length != PUBLIC_KEY_SIZE_BYTES) { throw new InvalidKeySpecException("Invalid key size"); } - } else if (keySpec.getFormat().equalsIgnoreCase("raw")) { + } else if (AddressUtils.asciiEqualsIgnoreCase(keySpec.getFormat(), "raw")) { if (encoded.length != PUBLIC_KEY_SIZE_BYTES) { throw new InvalidKeySpecException("Invalid key size"); } @@ -85,7 +85,7 @@ public byte[] getEncoded() { return ArrayUtils.concat(x509Preamble, raw); } - byte[] getRaw() { + public byte[] getRaw() { if (raw == null) { throw new IllegalStateException("key is destroyed"); } diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java index 55a0d2665..4571af83d 100644 --- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java +++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java @@ -17,6 +17,8 @@ package org.conscrypt; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.security.AlgorithmConstraints; import java.security.KeyManagementException; import java.security.KeyStore; @@ -29,11 +31,13 @@ import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.logging.Logger; import javax.crypto.SecretKey; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SNIMatcher; +import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; @@ -49,6 +53,8 @@ * socket or not. */ final class SSLParametersImpl implements Cloneable { + private static final Logger logger = Logger.getLogger(SSLParametersImpl.class.getName()); + // default source of X.509 certificate based authentication keys private static volatile X509KeyManager defaultX509KeyManager; // default source of X.509 certificate based authentication trust decisions @@ -73,6 +79,8 @@ final class SSLParametersImpl implements Cloneable { private final Spake2PlusTrustManager spake2PlusTrustManager; // source of Spake authentication or null if not provided private final Spake2PlusKeyManager spake2PlusKeyManager; + // getNetworkSecurityPolicy reflected method for x509TrustManager + private final Method getNetworkSecurityPolicy; // protocols enabled for SSL connection String[] enabledProtocols; @@ -109,14 +117,9 @@ final class SSLParametersImpl implements Cloneable { byte[] applicationProtocols = EmptyArray.BYTE; ApplicationProtocolSelectorAdapter applicationProtocolSelector; boolean useSessionTickets; + byte[] echConfigList; private Boolean useSni; - /** - * Whether the TLS Channel ID extension is enabled. This field is - * server-side only. - */ - boolean channelIdEnabled; - /** * Initializes the parameters. Naturally this constructor is used * in SSLContextImpl.engineInit method which directly passes its @@ -168,21 +171,21 @@ final class SSLParametersImpl implements Cloneable { "Spake2PlusTrustManager and Spake2PlusKeyManager should be set together"); } + getNetworkSecurityPolicy = getNetworkSecurityPolicyMethod(x509TrustManager); + // initialize the list of cipher suites and protocols enabled by default if (isSpake()) { enabledProtocols = new String[] {NativeCrypto.SUPPORTED_PROTOCOL_TLSV1_3}; } else if (protocols == null) { enabledProtocols = NativeCrypto.getDefaultProtocols().clone(); } else { - String[] filteredProtocols = filterFromProtocols( - protocols, - Arrays.asList(!Platform.isTlsV1Filtered() - ? new String[0] - : new String[] { - NativeCrypto.OBSOLETE_PROTOCOL_SSLV3, - NativeCrypto.DEPRECATED_PROTOCOL_TLSV1, - NativeCrypto.DEPRECATED_PROTOCOL_TLSV1_1, - })); + String[] filteredProtocols = + filterFromProtocols(protocols, Arrays.asList(Platform.isTlsV1Filtered() + ? new String[] { + NativeCrypto.OBSOLETE_PROTOCOL_SSLV3, + NativeCrypto.DEPRECATED_PROTOCOL_TLSV1, + NativeCrypto.DEPRECATED_PROTOCOL_TLSV1_1,} + : new String[0])); isEnabledProtocolsFiltered = protocols.length != filteredProtocols.length; enabledProtocols = NativeCrypto.checkEnabledProtocols(filteredProtocols).clone(); } @@ -213,6 +216,7 @@ private SSLParametersImpl(ClientSessionContext clientSessionContext, this.x509KeyManager = x509KeyManager; this.pskKeyManager = pskKeyManager; this.x509TrustManager = x509TrustManager; + this.getNetworkSecurityPolicy = getNetworkSecurityPolicyMethod(x509TrustManager); this.spake2PlusKeyManager = spake2PlusKeyManager; this.spake2PlusTrustManager = spake2PlusTrustManager; @@ -238,9 +242,12 @@ private SSLParametersImpl(ClientSessionContext clientSessionContext, : sslParams.applicationProtocols.clone(); this.applicationProtocolSelector = sslParams.applicationProtocolSelector; this.useSessionTickets = sslParams.useSessionTickets; + this.echConfigList = + (sslParams.echConfigList == null) ? null : sslParams.echConfigList.clone(); this.useSni = sslParams.useSni; - this.channelIdEnabled = sslParams.channelIdEnabled; this.namedGroups = (sslParams.namedGroups == null) ? null : sslParams.namedGroups.clone(); + this.sniMatchers = (sslParams.sniMatchers == null) ? null : new ArrayList<>(sslParams.sniMatchers); + this.algorithmConstraints = sslParams.algorithmConstraints; } /** @@ -254,6 +261,17 @@ void initSpake() throws KeyManagementException { } } + private Method getNetworkSecurityPolicyMethod(X509TrustManager tm) { + if (tm == null) { + return null; + } + try { + return tm.getClass().getMethod("getNetworkSecurityPolicy"); + } catch (NoSuchMethodException ignored) { + return null; + } + } + static SSLParametersImpl getDefault() throws KeyManagementException { SSLParametersImpl result = defaultParameters; if (result == null) { @@ -476,6 +494,10 @@ void setUseSessionTickets(boolean useSessionTickets) { this.useSessionTickets = useSessionTickets; } + void setEchConfigList(byte[] echConfigList) { + this.echConfigList = echConfigList; + } + /* * Whether connections using this SSL connection should use the TLS * extension Server Name Indication (SNI). @@ -558,9 +580,9 @@ private static String[] filterFromCipherSuites(String[] cipherSuites, Set SSL3_RT_MAX_PACKET_SIZE) { + // Neither SSLv3 or TLSv1 (i.e. SSLv2 or bad data) or oversized packet return -1; } return packetLength; diff --git a/common/src/main/java/org/conscrypt/SessionSnapshot.java b/common/src/main/java/org/conscrypt/SessionSnapshot.java index 5e92a059d..96c496218 100644 --- a/common/src/main/java/org/conscrypt/SessionSnapshot.java +++ b/common/src/main/java/org/conscrypt/SessionSnapshot.java @@ -42,6 +42,7 @@ final class SessionSnapshot implements ConscryptSession { private final String peerHost; private final String applicationProtocol; private final int peerPort; + private X509Certificate[] peerCertificates; private final String[] peerSupportedSignatureAlgorithms; private final String[] localSupportedSignatureAlgorithms; @@ -58,6 +59,11 @@ final class SessionSnapshot implements ConscryptSession { peerHost = session.getPeerHost(); peerPort = session.getPeerPort(); applicationProtocol = session.getApplicationProtocol(); + try { + peerCertificates = session.getPeerCertificates(); + } catch (SSLPeerUnverifiedException e) { + peerCertificates = null; + } peerSupportedSignatureAlgorithms = session.getPeerSupportedSignatureAlgorithms(); localSupportedSignatureAlgorithms = session.getLocalSupportedSignatureAlgorithms(); } @@ -137,7 +143,8 @@ public String[] getValueNames() { @Override public X509Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { - throw new SSLPeerUnverifiedException("No peer certificates"); + checkPeerCertificatesPresent(); + return peerCertificates.clone(); } @Override @@ -158,7 +165,8 @@ public javax.security.cert.X509Certificate[] getPeerCertificateChain() @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - throw new SSLPeerUnverifiedException("No peer certificates"); + checkPeerCertificatesPresent(); + return peerCertificates[0].getSubjectX500Principal(); } @Override @@ -201,6 +209,15 @@ public String getApplicationProtocol() { return applicationProtocol; } + /** + * Throw SSLPeerUnverifiedException on null or empty peerCertificates array + */ + private void checkPeerCertificatesPresent() throws SSLPeerUnverifiedException { + if (peerCertificates == null || peerCertificates.length == 0) { + throw new SSLPeerUnverifiedException("No peer certificates"); + } + } + @Override public String[] getPeerSupportedSignatureAlgorithms() { return peerSupportedSignatureAlgorithms != null ? peerSupportedSignatureAlgorithms.clone() diff --git a/common/src/main/java/org/conscrypt/Spake2PlusKeyManager.java b/common/src/main/java/org/conscrypt/Spake2PlusKeyManager.java index bb324c9ce..7a384b7cf 100644 --- a/common/src/main/java/org/conscrypt/Spake2PlusKeyManager.java +++ b/common/src/main/java/org/conscrypt/Spake2PlusKeyManager.java @@ -24,7 +24,6 @@ /** * Provider of key material for Spake2Plus - * @hide This class is not part of the Android public SDK API */ @Internal public class Spake2PlusKeyManager implements KeyManager { diff --git a/common/src/main/java/org/conscrypt/Spake2PlusTrustManager.java b/common/src/main/java/org/conscrypt/Spake2PlusTrustManager.java index c6b56dbf3..5acf72f10 100644 --- a/common/src/main/java/org/conscrypt/Spake2PlusTrustManager.java +++ b/common/src/main/java/org/conscrypt/Spake2PlusTrustManager.java @@ -20,7 +20,6 @@ /** * Noop TrustManager for Spake2Plus - * @hide This class is not part of the Android public SDK API */ @Internal public class Spake2PlusTrustManager implements TrustManager { diff --git a/common/src/main/java/org/conscrypt/TrustManagerImpl.java b/common/src/main/java/org/conscrypt/TrustManagerImpl.java index adb206e5b..557c3bef1 100644 --- a/common/src/main/java/org/conscrypt/TrustManagerImpl.java +++ b/common/src/main/java/org/conscrypt/TrustManagerImpl.java @@ -34,6 +34,8 @@ package org.conscrypt; +import org.conscrypt.metrics.CertificateValidationFailureReason; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; @@ -61,6 +63,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import java.util.logging.Logger; import javax.net.ssl.HttpsURLConnection; @@ -72,15 +75,21 @@ /** * TrustManager implementation. The implementation is based on CertPathValidator - * PKIX and CertificateFactory X509 implementations. This implementations should + * PKIX and CertificateFactory X509 implementations. These implementations should * be provided by some certification provider. * * @see javax.net.ssl.X509ExtendedTrustManager + * @see org.conscrypt.ConscryptX509TrustManager */ @Internal @SuppressWarnings("CustomX509TrustManager") -public final class TrustManagerImpl extends X509ExtendedTrustManager { +public final class TrustManagerImpl + extends X509ExtendedTrustManager implements ConscryptX509TrustManager { private static final Logger logger = Logger.getLogger(TrustManagerImpl.class.getName()); + /** RFC 5280 recommends maxPathLength defaults; 10 untrusted + anchors is generous. */ + private static final int MAX_UNTRUSTED_CHAIN_LENGTH = 20; + /** Hard cap on DFS node visits across all backtracking branches. */ + private static final int MAX_PATH_BUILD_ITERATIONS = 2048; /** * Comparator used for ordering trust anchors during certificate path building. @@ -102,6 +111,15 @@ public final class TrustManagerImpl extends X509ExtendedTrustManager { */ private final CertPinManager pinManager; + /** + * The ConscryptNetworkSecurityPolicy associated with this TrustManager. + * + * The policy is used to decide if various mechanisms should be enabled, + * mostly based on the process configuration and the hostname queried. The + * policy is aligned with Android's libcore NetworkSecurityPolicy. + */ + private ConscryptNetworkSecurityPolicy policy; + /** * The backing store for the AndroidCAStore if non-null. This will * be null when the rootKeyStore is null, implying we are not @@ -153,12 +171,6 @@ public TrustManagerImpl(KeyStore keyStore, CertPinManager manager) { public TrustManagerImpl(KeyStore keyStore, CertPinManager manager, ConscryptCertStore certStore) { - this(keyStore, manager, certStore, null, null); - } - - private TrustManagerImpl(KeyStore keyStore, CertPinManager manager, - ConscryptCertStore certStore, CertBlocklist blocklist, - org.conscrypt.ct.CertificateTransparency ct) { CertPathValidator validatorLocal = null; CertificateFactory factoryLocal = null; KeyStore rootKeyStoreLocal = null; @@ -188,13 +200,6 @@ private TrustManagerImpl(KeyStore keyStore, CertPinManager manager, errLocal = e; } - if (ct == null) { - ct = Platform.newDefaultCertificateTransparency(); - } - if (blocklist == null) { - blocklist = Platform.newDefaultBlocklist(); - } - this.pinManager = manager; this.rootKeyStore = rootKeyStoreLocal; this.trustedCertificateStore = trustedCertificateStoreLocal; @@ -204,8 +209,24 @@ private TrustManagerImpl(KeyStore keyStore, CertPinManager manager, this.intermediateIndex = new TrustedCertificateIndex(); this.acceptedIssuers = acceptedIssuersLocal; this.err = errLocal; - this.blocklist = blocklist; - this.ct = ct; + this.policy = ConscryptNetworkSecurityPolicy.getDefault(); + this.blocklist = Platform.newDefaultBlocklist(); + this.ct = Platform.newDefaultCertificateTransparency(new Supplier() { + @Override + public NetworkSecurityPolicy get() { + return policy; + } + }); + } + + @Override + public void setNetworkSecurityPolicy(ConscryptNetworkSecurityPolicy policy) { + this.policy = policy; + } + + @Override + public ConscryptNetworkSecurityPolicy getNetworkSecurityPolicy() { + return policy; } @SuppressWarnings("JdkObsolete") // KeyStore#aliases is the only API available @@ -298,12 +319,24 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) /** * For backward compatibility with older Android API that used String for the hostname only. */ + @Override public List checkServerTrusted(X509Certificate[] chain, String authType, String hostname) throws CertificateException { return checkTrusted(chain, null /* ocspData */, null /* tlsSctData */, authType, hostname, false); } + /** + * For compatibility with network stacks that cannot provide an SSLSession nor a + * Socket (e.g., Cronet). + */ + @Override + public List checkServerTrusted(X509Certificate[] chain, byte[] ocspData, + byte[] tlsSctData, String authType, + String hostname) throws CertificateException { + return checkTrusted(chain, ocspData, tlsSctData, authType, hostname, false); + } + /** * Returns the full trusted certificate chain found from {@code certs}. *

@@ -472,8 +505,9 @@ private List checkTrusted(X509Certificate[] certs, byte[] ocspD untrustedChain.add(leaf); } used.add(leaf); - return checkTrustedRecursive(certs, ocspData, tlsSctData, host, clientAuth, untrustedChain, - trustedChain, used); + int[] budget = { MAX_PATH_BUILD_ITERATIONS }; + return checkTrustedRecursive(certs, ocspData, tlsSctData, host, clientAuth, + untrustedChain, trustedChain, used, budget); } /** @@ -504,8 +538,16 @@ private List checkTrustedRecursive(X509Certificate[] certs, byt boolean clientAuth, List untrustedChain, List trustAnchorChain, - Set used) + Set used, + int[] budget) throws CertificateException { + if (--budget[0] < 0) { + throw new CertificateException("Path-building iteration limit exceeded"); + } + if (untrustedChain.size() > MAX_UNTRUSTED_CHAIN_LENGTH) { + throw new CertificateException("Certificate chain too long"); + } + CertificateException lastException = null; X509Certificate current; if (trustAnchorChain.isEmpty()) { @@ -541,7 +583,7 @@ private List checkTrustedRecursive(X509Certificate[] certs, byt trustAnchorChain.add(anchor); try { return checkTrustedRecursive(certs, ocspData, tlsSctData, host, clientAuth, - untrustedChain, trustAnchorChain, used); + untrustedChain, trustAnchorChain, used, budget); } catch (CertificateException ex) { lastException = ex; } @@ -588,7 +630,7 @@ private List checkTrustedRecursive(X509Certificate[] certs, byt untrustedChain.add(candidateIssuer); try { return checkTrustedRecursive(certs, ocspData, tlsSctData, host, clientAuth, - untrustedChain, trustAnchorChain, used); + untrustedChain, trustAnchorChain, used, budget); } catch (CertificateException ex) { lastException = ex; } @@ -611,7 +653,7 @@ private List checkTrustedRecursive(X509Certificate[] certs, byt untrustedChain.add(intermediateCert); try { return checkTrustedRecursive(certs, ocspData, tlsSctData, host, clientAuth, - untrustedChain, trustAnchorChain, used); + untrustedChain, trustAnchorChain, used, budget); } catch (CertificateException ex) { lastException = ex; } @@ -628,6 +670,8 @@ private List checkTrustedRecursive(X509Certificate[] certs, byt // 7. If no errors were encountered above then verifyChain was never called because it was // not possible to build a valid chain to a trusted certificate. CertPath certPath = factory.generateCertPath(untrustedChain); + Platform.getStatsLog().reportCertificationValidationFailure( + CertificateValidationFailureReason.NO_TRUST_ANCHOR, certs.length); throw new CertificateException(new CertPathValidatorException( "Trust anchor for certification path not found.", null, certPath, -1)); } @@ -662,7 +706,8 @@ private List verifyChain(List untrustedChain, } // Check Certificate Transparency (if required). - if (!clientAuth && host != null && ct != null && ct.isCTVerificationRequired(host)) { + if (!clientAuth && host != null && ct != null + && policy.isCertificateTransparencyVerificationRequired(host)) { ct.checkCT(wholeChain, ocspData, tlsSctData, host); } diff --git a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java index eae9fbadb..d28b03cd2 100644 --- a/common/src/main/java/org/conscrypt/ct/CertificateEntry.java +++ b/common/src/main/java/org/conscrypt/ct/CertificateEntry.java @@ -143,4 +143,17 @@ public void encode(OutputStream output) throws SerializationException { } Serialization.writeVariableBytes(output, certificate, Constants.CERTIFICATE_LENGTH_BYTES); } + + /** + * Expected size of the encoded CertificateEntry structure. + */ + public int encodedLength() { + int size = Constants.LOG_ENTRY_TYPE_LENGTH; + if (entryType == LogEntryType.PRECERT_ENTRY) { + size += issuerKeyHash.length; + } + size += Constants.CERTIFICATE_LENGTH_BYTES; + size += certificate.length; + return size; + } } diff --git a/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java b/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java index 1e13cdf6d..21aeee6c3 100644 --- a/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java +++ b/common/src/main/java/org/conscrypt/ct/CertificateTransparency.java @@ -17,6 +17,7 @@ package org.conscrypt.ct; import org.conscrypt.Internal; +import org.conscrypt.NetworkSecurityPolicy; import org.conscrypt.Platform; import org.conscrypt.metrics.CertificateTransparencyVerificationReason; import org.conscrypt.metrics.StatsLog; @@ -25,6 +26,7 @@ import java.security.cert.X509Certificate; import java.util.List; import java.util.Objects; +import java.util.function.Supplier; /** * Certificate Transparency subsystem. The implementation contains references @@ -36,9 +38,11 @@ public class CertificateTransparency { private Verifier verifier; private Policy policy; private StatsLog statsLog; + private Supplier policySupplier; public CertificateTransparency(LogStore logStore, Policy policy, Verifier verifier, - StatsLog statsLog) { + StatsLog statsLog, + Supplier policySupplier) { Objects.requireNonNull(logStore); Objects.requireNonNull(policy); Objects.requireNonNull(verifier); @@ -48,14 +52,11 @@ public CertificateTransparency(LogStore logStore, Policy policy, Verifier verifi this.policy = policy; this.verifier = verifier; this.statsLog = statsLog; + this.policySupplier = policySupplier; } - public boolean isCTVerificationRequired(String host) { - return Platform.isCTVerificationRequired(host); - } - - public CertificateTransparencyVerificationReason reasonCTVerificationRequired(String host) { - return Platform.reasonCTVerificationRequired(host); + public CertificateTransparencyVerificationReason getVerificationReason(String host) { + return policySupplier.get().getCertificateTransparencyVerificationReason(host); } public void checkCT(List chain, byte[] ocspData, byte[] tlsData, String host) @@ -67,16 +68,27 @@ public void checkCT(List chain, byte[] ocspData, byte[] tlsData statsLog.reportCTVerificationResult(logStore, /* VerificationResult */ null, /* PolicyCompliance */ null, - reasonCTVerificationRequired(host)); + getVerificationReason(host)); + return; + } + VerificationResult result; + try { + result = verifier.verifySignedCertificateTimestamps(chain, tlsData, ocspData); + } catch (LogStore.InvalidLogException e) { + /* Fail open. While validating an SCT, we found out that some data + * in the LogStore is unusable. It is likely that the LogStore is + * corrupted. */ + statsLog.reportCTVerificationResult(logStore, + /* VerificationResult */ null, + /* PolicyCompliance */ null, + getVerificationReason(host)); return; } - VerificationResult result = - verifier.verifySignedCertificateTimestamps(chain, tlsData, ocspData); X509Certificate leaf = chain.get(0); PolicyCompliance compliance = policy.doesResultConformToPolicy(result, leaf); statsLog.reportCTVerificationResult(logStore, result, compliance, - reasonCTVerificationRequired(host)); + getVerificationReason(host)); if (compliance != PolicyCompliance.COMPLY) { throw new CertificateException( "Certificate chain does not conform to required transparency policy: " diff --git a/common/src/main/java/org/conscrypt/ct/LogInfo.java b/common/src/main/java/org/conscrypt/ct/LogInfo.java index 8c3b82638..6412f2fe7 100644 --- a/common/src/main/java/org/conscrypt/ct/LogInfo.java +++ b/common/src/main/java/org/conscrypt/ct/LogInfo.java @@ -29,7 +29,7 @@ /** * Properties about a Certificate Transparency Log. - * This object stores information about a CT log, its public key, description and URL. + * This object stores information about a CT log, its public key and URL. * It allows verification of SCTs against the log's public key. */ @Internal @@ -42,13 +42,16 @@ public class LogInfo { public static final int STATE_RETIRED = 5; public static final int STATE_REJECTED = 6; + public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_RFC6962 = 1; + public static final int TYPE_STATIC_CT_API = 2; + private final byte[] logId; private final PublicKey publicKey; private final int state; private final long stateTimestamp; - private final String description; - private final String url; private final String operator; + private final int type; private LogInfo(Builder builder) { /* Based on the required fields for the log list schema v3. Notably, @@ -56,16 +59,14 @@ private LogInfo(Builder builder) { * is validated in the builder. */ Objects.requireNonNull(builder.logId); Objects.requireNonNull(builder.publicKey); - Objects.requireNonNull(builder.url); Objects.requireNonNull(builder.operator); this.logId = builder.logId; this.publicKey = builder.publicKey; this.state = builder.state; this.stateTimestamp = builder.stateTimestamp; - this.description = builder.description; - this.url = builder.url; this.operator = builder.operator; + this.type = builder.type; } public static class Builder { @@ -73,9 +74,8 @@ public static class Builder { private PublicKey publicKey; private int state; private long stateTimestamp; - private String description; - private String url; private String operator; + private int type; public Builder setPublicKey(PublicKey publicKey) { Objects.requireNonNull(publicKey); @@ -98,24 +98,20 @@ public Builder setState(int state, long timestamp) { return this; } - public Builder setDescription(String description) { - Objects.requireNonNull(description); - this.description = description; - return this; - } - - public Builder setUrl(String url) { - Objects.requireNonNull(url); - this.url = url; - return this; - } - public Builder setOperator(String operator) { Objects.requireNonNull(operator); this.operator = operator; return this; } + public Builder setType(int type) { + if (type < 0 || type > TYPE_STATIC_CT_API) { + throw new IllegalArgumentException("invalid type value"); + } + this.type = type; + return this; + } + public LogInfo build() { return new LogInfo(this); } @@ -132,14 +128,6 @@ public PublicKey getPublicKey() { return publicKey; } - public String getDescription() { - return description; - } - - public String getUrl() { - return url; - } - public int getState() { return state; } @@ -159,6 +147,10 @@ public String getOperator() { return operator; } + public int getType() { + return type; + } + @Override public boolean equals(Object other) { if (this == other) { @@ -169,16 +161,14 @@ public boolean equals(Object other) { } LogInfo that = (LogInfo) other; - return this.state == that.state && this.description.equals(that.description) - && this.url.equals(that.url) && this.operator.equals(that.operator) - && this.stateTimestamp == that.stateTimestamp + return this.state == that.state && this.operator.equals(that.operator) + && this.stateTimestamp == that.stateTimestamp && this.type == that.type && Arrays.equals(this.logId, that.logId); } @Override public int hashCode() { - return Objects.hash(Arrays.hashCode(logId), description, url, state, stateTimestamp, - operator); + return Objects.hash(Arrays.hashCode(logId), state, stateTimestamp, operator, type); } /** diff --git a/common/src/main/java/org/conscrypt/ct/LogStore.java b/common/src/main/java/org/conscrypt/ct/LogStore.java index 1e614dc9e..6daecbf1f 100644 --- a/common/src/main/java/org/conscrypt/ct/LogStore.java +++ b/common/src/main/java/org/conscrypt/ct/LogStore.java @@ -29,6 +29,13 @@ public enum State { NON_COMPLIANT, } + /** Thrown when the LogStore was found to be unusable while retrieving a log's information. */ + public class InvalidLogException extends Exception { + public InvalidLogException(Throwable cause) { + super(cause); + } + } + State getState(); int getMajorVersion(); @@ -41,5 +48,5 @@ public enum State { long getTimestamp(); - LogInfo getKnownLog(byte[] logId); + LogInfo getKnownLog(byte[] logId) throws InvalidLogException; } diff --git a/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java b/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java index 68cffbb6e..c84297937 100644 --- a/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java +++ b/common/src/main/java/org/conscrypt/ct/PolicyCompliance.java @@ -18,4 +18,11 @@ import org.conscrypt.Internal; -@Internal public enum PolicyCompliance { COMPLY, NOT_ENOUGH_SCTS, NOT_ENOUGH_DIVERSE_SCTS } +@Internal +public enum PolicyCompliance { + COMPLY, + NOT_ENOUGH_SCTS, + + NOT_ENOUGH_DIVERSE_SCTS, + NO_RFC6962_LOG +} diff --git a/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java b/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java index f9a737f66..9e89f5ce9 100644 --- a/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java +++ b/common/src/main/java/org/conscrypt/ct/SignedCertificateTimestamp.java @@ -135,11 +135,22 @@ public void encodeTBS(OutputStream output, CertificateEntry certEntry) Serialization.writeVariableBytes(output, extensions, Constants.EXTENSIONS_LENGTH_BYTES); } + private int encodedLength(CertificateEntry certEntry) { + int size = Constants.VERSION_LENGTH; + size += Constants.SIGNATURE_TYPE_LENGTH; + size += Constants.TIMESTAMP_LENGTH; + size += certEntry.encodedLength(); + size += Constants.EXTENSIONS_LENGTH_BYTES; + size += extensions.length; + return size; + } + /** * TLS encode the signed part of the SCT, as described by RFC6962 section 3.2. */ public byte[] encodeTBS(CertificateEntry certEntry) throws SerializationException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); + int bufferSize = encodedLength(certEntry); + ByteArrayOutputStream output = new ByteArrayOutputStream(bufferSize); encodeTBS(output, certEntry); return output.toByteArray(); } diff --git a/common/src/main/java/org/conscrypt/ct/Verifier.java b/common/src/main/java/org/conscrypt/ct/Verifier.java index 8dd3dbbef..db1e2e89f 100644 --- a/common/src/main/java/org/conscrypt/ct/Verifier.java +++ b/common/src/main/java/org/conscrypt/ct/Verifier.java @@ -37,7 +37,7 @@ public Verifier(LogStore store) { public VerificationResult verifySignedCertificateTimestamps(List chain, byte[] tlsData, byte[] ocspData) - throws CertificateEncodingException { + throws CertificateEncodingException, LogStore.InvalidLogException { OpenSSLX509Certificate[] certs = new OpenSSLX509Certificate[chain.size()]; int i = 0; for (X509Certificate cert : chain) { @@ -54,7 +54,7 @@ public VerificationResult verifySignedCertificateTimestamps(List scts, - OpenSSLX509Certificate[] chain, VerificationResult result) { + OpenSSLX509Certificate[] chain, VerificationResult result) + throws LogStore.InvalidLogException { // Avoid creating the cert entry if we don't need it if (scts.isEmpty()) { return; @@ -110,7 +111,8 @@ private void verifyEmbeddedSCTs(List scts, * The result of the verification for each sct is added to {@code result}. */ private void verifyExternalSCTs(List scts, - OpenSSLX509Certificate leaf, VerificationResult result) { + OpenSSLX509Certificate leaf, VerificationResult result) + throws LogStore.InvalidLogException { // Avoid creating the cert entry if we don't need it if (scts.isEmpty()) { return; @@ -131,7 +133,7 @@ private void verifyExternalSCTs(List scts, * Verify a list of SCTs. */ private void verifySCTs(List scts, CertificateEntry certEntry, - VerificationResult result) { + VerificationResult result) throws LogStore.InvalidLogException { for (SignedCertificateTimestamp sct : scts) { VerifiedSCT.Builder builder = new VerifiedSCT.Builder(sct); LogInfo log = store.getKnownLog(sct.getLogID()); diff --git a/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java b/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java index 0c7fab7aa..ef4a5b999 100644 --- a/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java +++ b/common/src/main/java/org/conscrypt/metrics/CertificateTransparencyVerificationReason.java @@ -16,8 +16,10 @@ package org.conscrypt.metrics; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_DRY_RUN; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_APP_OPT_IN; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_SDK_TARGET_DEFAULT_ENABLED; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_UNKNOWN; import org.conscrypt.Internal; @@ -30,7 +32,10 @@ public enum CertificateTransparencyVerificationReason { UNKNOWN(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_UNKNOWN), APP_OPT_IN(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_APP_OPT_IN), DOMAIN_OPT_IN( - CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN); + CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN), + SDK_TARGET_DEFAULT_ENABLED( + CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_SDK_TARGET_DEFAULT_ENABLED), + DRY_RUN(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_DRY_RUN); final int id; diff --git a/common/src/main/java/org/conscrypt/metrics/CertificateValidationFailureReason.java b/common/src/main/java/org/conscrypt/metrics/CertificateValidationFailureReason.java new file mode 100644 index 000000000..638d4f950 --- /dev/null +++ b/common/src/main/java/org/conscrypt/metrics/CertificateValidationFailureReason.java @@ -0,0 +1,39 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt.metrics; + +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_NO_TRUST_ANCHOR; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_UNKNOWN; + +import org.conscrypt.Internal; + +@Internal +public enum CertificateValidationFailureReason { + UNKNOWN(CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_UNKNOWN), + NO_TRUST_ANCHOR( + CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_NO_TRUST_ANCHOR); + + final int id; + + public int getId() { + return this.id; + } + + private CertificateValidationFailureReason(int id) { + this.id = id; + } +} diff --git a/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java index a94a4e760..c035accab 100644 --- a/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java +++ b/common/src/main/java/org/conscrypt/metrics/ConscryptStatsLog.java @@ -62,10 +62,28 @@ public final class ConscryptStatsLog { /** * CertificateTransparencyVerificationReported certificate_transparency_verification_reported
- * Usage: StatsLog.write(StatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, int result, int reason, int policy_compatibility_version, int major_version, int minor_version, int num_cert_scts, int num_ocsp_scts, int num_tls_scts);
+ * Usage: StatsLog.write(StatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, int result, int reason, int policy_compatibility_version, int major_version, int minor_version, int num_cert_scts, int num_ocsp_scts, int num_tls_scts, int uid, long timestamp_millis);
*/ public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED = 989; + /** + * CertificateBlocklistBlockReported certificate_blocklist_block_reported
+ * Usage: StatsLog.write(StatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED, int source, int index, int uid);
+ */ + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED = 1143; + + /** + * CertificateValidationFailureReported certificate_validation_failure_reported
+ * Usage: StatsLog.write(StatsLog.CERTIFICATE_VALIDATION_FAILURE_REPORTED, int reason, int chain_length, int uid);
+ */ + public static final int CERTIFICATE_VALIDATION_FAILURE_REPORTED = 1326; + + /** + * TlsEncryptedClientHelloHandshakeReported tls_encrypted_client_hello_handshake_reported
+ * Usage: StatsLog.write(StatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED, int result, int usage_reason, int skip_reason, int failure_reason, int handshake_duration_millis, int uid);
+ */ + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED = 1327; + // Constants for enum values. // Values for TlsHandshakeReported.protocol @@ -120,10 +138,14 @@ public final class ConscryptStatsLog { // Values for CertificateTransparencyLogListStateChanged.loaded_compat_version public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__LOADED_COMPAT_VERSION__COMPAT_VERSION_UNKNOWN = 0; public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__LOADED_COMPAT_VERSION__COMPAT_VERSION_V1 = 1; + public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__LOADED_COMPAT_VERSION__COMPAT_VERSION_V2 = 2; + public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__LOADED_COMPAT_VERSION__COMPAT_VERSION_V3 = 3; // Values for CertificateTransparencyLogListStateChanged.min_compat_version public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__MIN_COMPAT_VERSION__COMPAT_VERSION_UNKNOWN = 0; public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__MIN_COMPAT_VERSION__COMPAT_VERSION_V1 = 1; + public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__MIN_COMPAT_VERSION__COMPAT_VERSION_V2 = 2; + public static final int CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__MIN_COMPAT_VERSION__COMPAT_VERSION_V3 = 3; // Values for ConscryptServiceUsed.algorithm public static final int CONSCRYPT_SERVICE_USED__ALGORITHM__UNKNOWN_ALGORITHM = 0; @@ -172,6 +194,8 @@ public final class ConscryptStatsLog { public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAILURE_SCTS_NOT_COMPLIANT = 4; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_NO_LOG_LIST_AVAILABLE = 5; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_LOG_LIST_NOT_COMPLIANT = 6; + public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_LOG_LIST_DATE_INVALID = 7; + public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_LOG_LIST_DATE_EXPIRED = 8; // Values for CertificateTransparencyVerificationReported.reason public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_UNKNOWN = 0; @@ -179,10 +203,57 @@ public final class ConscryptStatsLog { public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_SDK_TARGET_DEFAULT_ENABLED = 2; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_APP_OPT_IN = 3; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_NSCONFIG_DOMAIN_OPT_IN = 4; + public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__REASON__REASON_DRY_RUN = 5; + // Values for CertificateTransparencyVerificationReported.policy_compatibility_version public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__POLICY_COMPATIBILITY_VERSION__COMPAT_VERSION_UNKNOWN = 0; public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__POLICY_COMPATIBILITY_VERSION__COMPAT_VERSION_V1 = 1; + public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__POLICY_COMPATIBILITY_VERSION__COMPAT_VERSION_V2 = 2; + public static final int CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__POLICY_COMPATIBILITY_VERSION__COMPAT_VERSION_V3 = 3; + + // Values for CertificateBlocklistBlockReported.source + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_UNKNOWN = 0; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_TEST = 1; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_BUILT_IN = 2; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_FILE = 3; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_TEST = 4; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_BUILT_IN = 5; + public static final int CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_FILE = 6; + + // Values for CertificateValidationFailureReported.reason + public static final int CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_UNKNOWN = 0; + public static final int CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_NO_TRUST_ANCHOR = 1; + + // Values for TlsEncryptedClientHelloHandshakeReported.result + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_UNKNOWN = 0; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS = 1; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS_GREASE = 2; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_FAILURE = 3; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SKIPPED = 4; + + // Values for TlsEncryptedClientHelloHandshakeReported.usage_reason + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_UNKNOWN = 0; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_DEFAULT = 1; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_SDK_TARGET = 2; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_APP_OPT_IN = 3; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_DOMAIN_OPT_IN = 4; + + // Values for TlsEncryptedClientHelloHandshakeReported.skip_reason + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNKNOWN = 0; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SDK_TARGET = 1; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_APP_OPT_OUT = 2; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_DOMAIN_OPT_OUT = 3; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NO_CONFIG = 4; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SERVER_SNI_MISMATCH = 5; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNSUPPORTED_TLS_VERSION = 6; + + // Values for TlsEncryptedClientHelloHandshakeReported.failure_reason + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_UNKNOWN = 0; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_SERVER_REJECTION = 1; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INVALID_CONFIG = 2; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INCONSISTENT_NEGOTIATION = 3; + public static final int TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_NO_RETRY_CONFIGS = 4; // Write methods public static void write(int code, boolean arg1, int arg2, int arg3, int arg4, int arg5, int[] arg6) { @@ -224,7 +295,33 @@ public static void write(int code, int arg1, int arg2, int arg3, int arg4, int a ReflexiveStatsLog.write(builder.build()); } - public static void write(int code, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8) { + public static void write(int code, int arg1, int arg2, int arg3) { + final ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder(); + builder.setAtomId(code); + builder.writeInt(arg1); + builder.writeInt(arg2); + builder.writeInt(arg3); + + builder.usePooledBuffer(); + ReflexiveStatsLog.write(builder.build()); + } + + public static void write(int code, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6) { + final ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder(); + builder.setAtomId(code); + builder.writeInt(arg1); + builder.writeInt(arg2); + builder.writeInt(arg3); + builder.writeInt(arg4); + builder.writeInt(arg5); + builder.writeInt(arg6); + + builder.usePooledBuffer(); + ReflexiveStatsLog.write(builder.build()); + } + + @SuppressWarnings("NewApi") + public static void write(int code, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, long arg10) { final ReflexiveStatsEvent.Builder builder = ReflexiveStatsEvent.newBuilder(); builder.setAtomId(code); builder.writeInt(arg1); @@ -235,6 +332,8 @@ public static void write(int code, int arg1, int arg2, int arg3, int arg4, int a builder.writeInt(arg6); builder.writeInt(arg7); builder.writeInt(arg8); + builder.writeInt(arg9); + builder.writeLong(arg10); builder.usePooledBuffer(); ReflexiveStatsLog.write(builder.build()); diff --git a/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java b/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java index 1144e3350..ab3125c90 100644 --- a/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java +++ b/common/src/main/java/org/conscrypt/metrics/NoopStatsLog.java @@ -15,6 +15,7 @@ */ package org.conscrypt.metrics; +import org.conscrypt.CertBlocklistEntry; import org.conscrypt.Internal; import org.conscrypt.ct.LogStore; import org.conscrypt.ct.PolicyCompliance; @@ -38,4 +39,11 @@ public void updateCTLogListStatusChanged(LogStore logStore) {} public void reportCTVerificationResult(LogStore logStore, VerificationResult result, PolicyCompliance compliance, CertificateTransparencyVerificationReason reason) {} + + public void reportBlocklistHit(CertBlocklistEntry entry) {} + + public void reportCertificationValidationFailure(CertificateValidationFailureReason reason, + int chainLength) {} + + public void reportTlsEchHandshake(TlsEncryptedClientHelloHandshake handshake) {} } diff --git a/common/src/main/java/org/conscrypt/metrics/OptionalMethod.java b/common/src/main/java/org/conscrypt/metrics/OptionalMethod.java index 6c20ca743..10fa6830f 100644 --- a/common/src/main/java/org/conscrypt/metrics/OptionalMethod.java +++ b/common/src/main/java/org/conscrypt/metrics/OptionalMethod.java @@ -22,8 +22,6 @@ /** * Helper class to handle reflexive loading and invocation of methods which may be absent. - * - * @hide This class is not part of the Android public SDK API */ @Internal public final class OptionalMethod { diff --git a/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsEvent.java b/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsEvent.java index 64236b42a..390c38db4 100644 --- a/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsEvent.java +++ b/common/src/main/java/org/conscrypt/metrics/ReflexiveStatsEvent.java @@ -92,6 +92,7 @@ public static final class Builder { private static final OptionalMethod setAtomId; private static final OptionalMethod writeBoolean; private static final OptionalMethod writeInt; + private static final OptionalMethod writeLong; private static final OptionalMethod build; private static final OptionalMethod usePooledBuffer; private static final OptionalMethod writeIntArray; @@ -101,6 +102,7 @@ public static final class Builder { setAtomId = new OptionalMethod(c_statsEvent_Builder, "setAtomId", int.class); writeBoolean = new OptionalMethod(c_statsEvent_Builder, "writeBoolean", boolean.class); writeInt = new OptionalMethod(c_statsEvent_Builder, "writeInt", int.class); + writeLong = new OptionalMethod(c_statsEvent_Builder, "writeLong", long.class); build = new OptionalMethod(c_statsEvent_Builder, "build"); usePooledBuffer = new OptionalMethod(c_statsEvent_Builder, "usePooledBuffer"); writeIntArray = new OptionalMethod(c_statsEvent_Builder, "writeIntArray", int[].class); @@ -135,6 +137,11 @@ public Builder writeInt(final int value) { return this; } + public Builder writeLong(final long value) { + writeLong.invoke(this.builder, value); + return this; + } + public void usePooledBuffer() { usePooledBuffer.invoke(this.builder); } diff --git a/common/src/main/java/org/conscrypt/metrics/StatsLog.java b/common/src/main/java/org/conscrypt/metrics/StatsLog.java index 0779946d7..8294e8276 100644 --- a/common/src/main/java/org/conscrypt/metrics/StatsLog.java +++ b/common/src/main/java/org/conscrypt/metrics/StatsLog.java @@ -15,6 +15,7 @@ */ package org.conscrypt.metrics; +import org.conscrypt.CertBlocklistEntry; import org.conscrypt.Internal; import org.conscrypt.ct.LogStore; import org.conscrypt.ct.PolicyCompliance; @@ -30,4 +31,11 @@ public void countTlsHandshake(boolean success, String protocol, String cipherSui public void reportCTVerificationResult(LogStore logStore, VerificationResult result, PolicyCompliance compliance, CertificateTransparencyVerificationReason reason); + + public void reportBlocklistHit(CertBlocklistEntry entry); + + public void reportCertificationValidationFailure(CertificateValidationFailureReason reason, + int chainLength); + + public void reportTlsEchHandshake(TlsEncryptedClientHelloHandshake handshake); } diff --git a/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java b/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java index 9a5365ef1..724d3d7c4 100644 --- a/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java +++ b/common/src/main/java/org/conscrypt/metrics/StatsLogImpl.java @@ -15,6 +15,14 @@ */ package org.conscrypt.metrics; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_BUILT_IN; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_FILE; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_TEST; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_BUILT_IN; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_FILE; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_TEST; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_UNKNOWN; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__STATUS__STATUS_EXPIRED; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_LOG_LIST_STATE_CHANGED__STATUS__STATUS_NOT_FOUND; @@ -28,23 +36,109 @@ import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_NO_LOG_LIST_AVAILABLE; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_SUCCESS; import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_UNKNOWN; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_VALIDATION_FAILURE_REPORTED; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_NO_TRUST_ANCHOR; +import static org.conscrypt.metrics.ConscryptStatsLog.CERTIFICATE_VALIDATION_FAILURE_REPORTED__REASON__CERTIFICATE_VALIDATION_FAILURE_REASON_UNKNOWN; import static org.conscrypt.metrics.ConscryptStatsLog.TLS_HANDSHAKE_REPORTED; +import static org.conscrypt.metrics.ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED; +import org.conscrypt.CertBlocklistEntry; import org.conscrypt.Internal; import org.conscrypt.Platform; import org.conscrypt.ct.LogStore; import org.conscrypt.ct.PolicyCompliance; import org.conscrypt.ct.VerificationResult; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; + /** * Implements logging for Conscrypt metrics. */ @Internal public final class StatsLogImpl implements StatsLog { - private static final StatsLog INSTANCE = new StatsLogImpl(); - private StatsLogImpl() {} + // The queue capacity is capped to prevent memory exhaustion if logging requests + // flood in faster than the background thread can process them. + private static final int QUEUE_CAPACITY = 100; + + private final BlockingQueue logQueue; + private final ExecutorService writerThreadExecutor; + + /** + * Initialization-on-demand holder idiom. + * Thread-safe and lazy. Crucial to avoid spawning the writer thread while + * running in the Zygote process, as thread creation in Zygote is forbidden + * and causes crashes/deadlocks after fork. + */ + private static class Holder { + private static final StatsLogImpl INSTANCE = new StatsLogImpl(); + } + + private StatsLogImpl() { + this.logQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY); + this.writerThreadExecutor = + Executors.newSingleThreadExecutor(new LowPriorityThreadFactory()); + startWriterThread(); + } + + /** + * Returns the singleton instance of StatsLogImpl. + * Using a singleton ensures all logging requests from various connections + * are funneled into a single shared queue and processed by a single background thread, + * conserving system resources. + */ public static StatsLog getInstance() { - return INSTANCE; + return Holder.INSTANCE; + } + + public void stop() { + // shutdownNow() sends an interrupt to the running thread + writerThreadExecutor.shutdownNow(); + try { + writerThreadExecutor.awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void startWriterThread() { + writerThreadExecutor.execute(() -> { + try { + // Loop until the thread is interrupted + while (!Thread.currentThread().isInterrupted()) { + // Blocks until a log task is available. + // If interrupted while blocking, it throws InterruptedException. + Runnable logTask = logQueue.take(); + logTask.run(); + } + } catch (InterruptedException e) { + // Thread was interrupted while blocking in take(). Allow it to exit the loop. + } finally { + // TRADEOFF: We drain the queue on shutdown to ensure best-effort delivery + // of the remaining metrics (crucial for unit/integration test stability). + // However, this can delay shutdown by up to the executor's termination timeout + // (5 seconds) if the log tasks block. In production, the OS usually terminates + // the process abruptly (SIGKILL), so this delay only affects clean teardowns. + while (!logQueue.isEmpty()) { + Runnable logTask = logQueue.poll(); + if (logTask != null) { + logTask.run(); + } + } + } + }); + } + + private static class LowPriorityThreadFactory implements ThreadFactory { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "ConscryptStatsLogWriter"); + thread.setPriority(Thread.MIN_PRIORITY); + return thread; + } } @Override @@ -89,12 +183,21 @@ private static int policyComplianceToMetrics(VerificationResult result, } else if (result.getValidSCTs().size() == 0) { return CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAILURE_NO_SCTS_FOUND; } else if (compliance == PolicyCompliance.NOT_ENOUGH_SCTS - || compliance == PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS) { + || compliance == PolicyCompliance.NOT_ENOUGH_DIVERSE_SCTS + || compliance == PolicyCompliance.NO_RFC6962_LOG) { return CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAILURE_SCTS_NOT_COMPLIANT; } return CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_UNKNOWN; } + private static int getUid() { + int[] uids = Platform.getUids(); + if (uids != null && uids.length != 0) { + return uids[0]; + } + return 0; + } + @Override public void reportCTVerificationResult(LogStore store, VerificationResult result, PolicyCompliance compliance, @@ -103,19 +206,64 @@ public void reportCTVerificationResult(LogStore store, VerificationResult result || store.getState() == LogStore.State.MALFORMED) { write(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_NO_LOG_LIST_AVAILABLE, - reason.getId(), 0, 0, 0, 0, 0, 0); + reason.getId(), store.getCompatVersion(), store.getMajorVersion(), + store.getMinorVersion(), 0, 0, 0, getUid(), store.getTimestamp()); } else if (store.getState() == LogStore.State.NON_COMPLIANT) { write(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED__RESULT__RESULT_FAIL_OPEN_LOG_LIST_NOT_COMPLIANT, - reason.getId(), 0, 0, 0, 0, 0, 0); + reason.getId(), store.getCompatVersion(), store.getMajorVersion(), + store.getMinorVersion(), 0, 0, 0, getUid(), store.getTimestamp()); } else if (store.getState() == LogStore.State.COMPLIANT) { int comp = policyComplianceToMetrics(result, compliance); write(CERTIFICATE_TRANSPARENCY_VERIFICATION_REPORTED, comp, reason.getId(), store.getCompatVersion(), store.getMajorVersion(), store.getMinorVersion(), - result.numCertSCTs(), result.numOCSPSCTs(), result.numTlsSCTs()); + result.numCertSCTs(), result.numOCSPSCTs(), result.numTlsSCTs(), getUid(), + store.getTimestamp()); } } + private static int blocklistOriginToMetrics(CertBlocklistEntry.Origin origin) { + switch (origin) { + case SHA1_TEST: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_TEST; + case SHA1_BUILT_IN: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_BUILT_IN; + case SHA1_FILE: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA1_FILE; + case SHA256_TEST: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_TEST; + case SHA256_BUILT_IN: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_BUILT_IN; + case SHA256_FILE: + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_SHA256_FILE; + } + return CERTIFICATE_BLOCKLIST_BLOCK_REPORTED__SOURCE__BLOCKLIST_SOURCE_UNKNOWN; + } + + @Override + public void reportBlocklistHit(CertBlocklistEntry entry) { + write(CERTIFICATE_BLOCKLIST_BLOCK_REPORTED, blocklistOriginToMetrics(entry.getOrigin()), + entry.getIndex(), getUid()); + } + + @Override + public void reportCertificationValidationFailure(CertificateValidationFailureReason reason, + int chainLength) { + write(CERTIFICATE_VALIDATION_FAILURE_REPORTED, reason.getId(), chainLength, getUid()); + } + + @Override + public void reportTlsEchHandshake(TlsEncryptedClientHelloHandshake handshake) { + write( + TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED, + handshake.getResult().getMetricsValue(), + handshake.getUsageReason().getMetricsValue(), + handshake.getSkipReason().getMetricsValue(), + handshake.getFailureReason().getMetricsValue(), + handshake.getHandshakeDurationMillis(), + getUid()); + } + private static final boolean sdkVersionBiggerThan32; static { @@ -137,7 +285,9 @@ private void write(int atomId, boolean success, int protocol, int cipherSuite, i builder.usePooledBuffer(); ReflexiveStatsLog.write(builder.build()); } else { - ConscryptStatsLog.write(atomId, success, protocol, cipherSuite, duration, source, uids); + logQueue.offer(() + -> ConscryptStatsLog.write(atomId, success, protocol, + cipherSuite, duration, source, uids)); } } @@ -149,9 +299,20 @@ private void write(int atomId, int status, int loadedCompatVersion, private void write(int atomId, int verificationResult, int verificationReason, int policyCompatVersion, int majorVersion, int minorVersion, - int numEmbeddedScts, int numOcspScts, int numTlsScts) { + int numEmbeddedScts, int numOcspScts, int numTlsScts, int uid, + long logListTimestampMillis) { ConscryptStatsLog.write(atomId, verificationResult, verificationReason, policyCompatVersion, majorVersion, minorVersion, numEmbeddedScts, numOcspScts, - numTlsScts); + numTlsScts, uid, logListTimestampMillis); + } + + private void write(int atomId, int origin, int index, int uid) { + ConscryptStatsLog.write(atomId, origin, index, uid); + } + + private void write(int atomId, int result, int usageReason, int skipReason, int failureReason, + int handshakeDurationMillis, int uid) { + ConscryptStatsLog.write(atomId, result, usageReason, skipReason, failureReason, + handshakeDurationMillis, uid); } } diff --git a/common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java b/common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java new file mode 100644 index 000000000..0bff5cf7a --- /dev/null +++ b/common/src/main/java/org/conscrypt/metrics/TlsEncryptedClientHelloHandshake.java @@ -0,0 +1,296 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.conscrypt.metrics; + +import org.conscrypt.DomainEncryptionMode; +import org.conscrypt.EchOptions; +import org.conscrypt.Internal; +import org.conscrypt.NetworkSecurityPolicy; + +/** + * A TLS handshake with Encrypted Client Hello attempted, for the purpose of reporting. + */ +@Internal +public class TlsEncryptedClientHelloHandshake { + enum Result { + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_UNKNOWN), + SUCCESS( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS), + SUCCESS_WITH_GREASE( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SUCCESS_GREASE), + FAILURE( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_FAILURE), + SKIPPED( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__RESULT__ECH_RESULT_SKIPPED); + + private final int metricsValue; + + public int getMetricsValue() { + return metricsValue; + } + + private Result(int metricsValue) { + this.metricsValue = metricsValue; + } + } + + enum UsageReason { + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_UNKNOWN), + DEFAULT( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_DEFAULT), + SDK_TARGET( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_SDK_TARGET), + NSC_APP_OPT_IN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_APP_OPT_IN), + NSC_DOMAIN_OPT_IN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__USAGE_REASON__ECH_REASON_NSC_DOMAIN_OPT_IN); + + private final int metricsValue; + + public int getMetricsValue() { + return metricsValue; + } + + private UsageReason(int metricsValue) { + this.metricsValue = metricsValue; + } + } + + enum SkipReason { + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNKNOWN), + SDK_TARGET( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SDK_TARGET), + NSC_APP_OPT_OUT( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_APP_OPT_OUT), + NSC_DOMAIN_OPT_OUT( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NSC_DOMAIN_OPT_OUT), + NO_CONFIG( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_NO_CONFIG), + SERVER_SNI_MISMATCH( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_SERVER_SNI_MISMATCH), + UNSUPPORTED_TLS_VERSION( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__SKIP_REASON__ECH_SKIP_REASON_UNSUPPORTED_TLS_VERSION); + + private final int metricsValue; + + public int getMetricsValue() { + return metricsValue; + } + + private SkipReason(int metricsValue) { + this.metricsValue = metricsValue; + } + } + + public enum FailureReason { + UNKNOWN( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_UNKNOWN), + SERVER_REJECTION( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_SERVER_REJECTION), + INVALID_CONFIG( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INVALID_CONFIG), + INCONSISTENT_NEGOTIATION( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_INCONSISTENT_NEGOTIATION), + NO_RETRY_CONFIG( + ConscryptStatsLog.TLS_ENCRYPTED_CLIENT_HELLO_HANDSHAKE_REPORTED__FAILURE_REASON__ECH_FAILURE_REASON_NO_RETRY_CONFIGS); + + private final int metricsValue; + + public int getMetricsValue() { + return metricsValue; + } + + private FailureReason(int metricsValue) { + this.metricsValue = metricsValue; + } + } + + private final Result result; + private final UsageReason usageReason; + private final SkipReason skipReason; + private final FailureReason failureReason; + private final int handshakeDurationMillis; + + private TlsEncryptedClientHelloHandshake(Result result, UsageReason usageReason, + SkipReason skipReason, FailureReason failureReason, int handshakeDurationMillis) { + this.result = result; + this.usageReason = usageReason; + this.skipReason = skipReason; + this.failureReason = failureReason; + this.handshakeDurationMillis = handshakeDurationMillis; + } + + public static final class Builder { + private Result result = Result.UNKNOWN; + private EchOptions opts; + private FailureReason failureReason = FailureReason.UNKNOWN; + private NetworkSecurityPolicy policy; + private String hostname; + private byte[] retryConfigs; + private int handshakeDurationMillis; + private boolean handshakeSuccess; + + public Builder() { + } + + public Builder setEchOptions(EchOptions opts) { + this.opts = opts; + return this; + } + + public Builder setFailureReason(FailureReason failureReason) { + this.failureReason = failureReason; + return this; + } + + public Builder setPolicy(NetworkSecurityPolicy policy) { + this.policy = policy; + return this; + } + + public Builder setHostname(String hostname) { + this.hostname = hostname; + return this; + } + + public Builder setRetryConfigs(byte[] retryConfigs) { + this.retryConfigs = retryConfigs; + + this.failureReason = (this.retryConfigs == null || this.retryConfigs.length == 0) + ? FailureReason.NO_RETRY_CONFIG + : FailureReason.SERVER_REJECTION; + + return this; + } + + public Builder setHandshakeDurationMillis(int handshakeDurationMillis) { + this.handshakeDurationMillis = handshakeDurationMillis; + return this; + } + + public Builder setHandshakeSuccess(boolean handshakeSuccess) { + this.handshakeSuccess = handshakeSuccess; + return this; + } + + private Result calculateResult() { + if (!handshakeSuccess || failureReason != FailureReason.UNKNOWN) { + return Result.FAILURE; + } + + if (calculateSkipReason() != SkipReason.UNKNOWN) { + return (opts != null && opts.isGreaseEnabled()) + ? Result.SUCCESS_WITH_GREASE : Result.SKIPPED; + } + + return Result.SUCCESS; + } + + private UsageReason calculateUsageReason() { + if (policy == null || opts == null) { + // If policy is null, we can't access the NSC settings so no UsageReason can be + // determined. This may happen if the socket closes before the policy can be set. + // If opts is null, we aren't using ECH so no UsageReason needed. + return UsageReason.UNKNOWN; + } + + if (policy.getDomainEncryptionMode("") == DomainEncryptionMode.OPPORTUNISTIC || + policy.getDomainEncryptionMode(hostname) == DomainEncryptionMode.OPPORTUNISTIC) { + return UsageReason.DEFAULT; + } + + return (policy.getDomainEncryptionMode("") == DomainEncryptionMode.ENABLED || + policy.getDomainEncryptionMode("") == DomainEncryptionMode.REQUIRED) + ? UsageReason.NSC_APP_OPT_IN + : UsageReason.NSC_DOMAIN_OPT_IN; + } + + private SkipReason calculateSkipReason() { + if (policy == null) { + // If policy is null, we can't access the NSC settings so no SkipReason can be + // determined. This may happen if the socket closes before the policy can be set. + return SkipReason.UNKNOWN; + } + + if (opts == null) { + return policy.getDomainEncryptionMode("") == DomainEncryptionMode.DISABLED + ? SkipReason.NSC_APP_OPT_OUT + : SkipReason.NSC_DOMAIN_OPT_OUT; + } + + if (opts.getConfigList() == null) { + return SkipReason.NO_CONFIG; + } + + return SkipReason.UNKNOWN; + } + + public TlsEncryptedClientHelloHandshake build() { + return new TlsEncryptedClientHelloHandshake( + calculateResult(), calculateUsageReason(), calculateSkipReason(), + failureReason, handshakeDurationMillis); + } + } + + /** + * Returns the result of the handshake. + */ + public Result getResult() { + return this.result; + } + + /** + * Returns the reason for trying Encrypted Client Hello. + */ + public UsageReason getUsageReason() { + return this.usageReason; + } + + /** + * Returns the reason for skipping Encrypted Client Hello. + */ + public SkipReason getSkipReason() { + return this.skipReason; + } + + /** + * Returns the reason why Encrypted Client Hello failed on the handshake. + */ + public FailureReason getFailureReason() { + return this.failureReason; + } + + /** + * Returns the duration of the whole TLS handshake in milliseconds. + */ + public int getHandshakeDurationMillis() { + return this.handshakeDurationMillis; + } + + /** + * Returns true if we should emit the ECH handshake atom or not. + * + *

The bulk of handshakes will skip ECH due to no config being provided. Use this as a + * heuristic for whether to emit the ECH handshake atom or not. + */ + public boolean shouldReportEchHandshake() { + return result != Result.SKIPPED || skipReason != SkipReason.NO_CONFIG; + } +} diff --git a/common/src/test/java/org/conscrypt/CompositeMlDsaTest.java b/common/src/test/java/org/conscrypt/CompositeMlDsaTest.java new file mode 100644 index 000000000..c95a84e4e --- /dev/null +++ b/common/src/test/java/org/conscrypt/CompositeMlDsaTest.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2026 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.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +@RunWith(JUnit4.class) +public class CompositeMlDsaTest { + private static final Provider PROVIDER = TestUtils.getConscryptProvider(); + + private static Signature getInstance(CompositeMlDsaAlgorithm alg) throws Exception { + return Signature.getInstance(alg.getName(), PROVIDER); + } + + @Test + public void verifyTestVectors() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + byte[] message = TestUtils.decodeBase64(tv.getString("m")); + String algName = tv.getString("tcid"); + assertTrue(algName.startsWith("id-")); + algName = algName.substring(3); + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature signature = getInstance(alg); + + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + TestUtils.decodeBase64(tv.getString("pk")), alg); + byte[] sigBytes = TestUtils.decodeBase64(tv.getString("s")); + + signature.initVerify(publicKey); + signature.update(message, 0, message.length); + + assertTrue("Signature verification failed for algorithm: " + algName + + " (tcId: " + tv.getString("tcid") + ")", + signature.verify(sigBytes)); + } + } + + @Test + public void signVerify_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + byte[] message = TestUtils.decodeBase64(tv.getString("m")); + String algName = tv.getString("tcid"); + assertTrue(algName.startsWith("id-")); + algName = algName.substring(3); + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature signature = getInstance(alg); + OpenSslCompositeMlDsaPrivateKey privateKey = new OpenSslCompositeMlDsaPrivateKey( + TestUtils.decodeBase64(tv.getString("sk")), alg); + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + TestUtils.decodeBase64(tv.getString("pk")), alg); + + signature.initSign(privateKey); + signature.update(message, 0, message.length); + byte[] signatureBytes = signature.sign(); + signature.initVerify(publicKey); + signature.update(message, 0, message.length); + + assertTrue("Signature verification failed for algorithm: " + algName + + " (tcId: " + tv.getString("tcid") + ")", + signature.verify(signatureBytes)); + } + } + + private static final String TEST_M = + "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4="; + private static final String TEST_PK = + "FlplaQIgVuYEA04nN9VXyMWK4XDhRhnaGaWO6FqoCtiDr5rL89omEFSIf811XxbbHpKBJzROqBqKfbPgsrMRZj" + + "BdHeRn5Oi4pK7bshdjBkXm7yCqPfMC50PkzKI5CFTsw13ikA4UmJuBupSxXDzhEchHLCybpQPBdr++" + + "Tr4NKh0cEaDi9GFlDdzLsDP/1d9Dh1N57rHOCPLi/" + + "7tjmqPYrCzFspEADbBkKBfwNgPRajzMl8FnQB8qQfMzK5tiO6jKEb+" + + "fd0C5UhwOYe4NXLazpqGtsV82Z0IC1DrQvHXNvePyHT/" + + "hOASYQQKM7D8MUSs954UFZPYFqvQrh1JyRl+" + + "87cFioQGOjfn3tt2uhYpMqzyRyeaywjwq8tyJC4ZAGuTTfDqXr76QdvTB4lKX42llgyIIdBwc4DBjugWYEiV" + + "R/" + + "PxGmzwyIBSM9PgXPAW+ijfEkyqayX0cPQRFaqi6JKxhg0iWADGTssEK8ofZwz4N711co7LRNSqfcRFb+" + + "QT9v3aAQS/VZsxyTybLHLpACDsq9qDCctewHEKZpVazOFRkTZlTIsp/" + + "YBd9avMTFWIgaxRjnuKiaLpMek0Hlx9BdvSQM9b7zFlIMGw11HBBtg/" + + "R22UfK+" + + "x2zNqSZeS3ZN6fylfum2YAm7IrNzn4car8g64DfQ6AMZHePK4eql5d5n8iShRAFTRg8jGv6pkaQBM1OaPcWM" + + "ba1eDicN5pyCaYgwrhYy2yVN3uTOF4/Z1pxZ8XQ3Dx6zkA79f4P7dNhjGdYnlBH1wAnwTdGeP4epk3aF3jR/" + + "JJjRx0MxevLEYKN3DrSE580KWTmGEMM9UQcPz9sJ/" + + "+VgNkgRKGRXhw001q8vAzEhriL8mFnZhBaCNvGTbckPTH4ri3qWHbiDpztzjqKg7/" + + "bmZflQ7N7y3eX8wWSZ0g0Yh9KDJqwsIc7maoD20LxQRCr2dQZJ8mNOjsr+" + + "NonCDPBA4usp9i16WU1bBwRb81p+K6MWrax+wG+m2eBn/n/" + + "tkXBYYy1r0fBfiTizFQY6bna0HSzXqHkwwmDYc20ZGo77JE4bBlagKFQ12W74bSe7HwP28DxtvWKW56Is6lM" + + "uqZVkg8GQMW4Iz3hX9SKBe6/tw06PegPDbp/" + + "RfFAxzq9vcb5FU9kshMdnvTAY6I7OjgVGR8FXN4iZARzU5+" + + "BBQa4QblkxDUyiPLSjSSAX3oKkWmu9S9LxmrhsHgd2KOqbZL19EoZrx+" + + "eQyseaLjDuF569Da6V133Nqej46CPTZNp6ojwNSAjPY2ai1D0hZ8GQpTaeTRRiVcFUps4UHRCsUbBSTC5RJL" + + "VoGqj0eORAhib4xp7DGlKf7s1j88ZH1OgOw/rIbFMsyKSQsKKmI22Bo3FW2iVL/" + + "SoPGOKDupMQJvolkieVqK0DjUxsfDLf/VpkrM6fH3FIiw6Dz7vSz3CfUIQ0gDfQi/" + + "w08WWCP706VHeDwX1rscAvn2Cc6/q3TbP9bMYoEvS04p5LPGeHtvX/" + + "PVyfv9JNqAbrRVmtuTAd1rQhiPNHK5JH5feweblxNOsluNH5iJ2tH70R/" + + "3LcbL6jiHS9DfoxzHPnX3arWpVF1ZZ4aZivePAOX8B9J7zbxdhhdU1eUNFt+" + + "sEuGyA6VsTsHf8fZeXpbPwy1OBRMmNiJ3wG+" + + "VQC7guTMjnhs7kljuWlhZ1MlsnGroX7y51x2l78r3ntwiXkpyfpOBiDezTHPu8kYMNkLaZWeo4bygR04SD75" + + "GmjwkYGAXKSvL2XMqT8iCO8wQRrKneIeBgmQKojolj5EQlQIYptCzHjq0kVVout5a"; + private static final String TEST_S = + "jf3ldp/wzf7aHNAWXPULSa7WoqVbFXac+zkQpMtFRz/dQd8bbXrttS+XKo7F/" + + "zb0ZuRfHteGNNoSVnkUvPU0i2Y1rVtgY5j7DlHKGSgk+WncxH+nusXYb4kjltDPZe4A/yvGSD6Boaq/09Yr/" + + "edE79gSs1bkNyLL7Qa33BhiZEUAT4d3SDhi2WiPtv5gkiwRGrGs4PiRkkn9gTTPVHDlct1jpfip0lDt5BWXY" + + "214k013ZKL0a+55lmMEXmyrdSTi6PdBUEQ61OCiiWEYDateWKRqZgj04qCQFJ4EdyAxVnQkN7bO1+" + + "OZdE539uNDNs9nqToegkw1AE+" + + "Iew6WGpmD4at8EyKC46OsHbCYgLzXgNBUqQEWuGUdb9JjXJx6Ycir4S1dZVcSSMopyNPfNoNrkVcQrjXuCEh" + + "2wcUNq8YcYF8EapDBTZVdcGrkGeg7/kBexN/7jBXbTBfR0dXfX/" + + "EsLPXRoWr4jW9IfTPWzaWjZijzndJXb8Mbf+PxhVQAH4fKPzMUYXsnSVLjgYv4/" + + "EIqzC4CNXU2y3zEF+" + + "0EBVG0sF0KKfzuAWvRA2Sfoy6TLotPvj574MoWoUSym9Fvk60MHmtUvUow4AAQEgjAPdR1zpJsnKdmZUWKrf" + + "2MAB5powP7G94qywlLDlH93r9RarI90hGVdhaIoGdEU+e7A+i5Uj65AIQJGcHFxeHC1gQ+" + + "gjTlVTyJt6TFmlCAcj8aLlOpQXO4TTq5Y3izgAZs92t2Nrpx8GByKgC7OHYG+aYo3Z+KvIANyfh377cogp8+" + + "tW/YQchb/22l96/q3scWIViA+AKVRJnrOOYaaL9PHrCeG81b/" + + "QUClZrmFozUiU7yyv++" + + "J6orbIDXw5rGYEPaA618km10q0Podyy2WZvldOyR5MH238zql1lu1QIGL19ivHCnwJFtE9mJQJbZ+hUk/" + + "OLfdRN025junC6h1XP52zVDu4Eb/" + + "syrGu59ox5+JyF8czYVNQhfFjWD7+" + + "Dbw5BUemv3388zjgY3q9mOfZexZglIBGvRccBuNaPnPDkxVF8o64gHgV/" + + "ydyAUcq7kIKmUfi6geORAXtCjEtLQEDnkxGX/" + + "iS7mokZ9mrb7TfygiuG93+" + + "PPlxNu9PNG7ISXrGFFAwdZCPOyyiHIU1EquBehWnmvdcM0vt0BVz35hZq2LhcWW74xCMVpScU8CypJZCkgj8" + + "ZtDUgl5z7ZfpX9OZ21glJK9s/" + + "UQOmfoFZLYhszBB21S4X2nhKEOgFv8JW48SQzVoWLlATXen0UomBn3rh4PY+dmRGwj0gxEVGMDTeSRsF/" + + "jF0l0GV437hvP4UlYaAVfc50RTOFargFEBS91YLFNfpcq5PMECgwJc5EC/" + + "iK5FR2EXYJQM7xj0a5OH+qtVO+xNVlMJT7AXQ960ie+" + + "IxvqAyHuRCXUKyi14KOdLFH5iGL0VsB8VZ1RtfU1wkwHyKkqioFE5LaoZ6MPoYMtPvRf+/" + + "nOak9m6bwtF5IxrAS1fRGTTJmMlNK3xzMwwQsurt5KBaFBdE0FYOV01A0xMpN5Wo3YFpoOebnRYxXHdwDLZZ" + + "1UAfokYkf+mS53h8xLI2mSW4J9LQaDg/y/sDuZPGIqKTyjlvc/" + + "X8zN3UvQpXb8lAHAAfbo3Xj5mkGWUglUDBnIP378angbNEIzLZcuPsfrfcSGU0ki4zM4adptxGY5LX7DinlQ" + + "6Pe4FO39BURjnYBrVH+zFQu20WfLrwBthjInasLUUUvdBew+n1/" + + "gHaxDKqAIvfBubWXJdZpPqwT9wsHBwcKQhrXxnVDXeQD2n0Vxw4tMXc3KFVUEoLRxTWQlVveaJK3zSo8XZOL" + + "S4WqEJoMweOGOAMjny6UUAjTl7wlMHOukwVK5jPCrATL40V0QvMNP08uy76fMkIFOgXvn1UmeKE1+" + + "PP6k8iX4UiVxAbkAkHVc2bA7pPHot0Eii6QvEtZGrXATpIEcbN1FdS2FmxZ/" + + "Jw+Ln+8uTviKuvlcUBXTte1MSWu2pw/" + + "V2HskCGoxsvoyGIP3qRwooxEzPrN7IBLJVOpBj67dzxfoRwFIpGBrldINyQVxT9ENgwWnTCzrI+" + + "1yZfhTlNnpw7gugImTMm9TVhFOA9kJKT/hPxq3Hyp/WJDYP/" + + "qa+O7hL8zob6UBDuCgJToU3B5TXJNl2YGnmUF6TwyWt90l6kgBTqEX7PoaoFsXUdnAHtVc4BCqMGK+" + + "fDYlrZs7PgaQbeCVcUKComEMUvg37D2OByl+" + + "kaLUbWTCyv3ajWC7teAPSRTziIArC2YKJkHktKVssnUQWLKCqWmNkX9OzsaGzDFtP+2QOEVTnydi/" + + "Zv25WuYPN9QifZmBz59vm/" + + "7vl89loTK9LegfGjSk0U0scZGDnc7hMYKbo63mwifYY2hS26qb0ZFOioUfjrQgqoHzdvBcJpvJDaR3IeL9qy" + + "5LHgXfWu/" + + "Og4f7PAjX6e3mpyUA1XfxIXBT9VsuptCNnJxFBzG6V5eNPi4ZgDa3jEW3ZYs9JRUZbGQtSuTRg2JlS+xe7/" + + "HNZPzP8ALwsaJ4zkRdEe55lqNW9nF9XiKaYXoX4FvZIE1a1e7nID2rnPIBO/" + + "hdapEtaJFaed3dua6EmJjWBEMQfpjw5EczeHnE/zbyqSelUXKN7EH+eDnIoWAA4vBjurEOWcFCraT/" + + "GNiJ81n7pT7hfsxUSDLhOdfDxy9pmttccsA8bjk4avPUOPA/" + + "Z6+R5VhL9WS1Vzqr+" + + "B9uw7PbZrFsgoYCUmKfcGi9hm0NEKvENnqS8LzTYgwENtk7JCvTnBSx2KwjPLx5HqvvUqCabb1R5OojT9wn0" + + "IOGfD1Y4Q1x3PD4ZiqDAWG6iiG2wXL5fSEckqm8VJV/jgeXAZ/" + + "C4cjIqiqF6BC7AL1ZBUCgHovQehyxT44K2xCe1KtdCvmpX59y0ZTLBA7IvO8gkbMA07BSrAhiNITMUWJulYM" + + "H1D2lPZ2DKMqtTWe4HyPubEqBQAP6J9MPrCgsDuK+GVaFaCAzSj402on8ezdr12mHE3DiuGbdQLXJ/" + + "2PNuwL22dnLe+r2BvPaFFbW/scPuxc58V2kU1SQWO8vjfWvMdbxIkZr8gJTHeDBfSo1HDjSoth/" + + "kHJw+rbwe1iwyPuwz3fZX/ggsKF6tqZB1gejx/uzkMFx0kTVaOlq/c3/" + + "0KRUpgeJWnrsvN7fEtLzx4j6uu4fL4/" + + "QQmN1CiyNbe8gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwYIyw4p6ZpVOH9qAghOpGxm" + + "CwS6dSUaGbwxWhCXBSwStykpNKHmRTmcmD0as/7cBaKsCuyU6hziLYmqqRsg8TYKZQN"; + + @Test + public void verify_invalidMlDsaKey_throws() throws Exception { + byte[] message = TestUtils.decodeBase64(TEST_M); + String algName = "MLDSA44-Ed25519-SHA512"; + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature signature = getInstance(alg); + + byte[] invalidPublicKeyBytes = TestUtils.decodeBase64(TEST_PK); + invalidPublicKeyBytes[0] = (byte) (invalidPublicKeyBytes[0] ^ 0x01); + OpenSslCompositeMlDsaPublicKey invalidMldsaPublicKey = + new OpenSslCompositeMlDsaPublicKey(invalidPublicKeyBytes, alg); + byte[] sigBytes = TestUtils.decodeBase64(TEST_S); + + signature.initVerify(invalidMldsaPublicKey); + signature.update(message, 0, message.length); + + assertFalse("Signature verification failed for algorithm: " + algName + + " (tcId: id-MLDSA44-Ed25519-SHA512)", + signature.verify(sigBytes)); + } + + @Test + public void verify_invalidTradPublicKey_throws() throws Exception { + byte[] message = TestUtils.decodeBase64(TEST_M); + String algName = "MLDSA44-Ed25519-SHA512"; + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature signature = getInstance(alg); + + byte[] invalidPublicKeyBytes = TestUtils.decodeBase64(TEST_PK); + invalidPublicKeyBytes[invalidPublicKeyBytes.length - 1] = + (byte) (invalidPublicKeyBytes[invalidPublicKeyBytes.length - 1] ^ 0x01); + OpenSslCompositeMlDsaPublicKey invalidTradPublicKey = + new OpenSslCompositeMlDsaPublicKey(invalidPublicKeyBytes, alg); + byte[] sigBytes = TestUtils.decodeBase64(TEST_S); + + signature.initVerify(invalidTradPublicKey); + signature.update(message, 0, message.length); + + assertFalse("Signature verification failed for algorithm: " + algName + + " (tcId: id-MLDSA44-Ed25519-SHA512)", + signature.verify(sigBytes)); + } + + @Test + public void verify_invalidMessage_throws() throws Exception { + byte[] invalidMessage = TestUtils.decodeBase64(TEST_M); + invalidMessage[0] = (byte) (invalidMessage[0] ^ 0x01); + String algName = "MLDSA44-Ed25519-SHA512"; + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature signature = getInstance(alg); + + OpenSslCompositeMlDsaPublicKey publicKey = + new OpenSslCompositeMlDsaPublicKey(TestUtils.decodeBase64(TEST_PK), alg); + byte[] sigBytes = TestUtils.decodeBase64(TEST_S); + + signature.initVerify(publicKey); + signature.update(invalidMessage, 0, invalidMessage.length); + + assertFalse("Signature verification succeeded for algorithm: " + algName + + " (tcId: id-MLDSA44-Ed25519-SHA512)", + signature.verify(sigBytes)); + } + + @Test + public void verify_invalidMlDsaSignature_throws() throws Exception { + byte[] message = TestUtils.decodeBase64(TEST_M); + String algName = "MLDSA44-Ed25519-SHA512"; + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature signature = getInstance(alg); + + OpenSslCompositeMlDsaPublicKey publicKey = + new OpenSslCompositeMlDsaPublicKey(TestUtils.decodeBase64(TEST_PK), alg); + byte[] invalidSigBytes = TestUtils.decodeBase64(TEST_S); + invalidSigBytes[0] = (byte) (invalidSigBytes[0] ^ 0x01); + + signature.initVerify(publicKey); + signature.update(message, 0, message.length); + + assertFalse("Signature verification succeeded for algorithm: " + algName + + " (tcId: id-MLDSA44-Ed25519-SHA512)", + signature.verify(invalidSigBytes)); + } + + @Test + public void verify_invalidTradSignature_throws() throws Exception { + byte[] message = TestUtils.decodeBase64(TEST_M); + String algName = "MLDSA44-Ed25519-SHA512"; + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature signature = getInstance(alg); + + OpenSslCompositeMlDsaPublicKey publicKey = + new OpenSslCompositeMlDsaPublicKey(TestUtils.decodeBase64(TEST_PK), alg); + byte[] invalidSigBytes = TestUtils.decodeBase64(TEST_S); + invalidSigBytes[invalidSigBytes.length - 1] = + (byte) (invalidSigBytes[invalidSigBytes.length - 1] ^ 0x01); + + signature.initVerify(publicKey); + signature.update(message, 0, message.length); + + assertFalse("Signature verification succeeded for algorithm: " + algName + + " (tcId: id-MLDSA44-Ed25519-SHA512)", + signature.verify(invalidSigBytes)); + } + + // The fuller set of tests with the provider API is performed in the `SignatureTest.java`. + // Here we verify that the provider implementation indeed verifies the known test vectors. + @Test + public void verifyTestVectorsViaProvider_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + byte[] message = TestUtils.decodeBase64(tv.getString("m")); + String algName = tv.getString("tcid"); + assertTrue(algName.startsWith("id-")); + algName = algName.substring(3); + + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + Signature verifier = Signature.getInstance(algName, PROVIDER); + + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + TestUtils.decodeBase64(tv.getString("pk")), alg); + byte[] sigBytes = TestUtils.decodeBase64(tv.getString("s")); + + verifier.initVerify(publicKey); + verifier.update(message, 0, message.length); + + assertTrue("Signature verification failed for algorithm: " + algName + + " (tcId: " + tv.getString("tcid") + ")", + verifier.verify(sigBytes)); + } + } + + @Test + public void signWithoutInit_throws() throws Exception { + Signature signature = Signature.getInstance("MLDSA44-Ed25519-SHA512", PROVIDER); + assertThrows(SignatureException.class, () -> signature.sign()); + } + + @Test + public void verifyWithoutInit_throws() throws Exception { + Signature signature = Signature.getInstance("MLDSA44-Ed25519-SHA512", PROVIDER); + assertThrows(SignatureException.class, () -> signature.verify(new byte[0])); + } + + private void testSignVerifyRoundTripWithJce(String algName) throws Exception { + CompositeMlDsaAlgorithm alg = CompositeMlDsaAlgorithm.fromName(algName); + KeyPairGenerator kpg = KeyPairGenerator.getInstance(alg.getName(), PROVIDER); + KeyPair kp = kpg.generateKeyPair(); + + KeyFactory kf = KeyFactory.getInstance(alg.getName(), PROVIDER); + PublicKey publicKey = + kf.generatePublic(new X509EncodedKeySpec(kp.getPublic().getEncoded())); + PrivateKey privateKey = + kf.generatePrivate(new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded())); + + Signature signer = getInstance(alg); + signer.initSign(privateKey); + byte[] message = "test message".getBytes(UTF_8); + signer.update(message); + byte[] sig = signer.sign(); + + Signature verifier = getInstance(alg); + verifier.initVerify(publicKey); + verifier.update(message); + assertTrue(verifier.verify(sig)); + } + + @Test + public void testSignVerifyRoundTripEd25519() throws Exception { + testSignVerifyRoundTripWithJce("MLDSA44-Ed25519-SHA512"); + } + + @Test + public void testSignVerifyRoundTripRsa() throws Exception { + testSignVerifyRoundTripWithJce("MLDSA65-RSA3072-PSS-SHA512"); + } + + @Test + public void testSignVerifyRoundTripEcdsa() throws Exception { + testSignVerifyRoundTripWithJce("MLDSA87-ECDSA-P521-SHA512"); + } +} diff --git a/common/src/test/java/org/conscrypt/HostnameVerifierTest.java b/common/src/test/java/org/conscrypt/HostnameVerifierTest.java index 2dc6b0fdf..127f695af 100644 --- a/common/src/test/java/org/conscrypt/HostnameVerifierTest.java +++ b/common/src/test/java/org/conscrypt/HostnameVerifierTest.java @@ -64,8 +64,6 @@ public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException { } private static final Charset UTF_8 = Charset.forName("UTF-8"); - // BEGIN Android-changed: Run tests for both default and strict verifiers. http://b/144694112 - // private HostnameVerifier verifier = OkHostnameVerifier.INSTANCE; @Parameters() public static Collection data() { // Both verifiers should behave the same in all tests except for @@ -75,7 +73,6 @@ public static Collection data() { } @Parameter public OkHostnameVerifier verifier; - // END Android-changed: Run tests for both default and strict verifiers. http://b/144694112 @Test public void verify() throws Exception { @@ -114,8 +111,6 @@ public void verifyCn() throws Exception { + "HwlNrAu8jlZ2UqSgskSWlhYdMTAP9CPHiUv9N7FcT58Itv/I4fKREINQYjDpvQcx\n" + "SaTYb9dr5sB4WLNglk7zxDtM80H518VvihTcP7FHL+Gn6g4j5fkI98+S\n" + "-----END CERTIFICATE-----\n"); - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("foo.com", session)); X509Certificate[] certs = {}; assertFalse(verifier.verify(certs, "foo.com", session)); assertFalse(verifier.verify(certs, "a.foo.com", session)); @@ -152,8 +147,6 @@ public void verifyNonAsciiCn() throws Exception { + "9BsO7qe46hidgn39hKh1WjKK2VcL/3YRsC4wUi0PBtFW6ScMCuMhgIRXSPU55Rae\n" + "UIlOdPjjr1SUNWGId1rD7W16Scpwnknn310FNxFMHVI0GTGFkNdkilNCFJcIoRA=\n" + "-----END CERTIFICATE-----\n"); - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session)); X509Certificate[] certs = {}; assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session)); assertFalse(verifier.verify(certs, "a.\u82b1\u5b50.co.jp", session)); @@ -321,8 +314,6 @@ public void verifyMultipleCn() throws Exception { assertFalse(verifier.verify(certs, "a.foo.com", session)); assertFalse(verifier.verify(certs, "bar.com", session)); assertFalse(verifier.verify(certs, "a.bar.com", session)); - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session)); assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session)); assertFalse(verifier.verify(certs, "a.\u82b1\u5b50.co.jp", session)); } @@ -359,11 +350,7 @@ public void verifyWilcardCn() throws Exception { + "-----END CERTIFICATE-----\n"); X509Certificate[] certs = {}; assertFalse(verifier.verify(certs, "foo.com", session)); - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("www.foo.com", session)); assertFalse(verifier.verify(certs, "www.foo.com", session)); - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("\u82b1\u5b50.foo.com", session)); assertFalse(verifier.verify(certs, "\u82b1\u5b50.foo.com", session)); assertFalse(verifier.verify(certs, "a.b.foo.com", session)); } @@ -400,11 +387,7 @@ public void verifyWilcardCnOnTld() throws Exception { + "EJMryEzOjg4Tfuc5qM0EXoPcQ/JlheaxZ40p2IyHqbsWV4MRYuFH4bkM\n" + "-----END CERTIFICATE-----\n"); X509Certificate[] certs = {}; - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("foo.co.jp", session)); assertFalse(verifier.verify(certs, "foo.co.jp", session)); - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("\u82b1\u5b50.co.jp", session)); assertFalse(verifier.verify(certs, "\u82b1\u5b50.co.jp", session)); } @@ -545,8 +528,6 @@ public void wildcardsDoesNotNeedTwoDots() throws Exception { + "U6LFxmZr31lFyis2/T68PpjAppc0DpNQuA2m/Y7oTHBDi55Fw6HVHCw3lucuWZ5d\n" + "qUYo4ES548JdpQtcLrW2sA==\n" + "-----END CERTIFICATE-----"); - // Android-changed: Ignore common name in hostname verification. http://b/70278814 - // assertTrue(verifier.verify("google.com", session)); X509Certificate[] certs = {}; assertFalse(verifier.verify(certs, "google.com", session)); } @@ -619,7 +600,6 @@ public void subjectAltNameWithWildcard() throws Exception { assertFalse(verifier.verify(certs, "quux.com", session)); } - // BEGIN Android-added: Verify behaviour with top level wildcard SAN. http://b/144694112 @Test public void subjectAltNameWithToplevelWildcard() throws Exception { // Default OkHostnameVerifier instance should allow SANs which @@ -645,8 +625,9 @@ public void subjectAltNameWithToplevelWildcard() throws Exception { assertTrue(OkHostnameVerifier.INSTANCE.verify(certs, "google.com", session)); assertFalse(OkHostnameVerifier.strictInstance().verify(certs, "google.com", session)); } - // END Android-added: Verify behaviour with top level wildcard SAN. http://b/144694112 + // android-add: OkHostnameVerifier.verifyAsIpAddress not accessible on platform builds + @Ignore @Test public void verifyAsIpAddress() { // IPv4 @@ -682,4 +663,4 @@ private X509Certificate certificate(String certificate) throws Exception { private SSLSession session(String certificate) throws Exception { return new FakeSSLSession(certificate(certificate)); } -} \ No newline at end of file +} diff --git a/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java index e8a0f182f..4f9cb7067 100644 --- a/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java +++ b/common/src/test/java/org/conscrypt/NativeCryptoArgTest.java @@ -66,12 +66,6 @@ public class NativeCryptoArgTest { private final Map> classCache = new HashMap<>(); private final Map methodMap = buildMethodMap(); - @AfterClass - public static void after() { - // TODO(prb): Temporary hacky check - remove - assertTrue(testedMethods.size() >= 190); - } - @Test public void ecMethods() throws Throwable { markTestRun(); @@ -125,7 +119,6 @@ public void sslMethods() throws Throwable { // null args should throw NPE. String[] nonThrowingMethods = new String[] { "SSL_interrupt", - "SSL_shutdown", "ENGINE_SSL_shutdown", }; @@ -155,8 +148,6 @@ public void sslMethods() throws Throwable { filter = MethodFilter.nameFilter("Non throwing NativeSsl methods", nonThrowingMethods); testMethods(filter, null); - expectVoid("SSL_shutdown", NOT_NULL, null, null, null); - expectNPE("SSL_shutdown", NOT_NULL, null, new FileDescriptor(), null); expectNPE("ENGINE_SSL_shutdown", NOT_NULL, null, null); expectVoid("SSL_set_session", NOT_NULL, null, NULL); checkMethodsTested(); diff --git a/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactoryTest.java b/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactoryTest.java new file mode 100644 index 000000000..40db71941 --- /dev/null +++ b/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaKeyFactoryTest.java @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2026 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +import javax.crypto.spec.DESKeySpec; + +@RunWith(JUnit4.class) +@SuppressWarnings("InsecureCryptoUsage") +public class OpenSslCompositeMlDsaKeyFactoryTest { + private static final Provider PROVIDER = TestUtils.getConscryptProvider(); + + public static final class RawKeySpec extends EncodedKeySpec { + public RawKeySpec(byte[] encoded) { + super(encoded); + } + + @Override + public String getFormat() { + return "raw"; + } + } + + private static KeyFactory getInstance(String algName) throws NoSuchAlgorithmException { + return KeyFactory.getInstance(algName, PROVIDER); + } + + @Test + public void mldsa44Ed25519Sha512_correctAlgorithm() throws Exception { + OpenSslCompositeMlDsaKeyFactory keyFactory = + new OpenSslCompositeMlDsaKeyFactory.Mldsa44Ed25519Sha512(); + + assertEquals(CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512, keyFactory.getAlgorithm()); + } + + @Test + public void mldsa65Ed25519Sha512_correctAlgorithm() throws Exception { + OpenSslCompositeMlDsaKeyFactory keyFactory = + new OpenSslCompositeMlDsaKeyFactory.Mldsa65Ed25519Sha512(); + + assertEquals(CompositeMlDsaAlgorithm.MLDSA65_ED25519_SHA512, keyFactory.getAlgorithm()); + } + + @Test + public void generatePublic_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] pkBytes = TestUtils.decodeBase64(tv.getString("pk")); + RawKeySpec rawKeySpec = new RawKeySpec(pkBytes); + KeyFactory keyFactory = getInstance(algName); + + PublicKey publicKey = keyFactory.generatePublic(rawKeySpec); + + assertNotNull(publicKey); + assertTrue(publicKey instanceof OpenSslCompositeMlDsaPublicKey); + assertEquals(algName, publicKey.getAlgorithm()); + assertArrayEquals(pkBytes, ((OpenSslCompositeMlDsaPublicKey) publicKey).getRaw()); + } + } + + @Test + public void generatePrivate_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] skBytes = TestUtils.decodeBase64(tv.getString("sk")); + RawKeySpec rawKeySpec = new RawKeySpec(skBytes); + KeyFactory keyFactory = getInstance(algName); + + PrivateKey privateKey = keyFactory.generatePrivate(rawKeySpec); + + assertNotNull(privateKey); + assertTrue(privateKey instanceof OpenSslCompositeMlDsaPrivateKey); + assertEquals(algName, privateKey.getAlgorithm()); + assertArrayEquals(skBytes, ((OpenSslCompositeMlDsaPrivateKey) privateKey).getRaw()); + } + } + + @Test + public void getKeySpec_worksForPublicKey() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] pkBytes = TestUtils.decodeBase64(tv.getString("pk")); + KeyFactory keyFactory = getInstance(algName); + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + pkBytes, CompositeMlDsaAlgorithm.fromName(algName)); + + RawKeySpec returnedSpec = keyFactory.getKeySpec(publicKey, RawKeySpec.class); + + assertNotNull(returnedSpec); + assertEquals("raw", returnedSpec.getFormat()); + assertArrayEquals(pkBytes, returnedSpec.getEncoded()); + } + } + + @Test + public void getKeySpec_worksForPrivateKey() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] skBytes = TestUtils.decodeBase64(tv.getString("sk")); + KeyFactory keyFactory = getInstance(algName); + OpenSslCompositeMlDsaPrivateKey privateKey = new OpenSslCompositeMlDsaPrivateKey( + skBytes, CompositeMlDsaAlgorithm.fromName(algName)); + + RawKeySpec returnedSpec = keyFactory.getKeySpec(privateKey, RawKeySpec.class); + + assertNotNull(returnedSpec); + assertEquals("raw", returnedSpec.getFormat()); + assertArrayEquals(skBytes, returnedSpec.getEncoded()); + } + } + + @Test + public void getKeySpec_worksForPrivateKey_pkcs8() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] pkcs8Bytes = TestUtils.decodeBase64(tv.getString("skpkcs8")); + byte[] rawBytes = TestUtils.decodeBase64(tv.getString("sk")); + KeyFactory keyFactory = getInstance(algName); + OpenSslCompositeMlDsaPrivateKey privateKey = new OpenSslCompositeMlDsaPrivateKey( + rawBytes, CompositeMlDsaAlgorithm.fromName(algName)); + + PKCS8EncodedKeySpec returnedSpec = + keyFactory.getKeySpec(privateKey, PKCS8EncodedKeySpec.class); + + assertNotNull(returnedSpec); + assertEquals("PKCS#8", returnedSpec.getFormat()); + assertArrayEquals(pkcs8Bytes, returnedSpec.getEncoded()); + assertArrayEquals(pkcs8Bytes, privateKey.getEncoded()); + } + } + + @Test + public void getKeySpec_worksForPublicKey_x509() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] x509Bytes = Base64.getDecoder().decode(tv.getString("pkx509")); + byte[] rawBytes = Base64.getDecoder().decode(tv.getString("pk")); + KeyFactory keyFactory = getInstance(algName); + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + rawBytes, CompositeMlDsaAlgorithm.fromName(algName)); + + X509EncodedKeySpec returnedSpec = + keyFactory.getKeySpec(publicKey, X509EncodedKeySpec.class); + + assertNotNull(returnedSpec); + assertEquals("X.509", returnedSpec.getFormat()); + assertArrayEquals(x509Bytes, returnedSpec.getEncoded()); + assertArrayEquals(x509Bytes, publicKey.getEncoded()); + } + } + + @Test + public void translateKey_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] pkBytes = TestUtils.decodeBase64(tv.getString("pk")); + byte[] skBytes = TestUtils.decodeBase64(tv.getString("sk")); + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + pkBytes, CompositeMlDsaAlgorithm.fromName(algName)); + OpenSslCompositeMlDsaPrivateKey privateKey = new OpenSslCompositeMlDsaPrivateKey( + skBytes, CompositeMlDsaAlgorithm.fromName(algName)); + KeyFactory keyFactory = getInstance(algName); + + Key translatedPublicKey = keyFactory.translateKey(publicKey); + Key translatedPrivateKey = keyFactory.translateKey(privateKey); + + assertEquals(publicKey, translatedPublicKey); + assertEquals(privateKey, translatedPrivateKey); + } + } + + @Test + public void translateKey_throwsForUnsupportedKey() throws Exception { + OpenSslMlDsaPrivateKey mlDsaPrivateKey = + new OpenSslMlDsaPrivateKey(new byte[32], MlDsaAlgorithm.ML_DSA_44); + KeyFactory keyFactory = KeyFactory.getInstance("MLDSA44-Ed25519-SHA512", PROVIDER); + + assertThrows(InvalidKeyException.class, () -> keyFactory.translateKey(mlDsaPrivateKey)); + } + + @Test + public void generatePublic_nullKeySpec_throws() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + KeyFactory keyFactory = getInstance(algName); + + assertThrows(InvalidKeySpecException.class, () -> keyFactory.generatePublic(null)); + } + } + + @Test + public void generatePrivate_nullKeySpec_throws() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + KeyFactory keyFactory = getInstance(algName); + + assertThrows(InvalidKeySpecException.class, () -> keyFactory.generatePrivate(null)); + } + } + + @Test + public void generatePublic_unsupportedKeySpec_throws() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + KeyFactory keyFactory = getInstance(algName); + DESKeySpec unsupportedKeySpec = + new DESKeySpec(TestUtils.decodeBase64(tv.getString("pkx509"))); + + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(unsupportedKeySpec)); + } + } + + @Test + public void generatePublic_invalidKeySpecValue_throws() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + KeyFactory keyFactory = getInstance(algName); + X509EncodedKeySpec x509Spec = + new X509EncodedKeySpec(TestUtils.decodeBase64(tv.getString("pk"))); + + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(x509Spec)); + } + } + + @Test + public void generatePrivate_pkcs8_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] pkcs8Bytes = TestUtils.decodeBase64(tv.getString("skpkcs8")); + byte[] rawBytes = TestUtils.decodeBase64(tv.getString("sk")); + PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(pkcs8Bytes); + KeyFactory keyFactory = getInstance(algName); + + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8Spec); + + assertNotNull(privateKey); + assertTrue(privateKey instanceof OpenSslCompositeMlDsaPrivateKey); + assertEquals(algName, privateKey.getAlgorithm()); + assertArrayEquals(rawBytes, ((OpenSslCompositeMlDsaPrivateKey) privateKey).getRaw()); + } + } + + @Test + public void generatePrivate_invalidPkcs8_throws() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + KeyFactory keyFactory = getInstance(algName); + PKCS8EncodedKeySpec pkcs8Spec = + new PKCS8EncodedKeySpec(TestUtils.decodeBase64(tv.getString("sk"))); + + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(pkcs8Spec)); + } + } + + @Test + public void generatePrivate_unsupportedKeySpec_throws() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + KeyFactory keyFactory = getInstance(algName); + DESKeySpec unsupportedKeySpec = + new DESKeySpec(TestUtils.decodeBase64(tv.getString("skpkcs8"))); + + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(unsupportedKeySpec)); + } + } + + @Test + public void generatePrivate_pkcs8_shortKey_fails() throws Exception { + byte[] pkcs8 = TestUtils.decodeHex("3012020100300a06082b060105050706270401aa"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_corruptedHeader_fails() throws Exception { + // Length byte 0x10 instead of 0x51. + byte[] pkcs8 = TestUtils.decodeHex( + "3010020100300a06082b06010505070627044001d8df59152d8a44e39a25e6775" + + "ef18a0044b20e58c2c4346ac37c27d152176c5bea6c2454617bfb48479eb8b1" + + "3b711a98116b73f7ecac30faf1dd9b4fa17d68"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_wrongVersion_fails() throws Exception { + // Version byte 0x01 instead of 0x00. + byte[] pkcs8 = TestUtils.decodeHex( + "3051020101300a06082b06010505070627044001d8df59152d8a44e39a25e6775" + + "ef18a0044b20e58c2c4346ac37c27d152176c5bea6c2454617bfb48479eb8b1" + + "3b711a98116b73f7ecac30faf1dd9b4fa17d68"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_wrongOid_fails() throws Exception { + // OID byte 0x28 instead of 0x27. + byte[] pkcs8 = TestUtils.decodeHex( + "3051020100300a06082b06010505070628044001d8df59152d8a44e39a25e6775" + + "ef18a0044b20e58c2c4346ac37c27d152176c5bea6c2454617bfb48479eb8b1" + + "3b711a98116b73f7ecac30faf1dd9b4fa17d68"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_wrongOidTag_fails() throws Exception { + // OID tag byte 0x07 instead of 0x06. + byte[] pkcs8 = TestUtils.decodeHex( + "3051020100300a07082b06010505070627044001d8df59152d8a44e39a25e6775" + + "ef18a0044b20e58c2c4346ac37c27d152176c5bea6c2454617bfb48479eb8b1" + + "3b711a98116b73f7ecac30faf1dd9b4fa17d68"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_wrongOidLength_tooLong_fails() throws Exception { + // OID length byte 0x13 instead of 0x08. + byte[] pkcs8 = TestUtils.decodeHex( + "3051020100300a06132b06010505070627044001d8df59152d8a44e39a25e6775" + + "ef18a0044b20e58c2c4346ac37c27d152176c5bea6c2454617bfb48479eb8b1" + + "3b711a98116b73f7ecac30faf1dd9b4fa17d68"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_wrongOidLength_tooShort_fails() throws Exception { + // OID length byte 0x01 instead of 0x08. + byte[] pkcs8 = TestUtils.decodeHex( + "3051020100300a06012b06010505070627044001d8df59152d8a44e39a25e6775" + + "ef18a0044b20e58c2c4346ac37c27d152176c5bea6c2454617bfb48479eb8b1" + + "3b711a98116b73f7ecac30faf1dd9b4fa17d68"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_short_garbage_fails() throws Exception { + byte[] pkcs8 = TestUtils.decodeHex("0102030405060708"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePrivate_pkcs8_long_garbage_fails() throws Exception { + byte[] pkcs8 = TestUtils.decodeHex( + "12d0f753076b0c21d8565461f11e9b06baaf4a6fe504850df1aea1457a8be61386456aa7" + + "56f3b729a5ec4e4f54dc5bb1d7f8efcca13a2bc94815e82e2ae0566f73777de07c6ea4" + + "9ee5f1be1b069fbc96e6ae7359b92b52d2a70ffa1fd69e1bdeaa19322e3a1742fb46e3" + + "86f043be30c8fd26e7bb91576515e75b097b8a3bbb"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8))); + } + + @Test + public void generatePublic_x509_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + byte[] pkX509Bytes = TestUtils.decodeBase64(tv.getString("pkx509")); + byte[] rawBytes = TestUtils.decodeBase64(tv.getString("pk")); + X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(pkX509Bytes); + KeyFactory keyFactory = getInstance(algName); + + PublicKey publicKey = keyFactory.generatePublic(x509Spec); + + assertNotNull(publicKey); + assertTrue(publicKey instanceof OpenSslCompositeMlDsaPublicKey); + assertEquals(algName, publicKey.getAlgorithm()); + assertArrayEquals(rawBytes, ((OpenSslCompositeMlDsaPublicKey) publicKey).getRaw()); + } + } + + @Test + public void generatePublic_invalidX509_throws() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + KeyFactory keyFactory = getInstance(algName); + // using `pk` (raw) as invalid X.509 SPKI encoded byte array + X509EncodedKeySpec x509Spec = + new X509EncodedKeySpec(TestUtils.decodeBase64(tv.getString("pk"))); + + assertThrows(InvalidKeySpecException.class, () -> keyFactory.generatePublic(x509Spec)); + } + } + + @Test + public void generatePublic_x509_shortKey_fails() throws Exception { + // Only 10 byte payload inside bit string payload + byte[] spki = + TestUtils.decodeHex("30820551300a06082b06010505070628030b0011223344556677889900"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new X509EncodedKeySpec(spki))); + } + + @Test + public void generatePublic_x509_wrongOid_fails() throws Exception { + // OID byte 0x28 instead of 0x27 + byte[] spki = TestUtils.decodeHex( + "30820551300a06082b060105050706280382054100165a6569022056e604034e2737d557c8c58ae170" + + "e14619da19a58ee85aa80ad883af9acbf3da261054887fcd755f16db1e928127344ea81a8a7db3e0" + + "b2b31166305d1de467e4e8b8a4aedbb217630645e6ef20aa3df302e743e4cca2390854ecc35de290" + + "0e14989b81ba94b15c3ce111c8472c2c9ba503c176bfbe4ebe0d2a1d1c11a0e2f461650ddccbb033" + + "ffd5df43875379eeb1ce08f2e2ffbb639aa3d8ac2cc5b291000db0642817f03603d16a3ccc97c167" + + "401f2a41f3332b9b623ba8ca11bf9f7740b9521c0e61ee0d5cb6b3a6a1adb15f36674202d43ad0bc" + + "75cdbde3f21d3fe138049841028cec3f0c512b3de7850564f605aaf42b875272465fbcedc162a101" + + "8e8df9f7b6ddae858a4cab3c91c9e6b2c23c2af2dc890b86401ae4d37c3a97afbe9076f4c1e25297" + + "e36965832208741c1ce03063ba0598122551fcfc469b3c3220148cf4f8173c05be8a37c4932a9ac9" + + "7d1c3d04456aa8ba24ac61834896003193b2c10af287d9c33e0def5d5ca3b2d1352a9f71115bf904" + + "fdbf7680412fd566cc724f26cb1cba40083b2af6a0c272d7b01c4299a556b33854644d995322ca7f" + + "60177d6af3131562206b14639ee2a268ba4c7a4d07971f4176f49033d6fbcc5948306c35d47041b6" + + "0fd1db651f2bec76ccda9265e4b764de9fca57ee9b66009bb22b3739f871aafc83ae037d0e803191" + + "de3cae1eaa5e5de67f224a1440153460f231afea991a40133539a3dc58c6dad5e0e270de69c82698" + + "830ae1632db254ddee4ce178fd9d69c59f174370f1eb3900efd7f83fb74d86319d6279411f5c009f" + + "04dd19e3f87a9937685de347f2498d1c743317af2c460a3770eb484e7cd0a59398610c33d51070fc" + + "fdb09ffe560364811286457870d34d6af2f033121ae22fc9859d984168236f1936dc90f4c7e2b8b7" + + "a961db883a73b738ea2a0eff6e665f950ecdef2dde5fcc16499d20d1887d28326ac2c21cee66a80f" + + "6d0bc50442af6750649f2634e8ecafe3689c20cf040e2eb29f62d7a594d5b07045bf35a7e2ba316a" + + "dac7ec06fa6d9e067fe7fed917058632d6bd1f05f8938b315063a6e76b41d2cd7a87930c260d8736" + + "d191a8efb244e1b0656a0285435d96ef86d27bb1f03f6f03c6dbd6296e7a22cea532ea9956483c19" + + "0316e08cf7857f522817bafedc34e8f7a03c36e9fd17c5031ceaf6f71be4553d92c84c767bd3018e" + + "88ece8e054647c157378899011cd4e7e04141ae106e59310d4ca23cb4a3492017de82a45a6bbd4bd" + + "2f19ab86c1e077628ea9b64bd7d12866bc7e790cac79a2e30ee179ebd0dae95d77dcda9e8f8e823d" + + "364da7aa23c0d4808cf6366a2d43d2167c190a5369e4d146255c154a6ce141d10ac51b0524c2e512" + + "4b5681aa8f478e4408626f8c69ec31a529feecd63f3c647d4e80ec3fac86c532cc8a490b0a2a6236" + + "d81a37156da254bfd2a0f18e283ba931026fa25922795a8ad038d4c6c7c32dffd5a64acce9f1f714" + + "88b0e83cfbbd2cf709f5084348037d08bfc34f165823fbd3a547783c17d6bb1c02f9f609cebfab74" + + "db3fd6cc62812f4b4e29e4b3c6787b6f5ff3d5c9fbfd24da806eb4559adb9301dd6b42188f3472b9" + + "247e5f7b079b97134eb25b8d1f9889dad1fbd11ff72dc6cbea38874bd0dfa31cc73e75f76ab5a954" + + "5d596786998af78f00e5fc07d27bcdbc5d861754d5e50d16dfac12e1b203a56c4ec1dff1f65e5e96" + + "cfc32d4e051326362277c06f95402ee0b933239e1b3b9258ee5a5859d4c96c9c6ae85fbcb9d71da5" + + "efcaf79edc225e4a727e93818837b34c73eef2460c3642da6567a8e1bca0474e120fbe469a3c2460" + + "6017292bcbd9732a4fc8823bcc1046b2a778878182640aa23a258f9110950218a6d0b31e3ab49155" + + "68bade5a"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new X509EncodedKeySpec(spki))); + } + + @Test + public void generatePublic_x509_short_garbage_fails() throws Exception { + byte[] spki = TestUtils.decodeHex("0102030405060708"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new X509EncodedKeySpec(spki))); + } + + @Test + public void generatePublic_x509_wrongPaddingBits_fails() throws Exception { + // Padding bits byte 0x01 instead of 0x00 for the BIT STRING + byte[] spki = TestUtils.decodeHex( + "30820551300a06082b060105050706270382054101165a6569022056e604034e2737d557c8c58ae170" + + "e14619da19a58ee85aa80ad883af9acbf3da261054887fcd755f16db1e928127344ea81a8a7db3e0" + + "b2b31166305d1de467e4e8b8a4aedbb217630645e6ef20aa3df302e743e4cca2390854ecc35de290" + + "0e14989b81ba94b15c3ce111c8472c2c9ba503c176bfbe4ebe0d2a1d1c11a0e2f461650ddccbb033" + + "ffd5df43875379eeb1ce08f2e2ffbb639aa3d8ac2cc5b291000db0642817f03603d16a3ccc97c167" + + "401f2a41f3332b9b623ba8ca11bf9f7740b9521c0e61ee0d5cb6b3a6a1adb15f36674202d43ad0bc" + + "75cdbde3f21d3fe138049841028cec3f0c512b3de7850564f605aaf42b875272465fbcedc162a101" + + "8e8df9f7b6ddae858a4cab3c91c9e6b2c23c2af2dc890b86401ae4d37c3a97afbe9076f4c1e25297" + + "e36965832208741c1ce03063ba0598122551fcfc469b3c3220148cf4f8173c05be8a37c4932a9ac9" + + "7d1c3d04456aa8ba24ac61834896003193b2c10af287d9c33e0def5d5ca3b2d1352a9f71115bf904" + + "fdbf7680412fd566cc724f26cb1cba40083b2af6a0c272d7b01c4299a556b33854644d995322ca7f" + + "60177d6af3131562206b14639ee2a268ba4c7a4d07971f4176f49033d6fbcc5948306c35d47041b6" + + "0fd1db651f2bec76ccda9265e4b764de9fca57ee9b66009bb22b3739f871aafc83ae037d0e803191" + + "de3cae1eaa5e5de67f224a1440153460f231afea991a40133539a3dc58c6dad5e0e270de69c82698" + + "830ae1632db254ddee4ce178fd9d69c59f174370f1eb3900efd7f83fb74d86319d6279411f5c009f" + + "04dd19e3f87a9937685de347f2498d1c743317af2c460a3770eb484e7cd0a59398610c33d51070fc" + + "fdb09ffe560364811286457870d34d6af2f033121ae22fc9859d984168236f1936dc90f4c7e2b8b7" + + "a961db883a73b738ea2a0eff6e665f950ecdef2dde5fcc16499d20d1887d28326ac2c21cee66a80f" + + "6d0bc50442af6750649f2634e8ecafe3689c20cf040e2eb29f62d7a594d5b07045bf35a7e2ba316a" + + "dac7ec06fa6d9e067fe7fed917058632d6bd1f05f8938b315063a6e76b41d2cd7a87930c260d8736" + + "d191a8efb244e1b0656a0285435d96ef86d27bb1f03f6f03c6dbd6296e7a22cea532ea9956483c19" + + "0316e08cf7857f522817bafedc34e8f7a03c36e9fd17c5031ceaf6f71be4553d92c84c767bd3018e" + + "88ece8e054647c157378899011cd4e7e04141ae106e59310d4ca23cb4a3492017de82a45a6bbd4bd" + + "2f19ab86c1e077628ea9b64bd7d12866bc7e790cac79a2e30ee179ebd0dae95d77dcda9e8f8e823d" + + "364da7aa23c0d4808cf6366a2d43d2167c190a5369e4d146255c154a6ce141d10ac51b0524c2e512" + + "4b5681aa8f478e4408626f8c69ec31a529feecd63f3c647d4e80ec3fac86c532cc8a490b0a2a6236" + + "d81a37156da254bfd2a0f18e283ba931026fa25922795a8ad038d4c6c7c32dffd5a64acce9f1f714" + + "88b0e83cfbbd2cf709f5084348037d08bfc34f165823fbd3a547783c17d6bb1c02f9f609cebfab74" + + "db3fd6cc62812f4b4e29e4b3c6787b6f5ff3d5c9fbfd24da806eb4559adb9301dd6b42188f3472b9" + + "247e5f7b079b97134eb25b8d1f9889dad1fbd11ff72dc6cbea38874bd0dfa31cc73e75f76ab5a954" + + "5d596786998af78f00e5fc07d27bcdbc5d861754d5e50d16dfac12e1b203a56c4ec1dff1f65e5e96" + + "cfc32d4e051326362277c06f95402ee0b933239e1b3b9258ee5a5859d4c96c9c6ae85fbcb9d71da5" + + "efcaf79edc225e4a727e93818837b34c73eef2460c3642da6567a8e1bca0474e120fbe469a3c2460" + + "6017292bcbd9732a4fc8823bcc1046b2a778878182640aa23a258f9110950218a6d0b31e3ab49155" + + "68bade5a"); + KeyFactory keyFactory = getInstance("MLDSA44-Ed25519-SHA512"); + assertThrows(InvalidKeySpecException.class, + () -> keyFactory.generatePublic(new X509EncodedKeySpec(spki))); + } +} diff --git a/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKeyTest.java b/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKeyTest.java new file mode 100644 index 000000000..70fd897ff --- /dev/null +++ b/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPrivateKeyTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2026 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.security.spec.InvalidKeySpecException; +import java.util.List; + +@RunWith(JUnit4.class) +@SuppressWarnings("InsecureCryptoUsage") +public class OpenSslCompositeMlDsaPrivateKeyTest { + @Test + public void constructor_works() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/composite_mldsa.txt"); + for (TestVector tv : vectors) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + CompositeMlDsaAlgorithm compositeMlDsaAlgorithm = + CompositeMlDsaAlgorithm.fromName(algName); + byte[] rawKey = TestUtils.decodeBase64(tv.getString("sk")); + + OpenSslCompositeMlDsaPrivateKey privateKey = + new OpenSslCompositeMlDsaPrivateKey(rawKey, compositeMlDsaAlgorithm); + + assertEquals(algName, privateKey.getAlgorithm()); + assertArrayEquals("getRaw() must match original sk bytes for: " + tv.getString("tcid"), + rawKey, privateKey.getRaw()); + assertEquals(compositeMlDsaAlgorithm, privateKey.getCompositeMlDsaAlgorithm()); + } + } + + @Test + public void getEncoded_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName).startsWith("id-"); + algName = algName.substring(3); + // Temporarily skip EC keys until we add support for them. + if (algName.contains("EC")) { + continue; + } + byte[] pkcs8Bytes = TestUtils.decodeBase64(tv.getString("skpkcs8")); + byte[] rawBytes = TestUtils.decodeBase64(tv.getString("sk")); + OpenSslCompositeMlDsaPrivateKey privateKey = new OpenSslCompositeMlDsaPrivateKey( + rawBytes, CompositeMlDsaAlgorithm.fromName(algName)); + + assertEquals("PKCS#8", privateKey.getFormat()); + assertArrayEquals(pkcs8Bytes, privateKey.getEncoded()); + } + } + + @Test + public void keyTooShort_throws() throws Exception { + assertThrows( + InvalidKeySpecException.class, + () + -> new OpenSslCompositeMlDsaPrivateKey( + TestUtils.decodeHex("dfdfcfcbb4b2312f520007399e6172a434a1a2d86204cf" + + "1e0586d7925aaac457" + + "9c939e538ab579ee8e644b903bf92f19516709"), + CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512)); + } + + @Test + public void testKeyProperties_ed25519() throws Exception { + byte[] rawKey = TestUtils.decodeHex( + "dfdfcfcbb4b2312f520007399e6172a434a1a2d86204cf1e0586d7925aaac457" + + "9c939e538ab579ee8e644b903bf92f195167099d94fc9705addea8c73cb4454c"); + + OpenSslCompositeMlDsaPrivateKey privateKey = new OpenSslCompositeMlDsaPrivateKey( + rawKey, CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512); + + assertArrayEquals(rawKey, privateKey.getRaw()); + assertEquals("MLDSA44-Ed25519-SHA512", privateKey.getAlgorithm()); + assertEquals(CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512, + privateKey.getCompositeMlDsaAlgorithm()); + // Verify ML-DSA private key. + assertArrayEquals( + TestUtils.decodeHex( + "dfdfcfcbb4b2312f520007399e6172a434a1a2d86204cf1e0586d7925aaac457"), + privateKey.getMlDsaPrivateKey().getSeed()); + // Verify Classic private key. + assertThat(privateKey.getClassicPrivateKey()).isInstanceOf(OpenSslEdDsaPrivateKey.class); + OpenSslEdDsaPrivateKey classicKey = + (OpenSslEdDsaPrivateKey) privateKey.getClassicPrivateKey(); + assertArrayEquals( + TestUtils.decodeHex( + "9c939e538ab579ee8e644b903bf92f195167099d94fc9705addea8c73cb4454c"), + classicKey.getRaw()); + } +} diff --git a/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPublicKeyTest.java b/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPublicKeyTest.java new file mode 100644 index 000000000..5fc049913 --- /dev/null +++ b/common/src/test/java/org/conscrypt/OpenSslCompositeMlDsaPublicKeyTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2026 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 com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.List; + +@RunWith(JUnit4.class) +@SuppressWarnings("InsecureCryptoUsage") +public class OpenSslCompositeMlDsaPublicKeyTest { + private static final String MLDSA44_ED25519_PK_BASE64 = + "FlplaQIgVuYEA04nN9VXyMWK4XDhRhnaGaWO6FqoCtiDr5rL89omEFSIf811XxbbHpKBJzROqBqK" + + "fbPgsrMRZjBdHeRn5Oi4pK7bshdjBkXm7yCqPfMC50PkzKI5CFTsw13ikA4UmJuBupSxXDzhEchH" + + "LCybpQPBdr++Tr4NKh0cEaDi9GFlDdzLsDP/1d9Dh1N57rHOCPLi/7tjmqPYrCzFspEADbBkKBfw" + + "NgPRajzMl8FnQB8qQfMzK5tiO6jKEb+fd0C5UhwOYe4NXLazpqGtsV82Z0IC1DrQvHXNvePyHT/h" + + "OASYQQKM7D8MUSs954UFZPYFqvQrh1JyRl+87cFioQGOjfn3tt2uhYpMqzyRyeaywjwq8tyJC4ZA" + + "GuTTfDqXr76QdvTB4lKX42llgyIIdBwc4DBjugWYEiVR/PxGmzwyIBSM9PgXPAW+ijfEkyqayX0c" + + "PQRFaqi6JKxhg0iWADGTssEK8ofZwz4N711co7LRNSqfcRFb+QT9v3aAQS/VZsxyTybLHLpACDsq" + + "9qDCctewHEKZpVazOFRkTZlTIsp/YBd9avMTFWIgaxRjnuKiaLpMek0Hlx9BdvSQM9b7zFlIMGw1" + + "1HBBtg/R22UfK+x2zNqSZeS3ZN6fylfum2YAm7IrNzn4car8g64DfQ6AMZHePK4eql5d5n8iShRA" + + "FTRg8jGv6pkaQBM1OaPcWMba1eDicN5pyCaYgwrhYy2yVN3uTOF4/Z1pxZ8XQ3Dx6zkA79f4P7dN" + + "hjGdYnlBH1wAnwTdGeP4epk3aF3jR/JJjRx0MxevLEYKN3DrSE580KWTmGEMM9UQcPz9sJ/+VgNk" + + "gRKGRXhw001q8vAzEhriL8mFnZhBaCNvGTbckPTH4ri3qWHbiDpztzjqKg7/bmZflQ7N7y3eX8wW" + + "SZ0g0Yh9KDJqwsIc7maoD20LxQRCr2dQZJ8mNOjsr+NonCDPBA4usp9i16WU1bBwRb81p+K6MWra" + + "x+wG+m2eBn/n/tkXBYYy1r0fBfiTizFQY6bna0HSzXqHkwwmDYc20ZGo77JE4bBlagKFQ12W74bS" + + "e7HwP28DxtvWKW56Is6lMuqZVkg8GQMW4Iz3hX9SKBe6/tw06PegPDbp/RfFAxzq9vcb5FU9kshM" + + "dnvTAY6I7OjgVGR8FXN4iZARzU5+BBQa4QblkxDUyiPLSjSSAX3oKkWmu9S9LxmrhsHgd2KOqbZL" + + "19EoZrx+eQyseaLjDuF569Da6V133Nqej46CPTZNp6ojwNSAjPY2ai1D0hZ8GQpTaeTRRiVcFUps" + + "4UHRCsUbBSTC5RJLVoGqj0eORAhib4xp7DGlKf7s1j88ZH1OgOw/rIbFMsyKSQsKKmI22Bo3FW2i" + + "VL/SoPGOKDupMQJvolkieVqK0DjUxsfDLf/VpkrM6fH3FIiw6Dz7vSz3CfUIQ0gDfQi/w08WWCP7" + + "06VHeDwX1rscAvn2Cc6/q3TbP9bMYoEvS04p5LPGeHtvX/PVyfv9JNqAbrRVmtuTAd1rQhiPNHK5" + + "JH5feweblxNOsluNH5iJ2tH70R/3LcbL6jiHS9DfoxzHPnX3arWpVF1ZZ4aZivePAOX8B9J7zbxd" + + "hhdU1eUNFt+sEuGyA6VsTsHf8fZeXpbPwy1OBRMmNiJ3wG+VQC7guTMjnhs7kljuWlhZ1MlsnGro" + + "X7y51x2l78r3ntwiXkpyfpOBiDezTHPu8kYMNkLaZWeo4bygR04SD75GmjwkYGAXKSvL2XMqT8iC" + + "O8wQRrKneIeBgmQKojolj5EQlQIYptCzHjq0kVVout5a"; + + private static final String MLDSA44_PK_BASE64 = + "FlplaQIgVuYEA04nN9VXyMWK4XDhRhnaGaWO6FqoCtiDr5rL89omEFSIf811XxbbHpKBJzROqBqK" + + "fbPgsrMRZjBdHeRn5Oi4pK7bshdjBkXm7yCqPfMC50PkzKI5CFTsw13ikA4UmJuBupSxXDzhEchH" + + "LCybpQPBdr++Tr4NKh0cEaDi9GFlDdzLsDP/1d9Dh1N57rHOCPLi/7tjmqPYrCzFspEADbBkKBfw" + + "NgPRajzMl8FnQB8qQfMzK5tiO6jKEb+fd0C5UhwOYe4NXLazpqGtsV82Z0IC1DrQvHXNvePyHT/h" + + "OASYQQKM7D8MUSs954UFZPYFqvQrh1JyRl+87cFioQGOjfn3tt2uhYpMqzyRyeaywjwq8tyJC4ZA" + + "GuTTfDqXr76QdvTB4lKX42llgyIIdBwc4DBjugWYEiVR/PxGmzwyIBSM9PgXPAW+ijfEkyqayX0c" + + "PQRFaqi6JKxhg0iWADGTssEK8ofZwz4N711co7LRNSqfcRFb+QT9v3aAQS/VZsxyTybLHLpACDsq" + + "9qDCctewHEKZpVazOFRkTZlTIsp/YBd9avMTFWIgaxRjnuKiaLpMek0Hlx9BdvSQM9b7zFlIMGw1" + + "1HBBtg/R22UfK+x2zNqSZeS3ZN6fylfum2YAm7IrNzn4car8g64DfQ6AMZHePK4eql5d5n8iShRA" + + "FTRg8jGv6pkaQBM1OaPcWMba1eDicN5pyCaYgwrhYy2yVN3uTOF4/Z1pxZ8XQ3Dx6zkA79f4P7dN" + + "hjGdYnlBH1wAnwTdGeP4epk3aF3jR/JJjRx0MxevLEYKN3DrSE580KWTmGEMM9UQcPz9sJ/+VgNk" + + "gRKGRXhw001q8vAzEhriL8mFnZhBaCNvGTbckPTH4ri3qWHbiDpztzjqKg7/bmZflQ7N7y3eX8wW" + + "SZ0g0Yh9KDJqwsIc7maoD20LxQRCr2dQZJ8mNOjsr+NonCDPBA4usp9i16WU1bBwRb81p+K6MWra" + + "x+wG+m2eBn/n/tkXBYYy1r0fBfiTizFQY6bna0HSzXqHkwwmDYc20ZGo77JE4bBlagKFQ12W74bS" + + "e7HwP28DxtvWKW56Is6lMuqZVkg8GQMW4Iz3hX9SKBe6/tw06PegPDbp/RfFAxzq9vcb5FU9kshM" + + "dnvTAY6I7OjgVGR8FXN4iZARzU5+BBQa4QblkxDUyiPLSjSSAX3oKkWmu9S9LxmrhsHgd2KOqbZL" + + "19EoZrx+eQyseaLjDuF569Da6V133Nqej46CPTZNp6ojwNSAjPY2ai1D0hZ8GQpTaeTRRiVcFUps" + + "4UHRCsUbBSTC5RJLVoGqj0eORAhib4xp7DGlKf7s1j88ZH1OgOw/rIbFMsyKSQsKKmI22Bo3FW2i" + + "VL/SoPGOKDupMQJvolkieVqK0DjUxsfDLf/VpkrM6fH3FIiw6Dz7vSz3CfUIQ0gDfQi/w08WWCP7" + + "06VHeDwX1rscAvn2Cc6/q3TbP9bMYoEvS04p5LPGeHtvX/PVyfv9JNqAbrRVmtuTAd1rQhiPNHK5" + + "JH5feweblxNOsluNH5iJ2tH70R/3LcbL6jiHS9DfoxzHPnX3arWpVF1ZZ4aZivePAOX8B9J7zbxd" + + "hhdU1eUNFt+sEuGyA6VsTsHf8fZeXpbPwy1OBRMmNiJ3wG+VQC7guTMjnhs7kljuWlhZ1MlsnGro" + + "X7y51x2l78r3ntwiXkpyfpOBiDezTHPu8kYMNkLaZWeo4bygR04SD75GmjwkYGAXKSvL2XMqT8iC" + + "Ow=="; + + private static final String ED25519_PK_BASE64 = "zBBGsqd4h4GCZAqiOiWPkRCVAhim0LMeOrSRVWi63lo="; + + @Test + public void constructor_works() throws Exception { + List vectors = TestUtils.readTestVectors("crypto/composite_mldsa.txt"); + for (TestVector tv : vectors) { + String algName = tv.getString("tcid"); + assertThat(algName.startsWith("id-")).isTrue(); + algName = algName.substring(3); + CompositeMlDsaAlgorithm compositeMlDsaAlgorithm = + CompositeMlDsaAlgorithm.fromName(algName); + byte[] rawKey = TestUtils.decodeBase64(tv.getString("pk")); + + OpenSslCompositeMlDsaPublicKey publicKey = + new OpenSslCompositeMlDsaPublicKey(rawKey, compositeMlDsaAlgorithm); + + assertEquals(algName, publicKey.getAlgorithm()); + assertArrayEquals("getRaw() must match original pk bytes for: " + tv.getString("tcid"), + rawKey, publicKey.getRaw()); + assertEquals(compositeMlDsaAlgorithm, publicKey.getCompositeMlDsaAlgorithm()); + } + } + + @Test + public void getEncoded_works() throws Exception { + for (TestVector tv : TestUtils.readTestVectors("crypto/composite_mldsa.txt")) { + String algName = tv.getString("tcid"); + assertThat(algName.startsWith("id-")).isTrue(); + algName = algName.substring(3); + byte[] spkiBytes = TestUtils.decodeBase64(tv.getString("pkx509")); + byte[] rawBytes = TestUtils.decodeBase64(tv.getString("pk")); + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + rawBytes, CompositeMlDsaAlgorithm.fromName(algName)); + + assertEquals("X.509", publicKey.getFormat()); + assertArrayEquals(spkiBytes, publicKey.getEncoded()); + } + } + + @Test + public void keyShorterThanFullKey_throws() throws Exception { + assertThrows( + IllegalArgumentException.class, + () + -> new OpenSslCompositeMlDsaPublicKey( + Arrays.copyOf(TestUtils.decodeBase64(MLDSA44_ED25519_PK_BASE64), + 1330), + CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512)); + } + + @Test + public void keyShorterThanMlDsaKey_throws() throws Exception { + assertThrows(IllegalArgumentException.class, + () + -> new OpenSslCompositeMlDsaPublicKey( + Arrays.copyOf( + TestUtils.decodeBase64(MLDSA44_ED25519_PK_BASE64), 30), + CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512)); + } + + @Test + public void emptyKey_throws() throws Exception { + assertThrows(IllegalArgumentException.class, + () + -> new OpenSslCompositeMlDsaPublicKey( + new byte[0], CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512)); + } + + @Test + public void testKeyProperties_ed25519() throws Exception { + byte[] rawKey = TestUtils.decodeBase64(MLDSA44_ED25519_PK_BASE64); + OpenSslCompositeMlDsaPublicKey publicKey = new OpenSslCompositeMlDsaPublicKey( + rawKey, CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512); + + assertArrayEquals(rawKey, publicKey.getRaw()); + assertEquals("MLDSA44-Ed25519-SHA512", publicKey.getAlgorithm()); + assertEquals(CompositeMlDsaAlgorithm.MLDSA44_ED25519_SHA512, + publicKey.getCompositeMlDsaAlgorithm()); + // Verify ML-DSA public key. + assertArrayEquals(TestUtils.decodeBase64(MLDSA44_PK_BASE64), + publicKey.getMlDsaPublicKey().getRaw()); + // Verify Classic public key. + assertThat(publicKey.getClassicPublicKey()).isInstanceOf(OpenSslEdDsaPublicKey.class); + OpenSslEdDsaPublicKey classicKey = (OpenSslEdDsaPublicKey) publicKey.getClassicPublicKey(); + assertArrayEquals(TestUtils.decodeBase64(ED25519_PK_BASE64), classicKey.getRaw()); + } +} diff --git a/common/src/test/java/org/conscrypt/SignatureThreadMisuseTest.java b/common/src/test/java/org/conscrypt/SignatureThreadMisuseTest.java new file mode 100644 index 000000000..08fb66ada --- /dev/null +++ b/common/src/test/java/org/conscrypt/SignatureThreadMisuseTest.java @@ -0,0 +1,163 @@ +package org.conscrypt; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.Signature; +import java.security.spec.PKCS8EncodedKeySpec; + +/** + * These tests abuse a single Signature object across multiple threads to check for any native + * crashes. + */ +@RunWith(JUnit4.class) +public final class SignatureThreadMisuseTest { + private final Provider conscryptProvider = TestUtils.getConscryptProvider(); + + @Before + public void setUp() { + Assume.assumeFalse("Skipping thread misuse test under TSAN", TestUtils.isTsan()); + } + + @Test + public void testSha256withEcdsa() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", conscryptProvider); + kpg.initialize(256); + KeyPair kp = kpg.generateKeyPair(); + PrivateKey privateKey = kp.getPrivate(); + Signature signature = Signature.getInstance("SHA256withECDSA", conscryptProvider); + final byte[] message = new byte[64]; + TestUtils.stressTestAllowingExceptions(16, 100, () -> { + signature.initSign(privateKey); + signature.update(message); + signature.sign(); + }); + } + + @Test + public void testNoneWithEcdsa() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", conscryptProvider); + kpg.initialize(256); + KeyPair kp = kpg.generateKeyPair(); + PrivateKey privateKey = kp.getPrivate(); + Signature signature = Signature.getInstance("NONEwithECDSA", conscryptProvider); + final byte[] message = new byte[64]; + TestUtils.stressTestAllowingExceptions(16, 100, () -> { + signature.initSign(privateKey); + signature.update(message); + signature.sign(); + }); + } + + // We hard-code the private key to make the test run faster. + private static final byte[] rsaPrivKeyPkcs8 = TestUtils.decodeBase64( + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDGWlZEwCYGp4HD" + + "o+6p9xHo14eAMqQqaaPqy4CNqBwepXzfOyrWygJXxyXQNE3J/HkjvkXYjuF0BVfW" + + "nU1t58mjXn9z8FeIxgP4JlgrO/UZkp7KpnxU0foXrA73Xq57r7/8XLRtBaqBUC6p" + + "yxwNwgLbuvOaukSpU6+EbKJfPFO0yMVYjSZEg4NhgLud87SQ/Q8xMio9NhRtWtMV" + + "i1fTMOQtfX2ow+2ZE8eW2ScIHQZoizaIlJ/4tQDpY6ovRyPmmCP/1ypB/75d3odf" + + "IEOMwNQS+/e1Hxo1Ui89VLt80wx63i+nI60Rf6Num+jo3OOu5QZfr/279XA1CmaQ" + + "NJjJzKCRAgMBAAECggEATSQ1COfx4gSjYwMOfuun+3ZXLHSFhDkxls8uX+lRhlm2" + + "Bttr/bzyzFCXVDIDTtryAgSuBVsMM6MgVkc3JddPhEnsJ5sBcCASyx+eirH20MtF" + + "FmtJT3OaYDp6V5prIY9oiy1CvmPFvCUDAOYNMwyRMAO1Wie3LGNvj4DwZTmBFH2Q" + + "f++d3aVo8F4VsKHbPegIpZeRSeNvTBxqr++mEPlU3U4CvmGrEGHDkInumorL1r8v" + + "umwLgYmLTND/lIeMPu9qRP74SXU/y8BlkpBEd90FRXSRz1pvFXy//5Snd3syH0hL" + + "ow37+LFLzI92rtDv6NJiFm45C3Qz1Dc65DsScaQ3AQKBgQD6gy9FzIoETmvvfUkP" + + "7pYbL9tiNanTCg/QO7w2UaSqegDuXeuujer6+ZRYmHDcBgEPDM0IC6/VTfuNiJJf" + + "SA6zEY0QuOJ8/1NWgA6WRoTIdVOqb2rQZxPGmlApwNpm2INwgdIp8R8+z1TuiZ7Q" + + "BJGfDCyIMAg/OFNIsF2jjjWOFQKBgQDKsqdXGFLH+cfCf5D42F2RCTYW64nAq1S+" + + "6PjaeBKDt/UWKJIVuOlIK/+NTnkomgg9uYg3hz6StuyRRB+gt6ZvmdPZfbqmw013" + + "tjEaoajewTwTYTjiUEte3unSYC7eQdP3wozSSTj5xrJ0rzIylUArnep5oK9T75ZX" + + "nMQ/1P6jjQKBgQCH/N6UjUv+unNtNh2LQIDLkVcOIQNnRHcBGuw4sGkrAb+vpdxi" + + "jTeZthIJZfcd+URp3xEN5Qo2SHbdhd2vS3ZvTn+9LCAGqrOaqTlB6lX6W3ZndsQN" + + "DWd20B3kDISCf9YaJN7pVbMYbYQ1WQ+U7GYAaKNj6m8PoovTUaoDNxdrYQKBgBW9" + + "ZY9Ez5QdKRymO/FEm3xzxXZd2s9sUNGNASvFVw8DiujOfySPSY3xEh6gvQPqyVl9" + + "bauu+LoZnnPSH6ZILDSqBfu8rWk3ZzagttGJZyhFB2F4uvYi8IKDXAaxRDWYT9ix" + + "6BKhwdegRQGYzMR6F4DWFhDneAaDmtdYu/+wo4L9AoGAB2/QLydO0bicjcg/NPM6" + + "5saPHKRcF6rtRgYkAdD24NNd/hCSsnJF2Flpm0baLp/UN/eh514rsonNQy885k+4" + + "Y1uId7Gmwoo9+BmaEs+lz1TmSdPAvhAP12/wpqMMTwC91f+F2QZZtq32FoxeNOG4" + + "MUlGMKiKK2f8nB6soz84zbg="); + + @Test + public void testSha256withRsa() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("RSA", conscryptProvider); + PrivateKey privateKey = + keyFactory.generatePrivate(new PKCS8EncodedKeySpec(rsaPrivKeyPkcs8)); + + Signature signature = Signature.getInstance("SHA256withRSA", conscryptProvider); + final byte[] message = new byte[64]; + TestUtils.stressTestAllowingExceptions(16, 100, () -> { + signature.initSign(privateKey); + signature.update(message); + signature.sign(); + }); + } + + @Test + public void testSha256withRsaPss() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("RSA", conscryptProvider); + PrivateKey privateKey = + keyFactory.generatePrivate(new PKCS8EncodedKeySpec(rsaPrivKeyPkcs8)); + + Signature signature = Signature.getInstance("SHA256withRSA/PSS", conscryptProvider); + final byte[] message = new byte[64]; + TestUtils.stressTestAllowingExceptions(16, 100, () -> { + signature.initSign(privateKey); + signature.update(message); + signature.sign(); + }); + } + + @Test + public void testNoneWithRsa() throws Exception { + KeyFactory keyFactory = KeyFactory.getInstance("RSA", conscryptProvider); + PrivateKey privateKey = + keyFactory.generatePrivate(new PKCS8EncodedKeySpec(rsaPrivKeyPkcs8)); + + Signature signature = Signature.getInstance("NONEwithRSA", conscryptProvider); + final byte[] message = new byte[64]; + TestUtils.stressTestAllowingExceptions(16, 100, () -> { + signature.initSign(privateKey); + signature.update(message); + signature.sign(); + }); + } + + @Test + public void testEd25519() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519", conscryptProvider); + KeyPair kp = kpg.generateKeyPair(); + PrivateKey privateKey = kp.getPrivate(); + Signature signature = Signature.getInstance("Ed25519", conscryptProvider); + final byte[] message = new byte[64]; + TestUtils.stressTestAllowingExceptions(16, 100, () -> { + signature.initSign(privateKey); + signature.update(message); + signature.sign(); + }); + } + + @Test + public void testMlDsa44() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("ML-DSA-44", conscryptProvider); + KeyPair kp = kpg.generateKeyPair(); + PrivateKey privateKey = kp.getPrivate(); + Signature signature = Signature.getInstance("ML-DSA-44", conscryptProvider); + final byte[] message = new byte[64]; + TestUtils.stressTestAllowingExceptions(16, 100, () -> { + signature.initSign(privateKey); + signature.update(message); + signature.sign(); + }); + } + + // We currently don't run these tests for SLH-DSA-SHA2-128S, because it is too slow. +} diff --git a/common/src/test/java/org/conscrypt/TrustManagerImplTest.java b/common/src/test/java/org/conscrypt/TrustManagerImplTest.java index d4aaa2762..bb633c054 100644 --- a/common/src/test/java/org/conscrypt/TrustManagerImplTest.java +++ b/common/src/test/java/org/conscrypt/TrustManagerImplTest.java @@ -220,6 +220,16 @@ public boolean verify(X509Certificate[] certificates, String s, } } + @Test + public void testSetNetworkSecurityPolicy() throws Exception { + KeyStore keystore = TestKeyStore.createKeyStore(); + TrustManagerImpl tm = new TrustManagerImpl(keystore); + + ConscryptNetworkSecurityPolicy nsp = ConscryptNetworkSecurityPolicy.getDefault(); + tm.setNetworkSecurityPolicy(nsp); + assertEquals(nsp, tm.getNetworkSecurityPolicy()); + } + private X509TrustManager trustManager(X509Certificate ca) throws Exception { KeyStore keyStore = TestKeyStore.createKeyStore(); keyStore.setCertificateEntry("alias", ca); diff --git a/common/src/test/java/org/conscrypt/ct/SerializationTest.java b/common/src/test/java/org/conscrypt/ct/SerializationTest.java index 5ce182e10..9151fa49c 100644 --- a/common/src/test/java/org/conscrypt/ct/SerializationTest.java +++ b/common/src/test/java/org/conscrypt/ct/SerializationTest.java @@ -20,6 +20,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.test.annotation.NonCts; +// android-add: import libcore.test.reasons.NonCtsReasons; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,6 +32,7 @@ @RunWith(JUnit4.class) public class SerializationTest { @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_SignedCertificateTimestamp() throws Exception { byte[] in = new byte[] { 0x00, // version @@ -57,6 +60,7 @@ public void test_decode_SignedCertificateTimestamp() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_invalid_SignedCertificateTimestamp() throws Exception { byte[] sct = new byte[] { 0x00, // version @@ -91,6 +95,7 @@ public void test_decode_invalid_SignedCertificateTimestamp() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_DigitallySigned() throws Exception { byte[] in = new byte[] { 0x04, 0x03, // hash & signature algorithm @@ -105,6 +110,7 @@ public void test_decode_DigitallySigned() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_decode_invalid_DigitallySigned() throws Exception { try { DigitallySigned.decode(new byte[] { @@ -147,6 +153,7 @@ public void test_decode_invalid_DigitallySigned() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_encode_CertificateEntry_X509Certificate() throws Exception { // Use a dummy certificate. It doesn't matter, CertificateEntry doesn't care about the // contents. @@ -165,6 +172,7 @@ public void test_encode_CertificateEntry_X509Certificate() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_encode_CertificateEntry_PreCertificate() throws Exception { // Use a dummy certificate and issuer key hash. It doesn't matter, // CertificateEntry doesn't care about the contents. @@ -187,6 +195,7 @@ public void test_encode_CertificateEntry_PreCertificate() throws Exception { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_readDEROctetString() throws Exception { byte[] in, expected; diff --git a/common/src/test/java/org/conscrypt/ct/VerifierTest.java b/common/src/test/java/org/conscrypt/ct/VerifierTest.java index 02e8280d4..d612bedcb 100644 --- a/common/src/test/java/org/conscrypt/ct/VerifierTest.java +++ b/common/src/test/java/org/conscrypt/ct/VerifierTest.java @@ -20,6 +20,8 @@ import static org.conscrypt.TestUtils.readTestFile; import static org.junit.Assert.assertEquals; +// android-add: import libcore.test.annotation.NonCts; +// android-add: import libcore.test.reasons.NonCtsReasons; import org.conscrypt.OpenSSLX509Certificate; import org.conscrypt.TestUtils; import org.junit.Before; @@ -48,8 +50,7 @@ public void setUp() throws Exception { final LogInfo log = new LogInfo.Builder() .setPublicKey(key) - .setDescription("Test Log") - .setUrl("http://example.com") + .setType(LogInfo.TYPE_RFC6962) .setOperator("LogOperator") .setState(LogInfo.STATE_USABLE, 1643709600000L) .build(); @@ -98,6 +99,7 @@ public LogInfo getKnownLog(byte[] logId) { } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withOCSPResponse() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -109,6 +111,7 @@ public void test_verifySignedCertificateTimestamps_withOCSPResponse() throws Exc } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withTLSExtension() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -120,6 +123,7 @@ public void test_verifySignedCertificateTimestamps_withTLSExtension() throws Exc } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withEmbeddedExtension() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {certEmbedded, ca}; @@ -129,6 +133,7 @@ public void test_verifySignedCertificateTimestamps_withEmbeddedExtension() throw } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withoutTimestamp() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -138,6 +143,7 @@ public void test_verifySignedCertificateTimestamps_withoutTimestamp() throws Exc } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withInvalidSignature() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -152,6 +158,7 @@ public void test_verifySignedCertificateTimestamps_withInvalidSignature() throws } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withUnknownLog() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -165,6 +172,7 @@ public void test_verifySignedCertificateTimestamps_withUnknownLog() throws Excep } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withInvalidEncoding() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -178,6 +186,7 @@ public void test_verifySignedCertificateTimestamps_withInvalidEncoding() throws } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withInvalidOCSPResponse() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; @@ -191,6 +200,7 @@ public void test_verifySignedCertificateTimestamps_withInvalidOCSPResponse() thr } @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) public void test_verifySignedCertificateTimestamps_withMultipleTimestamps() throws Exception { OpenSSLX509Certificate[] chain = new OpenSSLX509Certificate[] {cert, ca}; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java index 0e52ad47b..192bd2bba 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestAES.java @@ -18,8 +18,12 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -32,6 +36,8 @@ @RunWith(JUnit4.class) public class AlgorithmParametersTestAES extends AbstractAlgorithmParametersTest { + // android-add: Allow access to deprecated BC algorithms. + private static final byte[] parameterData = new byte[] { (byte) 0x04, (byte) 0x08, (byte) 0x68, (byte) 0xC8, (byte) 0xFF, (byte) 0x64, (byte) 0x72, (byte) 0xF5, (byte) 0x04, (byte) 0x08, (byte) 0x68, (byte) 0xC8, diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java index 4efb757e0..858755fea 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestDESede.java @@ -18,8 +18,12 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -32,6 +36,8 @@ @RunWith(JUnit4.class) public class AlgorithmParametersTestDESede extends AbstractAlgorithmParametersTest { + // android-add: Allow access to deprecated BC algorithms. + private static final byte[] parameterData = new byte[] {(byte) 0x04, (byte) 0x08, (byte) 0x68, (byte) 0xC8, (byte) 0xFF, (byte) 0x64, (byte) 0x72, (byte) 0xF5}; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java index d9ff4fbf3..53a60f47b 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestEC.java @@ -18,8 +18,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -34,6 +38,8 @@ @RunWith(JUnit4.class) public class AlgorithmParametersTestEC extends AbstractAlgorithmParametersTest { + // android-add: Allow access to deprecated BC algorithms. + private static final String CURVE_NAME = "secp384r1"; private static final String CURVE_OID = "1.3.132.0.34"; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java index e7a4e664c..8ec33a416 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestGCM.java @@ -20,8 +20,12 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -39,6 +43,8 @@ @RunWith(JUnit4.class) public class AlgorithmParametersTestGCM extends AbstractAlgorithmParametersTest { + // android-add: Allow access to deprecated BC algorithms. + private static final byte[] IV = new byte[] { (byte) 0x04, (byte) 0x08, (byte) 0x68, (byte) 0xC8, (byte) 0xFF, (byte) 0x64, (byte) 0x72, (byte) 0xF5, (byte) 0x04, (byte) 0x08, (byte) 0x68, (byte) 0xC8}; diff --git a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java index 45c1b15f9..56b755e45 100644 --- a/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java +++ b/common/src/test/java/org/conscrypt/java/security/AlgorithmParametersTestOAEP.java @@ -18,8 +18,12 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -35,6 +39,8 @@ @RunWith(JUnit4.class) public class AlgorithmParametersTestOAEP extends AbstractAlgorithmParametersTest { + // android-add: Allow access to deprecated BC algorithms. + // The ASN.1 encoding for OAEP params (specified in RFC 4055 section 4.1) specifies // default values for all parameters, so we need to consider encodings with those // values both explicitly specified and unspecified. When encoding values, it is required diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java index 152b05750..87192b6a6 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestEC.java @@ -15,6 +15,11 @@ */ package org.conscrypt.java.security; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -32,6 +37,8 @@ @RunWith(JUnit4.class) public class KeyFactoryTestEC extends AbstractKeyFactoryTest { + // android-add: Allow access to deprecated BC algorithms. + public KeyFactoryTestEC() { super("EC", ECPublicKeySpec.class, ECPrivateKeySpec.class); } diff --git a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java index cc55d4ff5..a2e23bc53 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyFactoryTestRSA.java @@ -20,7 +20,11 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -47,6 +51,8 @@ @RunWith(JUnit4.class) public class KeyFactoryTestRSA extends AbstractKeyFactoryTest { + // android-add: Allow access to deprecated BC algorithms. + public KeyFactoryTestRSA() { super("RSA", RSAPublicKeySpec.class, RSAPrivateKeySpec.class); } diff --git a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java index 521266a28..6a1aa48eb 100644 --- a/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/java/security/KeyPairGeneratorTest.java @@ -20,8 +20,16 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; +// android-add: import libcore.test.annotation.NonCts; +// android-add: import libcore.test.annotation.NonMts; +// android-add: import libcore.test.reasons.NonCtsReasons; +// android-add: import libcore.test.reasons.NonMtsReasons; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -38,6 +46,7 @@ import java.security.Provider; import java.security.PublicKey; import java.security.SecureRandom; +import java.security.Security; import java.security.interfaces.DSAParams; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.DSAPublicKey; @@ -63,7 +72,11 @@ @RunWith(JUnit4.class) public class KeyPairGeneratorTest { + // android-add: Allow access to deprecated BC algorithms. + @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) + // android-add: @NonMts(reason = NonMtsReasons.API_LEVEL_GATING) public void test_getInstance() throws Exception { ServiceTester .test("KeyPairGenerator") @@ -162,8 +175,21 @@ private static List getKeySizes(String algorithm) throws Exception { putKeySize("ML-DSA-87", -1); putKeySize("SLH-DSA-SHA2-128S", -1); putKeySize("XWING", -1); + putKeySize("MLDSA44-RSA2048-PSS-SHA256", -1); + putKeySize("MLDSA44-RSA2048-PKCS15-SHA256", -1); putKeySize("MLDSA44-Ed25519-SHA512", -1); + putKeySize("MLDSA44-ECDSA-P256-SHA256", -1); + putKeySize("MLDSA65-RSA3072-PSS-SHA512", -1); + putKeySize("MLDSA65-RSA3072-PKCS15-SHA512", -1); + putKeySize("MLDSA65-RSA4096-PSS-SHA512", -1); + putKeySize("MLDSA65-RSA4096-PKCS15-SHA512", -1); putKeySize("MLDSA65-Ed25519-SHA512", -1); + putKeySize("MLDSA65-ECDSA-P256-SHA512", -1); + putKeySize("MLDSA65-ECDSA-P384-SHA512", -1); + putKeySize("MLDSA87-RSA3072-PSS-SHA512", -1); + putKeySize("MLDSA87-RSA4096-PSS-SHA512", -1); + putKeySize("MLDSA87-ECDSA-P384-SHA512", -1); + putKeySize("MLDSA87-ECDSA-P521-SHA512", -1); } /** Elliptic Curve Crypto named curves that should be supported. */ diff --git a/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java b/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java index de430ea1f..0d79008d4 100644 --- a/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java +++ b/common/src/test/java/org/conscrypt/java/security/MessageDigestTest.java @@ -18,8 +18,12 @@ import static org.junit.Assert.assertEquals; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -34,6 +38,8 @@ @RunWith(JUnit4.class) public final class MessageDigestTest { + // android-add: Allow access to deprecated BC algorithms. + private final byte[] sha_456 = {-24, 9, -59, -47, -50, -92, 123, 69, -29, 71, 1, -46, 63, 96, -118, -102, 88, 3, 77, -55}; diff --git a/common/src/test/java/org/conscrypt/java/security/SecureRandomTest.java b/common/src/test/java/org/conscrypt/java/security/SecureRandomTest.java new file mode 100644 index 000000000..acec7a35f --- /dev/null +++ b/common/src/test/java/org/conscrypt/java/security/SecureRandomTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2026 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.java.security; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.conscrypt.TestUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.security.Provider; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +@RunWith(JUnit4.class) +public class SecureRandomTest { + @Test + public void testSHA1PRNG_isThreadSafe_attribute() throws Exception { + Provider provider = TestUtils.getConscryptProvider(); + Provider.Service service = provider.getService("SecureRandom", "SHA1PRNG"); + assertNotNull(service); + assertEquals("true", service.getAttribute("ThreadSafe")); + } + + @Test + public void testSHA1PRNG_Concurrency() throws Exception { + Provider provider = TestUtils.getConscryptProvider(); + final SecureRandom random = SecureRandom.getInstance("SHA1PRNG", provider); + + int numThreads = 10; + final int iterations = 1000; + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + List> futures = new ArrayList<>(); + + for (int i = 0; i < numThreads; i++) { + futures.add(executor.submit(new Runnable() { + @Override + public void run() { + byte[] bytes = new byte[16]; + for (int j = 0; j < iterations; j++) { + random.nextBytes(bytes); + } + } + })); + } + + executor.shutdown(); + assertTrue(executor.awaitTermination(10, TimeUnit.SECONDS)); + + for (Future future : futures) { + future.get(); // Will throw exception if any thread failed + } + } +} diff --git a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java index 287a8388a..f006ecd7e 100644 --- a/common/src/test/java/org/conscrypt/java/security/SignatureTest.java +++ b/common/src/test/java/org/conscrypt/java/security/SignatureTest.java @@ -23,11 +23,19 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; +// android-add: import libcore.test.annotation.NonCts; +// android-add: import libcore.test.annotation.NonMts; +// android-add: import libcore.test.reasons.NonCtsReasons; +// android-add: import libcore.test.reasons.NonMtsReasons; + import org.conscrypt.Conscrypt; import org.conscrypt.TestUtils; import org.conscrypt.testing.BrokenProvider; import org.conscrypt.testing.OpaqueProvider; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -71,10 +79,14 @@ @RunWith(JUnit4.class) public class SignatureTest { + // android-add: Allow access to deprecated BC algorithms. + // 20 bytes for DSA private final byte[] EMPTY_DATA = new byte[20]; @Test + // android-add: @NonCts(reason = NonCtsReasons.INTERNAL_APIS) + // android-add: @NonMts(reason = NonMtsReasons.API_LEVEL_GATING) public void test_getInstance() throws Exception { ServiceTester .test("Signature") @@ -150,6 +162,8 @@ private KeyPair keyPair(String sigAlgorithm) throws Exception { kpAlgorithm = "ED25519"; } else if (sigAlgorithmUpperCase.startsWith("ML-DSA")) { kpAlgorithm = "ML-DSA"; + } else if (sigAlgorithmUpperCase.startsWith("MLDSA")) { + kpAlgorithm = sigAlgorithm; } else { throw new Exception("Unknown KeyPair algorithm for Signature algorithm " + sigAlgorithm); @@ -3186,8 +3200,13 @@ public void testVerify_SHA256withDSA_Key_Success() throws Exception { // Abuse a single Signature object across multiple threads to check for any native crashes. private void testSignature_ThreadMisuse(final Signature signature, final PrivateKey key) throws Exception { + if (TestUtils.isTsan()) { + return; + } final byte[] message = new byte[64]; - TestUtils.stressTestAllowingExceptions(16, 100, () -> { + int threads = signature.getAlgorithm().contains("MLDSA") ? 4 : 16; + int iterations = signature.getAlgorithm().contains("MLDSA") ? 20 : 100; + TestUtils.stressTestAllowingExceptions(threads, iterations, () -> { signature.initSign(key); signature.update(message); signature.sign(); diff --git a/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java b/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java index 910d597bf..a44e8c27f 100644 --- a/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java +++ b/common/src/test/java/org/conscrypt/java/security/cert/CertificateFactoryTest.java @@ -17,12 +17,15 @@ package org.conscrypt.java.security.cert; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; @@ -31,7 +34,9 @@ import org.conscrypt.Conscrypt; import org.conscrypt.TestUtils; import org.conscrypt.java.security.StandardNames; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -51,6 +56,7 @@ import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; +import java.security.Security; import java.security.cert.CRL; import java.security.cert.CRLException; import java.security.cert.CertPath; @@ -73,6 +79,8 @@ @RunWith(JUnit4.class) public class CertificateFactoryTest { + // android-add: Allow access to deprecated BC algorithms. + private static final String VALID_CERTIFICATE_PEM = "-----BEGIN CERTIFICATE-----\n" + "MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM\n" + "MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg\n" diff --git a/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java b/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java index b0244a724..5f8dcc57a 100644 --- a/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java +++ b/common/src/test/java/org/conscrypt/java/security/cert/X509CRLTest.java @@ -23,8 +23,12 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -44,6 +48,8 @@ @RunWith(JUnit4.class) public class X509CRLTest { + // android-add: Allow access to deprecated BC algorithms. + private static final String CA_CERT = "-----BEGIN CERTIFICATE-----\n" + "MIIC0DCCAjmgAwIBAgIBADANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJHQjEk\n" + "MCIGA1UEChMbQ2VydGlmaWNhdGUgVHJhbnNwYXJlbmN5IENBMQ4wDAYDVQQIEwVX\n" diff --git a/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java b/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java index 78d1ece96..cccb64c63 100644 --- a/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java +++ b/common/src/test/java/org/conscrypt/java/security/cert/X509CertificateTest.java @@ -23,6 +23,8 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,7 +48,6 @@ import java.util.Comparator; import java.util.Date; import java.util.List; -import java.util.Locale; import java.util.TimeZone; import javax.security.auth.x500.X500Principal; @@ -56,6 +57,8 @@ @RunWith(JUnit4.class) public class X509CertificateTest { + // android-add: Allow access to deprecated BC algorithms. + private static final String VALID_CERT = "-----BEGIN CERTIFICATE-----\n" + "MIIFMjCCAxqgAwIBAgIJAL0mG5fOeJ7xMA0GCSqGSIb3DQEBCwUAMC0xCzAJBgNV\n" + "BAYTAkdCMQ8wDQYDVQQHDAZMb25kb24xDTALBgNVBAoMBFRlc3QwIBcNMTgwOTE3\n" @@ -87,6 +90,122 @@ public class X509CertificateTest { + "KStYq7X9PKseN+PvmfeoffIKc5R/Ha39oi7cGMVHCr8aiEhsf94=\n" + "-----END CERTIFICATE-----"; + // Self-signed certificate using ECDSA over curve P-256 with SHA256. + private static final String CERT_WITH_ECDSA = "-----BEGIN CERTIFICATE-----\n" + + "MIICDzCCAbYCCQDlsuMWvgQzhTAKBggqhkjOPQQDAjCBjzELMAkGA1UEBhMCVVMx\n" + + "EzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZpZXcxFTAT\n" + + "BgNVBAoMDEdvb2dsZSwgSW5jLjEXMBUGA1UEAwwOd3d3Lmdvb2dsZS5jb20xIzAh\n" + + "BgkqhkiG9w0BCQEWFGdvbGFuZy1kZXZAZ21haWwuY29tMB4XDTEyMDUyMTAwMTkx\n" + + "NloXDTIyMDUxOTAwMTkxNlowgY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp\n" + + "Zm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRUwEwYDVQQKDAxHb29nbGUs\n" + + "IEluYy4xFzAVBgNVBAMMDnd3dy5nb29nbGUuY29tMSMwIQYJKoZIhvcNAQkBFhRn\n" + + "b2xhbmctZGV2QGdtYWlsLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPMt\n" + + "2ErhxAty5EJRu9yM+MTy+hUXm3pdW1ensAv382KoGExSXAFWP7pjJnNtHO+XSwVm\n" + + "YNtqjcAGFKpweoN//kQwCgYIKoZIzj0EAwIDRwAwRAIgIYSaUA/IB81gjbIw/hUV\n" + + "70twxJr5EcgOo0hLp3Jm+EYCIFDO3NNcgmURbJ1kfoS3N/0O+irUtoPw38YoNkqJ\n" + + "h5wi\n" + + "-----END CERTIFICATE-----\n"; + + // Self-signed certificate using Ed25519 + private static final String CERT_WITH_ED25519 = "-----BEGIN CERTIFICATE-----\n" + + "MIIBWzCCAQ2gAwIBAgIUDIPYISuCyyOYI2Pi95eKQ1vzvZIwBQYDK2VwMCMxITAf\n" + + "BgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZpY2F0ZTAeFw0xOTA1MDYxNzI3MTZa\n" + + "Fw0xOTA2MDUxNzI3MTZaMCMxITAfBgNVBAMMGEVkMjU1MTkgdGVzdCBjZXJ0aWZp\n" + + "Y2F0ZTAqMAUGAytlcAMhADYpxWwNTxRsgdD/ddNqcF9pzQ9NZtXamH6CSYmjijz6\n" + + "o1MwUTAdBgNVHQ4EFgQUCTs6nUop2JX/aL57Q1Ry4K2i464wHwYDVR0jBBgwFoAU\n" + + "CTs6nUop2JX/aL57Q1Ry4K2i464wDwYDVR0TAQH/BAUwAwEB/zAFBgMrZXADQQBT\n" + + "pVgcLDsqnqydTqUdX11tprUI3hKC85cgrvrYmPQagzJrkfUkHcQgfyziTdoTO21U\n" + + "GtKoKNxgudT0eEs8HJEA\n" + + "-----END CERTIFICATE-----\n"; + + // Self-signed certificate using ML-DSA-44 + private static final String CERT_WITH_MLDSA44 = "-----BEGIN CERTIFICATE-----\n" + + "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\n" + + "AAAAABcmOEM=\n" + + "-----END CERTIFICATE-----\n"; + /* This certificate is a modified version of the above self-signed cert. The cert has been modified to change the certificate data's signature algorithm @@ -454,10 +573,51 @@ public void testExplicitEcParams() { } @Test - public void testSigAlgName() { + public void testRsaCert() { ServiceTester.test("CertificateFactory").withAlgorithm("X509").run((p, algorithm) -> { X509Certificate c = certificateFromPEM(p, VALID_CERT); - assertEquals("SHA256WITHRSA", c.getSigAlgName().toUpperCase(Locale.ROOT)); + assertEquals("1.2.840.113549.1.1.11", c.getSigAlgOID()); + assertEquals("SHA256withRSA", c.getSigAlgName()); + c.verify(c.getPublicKey()); + }); + } + + @Test + public void testEcdsaCert() { + ServiceTester.test("CertificateFactory").withAlgorithm("X509").run((p, algorithm) -> { + X509Certificate c = certificateFromPEM(p, CERT_WITH_ECDSA); + assertEquals("1.2.840.10045.4.3.2", c.getSigAlgOID()); + assertEquals("SHA256withECDSA", c.getSigAlgName()); + c.verify(c.getPublicKey()); + }); + } + + @Test + public void testEd25519Cert() { + ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509"); + if (!TestUtils.isJavaVersion(18)) { + // Added because this test fails on Java 17 for unknown reasons. + tester.skipProvider("SUN"); + } + tester.run((p, algorithm) -> { + X509Certificate c = certificateFromPEM(p, CERT_WITH_ED25519); + assertEquals("1.3.101.112", c.getSigAlgOID()); + assertEquals("Ed25519", c.getSigAlgName()); + c.verify(c.getPublicKey()); + }); + } + + @Test + public void testMldsaCert() { + ServiceTester tester = ServiceTester.test("CertificateFactory").withAlgorithm("X509"); + if (!TestUtils.isJavaVersion(18)) { + // Added because this test fails on Java 17 for unknown reasons. + tester.skipProvider("SUN"); + } + tester.run((p, algorithm) -> { + X509Certificate c = certificateFromPEM(p, CERT_WITH_MLDSA44); + assertEquals("2.16.840.1.101.3.4.3.17", c.getSigAlgOID()); + assertEquals("ML-DSA-44", c.getSigAlgName()); c.verify(c.getPublicKey()); }); } diff --git a/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java b/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java index 471dfcc2c..ddd2fd818 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/AeadCipherTest.java @@ -27,17 +27,33 @@ public static void setUp() { TestUtils.assumeAllowsUnsignedCrypto(); } + // UTP's log writer attempts to write stdout to a file named after the test. + // Since Android's Context.openFileOutput forbids path separators (slashes), + // parameterized tests with slashes in their names (like cipher names) will crash. + // This helper replaces slashes with underscores in toString() to avoid this. + private static class CipherParam { + final String name; + CipherParam(String name) { + this.name = name; + } + @Override + public String toString() { + return name.replace('/', '_'); + } + } + @Parameterized.Parameters(name = "{0}") - public static Iterable ciphers() { - return Arrays.asList("AES/GCM/NoPadding", "AES/GCM-SIV/NoPadding", - "ChaCha20/Poly1305/NoPadding"); + public static Iterable ciphers() { + return Arrays.asList(new CipherParam("AES/GCM/NoPadding"), + new CipherParam("AES/GCM-SIV/NoPadding"), + new CipherParam("ChaCha20/Poly1305/NoPadding")); } private final String cipher; private byte counter; - public AeadCipherTest(String cipher) { - this.cipher = cipher; + public AeadCipherTest(CipherParam cipher) { + this.cipher = cipher.name; } private Key newKey() { diff --git a/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java b/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java index b8e72fcb6..5703f2451 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/CipherBasicsTest.java @@ -44,6 +44,7 @@ import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; + import tests.util.ServiceTester; /** diff --git a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java index 78b5e8779..874f886cf 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/CipherTest.java @@ -22,9 +22,14 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; +// android-add: import libcore.test.annotation.NonCts; +// android-add: import libcore.test.reasons.NonCtsReasons; + import org.bouncycastle.asn1.x509.KeyUsage; import org.conscrypt.Conscrypt; import org.conscrypt.TestUtils; @@ -32,7 +37,9 @@ import org.conscrypt.java.security.TestKeyStore; import org.junit.Assume; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -55,6 +62,7 @@ import java.security.Security; import java.security.cert.Certificate; import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; import java.security.spec.MGF1ParameterSpec; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPublicKeySpec; @@ -83,10 +91,13 @@ import javax.crypto.spec.PBEParameterSpec; import javax.crypto.spec.PSource; import javax.crypto.spec.SecretKeySpec; + import tests.util.ServiceTester; @RunWith(JUnit4.class) public final class CipherTest { + // android-add: Allow access to deprecated BC algorithms. + @BeforeClass public static void setUp() { TestUtils.assumeAllowsUnsignedCrypto(); @@ -3822,16 +3833,17 @@ private void testCipher_Success_ForAllSupportingProviders_AtLeastOneProviderRequ for (CipherTestParam testVector : testVectors) { ArrayList providers = new ArrayList<>(); - Provider[] providerArray = ServiceTester.getProviders("Cipher." + testVector.transformation); + Provider[] providerArray = + ServiceTester.getProviders("Cipher." + testVector.transformation); if (providerArray != null) { Collections.addAll(providers, providerArray); } if (testVector.transformation.indexOf('/') > 0) { - Provider[] baseTransformProviderArray = - ServiceTester.getProviders("Cipher." - + testVector.transformation.substring( - 0, testVector.transformation.indexOf('/'))); + Provider[] baseTransformProviderArray = ServiceTester.getProviders( + "Cipher." + + testVector.transformation.substring( + 0, testVector.transformation.indexOf('/'))); if (baseTransformProviderArray != null) { Collections.addAll(providers, baseTransformProviderArray); } @@ -4500,6 +4512,47 @@ private void testAES_ECB_PKCS5Padding_ShortBuffer_Failure(String provider) throw } } + @Test + public void testAES_doFinal_AfterFailedDoFinal_Success() throws Exception { + for (String provider : AES_PROVIDERS) { + testAES_doFinal_AfterFailedDoFinal_Success(provider); + } + } + + private void testAES_doFinal_AfterFailedDoFinal_Success(String provider) throws Exception { + Cipher enc = Cipher.getInstance("AES/ECB/PKCS5Padding", provider); + enc.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(new byte[16], "AES")); + byte[] plaintext = "Hello World!".getBytes("UTF-8"); + byte[] ciphertext = enc.doFinal(plaintext); + + Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding", provider); + c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(new byte[16], "AES")); + byte[] badCiphertext = new byte[16]; // Zeros, invalid padding + + try { + c.doFinal(badCiphertext); + fail("Should have thrown BadPaddingException"); + } catch (BadPaddingException expected) { + // expected + } + + // Cipher should be reset and usable again + byte[] decrypted = c.doFinal(ciphertext); + assertEquals(Arrays.toString(plaintext), Arrays.toString(decrypted)); + + // Test with output buffer + try { + c.doFinal(badCiphertext, 0, badCiphertext.length, new byte[16], 0); + fail("Should have thrown BadPaddingException"); + } catch (BadPaddingException expected) { + // expected + } + + byte[] decrypted2 = new byte[16]; + int len = c.doFinal(ciphertext, 0, ciphertext.length, decrypted2, 0); + assertEquals(Arrays.toString(plaintext), Arrays.toString(Arrays.copyOf(decrypted2, len))); + } + @Test public void testAES_ECB_NoPadding_IncrementalUpdate_Success() throws Exception { for (String provider : AES_PROVIDERS) { @@ -4588,7 +4641,6 @@ public void testRC4_MultipleKeySizes() throws Exception { /* Find all providers that provide ARC4. We must have at least one! */ Map filter = new HashMap<>(); filter.put("Cipher.ARC4", ""); - //? Provider[] providers = ServiceTester.getProviders(filter); assertTrue("There must be security providers of Cipher.ARC4", providers.length > 0); @@ -4778,40 +4830,6 @@ public void test_AESGCMNoPadding_init_algParams() throws Exception { assertEquals(Arrays.toString(c1.doFinal()), Arrays.toString(c2.doFinal())); } - /* - * http://b/27224566 - * http://b/27994930 - * Check that a PBKDF2WITHHMACSHA1 secret key factory works well with a - * PBEWITHSHAAND128BITAES-CBC-BC cipher. The former is PKCS5 and the latter is PKCS12, and so - * mixing them is not recommended. However, until 1.52 BouncyCastle was accepting this mixture, - * assuming the IV was a 0 vector. Some apps still use this functionality. This - * compatibility is likely to be removed in later versions of Android. - * TODO(27995180): consider whether we keep this compatibility. Consider whether we only allow - * if an IV is passed in the parameters. - */ - @Test - public void test_PBKDF2WITHHMACSHA1_SKFactory_and_PBEAESCBC_Cipher_noIV() throws Exception { - Assume.assumeNotNull(Security.getProvider("BC")); - byte[] plaintext = - new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; - byte[] ciphertext = new byte[] {92, -65, -128, 16, -102, -115, -44, 52, 16, 124, -34, - -45, 58, -70, -17, 127, 119, -67, 87, 91, 63, -13, - -40, 9, 97, -17, -71, 97, 10, -61, -19, -73}; - SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WITHHMACSHA1"); - PBEKeySpec pbeks = new PBEKeySpec("password".toCharArray(), - "salt".getBytes(TestUtils.UTF_8), 100, 128); - SecretKey secretKey = skf.generateSecret(pbeks); - - Cipher cipher = Cipher.getInstance("PBEWITHSHAAND128BITAES-CBC-BC"); - PBEParameterSpec paramSpec = new PBEParameterSpec("salt".getBytes(TestUtils.UTF_8), 100); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, paramSpec); - assertEquals(Arrays.toString(ciphertext), Arrays.toString(cipher.doFinal(plaintext))); - - secretKey = skf.generateSecret(pbeks); - cipher.init(Cipher.DECRYPT_MODE, secretKey, paramSpec); - assertEquals(Arrays.toString(plaintext), Arrays.toString(cipher.doFinal(ciphertext))); - } - /* * http://b/27224566 * http://b/27994930 diff --git a/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java b/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java index dacec62aa..5cce097e4 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/ECDHKeyAgreementTest.java @@ -23,12 +23,16 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import junit.framework.AssertionFailedError; import org.conscrypt.Conscrypt; import org.conscrypt.TestUtils; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -44,6 +48,7 @@ import java.security.PrivateKey; import java.security.Provider; import java.security.PublicKey; +import java.security.Security; import java.security.interfaces.ECKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; @@ -58,6 +63,7 @@ import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; + import tests.util.ServiceTester; /** @@ -65,6 +71,8 @@ */ @RunWith(JUnit4.class) public class ECDHKeyAgreementTest { + // android-add: Allow access to deprecated BC algorithms. + // Two key pairs and the resulting shared secret for the Known Answer Test private static final byte[] KAT_PUBLIC_KEY1_X509 = TestUtils.decodeHex( "3059301306072a8648ce3d020106082a8648ce3d030107034200049fc2f71f85446b1371244491d83" @@ -463,6 +471,19 @@ private static Provider[] getKeyAgreementProviders() { if (providers == null) { return new Provider[0]; } + + // Do not test AndroidKeyStore as KeyAgreement provider. It only handles Android + // Keystore-backed keys. It's OKish not to test AndroidKeyStore here because it's tested by + // cts/tests/test/keystore. + List filteredProvidersList = new ArrayList(providers.length); + for (Provider provider : providers) { + if ("AndroidKeyStore".equals(provider.getName())) { + continue; + } + filteredProvidersList.add(provider); + } + providers = filteredProvidersList.toArray(new Provider[filteredProvidersList.size()]); + // Sort providers by name to guarantee deterministic order in which providers are used in // the tests. return sortByName(providers); diff --git a/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java b/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java index 414ceb862..f3675c8d4 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/KeyGeneratorTest.java @@ -19,10 +19,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +// android-add: import libcore.junit.util.EnableDeprecatedBouncyCastleAlgorithmsRule; + import org.conscrypt.TestUtils; import org.conscrypt.java.security.StandardNames; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -40,6 +44,8 @@ @RunWith(JUnit4.class) public class KeyGeneratorTest { + // android-add: Allow access to deprecated BC algorithms. + private static boolean isUnsupported(KeyGenerator kg) { // Don't bother testing "Sun..." KeyGenerators or BC outside of Android return kg.getProvider().getName().startsWith("Sun") diff --git a/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java index c9d8c604b..6c59b8886 100644 --- a/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java +++ b/common/src/test/java/org/conscrypt/javax/crypto/XDHKeyAgreementTest.java @@ -14,6 +14,7 @@ import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyAgreement; + import tests.util.ServiceTester; /** diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java index 11d3d13c2..0f8ad71c5 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/KeyManagerFactoryTest.java @@ -85,6 +85,19 @@ private TestKeyStore getTestKeyStore() throws Exception { return testKeyStore; } + // Remove legacy EC_EC type, which Jdk now considers invalid. (may or may not be a bug: + // https://bugs.openjdk.org/browse/JDK-8379191) + // Also remove EdDSA, which is not supported by Bouncy Castle. + private static String[] removeUnsupportedTypes(String[] input) { + List list = new ArrayList<>(); + for (String s : input) { + if (s != null && !s.equals("EC_EC") && !s.equals("EdDSA")) { + list.add(s); + } + } + return list.toArray(new String[0]); + } + @Test public void test_KeyManagerFactory_getDefaultAlgorithm() throws Exception { String algorithm = KeyManagerFactory.getDefaultAlgorithm(); @@ -195,14 +208,13 @@ private void test_KeyManagerFactory_getKeyManagers(KeyManagerFactory kmf, boolea private void test_X509KeyManager(X509KeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = keyTypes(algorithm); + String[] keyTypes = removeUnsupportedTypes(keyTypes(algorithm)); for (String keyType : keyTypes) { String[] aliases = km.getClientAliases(keyType, null); if (empty || keyType == null || keyType.isEmpty()) { assertNull(keyType, aliases); continue; } - assertNotNull(keyType, aliases); for (String alias : aliases) { test_X509KeyManager_alias(km, alias, keyType, false, empty); } @@ -241,7 +253,7 @@ private void test_X509KeyManager(X509KeyManager km, boolean empty, String algori private void test_X509ExtendedKeyManager(X509ExtendedKeyManager km, boolean empty, String algorithm) throws Exception { - String[] keyTypes = keyTypes(algorithm); + String[] keyTypes = removeUnsupportedTypes(keyTypes(algorithm)); String[][] rotatedTypes = rotate(nonEmpty(keyTypes)); for (String[] keyList : rotatedTypes) { String alias = km.chooseEngineClientAlias(keyList, null, null); diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java index 48e3d7e90..a7efae383 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLContextTest.java @@ -16,6 +16,7 @@ package org.conscrypt.javax.net.ssl; +import static org.conscrypt.TestUtils.isTlsV1Supported; import static org.conscrypt.TestUtils.isWindows; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -70,6 +71,7 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactorySpi; import javax.net.ssl.X509KeyManager; + import tests.util.ServiceTester; @RunWith(JUnit4.class) @@ -685,6 +687,24 @@ public void test_SSLContext_SSLv3Unsupported() throws Exception { () -> SSLContext.getInstance("SSLv3", finalDefaultTlsProvider)); } + @Test + public void test_SSLContext_init_picksUpProviderChanges() throws Exception { + SSLContext initialContext = SSLContext.getInstance("Default"); + assertThrows(KeyManagementException.class, () -> initialContext.init(null, null, null)); + + Provider provider = new ThrowExceptionKeyAndTrustManagerFactoryProvider(); + invokeWithHighestPrioritySecurityProvider(provider, new Callable() { + @Override + public Void call() throws Exception { + SSLContext newContext = SSLContext.getInstance("Default"); + assertThrows(KeyManagementException.class, () -> newContext.init(null, null, null)); + return null; + } + }); + SSLContext finalContext = SSLContext.getInstance("Default"); + assertThrows(KeyManagementException.class, () -> finalContext.init(null, null, null)); + } + private static void assertContentsInOrder(List expected, String... actual) { List actualList = Arrays.asList(actual); if (expected.size() != actual.length) { diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java index ae5ede237..1147e9710 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLEngineVersionCompatibilityTest.java @@ -397,11 +397,10 @@ public void test_TestSSLEnginePair_create() throws Exception { @Test public void test_SSLEngine_Multiple_Thread_Success() throws Exception { TestSSLContext c = TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); - final TestSSLEnginePair pair = - TestSSLEnginePair.create(c); + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); + final TestSSLEnginePair pair = TestSSLEnginePair.create(c); try { assertConnected(pair); @@ -445,11 +444,10 @@ public Void call() throws Exception { @Test public void test_SSLEngine_CloseOutbound() throws Exception { TestSSLContext c = TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); - final TestSSLEnginePair pair = - TestSSLEnginePair.create(c); + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); + final TestSSLEnginePair pair = TestSSLEnginePair.create(c); try { assertConnected(pair); @@ -499,11 +497,10 @@ public void test_SSLEngine_CloseOutbound() throws Exception { @Test public void test_SSLEngine_Closed() throws Exception { TestSSLContext c = TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); - final TestSSLEnginePair pair = - TestSSLEnginePair.create(); + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); + final TestSSLEnginePair pair = TestSSLEnginePair.create(); pair.close(); ByteBuffer out = ByteBuffer.allocate(pair.client.getSession().getPacketBufferSize()); SSLEngineResult res = pair.client.wrap(ByteBuffer.wrap(new byte[] {0x01}), out); @@ -605,20 +602,18 @@ private static byte[] getFirstChunk(SSLEngine e) throws SSLException { @Test public void test_SSLEngine_TlsUnique() throws Exception { TestSSLContext c = TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); // tls_unique isn't supported in TLS 1.3 assumeTlsV1_2Connection(); - TestSSLEnginePair pair = TestSSLEnginePair.create( - c, - new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - assertNull(Conscrypt.getTlsUnique(client)); - assertNull(Conscrypt.getTlsUnique(server)); - } - }); + TestSSLEnginePair pair = TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + assertNull(Conscrypt.getTlsUnique(client)); + assertNull(Conscrypt.getTlsUnique(server)); + } + }); try { assertConnected(pair); @@ -636,22 +631,20 @@ void beforeBeginHandshake(SSLEngine client, SSLEngine server) { @Test public void test_SSLEngine_EKM() throws Exception { TestSSLContext c = TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); - TestSSLEnginePair pair = TestSSLEnginePair.create( - c, - new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - try { - assertNull(Conscrypt.exportKeyingMaterial(client, "FOO", null, 20)); - assertNull(Conscrypt.exportKeyingMaterial(server, "FOO", null, 20)); - } catch (SSLException e) { - throw new RuntimeException(e); - } - } - }); + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); + TestSSLEnginePair pair = TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + try { + assertNull(Conscrypt.exportKeyingMaterial(client, "FOO", null, 20)); + assertNull(Conscrypt.exportKeyingMaterial(server, "FOO", null, 20)); + } catch (SSLException e) { + throw new RuntimeException(e); + } + } + }); try { assertConnected(pair); @@ -762,22 +755,20 @@ public void sniHandlerFailureResultsInHandshakeError() throws Exception { assumeJava8(); TestSSLContext c = TestSSLContext.newBuilder() - .clientProtocol(clientVersion) - .serverProtocol(serverVersion) - .build(); + .clientProtocol(clientVersion) + .serverProtocol(serverVersion) + .build(); try { - TestSSLEnginePair.create( - c, - new TestSSLEnginePair.Hooks() { - @Override - void beforeBeginHandshake(SSLEngine client, SSLEngine server) { - Conscrypt.setHostname(client, "any.host"); - - SSLParameters sslParameters = server.getSSLParameters(); - sslParameters.setSNIMatchers(singleton(FailingSniMatcher.create())); - server.setSSLParameters(sslParameters); - } - }); + TestSSLEnginePair.create(c, new TestSSLEnginePair.Hooks() { + @Override + void beforeBeginHandshake(SSLEngine client, SSLEngine server) { + Conscrypt.setHostname(client, "any.host"); + + SSLParameters sslParameters = server.getSSLParameters(); + sslParameters.setSNIMatchers(singleton(FailingSniMatcher.create())); + server.setSSLParameters(sslParameters); + } + }); fail(); } catch (SSLHandshakeException e) { assertEquals("SNI match failed: any.host", e.getMessage()); diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java index d3dba5272..e104047b8 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSessionTest.java @@ -514,12 +514,10 @@ private static String alterOriginalHostName(InetAddress inetAddress, String orig // http://b/35942385 @Test public void test_SSLSession_getPeerHostFromInetAddress() throws Exception { - TestUtils.assumeAndroid(); - if (TestUtils.isAndroid()) { - // On Android 27 and below, this test fails because - // Platform.getOriginalHostNameFromInetAddress is not supported. - assumeTrue(TestUtils.isAndroidSdkGreater(27)); - } + // On Android, this test fails because Platform.getOriginalHostNameFromInetAddress + // and alterOriginalHostName use reflection on private members of InetAddressHolder, + // which is blocked by non-SDK interface restrictions (hidden API enforcement). + assumeFalse(TestUtils.isAndroid()); InetAddress inetAddress = TestUtils.getLoopbackAddress(); String oldOriginalHostName = alterOriginalHostName(inetAddress, "foobar"); try { diff --git a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java index f161b5073..32958deb5 100644 --- a/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java +++ b/common/src/test/java/org/conscrypt/javax/net/ssl/SSLSocketTest.java @@ -28,6 +28,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; +import org.conscrypt.AllocatedBuffer; +import org.conscrypt.BufferAllocator; +import org.conscrypt.Conscrypt; import org.conscrypt.OpenSSLSocketImpl; import org.conscrypt.TestUtils; import org.conscrypt.java.security.StandardNames; @@ -76,6 +79,7 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLProtocolException; @@ -83,7 +87,6 @@ import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509ExtendedTrustManager; -import org.conscrypt.Conscrypt; import tests.net.DelegatingSSLSocketFactory; import tests.util.ForEachRunner; @@ -269,6 +272,66 @@ private SecretKey newKey() { c.close(); } + @Test + public void setBufferAllocator_afterConnect_fails() throws Exception { + TestSSLSocketPair pair = TestSSLSocketPair.create(); + BufferAllocator unpooledBufferAllocator = BufferAllocator.unpooled(); + pair.connect(); + assertThrows(IllegalStateException.class, + () -> Conscrypt.setBufferAllocator(pair.client, unpooledBufferAllocator)); + } + + // BufferAllocator that counts allocations. + private static final class SpyBufferAllocator extends BufferAllocator { + private final BufferAllocator bufferAllocator; + private int directAllocationCount = 0; + private int heapAllocationCount = 0; + + + SpyBufferAllocator(BufferAllocator bufferAllocator) { + this.bufferAllocator = bufferAllocator; + } + + @Override + public AllocatedBuffer allocateDirectBuffer(int capacity) { + directAllocationCount++; + return bufferAllocator.allocateDirectBuffer(capacity); + } + + @Override + public AllocatedBuffer allocateHeapBuffer(int capacity) { + heapAllocationCount++; + return bufferAllocator.allocateHeapBuffer(capacity); + } + } + + @Test + public void setBufferAllocator_works() throws Exception { + TestSSLSocketPair pair = TestSSLSocketPair.create(); + SpyBufferAllocator clientBufferAllocator = + new SpyBufferAllocator(BufferAllocator.unpooled()); + SpyBufferAllocator serverBufferAllocator = + new SpyBufferAllocator(BufferAllocator.unpooled()); + Conscrypt.setBufferAllocator(pair.client, clientBufferAllocator); + Conscrypt.setBufferAllocator(pair.server, serverBufferAllocator); + pair.connect(); + + pair.client.getOutputStream().write(new byte[] {1, 2, 3, 4}); + assertEquals(1, pair.server.getInputStream().read()); + assertEquals(3, pair.server.getInputStream().read(new byte[4])); + + pair.server.getOutputStream().write(new byte[] {1, 2, 3, 4}); + assertEquals(1, pair.client.getInputStream().read()); + assertEquals(3, pair.client.getInputStream().read(new byte[4])); + + assertTrue(clientBufferAllocator.directAllocationCount > 0); + assertTrue(serverBufferAllocator.directAllocationCount > 0); + + // allocateHeapBuffer is currently not called. + assertEquals(0, clientBufferAllocator.heapAllocationCount); + assertEquals(0, serverBufferAllocator.heapAllocationCount); + } + @Test public void test_SSLSocket_getInputStream_available() throws Exception { TestSSLSocketPair pair = TestSSLSocketPair.create().connect(); @@ -510,10 +573,10 @@ public void test_SSLSocket_getHandshakeSession_unconnected() throws Exception { @Test public void test_SSLSocket_getHandshakeSession_duringHandshake_client() throws Exception { - // On Android 27 and below, this test fails because - // Platform.getOriginalHostNameFromInetAddress is not supported. - boolean isAndroid27OrBelow = TestUtils.isAndroid() && !TestUtils.isAndroidSdkGreater(27); - assumeFalse(isAndroid27OrBelow); + // On Android, this test fails because Platform.getOriginalHostNameFromInetAddress + // uses reflection on private members of InetAddressHolder, which is blocked + // by non-SDK interface restrictions (hidden API enforcement). + assumeFalse(TestUtils.isAndroid()); // We can't reference the actual context we're using, since we need to pass // the test trust manager in to construct it, so create reference objects that @@ -923,6 +986,34 @@ public void test_SSLSocket_setSoTimeout_wrapper() throws Exception { listening.close(); } + @Test + public void test_SSLSocket_oversizedTlsRecordHeader_throwsSSLException() throws Exception { + try (ServerSocket listening = new ServerSocket(0)) { + Future serverTask = executor.submit(() -> { + try (Socket server = listening.accept()) { + byte[] headerAndPayload = new byte[16709]; + headerAndPayload[0] = 0x16; // SSL3_RT_HANDSHAKE + headerAndPayload[1] = 0x03; // TLS 1.2 major + headerAndPayload[2] = 0x03; // TLS 1.2 minor + headerAndPayload[3] = (byte) 0xFF; // length high byte (0xFFFF = 65535) + headerAndPayload[4] = (byte) 0xFF; // length low byte + server.getOutputStream().write(headerAndPayload); + server.getOutputStream().flush(); + Thread.sleep(500); + } + return null; + }); + + SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); + try (SSLSocket client = (SSLSocket) sf.createSocket(listening.getInetAddress(), + listening.getLocalPort())) { + assertThrows(SSLException.class, client::startHandshake); + } finally { + serverTask.get(5, TimeUnit.SECONDS); + } + } + } + @Test public void test_TestSSLSocketPair_create() { TestSSLSocketPair test = TestSSLSocketPair.create().connect(); @@ -1053,9 +1144,9 @@ public void handshake_p256IsSupportedByDefault() throws Exception { String clientCurve = getCurveName(client); String serverCurve = getCurveName(server); assertTrue("Curve is not X25519 or X25519MLKEM768: " + clientCurve, - clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); assertTrue("Curve is not X25519 or X25519MLKEM768: " + serverCurve, - serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); } client.close(); server.close(); @@ -1093,9 +1184,9 @@ public void handshake_p384IsSupportedByDefault() throws Exception { String clientCurve = getCurveName(client); String serverCurve = getCurveName(server); assertTrue("Curve is not X25519 or X25519MLKEM768: " + clientCurve, - clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); assertTrue("Curve is not X25519 or X25519MLKEM768: " + serverCurve, - serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); } client.close(); server.close(); @@ -1134,9 +1225,9 @@ public void handshake_setsNamedGroups_usesFirstServerNamedGroupThatClientSupport String clientCurve = getCurveName(client); String serverCurve = getCurveName(server); assertTrue("Curve is not X25519 or X25519MLKEM768: " + clientCurve, - clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); assertTrue("Curve is not X25519 or X25519MLKEM768: " + serverCurve, - serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); } client.close(); server.close(); @@ -1197,9 +1288,9 @@ public void handshake_setsNamedGroupsBeforeAccept_usesFirstServerNamedGroupThatC String clientCurve = getCurveName(client); String serverCurve = getCurveName(server); assertTrue("Curve is not X25519 or X25519MLKEM768: " + clientCurve, - clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); assertTrue("Curve is not X25519 or X25519MLKEM768: " + serverCurve, - serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); } client.close(); server.close(); @@ -1236,9 +1327,9 @@ public void handshake_withX25519MLKEM768_works() throws Exception { String clientCurve = getCurveName(client); String serverCurve = getCurveName(server); assertTrue("Curve is not X25519 or X25519MLKEM768: " + clientCurve, - clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); assertTrue("Curve is not X25519 or X25519MLKEM768: " + serverCurve, - serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); } client.close(); server.close(); @@ -1335,9 +1426,9 @@ public void handshake_namedGroupsDontIntersect_throwsException() throws Exceptio String clientCurve = getCurveName(client); String serverCurve = getCurveName(server); assertTrue("Curve is not X25519 or X25519MLKEM768: " + clientCurve, - clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); + clientCurve.equals("X25519") || clientCurve.equals("X25519MLKEM768")); assertTrue("Curve is not X25519 or X25519MLKEM768: " + serverCurve, - serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); + serverCurve.equals("X25519") || serverCurve.equals("X25519MLKEM768")); } client.close(); server.close(); @@ -1351,8 +1442,9 @@ public void setNamedGroupsProperty_invalidValue_isIgnored() throws Exception { { TestSSLContext context = TestSSLContext.create(); - final SSLSocket client = (SSLSocket) context.clientContext.getSocketFactory().createSocket( - context.host, context.port); + final SSLSocket client = + (SSLSocket) context.clientContext.getSocketFactory().createSocket(context.host, + context.port); final SSLSocket server = (SSLSocket) context.serverSocket.accept(); Future s = runAsync(() -> { server.startHandshake(); @@ -1376,8 +1468,9 @@ public void setNamedGroupsProperty_invalidValue_isIgnored() throws Exception { { TestSSLContext context = TestSSLContext.create(); - final SSLSocket client = (SSLSocket) context.clientContext.getSocketFactory().createSocket( - context.host, context.port); + final SSLSocket client = + (SSLSocket) context.clientContext.getSocketFactory().createSocket(context.host, + context.port); final SSLSocket server = (SSLSocket) context.serverSocket.accept(); Future s = runAsync(() -> { server.startHandshake(); diff --git a/common/src/test/resources/crypto/composite_mldsa.txt b/common/src/test/resources/crypto/composite_mldsa.txt new file mode 100644 index 000000000..03e1507c9 --- /dev/null +++ b/common/src/test/resources/crypto/composite_mldsa.txt @@ -0,0 +1,138 @@ +# Test values from https://lamps-wg.github.io/draft-composite-sigs/draft-ietf-lamps-pq-composite-sigs.html#name-test-vectors. +# The formats are adapted (e.g., we only use the SubjectPubliKeyInfo part of the +# `x5c` field). + +name = TEST_1 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA44-RSA2048-PSS-SHA256 +pk = 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 +pkX509 = MIIGPzAKBggrBgEFBQcGJQOCBi8ALl22mDa1wg0KVq2Jf39IbdzTKeCd1gv4yIRnpXLrj1CZsOBCqBcmiSEyAi868sOwc9FsoXzNy8SIcsPPh6j5ydVW81jDJBcVinpeqz4zyNx6kDjWeJELf2nPHWVaPLlH+crE+msAKFR4SY7xO0A+vPG/P14eCye+4qZFSTFKpy67pXGjmrwQVmUdeFeBb3+2XMPjrASvKv7f3kLZ/j6Na/9bD2VXa+mX7KL7U2zKMyGBnztGp0xhSnkMf1TCglc7EIY38gcdo4WlPMul4F4ETfblkeV/0GcXBjs9QJvySgR8Glkh3WDnLbnuNjh8S5ZHty7Ec69/URMyPA6AcI5dCH3hRXYwNwpnzw2mnprVYofw78lVVNOzLlzFGZfu1LeeK/rtC5OZ3tMmLjmjCeEN+4WtFItdlDZLQydPT7Ql+wcpcWFBWRpvWnLMmbA/m0YbvmUoLUOTZOZko3eGwWgTh6xNR8vkPaeENT+LMnJI/pLmwp6vquDT5CzJo5HHpZZuMJ09uyY+/gpjnur9HWnmE/AFTjYCX2OEbHnWd+ZMYxtqyB9IjIL9uFjRrGUDduG9OW4tBzePHmvwP/JfW38+lquYFljx6EgM7IoFBrLY2Ni7VWLrdVHrCWZ3E7NqRGPdpd9FS8/H2cVVHo4ijKi75q9KQCsa1fFw06Ai2XCqlE3vdhMreNA7Sc7fJibuAeHRPwwOOHFm7rsBSCqKidMz213HrLIezwYSXfMfnZ29Mm2dSiUq3tKkPntPzmj53T3fzCTh9ZUBOp4j2UW8QfYSXSaDONrTFMgvm7H075xUQuNbOXeD9xDxPqPeskzRHYhgignqSaSmHfU2JVUCQPp7eUXHAXZo7gIBNauMJESMbn/LZAk0m2tn/TyXNyC9Oa/5LtQKrusW6FkC2kZzxxIgKQt86l01/zMbaUQb/cI6Tzszz7kSCK159r9PfLuTt2awc4an4D77KMYijnS7JB7AihO6TTzly69t0Y/ztazSlogB5VP+GjQ65BZwM5nytfwSK3zwvVnyH3xO0PkExjrFSLEFnquysxObNzlHoALCqiwtuPzET8AldgBmOg/bRPj5tk59MIwhb1hPgUKgYqnaaH0jOHFW4PPsfIhG0y25SP0xyMqwLrJDSeuavvFSLIdPjP/ddb1o2vk57VO0BatuRB/kjik1uPJiOBfcY7agFpsEQql/1/ZD1ePlkvt4uTS/sYPirG26YcmMMYzMrQIlEep+7t5aPDHtRAK90Y/LaH47U5EhoIf470BNms/qLBelxzqKlevlSKfMq+np6RyQEpbrSElrTFSDDlgDMxAWWjGBLtXO95WP+xdr8XAJjWdUHdOpEcJtIuTulthjLTv9NVQ7TY4E8y9DB/I/mWkNhq7bqSY4SYmbHcedIclq6RrP3h1NZzd0AYAbD4BQucP1QRTLYG8/77gUr5L3KnMAhjj25MCBq6mMw8XISC+rXH+kvOOpnLlCzKRZ4jJnynl44WdenbYPxvRI9UIdOXW84Mv3YSQB6NCdOiI885ROmKCy+Ry4zVzb72IIr7OJTNaIfx0MKw3fxboSYTfpqG/t1H7QEMccScg7+LTzxLEOopFxetKPlsE40lS66DyYW9HOUeyGuA7kqbsiV8zRdHSRQ3ERnY8Y8pOXKoBKZRXjO8ZrTmyLjxSqt8+MMjeD38VT0gry5U0CI0Q+M/9vE0Olh6cW2sfFX+Pvyij4Sf2QnZ7cSrUeERxTH9xrmQZUlw2UFTCCAQoCggEBANRYZQ7Neqmh3FZdij9alH6M3fo+08nlYYRQ5PUctPU5r2M8GM/9nVpgJf0EPFXfT7q9N89J5vmdxVPtvBxegMgvSTFmmFoH7SWFqlhRggIHEuQv9QSDLl/GVZ8zOfD6b0W7jXoIl3lZUnSerFBPSm/7mscI0v3G96m4SjMiql57TP1jFujYB1pRDSH430ookhOv2i2dOAndzkCg2xrKBqmv2sUCYZFsj8nG+BJx4gYCZ+1gcUuoaLqxw7Tk+40sr2lAxnHxyvW4T1NhKP07CE4tj3fsKnBe4TN7OI9SktKijeNQOFj9sYBisC2fJKzPESfUBinqpWQLjqVcqavLD5cCAwEAAQ== +sk = NPkBm9FU2TX1CaijKEhnsJ9yRbP+Ov+1E+fjPqvkix0wggSjAgEAAoIBAQDUWGUOzXqpodxWXYo/WpR+jN36PtPJ5WGEUOT1HLT1Oa9jPBjP/Z1aYCX9BDxV30+6vTfPSeb5ncVT7bwcXoDIL0kxZphaB+0lhapYUYICBxLkL/UEgy5fxlWfMznw+m9Fu416CJd5WVJ0nqxQT0pv+5rHCNL9xvepuEozIqpee0z9Yxbo2AdaUQ0h+N9KKJITr9otnTgJ3c5AoNsaygapr9rFAmGRbI/JxvgSceIGAmftYHFLqGi6scO05PuNLK9pQMZx8cr1uE9TYSj9OwhOLY937CpwXuEzeziPUpLSoo3jUDhY/bGAYrAtnySszxEn1AYp6qVkC46lXKmryw+XAgMBAAECggEAWF+Nd3Km6TA27i6x2ZoOEPj5bSt2oyD2y8WK9EQFP8XJK4iYXv+S3EkFpl576dUtbm9PadK88QfrVvmq/zeJa0batFeZcma4GJSfh3AspkFhaFxZIY6i3zNA8Se1pofjhWcAA1jOCa/V9DkRR78oIKDbEpimjv2elyDeqJd1xinSdY8p6ub3r+MdwhoBrRJqb2p2Vr46Rv++ZnSYvF5bOiA+2pS4Hx4WxyW/Zx/3almN/Qz149vzpkVNaRLrtNRQbQaZkhSqsDP5KlBWp6zshrAzP8VM9n5OcS/J/l8k1LwsVMYFsn1r98+RMrnCAVgwbQJCBeDWMWOZfZmPaN7AMQKBgQDyB3Qpnf0nbj4atEUx3yihn7peoOE5HCSoxvH+a4t5r8xokmqpqMBMP4L7b8EoN4FuXGaR2EhEBsvHp6m0LzygeGiO4dyx+cay+Eq8WVbZUsylUMfzu0cPmjAEf40rdckHElNOI8VeSw4qqzDl+JIn34wG7RM4tez/It/0bFnKjwKBgQDgmksGsKL3gqAdMEYBZIqaAEwxFIiNNQSpVfN2MOZryHoK9FveDf08oT8l4NcXGXDkx8CoNQIB73FgmWDj3DJKypjM55W0hNSpRwjcsFJ8Cy/dV2hsZYbASV7r4fUSoMjihFbpZVmHKpjZxfGX8a98pfSQyu0kjpDkI+7qVJGOeQKBgHnwjXMmWVSTc5DKwI4G7Ba6PhDNJ4w5hLLQQT44+vWdP/RzyG+gSPphiWGbBYt4o6pxvW+/s3Eqp2L5M0RIBFipMazDWQkGWjjzZdwNevdVgyvLTmKbOYs/2O97QCnkVxtL/VLCLP97+zA+Pg2vthuGwqr+qQ+KgVRuQr2IFZk7AoGBAMY6Rvc7lElgh1Hblh2Kj+1FT/mNRsthvKB7VGm+1M7R3Cyo6B++Nv94zNPwccVYVdQFHFsYlZIBsw3vsJzKbbSmxF8sEWuGRG62W/Lyx4nlEbSHfYkVve0dlGIZRgPP1hxdcpuBMJfkF400b3qL+zbG/WeBQfUewnAn6qf0RZb5AoGAa0WhETbIjDyrcWmtfHKQI9KeO3+fmdrZws4FCgQDURR8RNxznDfUE4JpFP0FyqMe8jlJYPgd5qMoWzMVxuLb3mnYV1gmlNctW6tg9EXNnWiSUyNKkiGxz6YEAiBXTPOU3SeW7Z+2AtshEoRrWA+DvLeiSJ+Kxi/Y9OIu+Dtrz1s= +skPkcs8 = MIIE2gIBADAKBggrBgEFBQcGJQSCBMc0+QGb0VTZNfUJqKMoSGewn3JFs/46/7UT5+M+q+SLHTCCBKMCAQACggEBANRYZQ7Neqmh3FZdij9alH6M3fo+08nlYYRQ5PUctPU5r2M8GM/9nVpgJf0EPFXfT7q9N89J5vmdxVPtvBxegMgvSTFmmFoH7SWFqlhRggIHEuQv9QSDLl/GVZ8zOfD6b0W7jXoIl3lZUnSerFBPSm/7mscI0v3G96m4SjMiql57TP1jFujYB1pRDSH430ookhOv2i2dOAndzkCg2xrKBqmv2sUCYZFsj8nG+BJx4gYCZ+1gcUuoaLqxw7Tk+40sr2lAxnHxyvW4T1NhKP07CE4tj3fsKnBe4TN7OI9SktKijeNQOFj9sYBisC2fJKzPESfUBinqpWQLjqVcqavLD5cCAwEAAQKCAQBYX413cqbpMDbuLrHZmg4Q+PltK3ajIPbLxYr0RAU/xckriJhe/5LcSQWmXnvp1S1ub09p0rzxB+tW+ar/N4lrRtq0V5lyZrgYlJ+HcCymQWFoXFkhjqLfM0DxJ7Wmh+OFZwADWM4Jr9X0ORFHvyggoNsSmKaO/Z6XIN6ol3XGKdJ1jynq5vev4x3CGgGtEmpvanZWvjpG/75mdJi8Xls6ID7alLgfHhbHJb9nH/dqWY39DPXj2/OmRU1pEuu01FBtBpmSFKqwM/kqUFanrOyGsDM/xUz2fk5xL8n+XyTUvCxUxgWyfWv3z5EyucIBWDBtAkIF4NYxY5l9mY9o3sAxAoGBAPIHdCmd/SduPhq0RTHfKKGful6g4TkcJKjG8f5ri3mvzGiSaqmowEw/gvtvwSg3gW5cZpHYSEQGy8enqbQvPKB4aI7h3LH5xrL4SrxZVtlSzKVQx/O7Rw+aMAR/jSt1yQcSU04jxV5LDiqrMOX4kiffjAbtEzi17P8i3/RsWcqPAoGBAOCaSwawoveCoB0wRgFkipoATDEUiI01BKlV83Yw5mvIegr0W94N/TyhPyXg1xcZcOTHwKg1AgHvcWCZYOPcMkrKmMznlbSE1KlHCNywUnwLL91XaGxlhsBJXuvh9RKgyOKEVullWYcqmNnF8Zfxr3yl9JDK7SSOkOQj7upUkY55AoGAefCNcyZZVJNzkMrAjgbsFro+EM0njDmEstBBPjj69Z0/9HPIb6BI+mGJYZsFi3ijqnG9b7+zcSqnYvkzREgEWKkxrMNZCQZaOPNl3A1691WDK8tOYps5iz/Y73tAKeRXG0v9UsIs/3v7MD4+Da+2G4bCqv6pD4qBVG5CvYgVmTsCgYEAxjpG9zuUSWCHUduWHYqP7UVP+Y1Gy2G8oHtUab7UztHcLKjoH742/3jM0/BxxVhV1AUcWxiVkgGzDe+wnMpttKbEXywRa4ZEbrZb8vLHieURtId9iRW97R2UYhlGA8/WHF1ym4Ewl+QXjTRveov7Nsb9Z4FB9R7CcCfqp/RFlvkCgYBrRaERNsiMPKtxaa18cpAj0p47f5+Z2tnCzgUKBANRFHxE3HOcN9QTgmkU/QXKox7yOUlg+B3moyhbMxXG4tveadhXWCaU1y1bq2D0Rc2daJJTI0qSIbHPpgQCIFdM85TdJ5btn7YC2yEShGtYD4O8t6JIn4rGL9j04i74O2vPWw== +s = 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 + +name = TEST_2 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA44-RSA2048-PKCS15-SHA256 +pk = 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 +pkX509 = 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 +sk = eFObttIdBg+c1tQAupqdt60bgeNRlZHihfeiQnmPgjQwggSkAgEAAoIBAQDTbb6Q2R2yfrZDUx4DJ4P4WxMTX617imPCnft2/KKzKz8+aefMulAU+gxdn8qD80FQL5uq0bouaoLawy1Te6FwtHf+915tB/BeoOL6KIE237UiWi/OpnL4epj7ojrWrlreeVPCndmZCb/FLJfpBbkGAV3mmRCC+s6l7sJT1Tn+6wnM3Ta4EwTnCmP0v2vJ7UOodjtOmcwFDzaBEjB4D/EoZ4erGBvPfA2Gg/uc2xcEQodaFWul3Hkg5jS3Se7NRMDjWYHVGZfYmq8AM8bpK1gOFz1mTFqHYhkceVPJbDQptq4/asMc+aiEtemx9A+3IrCa/QmC+mBU2TBsQPWg0xvTAgMBAAECggEAC1ppcRS/jqiq4oWcPHYgP4irHGS3B5sVoqCMyBsRJNwt0A0BeKcJNX8nL9XXRZZH4RBADyr7kOeA8adTupe5F7i7zXynXOUgEqzCqMUztGh3LQo5EU3d1EWdC7zD5RLwEoyNo2CKNo4P9qdfhuXwRV1zFza2l29JWR6vilnjpzJgbH/nOIdQ9+cHRss2X5fxeTDO9IZjvdRfKEy5m/29q1C4aXQwTjkWxeQqUdd2rbaHlE5fLk4D8IOee1Ow7iU+bz5Rq6h5fH4iBhv9ccRC8aEg2C7tPrNLroYi8YSDBRCntFQJg9BKgoUzuGzqU77T6nmQdZbWWx1UY6y9d4xpMQKBgQD68bB2Am0OIdWW+qpogL2q6OInhGm2SkhEWs5LqW8/0LFvW5EI6i5d17r0UMrZWF96BkM55J2hiaAjEbwg2fERXUDpfQRmDdvqkpsAmjevwceyOze29qPYsciHSBCsghcNlaw3CfIKpfXCyj6DmSlEsQUdt4M7uzrxi0/hsJrjsQKBgQDXsD5wGfwIdKE80oWiJlSVFtnwEn65/rZWO8H0WLtdvjRHK3BlIK7Jz8RrCpvx+uWEJ8cgQlQT8UQ583mWSeFTUjLcs4lL3HMl78WkV6E4+5I6XOQQHX0a+oULn8rQav2Lner7MLYkwIvUx4i7AFgJy8TPi/AYyFfN7i8VDplswwKBgQDPsPMsaIub87LUd3hMb6kK6B4tOLFJdydis4kkJAJ4XaBNGwrpxvBDKQaJqMiKpFK5Kq+/HZC9HqvT+pyqz9ZuWbEcziSy1muaNGZnVDwckhRWVh6hpnYrJdFi7ekm7bBoxOS41NlnqL3DzyF4R25ZdO1YEAdki2yYd4XQtBstsQKBgQC9OmG9Bf8JCBHBg8078jb4yiCQMBnAYkhkJW9HSWWwm8PPwLuN7XuLkN15L8ibJoygQinAEpEjIePCl+pPQSgPaqk22ciVpqXbXZ3fTgYjMQscawynWse8mJeLGDjeW09wYy6aDCVw3wCOwDQkI+wZRA26LMKLa5ElGVdzOOi/8wKBgHvgzZ099bE/8pi7JNyLQ80ETXfpwf+E/FaOUKMR/pUmTGlYWS1O2NzdD0CFjRTjWdkEbeKgVD2fOZjT3LzW15OuDFwh7hv1zVQegjWCR52EFgG55MxYY4igXUMCFh2ljrCLzXHnvRBZ9JModSESDxQFv93Nd6XumhWXz9Rm3i86 +skPkcs8 = MIIE2wIBADAKBggrBgEFBQcGJgSCBMh4U5u20h0GD5zW1AC6mp23rRuB41GVkeKF96JCeY+CNDCCBKQCAQACggEBANNtvpDZHbJ+tkNTHgMng/hbExNfrXuKY8Kd+3b8orMrPz5p58y6UBT6DF2fyoPzQVAvm6rRui5qgtrDLVN7oXC0d/73Xm0H8F6g4voogTbftSJaL86mcvh6mPuiOtauWt55U8Kd2ZkJv8Usl+kFuQYBXeaZEIL6zqXuwlPVOf7rCczdNrgTBOcKY/S/a8ntQ6h2O06ZzAUPNoESMHgP8Shnh6sYG898DYaD+5zbFwRCh1oVa6XceSDmNLdJ7s1EwONZgdUZl9iarwAzxukrWA4XPWZMWodiGRx5U8lsNCm2rj9qwxz5qIS16bH0D7cisJr9CYL6YFTZMGxA9aDTG9MCAwEAAQKCAQALWmlxFL+OqKrihZw8diA/iKscZLcHmxWioIzIGxEk3C3QDQF4pwk1fycv1ddFlkfhEEAPKvuQ54Dxp1O6l7kXuLvNfKdc5SASrMKoxTO0aHctCjkRTd3URZ0LvMPlEvASjI2jYIo2jg/2p1+G5fBFXXMXNraXb0lZHq+KWeOnMmBsf+c4h1D35wdGyzZfl/F5MM70hmO91F8oTLmb/b2rULhpdDBOORbF5CpR13attoeUTl8uTgPwg557U7DuJT5vPlGrqHl8fiIGG/1xxELxoSDYLu0+s0uuhiLxhIMFEKe0VAmD0EqChTO4bOpTvtPqeZB1ltZbHVRjrL13jGkxAoGBAPrxsHYCbQ4h1Zb6qmiAvaro4ieEabZKSERazkupbz/QsW9bkQjqLl3XuvRQytlYX3oGQznknaGJoCMRvCDZ8RFdQOl9BGYN2+qSmwCaN6/Bx7I7N7b2o9ixyIdIEKyCFw2VrDcJ8gql9cLKPoOZKUSxBR23gzu7OvGLT+GwmuOxAoGBANewPnAZ/Ah0oTzShaImVJUW2fASfrn+tlY7wfRYu12+NEcrcGUgrsnPxGsKm/H65YQnxyBCVBPxRDnzeZZJ4VNSMtyziUvccyXvxaRXoTj7kjpc5BAdfRr6hQufytBq/Yud6vswtiTAi9THiLsAWAnLxM+L8BjIV83uLxUOmWzDAoGBAM+w8yxoi5vzstR3eExvqQroHi04sUl3J2KziSQkAnhdoE0bCunG8EMpBomoyIqkUrkqr78dkL0eq9P6nKrP1m5ZsRzOJLLWa5o0ZmdUPBySFFZWHqGmdisl0WLt6SbtsGjE5LjU2WeovcPPIXhHbll07VgQB2SLbJh3hdC0Gy2xAoGBAL06Yb0F/wkIEcGDzTvyNvjKIJAwGcBiSGQlb0dJZbCbw8/Au43te4uQ3XkvyJsmjKBCKcASkSMh48KX6k9BKA9qqTbZyJWmpdtdnd9OBiMxCxxrDKdax7yYl4sYON5bT3BjLpoMJXDfAI7ANCQj7BlEDboswotrkSUZV3M46L/zAoGAe+DNnT31sT/ymLsk3ItDzQRNd+nB/4T8Vo5QoxH+lSZMaVhZLU7Y3N0PQIWNFONZ2QRt4qBUPZ85mNPcvNbXk64MXCHuG/XNVB6CNYJHnYQWAbnkzFhjiKBdQwIWHaWOsIvNcee9EFn0kyh1IRIPFAW/3c13pe6aFZfP1GbeLzo= +s = I8WTMuk7C0WsDBOG33bz3nsurihatMh9oklXWB0ViuMlM4+7DdWqF5y8yLyWZIQzzKQBb44W3D7AK+Fs3wcTlDyaL/KF9gPc51yRksfcQC7Pr0MwmuYoxOYPDa6/mmIXskxdTQDgKUd574UUxY1A5Bn7Ggvgrs35ppVAaBXwhAmtq+o2tTnfSGC/kUM6fo4MGUrxJbZnEp1rdf6X7ct3VEAPeL4o8ZW3ZxfEG2Y5fmVEQ78xaNyAwQ9eh90zCAf8TraPmxpiYS50jhNt+gt9WpuH9p36QRL47QPTvRDDj4SLufryx230o8wnoQqCtLWGl/YLyox3z5g1NnMrfSNMbkLnBYdPkV4mSy0Bd5IkjhIZ6uB1MYwgSe6Exuo9xpshFW7Nzlo5h98AoUl8A2if0olrfZlB9GmyzROtt+XdUovjXjrsT4/k0J3NeXBizYY3QsmYrQZdV++yYI3iwl1Rkewpje5HoKjwXNLShm/xhj/XFyw+ydpzIKIOXYvrO33UlxSf7My9lpXQvoKcicTc1uIBqC3oZp988fPplkia2EOCvlNKbWUSriwXi9d+zWsAPpW0HYv561ojgTwEyLCGr+h/mFGWpvn3u6Sj3WQZ/D0bSaCxrdZ4x+iaH6to4JLpv2T58CHWW2m0n0xRpsg4y/bedcwqcdLd0PtwVAvHLifJkfgAaddhx6hy+i13bukiVCKA9pdSRV6Sir8/1qB7waY1evBTW12HHVBaUvpTQ8m5KwRquUqWbYUM6mRC4ui0/aoNuUA7nFS1JJKNAB4Ak+DsW0ydq5mw2F0LsqW0n5IbB9h69lIxzcQ1lwfYMJm2FNjGlBZMHonLMmvYVKFeREGCoXLsu2Kx7AoqL2j5qkoRS12yytqWqCzSZptl1lC64E6t1fgJ8Ol+GH6/DE70GNECUzjSSSc+j8+hyNIuJ4MYMewYrInlnKGIZ4czf7Ig3/QCq7QbCKx3jb5phpzTsPrYBdDeG2UN6wAuNElMJBGmgsjTitoS20Xe7YM0BgKBwrsRiPeR7uGy7bD2WkhwFmHw9Tljj/pm4k67zLoPG1oB7ilk0ptJPZseM9H/VVkIuvPVnbhwmh7V4HpRTg+iHsUYl1mna8B5QrAWPXWM6Z1kJwxw2e5W2HbjdgsM+rdGFBsPun8jznZnqp2waVyUPkMt6SJxu6bVB+XHMZCOX7gZvH/CpGkL2H+kiAK/e1tr3vc3uj2GFdL9fFp72HBuFzsIs91XnadbpbfEgF6UiO6W+TAk7PstSUSKsa6Jc4o2w5whRNmgLSrs0ExZW9rgReWZVDIS2xn+/cElDTVfpnzGlySt9m9rN+GY5VVu2bDwqdPHdP8sqovqTIXkLP5Vum8FCx1CQZkGfn1+VNqGZ3Bzp3nJhMCkjlgTFuIJV65oW/yIDbCDKNrS7pdyWgqyc73agokkq6y5bs/5IZXciaujutfraGGUGhZlX4lrThi6mS+pMm+PQ40B3CFLT43it2h3TRf0kKK3ETs8sxKKBU48MVhOGQroOrHhMdosPOjKzUu6m5as++rN/nNX3rXtezLsKX/E+zjNUHfgFiCQhMPOnTfJwIB4nPToZTufS09Af67/UMKUWbe1haVh2m5xnfnWPtUX9+vsi6mDBARtTBdXhu0KhL9waHJeVN9MOMj27JOo7Lw38zYpXXScej6PPlaG2c+Tcj6Aw5ruJmg8px+54ESBDtndLsxl6lM7ECI3mljRQE0danmLdL7o5J4QNp1zjq6idAorg80j5wjAK0O/ypZ6M22Urglz5dAmoZeEci3NXsy8DIGdUSSut8UgnjMCqrcJYP6kJk7VgYDFEao+AK8QjTQIkMEkJ1I9lweH4Mry9OjyRobdCRySExh+5pDN9Bfor4oy3skbh2LZjAUYtDUn4Um05zc1bK4B4j/7LYMimG8G5WdIdp+lsHKTv0/M05zObAlZnXsWSybyqY6AU/OFKt1KZCBNkKU6fgotcYkSR2d1/5KpnYiOekeF2sO51wwpY51cHrkARbNEMnGeMepCgvoqU/+5EhHtQCWkYgQtTV9OBlhzu2uSMmktB0oBgbIdoFP9mIdfXoqUFHX+18f2Q8N9F0+o4lTtSeyeLq6Ahpyzehvv7HQBOUhF8W8oR9VNJqYHDLjojS2wDhrKTi47C7k/Ck12zZYhk5CFKXDXnAXcpEQKbm9e+99pFjG5ave3gT+v/Q50h07kpvpSblht7TUoXLwpc5ndTqPuSsFI8JYl3qjTglJXzDmcasUIQW5ndAjcH9c3V5NioQPio0TlOT1A3V5OsvrhXtAQjQbH56VKFxpHqhR9PhJ0ERU6cRCFT/dRI8BJWwWpImJCbffZLmvmWJAcax5gAYGsITC0F0trj2JE2NKjdejIQUFwBZwlEkUYy0tvGGeYZX2W60a1+UnwZGQaLh3lsTzokN6ERluiZBXVsHpzV4L3iJSJUsrFClXFOYA3+DCm7KhrzQV/K4FeADutk1afOJDdXz8GXK5Vb3TMGW7ZROC8Y/a9dRpGZ8P3/TOFMLKZOkztlbuhHMazhaF7DlCvNzx1swZIUCMtYtbGB5EzAGOcDiOsVBPznZ4RWhwIz7fwcrdgX1ZyHg6So1XPhVpJZgtVzkQmY8hUa6l38lcJvMBJ88VemtkJv8guQ65Ro5XvfY0T4rE/W0YGpCaTJ6FSpmF2uvAmap24WsDM+S2rfrAmfzLzhiInKr48e1c2sBkr7Tll9Dz7KcMD1RG1ubc03K+poKmmyNS9YAj3k8d0W8PoIzPn/F92SQdhqT22FbWGQQIiSe0JJvizI9pr7hYh0uCykkSfvN0lMp9jUWPvwNCYoNB7oyx4vJIt+e2Bo5RUrR6BxcQY/6B0E0I+J7RIOGmZkDEDnxA1dbq35iSqcgKBN9OBRgDBBMw3JitTUBFkgLPGjy1BgJY4HNLkqY5YZGn4UQmrGNPAe4IjXoDrYt3cPPotYEOcTQcHBsHJGBlVipzOCQ9SjBC89jscrnUChfrI/2TZzid0kCMNksitmhjy6JZ+gVs6uf5lIIxwp0aJYeE+g4EFcioE7tpFqOIrrzIc/owclxnxdNBSQabZzigahY4q5nwTbfX2XoYAgg1Eg8wMGz1CX2GHi4+Rytne7gcICxgvMzc8bHuHkqG4vcLQ5/URGyA/QlRtcpvh8ik5PENVZHN4h4uMoMPc5QAAAAAAAAAAAAAAAAAAAAAAAAAAAA4hLDvIsgfyjjMWJPpZnGttCpB5NxZB0lU48wGEkubgxgDsorbQ7qZ5dguDqOkoiJUC3ISGjDxHmRFasAxXdTkw8UJPGAStOa9pMEEiwIwlmdGK2NBiUhw0NHHsTBD6D5w6LKW49jfRW2miX0FUelyQgAom5XkFfusTaVVjYuXfpPLuIl3Wx4GnojHZ0054J7HpDm+64Ly2pLJCHqyzjgyIsnS4t6kTFZgVjWzD5+9TnEQfRq4DsBYinGzPrKsGrOHSO3fArOp/KEfK9HNKblu2aMDEXuL3DLl4qfovPvVurG/l/krrEbwKsndcxHetZlpq2jJ8UWVWDj1Ty1WeBb7tNnr0 + +name = TEST_3 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA44-Ed25519-SHA512 +pk = FlplaQIgVuYEA04nN9VXyMWK4XDhRhnaGaWO6FqoCtiDr5rL89omEFSIf811XxbbHpKBJzROqBqKfbPgsrMRZjBdHeRn5Oi4pK7bshdjBkXm7yCqPfMC50PkzKI5CFTsw13ikA4UmJuBupSxXDzhEchHLCybpQPBdr++Tr4NKh0cEaDi9GFlDdzLsDP/1d9Dh1N57rHOCPLi/7tjmqPYrCzFspEADbBkKBfwNgPRajzMl8FnQB8qQfMzK5tiO6jKEb+fd0C5UhwOYe4NXLazpqGtsV82Z0IC1DrQvHXNvePyHT/hOASYQQKM7D8MUSs954UFZPYFqvQrh1JyRl+87cFioQGOjfn3tt2uhYpMqzyRyeaywjwq8tyJC4ZAGuTTfDqXr76QdvTB4lKX42llgyIIdBwc4DBjugWYEiVR/PxGmzwyIBSM9PgXPAW+ijfEkyqayX0cPQRFaqi6JKxhg0iWADGTssEK8ofZwz4N711co7LRNSqfcRFb+QT9v3aAQS/VZsxyTybLHLpACDsq9qDCctewHEKZpVazOFRkTZlTIsp/YBd9avMTFWIgaxRjnuKiaLpMek0Hlx9BdvSQM9b7zFlIMGw11HBBtg/R22UfK+x2zNqSZeS3ZN6fylfum2YAm7IrNzn4car8g64DfQ6AMZHePK4eql5d5n8iShRAFTRg8jGv6pkaQBM1OaPcWMba1eDicN5pyCaYgwrhYy2yVN3uTOF4/Z1pxZ8XQ3Dx6zkA79f4P7dNhjGdYnlBH1wAnwTdGeP4epk3aF3jR/JJjRx0MxevLEYKN3DrSE580KWTmGEMM9UQcPz9sJ/+VgNkgRKGRXhw001q8vAzEhriL8mFnZhBaCNvGTbckPTH4ri3qWHbiDpztzjqKg7/bmZflQ7N7y3eX8wWSZ0g0Yh9KDJqwsIc7maoD20LxQRCr2dQZJ8mNOjsr+NonCDPBA4usp9i16WU1bBwRb81p+K6MWrax+wG+m2eBn/n/tkXBYYy1r0fBfiTizFQY6bna0HSzXqHkwwmDYc20ZGo77JE4bBlagKFQ12W74bSe7HwP28DxtvWKW56Is6lMuqZVkg8GQMW4Iz3hX9SKBe6/tw06PegPDbp/RfFAxzq9vcb5FU9kshMdnvTAY6I7OjgVGR8FXN4iZARzU5+BBQa4QblkxDUyiPLSjSSAX3oKkWmu9S9LxmrhsHgd2KOqbZL19EoZrx+eQyseaLjDuF569Da6V133Nqej46CPTZNp6ojwNSAjPY2ai1D0hZ8GQpTaeTRRiVcFUps4UHRCsUbBSTC5RJLVoGqj0eORAhib4xp7DGlKf7s1j88ZH1OgOw/rIbFMsyKSQsKKmI22Bo3FW2iVL/SoPGOKDupMQJvolkieVqK0DjUxsfDLf/VpkrM6fH3FIiw6Dz7vSz3CfUIQ0gDfQi/w08WWCP706VHeDwX1rscAvn2Cc6/q3TbP9bMYoEvS04p5LPGeHtvX/PVyfv9JNqAbrRVmtuTAd1rQhiPNHK5JH5feweblxNOsluNH5iJ2tH70R/3LcbL6jiHS9DfoxzHPnX3arWpVF1ZZ4aZivePAOX8B9J7zbxdhhdU1eUNFt+sEuGyA6VsTsHf8fZeXpbPwy1OBRMmNiJ3wG+VQC7guTMjnhs7kljuWlhZ1MlsnGroX7y51x2l78r3ntwiXkpyfpOBiDezTHPu8kYMNkLaZWeo4bygR04SD75GmjwkYGAXKSvL2XMqT8iCO8wQRrKneIeBgmQKojolj5EQlQIYptCzHjq0kVVout5a +pkX509 = MIIFUTAKBggrBgEFBQcGJwOCBUEAFlplaQIgVuYEA04nN9VXyMWK4XDhRhnaGaWO6FqoCtiDr5rL89omEFSIf811XxbbHpKBJzROqBqKfbPgsrMRZjBdHeRn5Oi4pK7bshdjBkXm7yCqPfMC50PkzKI5CFTsw13ikA4UmJuBupSxXDzhEchHLCybpQPBdr++Tr4NKh0cEaDi9GFlDdzLsDP/1d9Dh1N57rHOCPLi/7tjmqPYrCzFspEADbBkKBfwNgPRajzMl8FnQB8qQfMzK5tiO6jKEb+fd0C5UhwOYe4NXLazpqGtsV82Z0IC1DrQvHXNvePyHT/hOASYQQKM7D8MUSs954UFZPYFqvQrh1JyRl+87cFioQGOjfn3tt2uhYpMqzyRyeaywjwq8tyJC4ZAGuTTfDqXr76QdvTB4lKX42llgyIIdBwc4DBjugWYEiVR/PxGmzwyIBSM9PgXPAW+ijfEkyqayX0cPQRFaqi6JKxhg0iWADGTssEK8ofZwz4N711co7LRNSqfcRFb+QT9v3aAQS/VZsxyTybLHLpACDsq9qDCctewHEKZpVazOFRkTZlTIsp/YBd9avMTFWIgaxRjnuKiaLpMek0Hlx9BdvSQM9b7zFlIMGw11HBBtg/R22UfK+x2zNqSZeS3ZN6fylfum2YAm7IrNzn4car8g64DfQ6AMZHePK4eql5d5n8iShRAFTRg8jGv6pkaQBM1OaPcWMba1eDicN5pyCaYgwrhYy2yVN3uTOF4/Z1pxZ8XQ3Dx6zkA79f4P7dNhjGdYnlBH1wAnwTdGeP4epk3aF3jR/JJjRx0MxevLEYKN3DrSE580KWTmGEMM9UQcPz9sJ/+VgNkgRKGRXhw001q8vAzEhriL8mFnZhBaCNvGTbckPTH4ri3qWHbiDpztzjqKg7/bmZflQ7N7y3eX8wWSZ0g0Yh9KDJqwsIc7maoD20LxQRCr2dQZJ8mNOjsr+NonCDPBA4usp9i16WU1bBwRb81p+K6MWrax+wG+m2eBn/n/tkXBYYy1r0fBfiTizFQY6bna0HSzXqHkwwmDYc20ZGo77JE4bBlagKFQ12W74bSe7HwP28DxtvWKW56Is6lMuqZVkg8GQMW4Iz3hX9SKBe6/tw06PegPDbp/RfFAxzq9vcb5FU9kshMdnvTAY6I7OjgVGR8FXN4iZARzU5+BBQa4QblkxDUyiPLSjSSAX3oKkWmu9S9LxmrhsHgd2KOqbZL19EoZrx+eQyseaLjDuF569Da6V133Nqej46CPTZNp6ojwNSAjPY2ai1D0hZ8GQpTaeTRRiVcFUps4UHRCsUbBSTC5RJLVoGqj0eORAhib4xp7DGlKf7s1j88ZH1OgOw/rIbFMsyKSQsKKmI22Bo3FW2iVL/SoPGOKDupMQJvolkieVqK0DjUxsfDLf/VpkrM6fH3FIiw6Dz7vSz3CfUIQ0gDfQi/w08WWCP706VHeDwX1rscAvn2Cc6/q3TbP9bMYoEvS04p5LPGeHtvX/PVyfv9JNqAbrRVmtuTAd1rQhiPNHK5JH5feweblxNOsluNH5iJ2tH70R/3LcbL6jiHS9DfoxzHPnX3arWpVF1ZZ4aZivePAOX8B9J7zbxdhhdU1eUNFt+sEuGyA6VsTsHf8fZeXpbPwy1OBRMmNiJ3wG+VQC7guTMjnhs7kljuWlhZ1MlsnGroX7y51x2l78r3ntwiXkpyfpOBiDezTHPu8kYMNkLaZWeo4bygR04SD75GmjwkYGAXKSvL2XMqT8iCO8wQRrKneIeBgmQKojolj5EQlQIYptCzHjq0kVVout5a +sk = AdjfWRUtikTjmiXmd17xigBEsg5YwsQ0asN8J9FSF2xb6mwkVGF7+0hHnrixO3EamBFrc/fsrDD68d2bT6F9aA== +skPkcs8 = MFECAQAwCgYIKwYBBQUHBicEQAHY31kVLYpE45ol5nde8YoARLIOWMLENGrDfCfRUhdsW+psJFRhe/tIR564sTtxGpgRa3P37Kww+vHdm0+hfWg= +s = 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 + +name = TEST_4 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA44-ECDSA-P256-SHA256 +pk = 0tSxxXlGivbpfkDz/vQHy2wXg/XjioXxaKESOD3TRscEjU3FIQIXrPnmCyxikKfBeueT9tp0AdM7u3hP+9gK+Z71GuwD59hgUQ0+AEvV1kay89b2104pBPobBukjcqyn4XOeLiAOODomzBkCPaNuNlZpntDY4mqYJSzPAztqlOjwsSrfWeqwHAqvCNaQAfpnvE+5G1Z0cE4oQVD5jeGTfBaB6TdAq3XkIpESMBTm7YBmyXG3oL7Ss5sjMlCg5A8KXPZsg36fzAz8xMjZuwWV7Pw5YD5Sgu5K22Qjst+Kwax7gRczKUHqLKLUfwpiuKkPc5S2zAaRwi6N3sjKzKQzex0fvjX3SWQnfAGeeoQUQhZ/+8UI8/GTRu7ohwhCRn/Rk4fEms3t62qFZsvpqPtGV1iawI8M/jJH9VReS0q2EPRDzr8YfXuXF7KA/6EhrO9zdHbvHtPRdsGnGwL+Dcg6OePYkvdPnm7J+PzF5qSfw2HRJ2TRJhMKtm8i8SsvvZSVNIhVpMp0OwYCIszHE+3sHzYQIkSgkb+pX5+wGoUlfiNAOIk3wa3X85ZTXZrS6pvmB0yBd5Z8rFw1c8NYYHC1gR72J4p5ygghwl7ElZ+Ge7HnM1nrPHm/lxe7fA1jQ3BNADh0GNhiCed9uWecMSwjZlIeHkwbCy1xQeJ0eOPUWEf/+aGhIOEPUTC9tQHBQIk944xbOYY2ZRxFP0wiJGuBaA2S1A/VMg3JOYm0aB9prnqXFgLsEo1teN+Av+fPghnapZZwPFHAYdQ6MItvKH7ZwegrhstZdiOs/6SVWnDrYMCOWUVOgTIRlnoG1gClw3TGjIODCHoPzS/uNeSbTVIPkKgIXKDrOfm0Y4F1GzBRBUSP8sRnBxf72/RTwGT93shMDG36GmrSuFijXbxci0YXCrPrHuawy3OQk4qpbrynlwqq8NZUKd+UjXsahg/jlixeat3I4v8i8vYdV4K5VpaHIUiG0gfkglOv5vroxByQLYt6z3O449mzs9kzdyFwaApUSGp8mEv2H+2pFJN0ItL9IHgbgvrnZqimKKfu5fhLHZvV1rzQ/6FAClkqG2zkk/5Q+O0gbrdr/EwYe8JGjkqONM6lR5YyDG5g/2d7XIBSZs65hESTzqPJL7TzwZBBUDf5x92+9gaUIGYz65JQjT+lVkrAGwVQa7wxYkH7cNmt0s1eprnT3Zoji3zTbnN++RAybsG+HwrSEB/OOrANVR8hXmCYOFsX8WeJ1Dp40b0ZiqqPw7bANgT8JuqSXK07NCCUTFVkkrCTaXTI5zWAQwzQnFdgEwYeIXn2yupnNdLO8XKT4eSLwn792NG+AxiR0RVFJlUWed35/PMi1WQh+ijo+Gvm2r54FJn53h27kFzRpv2bwTu/K0cRNmodmvNnV3I+za+jtswaxputDkAPSjnaxQAzUC0SgbkrPCxexKJPMLOtg/TfAox4ziKZm7V4nHJuHuBIYZriof+1F/nZAbkOczJ01IO7wGnYbaX07J6EmNxvrmteht9I3yZpRh79hn5pCqXfsnuAfNPHwucz4UPvir7nz3X2+FtkBRbvZgHoya/XPk35EwFKqpk4sRtd5SnrAWTg5cb9PcHyroPp9pfhQyAHjH+17l+K2/UVgR8EULEN4VFcg4HUwX3iFzgBBVlEHov1xVdmsYvpIu4tj5Aw4ZSMNflUohpB49hukpt2r8mqA0o7xLgFVn3Sf8hYq/SpaxYQZsLH1t92IHQ0Gr5s0wTK8lixnbMoS6wRsEB65+I8OBSYH6/hJuZ9HJgyUYIKWBdBV6jt1a14nXf3ULsdsFXSV2GcBB9JqdR85Doi1I20 +pkX509 = MIIFcjAKBggrBgEFBQcGKAOCBWIA0tSxxXlGivbpfkDz/vQHy2wXg/XjioXxaKESOD3TRscEjU3FIQIXrPnmCyxikKfBeueT9tp0AdM7u3hP+9gK+Z71GuwD59hgUQ0+AEvV1kay89b2104pBPobBukjcqyn4XOeLiAOODomzBkCPaNuNlZpntDY4mqYJSzPAztqlOjwsSrfWeqwHAqvCNaQAfpnvE+5G1Z0cE4oQVD5jeGTfBaB6TdAq3XkIpESMBTm7YBmyXG3oL7Ss5sjMlCg5A8KXPZsg36fzAz8xMjZuwWV7Pw5YD5Sgu5K22Qjst+Kwax7gRczKUHqLKLUfwpiuKkPc5S2zAaRwi6N3sjKzKQzex0fvjX3SWQnfAGeeoQUQhZ/+8UI8/GTRu7ohwhCRn/Rk4fEms3t62qFZsvpqPtGV1iawI8M/jJH9VReS0q2EPRDzr8YfXuXF7KA/6EhrO9zdHbvHtPRdsGnGwL+Dcg6OePYkvdPnm7J+PzF5qSfw2HRJ2TRJhMKtm8i8SsvvZSVNIhVpMp0OwYCIszHE+3sHzYQIkSgkb+pX5+wGoUlfiNAOIk3wa3X85ZTXZrS6pvmB0yBd5Z8rFw1c8NYYHC1gR72J4p5ygghwl7ElZ+Ge7HnM1nrPHm/lxe7fA1jQ3BNADh0GNhiCed9uWecMSwjZlIeHkwbCy1xQeJ0eOPUWEf/+aGhIOEPUTC9tQHBQIk944xbOYY2ZRxFP0wiJGuBaA2S1A/VMg3JOYm0aB9prnqXFgLsEo1teN+Av+fPghnapZZwPFHAYdQ6MItvKH7ZwegrhstZdiOs/6SVWnDrYMCOWUVOgTIRlnoG1gClw3TGjIODCHoPzS/uNeSbTVIPkKgIXKDrOfm0Y4F1GzBRBUSP8sRnBxf72/RTwGT93shMDG36GmrSuFijXbxci0YXCrPrHuawy3OQk4qpbrynlwqq8NZUKd+UjXsahg/jlixeat3I4v8i8vYdV4K5VpaHIUiG0gfkglOv5vroxByQLYt6z3O449mzs9kzdyFwaApUSGp8mEv2H+2pFJN0ItL9IHgbgvrnZqimKKfu5fhLHZvV1rzQ/6FAClkqG2zkk/5Q+O0gbrdr/EwYe8JGjkqONM6lR5YyDG5g/2d7XIBSZs65hESTzqPJL7TzwZBBUDf5x92+9gaUIGYz65JQjT+lVkrAGwVQa7wxYkH7cNmt0s1eprnT3Zoji3zTbnN++RAybsG+HwrSEB/OOrANVR8hXmCYOFsX8WeJ1Dp40b0ZiqqPw7bANgT8JuqSXK07NCCUTFVkkrCTaXTI5zWAQwzQnFdgEwYeIXn2yupnNdLO8XKT4eSLwn792NG+AxiR0RVFJlUWed35/PMi1WQh+ijo+Gvm2r54FJn53h27kFzRpv2bwTu/K0cRNmodmvNnV3I+za+jtswaxputDkAPSjnaxQAzUC0SgbkrPCxexKJPMLOtg/TfAox4ziKZm7V4nHJuHuBIYZriof+1F/nZAbkOczJ01IO7wGnYbaX07J6EmNxvrmteht9I3yZpRh79hn5pCqXfsnuAfNPHwucz4UPvir7nz3X2+FtkBRbvZgHoya/XPk35EwFKqpk4sRtd5SnrAWTg5cb9PcHyroPp9pfhQyAHjH+17l+K2/UVgR8EULEN4VFcg4HUwX3iFzgBBVlEHov1xVdmsYvpIu4tj5Aw4ZSMNflUohpB49hukpt2r8mqA0o7xLgFVn3Sf8hYq/SpaxYQZsLH1t92IHQ0Gr5s0wTK8lixnbMoS6wRsEB65+I8OBSYH6/hJuZ9HJgyUYIKWBdBV6jt1a14nXf3ULsdsFXSV2GcBB9JqdR85Doi1I20 +sk = xzZ5w/4wfHqh6i+9R+QBQ0AFstNaWic0z2QeoqnL3sUwMQIBAQQguU52CacXarr71KNPq65CsJHkTZ5G5n/KVmwqGIpjxl+gCgYIKoZIzj0DAQc= +skPkcs8 = MGQCAQAwCgYIKwYBBQUHBigEU8c2ecP+MHx6oeovvUfkAUNABbLTWlonNM9kHqKpy97FMDECAQEEILlOdgmnF2q6+9SjT6uuQrCR5E2eRuZ/ylZsKhiKY8ZfoAoGCCqGSM49AwEH +s = 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 + +name = TEST_5 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA65-RSA3072-PSS-SHA512 +pk = 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 +pkX509 = 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 +sk = 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 +skPkcs8 = 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 +s = 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 + +name = TEST_6 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA65-RSA3072-PKCS15-SHA512 +pk = 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 +pkX509 = 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 +sk = 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 +skPkcs8 = MIIHGwIBADAKBggrBgEFBQcGKgSCBwi2XluhHrepWuiI0UbiKCu/4vYcHAcT42UkUuWBBtQXYzCCBuQCAQACggGBAI45LOhIVSrck0cEi+nxHAScUyiNobUYDu3MqcpdRWXflHu2dKv62voD1eevWqbccNzixW/8pg6d6s5enHkwTaohtTYo8vKOHxhNtvJq4qveovNygLL6Cggu1sqdBAFU0z+lP3PYXjx3T3SIR8WcyobLUQrBJ3kOmillZfxi7/0G2Q03GAO3J9Cx5xPZd92b4SXdZ2GIX8pIlhpjvgyoDNQkFAE3VrLme8zEMz0c2jZNzcw1qGTw8OOn6R/5QsNPd7HFPJ8aMDTCC0l+nDLmOY1tJ21lZqpym9gkKYkOCFnz90QWAMF8O0X7wavKMaW4bV7bxkvj6izCcD9OOMU69a1GfDu5MhkYbs/g2jHvVTmiVCPaS6hgptHmlL7J1G9kDU7ZiuqbiN2UoKf/JbyIzzp+6F9hVwAVyfI3teulwQpt+N4RcObrQUrW/YDrlN8ALgYyx8xF24U2wVPDNShETZRCM/GUKfVfxBychgAui4nt4ZMRwqFZjcwD6uW5avusuQIDAQABAoIBgCHvKHTf1VpduNJIFm4iNlIvTnQtnvjLnbxFifmb2ZQeDZqbBSmHuDnRMneOUm3fyRobDYrT2HdCXKUS0/jiepVuZ4Ndr+IJ4Qd2e4FJ2X2yqGS33FdX647aRiuf3TXIHINiFUq5xHpe6VlfruhKqdQRrOuI/LcYsdXQuM3IeYSA3scVbyhryhc0a3ND/yKkL8tOEX2nAVwNh6sSQ2VXr3/qf89uecRD1CQeaC9btAEi/UiNQPmct41KIgib+78INabpkTyVWXWDILTY49iBm5W9t4ZMH4r9g8BY5T9IJ5VojgjWeRhM4W0RUfHJR9nmS7Lsv8JyZH1XYfNbwR9bipngOuXAUiFi7+DRZHEoBs3doOoLes48nAxHn6f8Dt9dGvAFvDU8+yK8e0Hx8y6ih4l2MUSoE4ZPZ/7viFLuQf07DzZHtckhRf4PwoKp6/J8uHqcS66UQogDZdmunhXhPOos+TfXEv8FRto9FHI1hJdIj2Ww9CjFwnmQaJVwiw0z0QKBwQC+83tl79dQjQUhMnvYehp856KtNrXoYX9GH26+/DouU7IUrDwHXHZupBHEQRMerDG4rQ3niPsy5oxtPZqpmsy6z0FdUjcYVuCQbIS7+EwWKlkVu+2BwHFkxrS+g2TqB544deZB+I6wUlba9r9ptGWcmiKEusCoZW0JTB595kRihxG+OiKoIRw3joYO0CBNPWjY5qlOUQZa/gGXsW6dDSu7MCliIwwgWOtADicH4Y1DwIwwto9KjvP/xNcDp61v9mUCgcEAvqw6oZ80rhs4FzZTHVEXM1axIiee4O0Q4imSbk0Wp2RLYjBjQVo8WntYCYkPSGRVj78OUoplMs0FF297bnAT5ywWvMFiq+9Cpgy7LxGsMyEJH6EtjHtmrF9p3wu641mMMZqZKps2CQ02VAto8yS6XFz+IAsna3d7zBCvVvtwvu+OWlqSF1Ew0xQRk2ZP13Fr0PdX06loX1C8Pwh/LDC3za2DpX1Ifpj21EX3HwlAZs2Ifd7xoOwsrU5qT3HZWT3FAoHBALnziE67otV5ju5GKF6mgdbzB/NgXQNgtr3F7GYmYE4vCTOiio6sl1Slh/uzV3g+PelW9TxtpBpsc4IKROw3qHmfTUsgvjCAog1i8FvQr9/B+6DzspXxnzuBWhBRClvxcT4QalwCPooslRKEyeCZYX6Ah270IpuasfUk4GjAbgX4/Olf2dIa9EySCbCxM1Okos7eyS9QLj08MLbVfh+I7PZML3C3Kp7xmJ53acD+AEBjaOIjViVIUwrXWH/hsT9gFQKBwFd5SASxt0dQsXDkuUiv1Qb2dJkIuDjUSXSYQddcglIVDwhizoZC0YFpKMBnIp3BegdSTU3MtBSUPgu8V1IZFOhvL5c02QGr6FWpbY5weyGRvE/Ib88NJdNaioWLKhf3EvwGwtHrFuUcLJpuCZ3ScbTB7MvikhkZXbAGz4oGHHlcCWCmQHN2CgIsKMwImgRqwl3Bu4cdJwbjURlR2pX5W7l+KvEXAtUhc8t6tZ6Fbgg7rqygRRXgg7aWTOtZfa8VSQKBwQCRIqiBKOz+ZrA2PJRfibF1rYLoMrOmHrWB1iaLYMBrCXY19Eh/3m3v/54V4XIaiwHGyYI/LAT2VNeFpu37DHdHadNap222obrvjc4RfyEOY4GpJ7uEaXJarsBWXLtmDVL/lJ84OW+fGD3AxCEah8NIP6DhZSfI1hrTWGEiak6EkmoDYnvvSsftg9itYMKtqHUMCgzit3FGcS9z32Dj3UW+O5dzPo762pS+hikc2AU704nhq9s3sEWvWeJ8B6edm2A= +s = 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 + +name = TEST_7 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA65-RSA4096-PSS-SHA512 +pk = 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 +pkX509 = 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 +sk = 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 +skPkcs8 = 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 +s = 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 + +name = TEST_8 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA65-RSA4096-PKCS15-SHA512 +pk = 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 +pkX509 = MIIJvzAKBggrBgEFBQcGLAOCCa8A8bnpV1xI+dtfZriP9qe3CZW2O9+V88CwLBG2AyM0c/sOjbxzzdfJzARraoEwYeoGRnxbrlEA0aRWpejPNhnoZuHFY/TBIuaxM4U2kekYkJ/W17h6bymiMpRWKdwk0Uv5/N6r8LhljB8FL/3NGT7An8aZebo8MGGsKH7jPdbvkVpJa4vRwR9X1RFXG+c+qzZT6S8wg+z169VK5hZZq6aNa+RqJ2EOIb2ctUTq36jOp01xrlskka/8M9RTubJtR6GQeXiw3VB2h4Ai5GaHCUafJf2kscgIXz13PYx1rcSvsN7r7mO1OB2yVrROln/1qN8UoDko93D2zuf98LoOBUfxj2QmlQEkpiudAFO4cP6ZE2lYPlq3Mm+SLONkjVQxHMGuD1TWTQx8qYOfqjKUtjLsv939v3GfeHVleb8CHC3dkNAIlkRrWGbGIqRHCuiKh4yfhp0tacZwctXPdjvbpONYqyjior9+hMHWmaL+b/b4d/hKvUU51J9pQRaQ1/yLNCFA7dwxy0mY65U8PKqQ6Tw5tFmsTKSHvNN74J0ouUUJzUjn/wWt6FFVyDNZpIi6+LZ5hZxRMqcWC3cFtbvlWFgq3WALsZne8r68I8WlmUbGfo1KdfwfSkMEQhSSp2vg4vm+YjTO+cIBgn5j+RddynQj/zguxl3IDyl0ZaLQpOVspwRs2st0Wbom2I38GMQy1MMb57VS2HaUaNRm7NnlvtNDVGhm74STQ4hElMifldY1wVE2GV1Aa8ZT/XmkqOqC9Ms8shF8pDZv0B4CsuthqPQBU/TJ14Fc+aR7nP+yo5S114V7jJcRFGEACr48XNHI2CVcfGufWpV070wZRkcbxjlpXnJEZjHaKKXu/bjlTxrXxNp3yvpm2LSwW4PKbUqcUZ70ogN+VOepRDFFjnQSudS9+eTMa3/m9iaZx/UD2jZahwefyLdKVa/5m+eG8xEsFQhCvbQ0ZKaq8kDK5ytLbQfDVJEYVMIXWbOr/d54dOAob/R0D2FdhL/Lb8huHH28IObCXahuaTkQy9fF+MOhUvkxbloi6JL3j12ZPTou6k9uw33k9YoWyYQdfZm02dCOmb2DjctQWiYJWmygyq+WapiEmjoqDV+F/b+b5uRss3uIKKKO6h3pUixba87kCzFhj/PMzcw1jZJULpXHPIphbTq3nypouFq9omXFN2WNsOQwfb+Op3y8ia2qja1drlHAoRfUYXVQ1e8C9lJpP1nqLpKU2l52d12YyeNa7vfJvImByA594Gu28sxq033W5PItuocegRe+Apg77JkrfLlFSc4Qohrc62s94uGy/1FH0NnO+qo5dC81pKnEzmouuNad5RCsjdQUiLqQ7MLFPpwlyDQN83EphxZpKkDmXHvldPL8Fm3Zo2vdLQL7yOX/8OlGVUARQeD3yplt6BwquS/RCIgn5akYSKfgXAIokz/hoc6WWRxNuXdcuP4jIqxXKgMSgQEqo+Nuinqg2xu0o6EN5OVH69vNkEhRZv5RIZtkQueNttI6qVqwfPgbxUDGFlT3O71FSJ0BBJwwb32792Tbjco899+VwQBlQo32L3jdDyaBbNkTd9P4tg6nOZnoWP0kek3UzK5mXqPe6lr+WHjp0gAHFshM/ajNP4Teet+h/7g3i9gcwbk/e9lX0rWqYF21SaAh3V1dit7b/HzKnzDOBlTcAL7hDjJVtWFLCDgkcQjkyCsDzZeksE+Vy8OHmopChmoovLYgSrDtJqiMxb9GO8koy3rZUjFNwJe7kQJedFb6vsyJkimCfQymj1c0J+NiD6p2fLxlo/DwRzZ2CZER1dN0fBrTF0qC36sY7h4UzJYwaZdd0RzROwYa4zF9pbRhOsPu4lur4kCp1qNQTK0WNmKa4d9RC+Nm2FVXWvZlwIK0abap5FfuyCMseizerlSvc49seA/hDfpk1XDGeUYs2987fFNmq+mgSbAP3FOiA8n8zH0jB+ZSF9mW+3yT3sK26SKRCFSBDNNXB5IPugMuWBzuH2O15AkZF9GJZhl21s72vpO2GdRYQ++HLSSYTTvOyhcM3fvCXaMjunPB/JEYt8KkfBS1Vwg/d4yD8CUm1Evv8N2s6Yv3viRpYZI8C42wpGU6NHRHSqns70Vg0aw41tCxzkLdRPWKWuN2KEBRVa7czdBq/+T9sBkimAksS1WGSGQD1jvvj7yB7OKaoOQRTcsW38G5Cur3w/CgY6gPGrgDdqRJrYpxjTs0RdCwvngFE7/4E2HhLNHMLvpxAQAUnd+SXZXEs0pcKU6xnm+P/qQlktU2JixNvJvMbC6qyIzjUE+EQXV4VIlt4GMlw6cqDozu4FT9XMja/hQmpqb+ggUD2mf3vAH9FIOJfihC8+wYV79n0VduLkdfb+ZwZfkaWaSuUl07UXdgw7RfIMj8Kfz71clBo/rhOYg47p3iZRYNeGXwWviVZFw3GWcGInS4NhbsUxgeoLNDTPsZZFAPGmB92/eKLIPJvjry+2yrty1JGh2jAHJsqwLefzr+YtLBlp+0dca3taRIsppLrbn1wseMmh+AgmhvTK+q2iGRga+h+1RRQgqPN9ZtFaxUN7U9C5UylT2CCbGM9BniUb7MhLADkBkwggIKAoICAQC1hi5ZcZXqhhqGiF0h7l4mRDsHrdTo7hgTOIuuqMrLIaYIpuCC7HYDWKt5r7PQ89YQ+dj2qz7KRHQj2rljNkbBM+nzGk+gHTC5JbnUjyOYLsuVVfcWwYuvGQqL0xSYiC3L/LFzoJffxq3qG2tUEi2abtOL35iUsgy0a6QcPSTdBS3Jw74cM8PlDSyL+OsElV3gfAq/Fzp9HrW97Di9LqFAI7UgADsAwBJHkpLDDntRKP+RK3jlvrz15ZObHawEzyIW8UIpZiDSuAm0MuPZa9EmKcgoAWs9P0+t5gUMhYAQQ74Z1mWidT8iOAlUkwprbhFnfXBHUzz0xKT/SfU8pLk6bk0UZVQYvgVCj5JOTYPVnptfMFSgJmCtUXc+Ers1pEXMRmArKgdlQYHOgVpHKGfUoUpnuxz9xEgE2Sz3eG2C3AX2yFVybCByiCiJlsoyKkYvfhmSZUB5aIJrfuZmUVbdDuWAqh+WtAk3LQNK6+eJz/QHfCRp3Ta070CC9vQq5XPJWWIQR3cHFUBY9qzQpp0Uuak704gXfA3yvy1OEmYihezSsq943lSrjI/RMMrUa9haUyKvRpgpPLN1WfRkh+a+Ub7QeecFmKqu+IwSmj1b10JAgI3eSSbghEAe2xRaBvATLG2E1YlL36aZ+LNXixDHrVlIFFzpBoKSMIHecxUhIwIDAQAB +sk = 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 +skPkcs8 = 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 +s = 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 + +name = TEST_9 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA65-ECDSA-P256-SHA512 +pk = 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 +pkX509 = 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 +sk = bsdcWusLmyFNumCKr944e8i+AxpGXddgx9fH69Yo49owMQIBAQQgmEOXvMQ5wGDvUQnM5wuW3XNaM9DuU6F6uZepzORqyqugCgYIKoZIzj0DAQc= +skPkcs8 = MGQCAQAwCgYIKwYBBQUHBi0EU27HXFrrC5shTbpgiq/eOHvIvgMaRl3XYMfXx+vWKOPaMDECAQEEIJhDl7zEOcBg71EJzOcLlt1zWjPQ7lOhermXqczkasqroAoGCCqGSM49AwEH +s = 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 + +name = TEST_10 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA65-ECDSA-P384-SHA512 +pk = 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 +pkX509 = MIIIEjAKBggrBgEFBQcGLgOCCAIAtdGwjOkGFVBJ2pm+CpTYs2weCCfZVqFJJdD7Xicesj1Z+bnu+RJriSqrde5itNwjEuPE12wmZ18qMXzy3h/Isuodh4zCr7hAbf34EbEJN8C+n1qVX6F95esYkoYI7RtC3imkjyOyKpUikhQ2pB6WHig7JDE86ySMmTwzdNqk8vy9SveBf6+SjLd7HWmcY2MGsrCGhMLHfc30BYeWFuz08iQcgt5zw80gSU8uyxtnrEddYb9GkOpScvEXK5tHOCaAGOxp41JXepcHUeodCIcWlfHfjuoKYTBpdhvFnRrwo3ROaQKJDZligr9P7w9m+sPAGRetk7MGvqzKKmDi1oFpXA6NotDK7jYvmHh3AfT9BDpcmnK0EJBnA0H6h1oFeMS4BU2wDimfnOMdiKenQ6VvpnQm68KNW9B5kketHz8vMDafpafQT+P9LfjMytmRJAcaDYHgYRFOUjrdVnkFLqG3m/iHv9byYN2MIdr7sR0Ryr3Um8wqXus4oAmKjEHrvOxyVicuEO4LJMlGWrMSDCkM+O4EQV+blNRYkjiZv6D+Cre+QK8kG+o6zJj3Mu53jZ01gK2il0HGhDQLcBy6yc5XawA8M0b39yILfDntDycn3Uxfp/upceRY8Cd9IX504fPaBvX0R5O5CUhazdTZYxndYCIvXtHU/HE7T5zKfhtClgWGTUQD+51uX75H5xnles3NZIwMO8Orp9Rs+OkERK3rnFjiY+5EqO3VuTvC2fi6sgnT8uLyunaGUGz0eqwQioFO7P6zo8E1J0WO0tBkflLrYh2/GCclng4YB3IMlnntDvMY9IFkZQ+tGtot1i7P3HZbH8vkKaWmcCTHpki4Wf59tpQxmw2m7PkOE04G4Tg6Hj++wB79R0ID5wzqWwhJdgEDZ1efir5DcEnoqCGHYBdpp4N3iE7622lvI47OzU+gziTMeXW7ACe8Vo4hdupA1RVpMiWIsYT3VZBSoKJfy91C44bZNtZxyOKSB0yGWvxec4qaxfRQopxIS17qNfkm/Tp90ZjQ3nPraiVgzrugyANY2XPKqRrMZ1oN4xSWZBdI2VSN3QsWZyhKePqWbUnUc1bWOccRfLCBThJVMJzDgadzUjQj1TKnOjv+eYmITKfchlcgfuZj9nsTyPNQf6wJHXPj67jPc6SLFL7m1UsfmeR4iNWsQZFKWVbSYqFiUzzaE8VT6Yr6nyAGJiFoR2c5PbgAV6PmEegXutmZPWzp2qwi1BrT787+Rn0KdQp0FfUAa0nRrNbKBzlSrPwx+6vBFFy/viemSDjqbDjagtvSHZU3CqIIf81Eleo8cnEeaFzUYW2oS1vqC5euzIU1bPL1BUfxYYZzLGgFtAozyqZk7SpOb7VlpvAhmYTZXxtK8BuMj2/1l1GtzR/eI+ynhpIU1mXwkxjr114lz/LkLxLv7kErBegoH1LorRlb2qmXG8w0kJsVAuMl7c9X9ye9Stccd7cJgHshpXmhDnVIufEunsAt1nW4Gfoy22q6Yxskm16TQ7GwaKXO4fS501emN5ilUb7eU/OtE22uTOLWVTOABwQ5Dbu+TVAt2KOUOt/j9Ujoj+kTlKnlKTv6dBHIUXjp0ng28nlXiPxqD0fKxsVNEr1ps6WkjixHJ8aywYX9WjSc34VCZgXrgoBm3oOCqsGT2X+jce/fYu9hiBo5M77kBQvL/bvhZ6TrNelTmsSapbf2elB4fQEbGFiH+xN07cYgSxRLdApsrw70e7eA27Y6RtkAwgBRgPGzUj1bPl+vHRpzhIfryE8rgJ3DXU/bZYp6/04n/RbmU4SINTk5DHoxFrReSx+hsW3WKZ0bpzvGONRB+KTiBuiuFLNIKqzBgEmERZs4nus4xpNTor0tFitX8UyXm795OI49pp+jWO99x8lWBsgPNGr1qnEImK4Yyd5tAwV56YMpFFrj2I+yaI8enPDJF5rrDOV2X3qvGSe6QBOnhvKxI3CwJsw9XdI7Nmfjl4q2ZLh4H/qivTz2mba/OeMipu0v93NugmH1zEi4oXoQPcI3nPfv4uM31Y1oFnCshCu0MW32i/g/iwXdGld8guzXtxFaG1Sy5mruc/W7UHzcErwAk4v4MwC3h9aAg3stZWWTZG4jcwBC01UHqvn6hmTiZ7/vOPakQUYlnTH+rQWF2XVLRux4Z1Y2a3ow9+MTPQ6M08JPHfjF23l9ifE7hg9WkqtEIeHsXPrc/Y6sayfV0be7SziXANw3chGZtRRWf2GOfXhqjbEDV8vZm9EgU6ieMsk02T/nK6l8TrBgbc3XzskgZoZFHbXsaUnp7sJ82gmXyUufomBZ5ZF4MD3cdigHgTNM3cVf2nWgCs6HQEVnV9Av+PwpKYD/xpIioxuFeVCDxb1z6n6Z9kFFb/RC8XhPy0mOy7+bdndJ5LzJ3pX14S35vfGlz7cTHhgd2A8U1ULiNrfyoHw5g5D2bjYxEr6GSfcxwYklVuS/rrsCbCCZLu0TRCRtDXuCdrgeGOpm7AXnTXoD6W8jFV1DXeHY0V9wibN1p8FEe/q89P0N9zppP1225z0QGuW2tRxR2TspCZq9Ajh81CiCDN88Bt/V27eZudatsYx5QrzcL9r+C99mn6EEO5lihplHOrLaNKOON2H1FM5pXBD+q82CRD9rMm17kgGc3XA5PYahLxuNl7xPqTaDXJ7z5DyGSpFn/6qTJO0KCJi/MdRWeqw2+OwcYvV3rv5JabLOVtrutejeW48xwSRK +sk = GBzZVGrgCtjNd7FtNzoxmzXVEcHki9NScm7fSsJokqAwPgIBAQQwHgigRN8usgijZwZRq9Xo0wu5aeauYgfkTTKGmPFfoomz4FJBpwmk5oRVHB8eyrtHoAcGBSuBBAAi +skPkcs8 = MHECAQAwCgYIKwYBBQUHBi4EYBgc2VRq4ArYzXexbTc6MZs11RHB5IvTUnJu30rCaJKgMD4CAQEEMB4IoETfLrIIo2cGUavV6NMLuWnmrmIH5E0yhpjxX6KJs+BSQacJpOaEVRwfHsq7R6AHBgUrgQQAIg== +s = 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 + +name = TEST_11 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA65-Ed25519-SHA512 +pk = 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 +pkX509 = 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 +sk = I6V4IFKJVV5l3fVnYBy0cV+1k6d5ao9x1mjDvKh22Tdg6XQfX7imizxJC5Pkqg5dgiBu87jiJC4bz0cp8uFGqg== +skPkcs8 = MFECAQAwCgYIKwYBBQUHBjAEQCOleCBSiVVeZd31Z2ActHFftZOneWqPcdZow7yodtk3YOl0H1+4pos8SQuT5KoOXYIgbvO44iQuG89HKfLhRqo= +s = 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 + +name = TEST_12 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA87-ECDSA-P384-SHA512 +pk = VJ3mQ7kwpzVTIf9jMh3pRI1y+baUMFVfm9czHDJ/yqV9m797G/ARvNXDFf+s1sZr5vEMhaY4fPbVXErwqUhvwkgQ26sK5+0r6v21Kz5/CvX4p00ntTPYzzcbh2kR08aox5AvNC2nU/zNim2GJ7dy6y75zFyXDKXmz0gROmYWWupo1CjTYvWWApv2tRP5eC0xb12hUkjJNFiPQzFc/3OFFGox2mig79NSwHsd98nHZmXTZs1IoTyVnWRG8q1qmXaq/6RfhWOG+2+YHEAVUDOFUZBXpM4gMjmuzBx11LiU6MuUXD7b5myjgTvU55mCiHae8oBpwjbzzc/J02SjCwn1B5Ko7mrzEwthJMKAOgQ6D0LXPOEt+1RenhqRdDt3fvp1buHdVmUwhj9IQfZ0yJExugF4w4ORlNWrzMwgO3XhwSbTgzko/3jeqejnVfi/OBBSLHEq1ohW7s14w7JWog/03njtIFLvO3HsoD3LRZDzAmnphP7iN6NI2q6ovs0KthxaqGLA8b303lq5RA5YDlqAG7/2T+yRUFZCkMQFGuhipdE+VB6Y3BDAEi6vwdrFIwz+1y8yk1PmaJNOczY+/yl4Mh2iosRpbjPEoQbBZG4KgCJb4xpIw1QkVt1stCLLf7dmEGugdyTTrCTLWhRHnAM2rteNGje8sTWzDeO6jELRaBScZYHaH2gBo1AGYvd0AlJRWaNZiBKiUJZSzjuRDSP7E5pqvELAKuFyAhe9FA3rwts1B/A+cPkj1uXvRi1TIj4ZX0uEOAzseYafRfWSZbc+i5c7hqcgwFxToHT1PdxtpHGSeVDAFEk4tTYQrBaiDavl1ht7NrlkTAWskfalEF3oku01od35jfqt0wKArGOLILB3/pw57B/hYWskJIakTPLQ+hnwiyKLsk5Q2f01N3MOH/Mc2PNew8EnhlMOcy9D4adoA3JJ2OsLHYvRCqrQiUN4RbA631P3JJNn9Tw2GoE7BUX1BKlUcG31AYohnZqCcx0vd+Ogy2ApX2xnU4JeIjFt7vBTQs1VRGpIjZdzSVGcxdfZXILj2ThNezzedH8bi0hGbKzsAOYlkuknCwJoqjxw8/QTCzNtM4vxxBl9qSs6iO2tLwuk5zG4TbrBFlLjUWZofZbW8re72aphL3W8Wcobk/5V2mt7B5QAb6aLyMbzvne8pPaLLvtO3j4eaTpY27/zGekeKyMxtTt8FiwoefAIh5HseCrNco2e05+Du1os0q5EjpgyMD7t8Cesn5cBRUXccqdj69+P3oUy19GWZ1oOYYfQlbDMnfvbxsRW993mpytaVkpR4ODiHH0Qtik2okNRb7EoR2dYQLRCnTqmTlGLlHQHUuVmiMxjenyZ7+f2DW0Dm1EEGzzI8QECi/bovBX8RWsMrR12Tnc1HkxfhYgGkanyJzgCbEVm/8TIkvjsHMHQfLsUCm1NXrwP3yZFhSWhXrCdnRPqA3G95rz+F/hfwqL71I3UBupcvPx0asPnKhen+dLdJViPU2ewLlTNuv6gQwgXy5Ts6/5Vnft4jk+ExD1bpQ/TaqLharu9IXc6Lc0c0WjzOEWQFCdktRQjw77CfZ4XIbi+l6dm3GbXQi9NSP7RDUcw/lFCdrHiIFhIiE68HYttZVuj5zYg6ek1/kFdgTDkKOTEKHR1N1cc89WYqbImwSxirkBHhIxm2qIZIHMa7mrJMkTG2FsNbdYyvwEefl7XaNqesaY6CaBmaBVh6BgtJQqvjGq6Lgi8vtO6UJduV9tLIqjKiNfYDCmlVZjuQgblbSW13HEkEqu8Bc77LuHt9jwuzGInovTcBgoOzq0YXMvt1V3p3XhfNupzbEpEGPFpHU6cAu2/X9llDqNF8g1MmKgOlpWO7FmJShm0xRRuVAXFv5D9Cr9YJjf54ESLVV+LpMZycP5ZRDmwAdwKuYI2oRfZCztg8ZOnSpCxXyVWqzNEEwoQPwAyFIt3hXQ5hrjTiSTNQvom45WCH3llzCJahmEVXLiKlwO3K6zDy3sqlGjVT0V2B9bU5eQC/I6i+mYzG43IiKXZG8977+uXkbOv/LRbdIQwvCioIi9qmCLsoggBSfAry4oL+SOqOpgjHqvnRGZey6ISZO+wUoki+QdrHh19mHV+rlUEQNKSN5KosLVgzpN7D8lUw0IGJBZ4RHPVZhEc9HEd4Kwne/rqP8w8j46MmVv7mP4IdU7KoBbkwGMKh3wZ/0MqwERfRynAaSG9HQJQVPc7oGQofT6Fyph8DIbgyYpxXH310/WmdEJQtdsc/Q5IDt9SA9SuCYzbWp++05SHm7zu9MLQm6DvVzhn6Kswkd41leM2php6hmyUgIITCyO9NWTgpXNQ/AIO84n0jydLQOb/pWAP1cHAOC3KT7uK79WrCNrBHhIrDK55wzXArifLfrEoq5ueAvNrr8JVo3FWqfmVRy/sXxWW9Ly3DTBbZcrhloif2g2PXU0f2A73sZ8j0gxwCJVufwQA6c+OOUwvmTM7j9FK2YPMmD7deL9mQGFzc8ICP2zrhSKyi3mMN3Do/kWYgCy3veCCYlElGuTLox0oxQq/njnPh+jHa7sSzRYeiNsX5KtfeecTSsUfri/f9yAbV7nZY4Q1cU2BpotGWoSltpUMDCm+Kpq5IYfx70hJpOStgeLV6OhxcOR2wjQBp4Yvcl1rVBICYCh7LT0foSg/JHtHQIsYvT19ZRJ9Xt6nqlqX4R+orh7SOlQsMdlL77QbCttXqPfcP7GK+14/npfTfBVmgm8TladrIs12DWQC8lqSWV+WWyEO9TH/EZWeOuBNbJ4mo9YA7q9fkq+xWJqe5R8fQaqVX4AHLQSPLdLGIY3CQe5IMIrsZD7wYEtlwSp3qO+rPVwBogvz+wSabkL3Hn/bBpjoahUL1WdeQ/AYXFmkHqT3RdjSDu/KmCSSHiqL/l+MvnplHMJfzNIjRihfWZDE5WkOIGe9X03KIOu87nD3+J1mtq2OZ4IJAp6DA9BcmX55iG1FysqEtC8qESUlzfl7G/xGP8WHzZ3m9b0YgJKRICKcZlximJmApUPIwaMkgBl5Pyiay+jc0KHk8mLdQwbbSk+XrAHPA5YP9IoyKUk3+nNoVAh3NAICpOu3KjA73DeppIxAKjbkjNNxQWKPrDI3bigZi1ZVCxjQ7gbOhxiQHUswaT1z0Pt6ViGiXUH7qf4Q9L6n9OS2w5BLZnlhiu8bjXsiUIca9r49ZZ2N8xS3epv9JZKSBeas3p+62O+lQUy8ZSCDC1fxzO1T4FmUoYetpKu0ZLsHndm3xXR56r2MARjUQ22tYhERK/2vK2Cfw9y0nBf/0PLVQhxoM5kzrryMhmYSDuJE1O3b9MggYPHEdaxiSu5vG5Hn5GtTgbnbZNldeiyds08BcSHajPsSOHvXhQ41nk312NcXe5IptLq77IWnfa/SPyJ0BBAERD8nytcvM9yeMFqUqrSR5c5XUy7wexTzBPLPlPuGzzJrMroICWsg5JxCJOVAemo+7cMmxKHK+AqDCxg6qG+m+gpDUSEKRw6gE5UNiQYe9rI14hMgrCZt00ikEx+AzOUC91bD/qQLe02hGFRPqYUJeT/T8U5Arcap6w== +pkX509 = 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 +sk = yCBUZVCkA6UiJYJdYS8gXsRQEnbPGZbvvVQWeWE9UoMwPgIBAQQwSCmFoWj68Zr/KCGPh/W9yQwiv0/Sa0HIqKhYdA0S5Nt4Kg+FjcZGjUJNK3aSxtGPoAcGBSuBBAAi +skPkcs8 = MHECAQAwCgYIKwYBBQUHBjEEYMggVGVQpAOlIiWCXWEvIF7EUBJ2zxmW771UFnlhPVKDMD4CAQEEMEgphaFo+vGa/yghj4f1vckMIr9P0mtByKioWHQNEuTbeCoPhY3GRo1CTSt2ksbRj6AHBgUrgQQAIg== +s = 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 + +name = TEST_13 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA87-RSA3072-PSS-SHA512 +pk = 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 +pkX509 = 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 +sk = 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 +skPkcs8 = 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 +s = 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 + +name = TEST_14 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA87-RSA4096-PSS-SHA512 +pk = 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 +pkX509 = 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 +sk = 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 +skPkcs8 = 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 +s = 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 + +name = TEST_15 +m = VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4= +tcId = id-MLDSA87-ECDSA-P521-SHA512 +pk = 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 +pkX509 = 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 +sk = i6WXRYb3bZgYjyZTAkTzGOG3RxqlKx7H5mvjE4sASjAwUAIBAQRCAPV/kCyalJFpSWFTmSR7inkTFwk15OcdfGNDHROW9GWcbS45POp6wX9XWcPb423uihFk2484/dlTJ3sdDpCXGgFgoAcGBSuBBAAj +skPkcs8 = MIGDAgEAMAoGCCsGAQUFBwY2BHKLpZdFhvdtmBiPJlMCRPMY4bdHGqUrHsfma+MTiwBKMDBQAgEBBEIA9X+QLJqUkWlJYVOZJHuKeRMXCTXk5x18Y0MdE5b0ZZxtLjk86nrBf1dZw9vjbe6KEWTbjzj92VMnex0OkJcaAWCgBwYFK4EEACM= +s = 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 diff --git a/constants/src/gen/cpp/generate_constants.cc b/constants/src/gen/cpp/generate_constants.cc index 7a9a5187c..7ea811031 100644 --- a/constants/src/gen/cpp/generate_constants.cc +++ b/constants/src/gen/cpp/generate_constants.cc @@ -40,10 +40,16 @@ static const char kCopyright[] = " * See the License for the specific language governing permissions and\n" " * limitations under the License. */\n"; -int main(int /* argc */, char** /* argv */) { +int main(int argc, char** argv) { + const char* package; + if (argc == 1) { + package = "org.conscrypt"; + } else { + package = argv[1]; + } printf("%s\n", kCopyright); printf("/* This file was generated by generate_constants.cc. */\n\n"); - printf("package org.conscrypt;\n\n"); + printf("package %s;\n\n", package); printf("final class NativeConstants {\n"); #define CONST(x) printf(" static final int %s = %ld;\n", #x, (long int)(x)) @@ -64,7 +70,6 @@ int main(int /* argc */, char** /* argv */) { CONST(NID_secp384r1); CONST(NID_secp521r1); CONST(NID_X25519MLKEM768); - CONST(NID_X25519Kyber768Draft00); CONST(NID_ML_KEM_1024); CONST(RSA_PKCS1_PADDING); diff --git a/constants/src/gen/java/README.md b/constants/src/gen/java/README.md new file mode 100644 index 000000000..a8d6dd355 --- /dev/null +++ b/constants/src/gen/java/README.md @@ -0,0 +1,7 @@ +# Updating the blocklist based on Chromium's source code + +1. Copy the latest version of `cert_verify_proc_blocklist.inc` from [Chromium's + repository](https://source.chromium.org/chromium/chromium/src/+/main:net/cert/cert_verify_proc_blocklist.inc) + into this directory. +2. Build, flash and run: `atest CtsLibcoreTestCases`. + diff --git a/constants/src/gen/java/cert_verify_proc_blocklist.inc b/constants/src/gen/java/cert_verify_proc_blocklist.inc new file mode 100644 index 000000000..3e6dcdb5d --- /dev/null +++ b/constants/src/gen/java/cert_verify_proc_blocklist.inc @@ -0,0 +1,447 @@ +// Copyright 2016 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// The certificate(s) that were misissued, and which represent these SPKIs, +// are stored within net/data/ssl/blocklist. Further details about the +// rationale is documented in net/data/ssl/blocklist/README.md +static constexpr uint8_t + kSPKIBlockList[][crypto::kSHA256Length] = { + // 2740d956b1127b791aa1b3cc644a4dbedba76186a23638b95102351a834ea861.pem + {0x04, 0xdd, 0xe9, 0xaa, 0x9a, 0x79, 0xf6, 0x14, 0x98, 0x68, 0x23, + 0x25, 0xfa, 0x08, 0x70, 0x27, 0x67, 0x07, 0xfb, 0x9c, 0xa9, 0x53, + 0x84, 0x12, 0x0b, 0x46, 0x89, 0x32, 0x68, 0x49, 0x4f, 0xc9}, + // 91e5cc32910686c5cac25c18cc805696c7b33868c280caf0c72844a2a8eb91e2.pem + {0x0c, 0x43, 0xea, 0x8b, 0xcd, 0xe9, 0xfc, 0x3b, 0xca, 0x16, 0x56, + 0x64, 0xac, 0x82, 0x15, 0x56, 0x7e, 0x34, 0x89, 0xd5, 0x39, 0x3a, + 0x0c, 0x81, 0xe1, 0xa7, 0x91, 0x41, 0x99, 0x2e, 0x19, 0x53}, + // ead610e6e90b439f2ecb51628b0932620f6ef340bd843fca38d3181b8f4ba197.pem + {0x12, 0x13, 0x23, 0x60, 0xa3, 0x3b, 0xfd, 0xc6, 0xc3, 0xbf, 0x7b, + 0x7f, 0xab, 0x26, 0xa1, 0x68, 0x48, 0x74, 0xe7, 0x2c, 0x12, 0x63, + 0xc1, 0xf5, 0xde, 0x56, 0x5b, 0xb4, 0x9e, 0xf0, 0x37, 0x53}, + // 4bf6bb839b03b72839329b4ea70bb1b2f0d07e014d9d24aa9cc596114702bee3.pem + {0x12, 0x7d, 0xa2, 0x7a, 0x9e, 0x45, 0xf0, 0x82, 0x28, 0x0b, 0x31, + 0xbf, 0x1e, 0x56, 0x15, 0x20, 0x38, 0x9f, 0x96, 0x65, 0x90, 0x93, + 0xb2, 0x69, 0x7c, 0x40, 0xfe, 0x86, 0x00, 0x23, 0x6c, 0x8c}, + // 0f912fd7be760be25afbc56bdc09cd9e5dcc9c6f6a55a778aefcb6aa30e31554.pem + {0x13, 0x0a, 0xd4, 0xe0, 0x63, 0x35, 0x21, 0x29, 0x05, 0x31, 0xb6, + 0x65, 0x1f, 0x57, 0x59, 0xb0, 0xbc, 0x7b, 0xc6, 0x56, 0x70, 0x9f, + 0xf8, 0xf3, 0x65, 0xc2, 0x14, 0x3b, 0x03, 0x89, 0xb6, 0xf6}, + // 91018fcd3e0dc73f48d011a123f604d846d66821c58304474f949d7449dd600a.pem + {0x15, 0xe7, 0xae, 0x40, 0xcc, 0x4b, 0x3f, 0x72, 0x22, 0xa5, 0xa6, + 0xfe, 0x3e, 0x7d, 0xc4, 0x7f, 0x6e, 0x46, 0xee, 0x9a, 0x22, 0x51, + 0x83, 0x9d, 0xb2, 0x96, 0xd6, 0x2a, 0xda, 0x2a, 0x0d, 0xf7}, + // c7ba6567de93a798ae1faa791e712d378fae1f93c4397fea441bb7cbe6fd5995.pem + {0x15, 0x28, 0x39, 0x7d, 0xa2, 0x12, 0x89, 0x0a, 0x83, 0x0b, 0x0b, + 0x95, 0xa5, 0x99, 0x68, 0xce, 0xf2, 0x34, 0x77, 0x37, 0x79, 0xdf, + 0x51, 0x81, 0xcf, 0x10, 0xfa, 0x64, 0x75, 0x34, 0xbb, 0x65}, + // 1af56c98ff043ef92bebff54cebb4dd67a25ba956c817f3e6dd3c1e52eb584c1.key + {0x1a, 0xf5, 0x6c, 0x98, 0xff, 0x04, 0x3e, 0xf9, 0x2b, 0xeb, 0xff, + 0x54, 0xce, 0xbb, 0x4d, 0xd6, 0x7a, 0x25, 0xba, 0x95, 0x6c, 0x81, + 0x7f, 0x3e, 0x6d, 0xd3, 0xc1, 0xe5, 0x2e, 0xb5, 0x84, 0xc1}, + // e28393773da845a679f2080cc7fb44a3b7a1c3792cb7eb7729fdcb6a8d99aea7.pem + {0x1f, 0x42, 0x24, 0xce, 0xc8, 0x4f, 0xc9, 0x9c, 0xed, 0x88, 0x1f, + 0xf6, 0xfc, 0xfd, 0x3e, 0x21, 0xf8, 0xc5, 0x19, 0xc5, 0x47, 0xaa, + 0x6a, 0x5d, 0xd3, 0xde, 0x24, 0x73, 0x02, 0xce, 0x50, 0xd1}, + // e54e9fc27e7350ff63a77764a40267b7e95ae5df3ed7df5336e8f8541356c845.pem + {0x25, 0xda, 0x1a, 0xd5, 0x8b, 0xbf, 0xcf, 0xb2, 0x27, 0xd8, 0x72, + 0x3b, 0x18, 0x57, 0xd4, 0xc1, 0x8e, 0x7b, 0xaa, 0x74, 0x17, 0xb4, + 0xf9, 0xef, 0xf9, 0x36, 0x6b, 0x5e, 0x86, 0x9f, 0x8b, 0x39}, + // 159ca03a88897c8f13817a212629df84ce824709492b8c9adb8e5437d2fc72be.pem + {0x2c, 0x99, 0x8e, 0x76, 0x11, 0x60, 0xc3, 0xb0, 0x6d, 0x82, 0xfa, + 0xa9, 0xfd, 0xc7, 0x54, 0x5d, 0x9b, 0xda, 0x9e, 0xb6, 0x03, 0x10, + 0xf9, 0x92, 0xaa, 0x51, 0x0a, 0x62, 0x80, 0xb7, 0x42, 0x45}, + // 82a4cedbc7f61ce5cb04482aa27ea3145bb0cea58ab63ba1931a1654bfbdbb4f.pem + {0x2d, 0xc4, 0xcb, 0x59, 0x1f, 0x7e, 0xf0, 0x66, 0x34, 0x41, 0x64, + 0x6b, 0xcf, 0x5c, 0x0e, 0x9d, 0xbc, 0xde, 0xd7, 0x7c, 0xa0, 0x29, + 0x45, 0x19, 0x3c, 0xef, 0xc6, 0xed, 0xb1, 0x74, 0x06, 0x14}, + // d0d672c2547d574ae055d9e78a993ddbcc74044c4253fbfaca573a67d368e1db.pem + {0x30, 0xef, 0xe4, 0x13, 0x82, 0x47, 0x6c, 0x33, 0x80, 0xf0, 0x2f, + 0x7e, 0x23, 0xe6, 0x6b, 0xa2, 0xf8, 0x67, 0xb0, 0x59, 0xee, 0x1e, + 0xa6, 0x87, 0x96, 0xb4, 0x41, 0xb8, 0x5b, 0x5d, 0x12, 0x56}, + // 32ecc96f912f96d889e73088cd031c7ded2c651c805016157a23b6f32f798a3b.key + {0x32, 0xec, 0xc9, 0x6f, 0x91, 0x2f, 0x96, 0xd8, 0x89, 0xe7, 0x30, + 0x88, 0xcd, 0x03, 0x1c, 0x7d, 0xed, 0x2c, 0x65, 0x1c, 0x80, 0x50, + 0x16, 0x15, 0x7a, 0x23, 0xb6, 0xf3, 0x2f, 0x79, 0x8a, 0x3b}, + // 4aefc3d39ef59e4d4b0304b20f53a8af2efb69edece66def74494abfc10a2d66.pem + {0x36, 0xea, 0x96, 0x12, 0x8c, 0x89, 0x83, 0x9f, 0xb6, 0x21, 0xf8, + 0xad, 0x0e, 0x1e, 0xe0, 0xb9, 0xc2, 0x20, 0x6f, 0x62, 0xab, 0x7b, + 0x4d, 0xa2, 0xc6, 0x76, 0x58, 0x93, 0xc9, 0xb7, 0xce, 0xd2}, + // d487a56f83b07482e85e963394c1ecc2c9e51d0903ee946b02c301581ed99e16.pem + {0x38, 0x1a, 0x3f, 0xc7, 0xa8, 0xb0, 0x82, 0xfa, 0x28, 0x61, 0x3a, + 0x4d, 0x07, 0xf2, 0xc7, 0x55, 0x3f, 0x4e, 0x19, 0x18, 0xee, 0x07, + 0xca, 0xa9, 0xe8, 0xb7, 0xce, 0xde, 0x5a, 0x9c, 0xa0, 0x6a}, + // 0ef7c54a3af101a2cfedb0c9f36fe8214d51a504fdc2ad1e243019cefd7d03c2.pem + {0x38, 0x3e, 0x0e, 0x13, 0x7c, 0x37, 0xbf, 0xb9, 0xdb, 0x29, 0xf9, + 0xa8, 0xe4, 0x5e, 0x9f, 0xf8, 0xdd, 0x4c, 0x30, 0xe4, 0x40, 0xfe, + 0xc2, 0xac, 0xd3, 0xdb, 0xa7, 0xb6, 0xc7, 0x20, 0xb9, 0x93}, + // cb954e9d80a3e520ac71f1a84511657f2f309d172d0bb55e0ec2c236e74ff4b4.pem + {0x39, 0x4c, 0xff, 0x58, 0x9e, 0x68, 0x93, 0x12, 0xcf, 0xc0, 0x71, + 0xee, 0x0b, 0xc1, 0x9f, 0xe4, 0xc6, 0x06, 0x21, 0x6c, 0xe5, 0x43, + 0x42, 0x9d, 0xe6, 0xdb, 0x62, 0xe4, 0x2d, 0xbb, 0x3b, 0xc1}, + // 42187727be39faf667aeb92bf0cc4e268f6e2ead2cefbec575bdc90430024f69.pem + {0x3e, 0xdb, 0xd9, 0xac, 0xe6, 0x39, 0xba, 0x1a, 0x2d, 0x4a, 0xd0, + 0x47, 0x18, 0x71, 0x1f, 0xda, 0x23, 0xe8, 0x59, 0xb2, 0xfb, 0xf5, + 0xd1, 0x37, 0xd4, 0x24, 0x04, 0x5e, 0x79, 0x19, 0xdf, 0xb9}, + // 294f55ef3bd7244c6ff8a68ab797e9186ec27582751a791515e3292e48372d61.pem + {0x45, 0x5b, 0x87, 0xe9, 0x6f, 0x1c, 0xea, 0x2f, 0x8b, 0x6d, 0xae, + 0x08, 0x08, 0xec, 0x24, 0x73, 0x8f, 0xd9, 0x2b, 0x7f, 0xd3, 0x06, + 0x75, 0x71, 0x98, 0xbf, 0x38, 0x9d, 0x75, 0x5c, 0x0b, 0x6c}, + // 3ab0fcc7287454c405863e3aa204fea8eb0c50a524d2a7e15524a830cd4ab0fe.pem + {0x49, 0x0b, 0x6e, 0xc6, 0xbe, 0xb2, 0xd6, 0x03, 0x47, 0x20, 0xb5, + 0x14, 0x9b, 0x6b, 0x29, 0xcd, 0x35, 0x51, 0x59, 0x88, 0xcc, 0x16, + 0xaf, 0x85, 0x41, 0x48, 0xb0, 0x7b, 0x9b, 0x1f, 0x8a, 0x11}, + // b6fe9151402bad1c06d7e66db67a26aa7356f2e6c644dbcf9f98968ff632e1b7.pem + {0x4b, 0xb8, 0xf3, 0x5b, 0xa1, 0xe1, 0x26, 0xf8, 0xdd, 0xe1, 0xb0, + 0xc4, 0x20, 0x62, 0x5e, 0xd8, 0x6d, 0xce, 0x61, 0xa7, 0xbd, 0xda, + 0xdb, 0xde, 0xa9, 0xab, 0xa5, 0x78, 0xff, 0x13, 0x14, 0x5e}, + // fa5a828c9a7e732692682e60b14c634309cbb2bb79eb12aef44318d853ee97e3.pem + {0x4c, 0xdb, 0x06, 0x0f, 0x3c, 0xfe, 0x4c, 0x3d, 0x3f, 0x5e, 0x31, + 0xc3, 0x00, 0xfd, 0x68, 0xa9, 0x1e, 0x0d, 0x1e, 0x5f, 0x46, 0xb6, + 0x4e, 0x48, 0x95, 0xf2, 0x0e, 0x1b, 0x5c, 0xf8, 0x26, 0x9f}, + // ef3cb417fc8ebf6f97876c9e4ece39de1ea5fe649141d1028b7d11c0b2298ced.pem + {0x4e, 0xad, 0xa9, 0xb5, 0x31, 0x1e, 0x71, 0x81, 0x99, 0xd9, 0x8e, + 0xa8, 0x2b, 0x95, 0x00, 0x5c, 0xba, 0x93, 0x19, 0x8a, 0xb1, 0xf9, + 0x7e, 0xfc, 0xbe, 0x8d, 0xc6, 0x20, 0x16, 0x28, 0xf8, 0xaf}, + // c1d80ce474a51128b77e794a98aa2d62a0225da3f419e5c7ed73dfbf660e7109.pem + {0x4f, 0x71, 0x62, 0xb9, 0x74, 0x49, 0x1c, 0x98, 0x58, 0x5e, 0xc2, + 0x8f, 0xe7, 0x59, 0xaa, 0x00, 0xc3, 0x30, 0xd0, 0xb4, 0x65, 0x19, + 0x0a, 0x89, 0x6c, 0xc4, 0xb6, 0x16, 0x23, 0x18, 0x31, 0xfc}, + // 7abd72a323c9d179c722564f4e27a51dd4afd24006b38a40ce918b94960bcf18.pem + {0x57, 0x80, 0x94, 0x46, 0xea, 0xf1, 0x14, 0x84, 0x38, 0x54, 0xfe, + 0x63, 0x6e, 0xd9, 0xbc, 0xb5, 0x52, 0xe3, 0xc6, 0x16, 0x66, 0x3b, + 0xc4, 0x4c, 0xc9, 0x5a, 0xcf, 0x56, 0x50, 0x01, 0x6d, 0x3e}, + // 817d4e05063d5942869c47d8504dc56a5208f7569c3d6d67f3457cfe921b3e29.pem + {0x5c, 0x72, 0x2c, 0xb7, 0x0f, 0xb3, 0x11, 0xf2, 0x1e, 0x0d, 0xa0, + 0xe7, 0xd1, 0x2e, 0xbc, 0x8e, 0x05, 0xf6, 0x07, 0x96, 0xbc, 0x49, + 0xcf, 0x51, 0x18, 0x49, 0xd5, 0xbc, 0x62, 0x03, 0x03, 0x82}, + // 79f69a47cfd6c4b4ceae8030d04b49f6171d3b5d6c812f58d040e586f1cb3f14.pem + // 933f7d8cda9f0d7c8bfd3c22bf4653f4161fd38ccdcf66b22e95a2f49c2650f8.pem + // f8a5ff189fedbfe34e21103389a68340174439ad12974a4e8d4d784d1f3a0faa.pem + {0x5e, 0x53, 0xf2, 0x64, 0x67, 0xf8, 0x94, 0xfd, 0xe5, 0x3b, 0x3f, + 0xa4, 0x06, 0xa4, 0x40, 0xcb, 0xb3, 0xb0, 0x76, 0xbb, 0x5b, 0x75, + 0x8f, 0xe4, 0x83, 0x4a, 0xd6, 0x65, 0x00, 0x20, 0x89, 0x07}, + // 2d11e736f0427fd6ba4b372755d34a0edd8d83f7e9e7f6c01b388c9b7afa850d.pem + {0x6a, 0xdb, 0x8e, 0x3e, 0x05, 0x54, 0x60, 0x92, 0x2d, 0x15, 0x01, + 0xcb, 0x97, 0xf9, 0x4c, 0x6a, 0x02, 0xe3, 0x9c, 0x8f, 0x27, 0x74, + 0xca, 0x40, 0x88, 0x25, 0xb7, 0xb5, 0x83, 0x79, 0xdc, 0x14}, + // 2a33f5b48176523fd3c0d854f20093417175bfd498ef354cc7f38b54adabaf1a.pem + {0x70, 0x7d, 0x36, 0x4e, 0x72, 0xae, 0x52, 0x14, 0x31, 0xdd, 0x95, + 0x38, 0x97, 0xf9, 0xc4, 0x84, 0x6d, 0x5b, 0x8c, 0x32, 0x42, 0x98, + 0xfe, 0x53, 0xfb, 0xd4, 0xad, 0xa1, 0xf2, 0xd1, 0x15, 0x7f}, + // f4a5984324de98bd979ef181a100cf940f2166173319a86a0d9d7c8fac3b0a8f.pem + {0x71, 0x65, 0xe9, 0x91, 0xad, 0xe7, 0x91, 0x6d, 0x86, 0xb4, 0x66, + 0xab, 0xeb, 0xb6, 0xe4, 0x57, 0xca, 0x93, 0x1c, 0x80, 0x4e, 0x58, + 0xce, 0x1f, 0xba, 0xba, 0xe5, 0x09, 0x15, 0x6f, 0xfb, 0x43}, + // 3ae699d94e8febdacb86d4f90d40903333478e65e0655c432451197e33fa07f2.pem + {0x78, 0x1a, 0x4c, 0xf2, 0xe9, 0x24, 0x52, 0xf3, 0xee, 0x01, 0xd0, + 0xc3, 0x81, 0xa4, 0x21, 0x4f, 0x39, 0x04, 0x16, 0x5c, 0x39, 0x0a, + 0xdb, 0xd6, 0x1f, 0xcd, 0x11, 0x24, 0x4e, 0x09, 0xb2, 0xdc}, + // 8b45da1c06f791eb0cabf26be588f5fb23165c2e614bf885562d0dce50b29b02.pem + {0x7a, 0xed, 0xdd, 0xf3, 0x6b, 0x18, 0xf8, 0xac, 0xb7, 0x37, 0x9f, + 0xe1, 0xce, 0x18, 0x32, 0x12, 0xb2, 0x35, 0x0d, 0x07, 0x88, 0xab, + 0xe0, 0xe8, 0x24, 0x57, 0xbe, 0x9b, 0xad, 0xad, 0x6d, 0x54}, + // 5a885db19c01d912c5759388938cafbbdf031ab2d48e91ee15589b42971d039c.pem + {0x7a, 0xfe, 0x4b, 0x07, 0x1a, 0x2f, 0x1f, 0x46, 0xf8, 0xba, 0x94, + 0x4a, 0x26, 0xd5, 0x84, 0xd5, 0x96, 0x0b, 0x92, 0xfb, 0x48, 0xc3, + 0xba, 0x1b, 0x7c, 0xab, 0x84, 0x90, 0x5f, 0x32, 0xaa, 0xcd}, + // c43807a64c51a3fbde5421011698013d8b46f4e315c46186dc23aea2670cd34f.pem + {0x7c, 0xd2, 0x95, 0xb7, 0x55, 0x44, 0x80, 0x8a, 0xbd, 0x94, 0x09, + 0x46, 0x6f, 0x08, 0x37, 0xc5, 0xaa, 0xdc, 0x02, 0xe3, 0x3b, 0x61, + 0x50, 0xc6, 0x64, 0x4d, 0xe0, 0xa0, 0x96, 0x59, 0xf2, 0x3c}, + // f3bae5e9c0adbfbfb6dbf7e04e74be6ead3ca98a5604ffe591cea86c241848ec.pem + {0x7d, 0x5e, 0x3f, 0x50, 0x50, 0x81, 0x97, 0xb9, 0xa4, 0x78, 0xb1, + 0x13, 0x40, 0xb7, 0xdc, 0xe2, 0x0a, 0x3c, 0x4d, 0xe4, 0x9c, 0x48, + 0xc9, 0xa2, 0x94, 0x15, 0x8a, 0x89, 0x5c, 0x44, 0xa2, 0x1b}, + // b8686723e415534bc0dbd16326f9486f85b0b0799bf6639334e61daae67f36cd.pem + {0x7e, 0x70, 0x58, 0xea, 0x35, 0xad, 0x43, 0x59, 0x65, 0x41, 0x59, + 0x97, 0x3f, 0x56, 0x01, 0x87, 0xf1, 0x6d, 0x19, 0xc5, 0x14, 0xb9, + 0x39, 0xc5, 0x05, 0x56, 0x72, 0xd1, 0xd2, 0xa5, 0x18, 0xac}, + // 5e8e77aafdda2ba5ce442f27d8246650bbd6508befbeda35966a4dc7e6174edc.pem + {0x87, 0xbf, 0xd8, 0xaf, 0xa3, 0xaf, 0x5b, 0x42, 0x9d, 0x09, 0xa9, + 0xaa, 0x54, 0xee, 0x61, 0x36, 0x4f, 0x5a, 0xe1, 0x11, 0x31, 0xe4, + 0x38, 0xfc, 0x41, 0x09, 0x53, 0x43, 0xcd, 0x16, 0xb1, 0x35}, + // 0c258a12a5674aef25f28ba7dcfaeceea348e541e6f5cc4ee63b71b361606ac3.pem + {0x8a, 0x2a, 0xff, 0xbd, 0x1a, 0x1c, 0x5d, 0x1b, 0xdc, 0xcb, 0xb7, + 0xf5, 0x48, 0xba, 0x99, 0x5f, 0x96, 0x68, 0x06, 0xb3, 0xfd, 0x0c, + 0x3a, 0x00, 0xfa, 0xe2, 0xe5, 0x2f, 0x3c, 0x85, 0x39, 0x89}, + // 61c0fc2e38b5b6f9071b42cee54a9013d858b6697c68b460948551b3249576a1.pem + {0x8e, 0x12, 0xd0, 0xcb, 0x3b, 0x7d, 0xf3, 0xea, 0x22, 0x57, 0x57, + 0x94, 0x89, 0xfd, 0x86, 0x58, 0xc9, 0x56, 0x03, 0xea, 0x6c, 0xf4, + 0xb7, 0x31, 0x63, 0xa4, 0x1e, 0xb7, 0xb7, 0xe9, 0x3f, 0xee}, + // ddd8ab9178c99cbd9685ea4ae66dc28bfdc9a5a8a166f7f69ad0b5042ad6eb28.pem + {0x8f, 0x59, 0x1f, 0x7a, 0xa4, 0xdc, 0x3e, 0xfe, 0x94, 0x90, 0xc3, + 0x8a, 0x46, 0x92, 0xc9, 0x01, 0x1e, 0xd1, 0x28, 0xf1, 0xde, 0x59, + 0x55, 0x69, 0x40, 0x6d, 0x77, 0xb6, 0xfa, 0x1f, 0x6b, 0x4c}, + // 136335439334a7698016a0d324de72284e079d7b5220bb8fbd747816eebebaca.pem + {0x92, 0x7a, 0x1b, 0x85, 0x62, 0x28, 0x05, 0x76, 0xd0, 0x48, 0xc5, + 0x03, 0x21, 0xad, 0xa4, 0x3d, 0x87, 0x03, 0xd2, 0xd9, 0x52, 0x1a, + 0x18, 0xc2, 0x8b, 0x8c, 0x46, 0xcc, 0x6a, 0xae, 0x4e, 0xfd}, + // 450f1b421bb05c8609854884559c323319619e8b06b001ea2dcbb74a23aa3be2.pem + {0x93, 0xca, 0x2d, 0x43, 0x6c, 0xae, 0x7f, 0x68, 0xd2, 0xb4, 0x25, + 0x6c, 0xa1, 0x75, 0xc9, 0x85, 0xce, 0x39, 0x92, 0x6d, 0xc9, 0xf7, + 0xee, 0xae, 0xec, 0xf2, 0xf8, 0x97, 0x0f, 0xb9, 0x78, 0x02}, + // e757fd60d8dd4c26f77aca6a87f63ea4d38d0b736c7f79b56cad932d4c400fb5.pem + {0x96, 0x2e, 0x4b, 0x54, 0xbb, 0x98, 0xa7, 0xee, 0x5d, 0x5f, 0xeb, + 0x96, 0x33, 0xf9, 0x91, 0xd3, 0xc3, 0x30, 0x0e, 0x95, 0x14, 0xda, + 0xde, 0x7b, 0x0d, 0x4f, 0x82, 0x8c, 0x79, 0x4f, 0x8e, 0x87}, + // 3d3d823fad13dfeef32da580166d4a4992bed5a22d695d12c8b08cc3463c67a2.pem + {0x96, 0x8d, 0xba, 0x69, 0xfb, 0xff, 0x15, 0xbf, 0x37, 0x62, 0x08, + 0x94, 0x31, 0xad, 0xe5, 0xa7, 0xea, 0xd4, 0xb7, 0xea, 0xf1, 0xbe, + 0x70, 0x02, 0x68, 0x10, 0xbc, 0x57, 0xd1, 0xc6, 0x4f, 0x6e}, + // 1f17f2cbb109f01c885c94d9e74a48625ae9659665d6d7e7bc5a10332976370f.pem + {0x99, 0xba, 0x47, 0x84, 0xf9, 0xb0, 0x85, 0x12, 0x90, 0x2e, 0xb0, + 0xc3, 0xc8, 0x6d, 0xf0, 0xec, 0x04, 0x9e, 0xac, 0x9b, 0x65, 0xf7, + 0x7a, 0x9b, 0xa4, 0x2b, 0xe9, 0xd6, 0xeb, 0xce, 0x32, 0x0f}, + // a8e1dfd9cd8e470aa2f443914f931cfd61c323e94d75827affee985241c35ce5.pem + {0x9b, 0x8a, 0x93, 0xde, 0xcc, 0xcf, 0xba, 0xfc, 0xf4, 0xd0, 0x4d, + 0x34, 0x42, 0x12, 0x8f, 0xb3, 0x52, 0x18, 0xcf, 0xe4, 0x37, 0xa3, + 0xd8, 0xd0, 0x32, 0x8c, 0x99, 0xf8, 0x90, 0x89, 0xe4, 0x50}, + // 8253da6738b60c5c0bb139c78e045428a0c841272abdcb952f95ff05ed1ab476.pem + {0x9c, 0x59, 0xa3, 0xcc, 0xae, 0xa4, 0x69, 0x98, 0x42, 0xb0, 0x68, + 0xcf, 0xc5, 0x2c, 0xf9, 0x45, 0xdb, 0x51, 0x98, 0x69, 0x57, 0xc8, + 0x32, 0xcd, 0xb1, 0x8c, 0xa7, 0x38, 0x49, 0xfb, 0xb9, 0xee}, + // 7d8ce822222b90c0b14342c7a8145d1f24351f4d1a1fe0edfd312ee73fb00149.pem + {0x9d, 0x98, 0xa1, 0xfb, 0x60, 0x53, 0x8c, 0x4c, 0xc4, 0x85, 0x7f, + 0xf1, 0xa8, 0xc8, 0x03, 0x4f, 0xaf, 0x6f, 0xc5, 0x92, 0x09, 0x3f, + 0x61, 0x99, 0x94, 0xb2, 0xc8, 0x13, 0xd2, 0x50, 0xb8, 0x64}, + // 1c01c6f4dbb2fefc22558b2bca32563f49844acfc32b7be4b0ff599f9e8c7af7.pem + {0x9d, 0xd5, 0x5f, 0xc5, 0x73, 0xf5, 0x46, 0xcb, 0x6a, 0x38, 0x31, + 0xd1, 0x11, 0x2d, 0x87, 0x10, 0xa6, 0xf4, 0xf8, 0x2d, 0xc8, 0x7f, + 0x5f, 0xae, 0x9d, 0x3a, 0x1a, 0x02, 0x8d, 0xd3, 0x6e, 0x4b}, + // 487afc8d0d411b2a05561a2a6f35918f4040e5570c4c73ee323cc50583bcfbb7.pem + {0xa0, 0xcf, 0x53, 0xf4, 0x22, 0x65, 0x1e, 0x39, 0x31, 0x7a, 0xe3, + 0x1a, 0xf6, 0x45, 0x77, 0xbe, 0x45, 0x0f, 0xa3, 0x76, 0xe2, 0x89, + 0xed, 0x83, 0x42, 0xb7, 0xfc, 0x13, 0x3c, 0x69, 0x74, 0x19}, + // 0d136e439f0ab6e97f3a02a540da9f0641aa554e1d66ea51ae2920d51b2f7217.pem + // 4fee0163686ecbd65db968e7494f55d84b25486d438e9de558d629d28cd4d176.pem + // 8a1bd21661c60015065212cc98b1abb50dfd14c872a208e66bae890f25c448af.pem + {0xa9, 0x03, 0xaf, 0x8c, 0x07, 0xbb, 0x91, 0xb0, 0xd9, 0xe3, 0xf3, + 0xa3, 0x0c, 0x6d, 0x53, 0x33, 0x9f, 0xc5, 0xbd, 0x47, 0xe5, 0xd6, + 0xbd, 0xb4, 0x76, 0x59, 0x88, 0x60, 0xc0, 0x68, 0xa0, 0x24}, + // a2e3bdaacaaf2d2e8204b3bc7eddc805d54d3ab8bdfe7bf102c035f67d8f898a.pem + {0xa9, 0xb5, 0x5a, 0x9b, 0x55, 0x31, 0xbb, 0xf7, 0xc7, 0x1a, 0x1e, + 0x49, 0x20, 0xef, 0xe7, 0x96, 0xc2, 0xb6, 0x79, 0x68, 0xf5, 0x5a, + 0x6c, 0xe5, 0xcb, 0x62, 0x17, 0x2e, 0xd9, 0x94, 0x5b, 0xca}, + // 5472692abe5d02cd22eae3e0a0077f17802721d6576cde1cba2263ee803410c5.pem + {0xaf, 0x59, 0x15, 0x18, 0xe2, 0xe6, 0xc6, 0x0e, 0xbb, 0xfc, 0x09, + 0x07, 0xaf, 0xaa, 0x49, 0xbc, 0x40, 0x51, 0xd4, 0x5e, 0x7f, 0x21, + 0x4a, 0xbf, 0xee, 0x75, 0x12, 0xee, 0x00, 0xf6, 0x61, 0xed}, + // 1df696f021ab1c3ace9a376b07ed7256a40214cd3396d7934087614924e2d7ef.pem + {0xb1, 0x3f, 0xa2, 0xe6, 0x13, 0x1a, 0x88, 0x8a, 0x01, 0xf3, 0xd6, + 0x20, 0x56, 0xfb, 0x0e, 0xfb, 0xe9, 0x99, 0xeb, 0x6b, 0x6e, 0x14, + 0x92, 0x76, 0x13, 0xe0, 0x2b, 0xa8, 0xb8, 0xfb, 0x04, 0x6e}, + // b8c1b957c077ea76e00b0f45bff5ae3acb696f221d2e062164fe37125e5a8d25.pem + {0xb3, 0x18, 0x2e, 0x28, 0x9a, 0xe3, 0x4d, 0xdf, 0x2b, 0xe6, 0x43, + 0xab, 0x79, 0xc2, 0x44, 0x30, 0x16, 0x05, 0xfa, 0x0f, 0x1e, 0xaa, + 0xe6, 0xd1, 0x0f, 0xb9, 0x29, 0x60, 0x0a, 0xf8, 0x4d, 0xf0}, + // be144b56fb1163c49c9a0e6b5a458df6b29f7e6449985960c178a4744624b7bc.pem + {0xb4, 0xd5, 0xc9, 0x20, 0x41, 0x5e, 0xd0, 0xcc, 0x4f, 0x5d, 0xbc, + 0x7f, 0x54, 0x26, 0x36, 0x76, 0x2e, 0x80, 0xda, 0x66, 0x25, 0xf3, + 0x3f, 0x2b, 0x6a, 0xd6, 0xdb, 0x68, 0xbd, 0xba, 0xb2, 0x9a}, + // 00309c736dd661da6f1eb24173aa849944c168a43a15bffd192eecfdb6f8dbd2.pem + {0xb5, 0xba, 0x8d, 0xd7, 0xf8, 0x95, 0x64, 0xc2, 0x88, 0x9d, 0x3d, + 0x64, 0x53, 0xc8, 0x49, 0x98, 0xc7, 0x78, 0x24, 0x91, 0x9b, 0x64, + 0xea, 0x08, 0x35, 0xaa, 0x62, 0x98, 0x65, 0x91, 0xbe, 0x50}, + // 04f1bec36951bc1454a904ce32890c5da3cde1356b7900f6e62dfa2041ebad51.pem + {0xb8, 0x9b, 0xcb, 0xb8, 0xac, 0xd4, 0x74, 0xc1, 0xbe, 0xa7, 0xda, + 0xd6, 0x50, 0x37, 0xf4, 0x8d, 0xce, 0xcc, 0x9d, 0xfa, 0xa0, 0x61, + 0x2c, 0x3c, 0x24, 0x45, 0x95, 0x64, 0x19, 0xdf, 0x32, 0xfe}, + // d8888f4a84f74c974dffb573a1bf5bbbacd1713b905096f8eb015062bf396c4d.pem + {0xc0, 0xed, 0x20, 0x53, 0x46, 0xbb, 0xbd, 0xe0, 0x6e, 0xb5, 0x60, + 0xf5, 0xce, 0xe0, 0x2a, 0x36, 0x34, 0xe2, 0x47, 0x4a, 0x7e, 0x76, + 0xcf, 0x8f, 0xbe, 0xf5, 0x63, 0xbb, 0x11, 0x7d, 0xd0, 0xe3}, + // 372447c43185c38edd2ce0e9c853f9ac1576ddd1704c2f54d96076c089cb4227.pem + {0xc1, 0x73, 0xf0, 0x62, 0x64, 0x56, 0xca, 0x85, 0x4f, 0xf2, 0xa7, + 0xf0, 0xb1, 0x33, 0xa7, 0xcf, 0x4d, 0x02, 0x11, 0xe5, 0x52, 0xf2, + 0x4b, 0x3e, 0x33, 0xad, 0xe8, 0xc5, 0x9f, 0x0a, 0x42, 0x4c}, + // c4387d45364a313fbfe79812b35b815d42852ab03b06f11589638021c8f2cb44.key + {0xc4, 0x38, 0x7d, 0x45, 0x36, 0x4a, 0x31, 0x3f, 0xbf, 0xe7, 0x98, + 0x12, 0xb3, 0x5b, 0x81, 0x5d, 0x42, 0x85, 0x2a, 0xb0, 0x3b, 0x06, + 0xf1, 0x15, 0x89, 0x63, 0x80, 0x21, 0xc8, 0xf2, 0xcb, 0x44}, + // 8290cc3fc1c3aac3239782c141ace8f88aeef4e9576a43d01867cf19d025be66.pem + // 9532e8b504964331c271f3f5f10070131a08bf8ba438978ce394c34feeae246f.pem + {0xc6, 0x01, 0x23, 0x4e, 0x2b, 0x93, 0x25, 0xdc, 0x92, 0xe3, 0xea, + 0xba, 0xc1, 0x96, 0x00, 0xb0, 0xb4, 0x99, 0x47, 0xd4, 0xd0, 0x4d, + 0x8c, 0x99, 0xd3, 0x21, 0x27, 0x49, 0x3e, 0xa0, 0x28, 0xf8}, + // 0753e940378c1bd5e3836e395daea5cb839e5046f1bd0eae1951cf10fec7c965.pem + {0xc6, 0x3d, 0x68, 0xc6, 0x48, 0xa1, 0x8b, 0x77, 0x64, 0x1c, 0x42, + 0x7a, 0x66, 0x9d, 0x61, 0xc9, 0x76, 0x8a, 0x55, 0xf4, 0xfc, 0xd0, + 0x32, 0x2e, 0xac, 0x96, 0xc5, 0x77, 0x00, 0x29, 0x9c, 0xf1}, + // 53d48e7b8869a3314f213fd2e0178219ca09022dbe50053bf6f76fccd61e8112.pem + {0xc8, 0xfd, 0xdc, 0x75, 0xcb, 0x1b, 0xdb, 0xb5, 0x8c, 0x07, 0xb4, + 0xea, 0x84, 0x72, 0x87, 0xf6, 0x26, 0x65, 0x9d, 0xd6, 0x6b, 0xc1, + 0x0a, 0x26, 0xad, 0xd9, 0xb5, 0x75, 0xb3, 0xa0, 0xa3, 0x8d}, + // ec30c9c3065a06bb07dc5b1c6b497f370c1ca65c0f30c08e042ba6bcecc78f2c.pem + {0xcd, 0xee, 0x9f, 0x33, 0x05, 0x57, 0x2a, 0x67, 0x7e, 0x1a, 0x6c, + 0x82, 0xdc, 0x1e, 0x02, 0xa3, 0x5b, 0x11, 0xca, 0xe6, 0xa6, 0x84, + 0x33, 0x8c, 0x9f, 0x37, 0xfe, 0x1a, 0xc8, 0xda, 0xec, 0x23}, + // 063e4afac491dfd332f3089b8542e94617d893d7fe944e10a7937ee29d9693c0.pem + {0xce, 0xd4, 0x39, 0x02, 0xab, 0x5f, 0xb5, 0x7b, 0x44, 0x23, 0x22, + 0xdc, 0x0e, 0x17, 0x2a, 0x4f, 0xb5, 0x5f, 0x71, 0x78, 0xb8, 0x08, + 0xf9, 0x4e, 0x78, 0x0a, 0x6f, 0xd6, 0xcc, 0x6b, 0xd8, 0x18}, + // c71f33c36d8efeefbed9d44e85e21cfe96b36fb0e132c52dca2415868492bf8a.pem + {0xd3, 0x1e, 0xc3, 0x92, 0x85, 0xb7, 0xa5, 0x31, 0x9d, 0x01, 0x57, + 0xdb, 0x42, 0x0e, 0xd8, 0x7c, 0x74, 0x3e, 0x33, 0x3b, 0xbc, 0x77, + 0xf8, 0x77, 0x1f, 0x70, 0x46, 0x4f, 0x43, 0x6a, 0x60, 0x49}, + // 9ed8f9b0e8e42a1656b8e1dd18f42ba42dc06fe52686173ba2fc70e756f207dc.pem + // a686fee577c88ab664d0787ecdfff035f4806f3de418dc9e4d516324fff02083.pem + // fdedb5bdfcb67411513a61aee5cb5b5d7c52af06028efc996cc1b05b1d6cea2b.pem + {0xd3, 0x4b, 0x25, 0x5b, 0x2f, 0xe7, 0xd1, 0xa0, 0x96, 0x56, 0xcb, + 0xab, 0x64, 0x09, 0xf7, 0x3c, 0x79, 0x6e, 0xc7, 0xd6, 0x6a, 0xf7, + 0x36, 0x53, 0xec, 0xc3, 0x9a, 0xf9, 0x78, 0x29, 0x73, 0x10}, + // 4b22d5a6aec99f3cdb79aa5ec06838479cd5ecba7164f7f22dc1d65f63d85708.pem + {0xd6, 0xa1, 0x84, 0x43, 0xd3, 0x48, 0xdb, 0x99, 0x4f, 0x93, 0x4c, + 0xcd, 0x8e, 0x63, 0x5d, 0x83, 0x3a, 0x27, 0xac, 0x1e, 0x56, 0xf8, + 0xaf, 0xaf, 0x7c, 0x97, 0xcb, 0x4f, 0x43, 0xea, 0xb6, 0x8b}, + // d6f034bd94aa233f0297eca4245b283973e447aa590f310c77f48fdf83112254.pem + {0xdb, 0x15, 0xc0, 0x06, 0x2b, 0x52, 0x0f, 0x31, 0x8a, 0x19, 0xda, + 0xcf, 0xec, 0xd6, 0x4f, 0x9e, 0x7a, 0x3f, 0xbe, 0x60, 0x9f, 0xd5, + 0x86, 0x79, 0x6f, 0x20, 0xae, 0x02, 0x8e, 0x8e, 0x30, 0x58}, + // 2a4397aafa6227fa11f9f9d76ecbb022b0a4494852c2b93fb2085c8afb19b62a.pem + {0xdb, 0x1d, 0x13, 0xec, 0x42, 0xa2, 0xcb, 0xa3, 0x67, 0x3b, 0xa6, + 0x7a, 0xf2, 0xde, 0xf8, 0x12, 0xe9, 0xc3, 0x55, 0x66, 0x61, 0x75, + 0x76, 0xd9, 0x5b, 0x4d, 0x6f, 0xac, 0xe3, 0xef, 0x0a, 0xe8}, + // 3946901f46b0071e90d78279e82fababca177231a704be72c5b0e8918566ea66.pem + {0xdd, 0x3e, 0xeb, 0x77, 0x9b, 0xee, 0x07, 0xf9, 0xef, 0xda, 0xc3, + 0x82, 0x40, 0x8b, 0x28, 0xd1, 0x42, 0xfa, 0x84, 0x2c, 0x78, 0xe8, + 0xbc, 0x0e, 0x33, 0x34, 0x8d, 0x57, 0xb9, 0x2f, 0x05, 0x83}, + // c67d722c1495be02cbf9ef1159f5ca4aa782dc832dc6aa60c9aa076a0ad1e69d.pem + {0xde, 0x8f, 0x05, 0x07, 0x4e, 0xc0, 0x31, 0x8e, 0x7e, 0x7e, 0x8d, + 0x31, 0x90, 0xda, 0xe8, 0xb0, 0x08, 0x94, 0xf0, 0xe8, 0xdd, 0xdf, + 0xd3, 0x91, 0x3d, 0x01, 0x75, 0x9b, 0x4f, 0x79, 0xb0, 0x5d}, + // c766a9bef2d4071c863a31aa4920e813b2d198608cb7b7cfe21143b836df09ea.pem + // e17890ee09a3fbf4f48b9c414a17d637b7a50647e9bc752322727fcc1742a911.pem + {0xe4, 0x2f, 0x24, 0xbd, 0x4d, 0x37, 0xf4, 0xaa, 0x2e, 0x56, 0xb9, + 0x79, 0xd8, 0x3d, 0x1e, 0x65, 0x21, 0x9f, 0xe0, 0xe9, 0xe3, 0xa3, + 0x82, 0xa1, 0xb3, 0xcb, 0x66, 0xc9, 0x39, 0x55, 0xde, 0x75}, + // e4f9a3235df7330255f36412bc849fb630f8519961ec3538301deb896c953da5.pem + {0xe6, 0xe1, 0x36, 0xc8, 0x61, 0x54, 0xf3, 0x2c, 0x3e, 0x49, 0xf4, + 0x7c, 0xfc, 0x6b, 0x33, 0x8f, 0xf2, 0xdc, 0x61, 0xce, 0x14, 0xfc, + 0x75, 0x89, 0xb3, 0xb5, 0x6a, 0x14, 0x50, 0x13, 0x27, 0x01}, + // 3e26492e20b52de79e15766e6cb4251a1d566b0dbfb225aa7d08dda1dcebbf0a.pem + {0xe7, 0xb9, 0x32, 0xae, 0x7e, 0x9b, 0xdc, 0x70, 0x1d, 0x77, 0x1d, + 0x6f, 0x39, 0xe8, 0xa6, 0x53, 0x44, 0x9e, 0xea, 0x43, 0xbd, 0xb4, + 0x7b, 0xd9, 0x10, 0x22, 0x95, 0x0d, 0x91, 0x79, 0xd8, 0x7e}, + // 5ccaf9f8f2bb3a0d215922eca383354b6ee3c62407ed32e30f6fb2618edeea10.pem + {0xe8, 0x49, 0xc7, 0x17, 0x6c, 0x93, 0xdf, 0x65, 0xf6, 0x4b, 0x61, + 0x69, 0x82, 0x36, 0x6e, 0x56, 0x63, 0x11, 0x78, 0x12, 0xb6, 0xfa, + 0x2b, 0xc0, 0xc8, 0xfa, 0x8a, 0xea, 0xee, 0x41, 0x81, 0xcc}, + // ea08c8d45d52ca593de524f0513ca6418da9859f7b08ef13ff9dd7bf612d6a37.key + {0xea, 0x08, 0xc8, 0xd4, 0x5d, 0x52, 0xca, 0x59, 0x3d, 0xe5, 0x24, + 0xf0, 0x51, 0x3c, 0xa6, 0x41, 0x8d, 0xa9, 0x85, 0x9f, 0x7b, 0x08, + 0xef, 0x13, 0xff, 0x9d, 0xd7, 0xbf, 0x61, 0x2d, 0x6a, 0x37}, + // d40e9c86cd8fe468c1776959f49ea774fa548684b6c406f3909261f4dce2575c.pem + {0xea, 0x87, 0xf4, 0x62, 0xde, 0xef, 0xff, 0xbd, 0x77, 0x75, 0xaa, + 0x2a, 0x4b, 0x7e, 0x0f, 0xcb, 0x91, 0xc2, 0x2e, 0xee, 0x6d, 0xf6, + 0x9e, 0xd9, 0x01, 0x00, 0xcc, 0xc7, 0x3b, 0x31, 0x14, 0x76}, + // 60911c79835c3739432d08c45df64311e06985c5889dc5420ce3d142c8c7ef58.pem + {0xef, 0x55, 0x12, 0x84, 0x71, 0x52, 0x32, 0xde, 0x92, 0xe2, 0x46, + 0xc3, 0x23, 0x32, 0x93, 0x62, 0xb1, 0x32, 0x49, 0x3b, 0xb1, 0x6b, + 0x58, 0x9e, 0x47, 0x75, 0x52, 0x0b, 0xeb, 0x87, 0x1a, 0x56}, + // 31c8fd37db9b56e708b03d1f01848b068c6da66f36fb5d82c008c6040fa3e133.pem + {0xf0, 0x34, 0xf6, 0x42, 0xca, 0x1d, 0x9e, 0x88, 0xe9, 0xef, 0xea, + 0xfc, 0xb1, 0x5c, 0x7c, 0x93, 0x7a, 0xa1, 0x9e, 0x04, 0xb0, 0x80, + 0xf2, 0x73, 0x35, 0xe1, 0xda, 0x70, 0xd1, 0xca, 0x12, 0x01}, + // 83618f932d6947744d5ecca299d4b2820c01483947bd16be814e683f7436be24.pem + {0xf2, 0xbb, 0xe0, 0x4c, 0x5d, 0xc7, 0x0d, 0x76, 0x3e, 0x89, 0xc5, + 0xa0, 0x52, 0x70, 0x48, 0xcd, 0x9e, 0xcd, 0x39, 0xeb, 0x62, 0x1e, + 0x20, 0x72, 0xff, 0x9a, 0x5f, 0x84, 0x32, 0x57, 0x1a, 0xa0}, + // 2a3699deca1e9fd099ba45de8489e205977c9f2a5e29d5dd747381eec0744d71.pem + {0xf3, 0x0e, 0x8f, 0x61, 0x01, 0x1d, 0x65, 0x87, 0x3c, 0xcb, 0x81, + 0xb4, 0x0f, 0xa6, 0x21, 0x97, 0x49, 0xb9, 0x94, 0xf0, 0x1f, 0xa2, + 0x4d, 0x02, 0x01, 0xd5, 0x21, 0xc2, 0x43, 0x56, 0x03, 0xca}, + // 0d90cd8e35209b4cefebdd62b644bed8eb55c74dddff26e75caf8ae70491f0bd.pem + {0xf5, 0x29, 0x3d, 0x47, 0xed, 0x38, 0xd4, 0xc3, 0x1b, 0x2d, 0x42, + 0xde, 0xe3, 0xb5, 0xb3, 0xac, 0xe9, 0x7c, 0xa2, 0x6c, 0xa2, 0xac, + 0x03, 0x65, 0xe3, 0x62, 0x2e, 0xe8, 0x02, 0x13, 0x1f, 0xbb}, + // 67ed4b703d15dc555f8c444b3a05a32579cb7599bd19c9babe10c584ea327ae0.pem + {0xfa, 0x00, 0xbe, 0xc7, 0x3d, 0xd9, 0x97, 0x95, 0xdf, 0x11, 0x62, + 0xc7, 0x89, 0x98, 0x70, 0x04, 0xc2, 0x6c, 0xbf, 0x90, 0xaf, 0x4d, + 0xb4, 0x42, 0xf6, 0x62, 0x20, 0xde, 0x41, 0x35, 0x4a, 0xc9}, + // a25a19546819d048000ef9c6577c4bcd8d2155b1e4346a4599d6c8b79799d4a1.pem + {0xfc, 0xd7, 0x6c, 0xca, 0x23, 0x47, 0xe5, 0xcd, 0x5b, 0x39, 0x34, + 0x7f, 0x51, 0xcf, 0x43, 0x65, 0x4b, 0x69, 0xa2, 0xbf, 0xc9, 0x07, + 0x36, 0x70, 0xa6, 0xbe, 0x47, 0xd8, 0x70, 0x1e, 0x6e, 0x0e}, + // 44a244105569a730791f509b24c3d7838a462216bb0f560ef87fbe76c2e6005a.pem + {0xb0, 0xfc, 0xce, 0x78, 0xc1, 0x66, 0x4e, 0x29, 0x35, 0x44, 0xc1, + 0x43, 0xe3, 0xd2, 0x68, 0x9f, 0x72, 0x3f, 0x5b, 0x6e, 0x63, 0x17, + 0x10, 0x7e, 0x16, 0x3d, 0x22, 0xba, 0x80, 0x69, 0x79, 0x4a}, + // 0230a604d99220e5612ee7862ab9f7a6e18e4f1ac4c9e27075788cc5220169ab.pem + {0xc5, 0x62, 0x17, 0xb7, 0xa8, 0x28, 0xc7, 0x34, 0x1c, 0x0a, 0xe7, + 0xa5, 0x90, 0xd8, 0x79, 0x0d, 0x4d, 0xef, 0x53, 0x66, 0x52, 0xe6, + 0x0a, 0xe5, 0xb8, 0xbd, 0xfa, 0x26, 0x97, 0x8f, 0xe0, 0x9c}, + // 06fd20629c143b9eab28d2799caefc5d23fde267d16c631e3f5b8b4bab3f68e6.pem + {0xe4, 0x7c, 0x5c, 0xd2, 0xdc, 0x8b, 0xab, 0xb4, 0xe5, 0x3f, 0x8a, + 0x49, 0x83, 0x92, 0x02, 0x75, 0xef, 0x6f, 0xfa, 0xac, 0xb0, 0x89, + 0xe8, 0x7a, 0x2c, 0x1f, 0xbe, 0x5a, 0x58, 0x5f, 0x05, 0xed}, + // 0bd39de4793cdc117138f47708aa4d583acf67adb059a0d91f668d1803bf6489.pem + {0x39, 0x73, 0x65, 0x88, 0xb9, 0x4a, 0x4c, 0xe7, 0x67, 0xf7, 0x31, + 0xca, 0xd5, 0x3f, 0x4c, 0xbe, 0x44, 0x13, 0x7e, 0x32, 0x1e, 0xad, + 0xca, 0xef, 0x8c, 0xe7, 0x9a, 0x22, 0x9b, 0xbc, 0xa9, 0x89}, + // c95c133b68319ee516b5f41e377f589878af1556567cc2834ef03b1d10830fd3.pem + {0xea, 0x12, 0x70, 0x5d, 0xe7, 0xc4, 0x8f, 0x6f, 0xcc, 0xe2, 0xcb, + 0x8d, 0xbc, 0x54, 0x2e, 0x0f, 0xc3, 0x8a, 0xc3, 0x8e, 0x08, 0x88, + 0x0d, 0xd0, 0x4a, 0x02, 0xef, 0x67, 0xc9, 0x3a, 0xe1, 0x35}, + // 29abf614b2870ed70df11225e9ae2068e3074eb9845ae252c2064e31ce9fe8a1.pem + {0xa6, 0xac, 0xa1, 0xec, 0x98, 0x09, 0xcc, 0x5b, 0x48, 0x21, 0xff, + 0x9d, 0x29, 0xc5, 0xeb, 0xe6, 0x51, 0x96, 0x0b, 0x91, 0xb1, 0xf1, + 0x9c, 0xc8, 0x9b, 0x55, 0xef, 0x87, 0x81, 0x8a, 0x95, 0x09}, + // c530fadc9bfa265e63b755cc6ee04c2d70d60bb916ce2f331dc7359362571b25.pem + {0x02, 0xa9, 0x5f, 0x43, 0x43, 0x10, 0x19, 0xe9, 0xdc, 0x22, 0x5f, + 0x05, 0xf4, 0x19, 0x33, 0x01, 0x90, 0xde, 0xb4, 0xa3, 0xf1, 0x86, + 0x9c, 0xaa, 0xc9, 0x84, 0x2b, 0x40, 0x3d, 0xcb, 0xee, 0x77}, + // 89107c8e50e029b7b5f4ff0ccd2956bcc9d0c8ba2bfb6a58374ed63a6b034a30.pem + {0x89, 0x28, 0xc5, 0x93, 0x98, 0xb0, 0xf1, 0x71, 0xc0, 0xf9, 0x6f, + 0xda, 0xe6, 0xab, 0x8d, 0xd0, 0xf4, 0x8e, 0xe0, 0x6d, 0x17, 0x4d, + 0xa1, 0x0c, 0x40, 0x4a, 0xc0, 0x01, 0x43, 0xc7, 0xa7, 0x49}, +}; + +// Hashes of SubjectPublicKeyInfos known to be used for interception by a +// party other than the device or machine owner. +static constexpr uint8_t kKnownInterceptionList[][crypto::kSHA256Length] = { + // 1df696f021ab1c3ace9a376b07ed7256a40214cd3396d7934087614924e2d7ef.pem + {0xb1, 0x3f, 0xa2, 0xe6, 0x13, 0x1a, 0x88, 0x8a, 0x01, 0xf3, 0xd6, 0x20, + 0x56, 0xfb, 0x0e, 0xfb, 0xe9, 0x99, 0xeb, 0x6b, 0x6e, 0x14, 0x92, 0x76, + 0x13, 0xe0, 0x2b, 0xa8, 0xb8, 0xfb, 0x04, 0x6e}, + // 61c0fc2e38b5b6f9071b42cee54a9013d858b6697c68b460948551b3249576a1.pem + {0x8e, 0x12, 0xd0, 0xcb, 0x3b, 0x7d, 0xf3, 0xea, 0x22, 0x57, 0x57, 0x94, + 0x89, 0xfd, 0x86, 0x58, 0xc9, 0x56, 0x03, 0xea, 0x6c, 0xf4, 0xb7, 0x31, + 0x63, 0xa4, 0x1e, 0xb7, 0xb7, 0xe9, 0x3f, 0xee}, + // 143315c857a9386973ed16840899c3f96b894a7a612c444efb691f14b0dedd87.pem + {0xa4, 0xe9, 0xaf, 0x01, 0x41, 0x6e, 0x3a, 0x02, 0x9b, 0x5d, 0x35, 0xe5, + 0xb1, 0x19, 0xde, 0x00, 0xcf, 0xe1, 0x56, 0xc5, 0xcf, 0x95, 0xfc, 0x82, + 0x3c, 0xf6, 0xd0, 0x5e, 0x3c, 0x1a, 0x82, 0x37}, + // 44a244105569a730791f509b24c3d7838a462216bb0f560ef87fbe76c2e6005a.pem + {0xb0, 0xfc, 0xce, 0x78, 0xc1, 0x66, 0x4e, 0x29, 0x35, 0x44, 0xc1, 0x43, + 0xe3, 0xd2, 0x68, 0x9f, 0x72, 0x3f, 0x5b, 0x6e, 0x63, 0x17, 0x10, 0x7e, + 0x16, 0x3d, 0x22, 0xba, 0x80, 0x69, 0x79, 0x4a}, + // 0230a604d99220e5612ee7862ab9f7a6e18e4f1ac4c9e27075788cc5220169ab.pem + {0xc5, 0x62, 0x17, 0xb7, 0xa8, 0x28, 0xc7, 0x34, 0x1c, 0x0a, 0xe7, 0xa5, + 0x90, 0xd8, 0x79, 0x0d, 0x4d, 0xef, 0x53, 0x66, 0x52, 0xe6, 0x0a, 0xe5, + 0xb8, 0xbd, 0xfa, 0x26, 0x97, 0x8f, 0xe0, 0x9c}, + // 06fd20629c143b9eab28d2799caefc5d23fde267d16c631e3f5b8b4bab3f68e6.pem + {0xe4, 0x7c, 0x5c, 0xd2, 0xdc, 0x8b, 0xab, 0xb4, 0xe5, 0x3f, 0x8a, 0x49, + 0x83, 0x92, 0x02, 0x75, 0xef, 0x6f, 0xfa, 0xac, 0xb0, 0x89, 0xe8, 0x7a, + 0x2c, 0x1f, 0xbe, 0x5a, 0x58, 0x5f, 0x05, 0xed}, + // 0bd39de4793cdc117138f47708aa4d583acf67adb059a0d91f668d1803bf6489.pem + {0x39, 0x73, 0x65, 0x88, 0xb9, 0x4a, 0x4c, 0xe7, 0x67, 0xf7, 0x31, + 0xca, 0xd5, 0x3f, 0x4c, 0xbe, 0x44, 0x13, 0x7e, 0x32, 0x1e, 0xad, + 0xca, 0xef, 0x8c, 0xe7, 0x9a, 0x22, 0x9b, 0xbc, 0xa9, 0x89}, + // c95c133b68319ee516b5f41e377f589878af1556567cc2834ef03b1d10830fd3.pem + {0xea, 0x12, 0x70, 0x5d, 0xe7, 0xc4, 0x8f, 0x6f, 0xcc, 0xe2, 0xcb, 0x8d, + 0xbc, 0x54, 0x2e, 0x0f, 0xc3, 0x8a, 0xc3, 0x8e, 0x08, 0x88, 0x0d, 0xd0, + 0x4a, 0x02, 0xef, 0x67, 0xc9, 0x3a, 0xe1, 0x35}, + // c530fadc9bfa265e63b755cc6ee04c2d70d60bb916ce2f331dc7359362571b25.pem + {0x02, 0xa9, 0x5f, 0x43, 0x43, 0x10, 0x19, 0xe9, 0xdc, 0x22, 0x5f, 0x05, + 0xf4, 0x19, 0x33, 0x01, 0x90, 0xde, 0xb4, 0xa3, 0xf1, 0x86, 0x9c, 0xaa, + 0xc9, 0x84, 0x2b, 0x40, 0x3d, 0xcb, 0xee, 0x77}, + // 89107c8e50e029b7b5f4ff0ccd2956bcc9d0c8ba2bfb6a58374ed63a6b034a30.pem + {0x89, 0x28, 0xc5, 0x93, 0x98, 0xb0, 0xf1, 0x71, 0xc0, 0xf9, 0x6f, + 0xda, 0xe6, 0xab, 0x8d, 0xd0, 0xf4, 0x8e, 0xe0, 0x6d, 0x17, 0x4d, + 0xa1, 0x0c, 0x40, 0x4a, 0xc0, 0x01, 0x43, 0xc7, 0xa7, 0x49}, + // 3472e4f16c570e0dd388aaaa4a64a34a4b939f1ca770996b5be0037c1aded9c1.pem + {0x34, 0x72, 0xe4, 0xf1, 0x6c, 0x57, 0x0e, 0x0d, 0xd3, 0x88, 0xaa, + 0xaa, 0x4a, 0x64, 0xa3, 0x4a, 0x4b, 0x93, 0x9f, 0x1c, 0xa7, 0x70, + 0x99, 0x6b, 0x5b, 0xe0, 0x03, 0x7c, 0x1a, 0xde, 0xd9, 0xc1} +}; diff --git a/constants/src/gen/java/generate_blocklist.awk b/constants/src/gen/java/generate_blocklist.awk new file mode 100644 index 000000000..f5c58b853 --- /dev/null +++ b/constants/src/gen/java/generate_blocklist.awk @@ -0,0 +1,106 @@ +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +BEGIN { + inSPKIBlockList = 0 + inKnownInterceptionList = 0 + currentHash = 0 + if (! package) { + package = "org.conscrypt" + } +} + +# Keep track of which block definition we are in now. +/kSPKIBlockList\[\]\[crypto::kSHA256Length\] = {/ { + inSPKIBlockList = 1 +} +/kKnownInterceptionList\[\]\[crypto::kSHA256Length\] = {/ { + inKnownInterceptionList = 1 +} +/};/ { + if (inSPKIBlockList) + inSPKIBlockList = 0 + if (inKnownInterceptionList) + inKnownInterceptionList= 0 +} + +# Extract the bytes from the public key hashes. +/\{0x/ { currentHash = "" } +/0x/ { + newLine = $0 + gsub(/[ {}]/, "", newLine) + gsub(/0x/, "", newLine) + currentHash = currentHash newLine +} +/\},/ { + if (inSPKIBlockList) + hashes[currentHash] = 1 + if (inKnownInterceptionList) + hashes[currentHash] = 0 + currentHash = 0 +} + +END { + # Make sure we are out of the declaration blocks. + if (inSPKIBlockList || inKnownInterceptionList) + exit 1 + + # Only keep the hashes that are only in kSPKIBlockList. + for (h in hashes) { + if (hashes[h]) { + blockedHashes[i++] = h + } + } + + # Generate the Java class. + print "/*" + print " * Copyright (C) 2024 The Android Open Source Project" + print " *" + print " * Licensed under the Apache License, Version 2.0 (the \"License\");" + print " * you may not use this file except in compliance with the License." + print " * You may obtain a copy of the License at" + print " *" + print " * http://www.apache.org/licenses/LICENSE-2.0" + print " *" + print " * Unless required by applicable law or agreed to in writing, software" + print " * distributed under the License is distributed on an \"AS IS\" BASIS," + print " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." + print " * See the License for the specific language governing permissions and" + print " * limitations under the License." + print " */" + print "" + print "/* This file was generated by generate_blocklist.awk. Do not modify. */" + print "" + print "package", package ";" + print "" + print "final class StaticBlocklist {" + print " static final byte[][] PUBLIC_KEYS = {" + ORS = "" + for (h in blockedHashes) { + print " {\n " + n = split(blockedHashes[h], splitHash, ",") + for (i=1; i<=n; i++) { + if (splitHash[i] != "") { + print "(byte) 0x" splitHash[i] ", " + if (i % 8 == 0) { + print "\n " + } + } + } + print "},\n" + } + ORS = "\n" + print " };" + print "}" +} diff --git a/expectations/Android.bp b/expectations/Android.bp new file mode 100644 index 000000000..217071600 --- /dev/null +++ b/expectations/Android.bp @@ -0,0 +1,37 @@ +// +// Copyright (C) 2026 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 { + default_visibility: [ + "//external/conscrypt:__subpackages__", + ], + default_applicable_licenses: ["external_conscrypt_license"], +} + +java_library { + name: "conscrypt-expectations-jar", + java_resources: [ + "knownfailures.txt", + "skippedCtsTest.txt", + ], +} + +java_library { + name: "conscrypt-skippedCtsTest-manual-base-jar", + host_supported: true, + java_resources: ["skippedCtsTest_manual_base.txt"], + sdk_version: "core_current", +} diff --git a/expectations/knownfailures.txt b/expectations/knownfailures.txt new file mode 100644 index 000000000..26e9f50a4 --- /dev/null +++ b/expectations/knownfailures.txt @@ -0,0 +1,5 @@ +/* + * This file contains test expectations for libcore. + */ +[ +] diff --git a/expectations/skippedCtsTest.txt b/expectations/skippedCtsTest.txt new file mode 100644 index 000000000..0420279c8 --- /dev/null +++ b/expectations/skippedCtsTest.txt @@ -0,0 +1,14 @@ +/* Do not modify directly. + * Generated by tools/non-cts-json-generator/update_skippedCtsTest.sh + * which dumps all @NonCts tests. + */ +[ + { + "description": "Test for internal APIs.", + "names": [ + "com.android.org.conscrypt.XwingTest#x509AndPkcs8_areNotSupported", + "com.android.org.conscrypt.java.security.KeyPairGeneratorTest#test_getInstance", + "com.android.org.conscrypt.java.security.SignatureTest#test_getInstance" + ] + } +] \ No newline at end of file diff --git a/expectations/skippedCtsTest_manual_base.txt b/expectations/skippedCtsTest_manual_base.txt new file mode 100644 index 000000000..694675100 --- /dev/null +++ b/expectations/skippedCtsTest_manual_base.txt @@ -0,0 +1,8 @@ +[ + { + "description": "Test for internal APIs.", + "names": [ + "com.android.org.conscrypt.XwingTest#x509AndPkcs8_areNotSupported" + ] + } +] diff --git a/expectations/update_skippedCtsTest.sh b/expectations/update_skippedCtsTest.sh new file mode 100755 index 000000000..290b00e23 --- /dev/null +++ b/expectations/update_skippedCtsTest.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Copyright (C) 2026 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. + +# Exit this shell script when a command fails. +set -e + +cd ${ANDROID_BUILD_TOP} +build/soong/soong_ui.bash --make-mode conscrypt-non-cts-tests-txt +cp ${ANDROID_BUILD_TOP}/out/soong/.intermediates/external/conscrypt/conscrypt-non-cts-tests-txt/android_common/gen/conscrypt-non-cts-tests.txt \ + ${ANDROID_BUILD_TOP}/external/conscrypt/expectations/skippedCtsTest.txt diff --git a/fix_format.py b/fix_format.py index 905de6e60..25f164521 100644 --- a/fix_format.py +++ b/fix_format.py @@ -40,6 +40,46 @@ def parse_files(g4_output: str, project_prefix: str, root_dir: Path) -> List[str return files_to_format +def get_jj_output() -> str: + """Runs jj diff and returns the stdout.""" + try: + return subprocess.check_output(["jj", "diff", "--name-only", "-r", "@"], text=True) + except subprocess.CalledProcessError as e: + sys.exit(f"ERROR: Failed to run 'jj diff'.\nDetails: {e}") + +def parse_files_jj(jj_output: str, project_prefix: str, root_dir: Path) -> List[str]: + """Parses jj output and returns a list of absolute file paths to format.""" + files_to_format: List[str] = [] + + for line in jj_output.splitlines(): + line = line.strip() + if not line: + continue + + if not line.endswith(tuple(VALID_EXTENSIONS)): + continue + + prefix = project_prefix.removeprefix("//depot/google3/") + if prefix and not line.startswith(prefix): + continue + + relative_path = line[len(prefix):] + abs_path = root_dir / relative_path + files_to_format.append(str(abs_path)) + + return files_to_format + +def is_jj_workspace() -> bool: + if not shutil.which("jj"): + return False + return subprocess.run(["jj", "root"], capture_output=True).returncode == 0 + +def is_g4_workspace() -> bool: + if not shutil.which("g4"): + return False + proc = subprocess.run(["g4", "opened"], capture_output=True, text=True) + return "unknown" not in proc.stderr and "unknown" not in proc.stdout + def main() -> None: script_dir = Path(__file__).resolve().parent config_path = script_dir / ".clang-format" @@ -54,8 +94,15 @@ def main() -> None: sys.exit(f"ERROR: '{CLANG_FORMAT_BIN}' not found in PATH.") print("Querying opened files...") - g4_output = get_g4_output() - files_to_format = parse_files(g4_output, PROJECT_PREFIX, script_dir) + + if is_g4_workspace(): + g4_output = get_g4_output() + files_to_format = parse_files(g4_output, PROJECT_PREFIX, script_dir) + elif is_jj_workspace(): + jj_output = get_jj_output() + files_to_format = parse_files_jj(jj_output, PROJECT_PREFIX, script_dir) + else: + sys.exit("ERROR: Could not determine if we are in a g4 or jj workspace.") if not files_to_format: print(f"No source files under {PROJECT_PREFIX} are currently open.") diff --git a/flatbuffers-jarjar-rules.txt b/flatbuffers-jarjar-rules.txt new file mode 100644 index 000000000..12126b3f9 --- /dev/null +++ b/flatbuffers-jarjar-rules.txt @@ -0,0 +1,2 @@ +# Repackage flatbuffer library under an internal package. +rule com.google.flatbuffers.** com.android.org.conscrypt.internal.flatbuffers.@1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d900231e8..ceaccb45c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,6 +8,7 @@ jmh = "1.37" jmh-plugin = "0.7.2" junit = "4.13.2" mockito = "2.28.2" +truth = "1.4.4" netty-handler = "4.1.24.Final" netty-tcnative = "2.0.26.Final" shadow = "7.1.2" @@ -32,6 +33,7 @@ jacoco-ant = { module = "org.jacoco:org.jacoco.ant", version.ref = "jacoco" } junit = { module = "junit:junit", version.ref = "junit" } mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" } mockito-android = { module = "org.mockito:mockito-android", version.ref = "mockito" } +truth = { module = "com.google.truth:truth", version.ref = "truth" } # JMH Benchmarking jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" } diff --git a/openjdk/build.gradle b/openjdk/build.gradle index d0a7aded8..1f7ad6e10 100644 --- a/openjdk/build.gradle +++ b/openjdk/build.gradle @@ -292,7 +292,8 @@ dependencies { testImplementation project(':conscrypt-constants'), project(path: ':conscrypt-testing', configuration: 'shadow'), libs.junit, - libs.mockito + libs.mockito, + libs.truth testRuntimeOnly sourceSets["$preferredSourceSet"].output diff --git a/openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java b/openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java new file mode 100644 index 000000000..1e516c8fc --- /dev/null +++ b/openjdk/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 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 org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * A default NetworkSecurityPolicy for OpenJDK. + */ +@Internal +public class ConscryptNetworkSecurityPolicy implements NetworkSecurityPolicy { + public static ConscryptNetworkSecurityPolicy getDefault() { + return new ConscryptNetworkSecurityPolicy(); + } + + @Override + public boolean isCertificateTransparencyVerificationRequired(String hostname) { + return false; + } + + @Override + public CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname) { + return CertificateTransparencyVerificationReason.UNKNOWN; + } + + @Override + public DomainEncryptionMode getDomainEncryptionMode(String hostname) { + return DomainEncryptionMode.UNKNOWN; + } +} diff --git a/openjdk/src/main/java/org/conscrypt/Platform.java b/openjdk/src/main/java/org/conscrypt/Platform.java index eec29e027..083a91600 100644 --- a/openjdk/src/main/java/org/conscrypt/Platform.java +++ b/openjdk/src/main/java/org/conscrypt/Platform.java @@ -42,6 +42,7 @@ import org.conscrypt.metrics.NoopStatsLog; import org.conscrypt.metrics.Source; import org.conscrypt.metrics.StatsLog; +import org.conscrypt.metrics.StatsLogImpl; import java.io.File; import java.io.FileDescriptor; @@ -76,9 +77,11 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.function.Supplier; import javax.crypto.spec.GCMParameterSpec; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; @@ -256,7 +259,7 @@ static void setSocketWriteTimeout(@SuppressWarnings("unused") Socket s, // TODO: figure this out on the RI } - static void setSSLParameters(SSLParameters params, SSLParametersImpl impl) { + public static void setSSLParameters(SSLParameters params, SSLParametersImpl impl) { if (JAVA_VERSION >= 9) { Java9PlatformUtil.setSSLParameters(params, impl); } else if (JAVA_VERSION >= 8) { @@ -277,7 +280,7 @@ static void setSSLParameters(SSLParameters params, SSLParametersImpl impl, } } - static void getSSLParameters(SSLParameters params, SSLParametersImpl impl) { + public static void getSSLParameters(SSLParameters params, SSLParametersImpl impl) { if (JAVA_VERSION >= 9) { Java9PlatformUtil.getSSLParameters(params, impl); } else if (JAVA_VERSION >= 8) { @@ -453,68 +456,6 @@ static ConscryptEngineSocket createEngineSocket(Socket socket, String hostname, return new ConscryptEngineSocket(socket, hostname, port, autoClose, sslParameters); } - static ConscryptFileDescriptorSocket createFileDescriptorSocket(SSLParametersImpl sslParameters) - throws IOException { - if (JAVA_VERSION >= 8) { - return new Java8FileDescriptorSocket(sslParameters); - } - return new ConscryptFileDescriptorSocket(sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port, - SSLParametersImpl sslParameters) - throws IOException { - if (JAVA_VERSION >= 8) { - return new Java8FileDescriptorSocket(hostname, port, sslParameters); - } - return new ConscryptFileDescriptorSocket(hostname, port, sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port, - SSLParametersImpl sslParameters) - throws IOException { - if (JAVA_VERSION >= 8) { - return new Java8FileDescriptorSocket(address, port, sslParameters); - } - return new ConscryptFileDescriptorSocket(address, port, sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port, - InetAddress clientAddress, - int clientPort, - SSLParametersImpl sslParameters) - throws IOException { - if (JAVA_VERSION >= 8) { - return new Java8FileDescriptorSocket(hostname, port, clientAddress, clientPort, - sslParameters); - } - return new ConscryptFileDescriptorSocket(hostname, port, clientAddress, clientPort, - sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port, - InetAddress clientAddress, - int clientPort, - SSLParametersImpl sslParameters) - throws IOException { - if (JAVA_VERSION >= 8) { - return new Java8FileDescriptorSocket(address, port, clientAddress, clientPort, - sslParameters); - } - return new ConscryptFileDescriptorSocket(address, port, clientAddress, clientPort, - sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(Socket socket, String hostname, - int port, boolean autoClose, - SSLParametersImpl sslParameters) - throws IOException { - if (JAVA_VERSION >= 8) { - return new Java8FileDescriptorSocket(socket, hostname, port, autoClose, sslParameters); - } - return new ConscryptFileDescriptorSocket(socket, hostname, port, autoClose, sslParameters); - } - /** * Currently we don't wrap anything from the RI. */ @@ -667,57 +608,13 @@ static boolean supportsX509ExtendedTrustManager() { return true; } - /** - * Check if SCT verification is required for a given hostname. - * - * SCT Verification is enabled using {@code Security} properties. - * The "conscrypt.ct.enable" property must be true, as well as a per domain property. - * The reverse notation of the domain name, prefixed with "conscrypt.ct.enforce." - * is used as the property name. - * Basic globbing is also supported. - * - * For example, for the domain foo.bar.com, the following properties will be - * looked up, in order of precedence. - * - conscrypt.ct.enforce.com.bar.foo - * - conscrypt.ct.enforce.com.bar.* - * - conscrypt.ct.enforce.com.* - * - conscrypt.ct.enforce.* - */ - public static boolean isCTVerificationRequired(String hostname) { - if (hostname == null) { - return false; - } - - String property = Security.getProperty("conscrypt.ct.enable"); - if (property == null || !Boolean.parseBoolean(property.toLowerCase(Locale.ROOT))) { - return false; - } - - List parts = Arrays.asList(hostname.split("\\.")); - Collections.reverse(parts); - - boolean enable = false; - StringBuilder propertyName = new StringBuilder("conscrypt.ct.enforce"); - // The loop keeps going on even once we've found a match - // This allows for finer grained settings on subdomains - for (String part : parts) { - property = Security.getProperty(propertyName + ".*"); - if (property != null) { - enable = Boolean.parseBoolean(property.toLowerCase(Locale.ROOT)); - } - propertyName.append(".").append(part); - } - - property = Security.getProperty(propertyName.toString()); - if (property != null) { - enable = Boolean.parseBoolean(property.toLowerCase(Locale.ROOT)); - } - return enable; + static SSLException wrapInvalidEchDataException(SSLException e) { + return e; } - public static CertificateTransparencyVerificationReason reasonCTVerificationRequired( - String hostname) { - return CertificateTransparencyVerificationReason.UNKNOWN; + static SSLException wrapEchRejectedException(EchRejectedException e, String hostname, + byte[] retryConfigs) { + return e; } static boolean supportsConscryptCertStore() { @@ -784,7 +681,8 @@ static CertBlocklist newDefaultBlocklist() { return null; } - static CertificateTransparency newDefaultCertificateTransparency() { + static CertificateTransparency newDefaultCertificateTransparency( + Supplier policySupplier) { return null; } diff --git a/openjdk/src/test/java/org/conscrypt/AddressUtilsTest.java b/openjdk/src/test/java/org/conscrypt/AddressUtilsTest.java index 36f40bf5d..9abdd0d8b 100644 --- a/openjdk/src/test/java/org/conscrypt/AddressUtilsTest.java +++ b/openjdk/src/test/java/org/conscrypt/AddressUtilsTest.java @@ -20,13 +20,14 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import java.util.Random; -import java.util.regex.Pattern; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.Random; +import java.util.regex.Pattern; + /** Test for AddressUtils */ @RunWith(JUnit4.class) public class AddressUtilsTest { @@ -126,34 +127,48 @@ public void test_isLiteralIpAddress_null_throwsNPE() throws Exception { private static final String OLD_IP_PATTERN = // IPv4 part: matches d.d.d.d where d is 0-255 (allows leading zeros) - "^(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9]))|" - // IPv6 part (case-insensitive) - + "(?i:" - // Case 1: 7 prefix groups. Matches full (8 groups) or compressed at end (7 - // groups + ::) - // e.g., 1:2:3:4:5:6:7:8 or 1:2:3:4:5:6:7:: - + "(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|" - // Case 2: 6 prefix groups. Matches compressed in middle, compressed at end, or - // embedded - // IPv4 - // e.g., 1:2:3:4:5:6::8, 1:2:3:4:5:6::, 1:2:3:4:5:6:1.2.3.4 - + "(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|" - // Case 3: 5 prefix groups. - // e.g., 1:2:3:4:5::7:8, 1:2:3:4:5::1.2.3.4, 1:2:3:4:5:: - + "(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|" - // Case 4: 4 prefix groups. - + "(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|" - // Case 5: 3 prefix groups. - + "(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|" - // Case 6: 2 prefix groups. - + "(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|" - // Case 7: 1 prefix group. - + "(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|" - // Case 8: 0 prefix groups. Starts with :: - // e.g., ::1, ::1.2.3.4, :: - + "(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))" - // Optional Zone ID (e.g., %eth0) at the end of IPv6 - + ")(?:%.+)?$"; + "^(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9]?[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-" + + "9]?[0-9]))|" + // IPv6 part (case-insensitive) + + "(?i:" + // Case 1: 7 prefix groups. Matches full (8 groups) or compressed at end (7 + // groups + ::) + // e.g., 1:2:3:4:5:6:7:8 or 1:2:3:4:5:6:7:: + + "(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|" + // Case 2: 6 prefix groups. Matches compressed in middle, compressed at end, or + // embedded + // IPv4 + // e.g., 1:2:3:4:5:6::8, 1:2:3:4:5:6::, 1:2:3:4:5:6:1.2.3.4 + + "(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-" + + "9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|" + // Case 3: 5 prefix groups. + // e.g., 1:2:3:4:5::7:8, 1:2:3:4:5::1.2.3.4, 1:2:3:4:5:: + + "(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4][0-9]|1[" + + "0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:" + + "))|" + // Case 4: 4 prefix groups. + + "(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:" + + "25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-" + + "9]|[1-9]?[0-9])){3}))|:))|" + // Case 5: 3 prefix groups. + + "(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?" + + ":(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-" + + "9][0-9]|[1-9]?[0-9])){3}))|:))|" + // Case 6: 2 prefix groups. + + "(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?" + + ":(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-" + + "9][0-9]|[1-9]?[0-9])){3}))|:))|" + // Case 7: 1 prefix group. + + "(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?" + + ":(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-" + + "9][0-9]|[1-9]?[0-9])){3}))|:))|" + // Case 8: 0 prefix groups. Starts with :: + // e.g., ::1, ::1.2.3.4, :: + + "(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4][" + + "0-9]|1[0-9][0-9]|[1-9]?[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])" + + "){3}))|:))" + // Optional Zone ID (e.g., %eth0) at the end of IPv6 + + ")(?:%.+)?$"; private static final Pattern OLD_PATTERN = Pattern.compile(OLD_IP_PATTERN); private static boolean oldIsLiteralIpAddress(String hostname) { @@ -163,215 +178,78 @@ private static boolean oldIsLiteralIpAddress(String hostname) { @Test public void test_isLiteralIpAddress_differential() { String[] testCases = { - // IPv4 Success - "127.0.0.1", - "255.255.255.255", - "0.0.00.000", - "192.009.010.19", - "254.249.190.094", - // IPv4 Failure - "127.0.0.1a", - " 255.255.255.255", - "0.0.00.0009", - "192.009z.010.19", - "254.249..094", - "192.168.2.1%1", - "192.168.2.1%eth0", - "256.255.255.255", - "255.255.255.256", - "192.168.1.260", - // IPv6 Success - "::1", - "2001:Db8::1", - "2001:cdbA:0000:0000:0000:0000:3257:9652", - "2001:cdba:0:0:0:0:3257:9652", - "2001:cdBA::3257:9652", - "2001:cdba::3257:9652%1", - "2001:cdba::3257:9652%eth0", - "2001:cdba::3257:9652%int2.3!", - // IPv6 Failure - ":::1", - "::11111", - "20011::1111", - "2001:db8:::1", - "2001:cdba:0000:00000:0000:0000:3257:9652", - "2001:cdbA:0000:0000:0000:0000:0000:3257:9652", - "2001:cdba:0::0:0:0:3257:9652", - "02001:cdba::3257:9652", - "2001:cdba::3257:96521", - "2001:cdba::3257:9652%", - // Additional Edge Cases - "", - " ", - "a", - "1", - "1.", - "1.2", - "1.2.3", - "1.2.3.", - "11.22.33.", - "1.2.3.4.5", - ":", - ":::", - "::::", - "1:", - ":1", - "1::2::", - "::1::2", - "2001:db8:1:2:3:4:5:6:7", - "2001:db8:1:2:3:4:5:6:7:8", - "1:2:3:4:5:6:7:8::", - "2001:db8:1:2:3:4:5:6:1.2.3.4", - "2001:db8:1:2:3:4:5:1.2.3.4", - "2001:db8:1:2:3:4:1.2.3.4.5", - "2001:db8:1:2:3:4:256.1.1.1", - "2001:db8::1.2.3.4%zone", - "2001:db8::1.2.3.4%", - "2001:db8::1.2.3.04", - "localhost", - "www.google.com", - "127.0.0.1.ipv4.google.com", - "1::", - "2001:db8::1%eth%0", - "2001:db8::1%eth 0", - "2001:db8::1%\u000B", - "2001:db8::1%\u000C", - "2001:db8::1%\r", - "2001:db8::1%\n", - "2001:db8::1%\u0085", - "2001:db8::1%\u2028", - "2001:db8::1%\u2029", - // Some random junk - "asdfasdfasdf", - "1:2:3:4:5:6:7:8:9:0", - "::1.2.3.4.5.6", - "2001:db8::1%_unsafe_zone_name", - "2001:db8::1%eth\n0", - "2001:db8::1.2.3.4%eth\n0", - "[2001:db8::1]", - "[::1]", - "2001:db8::1%eth0%1", - "2001:db8::1%eth0\u000C", - "2001:db8::1%%", - "2001:db8::1.2.3.4%%", - "1.2.3.4\u0000", - "2001:db8::1\u0000", - "2001:db8::1%\u0000", - "2001:db8::1%eth0\n", - "127.0.0.1\n", - "2001:db8::1\n", - "::ffff:192.168.1.1", - "::192.168.1.1", - "::1.2.3.4::", - "2001:db8::1.2.3.4::", - // Tricky Zone ID cases - "2001:db8::1%eth%0", - "2001:db8::1.2.3.4%eth%0", - "2001:db8::1%eth%", - "2001:db8::1.2.3.4%eth%", - "1.2.3.4%eth0", - "1.2.3.4%", - "2001:db8::1%", - "2001:db8::1.2.3.4%", - "2001:db8::1%eth0::", - "2001:db8::1.2.3.4%eth0::", - "2001:db8::1.2.3.4%eth0%eth1", - "2001:db8:1:2:3:4:5:6%eth0::1", - "2001:db8::%eth0", - ":.1.2.3.4", - "1::.2", - "2001:db8::.1.2.3.4", - // Tricky group and compression count cases - "1:2:3:4:5:6::7", - "1:2:3:4:5:6:7::", - "::1:2:3:4:5:6:7", - "1:2:3:4:5:6:7:8::", - "::1:2:3:4:5:6:7:8", - "1:2:3:4:5:6:7::8", - // Leading zeros in IPv4/embedded IPv4 - "1.2.3.01", - "0000.0.0.0", - "2001:db8::1.2.3.0", - "2001:db8::1.2.3.00", - "2001:db8::%1.2.3.4", - "2001:db8::1.2.3.4%5.6.7.8", - "::%eth0", - "1:2:3:4:5:6:7::", - "1:2:3:4:5:6:7:8::", - "1:2:3:4:5:6:7:8%eth0", - "1:2:3:4:5:6:7:8:9%eth0", - "2001:db8::1.2.3.01", - "2001:db8::1.2.3.0", - "2001:db8::1.2.3.00", - // Systematic Regex Cases - // Case 1 (7 prefix): - "1:2:3:4:5:6:7:8", - "1:2:3:4:5:6:7::", - // Case 2 (6 prefix): - "1:2:3:4:5:6::8", - "1:2:3:4:5:6:1.2.3.4", - "1:2:3:4:5:6::", - // Case 3 (5 prefix): - "1:2:3:4:5::8", - "1:2:3:4:5::7:8", - "1:2:3:4:5::1.2.3.4", - "1:2:3:4:5::", - // Case 4 (4 prefix): - "1:2:3:4::8", - "1:2:3:4::7:8", - "1:2:3:4::6:7:8", - "1:2:3:4::1.2.3.4", - "1:2:3:4::8:1.2.3.4", - "1:2:3:4::", - // Case 5 (3 prefix): - "1:2:3::8", - "1:2:3::7:8", - "1:2:3::6:7:8", - "1:2:3::5:6:7:8", - "1:2:3::1.2.3.4", - "1:2:3::8:1.2.3.4", - "1:2:3::7:8:1.2.3.4", - "1:2:3::", - // Case 6 (2 prefix): - "1:2::8", - "1:2::7:8", - "1:2::6:7:8", - "1:2::5:6:7:8", - "1:2::4:5:6:7:8", - "1:2::1.2.3.4", - "1:2::8:1.2.3.4", - "1:2::7:8:1.2.3.4", - "1:2::6:7:8:1.2.3.4", - "1:2::", - // Case 7 (1 prefix): - "1::8", - "1::7:8", - "1::6:7:8", - "1::5:6:7:8", - "1::4:5:6:7:8", - "1::3:4:5:6:7:8", - "1::1.2.3.4", - "1::8:1.2.3.4", - "1::7:8:1.2.3.4", - "1::6:7:8:1.2.3.4", - "1::5:6:7:8:1.2.3.4", - "1::", - // Case 8 (0 prefix): - "::8", - "::7:8", - "::6:7:8", - "::5:6:7:8", - "::4:5:6:7:8", - "::3:4:5:6:7:8", - "::2:3:4:5:6:7:8", - "::1.2.3.4", - "::8:1.2.3.4", - "::7:8:1.2.3.4", - "::6:7:8:1.2.3.4", - "::5:6:7:8:1.2.3.4", - "::4:5:6:7:8:1.2.3.4", - "::" - }; + // IPv4 Success + "127.0.0.1", "255.255.255.255", "0.0.00.000", "192.009.010.19", "254.249.190.094", + // IPv4 Failure + "127.0.0.1a", " 255.255.255.255", "0.0.00.0009", "192.009z.010.19", "254.249..094", + "192.168.2.1%1", "192.168.2.1%eth0", "256.255.255.255", "255.255.255.256", + "192.168.1.260", + // IPv6 Success + "::1", "2001:Db8::1", "2001:cdbA:0000:0000:0000:0000:3257:9652", + "2001:cdba:0:0:0:0:3257:9652", "2001:cdBA::3257:9652", "2001:cdba::3257:9652%1", + "2001:cdba::3257:9652%eth0", "2001:cdba::3257:9652%int2.3!", + // IPv6 Failure + ":::1", "::11111", "20011::1111", "2001:db8:::1", + "2001:cdba:0000:00000:0000:0000:3257:9652", + "2001:cdbA:0000:0000:0000:0000:0000:3257:9652", "2001:cdba:0::0:0:0:3257:9652", + "02001:cdba::3257:9652", "2001:cdba::3257:96521", "2001:cdba::3257:9652%", + // Additional Edge Cases + "", " ", "a", "1", "1.", "1.2", "1.2.3", "1.2.3.", "11.22.33.", "1.2.3.4.5", ":", + ":::", "::::", "1:", ":1", "1::2::", "::1::2", "2001:db8:1:2:3:4:5:6:7", + "2001:db8:1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7:8::", "2001:db8:1:2:3:4:5:6:1.2.3.4", + "2001:db8:1:2:3:4:5:1.2.3.4", "2001:db8:1:2:3:4:1.2.3.4.5", + "2001:db8:1:2:3:4:256.1.1.1", "2001:db8::1.2.3.4%zone", "2001:db8::1.2.3.4%", + "2001:db8::1.2.3.04", "localhost", "www.google.com", "127.0.0.1.ipv4.google.com", + "1::", "2001:db8::1%eth%0", "2001:db8::1%eth 0", "2001:db8::1%\u000B", + "2001:db8::1%\u000C", "2001:db8::1%\r", "2001:db8::1%\n", "2001:db8::1%\u0085", + "2001:db8::1%\u2028", "2001:db8::1%\u2029", + // Some random junk + "asdfasdfasdf", "1:2:3:4:5:6:7:8:9:0", "::1.2.3.4.5.6", + "2001:db8::1%_unsafe_zone_name", "2001:db8::1%eth\n0", "2001:db8::1.2.3.4%eth\n0", + "[2001:db8::1]", "[::1]", "2001:db8::1%eth0%1", "2001:db8::1%eth0\u000C", + "2001:db8::1%%", "2001:db8::1.2.3.4%%", "1.2.3.4\u0000", "2001:db8::1\u0000", + "2001:db8::1%\u0000", "2001:db8::1%eth0\n", "127.0.0.1\n", "2001:db8::1\n", + "::ffff:192.168.1.1", "::192.168.1.1", "::1.2.3.4::", "2001:db8::1.2.3.4::", + // Tricky Zone ID cases + "2001:db8::1%eth%0", "2001:db8::1.2.3.4%eth%0", "2001:db8::1%eth%", + "2001:db8::1.2.3.4%eth%", "1.2.3.4%eth0", "1.2.3.4%", "2001:db8::1%", + "2001:db8::1.2.3.4%", + "2001:db8::1%eth0::", "2001:db8::1.2.3.4%eth0::", "2001:db8::1.2.3.4%eth0%eth1", + "2001:db8:1:2:3:4:5:6%eth0::1", "2001:db8::%eth0", ":.1.2.3.4", "1::.2", + "2001:db8::.1.2.3.4", + // Tricky group and compression count cases + "1:2:3:4:5:6::7", "1:2:3:4:5:6:7::", "::1:2:3:4:5:6:7", + "1:2:3:4:5:6:7:8::", "::1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::8", + // Leading zeros in IPv4/embedded IPv4 + "1.2.3.01", "0000.0.0.0", "2001:db8::1.2.3.0", "2001:db8::1.2.3.00", + "2001:db8::%1.2.3.4", "2001:db8::1.2.3.4%5.6.7.8", "::%eth0", + "1:2:3:4:5:6:7::", "1:2:3:4:5:6:7:8::", "1:2:3:4:5:6:7:8%eth0", + "1:2:3:4:5:6:7:8:9%eth0", "2001:db8::1.2.3.01", "2001:db8::1.2.3.0", + "2001:db8::1.2.3.00", + // Systematic Regex Cases + // Case 1 (7 prefix): + "1:2:3:4:5:6:7:8", "1:2:3:4:5:6:7::", + // Case 2 (6 prefix): + "1:2:3:4:5:6::8", "1:2:3:4:5:6:1.2.3.4", "1:2:3:4:5:6::", + // Case 3 (5 prefix): + "1:2:3:4:5::8", "1:2:3:4:5::7:8", "1:2:3:4:5::1.2.3.4", "1:2:3:4:5::", + // Case 4 (4 prefix): + "1:2:3:4::8", "1:2:3:4::7:8", "1:2:3:4::6:7:8", "1:2:3:4::1.2.3.4", + "1:2:3:4::8:1.2.3.4", "1:2:3:4::", + // Case 5 (3 prefix): + "1:2:3::8", "1:2:3::7:8", "1:2:3::6:7:8", "1:2:3::5:6:7:8", "1:2:3::1.2.3.4", + "1:2:3::8:1.2.3.4", "1:2:3::7:8:1.2.3.4", "1:2:3::", + // Case 6 (2 prefix): + "1:2::8", "1:2::7:8", "1:2::6:7:8", "1:2::5:6:7:8", "1:2::4:5:6:7:8", + "1:2::1.2.3.4", "1:2::8:1.2.3.4", "1:2::7:8:1.2.3.4", "1:2::6:7:8:1.2.3.4", "1:2::", + // Case 7 (1 prefix): + "1::8", "1::7:8", "1::6:7:8", "1::5:6:7:8", "1::4:5:6:7:8", "1::3:4:5:6:7:8", + "1::1.2.3.4", "1::8:1.2.3.4", "1::7:8:1.2.3.4", "1::6:7:8:1.2.3.4", + "1::5:6:7:8:1.2.3.4", "1::", + // Case 8 (0 prefix): + "::8", "::7:8", "::6:7:8", "::5:6:7:8", "::4:5:6:7:8", "::3:4:5:6:7:8", + "::2:3:4:5:6:7:8", "::1.2.3.4", "::8:1.2.3.4", "::7:8:1.2.3.4", "::6:7:8:1.2.3.4", + "::5:6:7:8:1.2.3.4", "::4:5:6:7:8:1.2.3.4", "::"}; for (String tc : testCases) { boolean expected = oldIsLiteralIpAddress(tc); @@ -390,57 +268,45 @@ public void test_isLiteralIpAddress_differential() { @Test public void test_isLiteralIpAddress_exhaustive() { - String[] prefixes = { - "", - "1:", - "1:2:", - "1:2:3:", - "1:2:3:4:", - "1:2:3:4:5:", - "1:2:3:4:5:6:", - "1:2:3:4:5:6:7:", - "1:2:3:4:5:6:7:8:", - "::", - "1::", - "::1", - "1::2", - "1:2::", - "::1:2", - "1:2::3", - "1::2:3", - "1:2:3::", - "::1:2:3", - "1:2:3::4", - "1::2:3:4", - "1:2:3:4::", - "::1:2:3:4", - "1:2:3:4::5", - "1::2:3:4:5", - "1:2:3:4:5::", - "::1:2:3:4:5", - "1:2:3:4:5::6", - "1::2:3:4:5:6", - "1:2:3:4:5:6::", - "::1:2:3:4:5:6", - "1:2:3:4:5:6::7", - "1::2:3:4:5:6:7", - "1:2:3:4:5:6:7::", - "::1:2:3:4:5:6:7", - "1:2::3::4" - }; - - String[] suffixes = { - "8", - "8:9", - "8:9:10", - "1.2.3.4", - "1.2.3.4.5", - "1.2.3", - "256.1.2.3", - "1.2.3.04", - "01.2.3.4", - "" - }; + String[] prefixes = {"", + "1:", + "1:2:", + "1:2:3:", + "1:2:3:4:", + "1:2:3:4:5:", + "1:2:3:4:5:6:", + "1:2:3:4:5:6:7:", + "1:2:3:4:5:6:7:8:", + "::", + "1::", + "::1", + "1::2", + "1:2::", + "::1:2", + "1:2::3", + "1::2:3", + "1:2:3::", + "::1:2:3", + "1:2:3::4", + "1::2:3:4", + "1:2:3:4::", + "::1:2:3:4", + "1:2:3:4::5", + "1::2:3:4:5", + "1:2:3:4:5::", + "::1:2:3:4:5", + "1:2:3:4:5::6", + "1::2:3:4:5:6", + "1:2:3:4:5:6::", + "::1:2:3:4:5:6", + "1:2:3:4:5:6::7", + "1::2:3:4:5:6:7", + "1:2:3:4:5:6:7::", + "::1:2:3:4:5:6:7", + "1:2::3::4"}; + + String[] suffixes = {"8", "8:9", "8:9:10", "1.2.3.4", "1.2.3.4.5", + "1.2.3", "256.1.2.3", "1.2.3.04", "01.2.3.4", ""}; String[] zones = {"", "%eth0", "%", "%eth0%1", "%\n"}; @@ -473,9 +339,8 @@ public void test_isLiteralIpAddress_randomFuzz() { boolean actual = AddressUtils.isLiteralIpAddress(tc); if (expected != actual) { Assert.fail( - String.format( - "Mismatch for input '%s' (hex: %s): expected %b, actual %b", - tc, toHex(tc), expected, actual)); + String.format("Mismatch for input '%s' (hex: %s): expected %b, actual %b", + tc, toHex(tc), expected, actual)); } } } @@ -496,4 +361,31 @@ private static boolean isRealAndroid() { private static boolean isQuirkyCase(String tc) { return tc.contains("\u000B") || tc.contains("\u000C"); } + + @Test + public void test_asciiEqualsIgnoreCase() { + assertTrue(AddressUtils.asciiEqualsIgnoreCase("", "")); + assertTrue(AddressUtils.asciiEqualsIgnoreCase("hello", "hello")); + assertTrue(AddressUtils.asciiEqualsIgnoreCase("hello", "HELLO")); + assertTrue(AddressUtils.asciiEqualsIgnoreCase("hello", "Hello")); + assertTrue(AddressUtils.asciiEqualsIgnoreCase("hello", "HeLlO")); + assertFalse(AddressUtils.asciiEqualsIgnoreCase("hello", "hellp")); + assertFalse(AddressUtils.asciiEqualsIgnoreCase("hello", "hell")); + assertFalse(AddressUtils.asciiEqualsIgnoreCase("hello", "helloo")); + assertFalse(AddressUtils.asciiEqualsIgnoreCase("hello", "héllo")); + assertFalse(AddressUtils.asciiEqualsIgnoreCase("", "hello")); + } + + @Test + public void test_asciiEqualsIgnoreCase_compareWithNull_returnsFalse() { + assertFalse(AddressUtils.asciiEqualsIgnoreCase("hello", null)); + assertFalse(AddressUtils.asciiEqualsIgnoreCase("", null)); + assertFalse(AddressUtils.asciiEqualsIgnoreCase(null, "hello")); + assertFalse(AddressUtils.asciiEqualsIgnoreCase(null, "")); + } + + @Test + public void test_asciiEqualsIgnoreCase_bothNull_returnsTrue() { + assertTrue(AddressUtils.asciiEqualsIgnoreCase(null, null)); + } } diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java index 51a3a6698..067b4f896 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidSuite.java @@ -104,6 +104,7 @@ NativeSslSessionTest.class, OpenSSLKeyTest.class, OpenSSLX509CertificateTest.class, + SignatureThreadMisuseTest.class, SSLUtilsTest.class, SlhDsaTest.class, TestSessionBuilderTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java index 2f76dde5e..81aa0abe3 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptAndroidWithoutTlsSuite.java @@ -97,6 +97,7 @@ NativeSslSessionTest.class, OpenSSLKeyTest.class, OpenSSLX509CertificateTest.class, + SignatureThreadMisuseTest.class, SSLUtilsTest.class, SlhDsaTest.class, TestSessionBuilderTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java index cb639e1f6..656ed38a9 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptEngineTest.java @@ -432,6 +432,28 @@ public void getAlpnIsNullBeforeHandshake() throws Exception { assertEquals(alpnProtocol, Conscrypt.getApplicationProtocol(clientEngine)); } + @Test + public void unwrapOversizedRecordHeaderThrowsSSLException() throws Exception { + setupEngines(TestKeyStore.getClient(), TestKeyStore.getServer()); + + // Generate ClientHello so clientEngine enters NEED_UNWRAP state + ByteBuffer clientPacketBuffer = bufferType.newBuffer(clientEngine.getSession().getPacketBufferSize()); + ByteBuffer emptyAppBuffer = bufferType.newBuffer(0); + SSLEngineResult wrapResult = clientEngine.wrap(emptyAppBuffer, clientPacketBuffer); + assertEquals(HandshakeStatus.NEED_UNWRAP, wrapResult.getHandshakeStatus()); + + ByteBuffer src = ByteBuffer.allocate(100); + src.put((byte) 0x16); // Handshake + src.put((byte) 0x03); // Major 3 + src.put((byte) 0x03); // Minor 3 + src.putShort((short) 0xFFFF); // Length 65535 > SSL3_RT_MAX_PACKET_SIZE + src.flip(); + + ByteBuffer dst = bufferType.newBuffer(clientEngine.getSession().getApplicationBufferSize()); + + assertThrows(SSLException.class, () -> clientEngine.unwrap(src, dst)); + } + private void doMutualAuthHandshake(TestKeyStore clientKs, TestKeyStore serverKs, ClientAuth clientAuth) throws Exception { setupEngines(clientKs, serverKs); diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java index b4e8708b3..69e8f59a9 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptOpenJdkSuite.java @@ -112,6 +112,7 @@ OpenSSLKeyTest.class, OpenSSLX509CertificateTest.class, PlatformTest.class, + SignatureThreadMisuseTest.class, SSLUtilsTest.class, ServerSessionContextTest.class, SlhDsaTest.class, diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java index 942bacc5f..b7f506640 100644 --- a/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java +++ b/openjdk/src/test/java/org/conscrypt/ConscryptSocketTest.java @@ -137,13 +137,6 @@ Socket newServerSocket(OpenSSLContextImpl context, ServerSocket server, * Various factories for SSL server sockets. */ public enum SocketType { - FILE_DESCRIPTOR(false) { - @Override - void assertSocketType(Socket socket) { - assertTrue("Unexpected socket type: " + socket.getClass().getName(), - socket instanceof ConscryptFileDescriptorSocket); - } - }, ENGINE(true) { @Override void assertSocketType(Socket socket) { @@ -222,17 +215,7 @@ public ServerSocket newServerSocket() throws IOException { @Parameters(name = "{0} wrapping {1} connecting to {2}") public static Object[][] data() { - Object[][] fd_cases = new Object[][] { - {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE, ServerSocketType.PLAIN}, - {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.NONE, ServerSocketType.CHANNEL}, - {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN, ServerSocketType.PLAIN}, - {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.PLAIN, ServerSocketType.CHANNEL}, - {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.CHANNEL, ServerSocketType.PLAIN}, - {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.CHANNEL, ServerSocketType.CHANNEL} - // Not supported: {SocketType.FILE_DESCRIPTOR, UnderlyingSocketType.SSL}, - }; - - Object[][] engine_cases = new Object[][] { + return new Object[][] { {SocketType.ENGINE, UnderlyingSocketType.NONE, ServerSocketType.PLAIN}, {SocketType.ENGINE, UnderlyingSocketType.NONE, ServerSocketType.CHANNEL}, {SocketType.ENGINE, UnderlyingSocketType.PLAIN, ServerSocketType.PLAIN}, @@ -241,12 +224,6 @@ public static Object[][] data() { {SocketType.ENGINE, UnderlyingSocketType.CHANNEL, ServerSocketType.CHANNEL}, {SocketType.ENGINE, UnderlyingSocketType.SSL, ServerSocketType.PLAIN}, {SocketType.ENGINE, UnderlyingSocketType.SSL, ServerSocketType.CHANNEL}}; - - if (TestUtils.isJavaVersion(17)) { - // FD Socket not feasible on Java 17+ - return engine_cases; - } - return ArrayUtils.concat(fd_cases, engine_cases); } @Parameter public SocketType socketType; diff --git a/openjdk/src/test/java/org/conscrypt/ConscryptTestListener.java b/openjdk/src/test/java/org/conscrypt/ConscryptTestListener.java new file mode 100644 index 000000000..27f214bfc --- /dev/null +++ b/openjdk/src/test/java/org/conscrypt/ConscryptTestListener.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2026 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 org.junit.runner.Description; +import org.junit.runner.notification.RunListener; + +import java.security.Provider; +import java.security.Security; + +import tests.util.ServiceTester; + +/** + * Custom JUnit RunListener for Conscrypt tests. + * + * This is required in Google3 because UTP (Unified Test Platform) automatically + * shards and splits the ConscryptAndroidSuite, running individual test classes + * directly. This bypasses the suite's @BeforeClass setup which registers the provider. + * + * Additionally, this listener configures ServiceTester to only test Conscrypt, + * preventing tests from failing due to Bouncy Castle deprecation on Android. + */ +public class ConscryptTestListener extends RunListener { + @Override + public void testRunStarted(Description description) throws Exception { + Provider conscryptProvider = TestUtils.getConscryptProvider(); + Security.insertProviderAt(conscryptProvider, 1); + ServiceTester.setProviders(new Provider[] {conscryptProvider}); + super.testRunStarted(description); + } +} diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index dfb77485d..3d013fb33 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -58,14 +58,18 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.EOFException; import java.io.FileDescriptor; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.reflect.Method; import java.math.BigInteger; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyPair; @@ -92,12 +96,12 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import javax.crypto.BadPaddingException; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLProtocolException; import javax.security.auth.x500.X500Principal; -import javax.crypto.BadPaddingException; @RunWith(JUnit4.class) public class NativeCryptoTest { @@ -117,8 +121,6 @@ public class NativeCryptoTest { private static long[] CLIENT_CERTIFICATE_REFS; private static byte[][] ENCODED_CLIENT_CERTIFICATES; private static byte[][] CA_PRINCIPALS; - private static OpenSSLKey CHANNEL_ID_PRIVATE_KEY; - private static byte[] CHANNEL_ID; private static Method m_Platform_getFileDescriptor; private static RSAPrivateCrtKey TEST_RSA_KEY; @@ -158,17 +160,6 @@ public static void initStatics() throws Exception { OpenSSLECGroupContext openSslSpec = OpenSSLECGroupContext.getCurveByName("prime256v1"); BigInteger s = new BigInteger( "229cdbbf489aea584828a261a23f9ff8b0f66f7ccac98bf2096ab3aee41497c5", 16); - CHANNEL_ID_PRIVATE_KEY = - new OpenSSLECPrivateKey(new ECPrivateKeySpec(s, openSslSpec.getECParameterSpec())) - .getOpenSSLKey(); - - // Channel ID is the concatenation of the X and Y coordinates of the public key. - CHANNEL_ID = - new BigInteger("702b07871fd7955c320b26f15e244e47eed60272124c92b9ebecf0b42f90069b" - + "ab53592ebfeb4f167dbf3ce61513afb0e354c479b1c1b69874fa47129" - + "3494f77", - 16) - .toByteArray(); // RSA keys are slow to generate, so prefer to reuse the key when possible. TEST_RSA_KEY = generateRsaKey(); @@ -243,24 +234,356 @@ public static void assertEqualByteArrays(byte[][] expected, byte[][] actual) { assertEquals(Arrays.deepToString(expected), Arrays.deepToString(actual)); } + // Test values from wycheproof/testvectors/rsa_pkcs1_2048_test.json. + @Test + public void wrap_RSA_private_key_pkcs8_success() throws Exception { + byte[] expectedPkcs8 = decodeHex( + "308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100b3510a" + + "2bcd4ce644c5b594ae5059e12b2f054b658d5da5959a2fdf1871b808bc3df3e628d2792e51aad5c1" + + "24b43bda453dca5cde4bcf28e7bd4effba0cb4b742bbb6d5a013cb63d1aa3a89e02627ef5398b52c" + + "0cfd97d208abeb8d7c9bce0bbeb019a86ddb589beb29a5b74bf861075c677c81d430f030c265247a" + + "f9d3c9140ccb65309d07e0adc1efd15cf17e7b055d7da3868e4648cc3a180f0ee7f8e1e7b18098a3" + + "391b4ce7161e98d57af8a947e201a463e2d6bbca8059e5706e9dfed8f4856465ffa712ed1aa18e88" + + "8d12dc6aa09ce95ecfca83cc5b0b15db09c8647f5d524c0f2e7620a3416b9623cadc0f097af57326" + + "1c98c8400aa12af38e43cad84d0203010001028201001a502d0eea6c7b69e21d5839101f705456ed" + + "0ef852fb47fe21071f54c5f33c8ceb066c62d727e32d26c58137329f89d3195325b795264c195d85" + + "472f7507dbd0961d2951f935a26b34f0ac24d15490e1128a9b7138915bc7dbfa8fe396357131c543" + + "ae9c98507368d9ceb08c1c6198a3eda7aea185a0e976cd42c22d00f003d9f19d96ea4c9afcbfe144" + + "1ccc802cfb0689f59d804c6a4e4f404c15174745ed6cb8bc88ef0b33ba0d2a80e35e43bc90f35005" + + "2e72016e75b00d357a381c9c0d467069ca660887c987766349fcc43460b4aa516bce079edd87ba16" + + "4307b752c277ed9528ad3ba0bf1877349ed3b7966a6c240110409bf4d0fade0c68fdadd847fd0281" + + "8100ec125cf37e310a2ff46263b9b2e0629d6390005ec88913d4fb71bd4dd856124498aaeba983d7" + + "ba2bd942e64d223feb7a23af4d605efeea6bd70d39afe99d35a3aa15e74a1768778093be0edd4a8d" + + "09b2def6dc9b67ff85764625c2e19236db4c401ce30a2572d3ecb4f969b7ad19c522c02d77446567" + + "6e1a3776c54d6248348b02818100c2742abcd9897bd4b0b671f973fc82a8f84abf5705ff88dd4194" + + "8623afe9dca60dc6543390767feaebeb539576ee8bfa61b5fcbca94a7cef75a09150c540fa9694dd" + + "8004ad23718c889049219369c99f4458d4afc148f6f07df87324a96d9cf7b385dd8622414a1832f9" + + "f29446f050c2d5a6407649dc41ab70e23b3dcc22c9870281810096a9798d250263400bb627734288" + + "1627e07cecdf91187b01b89ff47314188a7c20fb24800156d2c85d5666e8df6ceff9f9804ddfad80" + + "ff5767de56ecc029c72bf6c717df9f64daafc29acf9dc7908f9a0ad67e20e8949936ccba18d021a2" + + "c4febb04349a2b2047c4901385b6e5d0c691d118b33f81802b32ac272ef09e42fad50281800554f4" + + "1b0b87f68a45722b3be0cf4ab1e165034c1a91002ab8f29e9ef9e2dab6fee7b2455bafb42037e9d2" + + "f7e533f348a147412fd72080be7c2633f5d802c91c39e6bcece3e675e59995033c55737020dad9e8" + + "b30d04b828adfb9304ad54a11a35a4f50709876ac5b118236ba76a4d7c9a291dd9607b169de1d182" + + "385691999f0281801c640189d9bfe8c623833210a76c420c6f44e5d760e259916cec2ae2b1564569" + + "60fd95e2747660c389562250f055049cfab7e5c3039549384a7a2aaeb1c824d3af709482a8cf9b58" + + "7022a00b1f0722db50f33cb26dc20dd2245d5265df61ee2983c938c2167dcee121fc4b4479c237e7" + + "28cf633ab60a8c0ecd04fce7e3baa559"); + byte[] rawKey = decodeHex( + "308204a30201000282010100b3510a2bcd4ce644c5b594ae5059e12b2f054b658d5da5959a2fdf1871" + + "b808bc3df3e628d2792e51aad5c124b43bda453dca5cde4bcf28e7bd4effba0cb4b742bbb6d5a013" + + "cb63d1aa3a89e02627ef5398b52c0cfd97d208abeb8d7c9bce0bbeb019a86ddb589beb29a5b74bf8" + + "61075c677c81d430f030c265247af9d3c9140ccb65309d07e0adc1efd15cf17e7b055d7da3868e46" + + "48cc3a180f0ee7f8e1e7b18098a3391b4ce7161e98d57af8a947e201a463e2d6bbca8059e5706e9d" + + "fed8f4856465ffa712ed1aa18e888d12dc6aa09ce95ecfca83cc5b0b15db09c8647f5d524c0f2e76" + + "20a3416b9623cadc0f097af573261c98c8400aa12af38e43cad84d0203010001028201001a502d0e" + + "ea6c7b69e21d5839101f705456ed0ef852fb47fe21071f54c5f33c8ceb066c62d727e32d26c58137" + + "329f89d3195325b795264c195d85472f7507dbd0961d2951f935a26b34f0ac24d15490e1128a9b71" + + "38915bc7dbfa8fe396357131c543ae9c98507368d9ceb08c1c6198a3eda7aea185a0e976cd42c22d" + + "00f003d9f19d96ea4c9afcbfe1441ccc802cfb0689f59d804c6a4e4f404c15174745ed6cb8bc88ef" + + "0b33ba0d2a80e35e43bc90f350052e72016e75b00d357a381c9c0d467069ca660887c987766349fc" + + "c43460b4aa516bce079edd87ba164307b752c277ed9528ad3ba0bf1877349ed3b7966a6c24011040" + + "9bf4d0fade0c68fdadd847fd02818100ec125cf37e310a2ff46263b9b2e0629d6390005ec88913d4" + + "fb71bd4dd856124498aaeba983d7ba2bd942e64d223feb7a23af4d605efeea6bd70d39afe99d35a3" + + "aa15e74a1768778093be0edd4a8d09b2def6dc9b67ff85764625c2e19236db4c401ce30a2572d3ec" + + "b4f969b7ad19c522c02d774465676e1a3776c54d6248348b02818100c2742abcd9897bd4b0b671f9" + + "73fc82a8f84abf5705ff88dd41948623afe9dca60dc6543390767feaebeb539576ee8bfa61b5fcbc" + + "a94a7cef75a09150c540fa9694dd8004ad23718c889049219369c99f4458d4afc148f6f07df87324" + + "a96d9cf7b385dd8622414a1832f9f29446f050c2d5a6407649dc41ab70e23b3dcc22c98702818100" + + "96a9798d250263400bb6277342881627e07cecdf91187b01b89ff47314188a7c20fb24800156d2c8" + + "5d5666e8df6ceff9f9804ddfad80ff5767de56ecc029c72bf6c717df9f64daafc29acf9dc7908f9a" + + "0ad67e20e8949936ccba18d021a2c4febb04349a2b2047c4901385b6e5d0c691d118b33f81802b32" + + "ac272ef09e42fad50281800554f41b0b87f68a45722b3be0cf4ab1e165034c1a91002ab8f29e9ef9" + + "e2dab6fee7b2455bafb42037e9d2f7e533f348a147412fd72080be7c2633f5d802c91c39e6bcece3" + + "e675e59995033c55737020dad9e8b30d04b828adfb9304ad54a11a35a4f50709876ac5b118236ba7" + + "6a4d7c9a291dd9607b169de1d182385691999f0281801c640189d9bfe8c623833210a76c420c6f44" + + "e5d760e259916cec2ae2b156456960fd95e2747660c389562250f055049cfab7e5c3039549384a7a" + + "2aaeb1c824d3af709482a8cf9b587022a00b1f0722db50f33cb26dc20dd2245d5265df61ee2983c9" + + "38c2167dcee121fc4b4479c237e728cf633ab60a8c0ecd04fce7e3baa559"); + + byte[] result = NativeCrypto.wrap_RSA_private_key_pkcs8(rawKey); + assertArrayEquals(expectedPkcs8, result); + } + + @Test + public void wrap_RSA_private_key_pkcs8_fail() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> { NativeCrypto.wrap_RSA_private_key_pkcs8(invalidKey); }); + } + + @Test + public void wrap_EC_private_key_pkcs8_success() throws Exception { + byte[] expectedPkcs8 = decodeHex( + "3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420b94e7609a7" + + "176abafbd4a34fabae42b091e44d9e46e67fca566c2a188a63c65f"); + byte[] rawKey = decodeHex("30310201010420b94e7609a7176abafbd4a34fabae42b091e44d9e46e67fca56" + + "6c2a188a63c65fa00a06082a8648ce3d030107"); + + byte[] result = NativeCrypto.wrap_EC_private_key_pkcs8(rawKey); + assertArrayEquals(expectedPkcs8, result); + } + + @Test + public void wrap_EC_private_key_pkcs8_fail() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> NativeCrypto.wrap_EC_private_key_pkcs8(invalidKey)); + } + + // Test value from wycheproof/testvectors/rsa_pkcs1_2048_test.json. + @Test + public void unwrap_RSA_private_key_pkcs8_success() throws Exception { + byte[] expectedRawKey = decodeHex( + "308204a30201000282010100b3510a2bcd4ce644c5b594ae5059e12b2f054b658d5da5959a2fdf1871" + + "b808bc3df3e628d2792e51aad5c124b43bda453dca5cde4bcf28e7bd4effba0cb4b742bbb6d5a013" + + "cb63d1aa3a89e02627ef5398b52c0cfd97d208abeb8d7c9bce0bbeb019a86ddb589beb29a5b74bf8" + + "61075c677c81d430f030c265247af9d3c9140ccb65309d07e0adc1efd15cf17e7b055d7da3868e46" + + "48cc3a180f0ee7f8e1e7b18098a3391b4ce7161e98d57af8a947e201a463e2d6bbca8059e5706e9d" + + "fed8f4856465ffa712ed1aa18e888d12dc6aa09ce95ecfca83cc5b0b15db09c8647f5d524c0f2e76" + + "20a3416b9623cadc0f097af573261c98c8400aa12af38e43cad84d0203010001028201001a502d0e" + + "ea6c7b69e21d5839101f705456ed0ef852fb47fe21071f54c5f33c8ceb066c62d727e32d26c58137" + + "329f89d3195325b795264c195d85472f7507dbd0961d2951f935a26b34f0ac24d15490e1128a9b71" + + "38915bc7dbfa8fe396357131c543ae9c98507368d9ceb08c1c6198a3eda7aea185a0e976cd42c22d" + + "00f003d9f19d96ea4c9afcbfe1441ccc802cfb0689f59d804c6a4e4f404c15174745ed6cb8bc88ef" + + "0b33ba0d2a80e35e43bc90f350052e72016e75b00d357a381c9c0d467069ca660887c987766349fc" + + "c43460b4aa516bce079edd87ba164307b752c277ed9528ad3ba0bf1877349ed3b7966a6c24011040" + + "9bf4d0fade0c68fdadd847fd02818100ec125cf37e310a2ff46263b9b2e0629d6390005ec88913d4" + + "fb71bd4dd856124498aaeba983d7ba2bd942e64d223feb7a23af4d605efeea6bd70d39afe99d35a3" + + "aa15e74a1768778093be0edd4a8d09b2def6dc9b67ff85764625c2e19236db4c401ce30a2572d3ec" + + "b4f969b7ad19c522c02d774465676e1a3776c54d6248348b02818100c2742abcd9897bd4b0b671f9" + + "73fc82a8f84abf5705ff88dd41948623afe9dca60dc6543390767feaebeb539576ee8bfa61b5fcbc" + + "a94a7cef75a09150c540fa9694dd8004ad23718c889049219369c99f4458d4afc148f6f07df87324" + + "a96d9cf7b385dd8622414a1832f9f29446f050c2d5a6407649dc41ab70e23b3dcc22c98702818100" + + "96a9798d250263400bb6277342881627e07cecdf91187b01b89ff47314188a7c20fb24800156d2c8" + + "5d5666e8df6ceff9f9804ddfad80ff5767de56ecc029c72bf6c717df9f64daafc29acf9dc7908f9a" + + "0ad67e20e8949936ccba18d021a2c4febb04349a2b2047c4901385b6e5d0c691d118b33f81802b32" + + "ac272ef09e42fad50281800554f41b0b87f68a45722b3be0cf4ab1e165034c1a91002ab8f29e9ef9" + + "e2dab6fee7b2455bafb42037e9d2f7e533f348a147412fd72080be7c2633f5d802c91c39e6bcece3" + + "e675e59995033c55737020dad9e8b30d04b828adfb9304ad54a11a35a4f50709876ac5b118236ba7" + + "6a4d7c9a291dd9607b169de1d182385691999f0281801c640189d9bfe8c623833210a76c420c6f44" + + "e5d760e259916cec2ae2b156456960fd95e2747660c389562250f055049cfab7e5c3039549384a7a" + + "2aaeb1c824d3af709482a8cf9b587022a00b1f0722db50f33cb26dc20dd2245d5265df61ee2983c9" + + "38c2167dcee121fc4b4479c237e728cf633ab60a8c0ecd04fce7e3baa559"); + byte[] pkcs8Der = decodeHex( + "308204bd020100300d06092a864886f70d0101010500048204a7308204a30201000282010100b3510a" + + "2bcd4ce644c5b594ae5059e12b2f054b658d5da5959a2fdf1871b808bc3df3e628d2792e51aad5c1" + + "24b43bda453dca5cde4bcf28e7bd4effba0cb4b742bbb6d5a013cb63d1aa3a89e02627ef5398b52c" + + "0cfd97d208abeb8d7c9bce0bbeb019a86ddb589beb29a5b74bf861075c677c81d430f030c265247a" + + "f9d3c9140ccb65309d07e0adc1efd15cf17e7b055d7da3868e4648cc3a180f0ee7f8e1e7b18098a3" + + "391b4ce7161e98d57af8a947e201a463e2d6bbca8059e5706e9dfed8f4856465ffa712ed1aa18e88" + + "8d12dc6aa09ce95ecfca83cc5b0b15db09c8647f5d524c0f2e7620a3416b9623cadc0f097af57326" + + "1c98c8400aa12af38e43cad84d0203010001028201001a502d0eea6c7b69e21d5839101f705456ed" + + "0ef852fb47fe21071f54c5f33c8ceb066c62d727e32d26c58137329f89d3195325b795264c195d85" + + "472f7507dbd0961d2951f935a26b34f0ac24d15490e1128a9b7138915bc7dbfa8fe396357131c543" + + "ae9c98507368d9ceb08c1c6198a3eda7aea185a0e976cd42c22d00f003d9f19d96ea4c9afcbfe144" + + "1ccc802cfb0689f59d804c6a4e4f404c15174745ed6cb8bc88ef0b33ba0d2a80e35e43bc90f35005" + + "2e72016e75b00d357a381c9c0d467069ca660887c987766349fcc43460b4aa516bce079edd87ba16" + + "4307b752c277ed9528ad3ba0bf1877349ed3b7966a6c240110409bf4d0fade0c68fdadd847fd0281" + + "8100ec125cf37e310a2ff46263b9b2e0629d6390005ec88913d4fb71bd4dd856124498aaeba983d7" + + "ba2bd942e64d223feb7a23af4d605efeea6bd70d39afe99d35a3aa15e74a1768778093be0edd4a8d" + + "09b2def6dc9b67ff85764625c2e19236db4c401ce30a2572d3ecb4f969b7ad19c522c02d77446567" + + "6e1a3776c54d6248348b02818100c2742abcd9897bd4b0b671f973fc82a8f84abf5705ff88dd4194" + + "8623afe9dca60dc6543390767feaebeb539576ee8bfa61b5fcbca94a7cef75a09150c540fa9694dd" + + "8004ad23718c889049219369c99f4458d4afc148f6f07df87324a96d9cf7b385dd8622414a1832f9" + + "f29446f050c2d5a6407649dc41ab70e23b3dcc22c9870281810096a9798d250263400bb627734288" + + "1627e07cecdf91187b01b89ff47314188a7c20fb24800156d2c85d5666e8df6ceff9f9804ddfad80" + + "ff5767de56ecc029c72bf6c717df9f64daafc29acf9dc7908f9a0ad67e20e8949936ccba18d021a2" + + "c4febb04349a2b2047c4901385b6e5d0c691d118b33f81802b32ac272ef09e42fad50281800554f4" + + "1b0b87f68a45722b3be0cf4ab1e165034c1a91002ab8f29e9ef9e2dab6fee7b2455bafb42037e9d2" + + "f7e533f348a147412fd72080be7c2633f5d802c91c39e6bcece3e675e59995033c55737020dad9e8" + + "b30d04b828adfb9304ad54a11a35a4f50709876ac5b118236ba76a4d7c9a291dd9607b169de1d182" + + "385691999f0281801c640189d9bfe8c623833210a76c420c6f44e5d760e259916cec2ae2b1564569" + + "60fd95e2747660c389562250f055049cfab7e5c3039549384a7a2aaeb1c824d3af709482a8cf9b58" + + "7022a00b1f0722db50f33cb26dc20dd2245d5265df61ee2983c938c2167dcee121fc4b4479c237e7" + + "28cf633ab60a8c0ecd04fce7e3baa559"); + + byte[] result = NativeCrypto.unwrap_RSA_private_key_pkcs8(pkcs8Der); + assertArrayEquals(expectedRawKey, result); + } + + @Test + public void unwrap_nonRSA_private_key_pkcs8_fail() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> NativeCrypto.unwrap_RSA_private_key_pkcs8(invalidKey)); + } + + @Test + public void unwrap_EC_private_key_pkcs8_success() throws Exception { + byte[] expectedRawKey = + decodeHex("30310201010420b94e7609a7176abafbd4a34fabae42b091e44d9e46e67fca56" + + "6c2a188a63c65fa00a06082a8648ce3d030107"); + byte[] pkcs8Der = decodeHex( + "3041020100301306072a8648ce3d020106082a8648ce3d030107042730250201010420b94e7609a7" + + "176abafbd4a34fabae42b091e44d9e46e67fca566c2a188a63c65f"); + + byte[] result = NativeCrypto.unwrap_EC_private_key_pkcs8(pkcs8Der); + assertArrayEquals(expectedRawKey, result); + } + + @Test + public void unwrap_nonEC_private_key_pkcs8_fail() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> NativeCrypto.unwrap_EC_private_key_pkcs8(invalidKey)); + } + + // Test values from wycheproof/testvectors/rsa_pss_misc_test.json. + @Test + public void wrap_RSA_public_key_x509_success() throws Exception { + byte[] expectedX509 = decodeHex("30820122300d06092a864886f70d01010105000382010f003082010a02" + + "82010100b3510a2bcd4ce644c5b" + + "594ae5059e12b2f054b658d5da5959a2fdf1871b808bc3df3e628d27" + + "92e51aad5c124b43bda453dca5" + + "cde4bcf28e7bd4effba0cb4b742bbb6d5a013cb63d1aa3a89e02627e" + + "f5398b52c0cfd97d208abeb8d7" + + "c9bce0bbeb019a86ddb589beb29a5b74bf861075c677c81d430f030c" + + "265247af9d3c9140ccb65309d0" + + "7e0adc1efd15cf17e7b055d7da3868e4648cc3a180f0ee7f8e1e7b18" + + "098a3391b4ce7161e98d57af8a" + + "947e201a463e2d6bbca8059e5706e9dfed8f4856465ffa712ed1aa18" + + "e888d12dc6aa09ce95ecfca83c" + + "c5b0b15db09c8647f5d524c0f2e7620a3416b9623cadc0f097af5732" + + "61c98c8400aa12af38e43cad84" + + "d0203010001"); + byte[] rawKey = decodeHex( + "3082010a0282010100b3510a2bcd4ce644c5b594ae5059e12b2f054b658d5da5959a2fdf1871b808bc" + + "3df3e628d2792e51aad5c124b43bda453dca5cde4bcf28e7bd4effba0cb4b742bbb6d5a013cb63d1" + + "aa" + + "3a89e02627ef5398b52c0cfd97d208abeb8d7c9bce0bbeb019a86ddb589beb29a5b74bf861075c67" + + "7c" + + "81d430f030c265247af9d3c9140ccb65309d07e0adc1efd15cf17e7b055d7da3868e4648cc3a180f" + + "0e" + + "e7f8e1e7b18098a3391b4ce7161e98d57af8a947e201a463e2d6bbca8059e5706e9dfed8f4856465" + + "ff" + + "a712ed1aa18e888d12dc6aa09ce95ecfca83cc5b0b15db09c8647f5d524c0f2e7620a3416b9623ca" + + "dc" + + "0f097af573261c98c8400aa12af38e43cad84d0203010001"); + + byte[] result = NativeCrypto.wrap_RSA_public_key_x509(rawKey); + assertArrayEquals(expectedX509, result); + } + + @Test + public void wrap_RSA_public_key_x509_fail() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> NativeCrypto.wrap_RSA_public_key_x509(invalidKey)); + } + + // Test values from third_party/wycheproof/testvectors/ecdsa_secp256r1_sha256_test.json. + @Test + public void wrap_EC_public_key_x509_success() throws Exception { + byte[] expectedX509 = decodeHex( + "3059301306072a8648ce3d020106082a8648ce3d030107034200" + + "042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964" + + "eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e"); + byte[] rawKey = + decodeHex("042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c77879" + + "64eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e"); + byte[] result = NativeCrypto.wrap_EC_public_key_x509(rawKey, "prime256v1"); + assertArrayEquals(expectedX509, result); + } + + @Test + public void wrap_EC_public_key_x509_wrong_key_fails() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> NativeCrypto.wrap_EC_public_key_x509(invalidKey, "prime256v1")); + } + + @Test + public void wrap_EC_public_key_x509_wrong_curve_fails() throws Exception { + byte[] rawKey = + decodeHex("042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c77879" + + "64eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e"); + assertThrows(ParsingException.class, + () -> NativeCrypto.wrap_EC_public_key_x509(rawKey, "unknowncurve")); + } + + @Test + public void unwrap_RSA_public_key_x509_success() throws Exception { + byte[] expectedRawKey = decodeHex( + "3082010a0282010100b3510a2bcd4ce644c5b594ae5059e12b2f054b658d5da5959a2fdf1871b808bc" + + "3df3e628d2792e51aad5c124b43bda453dca5cde4bcf28e7bd4effba0cb4b742bbb6d5a013cb63d1" + + "aa" + + "3a89e02627ef5398b52c0cfd97d208abeb8d7c9bce0bbeb019a86ddb589beb29a5b74bf861075c67" + + "7c" + + "81d430f030c265247af9d3c9140ccb65309d07e0adc1efd15cf17e7b055d7da3868e4648cc3a180f" + + "0e" + + "e7f8e1e7b18098a3391b4ce7161e98d57af8a947e201a463e2d6bbca8059e5706e9dfed8f4856465" + + "ff" + + "a712ed1aa18e888d12dc6aa09ce95ecfca83cc5b0b15db09c8647f5d524c0f2e7620a3416b9623ca" + + "dc" + + "0f097af573261c98c8400aa12af38e43cad84d0203010001"); + byte[] x509Key = decodeHex("30820122300d06092a864886f70d01010105000382010f003082010a02" + + "82010100b3510a2bcd4ce644c5b" + + "594ae5059e12b2f054b658d5da5959a2fdf1871b808bc3df3e628d27" + + "92e51aad5c124b43bda453dca5" + + "cde4bcf28e7bd4effba0cb4b742bbb6d5a013cb63d1aa3a89e02627e" + + "f5398b52c0cfd97d208abeb8d7" + + "c9bce0bbeb019a86ddb589beb29a5b74bf861075c677c81d430f030c" + + "265247af9d3c9140ccb65309d0" + + "7e0adc1efd15cf17e7b055d7da3868e4648cc3a180f0ee7f8e1e7b18" + + "098a3391b4ce7161e98d57af8a" + + "947e201a463e2d6bbca8059e5706e9dfed8f4856465ffa712ed1aa18" + + "e888d12dc6aa09ce95ecfca83c" + + "c5b0b15db09c8647f5d524c0f2e7620a3416b9623cadc0f097af5732" + + "61c98c8400aa12af38e43cad84" + + "d0203010001"); + + byte[] result = NativeCrypto.unwrap_RSA_public_key_x509(x509Key); + assertArrayEquals(expectedRawKey, result); + } + + @Test + public void unwrap_nonRSA_public_key_x509_fail() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> NativeCrypto.unwrap_RSA_public_key_x509(invalidKey)); + } + + // Test values from third_party/wycheproof/testvectors/ecdsa_secp256r1_sha256_test.json. @Test - public void EVP_PKEY_new_RSA_invalidParameters_throwsBoringSSLErrorAndClearsQueue() throws Exception { + public void unwrap_EC_public_key_x509_success() throws Exception { + byte[] expectedRawKey = + decodeHex("042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c77879" + + "64eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e"); + byte[] x509Key = decodeHex( + "3059301306072a8648ce3d020106082a8648ce3d030107034200" + + "042927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964" + + "eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e"); + + byte[] result = NativeCrypto.unwrap_EC_public_key_x509(x509Key); + assertArrayEquals(expectedRawKey, result); + } + + @Test + public void unwrap_nonEC_public_key_x509_fail() throws Exception { + byte[] invalidKey = decodeHex("1234567890abcdef"); + assertThrows(ParsingException.class, + () -> NativeCrypto.unwrap_EC_public_key_x509(invalidKey)); + } + + @Test + public void EVP_PKEY_new_RSA_invalidParameters_throwsBoringSSLErrorAndClearsQueue() + throws Exception { RSAPrivateCrtKey privKey = TEST_RSA_KEY; RuntimeException ex = assertThrows( - RuntimeException.class, - () -> - new NativeRef.EVP_PKEY( - NativeCrypto.EVP_PKEY_new_RSA( - // we mixed the order of the arguments to make an invalid key. - privKey.getPrivateExponent().toByteArray(), - privKey.getPublicExponent().toByteArray(), - privKey.getModulus().toByteArray(), - privKey.getPrimeP().toByteArray(), - privKey.getPrimeQ().toByteArray(), - privKey.getPrimeExponentP().toByteArray(), - privKey.getPrimeExponentQ().toByteArray(), - privKey.getCrtCoefficient().toByteArray()))); + RuntimeException.class, + () + -> new NativeRef.EVP_PKEY(NativeCrypto.EVP_PKEY_new_RSA( + // we mixed the order of the arguments to make an invalid key. + privKey.getPrivateExponent().toByteArray(), + privKey.getPublicExponent().toByteArray(), + privKey.getModulus().toByteArray(), + privKey.getPrimeP().toByteArray(), + privKey.getPrimeQ().toByteArray(), + privKey.getPrimeExponentP().toByteArray(), + privKey.getPrimeExponentQ().toByteArray(), + privKey.getCrtCoefficient().toByteArray()))); // check that the exception message contains the error message from BoringSSL. assertTrue(ex.getMessage().contains("OPENSSL_internal:")); @@ -273,10 +596,55 @@ public void EVP_PKEY_new_RSA_invalidParameters_throwsBoringSSLErrorAndClearsQueu byte[] aad = new byte[100]; byte[] output = new byte[100]; long evpAead = NativeCrypto.EVP_aead_chacha20_poly1305(); - assertThrows( - BadPaddingException.class, - () -> NativeCrypto.EVP_AEAD_CTX_open( - evpAead, encodedKey, 16, output, 0, iv, in, 0, in.length, aad)); + assertThrows(BadPaddingException.class, + () + -> NativeCrypto.EVP_AEAD_CTX_open(evpAead, encodedKey, 16, output, 0, + iv, in, 0, in.length, aad)); + } + + @Test + public void setApplicationProtocols_invalid_clearsErrorQueue() throws Exception { + long sslCtx = NativeCrypto.SSL_CTX_new(); + long ssl = NativeCrypto.SSL_new(sslCtx, null); + try { + assertThrows(SSLException.class, + () + -> NativeCrypto.setApplicationProtocols( + ssl, null, true, new byte[] {(byte) 255, 0, 0})); + + byte[] encodedKey = new byte[32]; + byte[] iv = new byte[12]; + byte[] in = new byte[100]; + byte[] aad = new byte[100]; + byte[] output = new byte[100]; + long evpAead = NativeCrypto.EVP_aead_chacha20_poly1305(); + assertThrows(BadPaddingException.class, + () + -> NativeCrypto.EVP_AEAD_CTX_open(evpAead, encodedKey, 16, output, + 0, iv, in, 0, in.length, aad)); + } finally { + NativeCrypto.SSL_free(ssl, null); + NativeCrypto.SSL_CTX_free(sslCtx, null); + } + } + + @Test + public void get_cipher_names_invalid_clearsErrorQueue() throws Exception { + IllegalArgumentException e = + assertThrows(IllegalArgumentException.class, + () -> NativeCrypto.get_cipher_names("INVALID_CIPHER_NAME")); + assertTrue(e.getMessage().contains("Unable to")); + + byte[] encodedKey = new byte[32]; + byte[] iv = new byte[12]; + byte[] in = new byte[100]; + byte[] aad = new byte[100]; + byte[] output = new byte[100]; + long evpAead = NativeCrypto.EVP_aead_chacha20_poly1305(); + assertThrows(BadPaddingException.class, + () + -> NativeCrypto.EVP_AEAD_CTX_open(evpAead, encodedKey, 16, output, 0, + iv, in, 0, in.length, aad)); } @Test @@ -482,39 +850,6 @@ public void setLocalCertsAndPrivateKey() throws Exception { NativeCrypto.SSL_CTX_free(c, null); } - @Test - public void SSL_set1_tls_channel_id_withNullChannelShouldThrow() throws Exception { - assertThrows(NullPointerException.class, - () -> NativeCrypto.SSL_set1_tls_channel_id(NULL, null, null)); - } - - @Test - public void SSL_set1_tls_channel_id_withNullKeyShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - assertThrows(NullPointerException.class, () -> { - try { - NativeCrypto.SSL_set1_tls_channel_id(s, null, null); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); - } - - @Test - public void test_SSL_use_PrivateKey_for_tls_channel_id() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - - // Use the key natively. This works because the initChannelIdKey method ensures that the - // key is backed by OpenSSL. - NativeCrypto.SSL_set1_tls_channel_id(s, null, CHANNEL_ID_PRIVATE_KEY.getNativeRef()); - - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - @Test public void SSL_get_mode_withNullShouldThrow() throws Exception { assertThrows(NullPointerException.class, () -> NativeCrypto.SSL_get_mode(NULL, null)); @@ -1013,7 +1348,6 @@ public void test_SSL_set_verify() throws Exception { public static class Hooks { String negotiatedCipherSuite; - private OpenSSLKey channelIdPrivateKey; boolean pskEnabled; byte[] pskKey; List enabledCipherSuites; @@ -1045,9 +1379,6 @@ public long beforeHandshake(long context) throws SSLException { NativeCrypto.setEnabledCipherSuites(s, null, cipherSuites.toArray(new String[0]), new String[] {"TLSv1.2"}); - if (channelIdPrivateKey != null) { - NativeCrypto.SSL_set1_tls_channel_id(s, null, channelIdPrivateKey.getNativeRef()); - } return s; } public void configureCallbacks(@SuppressWarnings("unused") @@ -1062,13 +1393,14 @@ public void afterHandshake(long session, long ssl, long context, Socket socket, NativeCrypto.SSL_SESSION_free(session); } if (ssl != NULL) { - try { - NativeCrypto.SSL_shutdown(ssl, null, fd, callback); - } catch (IOException e) { - // Expected. - } NativeCrypto.SSL_free(ssl, null); } + if (callback instanceof TestSSLHandshakeCallbacks) { + long bio = ((TestSSLHandshakeCallbacks) callback).getBioRef(); + if (bio != 0) { + NativeCrypto.BIO_free_all(bio); + } + } if (context != NULL) { NativeCrypto.SSL_CTX_free(context, null); } @@ -1083,6 +1415,25 @@ static class TestSSLHandshakeCallbacks implements SSLHandshakeCallbacks { private final long sslNativePointer; private final Hooks hooks; private final ApplicationProtocolSelectorAdapter alpnSelector; + private EngineTestConnection engineConn; + private long bioRef; + + void setEngineConn(EngineTestConnection conn) { + this.engineConn = conn; + this.bioRef = conn.bio; + } + + EngineTestConnection getEngineConn() { + return engineConn; + } + + void setBioRef(long bioRef) { + this.bioRef = bioRef; + } + + long getBioRef() { + return bioRef; + } TestSSLHandshakeCallbacks(Socket socket, long sslNativePointer, Hooks hooks, ApplicationProtocolSelectorAdapter alpnSelector) { @@ -1278,9 +1629,6 @@ public long beforeHandshake(long c) throws SSLException { static class ServerHooks extends Hooks { private final OpenSSLKey privateKey; private final byte[][] certificates; - private boolean channelIdEnabled; - private byte[] channelIdAfterHandshake; - private Throwable channelIdAfterHandshakeException; private String pskIdentityHint; @@ -1300,9 +1648,6 @@ public long beforeHandshake(long c) throws SSLException { NativeCrypto.setLocalCertsAndPrivateKey(s, null, certificates, privateKey.getNativeRef()); } - if (channelIdEnabled) { - NativeCrypto.SSL_enable_tls_channel_id(s, null); - } if (pskEnabled) { NativeCrypto.set_SSL_psk_server_callback_enabled(s, null, true); NativeCrypto.SSL_use_psk_identity_hint(s, null, pskIdentityHint); @@ -1324,13 +1669,6 @@ public void configureCallbacks(TestSSLHandshakeCallbacks callbacks) { public void afterHandshake(long session, long ssl, long context, Socket socket, FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { - if (channelIdEnabled) { - try { - channelIdAfterHandshake = NativeCrypto.SSL_get_tls_channel_id(ssl, null); - } catch (Exception e) { - channelIdAfterHandshakeException = e; - } - } super.afterHandshake(session, ssl, context, socket, fd, callback); } @@ -1340,6 +1678,165 @@ public void clientCertificateRequested(long s) { } } + static class EngineTestConnection { + final long ssl; + final long bio; + final Socket socket; + final InputStream socketIn; + final OutputStream socketOut; + final SSLHandshakeCallbacks callback; + final byte[] socketBuffer = new byte[8192]; + final byte[] bioBuffer = new byte[8192]; + final ByteBuffer directBuffer = ByteBuffer.allocateDirect(16384); + boolean atEof = false; + + EngineTestConnection(long ssl, Socket socket, SSLHandshakeCallbacks callback) + throws IOException { + this.ssl = ssl; + this.socket = socket; + this.socketIn = socket.getInputStream(); + this.socketOut = socket.getOutputStream(); + this.callback = callback; + this.bio = NativeCrypto.SSL_BIO_new(ssl, null); + } + + void flushBIO() throws IOException { + int pendingWrite = NativeCrypto.SSL_pending_written_bytes_in_BIO(bio); + while (pendingWrite > 0) { + int read = NativeCrypto.BIO_read(bio, bioBuffer); + if (read > 0) { + socketOut.write(bioBuffer, 0, read); + socketOut.flush(); + } else { + break; + } + pendingWrite = NativeCrypto.SSL_pending_written_bytes_in_BIO(bio); + } + } + + void readToBIO() throws IOException { + if (atEof) { + return; + } + int read = socketIn.read(socketBuffer); + if (read > 0) { + NativeCrypto.BIO_write(bio, socketBuffer, 0, read); + } else if (read < 0) { + atEof = true; + } + } + + int doHandshake() throws IOException { + int ret = NativeCrypto.ENGINE_SSL_do_handshake(ssl, null, callback); + flushBIO(); + return ret; + } + + void runHandshake(int timeoutMillis) throws IOException { + int oldTimeout = socket.getSoTimeout(); + if (timeoutMillis > 0) { + socket.setSoTimeout(timeoutMillis); + } + try { + boolean finished = false; + while (!finished) { + int ret = doHandshake(); + if (ret == NativeConstants.SSL_ERROR_NONE) { + finished = true; + } else if (ret == NativeConstants.SSL_ERROR_WANT_READ) { + if (atEof) { + throw new SSLProtocolException( + "Connection closed by peer during handshake"); + } + readToBIO(); + } else if (ret == NativeConstants.SSL_ERROR_WANT_WRITE) { + flushBIO(); + } else { + throw new SSLException("Handshake failed with code: " + ret); + } + } + } finally { + if (timeoutMillis > 0) { + socket.setSoTimeout(oldTimeout); + } + } + } + + int read(byte[] b, int off, int len, int timeoutMillis) + throws IOException, CertificateException { + int oldTimeout = socket.getSoTimeout(); + if (timeoutMillis > 0) { + socket.setSoTimeout(timeoutMillis); + } + try { + directBuffer.clear(); + int toRead = Math.min(len, directBuffer.remaining()); + long address = NativeCrypto.getDirectBufferAddress(directBuffer); + + while (true) { + int read = NativeCrypto.ENGINE_SSL_read_direct(ssl, null, address, toRead, + callback); + if (read > 0) { + directBuffer.position(read); + directBuffer.flip(); + directBuffer.get(b, off, read); + return read; + } + + if (read == -NativeConstants.SSL_ERROR_WANT_READ) { + if (atEof) { + return -1; + } + readToBIO(); + } else if (read == -NativeConstants.SSL_ERROR_WANT_WRITE) { + flushBIO(); + } else if (read == -NativeConstants.SSL_ERROR_ZERO_RETURN) { + return -1; // EOF + } else { + throw new SSLException("Read failed with code: " + read); + } + } + } finally { + if (timeoutMillis > 0) { + socket.setSoTimeout(oldTimeout); + } + } + } + + void write(byte[] b, int off, int len) throws IOException { + int remaining = len; + int currentOff = off; + while (remaining > 0) { + directBuffer.clear(); + int toWrite = Math.min(remaining, directBuffer.remaining()); + directBuffer.put(b, currentOff, toWrite); + directBuffer.flip(); + long address = NativeCrypto.getDirectBufferAddress(directBuffer); + + int written = 0; + while (written < toWrite) { + int ret = NativeCrypto.ENGINE_SSL_write_direct(ssl, null, address + written, + toWrite - written, callback); + if (ret > 0) { + written += ret; + flushBIO(); + } else { + int error = NativeCrypto.SSL_get_error(ssl, null, ret); + if (error == NativeConstants.SSL_ERROR_WANT_READ) { + readToBIO(); + } else if (error == NativeConstants.SSL_ERROR_WANT_WRITE) { + flushBIO(); + } else { + throw new SSLException("Write failed with error: " + error); + } + } + } + currentOff += toWrite; + remaining -= toWrite; + } + } + } + // wrapper method added for ECH testing // Note: This method only works for pre Java 17 as it uses FD sockets. // TODO(b/502061834): Rewrite this for engine sockets to make it work on Java 17+. @@ -1358,19 +1855,20 @@ public TestSSLHandshakeCallbacks call() throws Exception { if (timeout == -1) { return new TestSSLHandshakeCallbacks(socket, 0, null, null); } - FileDescriptor fd = - (FileDescriptor) m_Platform_getFileDescriptor.invoke(null, socket); long c = hooks.getContext(); long s = hooks.beforeHandshake(c); TestSSLHandshakeCallbacks callback = new TestSSLHandshakeCallbacks(socket, s, hooks, alpnSelector); hooks.configureCallbacks(callback); + + EngineTestConnection conn = new EngineTestConnection(s, socket, callback); + callback.setEngineConn(conn); + if (DEBUG) { System.out.println("ssl=0x" + Long.toString(s, 16) + " handshake" + " context=0x" + Long.toString(c, 16) - + " socket=" + socket + " fd=0x" - + Long.toString(System.identityHashCode(fd), 16) - + " timeout=" + timeout + " client=" + client); + + " socket=" + socket + " timeout=" + timeout + + " client=" + client); } long session = NULL; try { @@ -1387,7 +1885,7 @@ public TestSSLHandshakeCallbacks call() throws Exception { NativeCrypto.setHasApplicationProtocolSelector(s, null, true); } - NativeCrypto.SSL_do_handshake(s, null, fd, callback, timeout); + conn.runHandshake(timeout); session = NativeCrypto.SSL_get1_session(s, null); if (DEBUG) { @@ -1396,7 +1894,7 @@ public TestSSLHandshakeCallbacks call() throws Exception { } } finally { // Ensure afterHandshake is called to free resources - hooks.afterHandshake(session, s, c, socket, fd, callback); + hooks.afterHandshake(session, s, c, socket, null, callback); } return callback; } @@ -1408,22 +1906,7 @@ public TestSSLHandshakeCallbacks call() throws Exception { @Test public void test_SSL_do_handshake_NULL_SSL() throws Exception { assertThrows(NullPointerException.class, - () -> NativeCrypto.SSL_do_handshake(NULL, null, null, null, 0)); - } - - @Test - public void test_SSL_do_handshake_withNullFdShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - NativeCrypto.SSL_set_connect_state(s, null); - assertThrows(NullPointerException.class, () -> { - try { - NativeCrypto.SSL_do_handshake(s, null, null, null, 0); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); + () -> NativeCrypto.ENGINE_SSL_do_handshake(NULL, null, null)); } @Test @@ -1433,7 +1916,7 @@ public void test_SSL_do_handshake_withNullShcShouldThrow() throws Exception { NativeCrypto.SSL_set_connect_state(s, null); assertThrows(NullPointerException.class, () -> { try { - NativeCrypto.SSL_do_handshake(s, null, INVALID_FD, null, 0); + NativeCrypto.ENGINE_SSL_do_handshake(s, null, null); } finally { NativeCrypto.SSL_free(s, null); NativeCrypto.SSL_CTX_free(c, null); @@ -1472,7 +1955,6 @@ public void test_SSL_do_handshake_normal() throws Exception { assertFalse(clientCallback.serverCertificateRequestedInvoked); assertTrue(serverCallback.serverCertificateRequestedInvoked); assertNotNull(serverCallback.serverSignatureAlgs); - // g3-add: assertTrue(serverCallback.serverSignatureAlgs.length > 0); } @Test @@ -1704,123 +2186,6 @@ public void test_SSL_do_handshake_server_timeout() throws Exception { } } - @Test - public void test_SSL_do_handshake_with_channel_id_normal() throws Exception { - // This test only works on older versions of Java, see b/502061834. - assumeFalse(TestUtils.isJavaVersion(17)); - - // Normal handshake with TLS Channel ID. - final ServerSocket listener = newServerSocket(); - Hooks cHooks = new Hooks(); - cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY; - // TLS Channel ID currently requires ECDHE-based key exchanges. - cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA"); - ServerHooks sHooks = new ServerHooks(SERVER_PRIVATE_KEY, ENCODED_SERVER_CERTIFICATES); - sHooks.channelIdEnabled = true; - sHooks.enabledCipherSuites = cHooks.enabledCipherSuites; - Future client = handshake(listener, 0, true, cHooks, null, null); - Future server = - handshake(listener, 0, false, sHooks, null, null); - TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - assertTrue(clientCallback.verifyCertificateChainCalled); - assertEqualCertificateChains(SERVER_CERTIFICATE_REFS, clientCallback.certificateChainRefs); - assertEquals("ECDHE_RSA", clientCallback.authMethod); - assertFalse(serverCallback.verifyCertificateChainCalled); - assertFalse(clientCallback.clientCertificateRequestedCalled); - assertFalse(serverCallback.clientCertificateRequestedCalled); - assertFalse(clientCallback.clientPSKKeyRequestedInvoked); - assertFalse(serverCallback.clientPSKKeyRequestedInvoked); - assertFalse(clientCallback.serverPSKKeyRequestedInvoked); - assertFalse(serverCallback.serverPSKKeyRequestedInvoked); - assertTrue(clientCallback.onNewSessionEstablishedInvoked); - assertTrue(serverCallback.onNewSessionEstablishedInvoked); - assertTrue(clientCallback.handshakeCompletedCalled); - assertTrue(serverCallback.handshakeCompletedCalled); - assertNull(sHooks.channelIdAfterHandshakeException); - assertFalse(clientCallback.serverCertificateRequestedInvoked); - assertTrue(serverCallback.serverCertificateRequestedInvoked); - assertEqualByteArrays(CHANNEL_ID, sHooks.channelIdAfterHandshake); - } - - @Test - public void test_SSL_do_handshake_with_channel_id_not_supported_by_server() throws Exception { - // This test only works on older versions of Java, see b/502061834. - assumeFalse(TestUtils.isJavaVersion(17)); - - // Client tries to use TLS Channel ID but the server does not enable/offer the extension. - final ServerSocket listener = newServerSocket(); - Hooks cHooks = new Hooks(); - cHooks.channelIdPrivateKey = CHANNEL_ID_PRIVATE_KEY; - // TLS Channel ID currently requires ECDHE-based key exchanges. - cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA"); - ServerHooks sHooks = new ServerHooks(SERVER_PRIVATE_KEY, ENCODED_SERVER_CERTIFICATES); - sHooks.channelIdEnabled = false; - sHooks.enabledCipherSuites = cHooks.enabledCipherSuites; - Future client = handshake(listener, 0, true, cHooks, null, null); - Future server = - handshake(listener, 0, false, sHooks, null, null); - TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - assertTrue(clientCallback.verifyCertificateChainCalled); - assertEqualCertificateChains(SERVER_CERTIFICATE_REFS, clientCallback.certificateChainRefs); - assertEquals("ECDHE_RSA", clientCallback.authMethod); - assertFalse(serverCallback.verifyCertificateChainCalled); - assertFalse(clientCallback.clientCertificateRequestedCalled); - assertFalse(serverCallback.clientCertificateRequestedCalled); - assertFalse(clientCallback.clientPSKKeyRequestedInvoked); - assertFalse(serverCallback.clientPSKKeyRequestedInvoked); - assertFalse(clientCallback.serverPSKKeyRequestedInvoked); - assertFalse(serverCallback.serverPSKKeyRequestedInvoked); - assertTrue(clientCallback.onNewSessionEstablishedInvoked); - assertTrue(serverCallback.onNewSessionEstablishedInvoked); - assertTrue(clientCallback.handshakeCompletedCalled); - assertTrue(serverCallback.handshakeCompletedCalled); - assertFalse(clientCallback.serverCertificateRequestedInvoked); - assertTrue(serverCallback.serverCertificateRequestedInvoked); - assertNull(sHooks.channelIdAfterHandshakeException); - assertNull(sHooks.channelIdAfterHandshake); - } - - @Test - public void test_SSL_do_handshake_with_channel_id_not_enabled_by_client() throws Exception { - // This test only works on older versions of Java, see b/502061834. - assumeFalse(TestUtils.isJavaVersion(17)); - - // Client does not use TLS Channel ID when the server has the extension enabled/offered. - final ServerSocket listener = newServerSocket(); - Hooks cHooks = new Hooks(); - cHooks.channelIdPrivateKey = null; - // TLS Channel ID currently requires ECDHE-based key exchanges. - cHooks.enabledCipherSuites = Collections.singletonList("ECDHE-RSA-AES128-SHA"); - ServerHooks sHooks = new ServerHooks(SERVER_PRIVATE_KEY, ENCODED_SERVER_CERTIFICATES); - sHooks.channelIdEnabled = true; - sHooks.enabledCipherSuites = cHooks.enabledCipherSuites; - Future client = handshake(listener, 0, true, cHooks, null, null); - Future server = - handshake(listener, 0, false, sHooks, null, null); - TestSSLHandshakeCallbacks clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - TestSSLHandshakeCallbacks serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - assertTrue(clientCallback.verifyCertificateChainCalled); - assertEqualCertificateChains(SERVER_CERTIFICATE_REFS, clientCallback.certificateChainRefs); - assertEquals("ECDHE_RSA", clientCallback.authMethod); - assertFalse(serverCallback.verifyCertificateChainCalled); - assertFalse(clientCallback.clientCertificateRequestedCalled); - assertFalse(serverCallback.clientCertificateRequestedCalled); - assertFalse(clientCallback.clientPSKKeyRequestedInvoked); - assertFalse(serverCallback.clientPSKKeyRequestedInvoked); - assertFalse(clientCallback.serverPSKKeyRequestedInvoked); - assertFalse(serverCallback.serverPSKKeyRequestedInvoked); - assertTrue(clientCallback.onNewSessionEstablishedInvoked); - assertTrue(serverCallback.onNewSessionEstablishedInvoked); - assertTrue(clientCallback.handshakeCompletedCalled); - assertTrue(serverCallback.handshakeCompletedCalled); - assertFalse(clientCallback.serverCertificateRequestedInvoked); - assertTrue(serverCallback.serverCertificateRequestedInvoked); - assertNull(sHooks.channelIdAfterHandshakeException); - assertNull(sHooks.channelIdAfterHandshake); - } - @Test public void test_SSL_do_handshake_with_psk_normal() throws Exception { // This test only works on older versions of Java, see b/502061834. @@ -2347,7 +2712,7 @@ public long beforeHandshake(long c) throws SSLException { client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); fail(); } catch (ExecutionException expected) { - assertEquals(SSLHandshakeException.class, expected.getCause().getClass()); + assertEquals(SSLProtocolException.class, expected.getCause().getClass()); } try { server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); @@ -2681,21 +3046,7 @@ public void test_SSL_cipher_names() throws Exception { @Test public void SSL_read_withNullSslShouldThrow() throws Exception { assertThrows(NullPointerException.class, - () -> NativeCrypto.SSL_read(NULL, null, null, null, null, 0, 0, 0)); - } - - @Test - public void SSL_read_withNullFdShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - assertThrows(NullPointerException.class, () -> { - try { - NativeCrypto.SSL_read(s, null, null, DUMMY_CB, null, 0, 0, 0); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); + () -> NativeCrypto.ENGINE_SSL_read_direct(NULL, null, 0, 0, null)); } @Test @@ -2704,35 +3055,7 @@ public void SSL_read_withNullCallbacksShouldThrow() throws Exception { long s = NativeCrypto.SSL_new(c, null); assertThrows(NullPointerException.class, () -> { try { - NativeCrypto.SSL_read(s, null, INVALID_FD, null, null, 0, 0, 0); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); - } - - @Test - public void SSL_read_withNullBytesShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - assertThrows(NullPointerException.class, () -> { - try { - NativeCrypto.SSL_read(s, null, INVALID_FD, DUMMY_CB, null, 0, 0, 0); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); - } - - @Test - public void SSL_read_beforeHandshakeShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - assertThrows(SSLException.class, () -> { - try { - NativeCrypto.SSL_read(s, null, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0); + NativeCrypto.ENGINE_SSL_read_direct(s, null, 0, 0, null); } finally { NativeCrypto.SSL_free(s, null); NativeCrypto.SSL_CTX_free(c, null); @@ -2755,9 +3078,9 @@ public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { byte[] in = new byte[256]; - assertEquals( - BYTES.length, - NativeCrypto.SSL_read(s, null, fd, callback, in, 0, BYTES.length, 0)); + EngineTestConnection conn = + ((TestSSLHandshakeCallbacks) callback).getEngineConn(); + assertEquals(BYTES.length, conn.read(in, 0, BYTES.length, 0)); for (int i = 0; i < BYTES.length; i++) { assertEquals(BYTES[i], in[i]); } @@ -2769,7 +3092,9 @@ public void afterHandshake(long session, long s, long c, Socket sock, public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { - NativeCrypto.SSL_write(s, null, fd, callback, BYTES, 0, BYTES.length, 0); + EngineTestConnection conn = + ((TestSSLHandshakeCallbacks) callback).getEngineConn(); + conn.write(BYTES, 0, BYTES.length); super.afterHandshake(session, s, c, sock, fd, callback); } }; @@ -2788,7 +3113,9 @@ public void afterHandshake(long session, long s, long c, Socket sock, public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { - NativeCrypto.SSL_read(s, null, fd, callback, new byte[1], 0, 1, 1); + EngineTestConnection conn = + ((TestSSLHandshakeCallbacks) callback).getEngineConn(); + conn.read(new byte[1], 0, 1, 1); fail(); } }; @@ -2797,7 +3124,9 @@ public void afterHandshake(long session, long s, long c, Socket sock, public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { - NativeCrypto.SSL_read(s, null, fd, callback, new byte[1], 0, 1, 0); + EngineTestConnection conn = + ((TestSSLHandshakeCallbacks) callback).getEngineConn(); + conn.read(new byte[1], 0, 1, 0); super.afterHandshake(session, s, c, sock, fd, callback); } }; @@ -2816,21 +3145,7 @@ public void afterHandshake(long session, long s, long c, Socket sock, @Test public void SSL_write_withNullSslShouldThrow() throws Exception { assertThrows(NullPointerException.class, - () -> NativeCrypto.SSL_write(NULL, null, null, null, null, 0, 0, 0)); - } - - @Test - public void SSL_write_withNullFdShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - assertThrows(NullPointerException.class, () -> { - try { - NativeCrypto.SSL_write(s, null, null, DUMMY_CB, null, 0, 1, 0); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); + () -> NativeCrypto.ENGINE_SSL_write_direct(NULL, null, 0, 0, null)); } @Test @@ -2839,7 +3154,7 @@ public void SSL_write_withNullCallbacksShouldThrow() throws Exception { long s = NativeCrypto.SSL_new(c, null); assertThrows(NullPointerException.class, () -> { try { - NativeCrypto.SSL_write(s, null, INVALID_FD, null, null, 0, 1, 0); + NativeCrypto.ENGINE_SSL_write_direct(s, null, 0, 0, null); } finally { NativeCrypto.SSL_free(s, null); NativeCrypto.SSL_CTX_free(c, null); @@ -2847,93 +3162,6 @@ public void SSL_write_withNullCallbacksShouldThrow() throws Exception { }); } - @Test - public void SSL_write_withNullBytesShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - assertThrows(NullPointerException.class, () -> { - try { - NativeCrypto.SSL_write(s, null, INVALID_FD, DUMMY_CB, null, 0, 1, 0); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); - } - - @Test - public void SSL_write_beforeHandshakeShouldThrow() throws Exception { - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - assertThrows(SSLException.class, () -> { - try { - NativeCrypto.SSL_write(s, null, INVALID_FD, DUMMY_CB, new byte[1], 0, 1, 0); - } finally { - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - }); - } - - @Test - public void SSL_interrupt_withNullShouldSucceed() { - // SSL_interrupt is a rare case that tolerates a null SSL argument - NativeCrypto.SSL_interrupt(NULL, null); - } - - @Test - public void SSL_interrupt_withoutHandshakeShouldSucceed() throws Exception { - // also works without handshaking - long c = NativeCrypto.SSL_CTX_new(); - long s = NativeCrypto.SSL_new(c, null); - NativeCrypto.SSL_interrupt(s, null); - NativeCrypto.SSL_free(s, null); - NativeCrypto.SSL_CTX_free(c, null); - } - - @Test - public void test_SSL_interrupt() throws Exception { - // This test only works on older versions of Java, see b/502061834. - assumeFalse(TestUtils.isJavaVersion(17)); - - final ServerSocket listener = newServerSocket(); - - Hooks cHooks = new Hooks() { - @Override - public void afterHandshake(long session, long s, long c, Socket sock, FileDescriptor fd, - SSLHandshakeCallbacks callback) throws Exception { - NativeCrypto.SSL_read(s, null, fd, callback, new byte[1], 0, 1, 0); - super.afterHandshake(session, s, c, sock, fd, callback); - } - }; - Hooks sHooks = new ServerHooks(SERVER_PRIVATE_KEY, ENCODED_SERVER_CERTIFICATES) { - @Override - public void afterHandshake(long session, final long s, long c, Socket sock, - FileDescriptor fd, SSLHandshakeCallbacks callback) - throws Exception { - new Thread() { - @Override - public void run() { - try { - Thread.sleep(1000); - NativeCrypto.SSL_interrupt(s, null); - } catch (Exception e) { - // Expected. - } - } - }.start(); - assertEquals(-1, - NativeCrypto.SSL_read(s, null, fd, callback, new byte[1], 0, 1, 0)); - super.afterHandshake(session, s, c, sock, fd, callback); - } - }; - Future client = handshake(listener, 0, true, cHooks, null, null); - Future server = - handshake(listener, 0, false, sHooks, null, null); - client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); - } - private static abstract class SSLSessionWrappedTask { public abstract void run(long sslSession) throws Exception; } @@ -2949,24 +3177,13 @@ private void wrapWithSSLSession(SSLSessionWrappedTask task) throws Exception { } } - @Test - public void SSL_shutdown_withNullFdShouldSucceed() throws Exception { - // We tolerate a null FileDescriptor - wrapWithSSLSession(new SSLSessionWrappedTask() { - @Override - public void run(long sslSession) throws Exception { - NativeCrypto.SSL_shutdown(sslSession, null, null, DUMMY_CB); - } - }); - } - @Test public void SSL_shutdown_withNullCallbacksShouldThrow() throws Exception { assertThrows(NullPointerException.class, () -> wrapWithSSLSession(new SSLSessionWrappedTask() { @Override public void run(long sslSession) throws Exception { - NativeCrypto.SSL_shutdown(sslSession, null, INVALID_FD, null); + NativeCrypto.ENGINE_SSL_shutdown(sslSession, null, null); } })); } @@ -2974,16 +3191,16 @@ public void run(long sslSession) throws Exception { @Test public void SSL_shutdown_withNullSslShouldSucceed() throws Exception { // SSL_shutdown is a rare case that tolerates a null SSL argument - NativeCrypto.SSL_shutdown(NULL, null, INVALID_FD, DUMMY_CB); + NativeCrypto.ENGINE_SSL_shutdown(NULL, null, DUMMY_CB); } @Test public void SSL_shutdown_beforeHandshakeShouldThrow() throws Exception { // handshaking not yet performed - assertThrows(SocketException.class, () -> wrapWithSSLSession(new SSLSessionWrappedTask() { + assertThrows(SSLException.class, () -> wrapWithSSLSession(new SSLSessionWrappedTask() { @Override public void run(long sslSession) throws Exception { - NativeCrypto.SSL_shutdown(sslSession, null, INVALID_FD, DUMMY_CB); + NativeCrypto.ENGINE_SSL_shutdown(sslSession, null, DUMMY_CB); } })); @@ -4024,6 +4241,93 @@ public void EVP_PKEY_encrypt_InputIndexLengthOOB() throws Exception { 0, new byte[128], 100, 29)); } + @Test + public void EVP_PKEY_CTX_set1_signature_context_string_nullPkeyCtxThrows() throws Exception { + assertThrows( + NullPointerException.class, + () -> NativeCrypto.EVP_PKEY_CTX_set1_signature_context_string(NULL, new byte[0])); + } + + @Test + public void EVP_PKEY_CTX_set1_signature_context_string_works() throws Exception { + // Test case from wycheproof/testvectors_v1/mldsa_44_sign_seed_test.json. + byte[] seed = TestUtils.decodeHex( + "2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a"); + NativeRef.EVP_PKEY privateKey = new NativeRef.EVP_PKEY( + NativeCrypto.EVP_PKEY_from_private_seed(NativeConstants.EVP_PKEY_ML_DSA_44, seed)); + byte[] contextStr = TestUtils.decodeHex("436f6e74657874"); + byte[] data = TestUtils.decodeHex("48656c6c6f20776f726c64"); + byte[] expectedSig = TestUtils.decodeHex( + "e11d24772c24efc107ae3abb0149817436f11684d3548748cba19fc0b373ddcb7c8f68f00407d96457" + + "0c155a9a34823d5b33345a2bb4dfc43d2e178331bc6573f39d634239230cfc160bf03f41d176854d" + + "fee5be915ed6c3f4112fff50d8effcc457708261e715fdf0676831989a15cbd16b92fc97bec06c75" + + "919c114c167d2bfae8d7dfa384068c0d96a8e6039e755f9b90cb57b4b0e678854a88a8fada69b91b" + + "bbea873f81a7489c0e3612774e8a00370b9b9650331bd2184b9037ce340d82b39436dab990f0c176" + + "b90421e71fd182bc07ed70e54587bf2b92c038e8794aded666a6c9cdb29d8747c223967c5a283d3b" + + "e2946584202a021c5264e04587b3c60bb5ec7a73e2d4d7caf4619e388d1beff4ec4bf7d104fee347" + + "65ab6a51108660f052a05d16aa46efc49d46ff42d65bbc6521d8a18c8cbe104de453367bae5c72b4" + + "3854def8222480746003fc8ec4efa2d122965ef9e0e5b3d68c9069af54ef4511036a079d9bb67a43" + + "eabec138d37eeaa918bf14815159b0216352a354110d5c835ea9631075317ba617085f2d86215c09" + + "c288a584add2809bcc7f50f9071fee5ea2fc08020f2a106fad222155155018f67162855ce6243287" + + "24b659c645cc30c6382c6fdf48e1c9e8499bf6f8ccd63f06113e3262efd0800d2619d59cd8966d84" + + "7c2de3854634f3b5e83f84e66cac84e1013b93fe3869f270380ccf8c26591a2635cfa048d1955516" + + "560c95ce0c39b0cd7c12c3234b13939386adcf557118f21811c3595151919da2bce155f9c6300703" + + "a7209fcd893305486df90a828bc551f23878b72f04fe471ed75982175b74ce135fbdf0c786acdefb" + + "09829afdaf7eab308cd8c181345e8f713afd5b433a6be59a4e70b421c216a02a16bf0e9276309922" + + "11d48d71ac0aec3d0626d84456303c3f35c132571eeafa0106cc7ff333e0d2dcd9352b3cdf36a8fe" + + "c2a750e5c8ebfeed52a94e5f41c1d295ddc01de6ddbf9df9970460f33fb362b0b94fac9b496459c6" + + "ca989e90d53ec8944d1518d7fcc21f1adca0bac93df266820dfbe9c7cbce4b762340ef8ea6464d26" + + "c5fd4f2b67b9776548b567d7426511aa9c2fdd19d85206130ab6cf6d7f5115dcb7f53b628b99ed8f" + + "a1bd6055764f950deeabae276b419370c4700cd37ca2a34b387d644d4e0ef6a380a5e2d2f32376b4" + + "b8752bfc3003c2b67111105b775fd21c3e5ae678f79975097e6c63e759eae6b14d60c9778b4bc31a" + + "aa4c9f4fa4911688dc390047aa11f9a998baa652eb9be561cb4039bd9801fd62eedb6f568ff4189d" + + "ffa4c9a7bc11d9faf26499285098043fe699b565545a930d9ce8f5247eea4c5f6df27f3e050b8d01" + + "eee5dd1058efe65190eebeaa0742515d9f8f36bd29e6d84e56d9e41c1a551d3ce6ad7e8967872abd" + + "60488d4172c56006eb2db95cb25743287a1d73fb3a36ca4d7f7dce22fd2baf10ad47aeacf82b37da" + + "fad7c06a6795be40bd6abfc8f998219f2a0e58531c8ccd1bf3ce66b960741a2da9d36971bad67ee4" + + "d75e660e0805e889eab0f0be62b38439476ec289e77176341461b474f66f44120f784de5490529a1" + + "f6f013eac2dfbdea11275733f1b1723357740a903085e09e8d61a2e2c84f26ddf95fe630a398329e" + + "48cd58cbf358b98b839c7f17893b6e913ee286c976bea3a0bbc58177ce0a35a28c5bb4ac6d9d5ffd" + + "b9dc626555a55bea17386237d8ccf2ef60a31393b1f49a37329598f706eeeca9c2d0b02ef13dfa6b" + + "b9f1e84517aa51d7d7e85ffbdacf23892962d231f67c142df49d6236630bdb50dad047bc84fec4f5" + + "17758c3f54c77f5f25fe78a12db9e4dd766198d6014b35cdbab0257cc50c7f9dfa5ac0a88c7d107c" + + "8f6bb50dee4d7a3e35cc54fb12572d901f02f4e8bf15cb6fef1910fcd5d54530dbca4046bd9ba303" + + "9c4ff97bcbfb6d00a16c1f902a25005c30d3d0d96a9d7116b15f81699614afe0aa448973b6da55c1" + + "8f20395a15d2ac53c5725e45711f9b3050ca8f409d4776b568afa8d6657668e7d6d3553d23bdbe09" + + "cd1957fc5c76fb733b237e60073dfff5d64ad3f03d3116fe1db0ee27c36b9671b0efa079cb0ae055" + + "8023ac6a0aa36f1f2d887805658131398f78b4c2fb2e0bfc4a37e444015879f0db10abd5b56d5993" + + "a3ccc0798651c0b85b658285cd00e898be4406a431e29d861379c26ed26cee7f23c05fba0519fa6d" + + "0336120dffd6d441d7de14233ff6c345425b852e1cbef6ac4d442e6f121975b912b9e60538b5efe7" + + "4c3df3861671b54d96d1d512725fe63b511c4d90261577f8a992746cfe6a4e1426a3d9fcbdb3098a" + + "626681ed5c41c3158667708c321a515a978c47c337b1d9cdf6be83fae368d57843baaea2b8b7a943" + + "98a8fcdb3b3e39c55a8feceae53f4b2b8967f5a7f671d7cff584596682ed7436979ee9e8610bdcdd" + + "0c065b39e22b3fefdb8ebbe7ea59ddb2058980f8c186ec95428a8cea2c41376312a073543283f2c8" + + "a970b11f1f31dc531748292cf198c63b2f21996f2bf769d397083f5f7c2da8952b38a199a2fa2698" + + "e156cc5550f123d99d4f65852fab97e184f0f615ac419af60c236f4e1c3c209b4eda22ec47c963d6" + + "b5318031cda0b1ce9dd0876b0a011d9d1a8a1233c38538581401dcb8766c4c9147d257828a0068a9" + + "1e458e3a312e398c2b1affcbd7a702efdcb3f79a28d131667545f2ac3d04fefee0228f257e689a85" + + "fb92f528d901768a2dfda51f65ad31e1b781759cde2a44adf0a4b84639a8160bf863445f94a04ab7" + + "885fa247fe057c161246f1202bad84345aea9e34b77ef93fe01d090f49e1ba3e214acfea26bc04e4" + + "bb2ef2f4fa2af4751a873573ee273d8ab7f1d59aad74c8da98232e2562966b6816f01c1db37c0b5a" + + "55710011656ff76f8eb4bbba1e5875e954f1dc43bbd0d77b09cfbc57890acedf796507d31fee6330" + + "5cc97209964cc7897befd20db3d6203a317bc8769b8b0081016f2180eb3b40d24ac1458d0afb8034" + + "b8babe87c91ead17f25715104be58a526409e8f5053b67e48d7de17a2f81f68a679a6d9192120eda" + + "7564c7970c88d4aa266f7063d6b24de7b402c69d9d14f8d51b3bdff45e952c45ead4e729d195f930" + + "870fda380f64085011fff63caca5e79d1dae0b2b0dad7e01c4b7b2714b20d3bb69dcee4fe9e04124" + + "20b55abba95bacbc1b1fe498474d8d3a5396968b057b8b5081ddb57eaae581da0a1b482879cdc1bd" + + "a82fe83d4007375831cf06bcd334ac42c780cb91121eb4021f39f9292a6a023b1010b35d378a7986" + + "01cd4a6cfebc0f45b1e7879a8f884e3d465a6680a0b8cbd5e0f210111a40464a586184859299a5e3" + + "29384751b5c5c6eceef1ff1d3864879398a1b5b7cbd8000000000000000000000000000000000000" + + "0000000000000000000000000000000b19242f"); + NativeRef.EVP_MD_CTX ctx = new NativeRef.EVP_MD_CTX(NativeCrypto.EVP_MD_CTX_create()); + long pkeyCtxVerify = NativeCrypto.EVP_DigestVerifyInit(ctx, 0, privateKey); + + NativeCrypto.EVP_PKEY_CTX_set1_signature_context_string(pkeyCtxVerify, contextStr); + + assertTrue(NativeCrypto.EVP_DigestVerify(ctx, expectedSig, 0, expectedSig.length, data, 0, + data.length)); + } + @Test public void EVP_PKEY_CTX_set_rsa_mgf1_md_NullPkeyCtx() throws Exception { assertThrows(NullPointerException.class, @@ -4686,4 +4990,149 @@ public void x25519_nullInput_throwsIllegalArgumentException() throws Exception { assertThrows(NullPointerException.class, () -> NativeCrypto.X25519(new byte[32], new byte[32], null)); } + @Test + public void asn1_read_oid_raw_works() throws Exception { + byte[] validOid = decodeHex("06082b06010505070627"); + long cbs = NativeCrypto.asn1_read_init(validOid); + try { + assertEquals("1.3.6.1.5.5.7.6.39", NativeCrypto.asn1_read_oid_raw(cbs)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } + + @Test + public void asn1_read_oid_raw_wrongOidTag_throws() throws Exception { + // OID tag byte 0x07 instead of 0x06. + byte[] invalidTag = decodeHex("07082b06010505070627"); + long cbs = NativeCrypto.asn1_read_init(invalidTag); + try { + assertThrows(IOException.class, () -> NativeCrypto.asn1_read_oid_raw(cbs)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } + + @Test + public void asn1_read_oid_raw_wrongOidLength_tooLong_throws() throws Exception { + // OID length byte 0x13 instead of 0x08. + byte[] invalidLong = decodeHex("06132b06010505070627"); + long cbs = NativeCrypto.asn1_read_init(invalidLong); + try { + assertThrows(IOException.class, () -> NativeCrypto.asn1_read_oid_raw(cbs)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } + + @Test + public void asn1_read_oid_raw_wrongOidLength_tooShort_succeedsButLeavesData() throws Exception { + // OID length byte 0x01 instead of 0x08. + byte[] invalidShort = decodeHex("06012b06010505070627"); + long cbs = NativeCrypto.asn1_read_init(invalidShort); + try { + // "06 01 2b" successfully parses as OID "1.3", leaving the rest of the bytes. + assertEquals("1.3", NativeCrypto.asn1_read_oid_raw(cbs)); + assertEquals(false, NativeCrypto.asn1_read_is_empty(cbs)); + } finally { + } + } + @Test + public void asn1_write_oid_raw_works() throws Exception { + long cbb = NativeCrypto.asn1_write_init(); + try { + NativeCrypto.asn1_write_oid_raw(cbb, "1.3.6.1.5.5.7.6.39"); + byte[] encoded = NativeCrypto.asn1_write_finish(cbb); + cbb = 0; + assertArrayEquals(decodeHex("06082b06010505070627"), encoded); + } finally { + if (cbb != 0) { + NativeCrypto.asn1_write_free(cbb); + } + } + } + + @Test + public void asn1_write_oid_raw_invalidOid_throws() throws Exception { + long cbb = NativeCrypto.asn1_write_init(); + try { + assertThrows(IOException.class, + () -> NativeCrypto.asn1_write_oid_raw(cbb, "1.3.notanoid")); + } finally { + NativeCrypto.asn1_write_free(cbb); + } + } + + @Test + public void asn1_write_bitstring_works() throws Exception { + long cbb = NativeCrypto.asn1_write_init(); + try { + NativeCrypto.asn1_write_bitstring(cbb, new byte[] {(byte) 0xaa}); + byte[] encoded = NativeCrypto.asn1_write_finish(cbb); + cbb = 0; + assertArrayEquals(decodeHex("030200aa"), encoded); + } finally { + if (cbb != 0) { + NativeCrypto.asn1_write_free(cbb); + } + } + } + + @Test + public void asn1_read_bitstring_payload_works() throws Exception { + byte[] bitString = decodeHex("030200aa"); // TAG 03, LEN 02, Padding 00, Payload aa + long cbs = NativeCrypto.asn1_read_init(bitString); + try { + assertArrayEquals(new byte[] {(byte) 0xaa}, + NativeCrypto.asn1_read_bitstring_payload(cbs, 0)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } + + @Test + public void asn1_read_bitstring_payload_wrongTag_throws() throws Exception { + byte[] invalidTag = decodeHex("040200aa"); // OCTET STRING 04 instead of BIT STRING 03 + long cbs = NativeCrypto.asn1_read_init(invalidTag); + try { + assertThrows(IOException.class, () -> NativeCrypto.asn1_read_bitstring_payload(cbs, 0)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } + + @Test + public void asn1_read_bitstring_payload_wrongLength_throws() throws Exception { + byte[] invalidLong = decodeHex("031300aa"); + long cbs = NativeCrypto.asn1_read_init(invalidLong); + try { + assertThrows(IOException.class, () -> NativeCrypto.asn1_read_bitstring_payload(cbs, 0)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } + + @Test + public void asn1_read_bitstring_payload_wrongPadding_throws() throws Exception { + // Encoded expects 0 padding, but the actual bytes specify 1 unused bit padding. + byte[] invalidPadding = decodeHex("030201aa"); + long cbs = NativeCrypto.asn1_read_init(invalidPadding); + try { + assertThrows(IOException.class, () -> NativeCrypto.asn1_read_bitstring_payload(cbs, 0)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } + + @Test + public void asn1_read_bitstring_payload_mismatchedExpectedPadding_throws() throws Exception { + byte[] bitString = decodeHex("030200aa"); // Encoded padding is 0. + long cbs = NativeCrypto.asn1_read_init(bitString); + try { + // But we tell the parser to explicitly expect padding 1. + assertThrows(IOException.class, () -> NativeCrypto.asn1_read_bitstring_payload(cbs, 1)); + } finally { + NativeCrypto.asn1_read_free(cbs); + } + } } diff --git a/openjdk/src/test/java/org/conscrypt/NativeSslTest.java b/openjdk/src/test/java/org/conscrypt/NativeSslTest.java index 2d22fb4bd..37d2b5e6b 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeSslTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeSslTest.java @@ -16,8 +16,8 @@ package org.conscrypt; import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import org.junit.Test; import org.junit.runner.RunWith; @@ -45,8 +45,6 @@ public void toBoringSslGroups_convertsKnownValues() { NativeSsl.toBoringSslGroups(new String[] {"secp521r1"})); assertArrayEquals(new int[] {NativeConstants.NID_X25519MLKEM768}, NativeSsl.toBoringSslGroups(new String[] {"X25519MLKEM768"})); - assertArrayEquals(new int[] {NativeConstants.NID_X25519Kyber768Draft00}, - NativeSsl.toBoringSslGroups(new String[] {"X25519Kyber768Draft00"})); assertArrayEquals(new int[] {NativeConstants.NID_ML_KEM_1024}, NativeSsl.toBoringSslGroups(new String[] {"MLKEM1024"})); } diff --git a/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java b/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java index c3532dbd7..6951d22eb 100644 --- a/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java +++ b/openjdk/src/test/java/org/conscrypt/SSLUtilsTest.java @@ -26,6 +26,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -111,13 +112,13 @@ public void decodeProtocolsWithLongLengthsShouldSucceed() { @Test public void decodeProtocolsWithInvalidLengthShouldThrow() { assertThrows(IllegalArgumentException.class, - () -> SSLUtils.decodeProtocols(new byte[] { 1 })); + () -> SSLUtils.decodeProtocols(new byte[] {1})); assertThrows(IllegalArgumentException.class, - () -> SSLUtils.decodeProtocols(new byte[] { 2, 'a' })); + () -> SSLUtils.decodeProtocols(new byte[] {2, 'a'})); assertThrows(IllegalArgumentException.class, - () -> SSLUtils.decodeProtocols(new byte[] { 1, 'a', 5, 'b' })); + () -> SSLUtils.decodeProtocols(new byte[] {1, 'a', 5, 'b'})); } @Test @@ -216,6 +217,19 @@ public void testGetSupportedClientKeyTypes_ordered() { assertEquals(Arrays.asList("EC", "RSA"), keyTypes); } + @Test + public void testGetEncryptedPacketLength_oversized() { + ByteBuffer buffer = ByteBuffer.allocate(5); + buffer.put((byte) 23); // SSL3_RT_APPLICATION_DATA + buffer.put((byte) 3); // majorVersion + buffer.put((byte) 3); // minorVersion + buffer.putShort((short) (NativeConstants.SSL3_RT_MAX_PACKET_SIZE + - NativeConstants.SSL3_RT_HEADER_LENGTH + 1)); + buffer.flip(); + + assertEquals(-1, SSLUtils.getEncryptedPacketLength(new ByteBuffer[] {buffer}, 0)); + } + @Test public void engineStateValues() { int[] expectedValues = { diff --git a/platform/src/main/ct_log_store.fbs b/platform/src/main/ct_log_store.fbs new file mode 100644 index 000000000..9a15f998a --- /dev/null +++ b/platform/src/main/ct_log_store.fbs @@ -0,0 +1,38 @@ +namespace com.android.org.conscrypt.ct.fbs; + +file_identifier "CTFB"; +file_extension "ctfb"; + +enum LogType:byte { + Unknown = 0, + Rfc6962 = 1, + Static = 2 +} + +enum LogState:byte { + Unknown = 0, + Pending = 1, + Qualified = 2, + Usable = 3, + Readonly = 4, + Retired = 5, + Rejected = 6 +} + +table Log { + log_id:string (key); + public_key:[byte]; + operator:string; + type:LogType; + state:LogState; + state_timestamp:long; +} + +table LogList { + version_major:long; + version_minor:long; + timestamp:long; + logs:[Log]; +} + +root_type LogList; diff --git a/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java b/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java new file mode 100644 index 000000000..728ac4b93 --- /dev/null +++ b/platform/src/main/java/org/conscrypt/ConscryptNetworkSecurityPolicy.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2025 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 org.conscrypt.flags.Flags; +import org.conscrypt.metrics.CertificateTransparencyVerificationReason; + +/** + * ConscryptNetworkSecurityPolicy for the platform (mainline). + * + * The Conscrypt-internal interface NetworkSecurityPolicy is ignored when exporting the API. + */ +@SuppressWarnings("HiddenSuperclass") +public class ConscryptNetworkSecurityPolicy implements NetworkSecurityPolicy { + private final libcore.net.NetworkSecurityPolicy policy; + + public static ConscryptNetworkSecurityPolicy getDefault() { + return new ConscryptNetworkSecurityPolicy(libcore.net.NetworkSecurityPolicy.getInstance()); + } + + public ConscryptNetworkSecurityPolicy(libcore.net.NetworkSecurityPolicy policy) { + this.policy = policy; + } + + @Override + public boolean isCertificateTransparencyVerificationRequired(String hostname) { + if (Flags.certificateTransparencyPlatform()) { + return policy.isCertificateTransparencyVerificationRequired(hostname); + } + return false; + } + + @Override + public CertificateTransparencyVerificationReason getCertificateTransparencyVerificationReason( + String hostname) { + if (Platform.isSdkGreater(33) + && com.android.libcore.Flags.networkSecurityPolicyReasonCtEnabledApi()) { + CertificateTransparencyVerificationReason reason = plaformCtReasonToConscryptReason( + policy.getCertificateTransparencyVerificationReason(hostname)); + if (reason != CertificateTransparencyVerificationReason.UNKNOWN) { + return reason; + } + } + if (policy.isCertificateTransparencyVerificationRequired("")) { + return CertificateTransparencyVerificationReason.APP_OPT_IN; + } else if (policy.isCertificateTransparencyVerificationRequired(hostname)) { + return CertificateTransparencyVerificationReason.DOMAIN_OPT_IN; + } + return CertificateTransparencyVerificationReason.UNKNOWN; + } + + private static CertificateTransparencyVerificationReason plaformCtReasonToConscryptReason( + int platformReason) { + switch (platformReason) { + case libcore.net.NetworkSecurityPolicy.CERTIFICATE_TRANSPARENCY_REASON_APP_OPT_IN: + return CertificateTransparencyVerificationReason.APP_OPT_IN; + case libcore.net.NetworkSecurityPolicy.CERTIFICATE_TRANSPARENCY_REASON_DOMAIN_OPT_IN: + return CertificateTransparencyVerificationReason.DOMAIN_OPT_IN; + case libcore.net.NetworkSecurityPolicy + .CERTIFICATE_TRANSPARENCY_REASON_SDK_TARGET_DEFAULT_ENABLED: + return CertificateTransparencyVerificationReason.SDK_TARGET_DEFAULT_ENABLED; + default: + return CertificateTransparencyVerificationReason.UNKNOWN; + } + } + + @Override + public DomainEncryptionMode getDomainEncryptionMode(String hostname) { + // Domain encryption is only supported on SDK 37+. This check is exclusive. + if (Platform.isSdkGreater(36)) { + return platformToConscryptEncryptionMode(policy.getDomainEncryptionMode(hostname)); + } + return DomainEncryptionMode.UNKNOWN; + } + + private static DomainEncryptionMode platformToConscryptEncryptionMode(int platformMode) { + switch (platformMode) { + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_DISABLED: + return DomainEncryptionMode.DISABLED; + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_OPPORTUNISTIC: + return DomainEncryptionMode.OPPORTUNISTIC; + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_ENABLED: + return DomainEncryptionMode.ENABLED; + case libcore.net.NetworkSecurityPolicy.DOMAIN_ENCRYPTION_MODE_REQUIRED: + return DomainEncryptionMode.REQUIRED; + default: + return DomainEncryptionMode.UNKNOWN; + } + } +} diff --git a/platform/src/main/java/org/conscrypt/Hex.java b/platform/src/main/java/org/conscrypt/Hex.java index d88205e49..7cdfe0479 100644 --- a/platform/src/main/java/org/conscrypt/Hex.java +++ b/platform/src/main/java/org/conscrypt/Hex.java @@ -20,24 +20,12 @@ * Helper class for dealing with hexadecimal strings. */ @Internal -// public for testing by TrustedCertificateStoreTest -// TODO(nathanmittler): Move to InternalUtil? public final class Hex { private Hex() {} private final static char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - public static String bytesToHexString(byte[] bytes) { - char[] buf = new char[bytes.length * 2]; - int c = 0; - for (byte b : bytes) { - buf[c++] = DIGITS[(b >> 4) & 0xf]; - buf[c++] = DIGITS[b & 0xf]; - } - return new String(buf); - } - public static String intToHexString(int i, int minWidth) { int bufLen = 8; // Max number of hex digits in an int char[] buf = new char[bufLen]; @@ -49,4 +37,33 @@ public static String intToHexString(int i, int minWidth) { return new String(buf, cursor, bufLen - cursor); } + + public static byte[] decodeHex(String encoded) throws IllegalArgumentException { + if ((encoded.length() % 2) != 0) { + throw new IllegalArgumentException("Invalid input length: " + encoded.length()); + } + + int resultLengthBytes = encoded.length() / 2; + byte[] result = new byte[resultLengthBytes]; + + int resultOffset = 0; + int i = 0; + for (int len = encoded.length(); i < len; i += 2) { + result[resultOffset++] = + (byte) ((toDigit(encoded.charAt(i)) << 4) | toDigit(encoded.charAt(i + 1))); + } + + return result; + } + + private static int toDigit(char pseudoCodePoint) throws IllegalArgumentException { + if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') { + return pseudoCodePoint - '0'; + } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') { + return 10 + (pseudoCodePoint - 'a'); + } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') { + return 10 + (pseudoCodePoint - 'A'); + } + throw new IllegalArgumentException("Illegal char: " + pseudoCodePoint); + } } diff --git a/platform/src/main/java/org/conscrypt/PakeTrustManagerFactory.java b/platform/src/main/java/org/conscrypt/PakeTrustManagerFactory.java index f281e0197..5c184cf39 100644 --- a/platform/src/main/java/org/conscrypt/PakeTrustManagerFactory.java +++ b/platform/src/main/java/org/conscrypt/PakeTrustManagerFactory.java @@ -34,7 +34,6 @@ /** * A factory for creating {@link SpakeTrustManager} instances that use SPAKE2. - * @hide This class is not part of the Android public SDK API */ @Internal public class PakeTrustManagerFactory extends TrustManagerFactorySpi { diff --git a/platform/src/main/java/org/conscrypt/Platform.java b/platform/src/main/java/org/conscrypt/Platform.java index 61ca87a12..eba665892 100644 --- a/platform/src/main/java/org/conscrypt/Platform.java +++ b/platform/src/main/java/org/conscrypt/Platform.java @@ -26,23 +26,25 @@ import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; import dalvik.system.VMRuntime; - -import libcore.net.NetworkSecurityPolicy; +import dalvik.system.ZygoteHooks; import org.conscrypt.NativeCrypto; import org.conscrypt.ct.CertificateTransparency; import org.conscrypt.ct.LogStore; -import org.conscrypt.ct.LogStoreImpl; +import org.conscrypt.ct.LogStoreImplv3; import org.conscrypt.ct.Policy; import org.conscrypt.ct.PolicyImpl; import org.conscrypt.flags.Flags; import org.conscrypt.metrics.CertificateTransparencyVerificationReason; +import org.conscrypt.metrics.NoopStatsLog; import org.conscrypt.metrics.OptionalMethod; import org.conscrypt.metrics.Source; import org.conscrypt.metrics.StatsLog; import org.conscrypt.metrics.StatsLogImpl; +import java.io.BufferedReader; import java.io.FileDescriptor; +import java.io.FileReader; import java.io.IOException; import java.lang.System; import java.lang.reflect.Field; @@ -66,6 +68,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import javax.crypto.spec.GCMParameterSpec; import javax.net.ssl.HttpsURLConnection; @@ -73,6 +76,7 @@ import javax.net.ssl.SNIMatcher; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLException; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; @@ -90,8 +94,13 @@ private static class NoPreloadHolder { private static boolean DEPRECATED_TLS_V1 = true; private static boolean ENABLED_TLS_V1 = false; private static boolean FILTERED_TLS_V1 = true; + private static boolean RUNNING_IN_ZYGOTE = true; + private static final boolean canProbeZygote; + private static final boolean canCallZygoteMethod; static { + canProbeZygote = isSdkGreater(32); + canCallZygoteMethod = isSdkGreater(36); NativeCrypto.setTlsV1DeprecationStatus(DEPRECATED_TLS_V1, ENABLED_TLS_V1); } @@ -104,6 +113,7 @@ public static synchronized void setup(boolean deprecatedTlsV1, boolean enabledTl FILTERED_TLS_V1 = !enabledTlsV1; NoPreloadHolder.MAPPER.ping(); NativeCrypto.setTlsV1DeprecationStatus(DEPRECATED_TLS_V1, ENABLED_TLS_V1); + RUNNING_IN_ZYGOTE = inZygote(); } /** @@ -171,7 +181,8 @@ public static void setSSLParameters(SSLParameters params, SSLParametersImpl impl try { Method getNamedGroupsMethod = params.getClass().getMethod("getNamedGroups"); impl.setNamedGroups((String[]) getNamedGroupsMethod.invoke(params)); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } @@ -186,7 +197,8 @@ static void setSSLParameters(SSLParameters params, SSLParametersImpl impl, try { Method getNamedGroupsMethod = params.getClass().getMethod("getNamedGroups"); impl.setNamedGroups((String[]) getNamedGroupsMethod.invoke(params)); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } @@ -210,7 +222,8 @@ public static void getSSLParameters(SSLParameters params, SSLParametersImpl impl Method setNamedGroupsMethod = params.getClass().getMethod("setNamedGroups", String[].class); setNamedGroupsMethod.invoke(params, (Object) impl.getNamedGroups()); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } @@ -226,7 +239,8 @@ static void getSSLParameters(SSLParameters params, SSLParametersImpl impl, Method setNamedGroupsMethod = params.getClass().getMethod("setNamedGroups", String[].class); setNamedGroupsMethod.invoke(params, (Object) impl.getNamedGroups()); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } @@ -245,9 +259,11 @@ static void setSSLParameters(SSLParameters params, SSLParametersImpl impl, try { Method getNamedGroupsMethod = params.getClass().getMethod("getNamedGroups"); impl.setNamedGroups((String[]) getNamedGroupsMethod.invoke(params)); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } + List serverNames = params.getServerNames(); if (serverNames != null) { for (SNIServerName serverName : serverNames) { @@ -269,9 +285,11 @@ static void getSSLParameters(SSLParameters params, SSLParametersImpl impl, Method setNamedGroupsMethod = params.getClass().getMethod("setNamedGroups", String[].class); setNamedGroupsMethod.invoke(params, (Object) impl.getNamedGroups()); - } catch (NoSuchMethodException | IllegalArgumentException e) { + } catch (NoSuchMethodException | IllegalArgumentException | IllegalAccessException + | InvocationTargetException e) { // Do nothing. } + if (impl.getUseSni() && AddressUtils.isValidSniHostname(engine.getHostname())) { params.setServerNames(Collections.singletonList( new SNIHostName(engine.getHostname()))); @@ -397,48 +415,6 @@ static ConscryptEngineSocket createEngineSocket(Socket socket, String hostname, return new Java8EngineSocket(socket, hostname, port, autoClose, sslParameters); } - static ConscryptFileDescriptorSocket createFileDescriptorSocket(SSLParametersImpl sslParameters) - throws IOException { - return new Java8FileDescriptorSocket(sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port, - SSLParametersImpl sslParameters) - throws IOException { - return new Java8FileDescriptorSocket(hostname, port, sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port, - SSLParametersImpl sslParameters) - throws IOException { - return new Java8FileDescriptorSocket(address, port, sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(String hostname, int port, - InetAddress clientAddress, - int clientPort, - SSLParametersImpl sslParameters) - throws IOException { - return new Java8FileDescriptorSocket(hostname, port, clientAddress, clientPort, - sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(InetAddress address, int port, - InetAddress clientAddress, - int clientPort, - SSLParametersImpl sslParameters) - throws IOException { - return new Java8FileDescriptorSocket(address, port, clientAddress, clientPort, - sslParameters); - } - - static ConscryptFileDescriptorSocket createFileDescriptorSocket(Socket socket, String hostname, - int port, boolean autoClose, - SSLParametersImpl sslParameters) - throws IOException { - return new Java8FileDescriptorSocket(socket, hostname, port, autoClose, sslParameters); - } - /** * Wrap the SocketFactory with the platform wrapper if needed for compatability. * For the platform-bundled library we never need to wrap. @@ -562,23 +538,20 @@ static boolean supportsX509ExtendedTrustManager() { return true; } - public static boolean isCTVerificationRequired(String hostname) { - if (Flags.certificateTransparencyPlatform()) { - return NetworkSecurityPolicy.getInstance() - .isCertificateTransparencyVerificationRequired(hostname); - } - return false; + static SSLException wrapInvalidEchDataException(SSLException e) { + return new android.net.ssl.InvalidEchDataException(e.getMessage()); } - public static CertificateTransparencyVerificationReason reasonCTVerificationRequired( - String hostname) { - if (NetworkSecurityPolicy.getInstance().isCertificateTransparencyVerificationRequired("")) { - return CertificateTransparencyVerificationReason.APP_OPT_IN; - } else if (NetworkSecurityPolicy.getInstance() - .isCertificateTransparencyVerificationRequired(hostname)) { - return CertificateTransparencyVerificationReason.DOMAIN_OPT_IN; + static SSLException wrapEchRejectedException(EchRejectedException exception, String hostname, + byte[] retryConfigs) { + android.net.ssl.EchConfigList configs; + try { + configs = android.net.ssl.EchConfigList.fromBytes(retryConfigs); + } catch (Exception e) { + configs = null; } - return CertificateTransparencyVerificationReason.UNKNOWN; + return new android.net.ssl.EchConfigMismatchException( + "The ECH configuration has been rejected by the server", hostname, configs); } static boolean supportsConscryptCertStore() { @@ -603,11 +576,13 @@ static CertBlocklist newDefaultBlocklist() { return CertBlocklistImpl.getDefault(); } - static CertificateTransparency newDefaultCertificateTransparency() { + static CertificateTransparency newDefaultCertificateTransparency( + Supplier policySupplier) { org.conscrypt.ct.Policy policy = new org.conscrypt.ct.PolicyImpl(); - org.conscrypt.ct.LogStore logStore = new org.conscrypt.ct.LogStoreImpl(policy); + org.conscrypt.ct.LogStore logStore = new org.conscrypt.ct.LogStoreImplv3(policy); org.conscrypt.ct.Verifier verifier = new org.conscrypt.ct.Verifier(logStore); - return new CertificateTransparency(logStore, policy, verifier, getStatsLog()); + return new CertificateTransparency(logStore, policy, verifier, getStatsLog(), + policySupplier); } static boolean serverNamePermitted(SSLParametersImpl parameters, String serverName) { @@ -639,7 +614,14 @@ static long getMillisSinceBoot() { } public static StatsLog getStatsLog() { - return StatsLogImpl.getInstance(); + if (!RUNNING_IN_ZYGOTE) { + return StatsLogImpl.getInstance(); + } + if (!inZygote()) { + RUNNING_IN_ZYGOTE = false; + return StatsLogImpl.getInstance(); + } + return NoopStatsLog.getInstance(); } public static Source getStatsSource() { @@ -674,6 +656,31 @@ public static boolean isPakeSupported() { return true; } + private static boolean inZygote() { + if (canCallZygoteMethod) { + return ZygoteHooks.isInZygote(); + } + if (canProbeZygote) { + try { + Class zygoteHooksClass = Class.forName("dalvik.system.ZygoteHooks"); + Method inZygoteMethod = zygoteHooksClass.getDeclaredMethod("inZygote"); + Object inZygote = inZygoteMethod.invoke(null); + if (inZygote == null) { + return true; + } + return (boolean) inZygote; + } catch (IllegalAccessException | NullPointerException | InvocationTargetException + | ClassNotFoundException | NoSuchMethodException e) { + return true; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + // For previous releases, we have no mechanism to test if we are in Zygote. + // Assume we are not, to conserve the existing behaviour. + return false; + } + static Object getTargetSdkVersion() { try { Class vmRuntimeClass = Class.forName("dalvik.system.VMRuntime"); diff --git a/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java b/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java deleted file mode 100644 index fc875e907..000000000 --- a/platform/src/main/java/org/conscrypt/ct/LogStoreImpl.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (C) 2015 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.ct; - -import static java.nio.charset.StandardCharsets.US_ASCII; -import static java.nio.charset.StandardCharsets.UTF_8; - -import org.conscrypt.ByteArray; -import org.conscrypt.Internal; -import org.conscrypt.OpenSSLKey; -import org.conscrypt.Platform; -import org.conscrypt.metrics.StatsLog; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -@Internal -public class LogStoreImpl implements LogStore { - private static final Logger logger = Logger.getLogger(LogStoreImpl.class.getName()); - private static final String BASE_PATH = "misc/keychain/ct"; - private static final int COMPAT_VERSION = 1; - private static final String CURRENT = "current"; - private static final String LOG_LIST_FILENAME = "log_list.json"; - private static final Path DEFAULT_LOG_LIST; - - static { - String androidData = System.getenv("ANDROID_DATA"); - String compatVersion = String.format("v%d", COMPAT_VERSION); - DEFAULT_LOG_LIST = - Paths.get(androidData, BASE_PATH, compatVersion, CURRENT, LOG_LIST_FILENAME); - } - - private final Path logList; - private StatsLog metrics; - private State state; - private Policy policy; - private int majorVersion; - private int minorVersion; - private long timestamp; - private Map logs; - - public LogStoreImpl(Policy policy) { - this(policy, DEFAULT_LOG_LIST); - } - - public LogStoreImpl(Policy policy, Path logList) { - this(policy, logList, Platform.getStatsLog()); - } - - public LogStoreImpl(Policy policy, Path logList, StatsLog metrics) { - this.state = State.UNINITIALIZED; - this.policy = policy; - this.logList = logList; - this.metrics = metrics; - } - - @Override - public State getState() { - ensureLogListIsLoaded(); - return state; - } - - @Override - public long getTimestamp() { - return timestamp; - } - - @Override - public int getMajorVersion() { - return majorVersion; - } - - @Override - public int getMinorVersion() { - return minorVersion; - } - - @Override - public int getCompatVersion() { - // Currently, there is only one compatibility version supported. If we - // are loaded or initialized, it means the expected compatibility - // version was found. - if (state == State.LOADED || state == State.COMPLIANT || state == State.NON_COMPLIANT) { - return COMPAT_VERSION; - } - return 0; - } - - @Override - public int getMinCompatVersionAvailable() { - return getCompatVersion(); - } - - @Override - public LogInfo getKnownLog(byte[] logId) { - if (logId == null) { - return null; - } - if (!ensureLogListIsLoaded()) { - return null; - } - ByteArray buf = new ByteArray(logId); - LogInfo log = logs.get(buf); - if (log != null) { - return log; - } - return null; - } - - /* Ensures the log list is loaded. - * Returns true if the log list is usable. - */ - private boolean ensureLogListIsLoaded() { - synchronized (this) { - State previousState = state; - if (state == State.UNINITIALIZED) { - state = loadLogList(); - } - if (state == State.LOADED && policy != null) { - state = policy.isLogStoreCompliant(this) ? State.COMPLIANT : State.NON_COMPLIANT; - } - if (state != previousState) { - metrics.updateCTLogListStatusChanged(this); - } - return state == State.COMPLIANT; - } - } - - private State loadLogList() { - byte[] content; - try { - content = Files.readAllBytes(logList); - } catch (IOException e) { - return State.NOT_FOUND; - } - if (content == null) { - return State.NOT_FOUND; - } - JSONObject json; - try { - json = new JSONObject(new String(content, UTF_8)); - } catch (JSONException e) { - logger.log(Level.WARNING, "Unable to parse log list", e); - return State.MALFORMED; - } - HashMap logsMap = new HashMap<>(); - try { - majorVersion = parseMajorVersion(json.getString("version")); - minorVersion = parseMinorVersion(json.getString("version")); - timestamp = parseTimestamp(json.getString("log_list_timestamp")); - JSONArray operators = json.getJSONArray("operators"); - for (int i = 0; i < operators.length(); i++) { - JSONObject operator = operators.getJSONObject(i); - String operatorName = operator.getString("name"); - JSONArray logs = operator.getJSONArray("logs"); - for (int j = 0; j < logs.length(); j++) { - JSONObject log = logs.getJSONObject(j); - - LogInfo.Builder builder = - new LogInfo.Builder() - .setDescription(log.getString("description")) - .setPublicKey(parsePubKey(log.getString("key"))) - .setUrl(log.getString("url")) - .setOperator(operatorName); - - JSONObject stateObject = log.optJSONObject("state"); - if (stateObject != null) { - String state = stateObject.keys().next(); - String stateTimestamp = - stateObject.getJSONObject(state).getString("timestamp"); - builder.setState(parseState(state), parseTimestamp(stateTimestamp)); - } - - LogInfo logInfo = builder.build(); - byte[] logId = Base64.getDecoder().decode(log.getString("log_id")); - - // The logId computed using the public key should match the log_id field. - if (!Arrays.equals(logInfo.getID(), logId)) { - throw new IllegalArgumentException("logId does not match publicKey"); - } - - logsMap.put(new ByteArray(logId), logInfo); - } - } - } catch (JSONException | IllegalArgumentException e) { - logger.log(Level.WARNING, "Unable to parse log list", e); - return State.MALFORMED; - } - this.logs = Collections.unmodifiableMap(logsMap); - return State.LOADED; - } - - private static int parseMajorVersion(String version) { - int pos = version.indexOf("."); - if (pos == -1) { - pos = version.length(); - } - try { - return Integer.parseInt(version.substring(0, pos)); - } catch (IndexOutOfBoundsException | NumberFormatException e) { - return 0; - } - } - - private static int parseMinorVersion(String version) { - int pos = version.indexOf("."); - if (pos != -1 && pos < version.length()) { - try { - return Integer.parseInt(version.substring(pos + 1, version.length())); - } catch (IndexOutOfBoundsException | NumberFormatException e) { - return 0; - } - } - return 0; - } - - private static int parseState(String state) { - switch (state) { - case "pending": - return LogInfo.STATE_PENDING; - case "qualified": - return LogInfo.STATE_QUALIFIED; - case "usable": - return LogInfo.STATE_USABLE; - case "readonly": - return LogInfo.STATE_READONLY; - case "retired": - return LogInfo.STATE_RETIRED; - case "rejected": - return LogInfo.STATE_REJECTED; - default: - throw new IllegalArgumentException("Unknown log state: " + state); - } - } - - // ISO 8601 - private static DateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); - - @SuppressWarnings("JavaUtilDate") - private static long parseTimestamp(String timestamp) { - try { - Date date = dateFormatter.parse(timestamp); - return date.getTime(); - } catch (ParseException e) { - throw new IllegalArgumentException(e); - } - } - - private static PublicKey parsePubKey(String key) { - byte[] pem = ("-----BEGIN PUBLIC KEY-----\n" + key + "\n-----END PUBLIC KEY-----") - .getBytes(US_ASCII); - PublicKey pubkey; - try { - pubkey = OpenSSLKey.fromPublicKeyPemInputStream(new ByteArrayInputStream(pem)) - .getPublicKey(); - } catch (InvalidKeyException | NoSuchAlgorithmException e) { - throw new IllegalArgumentException(e); - } - return pubkey; - } -} diff --git a/platform/src/main/java/org/conscrypt/ct/LogStoreImplv3.java b/platform/src/main/java/org/conscrypt/ct/LogStoreImplv3.java new file mode 100644 index 000000000..21ce08e81 --- /dev/null +++ b/platform/src/main/java/org/conscrypt/ct/LogStoreImplv3.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2025 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.ct; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.google.flatbuffers.FlatBufferBuilder; + +import org.conscrypt.ByteArray; +import org.conscrypt.Internal; +import org.conscrypt.OpenSSLKey; +import org.conscrypt.Platform; +import org.conscrypt.ct.fbs.Log; +import org.conscrypt.ct.fbs.LogList; +import org.conscrypt.ct.fbs.LogState; +import org.conscrypt.ct.fbs.LogType; +import org.conscrypt.metrics.StatsLog; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +@Internal +public class LogStoreImplv3 implements LogStore { + private static final Logger logger = Logger.getLogger(LogStoreImplv3.class.getName()); + private static final int COMPAT_VERSION = 3; + private static final Path logListPrefix; + private static final Path logListSuffix; + private static final Path logListDeprecatedJsonSuffix; + private static final long LOG_LIST_CHECK_INTERVAL_IN_MS = 10L * 60 * 1_000; // 10 minutes + + static { + String androidData = System.getenv("ANDROID_DATA"); + // /data/misc/keychain/ct/v1/current/log_list.json + logListPrefix = Paths.get(androidData, "misc", "keychain", "ct"); + logListSuffix = Paths.get("current", "log_list.ctfb"); + logListDeprecatedJsonSuffix = Paths.get("current", "log_list.json"); + } + + /** The path to the log list on the filesystem */ + private final Path logPath; + + /** Metrics subsystem. A message is sent there whenever the log state changes */ + private StatsLog metrics; + + /** The current state of this store: UNINITIALIZED, LOADED, COMPLIANT, ... */ + private State state; + + /** The log policy. Used to set the status of this store */ + private Policy policy; + + /** The major version of the log list */ + private int majorVersion; + + /** The minor version of the log list */ + private int minorVersion; + + /** The timestamp of the log list */ + private long timestamp; + + /** The time at which the log list file was last modified */ + private long logListLastModified; + + /** Clock. Faked in testing. */ + private Supplier clock; + + /** The last time the log list file was checked for modification */ + private long logListLastChecked; + + /** The memory-mapped flat buffer */ + private MappedByteBuffer map; + + /** The flatbuffer root */ + private LogList logList; + + /** Cache of LogInfo already seen */ + private Map logCache; + + /* We do not have access to InstantSource. Implement a similar pattern using Supplier. */ + static class SystemTimeSupplier implements Supplier { + @Override + public Long get() { + return System.currentTimeMillis(); + } + } + + private static Path getPathForCompatVersion(int compatVersion) { + String version = String.format("v%d", compatVersion); + if (compatVersion > 2) { + return logListPrefix.resolve(version).resolve(logListSuffix); + } + return logListPrefix.resolve(version).resolve(logListDeprecatedJsonSuffix); + } + + public LogStoreImplv3(Policy policy) { + this(policy, getPathForCompatVersion(COMPAT_VERSION), Platform.getStatsLog(), + new SystemTimeSupplier()); + } + + public LogStoreImplv3(Policy policy, Path logPath, StatsLog metrics, Supplier clock) { + this.state = State.UNINITIALIZED; + this.policy = policy; + this.logPath = logPath; + this.metrics = metrics; + this.clock = clock; + this.logCache = Collections.synchronizedMap(new HashMap()); + } + + @Override + public State getState() { + ensureLogListIsLoaded(); + return state; + } + + @Override + public long getTimestamp() { + return timestamp; + } + + @Override + public int getMajorVersion() { + return majorVersion; + } + + @Override + public int getMinorVersion() { + return minorVersion; + } + + @Override + public int getCompatVersion() { + return COMPAT_VERSION; + } + + @Override + public int getMinCompatVersionAvailable() { + for (int i = 1; i < COMPAT_VERSION; i++) { + if (Files.exists(getPathForCompatVersion(i))) { + return i; + } + } + return getCompatVersion(); + } + + @Override + public LogInfo getKnownLog(byte[] logId) throws LogStore.InvalidLogException { + if (logId == null) { + return null; + } + if (!ensureLogListIsLoaded()) { + return null; + } + + ByteArray buf = new ByteArray(logId); + LogInfo log = logCache.get(buf); + if (log != null) { + return log; + } + synchronized (this) { + // Double-check. + log = logCache.get(buf); + if (log != null) { + return log; + } + + log = cacheLogEntry(logId); + if (log != null) { + return log; + } + } + return null; + } + + /* Ensures the log list is loaded. + * Returns true if the log list is usable. + */ + private synchronized boolean ensureLogListIsLoaded() { + resetLogListIfRequired(); + State previousState = state; + if (state == State.UNINITIALIZED) { + state = loadLogList(); + } + if (state == State.LOADED && policy != null) { + state = policy.isLogStoreCompliant(this) ? State.COMPLIANT : State.NON_COMPLIANT; + } + if (state != previousState) { + metrics.updateCTLogListStatusChanged(this); + } + return state == State.COMPLIANT; + } + + private synchronized void resetLogListIfRequired() { + long now = clock.get(); + if (now >= this.logListLastChecked + && now < this.logListLastChecked + LOG_LIST_CHECK_INTERVAL_IN_MS) { + return; + } + this.logListLastChecked = now; + try { + long lastModified = Files.getLastModifiedTime(logPath).toMillis(); + if (this.logListLastModified == lastModified) { + // The log list has the same last modified timestamp. Keep our + // current cached value. + return; + } + } catch (IOException e) { + if (this.logListLastModified == 0) { + // The log list is not accessible now and it has never been + // previously, there is nothing to do. + return; + } + } + this.state = State.UNINITIALIZED; + this.timestamp = 0; + this.majorVersion = 0; + this.minorVersion = 0; + this.logCache.clear(); + } + + private State loadLogList() { + FileChannel channel; + long lastModified; + try { + lastModified = Files.getLastModifiedTime(logPath).toMillis(); + channel = FileChannel.open(logPath); + map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); + channel.close(); + } catch (IOException e) { + return State.NOT_FOUND; + } + LogList logList; + try { + logList = LogList.getRootAsLogList(map); + } catch (IndexOutOfBoundsException e) { + return State.MALFORMED; + } + if (logList == null) { + return State.MALFORMED; + } + try { + this.majorVersion = Math.toIntExact(logList.versionMajor()); + this.minorVersion = Math.toIntExact(logList.versionMinor()); + this.timestamp = logList.timestamp(); + + // Verify that the list timestamp is in the past. This might fail + // if there is an issue with the device's clock which can cause + // false positives when validating SCTs. + if (clock.get() < this.timestamp) { + return State.MALFORMED; + } + + } catch (ArithmeticException e) { + logger.log(Level.WARNING, "Unable to parse log list", e); + return State.MALFORMED; + } + this.logList = logList; + this.logListLastModified = lastModified; + return State.LOADED; + } + + private synchronized LogInfo cacheLogEntry(byte[] logId) throws LogStore.InvalidLogException { + String encodedLogId = Base64.getEncoder().encodeToString(logId); + Log log = logList.logsByKey(encodedLogId); + if (log == null) { + return null; + } + + try { + byte[] pubKey = new byte[log.publicKeyLength()]; + log.publicKeyAsByteBuffer().get(pubKey); + LogInfo.Builder builder = + new LogInfo.Builder() + .setType(parseType(log.type())) + .setOperator(log.operator()) + .setPublicKey(parsePubKey(pubKey)) + .setState(parseState(log.state()), log.stateTimestamp()); + + LogInfo logInfo = builder.build(); + + // The logId computed using the public key should match the log_id field. + if (!Arrays.equals(logInfo.getID(), logId)) { + throw new IllegalArgumentException("logId does not match publicKey"); + } + + // Verify that the log is in a known state now. This might fail if + // there is an issue with the device's clock which can cause false + // positives when validating SCTs. + if (logInfo.getStateAt(clock.get()) == LogInfo.STATE_UNKNOWN) { + throw new IllegalArgumentException("Log current state is " + + "unknown, logId: " + encodedLogId); + } + + logCache.put(new ByteArray(logId), logInfo); + return logInfo; + } catch (Exception e) { + // There is something wrong with that log entry. Assume that the log list is corrupted. + // We throw a InvalidLogException here to fail-open in the CertificateTransparency + // class. + logger.log(Level.WARNING, "Unable to parse log entry", e); + state = State.MALFORMED; + metrics.updateCTLogListStatusChanged(this); + throw new LogStore.InvalidLogException(e); + } + } + + private static int parseState(byte state) { + switch (state) { + case LogState.Pending: + return LogInfo.STATE_PENDING; + case LogState.Qualified: + return LogInfo.STATE_QUALIFIED; + case LogState.Usable: + return LogInfo.STATE_USABLE; + case LogState.Readonly: + return LogInfo.STATE_READONLY; + case LogState.Retired: + return LogInfo.STATE_RETIRED; + case LogState.Rejected: + return LogInfo.STATE_REJECTED; + default: + throw new IllegalArgumentException("Unknown log state: " + state); + } + } + + private static int parseType(byte logType) { + switch (logType) { + case LogType.Rfc6962: + return LogInfo.TYPE_RFC6962; + case LogType.Static: + return LogInfo.TYPE_STATIC_CT_API; + default: + throw new IllegalArgumentException("Unknown log type: " + logType); + } + } + + private static PublicKey parsePubKey(byte[] key) { + // TODO(tweek): Replace with NativeCrypto.EVP_PKEY_from_subject_public_key_info + byte[] pem = ("-----BEGIN PUBLIC KEY-----\n" + Base64.getEncoder().encodeToString(key) + + "\n-----END PUBLIC KEY-----") + .getBytes(US_ASCII); + PublicKey pubkey; + try { + pubkey = OpenSSLKey.fromPublicKeyPemInputStream(new ByteArrayInputStream(pem)) + .getPublicKey(); + } catch (InvalidKeyException | NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e); + } + return pubkey; + } +} diff --git a/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java index a606557fa..471a8fec4 100644 --- a/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java +++ b/platform/src/main/java/org/conscrypt/ct/PolicyImpl.java @@ -178,7 +178,7 @@ private PolicyCompliance conformEmbeddedSCTs(Set embeddedValidSCTs, return PolicyCompliance.NOT_ENOUGH_SCTS; } - /* 3. Among the SCTs satisfying requirements 1 and 2, at least two SCTs + /* 3. Among the SCTs satisfying requirements 2, at least two SCTs * must be issued from distinct CT Log Operators as recognized by * Chrome. */ diff --git a/platform/src/test/java/org/conscrypt/CertBlocklistTest.java b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java index 31341ae9a..e37730b3b 100644 --- a/platform/src/test/java/org/conscrypt/CertBlocklistTest.java +++ b/platform/src/test/java/org/conscrypt/CertBlocklistTest.java @@ -16,9 +16,13 @@ package org.conscrypt; +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import org.conscrypt.metrics.NoopStatsLog; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -29,6 +33,7 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Collection; import javax.net.ssl.X509TrustManager; @@ -102,6 +107,44 @@ public void testBlocklistedIntermediateFallback() throws Exception { assertUntrusted(chain, getTrustManager(blocklistedCa)); } + static class FakeStatsLog extends NoopStatsLog { + public ArrayList entries = new ArrayList<>(); + + @Override + public void reportBlocklistHit(CertBlocklistEntry entry) { + entries.add(entry); + } + } + + @Test + public void isPublicKeyBlockListed_hitBlocklist_hitIsReported() throws Exception { + X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA); + FakeStatsLog fakeMetrics = new FakeStatsLog(); + CertBlocklist blocklist = + new CertBlocklistImpl.Builder().loadAllDefaults().setMetrics(fakeMetrics).build(); + + blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()); + + assertThat(fakeMetrics.entries).hasSize(1); + CertBlocklistEntry entry = fakeMetrics.entries.get(0); + assertThat(entry.getOrigin()).isEqualTo(CertBlocklistEntry.Origin.SHA1_TEST); + assertThat(entry.getIndex()).isEqualTo(0); + } + + @Test + public void isPublicKeyBlockListed_hitBlocklistTwiceWithSameKey_hitIsReportedTwice() + throws Exception { + X509Certificate blocklistedCa = loadCertificate(BLOCKLIST_CA); + FakeStatsLog fakeMetrics = new FakeStatsLog(); + CertBlocklist blocklist = + new CertBlocklistImpl.Builder().loadAllDefaults().setMetrics(fakeMetrics).build(); + + blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()); + blocklist.isPublicKeyBlockListed(blocklistedCa.getPublicKey()); + + assertThat(fakeMetrics.entries).hasSize(2); + } + private static X509Certificate loadCertificate(String file) throws Exception { return loadCertificates(file)[0]; } diff --git a/platform/src/test/java/org/conscrypt/PakeManagerFactoriesTest.java b/platform/src/test/java/org/conscrypt/PakeManagerFactoriesTest.java index 80c5041dd..20bc735c5 100644 --- a/platform/src/test/java/org/conscrypt/PakeManagerFactoriesTest.java +++ b/platform/src/test/java/org/conscrypt/PakeManagerFactoriesTest.java @@ -34,9 +34,6 @@ import javax.net.ssl.ManagerFactoryParameters; import javax.net.ssl.TrustManager; -/** - * @hide This class is not part of the Android public SDK API - */ public class PakeManagerFactoriesTest { private static final byte[] CLIENT_ID = new byte[] {4, 5, 6}; private static final byte[] SERVER_ID = new byte[] {7, 8, 9}; diff --git a/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java b/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java index 1a05c21a8..0ebb329ce 100644 --- a/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java +++ b/platform/src/test/java/org/conscrypt/TlsDeprecationTest.java @@ -39,6 +39,8 @@ @RunWith(JUnit4.class) public class TlsDeprecationTest { + // android-add: test rule for switching target SDK version to 36. + @Test @TargetSdkVersion(36) public void test_SSLSocket_SSLv3Unsupported_36() throws Exception { diff --git a/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java b/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java deleted file mode 100644 index 1af49b4ef..000000000 --- a/platform/src/test/java/org/conscrypt/ct/LogStoreImplTest.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright (C) 2015 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.ct; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; - -import static java.nio.charset.StandardCharsets.US_ASCII; -import static java.nio.charset.StandardCharsets.UTF_8; - -import org.conscrypt.OpenSSLKey; -import org.conscrypt.metrics.NoopStatsLog; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Base64; - -@RunWith(JUnit4.class) -public class LogStoreImplTest { - static class FakeStatsLog extends NoopStatsLog { - public ArrayList states = new ArrayList(); - - @Override - public void updateCTLogListStatusChanged(LogStore logStore) { - states.add(logStore.getState()); - } - } - - Policy alwaysCompliantStorePolicy = new Policy() { - @Override - public boolean isLogStoreCompliant(LogStore store) { - return true; - } - @Override - public PolicyCompliance doesResultConformToPolicy(VerificationResult result, - X509Certificate leaf) { - return PolicyCompliance.COMPLY; - } - }; - - Policy neverCompliantStorePolicy = new Policy() { - @Override - public boolean isLogStoreCompliant(LogStore store) { - return false; - } - @Override - public PolicyCompliance doesResultConformToPolicy(VerificationResult result, - X509Certificate leaf) { - return PolicyCompliance.COMPLY; - } - }; - - @Test - public void test_loadValidLogList() throws Exception { - // clang-format off - String content = "" + -"{" + -" \"version\": \"1.1\"," + -" \"log_list_timestamp\": \"2024-01-01T11:55:12Z\"," + -" \"operators\": [" + -" {" + -" \"name\": \"Operator 1\"," + -" \"email\": [\"ct@operator1.com\"]," + -" \"logs\": [" + -" {" + -" \"description\": \"Operator 1 'Test2024' log\"," + -" \"log_id\": \"7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs=\"," + -" \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHblsqctplMVc5ramA7vSuNxUQxcomQwGAVAdnWTAWUYr3MgDHQW0LagJ95lB7QT75Ve6JgT2EVLOFGU7L3YrwA==\"," + -" \"url\": \"https://operator1.example.com/logs/test2024/\"," + -" \"mmd\": 86400," + -" \"state\": {" + -" \"usable\": {" + -" \"timestamp\": \"2022-11-01T18:54:00Z\"" + -" }" + -" }," + -" \"temporal_interval\": {" + -" \"start_inclusive\": \"2024-01-01T00:00:00Z\"," + -" \"end_exclusive\": \"2025-01-01T00:00:00Z\"" + -" }" + -" }," + -" {" + -" \"description\": \"Operator 1 'Test2025' log\"," + -" \"log_id\": \"TnWjJ1yaEMM4W2zU3z9S6x3w4I4bjWnAsfpksWKaOd8=\"," + -" \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIIKh+WdoqOTblJji4WiH5AltIDUzODyvFKrXCBjw/Rab0/98J4LUh7dOJEY7+66+yCNSICuqRAX+VPnV8R1Fmg==\"," + -" \"url\": \"https://operator1.example.com/logs/test2025/\"," + -" \"mmd\": 86400," + -" \"state\": {" + -" \"usable\": {" + -" \"timestamp\": \"2023-11-26T12:00:00Z\"" + -" }" + -" }," + -" \"temporal_interval\": {" + -" \"start_inclusive\": \"2025-01-01T00:00:00Z\"," + -" \"end_exclusive\": \"2025-07-01T00:00:00Z\"" + -" }" + -" }" + -" ]" + -" }," + -" {" + -" \"name\": \"Operator 2\"," + -" \"email\": [\"ct@operator2.com\"]," + -" \"logs\": [" + -" {" + -" \"description\": \"Operator 2 'Test2024' Log\"," + -" \"log_id\": \"2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6s=\"," + -" \"key\": \"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd7Gbe4/mizX+OpIpLayKjVGKJfyTttegiyk3cR0zyswz6ii5H+Ksw6ld3Ze+9p6UJd02gdHrXSnDK0TxW8oVSA==\"," + -" \"url\": \"https://operator2.example.com/logs/test2024/\"," + -" \"mmd\": 86400," + -" \"state\": {" + -" \"usable\": {" + -" \"timestamp\": \"2022-11-30T17:00:00Z\"" + -" }" + -" }," + -" \"temporal_interval\": {" + -" \"start_inclusive\": \"2024-01-01T00:00:00Z\"," + -" \"end_exclusive\": \"2025-01-01T00:00:00Z\"" + -" }" + -" }" + -" ]" + -" }" + -" ]" + -"}"; - // clang-format on - - FakeStatsLog metrics = new FakeStatsLog(); - File logList = writeFile(content); - LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList.toPath(), metrics); - - assertNull("A null logId should return null", store.getKnownLog(null)); - - byte[] pem = ("-----BEGIN PUBLIC KEY-----\n" - + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHblsqctplMVc5ramA7vSuNxUQxcomQwGAVAdnW" - + "TAWUYr" - + "3MgDHQW0LagJ95lB7QT75Ve6JgT2EVLOFGU7L3YrwA==" - + "\n-----END PUBLIC KEY-----\n") - .getBytes(US_ASCII); - ByteArrayInputStream is = new ByteArrayInputStream(pem); - - LogInfo log1 = - new LogInfo.Builder() - .setPublicKey(OpenSSLKey.fromPublicKeyPemInputStream(is).getPublicKey()) - .setDescription("Operator 1 'Test2024' log") - .setUrl("https://operator1.example.com/logs/test2024/") - .setState(LogInfo.STATE_USABLE, 1667328840000L) - .setOperator("Operator 1") - .build(); - byte[] log1Id = Base64.getDecoder().decode("7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs="); - assertEquals("An existing logId should be returned", log1, store.getKnownLog(log1Id)); - assertEquals("One metric update should be emitted", metrics.states.size(), 1); - assertEquals("The metric update for log list state should be compliant", - metrics.states.get(0), LogStore.State.COMPLIANT); - } - - @Test - public void test_loadMalformedLogList() throws Exception { - FakeStatsLog metrics = new FakeStatsLog(); - String content = "}}"; - File logList = writeFile(content); - LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList.toPath(), metrics); - - assertEquals("The log state should be malformed", store.getState(), - LogStore.State.MALFORMED); - assertEquals("One metric update should be emitted", metrics.states.size(), 1); - assertEquals("The metric update for log list state should be malformed", - metrics.states.get(0), LogStore.State.MALFORMED); - } - - @Test - public void test_loadMissingLogList() throws Exception { - FakeStatsLog metrics = new FakeStatsLog(); - File logList = new File("does_not_exist"); - LogStore store = new LogStoreImpl(alwaysCompliantStorePolicy, logList.toPath(), metrics); - - assertEquals("The log state should be not found", store.getState(), - LogStore.State.NOT_FOUND); - assertEquals("One metric update should be emitted", metrics.states.size(), 1); - assertEquals("The metric update for log list state should be not found", - metrics.states.get(0), LogStore.State.NOT_FOUND); - } - - private File writeFile(String content) throws IOException { - File file = File.createTempFile("test", null); - file.deleteOnExit(); - try (FileWriter fw = new FileWriter(file)) { - fw.write(content); - } - return file; - } -} diff --git a/platform/src/test/java/org/conscrypt/ct/LogStoreImplv3Test.java b/platform/src/test/java/org/conscrypt/ct/LogStoreImplv3Test.java new file mode 100644 index 000000000..60a5e6d86 --- /dev/null +++ b/platform/src/test/java/org/conscrypt/ct/LogStoreImplv3Test.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2015 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.ct; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.conscrypt.OpenSSLKey; +import org.conscrypt.TestUtils; +import org.conscrypt.metrics.NoopStatsLog; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Base64; +import java.util.function.Supplier; + +@RunWith(JUnit4.class) +public class LogStoreImplv3Test { + /** FakeStatsLog captures the events being reported */ + static class FakeStatsLog extends NoopStatsLog { + public ArrayList states = new ArrayList(); + + @Override + public void updateCTLogListStatusChanged(LogStore logStore) { + states.add(logStore.getState()); + } + } + + Policy alwaysCompliantStorePolicy = new Policy() { + @Override + public boolean isLogStoreCompliant(LogStore store) { + return true; + } + @Override + public PolicyCompliance doesResultConformToPolicy(VerificationResult result, + X509Certificate leaf) { + return PolicyCompliance.COMPLY; + } + }; + + Policy neverCompliantStorePolicy = new Policy() { + @Override + public boolean isLogStoreCompliant(LogStore store) { + return false; + } + @Override + public PolicyCompliance doesResultConformToPolicy(VerificationResult result, + X509Certificate leaf) { + return PolicyCompliance.COMPLY; + } + }; + + /* Time supplier that can be set to any arbitrary time */ + static class TimeSupplier implements Supplier { + private long currentTimeInMs; + + TimeSupplier(long currentTimeInMs) { + this.currentTimeInMs = currentTimeInMs; + } + + @Override + public Long get() { + return currentTimeInMs; + } + + public void setCurrentTimeInMs(long currentTimeInMs) { + this.currentTimeInMs = currentTimeInMs; + } + } + + private static final long JAN2024 = 1704103200000L; + private static final long JAN2022 = 1641031200000L; + + byte[] validLogList; + Path grandparentDir; + Path parentDir; + Path logList; + + @Before + public void setUp() throws Exception { + // Generated using $ flatc -b ./platform/src/main/ct_log_store.fbs ./ct_log_store_v3.json + validLogList = TestUtils.readTestFile("ct_log_store_v3.ctfb"); + } + + @After + public void tearDown() throws Exception { + if (logList != null) { + Files.deleteIfExists(logList); + Files.deleteIfExists(parentDir); + Files.deleteIfExists(grandparentDir); + } + } + + @Test + public void loadValidLogList_returnsCompliantState() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + byte[] pem = ("-----BEGIN PUBLIC KEY-----\n" + + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHblsqctplMVc5ramA7vSuNxUQxcomQwGAVAdnW" + + "TAWUYr3MgDHQW0LagJ95lB7QT75Ve6JgT2EVLOFGU7L3YrwA==" + + "\n-----END PUBLIC KEY-----\n") + .getBytes(US_ASCII); + ByteArrayInputStream is = new ByteArrayInputStream(pem); + LogInfo log1 = + new LogInfo.Builder() + .setPublicKey(OpenSSLKey.fromPublicKeyPemInputStream(is).getPublicKey()) + .setType(LogInfo.TYPE_RFC6962) + .setState(LogInfo.STATE_USABLE, 1667328840000L) + .setOperator("Operator 1") + .build(); + byte[] log1Id = Base64.getDecoder().decode("7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs="); + + assertNull("A null logId should return null", store.getKnownLog(/* logId= */ null)); + assertEquals("An existing logId should be returned", log1, store.getKnownLog(log1Id)); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); + assertEquals("The metric update for log list state should be compliant", + LogStore.State.COMPLIANT, metrics.states.get(0)); + } + + @Test + public void loadMalformedLogList_returnsMalformedState() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + String content = "}}"; + logList = writeLogList(content.getBytes()); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + + assertEquals("The log state should be malformed", LogStore.State.MALFORMED, + store.getState()); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); + assertEquals("The metric update for log list state should be malformed", + LogStore.State.MALFORMED, metrics.states.get(0)); + } + + @Test + public void loadLogListWithCorruptedPublicKey_throwsExceptionWhenQueried() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + // This log_id is associated with a corrupted public key in the log list test file. + byte[] log3Id = Base64.getDecoder().decode("+3xjpo0eBq3Qg4ibuNQyHLJFROv2/mlyKRkuOD5ebiM="); + + assertThrows("An exception must be thrown if the log public key is invalid", + LogStore.InvalidLogException.class, () -> store.getKnownLog(log3Id)); + assertEquals("Two metric update should be emitted", 2, metrics.states.size()); + assertEquals("The metric update for log list state should be compliant at first", + LogStore.State.COMPLIANT, metrics.states.get(0)); + assertEquals("The metric update for log list state should be malformed after", + LogStore.State.MALFORMED, metrics.states.get(1)); + } + + @Test + public void loadFutureLogList_returnsMalformedState() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); // The logs are usable from 2024 onwards. + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2022); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + + assertEquals("The log state should be malformed", LogStore.State.MALFORMED, + store.getState()); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); + assertEquals("The metric update for log list state should be malformed", + LogStore.State.MALFORMED, metrics.states.get(0)); + } + + @Test + public void loadMissingLogList_returnsNotFoundState() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + Path missingLogList = Paths.get("missing_dir", "missing_subdir", "does_not_exist_log_list"); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = + new LogStoreImplv3(alwaysCompliantStorePolicy, missingLogList, metrics, fakeTime); + + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + assertEquals("One metric update should be emitted", 1, metrics.states.size()); + assertEquals("The metric update for log list state should be not found", + LogStore.State.NOT_FOUND, metrics.states.get(0)); + } + + @Test + public void loadMissingAndThenFoundLogList_logListIsLoaded() throws Exception { + // Arrange + FakeStatsLog metrics = new FakeStatsLog(); + // Allocate a temporary file path and delete it. We keep the temporary + // path so that we can add a valid log list later on. + logList = writeLogList(new byte[0]); + Files.deleteIfExists(logList); + Files.deleteIfExists(parentDir); + Files.deleteIfExists(grandparentDir); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + + // Act + Files.createDirectory(grandparentDir); + Files.createDirectory(parentDir); + Files.write(logList, validLogList); + + // Assert + // 5min < 10min, we should not check the log list yet. + fakeTime.setCurrentTimeInMs(JAN2024 + 5L * 60 * 1000); + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + + // 12min, the log list should be reloadable. + fakeTime.setCurrentTimeInMs(JAN2024 + 12L * 60 * 1000); + assertEquals("The log state should be compliant", LogStore.State.COMPLIANT, + store.getState()); + } + + @Test + public void loadMissingThenTimeTravelBackwardsAndThenFoundLogList_logListIsLoaded() + throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + // Allocate a temporary file path and delete it. We keep the temporary + // path so that we can add a valid log list later on. + logList = writeLogList(new byte[0]); + Files.deleteIfExists(logList); + Files.deleteIfExists(parentDir); + Files.deleteIfExists(grandparentDir); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024 + 100L * 60 * 1000); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log state should be not found", LogStore.State.NOT_FOUND, + store.getState()); + + Files.createDirectory(grandparentDir); + Files.createDirectory(parentDir); + Files.write(logList, validLogList); + // Move back in time. + fakeTime.setCurrentTimeInMs(JAN2024); + + assertEquals("The log state should be compliant", LogStore.State.COMPLIANT, + store.getState()); + } + + @Test + public void loadExistingAndThenRemovedLogList_logListIsNotFound() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log should be loaded", LogStore.State.COMPLIANT, store.getState()); + + Files.delete(logList); + // 12min, the log list should be reloadable. + fakeTime.setCurrentTimeInMs(JAN2024 + 12L * 60 * 1000); + + assertEquals("The log should have been refreshed", LogStore.State.NOT_FOUND, + store.getState()); + } + + @Test + public void loadExistingLogListAndThenMoveDirectory_logListIsNotFound() throws Exception { + FakeStatsLog metrics = new FakeStatsLog(); + logList = writeLogList(validLogList); + TimeSupplier fakeTime = new TimeSupplier(/* currentTimeInMs= */ JAN2024); + LogStore store = new LogStoreImplv3(alwaysCompliantStorePolicy, logList, metrics, fakeTime); + assertEquals("The log should be loaded", LogStore.State.COMPLIANT, store.getState()); + + Path oldParentDir = parentDir; + parentDir = grandparentDir.resolve("more_current"); + Files.move(oldParentDir, parentDir); + logList = parentDir.resolve("log_list.fbct"); + // 12min, the log list should be reloadable. + fakeTime.setCurrentTimeInMs(JAN2024 + 12L * 60 * 1000); + + assertEquals("The log should have been refreshed", LogStore.State.NOT_FOUND, + store.getState()); + } + + private Path writeLogList(byte[] content) throws IOException { + grandparentDir = Files.createTempDirectory("v3"); + parentDir = Files.createDirectory(grandparentDir.resolve("current")); + Path file = Files.createFile(parentDir.resolve("log_list.fbct")); + Files.write(file, content); + return file; + } +} diff --git a/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java b/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java index cc3b40b42..d47f18e17 100644 --- a/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java +++ b/platform/src/test/java/org/conscrypt/ct/PolicyImplTest.java @@ -20,9 +20,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + import org.conscrypt.java.security.cert.FakeX509Certificate; import org.junit.Assume; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -36,9 +40,11 @@ public class PolicyImplTest { private static final String OPERATOR2 = "operator 2"; private static LogInfo usableOp1Log1; private static LogInfo usableOp1Log2; + private static LogInfo usableStaticOp1Log; private static LogInfo retiredOp1LogOld; private static LogInfo retiredOp1LogNew; private static LogInfo usableOp2Log; + private static LogInfo usableStaticOp2Log; private static LogInfo retiredOp2Log; private static SignedCertificateTimestamp embeddedSCT; private static SignedCertificateTimestamp ocspSCT; @@ -81,6 +87,9 @@ public String getFormat() { } } + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + @BeforeClass public static void setUp() { /* Defines LogInfo for the tests. Only a subset of the attributes are @@ -89,37 +98,49 @@ public static void setUp() { */ usableOp1Log1 = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x01})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_USABLE, JAN2022) .build(); usableOp1Log2 = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x02})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_USABLE, JAN2022) .build(); + usableStaticOp1Log = new LogInfo.Builder() + .setPublicKey(new FakePublicKey(new byte[] {0x07})) + .setType(LogInfo.TYPE_STATIC_CT_API) + .setOperator(OPERATOR1) + .setState(LogInfo.STATE_USABLE, JAN2022) + .build(); retiredOp1LogOld = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x03})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_RETIRED, JAN2022) .build(); retiredOp1LogNew = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x06})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR1) .setState(LogInfo.STATE_RETIRED, JUN2023) .build(); usableOp2Log = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x04})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR2) .setState(LogInfo.STATE_USABLE, JAN2022) .build(); + usableStaticOp2Log = new LogInfo.Builder() + .setPublicKey(new FakePublicKey(new byte[] {0x08})) + .setType(LogInfo.TYPE_STATIC_CT_API) + .setOperator(OPERATOR2) + .setState(LogInfo.STATE_USABLE, JAN2022) + .build(); retiredOp2Log = new LogInfo.Builder() .setPublicKey(new FakePublicKey(new byte[] {0x05})) - .setUrl("") + .setType(LogInfo.TYPE_RFC6962) .setOperator(OPERATOR2) .setState(LogInfo.STATE_RETIRED, JAN2022) .build(); @@ -373,11 +394,44 @@ public void invalidOneEmbeddedOneOCSPVerificationResult() throws Exception { p.doesResultConformToPolicyAt(result, leaf, JAN2024)); } + public void validVerificationResultPartialStatic(SignedCertificateTimestamp sct) + throws Exception { + PolicyImpl p = new PolicyImpl(); + + VerifiedSCT vsct1 = new VerifiedSCT.Builder(sct) + .setStatus(VerifiedSCT.Status.VALID) + .setLogInfo(usableOp1Log1) + .build(); + + VerifiedSCT vsct2 = new VerifiedSCT.Builder(sct) + .setStatus(VerifiedSCT.Status.VALID) + .setLogInfo(usableStaticOp2Log) + .build(); + + VerificationResult result = new VerificationResult(); + result.add(vsct1); + result.add(vsct2); + + X509Certificate leaf = new FakeX509Certificate(); + assertEquals("Two valid SCTs from different operators", PolicyCompliance.COMPLY, + p.doesResultConformToPolicyAt(result, leaf, JAN2024)); + } + + @Test + public void validEmbeddedVerificationResultPartialStatic() throws Exception { + validVerificationResultPartialStatic(embeddedSCT); + } + + @Test + public void validOCSPVerificationResultPartialStatic() throws Exception { + validVerificationResultPartialStatic(ocspSCT); + } + @Test public void validRecentLogStore() throws Exception { PolicyImpl p = new PolicyImpl(); - LogStore store = new LogStoreImpl(p) { + LogStore store = new LogStoreImplv3(p) { @Override public long getTimestamp() { return DEC2023; @@ -390,7 +444,7 @@ public long getTimestamp() { public void invalidFutureLogStore() throws Exception { PolicyImpl p = new PolicyImpl(); - LogStore store = new LogStoreImpl(p) { + LogStore store = new LogStoreImplv3(p) { @Override public long getTimestamp() { return JAN2025; @@ -403,7 +457,7 @@ public long getTimestamp() { public void invalidOldLogStore() throws Exception { PolicyImpl p = new PolicyImpl(); - LogStore store = new LogStoreImpl(p) { + LogStore store = new LogStoreImplv3(p) { @Override public long getTimestamp() { return JAN2023; diff --git a/platform/src/test/java/org/conscrypt/metrics/MetricsTest.java b/platform/src/test/java/org/conscrypt/metrics/MetricsTest.java index b4ed38c37..6e316da6c 100644 --- a/platform/src/test/java/org/conscrypt/metrics/MetricsTest.java +++ b/platform/src/test/java/org/conscrypt/metrics/MetricsTest.java @@ -17,6 +17,8 @@ package org.conscrypt.metrics; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; import android.util.StatsEvent; @@ -31,6 +33,51 @@ public class MetricsTest { public static final int TLS_HANDSHAKE_REPORTED = 317; + @Test + public void test_statsLogImpl_isSingleton() { + TestUtils.assumeStatsLogAvailable(); + StatsLog instance1 = StatsLogImpl.getInstance(); + StatsLog instance2 = StatsLogImpl.getInstance(); + assertSame(instance1, instance2); + } + + @Test + public void test_statsLogImpl_threadProcessesQueue() throws Exception { + TestUtils.assumeStatsLogAvailable(); + StatsLog statsLog = StatsLogImpl.getInstance(); + assertTrue("Expected StatsLogImpl instance", statsLog instanceof StatsLogImpl); + StatsLogImpl impl = (StatsLogImpl) statsLog; + + java.lang.reflect.Field queueField = StatsLogImpl.class.getDeclaredField("logQueue"); + queueField.setAccessible(true); + @SuppressWarnings("unchecked") + java.util.concurrent.BlockingQueue queue = + (java.util.concurrent.BlockingQueue) queueField.get(impl); + + // Clear queue to start fresh + queue.clear(); + + // Log something to trigger queue insertion (only applies to SDK > 32) + impl.countTlsHandshake(true, "TLSv1.3", "TLS_AES_128_GCM_SHA256", 100); + + if (Platform.isSdkGreater(32)) { + // Verify the background thread processes and empties the queue + long startTime = System.currentTimeMillis(); + boolean empty = false; + while (System.currentTimeMillis() - startTime < 2000) { // 2s timeout + if (queue.isEmpty()) { + empty = true; + break; + } + Thread.sleep(10); + } + assertTrue("Queue was not processed (thread might be dead)", empty); + } else { + // For SDK <= 32, it writes directly, queue should remain empty + assertTrue("Queue should be empty for SDK <= 32", queue.isEmpty()); + } + } + // Tests that ReflexiveEvent produces the same event as framework's. @Test public void test_reflexiveEvent() throws Exception { diff --git a/platform/src/test/resources/ct_log_store_v3.ctfb b/platform/src/test/resources/ct_log_store_v3.ctfb new file mode 100644 index 0000000000000000000000000000000000000000..0b8f41bd11c383743a05688a5e741b892eab2b15 GIT binary patch literal 1008 zcmb1OU|?_#adQGv3_J`f3>*vs3=#}1KsEz|0uVC-F$lou6BjxU^*}kSKyeWu_5kvq zFflMR0NFf1+T!p3|3DqgHbA}s5DNgYgJ8Ew3sj#tkndlRT9jClU!>rfSC*KQnW7L- zn&h2Y3AaZFs7BkkBC85L4JVQ%LQb^?KEOwhz9ZvA`OJu*tOa`&e<|CvT=YJ%#7^J zP7EyUf8<^y_x+bK?E14qPEtD|fWfYB-mP-;$4gh->Yef<`-N>vzq)bHC?@dU8;X3 zZ@a#rThqKy*7(#J<5wCxL_NUnH!4ch zPpr~5EXnjv%eC+}k8m%`Pxf-FNb)HO%J2*|C`$~q^mWaO&^9XuyI+7o2AF<$fbq@7 zzz$3qpm+zR0~G%}P~iln9~A!>5{wUpriDM#GgdsgJ!QtDhZ*fpICB+DjV)}}i>$iN zA@Sj_*zC*yYt);rv~TxQakKutZr=&zAO-DJF0B7TeqQ}3>pBbUpOC!pEHA^#L|0#5 zlW?P|P~$4gV6zJ2auZLJq^$5f$Ktet?Ba0mME?{EsDBi&`lkcvD-|F{PuC>+Np@$> z%F~%sj>bIOwv2iAr5$%doW(U}@~|-m$j(hU5b36U=LEAX>lWP=oZn|UzGeCSG<=sD z%QwNGb0VqM`eoV&zHV%yPi%4=WHg}8gNlhv7H3-Si^7aVItF$opGj|CM&4>!h Lb4@FWg2WL3;c*m6 literal 0 HcmV?d00001 diff --git a/platform/src/test/resources/ct_log_store_v3.json b/platform/src/test/resources/ct_log_store_v3.json new file mode 100644 index 000000000..77eb084ff --- /dev/null +++ b/platform/src/test/resources/ct_log_store_v3.json @@ -0,0 +1,418 @@ +{ + "version_major": 1, + "version_minor": 1, + "timestamp": 1704070861000, + "logs": [ + { + "log_id": "7s3QZNXbGs7FXLedtM0TojKHRny87N7DUUhZRnEftZs=", + "public_key": [ + 48, + 89, + 48, + 19, + 6, + 7, + 42, + -122, + 72, + -50, + 61, + 2, + 1, + 6, + 8, + 42, + -122, + 72, + -50, + 61, + 3, + 1, + 7, + 3, + 66, + 0, + 4, + 29, + -71, + 108, + -87, + -53, + 105, + -108, + -59, + 92, + -26, + -74, + -90, + 3, + -69, + -46, + -72, + -36, + 84, + 67, + 23, + 40, + -103, + 12, + 6, + 1, + 80, + 29, + -99, + 100, + -64, + 89, + 70, + 43, + -36, + -56, + 3, + 29, + 5, + -76, + 45, + -88, + 9, + -9, + -103, + 65, + -19, + 4, + -5, + -27, + 87, + -70, + 38, + 4, + -10, + 17, + 82, + -50, + 20, + 101, + 59, + 47, + 118, + 43, + -64 + ], + "operator": "Operator 1", + "type": "Rfc6962", + "state": "Usable", + "state_timestamp": 1667328840000 + }, + { + "log_id": "TnWjJ1yaEMM4W2zU3z9S6x3w4I4bjWnAsfpksWKaOd8=", + "public_key": [ + 48, + 89, + 48, + 19, + 6, + 7, + 42, + -122, + 72, + -50, + 61, + 2, + 1, + 6, + 8, + 42, + -122, + 72, + -50, + 61, + 3, + 1, + 7, + 3, + 66, + 0, + 4, + 32, + -126, + -95, + -7, + 103, + 104, + -88, + -28, + -37, + -108, + -104, + -30, + -31, + 104, + -121, + -28, + 9, + 109, + 32, + 53, + 51, + 56, + 60, + -81, + 20, + -86, + -41, + 8, + 24, + -16, + -3, + 22, + -101, + -45, + -1, + 124, + 39, + -126, + -44, + -121, + -73, + 78, + 36, + 70, + 59, + -5, + -82, + -66, + -56, + 35, + 82, + 32, + 43, + -86, + 68, + 5, + -2, + 84, + -7, + -43, + -15, + 29, + 69, + -102 + ], + "operator": "Operator 1", + "type": "Rfc6962", + "state": "Usable", + "state_timestamp": 1700960461000 + }, + { + "log_id": "2ra/az+1tiKfm8K7XGvocJFxbLtRhIU0vaQ9MEjX+6s=", + "public_key": [ + 48, + 89, + 48, + 19, + 6, + 7, + 42, + -122, + 72, + -50, + 61, + 2, + 1, + 6, + 8, + 42, + -122, + 72, + -50, + 61, + 3, + 1, + 7, + 3, + 66, + 0, + 4, + 119, + -79, + -101, + 123, + -113, + -26, + -117, + 53, + -2, + 58, + -110, + 41, + 45, + -84, + -118, + -115, + 81, + -118, + 37, + -4, + -109, + -74, + -41, + -96, + -117, + 41, + 55, + 113, + 29, + 51, + -54, + -52, + 51, + -22, + 40, + -71, + 31, + -30, + -84, + -61, + -87, + 93, + -35, + -105, + -66, + -10, + -98, + -108, + 37, + -35, + 54, + -127, + -47, + -21, + 93, + 41, + -61, + 43, + 68, + -15, + 91, + -54, + 21, + 72 + ], + "operator": "Operator 2", + "type": "Rfc6962", + "state": "Usable", + "state_timestamp": 1669770061000 + }, + { + "log_id": "DleUvPOuqT4zGyyZB7P3kN+bwj1xMiXdIaklrGHFTiE=", + "public_key": [ + 48, + 89, + 48, + 19, + 6, + 7, + 42, + -122, + 72, + -50, + 61, + 2, + 1, + 6, + 8, + 42, + -122, + 72, + -50, + 61, + 3, + 1, + 7, + 3, + 66, + 0, + 4, + 7, + -4, + 30, + -24, + 99, + -114, + -1, + 28, + 49, + -118, + -4, + -72, + 30, + 25, + 43, + 96, + 80, + 0, + 62, + -114, + -98, + -38, + 119, + 55, + -29, + -91, + -88, + -38, + -115, + -108, + -8, + 107, + -24, + 61, + 100, + -113, + 39, + 63, + 117, + -77, + -4, + 107, + 18, + -16, + 55, + 6, + 79, + 100, + 88, + 117, + 20, + 93, + 86, + 82, + -26, + 106, + 43, + 20, + 76, + -20, + -127, + -47, + -22, + 62 + ], + "operator": "Operator 2", + "type": "Static", + "state": "Usable", + "state_timestamp": 1667328840000 + }, + { + "log_id": "+3xjpo0eBq3Qg4ibuNQyHLJFROv2/mlyKRkuOD5ebiM=", + "public_key": [ + 0 + ], + "operator": "Operator Invalid PubKey", + "type": "Rfc6962", + "state": "Usable", + "state_timestamp": 1667328840000 + } + ] +} + diff --git a/r8_include_list.txt b/r8_include_list.txt new file mode 100644 index 000000000..1c72450ea --- /dev/null +++ b/r8_include_list.txt @@ -0,0 +1 @@ +com.android.org.conscrypt.internal.flatbuffers.** diff --git a/scripts/export_to_ag.py b/scripts/export_to_ag.py new file mode 100755 index 000000000..6d2704a64 --- /dev/null +++ b/scripts/export_to_ag.py @@ -0,0 +1,747 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 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. + +"""Automated export tool for Conscrypt: google3 to Android Gerrit. + +This script coordinates the end-to-end export process by automating: +1. Pre-export workspace cleanup (git reset --hard & git clean -fd) in local +Android repo. +2. Google3 pre-export clang-formatting via fix_format.py. +3. Auto-discovery or provisioning of Android / Cider-G workspace. +4. Copybara export_to_ag execution. +5. Preserving the genuine Copybara export commit at HEAD (preserving commit +metadata/PiperOrigin-RevId). +6. Android repackaging (via direct currysrc Java transformer into temporary +directories). +7. Android-specific clang-formatting (git-clang-format) on modified and +repackaged files. +8. Git staging, amending the genuine Copybara commit with all repackaged files, +and uploading to Gerrit (refs/for/master). +""" + +import argparse +import getpass +import os +import pathlib +import re +import shutil +import subprocess +import sys +import tempfile +from typing import Any, Dict, List, Optional, Tuple + +DEFAULT_COPYBARA_BIN = "/google/data/ro/teams/copybara/copybara" +GERRIT_SSO_URL = "sso://googleplex-android/platform/external/conscrypt" + + +def run_cmd( + cmd: List[str], + cwd: Optional[pathlib.Path] = None, + env: Optional[Dict[str, str]] = None, + check: bool = True, +) -> subprocess.CompletedProcess[Any]: + """Helper to run a subprocess command with logging.""" + print(f"==> Running: {' '.join(cmd)}" + (f" (in {cwd})" if cwd else "")) + try: + return subprocess.run( + cmd, cwd=cwd, env=env, check=check, text=True, capture_output=False + ) + except subprocess.CalledProcessError as e: + sys.exit( + f"ERROR: Command failed with exit code {e.returncode}.\nCommand:" + f" {' '.join(cmd)}" + ) + + +def get_copybara_bin(custom_path: Optional[str]) -> str: + """Finds a valid Copybara executable.""" + if custom_path: + if shutil.which(custom_path) or pathlib.Path(custom_path).is_file(): + return custom_path + sys.exit(f"ERROR: Specified Copybara binary not found: {custom_path}") + + if pathlib.Path(DEFAULT_COPYBARA_BIN).is_file(): + return DEFAULT_COPYBARA_BIN + + which_copybara = shutil.which("copybara") + if which_copybara: + return which_copybara + + sys.exit( + f"ERROR: Copybara executable not found at '{DEFAULT_COPYBARA_BIN}' or in" + " PATH." + ) + + +def get_current_user() -> str: + """Extracts username using getpass or path fallback.""" + try: + return getpass.getuser() + except Exception: + pass + + parts = pathlib.Path(__file__).resolve().parts + if "cloud" in parts: + idx = parts.index("cloud") + if idx + 1 < len(parts): + return parts[idx + 1] + + return "miguelaranda" + + +def resolve_android_and_build_top( + explicit_dir: Optional[str], +) -> Tuple[ + pathlib.Path, + Optional[pathlib.Path], + Optional[tempfile.TemporaryDirectory[str]], +]: + """Resolves Android conscrypt directory and ANDROID_BUILD_TOP.""" + temp_dir_obj = None + + if explicit_dir: + p = pathlib.Path(explicit_dir).resolve() + if not p.is_dir(): + sys.exit( + f"ERROR: Specified Android directory does not exist: {explicit_dir}" + ) + + curr = p + build_top = None + while curr != curr.parent: + if (curr / "build" / "envsetup.sh").is_file() or ( + curr / "tools" / "currysrc" + ).is_dir(): + build_top = curr + break + curr = curr.parent + return p, build_top, None + + # Check local home directory checkouts first + user = get_current_user() + home_candidates = [ + pathlib.Path(f"/usr/local/google/home/{user}/main/external/conscrypt"), + pathlib.Path(f"/usr/local/google/home/{user}/external/conscrypt"), + pathlib.Path.home() / "main" / "external" / "conscrypt", + ] + for cand in home_candidates: + if (cand / ".git").is_dir(): + bt = ( + cand.parent.parent + if (cand.parent.parent / "tools" / "currysrc").is_dir() + else None + ) + return cand.resolve(), bt.resolve() if bt else None, None + + for env_var in ["CONSCYPT_ANDROID_DIR", "ANDROID_BUILD_TOP"]: + val = os.environ.get(env_var) + if val: + p = ( + pathlib.Path(val) / "external" / "conscrypt" + if not val.endswith("conscrypt") + else pathlib.Path(val) + ) + if p.is_dir(): + bt = pathlib.Path(os.environ.get("ANDROID_BUILD_TOP", val)) + return p.resolve(), bt.resolve(), None + + print("\nCreating temporary clone of Android Gerrit repo...") + temp_dir_obj = tempfile.TemporaryDirectory(prefix="conscrypt_export_") + temp_path = pathlib.Path(temp_dir_obj.name) + run_cmd(["git", "clone", "--depth", "1", GERRIT_SSO_URL, str(temp_path)]) + return temp_path, None, temp_dir_obj + + +def step_clean_android_repo( + android_dir: pathlib.Path, + is_explicit: bool = False, + force: bool = False, +) -> None: + """Resets working tree and cleans untracked files in the local Android repository.""" + if is_explicit and not force and sys.stdin.isatty(): + ans = input( + "\nWARNING: About to run 'git reset --hard' and 'git clean -fd' in" + f" {android_dir}.\nAny uncommitted changes in that directory will be" + " lost.\nContinue? [y/N] " + ) + if ans.strip().lower() not in ("y", "yes"): + sys.exit("Aborted by user.") + + print(f"\n--- Cleaning local Android repo working tree at {android_dir} ---") + run_cmd(["git", "reset", "--hard", "HEAD"], cwd=android_dir, check=False) + run_cmd(["git", "clean", "-fd"], cwd=android_dir, check=False) + + +def step_format_google3(script_dir: pathlib.Path) -> None: + """Runs fix_format.py on google3 workspace opened files.""" + fix_format_script = script_dir / "fix_format.py" + if not fix_format_script.is_file(): + print( + f"Warning: {fix_format_script} not found. Skipping google3 format fix." + ) + return + + print("\n--- Step 1: Formatting google3 source files ---") + run_cmd([sys.executable, str(fix_format_script)], cwd=script_dir) + + +def step_copybara_export( + copybara_bin: str, + copybara_config: pathlib.Path, + android_dir: pathlib.Path, + cl: Optional[str], + dry_run: bool, + extra_copybara_args: List[str], +) -> Optional[str]: + """Runs Copybara export_to_ag workflow and extracts the active Gerrit review URL or CL number.""" + print("\n--- Step 2: Running Copybara export_to_ag ---") + + cmd = [ + copybara_bin, + str(copybara_config), + "export_to_ag", + ] + + if cl: + cmd.append(cl) + + cmd.extend(["--force", "--init-history", "--ignore-noop", "--verbose"]) + + cmd.extend([ + f"--git-destination-url=file://{android_dir}", + "--git-destination-fetch=master", + "--git-destination-push=master", + ]) + + if dry_run: + cmd.append("--dry-run") + + print(f"--- Resetting {android_dir} master branch to goog/master ---") + run_cmd(["git", "fetch", "goog", "master"], cwd=android_dir, check=False) + run_cmd(["git", "checkout", "master"], cwd=android_dir, check=False) + run_cmd( + ["git", "reset", "--hard", "goog/master"], cwd=android_dir, check=False + ) + run_cmd( + ["git", "config", "receive.denyCurrentBranch", "ignore"], + cwd=android_dir, + check=False, + ) + + cmd.extend(extra_copybara_args) + print(f"==> Running: {' '.join(cmd)}") + proc = subprocess.run(cmd, text=True, capture_output=True, check=False) + if proc.stdout: + print(proc.stdout) + if proc.stderr: + print(proc.stderr, file=sys.stderr) + + if proc.returncode == 4: + print( + "Copybara reported NOOP (return code 4): changes are already exported" + " to destination." + ) + elif proc.returncode != 0 and not dry_run: + sys.exit( + f"ERROR: Copybara export failed with return code {proc.returncode}." + ) + + gerrit_cl = None + output = (proc.stdout or "") + (proc.stderr or "") + m = re.search( + r"https://googleplex-android-review\.git\.corp\.google\.com/c/platform/external/conscrypt/\+/(\d+)", + output, + ) + if m: + gerrit_cl = m.group(1) + + return gerrit_cl + + +def step_checkout_copybara_commit( + android_dir: pathlib.Path, +) -> None: + """Ensures we operate on the genuine local Copybara commit at HEAD.""" + print( + "\n--- Operating on fresh Copybara export commit at HEAD in" + f" {android_dir} ---" + ) + run_cmd(["git", "reset", "--hard", "master"], cwd=android_dir, check=False) + run_cmd(["git", "log", "-1", "--oneline"], cwd=android_dir) + + +def find_java_binary(build_top: Optional[pathlib.Path]) -> str: + """Finds an appropriate Java runtime binary (preferring prebuilt JDK 21).""" + if build_top: + jdk21 = ( + build_top / "prebuilts" / "jdk" / "jdk21" / "linux-x86" / "bin" / "java" + ) + if jdk21.is_file(): + return str(jdk21) + + for candidate in (build_top / "prebuilts" / "jdk").glob("**/bin/java"): + if candidate.is_file(): + return str(candidate) + + return shutil.which("java") or "java" + + +def find_currysrc_jar( + build_top: Optional[pathlib.Path], +) -> Optional[pathlib.Path]: + """Finds a prebuilt currysrc.jar in the build tree if available.""" + if not build_top: + return None + + paths = [ + build_top / "out" / "host" / "linux-x86" / "framework" / "currysrc.jar", + build_top + / "out" + / "soong" + / "host" + / "linux-x86" + / "framework" + / "currysrc.jar", + build_top + / "out" + / "soong" + / ".intermediates" + / "external" + / "icu" + / "tools" + / "srcgen" + / "currysrc" + / "currysrc" + / "linux_glibc_common" + / "combined" + / "currysrc.jar", + ] + for p in paths: + if p.is_file(): + return p + + for p in (build_top / "out").glob("**/currysrc.jar"): + if p.is_file(): + return p + + return None + + +def run_direct_repackage( + android_dir: pathlib.Path, build_top: pathlib.Path +) -> bool: + """Runs the currysrc Java repackaging transformation safely into temporary staging directories before copying.""" + currysrc_jar = find_currysrc_jar(build_top) + if not currysrc_jar: + return False + + java_bin = find_java_binary(build_top) + srcgen_dir = android_dir / "srcgen" + repackaged_dir = android_dir / "repackaged" + + flags = [ + "--package-transformation", + "org.conscrypt:com.android.org.conscrypt", + "--tab-size", + "4", + ] + + check_files = [ + ("default-constructors.txt", "--default-constructors-file"), + ("core-platform-api.txt", "--core-platform-api-file"), + ("stable-core-platform-api.txt", "--stable-core-platform-api-file"), + ("module-api.txt", "--module-api-file"), + ("intra-core-api.txt", "--intra-core-api-file"), + ("unsupported-app-usage.json", "--unsupported-app-usage-file"), + ("flagged-api.json", "--flagged-api-file"), + ] + for fname, flag in check_files: + fpath = srcgen_dir / fname + if fpath.is_file() and fpath.stat().st_size > 0: + flags.extend([flag, str(fpath)]) + + modules = ["common", "openjdk", "platform", "testing"] + source_dirs = ["src/main/java", "src/test/java"] + + total_generated = 0 + for mod in modules: + for sdir in source_dirs: + in_dir = android_dir / mod / sdir + if in_dir.is_dir(): + with tempfile.TemporaryDirectory() as tmp_out: + cmd = [ + java_bin, + "-cp", + str(currysrc_jar), + "com.google.currysrc.aosp.RepackagingTransform", + "--source-dir", + str(in_dir), + "--target-dir", + tmp_out, + ] + flags + res = subprocess.run(cmd, capture_output=True, text=True, check=False) + if res.returncode != 0: + print(f"Error repackaging {mod}/{sdir}: {res.stderr}") + return False + + out_dir = repackaged_dir / mod / sdir + out_dir.mkdir(parents=True, exist_ok=True) + for item in pathlib.Path(tmp_out).glob("**/*"): + if item.is_file(): + rel = item.relative_to(tmp_out) + dest = out_dir / rel + dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(item, dest) + total_generated += 1 + + removals = [ + repackaged_dir + / "common/src/test/java/com/android/org/conscrypt/ConscryptSuite.java", + repackaged_dir + / "common/src/test/java/com/android/org/conscrypt/ConscryptJava7Suite.java", + repackaged_dir + / "common/src/main/java/com/android/org/conscrypt/metrics/ConscryptStatsLog.java", + repackaged_dir + / "openjdk/src/main/java/dalvik/annotation/optimization/FastNative.java", + repackaged_dir + / "openjdk/src/main/java/dalvik/annotation/optimization/CriticalNative.java", + ] + for r in removals: + if r.is_file(): + r.unlink() + + print( + f"Direct currysrc repackaging completed successfully ({total_generated}" + " files generated)." + ) + return True + + +def run_generate_android_src( + android_dir: pathlib.Path, build_top: pathlib.Path +) -> bool: + """Runs external/conscrypt/srcgen/generate_android_src.sh.""" + script_path = android_dir / "srcgen" / "generate_android_src.sh" + if not script_path.is_file(): + print(f"Error: {script_path} not found.") + return False + + env = os.environ.copy() + env["ANDROID_BUILD_TOP"] = str(build_top) + env["SKIP_BUILD_CURRYSRC"] = "true" + env["ANDROID_HOST_OUT"] = str(build_top / "out" / "host" / "linux-x86") + + jdk21_bin = build_top / "prebuilts" / "jdk" / "jdk21" / "linux-x86" / "bin" + if jdk21_bin.is_dir(): + env["PATH"] = f"{jdk21_bin}:{env.get('PATH', '')}" + + print(f"Executing {script_path}...") + res = subprocess.run( + [str(script_path)], + cwd=android_dir, + env=env, + capture_output=True, + text=True, + check=False, + ) + if res.returncode != 0: + print( + "generate_android_src.sh failed (exit code" + f" {res.returncode}):\n{res.stderr}\n{res.stdout}" + ) + return False + + print("generate_android_src.sh completed successfully.") + return True + + +def step_repackage_android( + android_dir: pathlib.Path, build_top: Optional[pathlib.Path] +) -> None: + """Runs currysrc repackaging via generate_android_src.sh.""" + print("\n--- Step 3: Running Android repackaging ---") + if build_top: + success = run_generate_android_src(android_dir, build_top) + if success: + return + print( + "[Notice] generate_android_src.sh failed; falling back to direct" + " repackaging..." + ) + success = run_direct_repackage(android_dir, build_top) + if success: + return + + print("[Notice] Could not run currysrc repackaging.") + + +def step_format_and_commit_android( + android_dir: pathlib.Path, + build_top: Optional[pathlib.Path], + skip_format: bool, +) -> None: + """Stages repackaged files, formats with git-clang-format, and amends the Copybara commit.""" + print( + "\n--- Step 4 & 5: Formatting and staging Android repository changes ---" + ) + + run_cmd(["git", "add", "-A"], cwd=android_dir) + + if not skip_format: + git_clang_format = None + clang_format = None + if build_top: + cand_gcf = ( + build_top + / "prebuilts" + / "clang" + / "host" + / "linux-x86" + / "clang-stable" + / "bin" + / "git-clang-format" + ) + cand_cf = ( + build_top + / "prebuilts" + / "clang" + / "host" + / "linux-x86" + / "clang-stable" + / "bin" + / "clang-format" + ) + if cand_gcf.is_file(): + git_clang_format = str(cand_gcf) + if cand_cf.is_file(): + clang_format = str(cand_cf) + + if not git_clang_format: + git_clang_format = shutil.which("git-clang-format") + + if git_clang_format: + cmd = [git_clang_format] + if clang_format: + cmd.extend(["--binary", clang_format]) + cmd.append("HEAD~1") + run_cmd(cmd, cwd=android_dir, check=False) + run_cmd(["git", "add", "-A"], cwd=android_dir) + else: + print("Warning: git-clang-format not found. Skipping Android formatting.") + + print("Amending Copybara commit with repackaged and formatted changes...") + msg = subprocess.check_output( + ["git", "log", "-1", "--format=%B"], cwd=android_dir, text=True + ).strip() + run_cmd( + ["git", "commit", "--amend", "--allow-empty", "-m", msg], + cwd=android_dir, + check=False, + ) + + +def step_upload_gerrit(android_dir: pathlib.Path, upload: bool) -> None: + """Uploads to Gerrit directly.""" + if upload: + print("\n--- Step 6: Uploading complete change to Gerrit ---") + run_cmd( + [ + "git", + "push", + "-o", + "nokeycheck", + GERRIT_SSO_URL, + "HEAD:refs/for/master", + ], + cwd=android_dir, + ) + print( + "\nSuccessfully uploaded complete (source + repackaged + formatted)" + " change to Android Gerrit!" + ) + else: + print( + f"\nLocal Android repository at '{android_dir}' is updated, repackaged," + " and formatted." + ) + print("To upload to Gerrit, run:") + print( + f" cd {android_dir} && git push -o nokeycheck {GERRIT_SSO_URL}" + " HEAD:refs/for/master" + ) + + +def main() -> None: + parser = argparse.ArgumentParser( + description=( + "Automate exporting Conscrypt changes from google3 to Android Gerrit" + " with repackaging and clang-formatting." + ) + ) + parser.add_argument( + "--android_dir", + "-a", + type=str, + help=( + "Optional path to local Android external/conscrypt workspace. If" + " omitted, auto-detects workspaces. WARNING: Unless --skip_clean is" + " set, this executes 'git reset --hard' and 'git clean -fd' in the" + " target directory, wiping any uncommitted changes." + ), + ) + parser.add_argument( + "--cl", + type=str, + help="Specific Piper CL number or revision to export (e.g. 123456789).", + ) + parser.add_argument( + "--copybara_bin", + type=str, + help=( + "pathlib.Path to Copybara binary. Defaults to" + " /google/data/ro/teams/copybara/copybara." + ), + ) + parser.add_argument( + "--force", + "-f", + action="store_true", + help=( + "Skip interactive confirmation prompts when cleaning an explicit" + " --android_dir." + ), + ) + parser.add_argument( + "--skip_clean", + action="store_true", + help=( + "Skip cleaning the local Android git repository working tree before" + " export." + ), + ) + parser.add_argument( + "--skip_format_g3", + action="store_true", + help="Skip running fix_format.py in google3 before export.", + ) + parser.add_argument( + "--skip_repackage", + action="store_true", + help="Skip running currysrc repackaging in the Android repository.", + ) + parser.add_argument( + "--skip_format_ag", + action="store_true", + help="Skip running git-clang-format in the Android repository.", + ) + parser.add_argument( + "--upload", + "-u", + action="store_true", + help=( + "Upload / push the complete commit (source + repackaged + formatted)" + " to Android Gerrit." + ), + ) + parser.add_argument( + "--dry_run", + action="store_true", + help="Run Copybara with --dry-run and display planned actions.", + ) + + args, extra_copybara_args = parser.parse_known_args() + + script_path = pathlib.Path(__file__).resolve() + main_src_dir = ( + script_path.parent.parent + if script_path.parent.name == "scripts" + else script_path.parent + ) + copybara_config = main_src_dir / "copy.bara.sky" + + if not copybara_config.is_file(): + sys.exit(f"ERROR: Copybara config file not found at {copybara_config}") + + copybara_bin = get_copybara_bin(args.copybara_bin) + android_dir, build_top, temp_dir_holder = resolve_android_and_build_top( + args.android_dir + ) + + print("=======================================================") + print(" Conscrypt google3 -> Android Gerrit Automated Exporter") + print("=======================================================") + print(f"Google3 source dir : {main_src_dir}") + print(f"Copybara config : {copybara_config}") + print( + "CL / Revision :" + f" {args.cl if args.cl else '(Latest HEAD / default)'}" + ) + print(f"Android repo dir : {android_dir}") + print(f"Android build top : {build_top if build_top else '(None)'}") + print(f"Upload to Gerrit : {args.upload}") + print(f"Dry run mode : {args.dry_run}") + print("=======================================================\n") + + try: + # Step 0: Clean local Android repo + if not args.skip_clean: + step_clean_android_repo( + android_dir, + is_explicit=bool(args.android_dir), + force=args.force, + ) + + # Step 1: Format google3 + if not args.skip_format_g3 and not args.dry_run: + step_format_google3(main_src_dir) + + # Step 2: Copybara export + _ = step_copybara_export( + copybara_bin=copybara_bin, + copybara_config=copybara_config, + android_dir=android_dir, + cl=args.cl, + dry_run=args.dry_run, + extra_copybara_args=extra_copybara_args, + ) + + if args.dry_run: + print("\n[Dry Run] Copybara dry-run completed successfully.") + return + + # Step 3: Ensure we operate on the fresh Copybara export commit at HEAD + step_checkout_copybara_commit(android_dir) + + # Step 4: Repackage Android safely + if not args.skip_repackage: + step_repackage_android(android_dir, build_top) + + # Step 5: Format Android with git-clang-format and Amend Copybara commit + step_format_and_commit_android( + android_dir=android_dir, + build_top=build_top, + skip_format=args.skip_format_ag, + ) + + # Step 6: Upload to Gerrit + step_upload_gerrit(android_dir, upload=args.upload) + + finally: + if temp_dir_holder: + temp_dir_holder.cleanup() + + +if __name__ == "__main__": + main() diff --git a/settings.gradle b/settings.gradle index 830dc8e7a..bcf23507a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,13 +16,11 @@ dependencyResolutionManagement { rootProject.name = "conscrypt" if (System.env.ANDROID_HOME && file(System.env.ANDROID_HOME).exists()) { include ":conscrypt-android" - include ":conscrypt-android-platform" include ":conscrypt-android-stub" include ":conscrypt-benchmark-android" include ":conscrypt-libcore-stub" project(':conscrypt-android').projectDir = "$rootDir/android" as File - project(':conscrypt-android-platform').projectDir = "$rootDir/platform" as File project(':conscrypt-android-stub').projectDir = "$rootDir/android-stub" as File project(':conscrypt-benchmark-android').projectDir = "$rootDir/benchmark-android" as File project(':conscrypt-libcore-stub').projectDir = "$rootDir/libcore-stub" as File diff --git a/testing/src/main/java/org/conscrypt/TestUtils.java b/testing/src/main/java/org/conscrypt/TestUtils.java index ac1666d97..8ba7b3b47 100644 --- a/testing/src/main/java/org/conscrypt/TestUtils.java +++ b/testing/src/main/java/org/conscrypt/TestUtils.java @@ -407,20 +407,7 @@ public static SSLServerSocketFactory setUseEngineSocket(SSLServerSocketFactory c } static boolean getUseEngineSocketByDefault() { - try { - boolean sfDefault = - getBooleanField("OpenSSLSocketFactoryImpl", "useEngineSocketByDefault"); - boolean ssfDefault = - getBooleanField("OpenSSLServerSocketFactoryImpl", "useEngineSocketByDefault"); - if (sfDefault != ssfDefault) { - throw new IllegalStateException( - "Socket factory and server socket factory must\n" - + "use the same default implementation during testing"); - } - return sfDefault; - } catch (Exception e) { - throw new RuntimeException(e); - } + return true; } static boolean getBooleanField(String className, String fieldName) throws Exception { @@ -888,8 +875,7 @@ public static boolean isOsx() { public static boolean isAndroidSdkGreater(int sdkVersion) { try { - return (Boolean) - conscryptClass("Platform") + return (Boolean) conscryptClass("Platform") .getDeclaredMethod("isSdkGreater", int.class) .invoke(null, sdkVersion); } catch (ReflectiveOperationException e) { @@ -929,6 +915,20 @@ public static boolean callPlatformMethod(String methodName, boolean defaultValue } } + /** + * Best-effort check that this is running under TSAN. Returns false if not running under TSAN + * or when running outside Google. + */ + public static boolean isTsan() { + try { + return (Boolean) Class.forName("com.google.devtools.java.sanitizers.Sanitizers") + .getMethod("runningWithTsan") + .invoke(null); + } catch (Exception e) { + return false; + } + } + // Just a Runnable which can throw exceptions. @FunctionalInterface public interface ThrowingRunnable { diff --git a/testing/src/main/java/org/conscrypt/VeryBasicHttpServer.java b/testing/src/main/java/org/conscrypt/VeryBasicHttpServer.java index a61844035..40b5858ae 100644 --- a/testing/src/main/java/org/conscrypt/VeryBasicHttpServer.java +++ b/testing/src/main/java/org/conscrypt/VeryBasicHttpServer.java @@ -43,7 +43,8 @@ public class VeryBasicHttpServer { private final TestSSLContext context = TestSSLContext.create(); private final ServerSocket tlsSocket = context.serverSocket; - private final ServerSocket plainSocket = new ServerSocket(0); + private final ServerSocket plainSocket = + new ServerSocket(0, 50, TestUtils.getLoopbackAddress()); public VeryBasicHttpServer() throws IOException {} diff --git a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java index 9e5d69221..af253646e 100644 --- a/testing/src/main/java/org/conscrypt/java/security/StandardNames.java +++ b/testing/src/main/java/org/conscrypt/java/security/StandardNames.java @@ -22,6 +22,7 @@ import org.conscrypt.TestUtils; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -155,17 +156,24 @@ private static void provideSslContextEnabledProtocols(String algorithm, TLSVersi provideCipherPaddings("AES", new String[] {"PKCS7Padding"}); } - provideSslContextEnabledProtocols("TLS", TLSVersion.TLSv1, TLSVersion.TLSv13); - provideSslContextEnabledProtocols("TLSv1", TLSVersion.TLSv1, TLSVersion.TLSv12); - provideSslContextEnabledProtocols("TLSv1.1", TLSVersion.TLSv1, TLSVersion.TLSv12); - provideSslContextEnabledProtocols("TLSv1.2", TLSVersion.TLSv1, TLSVersion.TLSv12); - provideSslContextEnabledProtocols("TLSv1.3", TLSVersion.TLSv1, TLSVersion.TLSv13); - provideSslContextEnabledProtocols("Default", TLSVersion.TLSv1, TLSVersion.TLSv13); + if (TestUtils.isTlsV1Supported()) { + provideSslContextEnabledProtocols("TLS", TLSVersion.TLSv1, TLSVersion.TLSv13); + provideSslContextEnabledProtocols("TLSv1", TLSVersion.TLSv1, TLSVersion.TLSv12); + provideSslContextEnabledProtocols("TLSv1.1", TLSVersion.TLSv1, TLSVersion.TLSv12); + provideSslContextEnabledProtocols("TLSv1.2", TLSVersion.TLSv1, TLSVersion.TLSv12); + provideSslContextEnabledProtocols("TLSv1.3", TLSVersion.TLSv1, TLSVersion.TLSv13); + provideSslContextEnabledProtocols("Default", TLSVersion.TLSv1, TLSVersion.TLSv13); + } else { + provideSslContextEnabledProtocols("TLS", TLSVersion.TLSv12, TLSVersion.TLSv13); + provideSslContextEnabledProtocols("TLSv1.2", TLSVersion.TLSv12, TLSVersion.TLSv12); + provideSslContextEnabledProtocols("TLSv1.3", TLSVersion.TLSv12, TLSVersion.TLSv13); + provideSslContextEnabledProtocols("Default", TLSVersion.TLSv12, TLSVersion.TLSv13); + } } public static final String SSL_CONTEXT_PROTOCOLS_DEFAULT = "Default"; - public static final Set SSL_CONTEXT_PROTOCOLS = new HashSet(Arrays.asList( - SSL_CONTEXT_PROTOCOLS_DEFAULT, "TLS", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3")); + public static final Set SSL_CONTEXT_PROTOCOLS = new HashSet( + Arrays.asList(SSL_CONTEXT_PROTOCOLS_DEFAULT, "TLS", "TLSv1.2", "TLSv1.3")); public static final Set SSL_CONTEXT_PROTOCOLS_WITH_DEFAULT_CONFIG = new HashSet(Arrays.asList(SSL_CONTEXT_PROTOCOLS_DEFAULT, "TLS", "TLSv1.3")); // Deprecated TLS protocols... May or may not be present or enabled. @@ -182,7 +190,7 @@ private static void provideSslContextEnabledProtocols(String algorithm, TLSVersi } public static final Set KEY_TYPES = new HashSet( - Arrays.asList("RSA", "DSA", "DH_RSA", "DH_DSA", "EC", "EC_EC", "EC_RSA")); + Arrays.asList("RSA", "DSA", "DH_RSA", "DH_DSA", "EC", "EC_EC", "EC_RSA", "EdDSA")); static { if (IS_RI) { // DH_* are specified by standard names, but do not seem to be supported by RI @@ -191,8 +199,15 @@ private static void provideSslContextEnabledProtocols(String algorithm, TLSVersi } } - public static final Set SSL_SOCKET_PROTOCOLS = - new HashSet(Arrays.asList("TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3")); + public static final Set SSL_SOCKET_PROTOCOLS = new HashSet<>(); + static { + SSL_SOCKET_PROTOCOLS.add("TLSv1.2"); + SSL_SOCKET_PROTOCOLS.add("TLSv1.3"); + if (TestUtils.isTlsV1Supported()) { + SSL_SOCKET_PROTOCOLS.add("TLSv1"); + SSL_SOCKET_PROTOCOLS.add("TLSv1.1"); + } + } private enum TLSVersion { SSLv3("SSLv3"), @@ -423,11 +438,23 @@ public static void assertValidCipherSuites(String[] cipherSuites) { assertValidCipherSuites(CIPHER_SUITES, cipherSuites); } + private static final List OPTIONAL_CIPHER_SUITES = + Arrays.asList("SSL_RSA_WITH_3DES_EDE_CBC_SHA"); + /** * Assert that the provided list of cipher suites matches the supported list. */ public static void assertSupportedCipherSuites(String[] cipherSuites) { - assertSupportedCipherSuites(CIPHER_SUITES, cipherSuites); + List filteredCipherSuites = new ArrayList<>(); + for (String cipherSuite : cipherSuites) { + if (OPTIONAL_CIPHER_SUITES.contains(cipherSuite)) { + continue; + } + filteredCipherSuites.add(cipherSuite); + } + String[] filteredCipherSuitesArray = new String[filteredCipherSuites.size()]; + filteredCipherSuites.toArray(filteredCipherSuitesArray); + assertSupportedCipherSuites(CIPHER_SUITES, filteredCipherSuitesArray); } /** diff --git a/testing/src/main/java/tests/util/ServiceTester.java b/testing/src/main/java/tests/util/ServiceTester.java index d5b0fb570..7d3536121 100644 --- a/testing/src/main/java/tests/util/ServiceTester.java +++ b/testing/src/main/java/tests/util/ServiceTester.java @@ -24,6 +24,7 @@ import java.security.Security; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; @@ -44,25 +45,25 @@ public interface Test { void test(Provider p, String algorithm) throws Exception; } - private static Provider[] providersToTest = null; + private static Provider[] providersToTest; - /** - * Sets the list of providers to be returned by {@link #getProviders()}, {@link - * #getProviders(String)}, and {@link #getProviders(Map)}. - */ - public static void setProviders(Provider[] providers) { + /** + * Sets the list of providers to be returned by {@link #getProviders()}, {@link + * #getProviders(String)}, and {@link #getProviders(Map)}. + */ + public static void setProviders(Provider[] providers) { if (providers.length == 0) { throw new IllegalArgumentException("providers must not be empty"); } providersToTest = providers.clone(); } - /** - * If {@link #setProviders(Provider[])} has not been called, returns the list of providers from - * {@link Security.getProviders()}. Otherwise, returns the list of providers set by {@link - * #setProviders(Provider[])}. - */ - public static Provider[] getProviders() { + /** + * If {@link #setProviders(Provider[])} has not been called, returns the list of providers from + * {@link Security#getProviders()}. Otherwise, returns the list of providers set by {@link + * #setProviders(Provider[])}. + */ + public static Provider[] getProviders() { if (providersToTest != null) { return providersToTest.clone(); } else { @@ -70,12 +71,12 @@ public static Provider[] getProviders() { } } - /** - * If {@link #setProviders(Provider[])} has not been called, returns the list of providers from - * {@link Security.getProviders(filter)}. Otherwise, returns the list of providers set by {@link - * #setProviders(Provider[])}. - */ - public static Provider[] getProviders(String filter) { + /** + * If {@link #setProviders(Provider[])} has not been called, returns the list of providers from + * {@link Security#getProviders(String)}. Otherwise, returns the list of providers set by {@link + * #setProviders(Provider[])}. + */ + public static Provider[] getProviders(String filter) { if (providersToTest != null) { return providersToTest.clone(); } else { @@ -83,19 +84,18 @@ public static Provider[] getProviders(String filter) { } } - /** - * If {@link #setProviders(Provider[])} has not been called, returns the list of providers from - * {@link Security.getProviders(filter)} that support the given service and algorithm. Otherwise, - * returns the list of providers set by {@link #setProviders(Provider[])}. - */ - public static Provider[] getProviders(Map filter) { + /** + * If {@link #setProviders(Provider[])} has not been called, returns the list of providers from + * {@link Security#getProviders(Map)} that support the given service and algorithm. Otherwise, + * returns the list of providers set by {@link #setProviders(Provider[])}. + */ + public static Provider[] getProviders(Map filter) { if (providersToTest != null) { return providersToTest.clone(); } else { return Security.getProviders(filter); } } - private static final String SEPARATOR = "||"; private final String service; private final Set providers = new LinkedHashSet<>(); @@ -200,9 +200,7 @@ public ServiceTester skipCombination(String provider, String algorithm) { */ public void run(Test test) { if (providersToTest != null) { - for (Provider p : providersToTest) { - providers.add(p); - } + Collections.addAll(providers, providersToTest); } if (providers.isEmpty()) { providers.addAll(Arrays.asList(Security.getProviders()));