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