diff options
author | Pete Bentley <prb@google.com> | 2024-02-14 19:10:46 +0000 |
---|---|---|
committer | Pete Bentley <prb@google.com> | 2024-02-21 12:46:08 +0000 |
commit | 764e82c4deb6dc8230dba904b62ff7dff1bd7950 (patch) | |
tree | d8a3c9d87af0f6c7dea4973c351959fa33435380 | |
parent | e7b7dd6ca18c3cb6a91d16d23f1b95eaf834d226 (diff) | |
download | libcore-764e82c4deb6dc8230dba904b62ff7dff1bd7950.tar.gz |
Public HPKE API.
Provides an SPI for APIs to make use of HPKE implementations
available in the platform, and a KeySpec allowing "raw"
format XDH keys.
Bug: 323357598
Test: atest
API-Coverage-Bug: 326202329
Change-Id: Ia9e9977208156bd38c59e6502203110641fa5962
-rw-r--r-- | api/current.txt | 20 | ||||
-rw-r--r-- | luni/annotations/flagged_api/android/crypto/hpke/HpkeSpi.annotated.java | 49 | ||||
-rw-r--r-- | luni/annotations/flagged_api/android/crypto/hpke/XdhKeySpec.annotated.java | 41 | ||||
-rw-r--r-- | luni/src/main/java/android/crypto/hpke/HpkeSpi.java | 194 | ||||
-rw-r--r-- | luni/src/main/java/android/crypto/hpke/XdhKeySpec.java | 67 | ||||
-rw-r--r-- | luni/src/test/java/libcore/android/crypto/hpke/XdhKeySpecTest.java | 57 | ||||
-rw-r--r-- | non_openjdk_java_files.bp | 2 |
7 files changed, 430 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index 2ca5f34648f..c0af166c375 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1,4 +1,24 @@ // Signature format: 2.0 +package android.crypto.hpke { + + @FlaggedApi("com.android.libcore.hpke_v_apis") public interface HpkeSpi { + method @FlaggedApi("com.android.libcore.hpke_v_apis") @NonNull public byte[] engineExport(int, @Nullable byte[]); + method @FlaggedApi("com.android.libcore.hpke_v_apis") public void engineInitRecipient(@NonNull byte[], @NonNull java.security.PrivateKey, @Nullable byte[], @Nullable java.security.PublicKey, @Nullable byte[], @Nullable byte[]) throws java.security.InvalidKeyException; + method @FlaggedApi("com.android.libcore.hpke_v_apis") public void engineInitSender(@NonNull java.security.PublicKey, @Nullable byte[], @Nullable java.security.PrivateKey, @Nullable byte[], @Nullable byte[]) throws java.security.InvalidKeyException; + method @FlaggedApi("com.android.libcore.hpke_v_apis") public void engineInitSenderWithSeed(@NonNull java.security.PublicKey, @Nullable byte[], @Nullable java.security.PrivateKey, @Nullable byte[], @Nullable byte[], @NonNull byte[]) throws java.security.InvalidKeyException; + method @FlaggedApi("com.android.libcore.hpke_v_apis") @NonNull public byte[] engineOpen(@NonNull byte[], @Nullable byte[]) throws java.security.GeneralSecurityException; + method @FlaggedApi("com.android.libcore.hpke_v_apis") @NonNull public byte[] engineSeal(@NonNull byte[], @Nullable byte[]); + method @FlaggedApi("com.android.libcore.hpke_v_apis") @NonNull public byte[] getEncapsulated(); + } + + @FlaggedApi("com.android.libcore.hpke_v_apis") public final class XdhKeySpec extends java.security.spec.EncodedKeySpec { + ctor @FlaggedApi("com.android.libcore.hpke_v_apis") public XdhKeySpec(@NonNull byte[]); + method @FlaggedApi("com.android.libcore.hpke_v_apis") @NonNull public String getFormat(); + method @FlaggedApi("com.android.libcore.hpke_v_apis") @NonNull public byte[] getKey(); + } + +} + package android.system { public final class ErrnoException extends java.lang.Exception { diff --git a/luni/annotations/flagged_api/android/crypto/hpke/HpkeSpi.annotated.java b/luni/annotations/flagged_api/android/crypto/hpke/HpkeSpi.annotated.java new file mode 100644 index 00000000000..112cac1398a --- /dev/null +++ b/luni/annotations/flagged_api/android/crypto/hpke/HpkeSpi.annotated.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + + +package android.crypto.hpke; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.PrivateKey; +import java.security.PublicKey; + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +@SuppressWarnings({"unchecked", "deprecation", "all"}) +public interface HpkeSpi { + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public void engineInitSender(PublicKey recipientKey, byte[] info, PrivateKey senderKey, byte[] psk, byte[] psk_id); + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public void engineInitSenderWithSeed(PublicKey recipientKey, byte[] info, PrivateKey senderKey, byte[] psk, byte[] psk_id, byte[] sKe); + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public void engineInitRecipient(byte[] encapsulated, PrivateKey recipientKey, byte[] info, PublicKey senderKey, byte[] psk, byte[] psk_id); + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public byte[] engineSeal(byte[] plaintext, byte[] aad); + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public byte[] engineOpen(byte[] ciphertext, byte[] aad); + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public byte[] engineExport(int length, byte[] context); + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public byte[] getEncapsulated(); +} diff --git a/luni/annotations/flagged_api/android/crypto/hpke/XdhKeySpec.annotated.java b/luni/annotations/flagged_api/android/crypto/hpke/XdhKeySpec.annotated.java new file mode 100644 index 00000000000..5e44f555de3 --- /dev/null +++ b/luni/annotations/flagged_api/android/crypto/hpke/XdhKeySpec.annotated.java @@ -0,0 +1,41 @@ +/* + * 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. + */ + + +package android.crypto.hpke; + +import java.security.spec.EncodedKeySpec; + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +@SuppressWarnings({"unchecked", "deprecation", "all"}) +public final class XdhKeySpec extends EncodedKeySpec { + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public XdhKeySpec(byte[] encoded) { throw new RuntimeException("Stub!"); } + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public String getFormat() { throw new RuntimeException("Stub!"); } + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public byte[] getKey() { throw new RuntimeException("Stub!"); } + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public boolean equals(Object o) { throw new RuntimeException("Stub!"); } + +@android.annotation.FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_V_APIS) +public int hashCode() { throw new RuntimeException("Stub!"); } +} + diff --git a/luni/src/main/java/android/crypto/hpke/HpkeSpi.java b/luni/src/main/java/android/crypto/hpke/HpkeSpi.java new file mode 100644 index 00000000000..0abe0326daa --- /dev/null +++ b/luni/src/main/java/android/crypto/hpke/HpkeSpi.java @@ -0,0 +1,194 @@ +/* + * 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. + */ + +package android.crypto.hpke; + +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.PrivateKey; +import java.security.PublicKey; +import libcore.util.NonNull; +import libcore.util.Nullable; + + +/** + * Service Provider Interface for HPKE client API classes to communicate with implementations + * of HPKE as described in RFC 9180. + * <p> + * There are no standard Java Cryptography Architecture names or interface classes for HPKE, + * but instances of this class can be obtained by calling + * {@code Provider.getService("ConscryptHpke", String SuiteName)} where {@code suiteName} + * is the name of the HPKE suite, e.g. + * {@code "DHKEM_X25519_HKDF_SHA256/HKDF_SHA256/AES_128_GCM"}. + */ +public interface HpkeSpi { + /** + * Initialises an HPKE SPI in one of the sender modes described in RFC 9180. + * <p> + * If {@code psk} and {@code psk_id} are supplied then Pre-Shared Key Authentication + * will be used. + * <p> + * If {@code senderKey} is supplied then Asymmetric Key Authentication will be used. + * <p> + * If neither is supplied then "base" mode (no sender authentication) will be used. + * <p> + * Note that only base mode is currently supported on Android. + * <p> + * Public and private keys must be supplied in a format that can be used by the + * implementation. An instance of the {@code "XDH"} {@link java.security.KeyFactory} can + * be used to translate {@code KeySpecs} or keys from another {@link java.security.Provider} + * + * @param recipientKey public key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey private key of the sender, for symmetric auth modes only, else null + * @param psk pre-shared key, for PSK auth modes only, else null + * @param psk_id pre-shared key ID, for PSK auth modes only, else null + * @throws InvalidKeyException if recipientKey is null or an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this SPI has already been initialised + */ + void engineInitSender( + @NonNull PublicKey recipientKey, + @Nullable byte[] info, + @Nullable PrivateKey senderKey, + @Nullable byte[] psk, + @Nullable byte[] psk_id) + throws InvalidKeyException; + + /** + * Initialises an HPKE SPI in one of the sender modes described in RFC 9180 with + * a predefined random seed to allow testing against known test vectors. + * <p> + * This mode provides absolutely no security and should only be used for testing + * purposes. + * <p> + * If {@code psk} and {@code psk_id} are supplied then Pre-Shared Key Authentication + * will be used. + * <p> + * If {@code senderKey} is supplied then Asymmetric Key Authentication will be used. + * <p> + * If neither is supplied then "base" mode (no sender authentication) will be used. + * <p> + * Note that only base mode is currently supported on Android. + * <p> + * Public and private keys must be supplied in a format that can be used by the + * implementation. An instance of the {@code "XDH"} {@link java.security.KeyFactory} can + * be used to translate {@code KeySpecs} or keys from another {@link java.security.Provider} + * + * + * @param recipientKey public key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey private key of the sender, for symmetric auth modes only, else null + * @param psk pre-shared key, for PSK auth modes only, else null + * @param psk_id pre-shared key ID, for PSK auth modes only, else null + * @param sKe Predetermined random seed, should only be usesd for validation against + * known test vectors + * @throws InvalidKeyException if recipientKey is null or an unsupported key format or senderKey + * is an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this SPI has already been initialised + */ + void engineInitSenderWithSeed( + @NonNull PublicKey recipientKey, + @Nullable byte[] info, + @Nullable PrivateKey senderKey, + @Nullable byte[] psk, + @Nullable byte[] psk_id, + @NonNull byte[] sKe) + throws InvalidKeyException; + + /** + * Initialises an HPKE SPI in one of the sender modes described in RFC 9180. + * <p> + * If {@code psk} and {@code psk_id} are supplied then Pre-Shared Key Authentication + * will be used. + * <p> + * If {@code senderKey} is supplied then Asymmetric Key Authentication will be used. + * <p> + * If neither is supplied then "base" mode (no sender authentication) will be used. + * <p> + * Note that only base mode is currently supported on Android. + * <p> + * Public and private keys must be supplied in a format that can be used by the + * implementation. An instance of the {@code "XDH"} {@link java.security.KeyFactory} can + * be used to translate {@code KeySpecs} or keys from another {@link java.security.Provider} + * + * @param encapsulated encapsulated ephemeral key from a sender + * @param recipientKey private key of the recipient + * @param info application-supplied information, may be null or empty + * @param senderKey public key of sender, for asymmetric auth modes only, else null + * @param psk pre-shared key, for PSK auth modes only, else null + * @param psk_id pre-shared key ID, for PSK auth modes only, else null + * @throws InvalidKeyException if recipientKey is null or an unsupported key format or senderKey + * is an unsupported key format + * @throws UnsupportedOperationException if mode is not a supported HPKE mode + * @throws IllegalStateException if this SPI has already been initialised + */ + void engineInitRecipient( + @NonNull byte[] encapsulated, + @NonNull PrivateKey recipientKey, + @Nullable byte[] info, + @Nullable PublicKey senderKey, + @Nullable byte[] psk, + @Nullable byte[] psk_id) + throws InvalidKeyException; + + /** + * Seals a message, using the internal key schedule maintained by an HPKE sender SPI. + * + * @param plaintext the plaintext + * @param aad optional associated data, may be null or empty + * @return the ciphertext + * @throws NullPointerException if the plaintext is null + * @throws IllegalStateException if this SPI has not been initialised or if it was initialised + * as a recipient + */ + @NonNull byte[] engineSeal(@NonNull byte[] plaintext, @Nullable byte[] aad); + + /** + * Opens a message, using the internal key schedule maintained by an HPKE recipient SPI. + * + * @param ciphertext the ciphertext + * @param aad optional associated data, may be null or empty + * @return the plaintext + * @throws IllegalStateException if this SPI has not been initialised or if it was initialised + * as a sender + * @throws GeneralSecurityException on decryption failures + */ + @NonNull byte[] engineOpen(@NonNull byte[] ciphertext, @Nullable byte[] aad) + throws GeneralSecurityException; + + /** + * Exports secret key material from this SPI as described in RFC 9180. + * + * @param length expected output length + * @param context optional context string, may be null or empty + * @return exported value + * @throws IllegalArgumentException if the length is not valid for the KDF in use + * @throws IllegalStateException if this SPI has not been initialised + * + */ + @NonNull byte[] engineExport(int length, @Nullable byte[] context); + + /** + * Returns the encapsulated key material for an HPKE sender. + * + * @return the key material + * @throws IllegalStateException if this SPI has not been initialised or if it was initialised + * as a recipient + */ + @NonNull byte[] getEncapsulated(); +} diff --git a/luni/src/main/java/android/crypto/hpke/XdhKeySpec.java b/luni/src/main/java/android/crypto/hpke/XdhKeySpec.java new file mode 100644 index 00000000000..39627471cc5 --- /dev/null +++ b/luni/src/main/java/android/crypto/hpke/XdhKeySpec.java @@ -0,0 +1,67 @@ +/* + * 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. + */ + +package android.crypto.hpke; + +import java.security.spec.EncodedKeySpec; +import java.util.Arrays; +import java.util.Objects; +import libcore.util.NonNull; + +/** + * External Diffie–Hellman (XDH) key spec holding either a public or private key. + * <p> + * Subclasses {@code EncodedKeySpec} using the non-Standard "raw" format. The XdhKeyFactory + * class utilises this in order to create XDH keys from raw bytes and to return them + * as an XdhKeySpec allowing the raw key material to be extracted from an XDH key. + */ +public final class XdhKeySpec extends EncodedKeySpec { + /** + * Creates an instance of {@link XdhKeySpec} by passing a public or private key in its raw + * format. + */ + public XdhKeySpec(@NonNull byte[] encoded) { + super(encoded); + } + + @Override + @NonNull public String getFormat() { + return "raw"; + } + + /** + * Returns the public or private key in its raw format. + * + * @return key in its raw format. + */ + @NonNull public byte[] getKey() { + return getEncoded(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof EncodedKeySpec)) return false; + EncodedKeySpec that = (EncodedKeySpec) o; + return (getFormat().equals(that.getFormat()) + && (Arrays.equals(getEncoded(), that.getEncoded()))); + } + + @Override + public int hashCode() { + return Objects.hash(getFormat(), Arrays.hashCode(getEncoded())); + } +} diff --git a/luni/src/test/java/libcore/android/crypto/hpke/XdhKeySpecTest.java b/luni/src/test/java/libcore/android/crypto/hpke/XdhKeySpecTest.java new file mode 100644 index 00000000000..4187253f37d --- /dev/null +++ b/luni/src/test/java/libcore/android/crypto/hpke/XdhKeySpecTest.java @@ -0,0 +1,57 @@ +/* + * 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. + */ + +package libcore.android.crypto.hpke; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import android.crypto.hpke.XdhKeySpec; + +import java.util.Random; + +@RunWith(JUnit4.class) +public class XdhKeySpecTest { + byte[] keyBytes = new byte[32]; + + @Before + public void setup() { + Random random = new Random(); + random.nextBytes(keyBytes); + } + + @Test + public void create() { + XdhKeySpec spec = new XdhKeySpec(keyBytes); + assertEquals("raw", spec.getFormat().toLowerCase()); + assertArrayEquals(keyBytes, spec.getEncoded()); + assertArrayEquals(keyBytes, spec.getKey()); + } + + @Test + public void equality() { + XdhKeySpec spec1 = new XdhKeySpec(keyBytes); + XdhKeySpec spec2 = new XdhKeySpec(keyBytes); + assertEquals(spec1, spec2); + assertNotSame(spec1, spec2); + } +} diff --git a/non_openjdk_java_files.bp b/non_openjdk_java_files.bp index 0877a5009ce..bfe5ad8bad6 100644 --- a/non_openjdk_java_files.bp +++ b/non_openjdk_java_files.bp @@ -137,6 +137,8 @@ filegroup { name: "non_openjdk_javadoc_luni_files", srcs: [ "luni/src/main/java/android/compat/Compatibility.java", + "luni/src/main/java/android/crypto/hpke/HpkeSpi.java", + "luni/src/main/java/android/crypto/hpke/XdhKeySpec.java", "luni/src/main/java/android/system/ErrnoException.java", "luni/src/main/java/android/system/GaiException.java", "luni/src/main/java/android/system/IcmpHeaders.java", |