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.Key; 21 import com.google.crypto.tink.internal.EllipticCurvesUtil; 22 import com.google.crypto.tink.internal.OutputPrefixUtil; 23 import com.google.crypto.tink.subtle.EllipticCurves; 24 import com.google.crypto.tink.util.Bytes; 25 import com.google.errorprone.annotations.Immutable; 26 import com.google.errorprone.annotations.RestrictedApi; 27 import java.security.GeneralSecurityException; 28 import java.security.spec.ECPoint; 29 import java.security.spec.EllipticCurve; 30 import java.util.Objects; 31 import javax.annotation.Nullable; 32 33 /** Representation of the encryption function for an HPKE hybrid encryption primitive. */ 34 @Immutable 35 public final class HpkePublicKey extends HybridPublicKey { 36 private final HpkeParameters parameters; 37 private final Bytes publicKeyBytes; 38 private final Bytes outputPrefix; 39 @Nullable private final Integer idRequirement; 40 HpkePublicKey( HpkeParameters parameters, Bytes publicKeyBytes, Bytes outputPrefix, @Nullable Integer idRequirement)41 private HpkePublicKey( 42 HpkeParameters parameters, 43 Bytes publicKeyBytes, 44 Bytes outputPrefix, 45 @Nullable Integer idRequirement) { 46 this.parameters = parameters; 47 this.publicKeyBytes = publicKeyBytes; 48 this.outputPrefix = outputPrefix; 49 this.idRequirement = idRequirement; 50 } 51 validateIdRequirement( HpkeParameters.Variant variant, @Nullable Integer idRequirement)52 private static void validateIdRequirement( 53 HpkeParameters.Variant variant, @Nullable Integer idRequirement) 54 throws GeneralSecurityException { 55 if (!variant.equals(HpkeParameters.Variant.NO_PREFIX) && idRequirement == null) { 56 throw new GeneralSecurityException( 57 "'idRequirement' must be non-null for " + variant + " variant."); 58 } 59 if (variant.equals(HpkeParameters.Variant.NO_PREFIX) && idRequirement != null) { 60 throw new GeneralSecurityException("'idRequirement' must be null for NO_PREFIX variant."); 61 } 62 } 63 validatePublicKeyByteLength(HpkeParameters.KemId kemId, Bytes publicKeyBytes)64 private static void validatePublicKeyByteLength(HpkeParameters.KemId kemId, Bytes publicKeyBytes) 65 throws GeneralSecurityException { 66 // Key lengths from 'Npk' column in https://www.rfc-editor.org/rfc/rfc9180.html#table-2. 67 int keyLengthInBytes = publicKeyBytes.size(); 68 String parameterizedErrorMessage = 69 "Encoded public key byte length for " + kemId + " must be %d, not " + keyLengthInBytes; 70 if (kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) { 71 if (keyLengthInBytes != 65) { 72 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 65)); 73 } 74 return; 75 } 76 if (kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384) { 77 if (keyLengthInBytes != 97) { 78 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 97)); 79 } 80 return; 81 } 82 if (kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512) { 83 if (keyLengthInBytes != 133) { 84 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 133)); 85 } 86 return; 87 } 88 if (kemId == HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256) { 89 if (keyLengthInBytes != 32) { 90 throw new GeneralSecurityException(String.format(parameterizedErrorMessage, 32)); 91 } 92 return; 93 } 94 throw new GeneralSecurityException("Unable to validate public key length for " + kemId); 95 } 96 isNistKem(HpkeParameters.KemId kemId)97 private static boolean isNistKem(HpkeParameters.KemId kemId) { 98 return kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256 99 || kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384 100 || kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512; 101 } 102 getNistCurve(HpkeParameters.KemId kemId)103 private static EllipticCurve getNistCurve(HpkeParameters.KemId kemId) { 104 if (kemId == HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256) { 105 return EllipticCurves.getNistP256Params().getCurve(); 106 } 107 if (kemId == HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384) { 108 return EllipticCurves.getNistP384Params().getCurve(); 109 } 110 if (kemId == HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512) { 111 return EllipticCurves.getNistP521Params().getCurve(); 112 } 113 throw new IllegalArgumentException("Unable to determine NIST curve type for " + kemId); 114 } 115 validatePublicKeyOnCurve(HpkeParameters.KemId kemId, Bytes publicKeyBytes)116 private static void validatePublicKeyOnCurve(HpkeParameters.KemId kemId, Bytes publicKeyBytes) 117 throws GeneralSecurityException { 118 if (!isNistKem(kemId)) { 119 return; 120 } 121 EllipticCurve curve = getNistCurve(kemId); 122 ECPoint point = 123 EllipticCurves.pointDecode( 124 curve, EllipticCurves.PointFormatType.UNCOMPRESSED, publicKeyBytes.toByteArray()); 125 EllipticCurvesUtil.checkPointOnCurve(point, curve); 126 } 127 128 /** 129 * Validate public key according to https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1.4. 130 * 131 * <p>Specifically, validate public key lengths and NIST KEM public key values according to 132 * Section 5.6.2.3.4 of https://doi.org/10.6028/nist.sp.800-56ar3. 133 */ validatePublicKey(HpkeParameters.KemId kemId, Bytes publicKeyBytes)134 private static void validatePublicKey(HpkeParameters.KemId kemId, Bytes publicKeyBytes) 135 throws GeneralSecurityException { 136 validatePublicKeyByteLength(kemId, publicKeyBytes); 137 validatePublicKeyOnCurve(kemId, publicKeyBytes); 138 } 139 createOutputPrefix( HpkeParameters.Variant variant, @Nullable Integer idRequirement)140 private static Bytes createOutputPrefix( 141 HpkeParameters.Variant variant, @Nullable Integer idRequirement) { 142 if (variant == HpkeParameters.Variant.NO_PREFIX) { 143 return OutputPrefixUtil.EMPTY_PREFIX; 144 } 145 if (idRequirement == null) { 146 throw new IllegalStateException( 147 "idRequirement must be non-null for HpkeParameters.Variant " + variant); 148 } 149 if (variant == HpkeParameters.Variant.CRUNCHY) { 150 return OutputPrefixUtil.getLegacyOutputPrefix(idRequirement); 151 } 152 if (variant == HpkeParameters.Variant.TINK) { 153 return OutputPrefixUtil.getTinkOutputPrefix(idRequirement); 154 } 155 throw new IllegalStateException("Unknown HpkeParameters.Variant: " + variant); 156 } 157 158 /** 159 * Creates a new HPKE public key. 160 * 161 * @param parameters HPKE parameters for the public key 162 * @param publicKeyBytes Public key encoded according to 163 * https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1.1 164 * @param idRequirement Key id requirement, which must be null for {@code NO_PREFIX} variant and 165 * non-null for all other variants 166 */ 167 @RestrictedApi( 168 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 169 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 170 allowedOnPath = ".*Test\\.java", 171 allowlistAnnotations = {AccessesPartialKey.class}) create( HpkeParameters parameters, Bytes publicKeyBytes, @Nullable Integer idRequirement)172 public static HpkePublicKey create( 173 HpkeParameters parameters, Bytes publicKeyBytes, @Nullable Integer idRequirement) 174 throws GeneralSecurityException { 175 validateIdRequirement(parameters.getVariant(), idRequirement); 176 validatePublicKey(parameters.getKemId(), publicKeyBytes); 177 Bytes prefix = createOutputPrefix(parameters.getVariant(), idRequirement); 178 return new HpkePublicKey(parameters, publicKeyBytes, prefix, idRequirement); 179 } 180 181 @RestrictedApi( 182 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 183 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 184 allowedOnPath = ".*Test\\.java", 185 allowlistAnnotations = {AccessesPartialKey.class}) getPublicKeyBytes()186 public Bytes getPublicKeyBytes() { 187 return publicKeyBytes; 188 } 189 190 @Override getOutputPrefix()191 public Bytes getOutputPrefix() { 192 return outputPrefix; 193 } 194 195 @Override getParameters()196 public HpkeParameters getParameters() { 197 return parameters; 198 } 199 200 @Override 201 @Nullable getIdRequirementOrNull()202 public Integer getIdRequirementOrNull() { 203 return idRequirement; 204 } 205 206 @Override equalsKey(Key o)207 public boolean equalsKey(Key o) { 208 if (!(o instanceof HpkePublicKey)) { 209 return false; 210 } 211 HpkePublicKey other = (HpkePublicKey) o; 212 // Since outputPrefix is a function of parameters, we can ignore it here. 213 return parameters.equals(other.parameters) 214 && publicKeyBytes.equals(other.publicKeyBytes) 215 && Objects.equals(idRequirement, other.idRequirement); 216 } 217 } 218