• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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