1 // Copyright 2023 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; 18 19 import com.google.crypto.tink.AccessesPartialKey; 20 import com.google.crypto.tink.InsecureSecretKeyAccess; 21 import com.google.crypto.tink.Key; 22 import com.google.crypto.tink.internal.BigIntegerEncoding; 23 import com.google.crypto.tink.internal.EllipticCurvesUtil; 24 import com.google.crypto.tink.subtle.EllipticCurves; 25 import com.google.crypto.tink.subtle.X25519; 26 import com.google.crypto.tink.util.SecretBytes; 27 import com.google.errorprone.annotations.Immutable; 28 import com.google.errorprone.annotations.RestrictedApi; 29 import java.math.BigInteger; 30 import java.security.GeneralSecurityException; 31 import java.security.spec.ECParameterSpec; 32 import java.security.spec.ECPoint; 33 import java.util.Arrays; 34 35 /** Representation of the decryption function for an HPKE hybrid encryption primitive. */ 36 @Immutable 37 public final class HpkePrivateKey extends HybridPrivateKey { 38 private final HpkePublicKey publicKey; 39 private final SecretBytes privateKeyBytes; 40 HpkePrivateKey(HpkePublicKey publicKey, SecretBytes privateKeyBytes)41 private HpkePrivateKey(HpkePublicKey publicKey, SecretBytes privateKeyBytes) { 42 this.publicKey = publicKey; 43 this.privateKeyBytes = privateKeyBytes; 44 } 45 validatePrivateKeyByteLength( HpkeParameters.KemId kemId, SecretBytes privateKeyBytes)46 private static void validatePrivateKeyByteLength( 47 HpkeParameters.KemId kemId, SecretBytes privateKeyBytes) throws GeneralSecurityException { 48 // Key lengths from 'Nsk' column in https://www.rfc-editor.org/rfc/rfc9180.html#table-2. 49 int keyLengthInBytes = privateKeyBytes.size(); 50 String parameterizedErrorMessage = 51 "Encoded private key byte length for " + kemId + " must be %d, not " + keyLengthInBytes; 52 if (kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) { 53 if (keyLengthInBytes != 32) { 54 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 32)); 55 } 56 return; 57 } 58 if (kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384) { 59 if (keyLengthInBytes != 48) { 60 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 48)); 61 } 62 return; 63 } 64 if (kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512) { 65 if (keyLengthInBytes != 66) { 66 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 66)); 67 } 68 return; 69 } 70 if (kemId == HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) { 71 if (keyLengthInBytes != 32) { 72 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 32)); 73 } 74 return; 75 } 76 throw new GeneralSecurityException("Unable to validate private key length for " + kemId); 77 } 78 isNistKem(HpkeParameters.KemId kemId)79 private static boolean isNistKem(HpkeParameters.KemId kemId) { 80 return kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256 81 || kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384 82 || kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512; 83 } 84 getNistCurveParams(HpkeParameters.KemId kemId)85 private static ECParameterSpec getNistCurveParams(HpkeParameters.KemId kemId) { 86 if (kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) { 87 return EllipticCurves.getNistP256Params(); 88 } 89 if (kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384) { 90 return EllipticCurves.getNistP384Params(); 91 } 92 if (kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512) { 93 return EllipticCurves.getNistP521Params(); 94 } 95 throw new IllegalArgumentException("Unable to determine NIST curve params for " + kemId); 96 } 97 98 /** 99 * Confirms that the private key encoded as {@code privateKeyBytes} corresponds to the public key 100 * encoded as {@code publicKeyBytes} given the HPKE {@code kemId}. 101 */ validateKeyPair( HpkeParameters.KemId kemId, byte[] publicKeyBytes, byte[] privateKeyBytes)102 private static void validateKeyPair( 103 HpkeParameters.KemId kemId, byte[] publicKeyBytes, byte[] privateKeyBytes) 104 throws GeneralSecurityException { 105 if (isNistKem(kemId)) { 106 ECParameterSpec spec = getNistCurveParams(kemId); 107 BigInteger order = spec.getOrder(); 108 BigInteger privateKey = BigIntegerEncoding.fromUnsignedBigEndianBytes(privateKeyBytes); 109 if ((privateKey.signum() <= 0) || (privateKey.compareTo(order) >= 0)) { 110 throw new GeneralSecurityException("Invalid private key."); 111 } 112 ECPoint expectedPoint = EllipticCurvesUtil.multiplyByGenerator(privateKey, spec); 113 ECPoint publicPoint = 114 EllipticCurves.pointDecode( 115 spec.getCurve(), EllipticCurves.PointFormatType.UNCOMPRESSED, publicKeyBytes); 116 if (!expectedPoint.equals(publicPoint)) { 117 throw new GeneralSecurityException("Invalid private key for public key."); 118 } 119 return; 120 } 121 if (kemId == HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) { 122 byte[] expectedPublicKeyBytes = X25519.publicFromPrivate(privateKeyBytes); 123 if (!Arrays.equals(expectedPublicKeyBytes, publicKeyBytes)) { 124 throw new GeneralSecurityException("Invalid private key for public key."); 125 } 126 return; 127 } 128 throw new IllegalArgumentException("Unable to validate key pair for " + kemId); 129 } 130 131 /** 132 * Creates a new HPKE private key. 133 * 134 * @param publicKey Corresponding HPKE public key for this private key 135 * @param privateKeyBytes Private key encoded according to 136 * https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1.2 137 */ 138 @AccessesPartialKey 139 @RestrictedApi( 140 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 141 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 142 allowedOnPath = ".*Test\\.java", 143 allowlistAnnotations = {AccessesPartialKey.class}) create(HpkePublicKey publicKey, SecretBytes privateKeyBytes)144 public static HpkePrivateKey create(HpkePublicKey publicKey, SecretBytes privateKeyBytes) 145 throws GeneralSecurityException { 146 if (publicKey == null) { 147 throw new GeneralSecurityException( 148 "HPKE private key cannot be constructed without an HPKE public key"); 149 } 150 if (privateKeyBytes == null) { 151 throw new GeneralSecurityException("HPKE private key cannot be constructed without secret"); 152 } 153 validatePrivateKeyByteLength(publicKey.getParameters().getKemId(), privateKeyBytes); 154 validateKeyPair( 155 publicKey.getParameters().getKemId(), 156 publicKey.getPublicKeyBytes().toByteArray(), 157 privateKeyBytes.toByteArray(InsecureSecretKeyAccess.get())); 158 return new HpkePrivateKey(publicKey, privateKeyBytes); 159 } 160 161 @RestrictedApi( 162 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 163 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 164 allowedOnPath = ".*Test\\.java", 165 allowlistAnnotations = {AccessesPartialKey.class}) getPrivateKeyBytes()166 public SecretBytes getPrivateKeyBytes() { 167 return privateKeyBytes; 168 } 169 170 @Override getParameters()171 public HpkeParameters getParameters() { 172 return publicKey.getParameters(); 173 } 174 175 @Override getPublicKey()176 public HpkePublicKey getPublicKey() { 177 return publicKey; 178 } 179 180 @Override equalsKey(Key o)181 public boolean equalsKey(Key o) { 182 if (!(o instanceof HpkePrivateKey)) { 183 return false; 184 } 185 HpkePrivateKey other = (HpkePrivateKey) o; 186 return publicKey.equalsKey(other.publicKey) 187 && privateKeyBytes.equalsSecretBytes(other.privateKeyBytes); 188 } 189 } 190