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 /** Represents the encryption function for an ECIES hybrid encryption primitive. */ 34 @Immutable 35 public final class EciesPublicKey extends HybridPublicKey { 36 private final EciesParameters parameters; 37 38 /** Exactly one of nistPublicPoint and x25519PublicPointBytes is non-null. */ 39 @SuppressWarnings("Immutable") // ECPoint is immutable 40 @Nullable 41 private final ECPoint nistPublicPoint; 42 43 /** Exactly one of nistPublicPoint and x25519PublicPointBytes is non-null. */ 44 @Nullable private final Bytes x25519PublicPointBytes; 45 46 private final Bytes outputPrefix; 47 @Nullable private final Integer idRequirement; 48 EciesPublicKey( EciesParameters parameters, @Nullable ECPoint nistPublicPoint, @Nullable Bytes x25519PublicPointBytes, Bytes outputPrefix, @Nullable Integer idRequirement)49 private EciesPublicKey( 50 EciesParameters parameters, 51 @Nullable ECPoint nistPublicPoint, 52 @Nullable Bytes x25519PublicPointBytes, 53 Bytes outputPrefix, 54 @Nullable Integer idRequirement) { 55 this.parameters = parameters; 56 this.nistPublicPoint = nistPublicPoint; 57 this.x25519PublicPointBytes = x25519PublicPointBytes; 58 this.outputPrefix = outputPrefix; 59 this.idRequirement = idRequirement; 60 } 61 validateIdRequirement( EciesParameters.Variant variant, @Nullable Integer idRequirement)62 private static void validateIdRequirement( 63 EciesParameters.Variant variant, @Nullable Integer idRequirement) 64 throws GeneralSecurityException { 65 if (!variant.equals(EciesParameters.Variant.NO_PREFIX) && idRequirement == null) { 66 throw new GeneralSecurityException( 67 "'idRequirement' must be non-null for " + variant + " variant."); 68 } 69 if (variant.equals(EciesParameters.Variant.NO_PREFIX) && idRequirement != null) { 70 throw new GeneralSecurityException("'idRequirement' must be null for NO_PREFIX variant."); 71 } 72 } 73 getParameterSpecNistCurve(EciesParameters.CurveType curveType)74 private static EllipticCurve getParameterSpecNistCurve(EciesParameters.CurveType curveType) { 75 if (curveType == EciesParameters.CurveType.NIST_P256) { 76 return EllipticCurves.getNistP256Params().getCurve(); 77 } 78 if (curveType == EciesParameters.CurveType.NIST_P384) { 79 return EllipticCurves.getNistP384Params().getCurve(); 80 } 81 if (curveType == EciesParameters.CurveType.NIST_P521) { 82 return EllipticCurves.getNistP521Params().getCurve(); 83 } 84 throw new IllegalArgumentException("Unable to determine NIST curve type for " + curveType); 85 } 86 createOutputPrefix( EciesParameters.Variant variant, @Nullable Integer idRequirement)87 private static Bytes createOutputPrefix( 88 EciesParameters.Variant variant, @Nullable Integer idRequirement) { 89 if (variant == EciesParameters.Variant.NO_PREFIX) { 90 return OutputPrefixUtil.EMPTY_PREFIX; 91 } 92 if (idRequirement == null) { 93 throw new IllegalStateException( 94 "idRequirement must be non-null for EciesParameters.Variant: " + variant); 95 } 96 if (variant == EciesParameters.Variant.CRUNCHY) { 97 return OutputPrefixUtil.getLegacyOutputPrefix(idRequirement); 98 } 99 if (variant == EciesParameters.Variant.TINK) { 100 return OutputPrefixUtil.getTinkOutputPrefix(idRequirement); 101 } 102 throw new IllegalStateException("Unknown EciesParameters.Variant: " + variant); 103 } 104 105 /** 106 * Creates a new ECIES public key using Curve25519. 107 * 108 * @param parameters ECIES parameters for the public key 109 * @param publicPointBytes public point coordinates in bytes. 110 * @param idRequirement key id requirement, which must be null for {@code NO_PREFIX} variant and 111 * non-null for all other variants 112 */ 113 @RestrictedApi( 114 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 115 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 116 allowedOnPath = ".*Test\\.java", 117 allowlistAnnotations = {AccessesPartialKey.class}) createForCurveX25519( EciesParameters parameters, Bytes publicPointBytes, @Nullable Integer idRequirement)118 public static EciesPublicKey createForCurveX25519( 119 EciesParameters parameters, Bytes publicPointBytes, @Nullable Integer idRequirement) 120 throws GeneralSecurityException { 121 if (!parameters.getCurveType().equals(EciesParameters.CurveType.X25519)) { 122 throw new GeneralSecurityException( 123 "createForCurveX25519 may only be called with parameters for curve X25519"); 124 } 125 validateIdRequirement(parameters.getVariant(), idRequirement); 126 if (publicPointBytes.size() != 32) { 127 throw new GeneralSecurityException( 128 "Encoded public point byte length for X25519 curve must be 32"); 129 } 130 131 Bytes prefix = createOutputPrefix(parameters.getVariant(), idRequirement); 132 133 return new EciesPublicKey(parameters, null, publicPointBytes, prefix, idRequirement); 134 } 135 136 /** 137 * Creates a new ECIES public key using a NIST Curve. 138 * 139 * @param parameters ECIES parameters for the public key 140 * @param publicPoint public point as a {@code ECPoint}. 141 * @param idRequirement key id requirement, which must be null for {@code NO_PREFIX} variant and 142 * non-null for all other variants 143 */ createForNistCurve( EciesParameters parameters, ECPoint publicPoint, @Nullable Integer idRequirement)144 public static EciesPublicKey createForNistCurve( 145 EciesParameters parameters, ECPoint publicPoint, @Nullable Integer idRequirement) 146 throws GeneralSecurityException { 147 if (parameters.getCurveType().equals(EciesParameters.CurveType.X25519)) { 148 throw new GeneralSecurityException( 149 "createForNistCurve may only be called with parameters for NIST curve"); 150 } 151 validateIdRequirement(parameters.getVariant(), idRequirement); 152 EllipticCurvesUtil.checkPointOnCurve( 153 publicPoint, getParameterSpecNistCurve(parameters.getCurveType())); 154 155 Bytes prefix = createOutputPrefix(parameters.getVariant(), idRequirement); 156 157 return new EciesPublicKey(parameters, publicPoint, null, prefix, idRequirement); 158 } 159 160 /** 161 * Returns the underlying public point if the curve is a NIST curve. 162 * 163 * <p>Returns null if the curve used for this key is not a NIST curve. 164 */ 165 @RestrictedApi( 166 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 167 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 168 allowedOnPath = ".*Test\\.java", 169 allowlistAnnotations = {AccessesPartialKey.class}) 170 @Nullable getNistCurvePoint()171 public ECPoint getNistCurvePoint() { 172 return nistPublicPoint; 173 } 174 175 /** 176 * Returns the underlying public point as EC Point in case the curve is a NIST curve. 177 * 178 * <p>Returns null for X25519 curves. 179 */ 180 @RestrictedApi( 181 explanation = "Accessing parts of keys can produce unexpected incompatibilities, annotate the function with @AccessesPartialKey", 182 link = "https://developers.google.com/tink/design/access_control#accessing_partial_keys", 183 allowedOnPath = ".*Test\\.java", 184 allowlistAnnotations = {AccessesPartialKey.class}) 185 @Nullable getX25519CurvePointBytes()186 public Bytes getX25519CurvePointBytes() { 187 return x25519PublicPointBytes; 188 } 189 190 @Override getOutputPrefix()191 public Bytes getOutputPrefix() { 192 return outputPrefix; 193 } 194 195 @Override getParameters()196 public EciesParameters 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 EciesPublicKey)) { 209 return false; 210 } 211 EciesPublicKey other = (EciesPublicKey) o; 212 // Since outputPrefix is a function of parameters, we can ignore it here. 213 return parameters.equals(other.parameters) 214 && Objects.equals(x25519PublicPointBytes, other.x25519PublicPointBytes) 215 && Objects.equals(nistPublicPoint, other.nistPublicPoint) 216 && Objects.equals(idRequirement, other.idRequirement); 217 } 218 } 219