• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.crypto.hpke;
18 
19 import android.annotation.FlaggedApi;
20 
21 import libcore.util.NonNull;
22 import libcore.util.Nullable;
23 
24 import java.security.GeneralSecurityException;
25 import java.security.InvalidKeyException;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.NoSuchProviderException;
28 import java.security.PrivateKey;
29 import java.security.Provider;
30 import java.security.PublicKey;
31 import java.security.Security;
32 
33 /**
34  * Provides access to implementations of HPKE hybrid cryptography as per RFC 9180.
35  * <p>
36  * Provider and HPKE suite selection are done via the {@code getInstance}
37  * methods, and then instances of senders and receivers can be created using
38  * {@code newSender} or {newReceiver}.  Each sender and receiver is independent, i.e. does
39  * not share any encapsulated state with other senders or receivers created via this
40  * {@code Hpke}.
41  * <p>
42  * HPKE suites are composed of a key encapsulation mechanism (KEM), a key derivation
43  * function (KDF) and an authenticated cipher algorithm (AEAD) as defined in
44  * RFC 9180 section 7. {@link java.security.spec.NamedParameterSpec NamedParameterSpecs} for
45  * these can be found in {@link KemParameterSpec}, {@link KdfParameterSpec} and
46  * {@link AeadParameterSpec}.  These can be composed into a full HPKE suite name used to
47  * request a particular implementation using
48  * {@link Hpke#getSuiteName(KemParameterSpec, KdfParameterSpec, AeadParameterSpec)}.
49  *
50  * @see KemParameterSpec
51  * @see KdfParameterSpec
52  * @see AeadParameterSpec
53  */
54 @SuppressWarnings("NewApi") // Public HPKE classes are always all present together.
55 @FlaggedApi(com.android.libcore.Flags.FLAG_HPKE_PUBLIC_API)
56 public class Hpke {
57     private static final String SERVICE_NAME = "ConscryptHpke";
58     static final byte[] DEFAULT_PSK = new byte[0];
59     static final byte[] DEFAULT_PSK_ID = DEFAULT_PSK;
60     private final Provider provider;
61     private final Provider.Service service;
62 
Hpke(@onNull String suiteName, @NonNull Provider provider)63     private Hpke(@NonNull String suiteName, @NonNull Provider provider)
64             throws NoSuchAlgorithmException {
65         this.provider = provider;
66         service = getService(provider, suiteName);
67         if (service == null) {
68             throw new NoSuchAlgorithmException("No such HPKE suite: " + suiteName);
69         }
70     }
71 
findFirstProvider(@onNull String suiteName)72     private static @NonNull Provider findFirstProvider(@NonNull String suiteName)
73             throws NoSuchAlgorithmException {
74         for (Provider provider : Security.getProviders()) {
75             if (getService(provider, suiteName) != null) {
76                 return provider;
77             }
78         }
79         throw new NoSuchAlgorithmException("No Provider found for HPKE suite: " + suiteName);
80     }
81 
82     @SuppressWarnings("InlinedApi") // For SERVICE_NAME field which belongs to this class
getService(Provider provider, String suiteName)83     private static Provider.Service getService(Provider provider, String suiteName)
84             throws NoSuchAlgorithmException {
85         if (suiteName == null || suiteName.isEmpty()) {
86             throw new NoSuchAlgorithmException();
87         }
88         return provider.getService(SERVICE_NAME, suiteName);
89     }
90 
findSpi()91     @NonNull HpkeSpi findSpi() {
92         Object instance;
93         try {
94             instance = service.newInstance(null);
95         } catch (NoSuchAlgorithmException e) {
96             throw new IllegalStateException("Initialisation error", e);
97         }
98         if (instance instanceof HpkeSpi) {
99             return (HpkeSpi) instance;
100         } else {
101             DuckTypedHpkeSpi spi = DuckTypedHpkeSpi.newInstance(instance);
102             if (spi != null) {
103                 return spi;
104             }
105         }
106         throw new IllegalStateException(
107                 String.format("Provider %s is incorrectly configured", provider.getName()));
108     }
109 
110     /**
111      * Returns the {@link Provider} being used by this Hpke instance.
112      * <p>
113      *
114      * @return the Provider
115      */
getProvider()116     public @NonNull Provider getProvider() {
117         return provider;
118     }
119 
120     /**
121      * Returns an Hpke instance configured for the requested HPKE suite, using the
122      * highest priority {@link Provider} which implements it.
123      * <p>
124      * Use {@link Hpke#getSuiteName(KemParameterSpec, KdfParameterSpec, AeadParameterSpec)} for
125      * generating HPKE suite names from {@link java.security.spec.NamedParameterSpec
126      * NamedParameterSpecs}
127      *
128      * @param suiteName the HPKE suite to use
129      * @return an Hpke instance configured for the requested suite
130      * @throws NoSuchAlgorithmException if no Providers can be found for the requested suite
131      */
getInstance(@onNull String suiteName)132     public static @NonNull Hpke getInstance(@NonNull String suiteName)
133             throws NoSuchAlgorithmException {
134         return new Hpke(suiteName, findFirstProvider(suiteName));
135     }
136 
137     /**
138      * Returns an Hpke instance configured for the requested HPKE suite, using the
139      * requested {@link Provider} by name.
140      *
141      * @param suiteName    the HPKE suite to use
142      * @param providerName the name of the provider to use
143      * @return an Hpke instance configured for the requested suite and Provider
144      * @throws NoSuchAlgorithmException if the named Provider does not implement this suite
145      * @throws NoSuchProviderException  if no Provider with the requested name can be found
146      * @throws IllegalArgumentException if providerName is null or empty
147      */
getInstance(@onNull String suiteName, @NonNull String providerName)148     public static @NonNull Hpke getInstance(@NonNull String suiteName, @NonNull String providerName)
149             throws NoSuchAlgorithmException, NoSuchProviderException {
150         if (providerName == null || providerName.isEmpty()) {
151             throw new IllegalArgumentException("Invalid Provider Name");
152         }
153         Provider provider = Security.getProvider(providerName);
154         if (provider == null) {
155             throw new NoSuchProviderException();
156         }
157         return new Hpke(suiteName, provider);
158     }
159 
160     /**
161      * Returns an Hpke instance configured for the requested HPKE suite, using the
162      * requested {@link Provider}.
163      *
164      * @param suiteName the HPKE suite to use
165      * @param provider  the provider to use
166      * @return an Hpke instance configured for the requested suite and Provider
167      * @throws NoSuchAlgorithmException if the Provider does not implement this suite
168      * @throws IllegalArgumentException if provider is null
169      */
getInstance(@onNull String suiteName, @NonNull Provider provider)170     public static @NonNull Hpke getInstance(@NonNull String suiteName, @NonNull Provider provider)
171             throws NoSuchAlgorithmException, NoSuchProviderException {
172         if (provider == null) {
173             throw new IllegalArgumentException("Null Provider");
174         }
175         return new Hpke(suiteName, provider);
176     }
177 
178     /**
179      * Generates a full HPKE suite name from the named parameter specifications of its components,
180      * which have names reflecting their usage in RFC 9180.
181      * <p>
182      * HPKE suites are composed of a key encapsulation mechanism (KEM), a key derivation
183      * function (KDF) and an authenticated cipher algorithm (AEAD) as defined in
184      * RFC 9180 section 7. {@link java.security.spec.NamedParameterSpec NamedParameterSpecs} for
185      * these can be foundu in {@link KemParameterSpec}, {@link KdfParameterSpec} and
186      * {@link AeadParameterSpec}.
187      *
188      * @see <a href="https://www.rfc-editor.org/rfc/rfc9180.html#section-7">RFC 9180 Section 7</a>
189      * @see KemParameterSpec
190      * @see KdfParameterSpec
191      * @see AeadParameterSpec
192      *
193      * @param kem  the key encapsulation mechanism to use
194      * @param kdf  the key derivation function to use
195      * @param aead the AEAD cipher to use
196      * @return a fully composed HPKE suite name
197      */
getSuiteName(@onNull KemParameterSpec kem, @NonNull KdfParameterSpec kdf, @NonNull AeadParameterSpec aead)198     public static @NonNull String getSuiteName(@NonNull KemParameterSpec kem,
199             @NonNull KdfParameterSpec kdf, @NonNull AeadParameterSpec aead) {
200         return kem.getName() + "/" + kdf.getName() + "/" + aead.getName();
201     }
202 
203     /**
204      * One shot API to seal a single message using BASE mode (no authentication).
205      *
206      * @see <a href="https://www.rfc-editor.org/rfc/rfc9180.html#name-encryption-and-decryption-2">
207      *     Opening and sealing</a>
208      * @param recipientKey public key of the recipient
209      * @param info         additional application-supplied information, may be null or empty
210      * @param plaintext    the message to send
211      * @param aad          optional additional authenticated data, may be null or empty
212      * @return a Message object containing the encapsulated key, ciphertext and aad
213      * @throws InvalidKeyException      if recipientKey is null or an unsupported key format
214      */
seal(@onNull PublicKey recipientKey, @Nullable byte[] info, @NonNull byte[] plaintext, @Nullable byte[] aad)215     public @NonNull Message seal(@NonNull PublicKey recipientKey, @Nullable byte[] info,
216             @NonNull byte[] plaintext, @Nullable byte[] aad)
217             throws InvalidKeyException {
218         Sender.Builder senderBuilder = new Sender.Builder(this, recipientKey);
219         if (info != null) {
220             senderBuilder.setApplicationInfo(info);
221         }
222         Sender sender = senderBuilder.build();
223         byte[] encapsulated = sender.getEncapsulated();
224         byte[] ciphertext = sender.seal(plaintext, aad);
225         return new Message(encapsulated, ciphertext);
226     }
227 
228     /**
229      * One shot API to open a single ciphertext using BASE mode (no authentication).
230      *
231      * @see <a href="https://www.rfc-editor.org/rfc/rfc9180.html#name-encryption-and-decryption-2">
232      *     Opening and sealing</a>
233      * @param recipientKey private key of the recipient
234      * @param info         application-supplied information, may be null or empty
235      * @param message      the Message to open
236      * @param aad          optional additional authenticated data, may be null or empty
237      * @return decrypted plaintext
238      * @throws InvalidKeyException      if recipientKey is null or an unsupported key format
239      * @throws GeneralSecurityException if the decryption fails
240      */
open( @onNull PrivateKey recipientKey, @Nullable byte[] info, @NonNull Message message, @Nullable byte[] aad)241     public @NonNull byte[] open(
242             @NonNull PrivateKey recipientKey, @Nullable byte[] info, @NonNull Message message,
243             @Nullable byte[] aad)
244             throws GeneralSecurityException, InvalidKeyException {
245         Recipient.Builder recipientBuilder
246                 = new Recipient.Builder(this, message.getEncapsulated(), recipientKey);
247         if (info != null) {
248             recipientBuilder.setApplicationInfo(info);
249         }
250         return recipientBuilder.build().open(message.getCiphertext(), aad);
251     }
252 }
253