1 // Copyright 2021 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 /////////////////////////////////////////////////////////////////////////////// 16 17 package com.google.crypto.tink.hybrid.internal; 18 19 import com.google.crypto.tink.internal.BigIntegerEncoding; 20 import com.google.crypto.tink.proto.HpkePublicKey; 21 import com.google.crypto.tink.subtle.Bytes; 22 import java.math.BigInteger; 23 import java.security.GeneralSecurityException; 24 import javax.annotation.concurrent.GuardedBy; 25 import javax.annotation.concurrent.ThreadSafe; 26 27 /** 28 * Hybrid Public Key Encryption (HPKE) context for either a sender or a recipient. 29 * 30 * <p>https://www.rfc-editor.org/rfc/rfc9180.html#name-creating-the-encryption-con 31 */ 32 @ThreadSafe 33 final class HpkeContext { 34 private static final byte[] EMPTY_IKM = new byte[0]; 35 36 private final HpkeAead aead; 37 private final BigInteger maxSequenceNumber; 38 private final byte[] key; 39 private final byte[] baseNonce; 40 private final byte[] encapsulatedKey; 41 42 @GuardedBy("this") 43 private BigInteger sequenceNumber; 44 HpkeContext( byte[] encapsulatedKey, byte[] key, byte[] baseNonce, BigInteger maxSequenceNumber, HpkeAead aead)45 private HpkeContext( 46 byte[] encapsulatedKey, 47 byte[] key, 48 byte[] baseNonce, 49 BigInteger maxSequenceNumber, 50 HpkeAead aead) { 51 this.encapsulatedKey = encapsulatedKey; 52 this.key = key; 53 this.baseNonce = baseNonce; 54 this.sequenceNumber = BigInteger.ZERO; 55 this.maxSequenceNumber = maxSequenceNumber; 56 this.aead = aead; 57 } 58 59 /** Helper function factored out to facilitate unit testing. */ createContext( byte[] mode, byte[] encapsulatedKey, byte[] sharedSecret, HpkeKem kem, HpkeKdf kdf, HpkeAead aead, byte[] info)60 static HpkeContext createContext( 61 byte[] mode, 62 byte[] encapsulatedKey, 63 byte[] sharedSecret, 64 HpkeKem kem, 65 HpkeKdf kdf, 66 HpkeAead aead, 67 byte[] info) 68 throws GeneralSecurityException { 69 byte[] suiteId = HpkeUtil.hpkeSuiteId(kem.getKemId(), kdf.getKdfId(), aead.getAeadId()); 70 byte[] pskIdHash = kdf.labeledExtract(HpkeUtil.EMPTY_SALT, EMPTY_IKM, "psk_id_hash", suiteId); 71 byte[] infoHash = kdf.labeledExtract(HpkeUtil.EMPTY_SALT, info, "info_hash", suiteId); 72 byte[] keyScheduleContext = Bytes.concat(mode, pskIdHash, infoHash); 73 byte[] secret = kdf.labeledExtract(sharedSecret, EMPTY_IKM, "secret", suiteId); 74 75 byte[] key = kdf.labeledExpand(secret, keyScheduleContext, "key", suiteId, aead.getKeyLength()); 76 byte[] baseNonce = 77 kdf.labeledExpand(secret, keyScheduleContext, "base_nonce", suiteId, aead.getNonceLength()); 78 BigInteger maxSeqNo = maxSequenceNumber(aead.getNonceLength()); 79 80 return new HpkeContext(encapsulatedKey, key, baseNonce, maxSeqNo, aead); 81 } 82 83 /** 84 * Creates HPKE sender context according to KeySchedule() defined in 85 * https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1-9. 86 * 87 * @param recipientPublicKey recipient's public key (pkR) 88 * @param kem key encapsulation mechanism primitive 89 * @param kdf key derivation function primitive 90 * @param aead authenticated encryption with associated data primitive 91 * @param info application-specific information parameter to influence key generation 92 */ createSenderContext( HpkePublicKey recipientPublicKey, HpkeKem kem, HpkeKdf kdf, HpkeAead aead, byte[] info)93 static HpkeContext createSenderContext( 94 HpkePublicKey recipientPublicKey, HpkeKem kem, HpkeKdf kdf, HpkeAead aead, byte[] info) 95 throws GeneralSecurityException { 96 HpkeKemEncapOutput encapOutput = 97 kem.encapsulate(recipientPublicKey.getPublicKey().toByteArray()); 98 byte[] encapsulatedKey = encapOutput.getEncapsulatedKey(); 99 byte[] sharedSecret = encapOutput.getSharedSecret(); 100 return createContext(HpkeUtil.BASE_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info); 101 } 102 103 /** 104 * Creates HPKE sender context with authentication according to KeySchedule() defined in 105 * https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3. 106 * 107 * @param recipientPublicKey recipient's public key (pkR) 108 * @param kem key encapsulation mechanism primitive 109 * @param kdf key derivation function primitive 110 * @param aead authenticated encryption with associated data primitive 111 * @param info application-specific information parameter to influence key generation 112 * @param senderPrivateKey sender's private key (skS) 113 */ createAuthSenderContext( HpkePublicKey recipientPublicKey, HpkeKem kem, HpkeKdf kdf, HpkeAead aead, byte[] info, HpkeKemPrivateKey senderPrivateKey)114 static HpkeContext createAuthSenderContext( 115 HpkePublicKey recipientPublicKey, 116 HpkeKem kem, 117 HpkeKdf kdf, 118 HpkeAead aead, 119 byte[] info, 120 HpkeKemPrivateKey senderPrivateKey) 121 throws GeneralSecurityException { 122 HpkeKemEncapOutput encapOutput = 123 kem.authEncapsulate(recipientPublicKey.getPublicKey().toByteArray(), senderPrivateKey); 124 byte[] encapsulatedKey = encapOutput.getEncapsulatedKey(); 125 byte[] sharedSecret = encapOutput.getSharedSecret(); 126 return createContext(HpkeUtil.AUTH_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info); 127 } 128 129 /** 130 * Creates HPKE sender recipient context according to KeySchedule() defined in 131 * https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1-9. 132 * 133 * @param encapsulatedKey encapsulated key (enc) 134 * @param recipientPrivateKey recipient's private key (skR) 135 * @param kem key encapsulation mechanism primitive 136 * @param kdf key derivation function primitive 137 * @param aead authenticated encryption with associated data primitive 138 * @param info application-specific information parameter to influence key generation 139 */ createRecipientContext( byte[] encapsulatedKey, HpkeKemPrivateKey recipientPrivateKey, HpkeKem kem, HpkeKdf kdf, HpkeAead aead, byte[] info)140 static HpkeContext createRecipientContext( 141 byte[] encapsulatedKey, 142 HpkeKemPrivateKey recipientPrivateKey, 143 HpkeKem kem, 144 HpkeKdf kdf, 145 HpkeAead aead, 146 byte[] info) 147 throws GeneralSecurityException { 148 byte[] sharedSecret = kem.decapsulate(encapsulatedKey, recipientPrivateKey); 149 return createContext(HpkeUtil.BASE_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info); 150 } 151 152 /** 153 * Creates HPKE recipient context with authentication according to KeySchedule() defined in 154 * https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3. 155 * 156 * @param encapsulatedKey encapsulated key (enc) 157 * @param recipientPrivateKey recipient's private key (skR) 158 * @param kem key encapsulation mechanism primitive 159 * @param kdf key derivation function primitive 160 * @param aead authenticated encryption with associated data primitive 161 * @param info application-specific information parameter to influence key generation 162 * @param senderPublicKey sender's public key (pkS) 163 */ createAuthRecipientContext( byte[] encapsulatedKey, HpkeKemPrivateKey recipientPrivateKey, HpkeKem kem, HpkeKdf kdf, HpkeAead aead, byte[] info, HpkePublicKey senderPublicKey)164 static HpkeContext createAuthRecipientContext( 165 byte[] encapsulatedKey, 166 HpkeKemPrivateKey recipientPrivateKey, 167 HpkeKem kem, 168 HpkeKdf kdf, 169 HpkeAead aead, 170 byte[] info, 171 HpkePublicKey senderPublicKey) 172 throws GeneralSecurityException { 173 byte[] sharedSecret = 174 kem.authDecapsulate( 175 encapsulatedKey, recipientPrivateKey, senderPublicKey.getPublicKey().toByteArray()); 176 return createContext(HpkeUtil.AUTH_MODE, encapsulatedKey, sharedSecret, kem, kdf, aead, info); 177 } 178 maxSequenceNumber(int nonceLength)179 private static BigInteger maxSequenceNumber(int nonceLength) { 180 return BigInteger.ONE.shiftLeft(8 * nonceLength).subtract(BigInteger.ONE); 181 } 182 183 @GuardedBy("this") incrementSequenceNumber()184 private void incrementSequenceNumber() throws GeneralSecurityException { 185 if (sequenceNumber.compareTo(maxSequenceNumber) >= 0) { 186 throw new GeneralSecurityException("message limit reached"); 187 } 188 sequenceNumber = sequenceNumber.add(BigInteger.ONE); 189 } 190 191 /** ComputeNonce() from https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2-11. */ 192 @GuardedBy("this") computeNonce()193 private byte[] computeNonce() throws GeneralSecurityException { 194 return Bytes.xor( 195 baseNonce, 196 BigIntegerEncoding.toBigEndianBytesOfFixedLength(sequenceNumber, aead.getNonceLength())); 197 } 198 199 /** Returns the next nonce to use for seal/open. Also, increments the sequence number. */ computeNonceAndIncrementSequenceNumber()200 private synchronized byte[] computeNonceAndIncrementSequenceNumber() 201 throws GeneralSecurityException { 202 byte[] nonce = computeNonce(); 203 incrementSequenceNumber(); 204 return nonce; 205 } 206 getKey()207 byte[] getKey() { 208 return key; 209 } 210 getBaseNonce()211 byte[] getBaseNonce() { 212 return baseNonce; 213 } 214 getEncapsulatedKey()215 byte[] getEncapsulatedKey() { 216 return encapsulatedKey; 217 } 218 219 /** 220 * Performs AEAD encryption of {@code plaintext} with {@code associatedData} according to 221 * ContextS.Seal() defined in https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2-8. 222 * 223 * @return ciphertext 224 */ seal(byte[] plaintext, byte[] associatedData)225 byte[] seal(byte[] plaintext, byte[] associatedData) throws GeneralSecurityException { 226 byte[] nonce = computeNonceAndIncrementSequenceNumber(); 227 return aead.seal(key, nonce, plaintext, associatedData); 228 } 229 230 /** 231 * Performs AEAD decryption of {@code ciphertext} with {@code associatedData} according to 232 * ContextR.Open() defined in https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2-10. 233 * 234 * @return plaintext 235 */ open(byte[] ciphertext, byte[] associatedData)236 byte[] open(byte[] ciphertext, byte[] associatedData) throws GeneralSecurityException { 237 byte[] nonce = computeNonceAndIncrementSequenceNumber(); 238 return aead.open(key, nonce, ciphertext, associatedData); 239 } 240 } 241