1 /*
2  * Copyright (C) 2023 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 com.android.adservices.ohttp;
18 
19 import static com.android.adservices.service.adselection.encryption.AdSelectionEncryptionKey.AdSelectionEncryptionKeyType.AUCTION;
20 
21 import com.android.adservices.ohttp.algorithms.KemAlgorithmSpec;
22 import com.android.adservices.ohttp.algorithms.UnsupportedHpkeAlgorithmException;
23 import com.android.adservices.service.FlagsFactory;
24 import com.android.adservices.service.adselection.encryption.AdSelectionEncryptionKey;
25 import com.android.internal.util.Preconditions;
26 
27 import com.google.auto.value.AutoValue;
28 
29 import java.io.ByteArrayOutputStream;
30 import java.io.IOException;
31 import java.nio.charset.StandardCharsets;
32 import java.security.spec.InvalidKeySpecException;
33 import java.util.Arrays;
34 
35 /** Contains the key configuration required by Oblivious Http Client */
36 @AutoValue
37 public abstract class ObliviousHttpKeyConfig {
38 
39     private static int sKeyIdSizeInBytes = 1;
40     private static int sKemIdSizeInBytes = 2;
41     private static int sKdfIdSizeInBytes = 2;
42     private static int sAeadIdSizeInBytes = 2;
43     private static int sSymmetricAlgorithmsLengthInBytes = 2;
44 
45     /** Returns the Key Identifier that tells the server which public key we are using */
keyId()46     public abstract int keyId();
47 
48     /**
49      * Returns the Key Encapsulation Mechanism algorithm Identifier
50      *
51      * <p>https://www.rfc-editor.org/rfc/rfc9180#name-key-encapsulation-mechanism
52      */
kemId()53     public abstract int kemId();
54 
55     /**
56      * Returns the KDF Id from the first Symmetric Algorithm specified in the keyConfig
57      *
58      * <p>https://www.rfc-editor.org/rfc/rfc9180#name-key-derivation-functions-kd
59      */
kdfId()60     public abstract int kdfId();
61 
62     /**
63      * Returns the AEAD ID from the first Symmetric Algorithm specified in the keyConfig
64      *
65      * <p>https://www.rfc-editor.org/rfc/rfc9180#name-authenticated-encryption-wi
66      */
aeadId()67     public abstract int aeadId();
68 
69     /** Returns the public key in byte array format */
70     @SuppressWarnings("mutable")
publicKey()71     abstract byte[] publicKey();
72 
73     /** Returns true if the changed format for the auction server media type should be used */
useFledgeAuctionServerMediaTypeChange( @dSelectionEncryptionKey.AdSelectionEncryptionKeyType int keyType)74     public static boolean useFledgeAuctionServerMediaTypeChange(
75             @AdSelectionEncryptionKey.AdSelectionEncryptionKeyType int keyType) {
76         return keyType == AUCTION
77                 && FlagsFactory.getFlags().getFledgeAuctionServerMediaTypeChangeEnabled();
78     }
79 
80     /** Returns the OhttpRequestLabel depending on the FledgeAuctionServerMediaType */
getOhttpRequestLabel(boolean useFledgeAuctionServerMediaTypeChange)81     public static String getOhttpRequestLabel(boolean useFledgeAuctionServerMediaTypeChange) {
82         if (useFledgeAuctionServerMediaTypeChange) {
83             return "message/auction request";
84         }
85         return "message/bhttp request";
86     }
87 
88     /** Returns the OhttpResponseLabel depending on the FledgeAuctionServerMediaType */
getOhttpResponseLabel(boolean useFledgeAuctionServerMediaTypeChange)89     public static String getOhttpResponseLabel(boolean useFledgeAuctionServerMediaTypeChange) {
90         if (useFledgeAuctionServerMediaTypeChange) {
91             return "message/auction response";
92         }
93         return "message/bhttp response";
94     }
95 
96     /**
97      * Parses the keyConfig into its respective components
98      *
99      * <p>If there are multiple symmetric key algorithms defined in the keyConfig string, we only
100      * parse the first one.
101      *
102      * <p>https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-3.1-2
103      *
104      * <pre>
105      * HPKE Symmetric Algorithms {
106      *   HPKE KDF ID (16),
107      *   HPKE AEAD ID (16),
108      * }
109      *
110      * OHTTP Key Config {
111      *   Key Identifier (8),
112      *   HPKE KEM ID (16),
113      *   HPKE Public Key (Npk * 8),
114      *   HPKE Symmetric Algorithms Length (16),
115      *   HPKE Symmetric Algorithms (32..262140),
116      * }
117      * </pre>
118      *
119      * @param keyConfig The keyconfig to be parsed
120      * @throws InvalidKeySpecException if the key does not abide by the spec laid out in Oblivious
121      *     HTTP Draft.
122      */
fromSerializedKeyConfig(byte[] keyConfig)123     public static ObliviousHttpKeyConfig fromSerializedKeyConfig(byte[] keyConfig)
124             throws InvalidKeySpecException {
125 
126         int currentIndex = 0;
127 
128         // read KeyId
129         int keyId = writeUptoTwoBytesIntoInteger(keyConfig, currentIndex, sKeyIdSizeInBytes);
130         currentIndex += sKeyIdSizeInBytes;
131 
132         int kemId = writeUptoTwoBytesIntoInteger(keyConfig, currentIndex, sKemIdSizeInBytes);
133         currentIndex += sKemIdSizeInBytes;
134 
135         int publicKeyLength = getPublicKeyLengthInBytes(kemId);
136         byte[] publicKey =
137                 Arrays.copyOfRange(keyConfig, currentIndex, currentIndex + publicKeyLength);
138         currentIndex += publicKeyLength;
139 
140         int algorithmsLength =
141                 writeUptoTwoBytesIntoInteger(
142                         keyConfig, currentIndex, sSymmetricAlgorithmsLengthInBytes);
143         currentIndex += sSymmetricAlgorithmsLengthInBytes;
144 
145         if (currentIndex + algorithmsLength != keyConfig.length) {
146             throw new InvalidKeySpecException("Invalid length of symmetric algorithms");
147         }
148 
149         // We choose the first set of algorithms from the list
150 
151         int kdfId = writeUptoTwoBytesIntoInteger(keyConfig, currentIndex, sKdfIdSizeInBytes);
152         currentIndex += sKdfIdSizeInBytes;
153 
154         int aeadId = writeUptoTwoBytesIntoInteger(keyConfig, currentIndex, sAeadIdSizeInBytes);
155 
156         return builder()
157                 .setKeyId(keyId)
158                 .setKemId(kemId)
159                 .setKdfId(kdfId)
160                 .setAeadId(aeadId)
161                 .setPublicKey(publicKey)
162                 .build();
163     }
164 
165     /**
166      * Concatenates keyId, kemId, kdfId and aeadId according to OHTTP Spec.
167      *
168      * <p>https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-hpke-encapsulation
169      *
170      * <pre>hdr = concat(encode(1, keyID),
171      *            encode(2, kemID),
172      *            encode(2, kdfID),
173      *            encode(2, aeadID)) </pre>
174      */
serializeOhttpPayloadHeader()175     public byte[] serializeOhttpPayloadHeader() {
176         byte[] header = new byte[7];
177 
178         // read one byte from integer keyId
179         header[0] = (byte) (keyId() & 0xFF);
180 
181         copyTwoBytesFromInteger(kemId(), header, 1);
182 
183         copyTwoBytesFromInteger(kdfId(), header, 3);
184 
185         copyTwoBytesFromInteger(aeadId(), header, 5);
186 
187         return header;
188     }
189 
190     /**
191      * Generates the 'info' field as required by HPKE setupBaseS operation according to OHTTP spec
192      *
193      * <p>https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#name-hpke-encapsulation
194      *
195      * <pre>info = concat(encode_str("message/label request"),
196      *             encode(1, 0),
197      *             hdr)</pre>
198      */
createRecipientKeyInfo(boolean hasMediaTypeChanged)199     public RecipientKeyInfo createRecipientKeyInfo(boolean hasMediaTypeChanged) throws IOException {
200 
201         byte[] ohttpReqLabelBytes =
202                 getOhttpRequestLabel(hasMediaTypeChanged).getBytes(StandardCharsets.US_ASCII);
203         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
204         outputStream.write(ohttpReqLabelBytes);
205         outputStream.write((byte) 0);
206         outputStream.write(serializeOhttpPayloadHeader());
207 
208         return RecipientKeyInfo.create(outputStream.toByteArray());
209     }
210 
211     /** Get a copy of the public key array */
getPublicKey()212     public byte[] getPublicKey() {
213         return Arrays.copyOf(publicKey(), publicKey().length);
214     }
215 
216     /**
217      * Serialize the current keyConfig object into a byte array
218      *
219      * <p>This method is not a strict inverse of {@link #fromSerializedKeyConfig(byte[])}. While
220      * {@link #fromSerializedKeyConfig(byte[])} might receive a byte array with multiple AEAD and
221      * KDF algorithms, we eventually only store the first set of algorithms in
222      * ObliviousHttpKeyConfig, and thus we can only serialize the first set.
223      *
224      * <p>https://www.ietf.org/archive/id/draft-ietf-ohai-ohttp-03.html#section-3.1-2
225      *
226      * <pre>
227      * HPKE Symmetric Algorithms {
228      *   HPKE KDF ID (16),
229      *   HPKE AEAD ID (16),
230      * }
231      *
232      * OHTTP Key Config {
233      *   Key Identifier (8),
234      *   HPKE KEM ID (16),
235      *   HPKE Public Key (Npk * 8),
236      *   HPKE Symmetric Algorithms Length (16),
237      *   HPKE Symmetric Algorithms (32..262140),
238      * }
239      * </pre>
240      */
serializeKeyConfigToBytes()241     public byte[] serializeKeyConfigToBytes() {
242         int byteArraySize = getSerializedByteArraySize();
243         byte[] serializedArray = new byte[byteArraySize];
244         int currentIndex = 0;
245 
246         // read one byte from integer keyId
247         serializedArray[currentIndex++] = (byte) (keyId() & 0xFF);
248 
249         copyTwoBytesFromInteger(kemId(), serializedArray, currentIndex);
250         currentIndex += 2;
251 
252         // Copy the public key bytes
253         System.arraycopy(publicKey(), 0, serializedArray, currentIndex, publicKey().length);
254         currentIndex += publicKey().length;
255 
256         // Since we only store one set of algorithms, the algorithm length is the sum of KDF ID
257         // and AEAD id.
258         int algorithmsLength = sKdfIdSizeInBytes + sAeadIdSizeInBytes;
259         copyTwoBytesFromInteger(algorithmsLength, serializedArray, currentIndex);
260         currentIndex += 2;
261 
262         copyTwoBytesFromInteger(kdfId(), serializedArray, currentIndex);
263         currentIndex += 2;
264 
265         copyTwoBytesFromInteger(aeadId(), serializedArray, currentIndex);
266 
267         return serializedArray;
268     }
269 
270     /** Builder for ObliviousHttpKeyConfig. */
builder()271     public static ObliviousHttpKeyConfig.Builder builder() {
272         return new AutoValue_ObliviousHttpKeyConfig.Builder();
273     }
274 
275     /** Builder for {@link com.android.adservices.ohttp.ObliviousHttpKeyConfig}. */
276     @AutoValue.Builder
277     public abstract static class Builder {
278 
279         /** Sets key id. */
setKeyId(int keyId)280         public abstract Builder setKeyId(int keyId);
281 
282         /** Sets KEM id. */
setKemId(int kemId)283         public abstract Builder setKemId(int kemId);
284 
285         /** Sets KDF id. */
setKdfId(int kdfId)286         public abstract Builder setKdfId(int kdfId);
287 
288         /** Sets AEAD id. */
setAeadId(int aeadId)289         public abstract Builder setAeadId(int aeadId);
290 
291         /** Sets public key. */
setPublicKey(byte[] publicKey)292         public abstract Builder setPublicKey(byte[] publicKey);
293 
294         /** Builds the ObliviousHttpKeyConfig object. */
build()295         public abstract ObliviousHttpKeyConfig build();
296     }
297 
writeUptoTwoBytesIntoInteger( byte[] keyConfig, int startingIndex, int numberOfBytes)298     private static int writeUptoTwoBytesIntoInteger(
299             byte[] keyConfig, int startingIndex, int numberOfBytes) throws InvalidKeySpecException {
300         Preconditions.checkArgument(
301                 numberOfBytes == 1 || numberOfBytes == 2, "tried to write more than two bytes");
302 
303         if (startingIndex + numberOfBytes > keyConfig.length) {
304             throw new InvalidKeySpecException("Invalid length of the keyConfig");
305         }
306 
307         if (numberOfBytes == 1) {
308             return writeOneByteToInt(keyConfig, startingIndex);
309         }
310 
311         return writeTwoBytesToInt(keyConfig, startingIndex);
312     }
313 
writeOneByteToInt(byte[] input, int startingIndex)314     private static int writeOneByteToInt(byte[] input, int startingIndex) {
315         return input[startingIndex];
316     }
317 
writeTwoBytesToInt(byte[] input, int startingIndex)318     private static int writeTwoBytesToInt(byte[] input, int startingIndex) {
319         return ((input[startingIndex] & 0xff) << 8) | (input[startingIndex + 1] & 0xff);
320     }
321 
copyTwoBytesFromInteger( int sourceInteger, byte[] targetArray, int targetArrayStartPosition)322     private static void copyTwoBytesFromInteger(
323             int sourceInteger, byte[] targetArray, int targetArrayStartPosition) {
324         targetArray[targetArrayStartPosition] = (byte) ((sourceInteger >> 8) & 0xFF);
325         targetArray[targetArrayStartPosition + 1] = (byte) (sourceInteger & 0xFF);
326     }
327 
getPublicKeyLengthInBytes(int kemIdentifier)328     private static int getPublicKeyLengthInBytes(int kemIdentifier) throws InvalidKeySpecException {
329         try {
330             int length = KemAlgorithmSpec.get(kemIdentifier).publicKeyLength();
331             return length;
332         } catch (UnsupportedHpkeAlgorithmException e) {
333             throw new InvalidKeySpecException("Unsupported Kem identifier");
334         }
335     }
336 
getSerializedByteArraySize()337     private int getSerializedByteArraySize() {
338         return sKeyIdSizeInBytes
339                 + sKemIdSizeInBytes
340                 + publicKey().length
341                 + sSymmetricAlgorithmsLengthInBytes
342                 + sKdfIdSizeInBytes
343                 + sAeadIdSizeInBytes;
344     }
345 }
346