• 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 static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.assertThrows;
21 
22 import com.google.crypto.tink.internal.BigIntegerEncoding;
23 import com.google.crypto.tink.subtle.EllipticCurves;
24 import com.google.crypto.tink.subtle.EllipticCurves.PointFormatType;
25 import com.google.crypto.tink.subtle.X25519;
26 import com.google.crypto.tink.util.Bytes;
27 import java.math.BigInteger;
28 import java.security.GeneralSecurityException;
29 import java.security.interfaces.ECPublicKey;
30 import java.security.spec.ECPoint;
31 import java.security.spec.EllipticCurve;
32 import org.junit.Test;
33 import org.junit.experimental.theories.DataPoints;
34 import org.junit.experimental.theories.FromDataPoints;
35 import org.junit.experimental.theories.Theories;
36 import org.junit.experimental.theories.Theory;
37 import org.junit.runner.RunWith;
38 
39 @RunWith(Theories.class)
40 public final class HpkePublicKeyTest {
41   private static final class NistKemTuple {
42     final HpkeParameters.KemId kemId;
43     final EllipticCurves.CurveType curve;
44 
NistKemTuple(HpkeParameters.KemId kemId, EllipticCurves.CurveType curve)45     NistKemTuple(HpkeParameters.KemId kemId, EllipticCurves.CurveType curve) {
46       this.kemId = kemId;
47       this.curve = curve;
48     }
49   }
50 
51   @DataPoints("nistKemTuples")
52   public static final NistKemTuple[] KEMS =
53       new NistKemTuple[] {
54         new NistKemTuple(
55             HpkeParameters.KemId.DHKEM_P256_HKDF_SHA256, EllipticCurves.CurveType.NIST_P256),
56         new NistKemTuple(
57             HpkeParameters.KemId.DHKEM_P384_HKDF_SHA384, EllipticCurves.CurveType.NIST_P384),
58         new NistKemTuple(
59             HpkeParameters.KemId.DHKEM_P521_HKDF_SHA512, EllipticCurves.CurveType.NIST_P521),
60       };
61 
62   @Theory
createNistCurvePublicKey(@romDataPoints"nistKemTuples") NistKemTuple tuple)63   public void createNistCurvePublicKey(@FromDataPoints("nistKemTuples") NistKemTuple tuple)
64       throws Exception {
65     HpkeParameters params =
66         HpkeParameters.builder()
67             .setVariant(HpkeParameters.Variant.NO_PREFIX)
68             .setKemId(tuple.kemId)
69             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
70             .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
71             .build();
72     ECPublicKey ecPublicKey = (ECPublicKey) EllipticCurves.generateKeyPair(tuple.curve).getPublic();
73     Bytes publicKeyBytes =
74         Bytes.copyFrom(
75             EllipticCurves.pointEncode(
76                 tuple.curve, PointFormatType.UNCOMPRESSED, ecPublicKey.getW()));
77 
78     HpkePublicKey publicKey =
79         HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null);
80 
81     assertThat(publicKey.getPublicKeyBytes()).isEqualTo(publicKeyBytes);
82     assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {}));
83     assertThat(publicKey.getParameters()).isEqualTo(params);
84     assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null);
85   }
86 
87   @Test
createX25519PublicKey()88   public void createX25519PublicKey() throws Exception {
89     HpkeParameters params =
90         HpkeParameters.builder()
91             .setVariant(HpkeParameters.Variant.NO_PREFIX)
92             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
93             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
94             .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
95             .build();
96     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
97 
98     HpkePublicKey publicKey =
99         HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null);
100 
101     assertThat(publicKey.getPublicKeyBytes()).isEqualTo(publicKeyBytes);
102     assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {}));
103     assertThat(publicKey.getParameters()).isEqualTo(params);
104     assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null);
105   }
106 
107   @Theory
createNistCurvePublicKey_failsWithWrongKeyLength( @romDataPoints"nistKemTuples") NistKemTuple tuple)108   public void createNistCurvePublicKey_failsWithWrongKeyLength(
109       @FromDataPoints("nistKemTuples") NistKemTuple tuple) throws Exception {
110     HpkeParameters params =
111         HpkeParameters.builder()
112             .setVariant(HpkeParameters.Variant.NO_PREFIX)
113             .setKemId(tuple.kemId)
114             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
115             .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
116             .build();
117     ECPublicKey ecPublicKey = (ECPublicKey) EllipticCurves.generateKeyPair(tuple.curve).getPublic();
118     Bytes publicKeyBytes =
119         Bytes.copyFrom(
120             EllipticCurves.pointEncode(
121                 tuple.curve, PointFormatType.UNCOMPRESSED, ecPublicKey.getW()));
122     Bytes tooShort = Bytes.copyFrom(publicKeyBytes.toByteArray(), 0, publicKeyBytes.size() - 1);
123     byte[] tooLongBytes = new byte[publicKeyBytes.size() + 1];
124     System.arraycopy(publicKeyBytes.toByteArray(), 0, tooLongBytes, 0, publicKeyBytes.size());
125     Bytes tooLong = Bytes.copyFrom(tooLongBytes);
126 
127     assertThrows(
128         GeneralSecurityException.class,
129         () -> HpkePublicKey.create(params, tooShort, /* idRequirement= */ null));
130 
131     assertThrows(
132         GeneralSecurityException.class,
133         () -> HpkePublicKey.create(params, tooLong, /* idRequirement= */ null));
134   }
135 
136   @Test
createX25519PublicKey_failsWithWrongKeyLength()137   public void createX25519PublicKey_failsWithWrongKeyLength() throws Exception {
138     HpkeParameters params =
139         HpkeParameters.builder()
140             .setVariant(HpkeParameters.Variant.NO_PREFIX)
141             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
142             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
143             .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
144             .build();
145     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
146     Bytes tooShort = Bytes.copyFrom(publicKeyBytes.toByteArray(), 0, publicKeyBytes.size() - 1);
147     byte[] tooLongBytes = new byte[publicKeyBytes.size() + 1];
148     System.arraycopy(publicKeyBytes.toByteArray(), 0, tooLongBytes, 0, publicKeyBytes.size());
149     Bytes tooLong = Bytes.copyFrom(tooLongBytes);
150 
151     assertThrows(
152         GeneralSecurityException.class,
153         () -> HpkePublicKey.create(params, tooShort, /* idRequirement= */ null));
154 
155     assertThrows(
156         GeneralSecurityException.class,
157         () -> HpkePublicKey.create(params, tooLong, /* idRequirement= */ null));
158   }
159 
160   /** Copied from {@link EllipticCurves#pointEncode} to bypass point validation. */
encodeUncompressedPoint(EllipticCurve curve, ECPoint point)161   private static byte[] encodeUncompressedPoint(EllipticCurve curve, ECPoint point)
162       throws GeneralSecurityException {
163     int coordinateSize = EllipticCurves.fieldSizeInBytes(curve);
164     byte[] encoded = new byte[2 * coordinateSize + 1];
165     byte[] x = BigIntegerEncoding.toBigEndianBytes(point.getAffineX());
166     byte[] y = BigIntegerEncoding.toBigEndianBytes(point.getAffineY());
167     // Order of System.arraycopy is important because x,y can have leading 0's.
168     System.arraycopy(y, 0, encoded, 1 + 2 * coordinateSize - y.length, y.length);
169     System.arraycopy(x, 0, encoded, 1 + coordinateSize - x.length, x.length);
170     encoded[0] = 4;
171     return encoded;
172   }
173 
174   @Theory
createNistCurvePublicKey_failsIfPointNotOnCurve( @romDataPoints"nistKemTuples") NistKemTuple tuple)175   public void createNistCurvePublicKey_failsIfPointNotOnCurve(
176       @FromDataPoints("nistKemTuples") NistKemTuple tuple) throws Exception {
177     HpkeParameters params =
178         HpkeParameters.builder()
179             .setVariant(HpkeParameters.Variant.NO_PREFIX)
180             .setKemId(tuple.kemId)
181             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
182             .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
183             .build();
184     ECPublicKey ecPublicKey = (ECPublicKey) EllipticCurves.generateKeyPair(tuple.curve).getPublic();
185     ECPoint point = ecPublicKey.getW();
186     ECPoint badPoint = new ECPoint(point.getAffineX(), point.getAffineY().subtract(BigInteger.ONE));
187 
188     Bytes publicKeyBytes =
189         Bytes.copyFrom(
190             encodeUncompressedPoint(EllipticCurves.getCurveSpec(tuple.curve).getCurve(), badPoint));
191 
192     assertThrows(
193         GeneralSecurityException.class,
194         () -> HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null));
195   }
196 
197   @Test
createPublicKey_failsWithMismatchedIdRequirement()198   public void createPublicKey_failsWithMismatchedIdRequirement() throws Exception {
199     HpkeParameters.Builder paramsBuilder =
200         HpkeParameters.builder()
201             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
202             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
203             .setAeadId(HpkeParameters.AeadId.AES_128_GCM);
204     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
205 
206     HpkeParameters noPrefixParams =
207         paramsBuilder.setVariant(HpkeParameters.Variant.NO_PREFIX).build();
208     assertThrows(
209         GeneralSecurityException.class,
210         () -> HpkePublicKey.create(noPrefixParams, publicKeyBytes, /* idRequirement= */ 123));
211 
212     HpkeParameters tinkParams = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build();
213     assertThrows(
214         GeneralSecurityException.class,
215         () -> HpkePublicKey.create(tinkParams, publicKeyBytes, /* idRequirement= */ null));
216 
217     HpkeParameters crunchyParams = paramsBuilder.setVariant(HpkeParameters.Variant.CRUNCHY).build();
218     assertThrows(
219         GeneralSecurityException.class,
220         () -> HpkePublicKey.create(crunchyParams, publicKeyBytes, /* idRequirement= */ null));
221   }
222 
223   @Test
getOutputPrefix()224   public void getOutputPrefix() throws Exception {
225     HpkeParameters.Builder paramsBuilder =
226         HpkeParameters.builder()
227             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
228             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
229             .setAeadId(HpkeParameters.AeadId.AES_128_GCM);
230     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
231 
232     HpkeParameters noPrefixParams =
233         paramsBuilder.setVariant(HpkeParameters.Variant.NO_PREFIX).build();
234     HpkePublicKey noPrefixPublicKey =
235         HpkePublicKey.create(noPrefixParams, publicKeyBytes, /* idRequirement= */ null);
236     assertThat(noPrefixPublicKey.getIdRequirementOrNull()).isEqualTo(null);
237     assertThat(noPrefixPublicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {}));
238 
239     HpkeParameters tinkParams = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build();
240     HpkePublicKey tinkPublicKey =
241         HpkePublicKey.create(tinkParams, publicKeyBytes, /* idRequirement= */ 0x02030405);
242     assertThat(tinkPublicKey.getIdRequirementOrNull()).isEqualTo(0x02030405);
243     assertThat(tinkPublicKey.getOutputPrefix())
244         .isEqualTo(Bytes.copyFrom(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05}));
245 
246     HpkeParameters crunchyParams = paramsBuilder.setVariant(HpkeParameters.Variant.CRUNCHY).build();
247     HpkePublicKey crunchyPublicKey =
248         HpkePublicKey.create(crunchyParams, publicKeyBytes, /* idRequirement= */ 0x01020304);
249     assertThat(crunchyPublicKey.getIdRequirementOrNull()).isEqualTo(0x01020304);
250     assertThat(crunchyPublicKey.getOutputPrefix())
251         .isEqualTo(Bytes.copyFrom(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04}));
252   }
253 
254   @Test
sameKeysAreEqual()255   public void sameKeysAreEqual() throws Exception {
256     HpkeParameters params =
257         HpkeParameters.builder()
258             .setVariant(HpkeParameters.Variant.NO_PREFIX)
259             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
260             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
261             .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
262             .build();
263     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
264 
265     HpkePublicKey publicKey1 =
266         HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null);
267     HpkePublicKey publicKey2 =
268         HpkePublicKey.create(params, publicKeyBytes, /* idRequirement= */ null);
269 
270     assertThat(publicKey1.equalsKey(publicKey2)).isTrue();
271   }
272 
273   @Test
differentParamsAreNotEqual()274   public void differentParamsAreNotEqual() throws Exception {
275     HpkeParameters.Builder paramsBuilder =
276         HpkeParameters.builder()
277             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
278             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
279             .setAeadId(HpkeParameters.AeadId.AES_128_GCM);
280     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
281 
282     HpkeParameters params1 = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build();
283     HpkePublicKey publicKey1 =
284         HpkePublicKey.create(params1, publicKeyBytes, /* idRequirement= */ 123);
285     HpkeParameters params2 = paramsBuilder.setVariant(HpkeParameters.Variant.CRUNCHY).build();
286     HpkePublicKey publicKey2 =
287         HpkePublicKey.create(params2, publicKeyBytes, /* idRequirement= */ 123);
288 
289     assertThat(publicKey1.equalsKey(publicKey2)).isFalse();
290   }
291 
292   @Test
differentKeyBytesAreNotEqual()293   public void differentKeyBytesAreNotEqual() throws Exception {
294     HpkeParameters params =
295         HpkeParameters.builder()
296             .setVariant(HpkeParameters.Variant.NO_PREFIX)
297             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
298             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
299             .setAeadId(HpkeParameters.AeadId.AES_128_GCM)
300             .build();
301     Bytes publicKeyBytes1 = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
302     byte[] buf2 = publicKeyBytes1.toByteArray();
303     buf2[0] = (byte) (buf2[0] ^ 0x01);
304     Bytes publicKeyBytes2 = Bytes.copyFrom(buf2);
305 
306     HpkePublicKey publicKey1 =
307         HpkePublicKey.create(params, publicKeyBytes1, /* idRequirement= */ null);
308     HpkePublicKey publicKey2 =
309         HpkePublicKey.create(params, publicKeyBytes2, /* idRequirement= */ null);
310 
311     assertThat(publicKey1.equalsKey(publicKey2)).isFalse();
312   }
313 
314   @Test
differentIdsAreNotEqual()315   public void differentIdsAreNotEqual() throws Exception {
316     HpkeParameters.Builder paramsBuilder =
317         HpkeParameters.builder()
318             .setKemId(HpkeParameters.KemId.DHKEM_X25519_HKDF_SHA256)
319             .setKdfId(HpkeParameters.KdfId.HKDF_SHA256)
320             .setAeadId(HpkeParameters.AeadId.AES_128_GCM);
321     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
322 
323     HpkeParameters params1 = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build();
324     HpkePublicKey publicKey1 =
325         HpkePublicKey.create(params1, publicKeyBytes, /* idRequirement= */ 123);
326     HpkeParameters params2 = paramsBuilder.setVariant(HpkeParameters.Variant.TINK).build();
327     HpkePublicKey publicKey2 =
328         HpkePublicKey.create(params2, publicKeyBytes, /* idRequirement= */ 456);
329 
330     assertThat(publicKey1.equalsKey(publicKey2)).isFalse();
331   }
332 }
333