• 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.aead.XChaCha20Poly1305Parameters;
23 import com.google.crypto.tink.subtle.EllipticCurves;
24 import com.google.crypto.tink.subtle.X25519;
25 import com.google.crypto.tink.util.Bytes;
26 import java.math.BigInteger;
27 import java.security.GeneralSecurityException;
28 import java.security.KeyFactory;
29 import java.security.KeyPair;
30 import java.security.interfaces.ECPublicKey;
31 import java.security.spec.ECPoint;
32 import java.security.spec.ECPublicKeySpec;
33 import org.junit.Test;
34 import org.junit.experimental.theories.DataPoints;
35 import org.junit.experimental.theories.FromDataPoints;
36 import org.junit.experimental.theories.Theories;
37 import org.junit.experimental.theories.Theory;
38 import org.junit.runner.RunWith;
39 
40 @RunWith(Theories.class)
41 public final class EciesPublicKeyTest {
42   private static final class NistCurveMapping {
43     final EciesParameters.CurveType curveType;
44     final EllipticCurves.CurveType ecNistCurve;
45 
NistCurveMapping(EciesParameters.CurveType curveType, EllipticCurves.CurveType ecNistCurve)46     NistCurveMapping(EciesParameters.CurveType curveType, EllipticCurves.CurveType ecNistCurve) {
47       this.curveType = curveType;
48       this.ecNistCurve = ecNistCurve;
49     }
50   }
51 
52   @DataPoints("nistCurvesMapping")
53   public static final NistCurveMapping[] NIST_CURVES =
54       new NistCurveMapping[] {
55         new NistCurveMapping(
56             EciesParameters.CurveType.NIST_P256, EllipticCurves.CurveType.NIST_P256),
57         new NistCurveMapping(
58             EciesParameters.CurveType.NIST_P384, EllipticCurves.CurveType.NIST_P384),
59         new NistCurveMapping(
60             EciesParameters.CurveType.NIST_P521, EllipticCurves.CurveType.NIST_P521)
61       };
62 
63   @DataPoints("pointFormats")
64   public static final EciesParameters.PointFormat[] POINT_FORMATS =
65       new EciesParameters.PointFormat[] {
66         EciesParameters.PointFormat.UNCOMPRESSED,
67         EciesParameters.PointFormat.COMPRESSED,
68         EciesParameters.PointFormat.LEGACY_UNCOMPRESSED,
69       };
70 
71   @Test
convertToAndFromJavaECPublicKey()72   public void convertToAndFromJavaECPublicKey() throws Exception {
73     // Create an elliptic curve key pair using Java's KeyPairGenerator and get the public key.
74     KeyPair keyPair = EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256);
75     ECPublicKey ecPublicKey = (ECPublicKey) keyPair.getPublic();
76 
77     // Before conversion, check that the spec of the ecPublicKey are what we expect.
78     assertThat(ecPublicKey.getParams().getCurve())
79         .isEqualTo(EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256).getCurve());
80 
81     // Create EciesParameters that match the curve type.
82     EciesParameters parameters =
83         EciesParameters.builder()
84             .setHashType(EciesParameters.HashType.SHA256)
85             .setCurveType(EciesParameters.CurveType.NIST_P256)
86             .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED)
87             .setVariant(EciesParameters.Variant.NO_PREFIX)
88             .setDemParameters(XChaCha20Poly1305Parameters.create())
89             .build();
90 
91     // Create EciesPublicKey using the bytes from the ecPublicKey.
92     EciesPublicKey publicKey =
93         EciesPublicKey.createForNistCurve(
94             parameters, ecPublicKey.getW(), /* idRequirement= */ null);
95 
96     // Convert EciesPublicKey back into a ECPublicKey.
97     KeyFactory keyFactory = KeyFactory.getInstance("EC");
98     ECPublicKey ecPublicKey2 =
99         (ECPublicKey)
100             keyFactory.generatePublic(
101                 new ECPublicKeySpec(
102                     publicKey.getNistCurvePoint(),
103                     EllipticCurves.getCurveSpec(EllipticCurves.CurveType.NIST_P256)));
104     assertThat(ecPublicKey2.getW()).isEqualTo(ecPublicKey.getW());
105     assertThat(ecPublicKey2.getParams().getCurve()).isEqualTo(ecPublicKey.getParams().getCurve());
106   }
107 
108   @Theory
createNistCurvePublicKey_hasCorrectParameters( @romDataPoints"nistCurvesMapping") NistCurveMapping nistCurveMapping, @FromDataPoints("pointFormats") EciesParameters.PointFormat pointFormat)109   public void createNistCurvePublicKey_hasCorrectParameters(
110       @FromDataPoints("nistCurvesMapping") NistCurveMapping nistCurveMapping,
111       @FromDataPoints("pointFormats") EciesParameters.PointFormat pointFormat)
112       throws Exception {
113     EciesParameters params =
114         EciesParameters.builder()
115             .setHashType(EciesParameters.HashType.SHA256)
116             .setCurveType(nistCurveMapping.curveType)
117             .setNistCurvePointFormat(pointFormat)
118             .setVariant(EciesParameters.Variant.NO_PREFIX)
119             .setDemParameters(XChaCha20Poly1305Parameters.create())
120             .build();
121     ECPublicKey ecPublicKey =
122         (ECPublicKey) EllipticCurves.generateKeyPair(nistCurveMapping.ecNistCurve).getPublic();
123 
124     EciesPublicKey publicKey =
125         EciesPublicKey.createForNistCurve(params, ecPublicKey.getW(), /* idRequirement= */ null);
126 
127     assertThat(publicKey.getX25519CurvePointBytes()).isEqualTo(null);
128     assertThat(publicKey.getNistCurvePoint()).isEqualTo(ecPublicKey.getW());
129     assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {}));
130     assertThat(publicKey.getParameters()).isEqualTo(params);
131     assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null);
132   }
133 
134   @Test
callCreateForNistCurveWithX25519Params_throws()135   public void callCreateForNistCurveWithX25519Params_throws() throws Exception {
136     EciesParameters parameters =
137         EciesParameters.builder()
138             .setHashType(EciesParameters.HashType.SHA256)
139             .setCurveType(EciesParameters.CurveType.X25519)
140             .setVariant(EciesParameters.Variant.NO_PREFIX)
141             .setDemParameters(XChaCha20Poly1305Parameters.create())
142             .build();
143     ECPublicKey ecPublicKey =
144         (ECPublicKey)
145             EllipticCurves.generateKeyPair(EllipticCurves.CurveType.NIST_P256).getPublic();
146 
147     assertThrows(
148         GeneralSecurityException.class,
149         () ->
150             EciesPublicKey.createForNistCurve(
151                 parameters, ecPublicKey.getW(), /* idRequirement= */ null));
152   }
153 
154   @Test
createX25519PublicKey_hasCorrectParameters()155   public void createX25519PublicKey_hasCorrectParameters() throws Exception {
156     EciesParameters params =
157         EciesParameters.builder()
158             .setHashType(EciesParameters.HashType.SHA256)
159             .setCurveType(EciesParameters.CurveType.X25519)
160             .setVariant(EciesParameters.Variant.NO_PREFIX)
161             .setDemParameters(XChaCha20Poly1305Parameters.create())
162             .build();
163     Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
164 
165     EciesPublicKey publicKey =
166         EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null);
167 
168     assertThat(publicKey.getX25519CurvePointBytes()).isEqualTo(publicPointBytes);
169     assertThat(publicKey.getNistCurvePoint()).isEqualTo(null);
170     assertThat(publicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {}));
171     assertThat(publicKey.getParameters()).isEqualTo(params);
172     assertThat(publicKey.getIdRequirementOrNull()).isEqualTo(null);
173   }
174 
175   @Test
callCreateForCurve25519WithNistParams_throws()176   public void callCreateForCurve25519WithNistParams_throws() throws Exception {
177     EciesParameters parameters =
178         EciesParameters.builder()
179             .setHashType(EciesParameters.HashType.SHA256)
180             .setCurveType(EciesParameters.CurveType.NIST_P256)
181             .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED)
182             .setVariant(EciesParameters.Variant.NO_PREFIX)
183             .setDemParameters(XChaCha20Poly1305Parameters.create())
184             .build();
185     Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
186 
187     assertThrows(
188         GeneralSecurityException.class,
189         () ->
190             EciesPublicKey.createForCurveX25519(
191                 parameters, publicPointBytes, /* idRequirement= */ null));
192   }
193 
194   @Test
createX25519PublicKey_withWrongKeyLength_fails()195   public void createX25519PublicKey_withWrongKeyLength_fails() throws Exception {
196     EciesParameters params =
197         EciesParameters.builder()
198             .setHashType(EciesParameters.HashType.SHA256)
199             .setCurveType(EciesParameters.CurveType.X25519)
200             .setVariant(EciesParameters.Variant.NO_PREFIX)
201             .setDemParameters(XChaCha20Poly1305Parameters.create())
202             .build();
203     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
204     Bytes tooShort = Bytes.copyFrom(publicKeyBytes.toByteArray(), 0, publicKeyBytes.size() - 1);
205     byte[] tooLongBytes = new byte[publicKeyBytes.size() + 1];
206     System.arraycopy(publicKeyBytes.toByteArray(), 0, tooLongBytes, 0, publicKeyBytes.size());
207     Bytes tooLong = Bytes.copyFrom(tooLongBytes);
208 
209     assertThrows(
210         GeneralSecurityException.class,
211         () -> EciesPublicKey.createForCurveX25519(params, tooShort, /* idRequirement= */ null));
212 
213     assertThrows(
214         GeneralSecurityException.class,
215         () -> EciesPublicKey.createForCurveX25519(params, tooLong, /* idRequirement= */ null));
216   }
217 
218   @Theory
createNistCurvePublicKey_withPointNotOnCurve_fails( @romDataPoints"nistCurvesMapping") NistCurveMapping nistCurveMapping)219   public void createNistCurvePublicKey_withPointNotOnCurve_fails(
220       @FromDataPoints("nistCurvesMapping") NistCurveMapping nistCurveMapping) throws Exception {
221     EciesParameters params =
222         EciesParameters.builder()
223             .setHashType(EciesParameters.HashType.SHA256)
224             .setCurveType(nistCurveMapping.curveType)
225             .setNistCurvePointFormat(EciesParameters.PointFormat.UNCOMPRESSED)
226             .setVariant(EciesParameters.Variant.NO_PREFIX)
227             .setDemParameters(XChaCha20Poly1305Parameters.create())
228             .build();
229     ECPublicKey ecPublicKey =
230         (ECPublicKey) EllipticCurves.generateKeyPair(nistCurveMapping.ecNistCurve).getPublic();
231     ECPoint point = ecPublicKey.getW();
232     ECPoint badPoint = new ECPoint(point.getAffineX(), point.getAffineY().subtract(BigInteger.ONE));
233 
234     assertThrows(
235         GeneralSecurityException.class,
236         () -> EciesPublicKey.createForNistCurve(params, badPoint, /* idRequirement= */ null));
237   }
238 
239   @Test
createPublicKeyWithMismatchedIdRequirement_fails()240   public void createPublicKeyWithMismatchedIdRequirement_fails() throws Exception {
241     EciesParameters.Builder paramsBuilder =
242         EciesParameters.builder()
243             .setHashType(EciesParameters.HashType.SHA256)
244             .setCurveType(EciesParameters.CurveType.X25519)
245             .setDemParameters(XChaCha20Poly1305Parameters.create());
246     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
247 
248     EciesParameters noPrefixParams =
249         paramsBuilder.setVariant(EciesParameters.Variant.NO_PREFIX).build();
250     assertThrows(
251         GeneralSecurityException.class,
252         () ->
253             EciesPublicKey.createForCurveX25519(
254                 noPrefixParams, publicKeyBytes, /* idRequirement= */ 123));
255 
256     EciesParameters tinkParams = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build();
257     assertThrows(
258         GeneralSecurityException.class,
259         () ->
260             EciesPublicKey.createForCurveX25519(
261                 tinkParams, publicKeyBytes, /* idRequirement= */ null));
262 
263     EciesParameters crunchyParams =
264         paramsBuilder.setVariant(EciesParameters.Variant.CRUNCHY).build();
265     assertThrows(
266         GeneralSecurityException.class,
267         () ->
268             EciesPublicKey.createForCurveX25519(
269                 crunchyParams, publicKeyBytes, /* idRequirement= */ null));
270   }
271 
272   @Test
getOutputPrefix_isCorrect()273   public void getOutputPrefix_isCorrect() throws Exception {
274     EciesParameters.Builder paramsBuilder =
275         EciesParameters.builder()
276             .setHashType(EciesParameters.HashType.SHA256)
277             .setCurveType(EciesParameters.CurveType.X25519)
278             .setDemParameters(XChaCha20Poly1305Parameters.create());
279     Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
280 
281     EciesParameters noPrefixParams =
282         paramsBuilder.setVariant(EciesParameters.Variant.NO_PREFIX).build();
283     EciesPublicKey noPrefixPublicKey =
284         EciesPublicKey.createForCurveX25519(
285             noPrefixParams, publicPointBytes, /* idRequirement= */ null);
286     assertThat(noPrefixPublicKey.getIdRequirementOrNull()).isEqualTo(null);
287     assertThat(noPrefixPublicKey.getOutputPrefix()).isEqualTo(Bytes.copyFrom(new byte[] {}));
288 
289     EciesParameters tinkParams = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build();
290     EciesPublicKey tinkPublicKey =
291         EciesPublicKey.createForCurveX25519(
292             tinkParams, publicPointBytes, /* idRequirement= */ 0x02030405);
293     assertThat(tinkPublicKey.getIdRequirementOrNull()).isEqualTo(0x02030405);
294     assertThat(tinkPublicKey.getOutputPrefix())
295         .isEqualTo(Bytes.copyFrom(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05}));
296 
297     EciesParameters crunchyParams =
298         paramsBuilder.setVariant(EciesParameters.Variant.CRUNCHY).build();
299     EciesPublicKey crunchyPublicKey =
300         EciesPublicKey.createForCurveX25519(
301             crunchyParams, publicPointBytes, /* idRequirement= */ 0x01020304);
302     assertThat(crunchyPublicKey.getIdRequirementOrNull()).isEqualTo(0x01020304);
303     assertThat(crunchyPublicKey.getOutputPrefix())
304         .isEqualTo(Bytes.copyFrom(new byte[] {0x00, 0x01, 0x02, 0x03, 0x04}));
305   }
306 
307   @Test
sameKeys_areEqual()308   public void sameKeys_areEqual() throws Exception {
309     EciesParameters params =
310         EciesParameters.builder()
311             .setHashType(EciesParameters.HashType.SHA256)
312             .setCurveType(EciesParameters.CurveType.X25519)
313             .setVariant(EciesParameters.Variant.NO_PREFIX)
314             .setDemParameters(XChaCha20Poly1305Parameters.create())
315             .build();
316     Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
317 
318     EciesPublicKey publicKey1 =
319         EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null);
320     EciesPublicKey publicKey2 =
321         EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null);
322 
323     assertThat(publicKey1.equalsKey(publicKey2)).isTrue();
324   }
325 
326   @Test
sameKeys_nist_areEqual()327   public void sameKeys_nist_areEqual() throws Exception {
328     EciesParameters params =
329         EciesParameters.builder()
330             .setHashType(EciesParameters.HashType.SHA256)
331             .setCurveType(EciesParameters.CurveType.X25519)
332             .setVariant(EciesParameters.Variant.NO_PREFIX)
333             .setDemParameters(XChaCha20Poly1305Parameters.create())
334             .build();
335     Bytes publicPointBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
336 
337     EciesPublicKey publicKey1 =
338         EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null);
339     EciesPublicKey publicKey2 =
340         EciesPublicKey.createForCurveX25519(params, publicPointBytes, /* idRequirement= */ null);
341 
342     assertThat(publicKey1.equalsKey(publicKey2)).isTrue();
343   }
344 
345   @Test
keysWithDifferentParams_areNotEqual()346   public void keysWithDifferentParams_areNotEqual() throws Exception {
347     EciesParameters.Builder paramsBuilder =
348         EciesParameters.builder()
349             .setHashType(EciesParameters.HashType.SHA256)
350             .setCurveType(EciesParameters.CurveType.X25519)
351             .setDemParameters(XChaCha20Poly1305Parameters.create());
352     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
353 
354     EciesParameters params1 = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build();
355     EciesPublicKey publicKey1 =
356         EciesPublicKey.createForCurveX25519(params1, publicKeyBytes, /* idRequirement= */ 123);
357     EciesParameters params2 = paramsBuilder.setVariant(EciesParameters.Variant.CRUNCHY).build();
358     EciesPublicKey publicKey2 =
359         EciesPublicKey.createForCurveX25519(params2, publicKeyBytes, /* idRequirement= */ 123);
360 
361     assertThat(publicKey1.equalsKey(publicKey2)).isFalse();
362   }
363 
364   @Test
keysWithDifferentKeyBytes_areNotEqual()365   public void keysWithDifferentKeyBytes_areNotEqual() throws Exception {
366     EciesParameters params =
367         EciesParameters.builder()
368             .setHashType(EciesParameters.HashType.SHA256)
369             .setCurveType(EciesParameters.CurveType.X25519)
370             .setVariant(EciesParameters.Variant.NO_PREFIX)
371             .setDemParameters(
372                 XChaCha20Poly1305Parameters.create(XChaCha20Poly1305Parameters.Variant.NO_PREFIX))
373             .build();
374     Bytes publicKeyBytes1 = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
375     byte[] buf2 = publicKeyBytes1.toByteArray();
376     buf2[0] = (byte) (buf2[0] ^ 0x01);
377     Bytes publicKeyBytes2 = Bytes.copyFrom(buf2);
378 
379     EciesPublicKey publicKey1 =
380         EciesPublicKey.createForCurveX25519(params, publicKeyBytes1, /* idRequirement= */ null);
381     EciesPublicKey publicKey2 =
382         EciesPublicKey.createForCurveX25519(params, publicKeyBytes2, /* idRequirement= */ null);
383 
384     assertThat(publicKey1.equalsKey(publicKey2)).isFalse();
385   }
386 
387   @Test
keysWithdifferentIds_areNotEqual()388   public void keysWithdifferentIds_areNotEqual() throws Exception {
389     EciesParameters.Builder paramsBuilder =
390         EciesParameters.builder()
391             .setHashType(EciesParameters.HashType.SHA256)
392             .setCurveType(EciesParameters.CurveType.X25519)
393             .setDemParameters(
394                 XChaCha20Poly1305Parameters.create(XChaCha20Poly1305Parameters.Variant.NO_PREFIX));
395     Bytes publicKeyBytes = Bytes.copyFrom(X25519.publicFromPrivate(X25519.generatePrivateKey()));
396 
397     EciesParameters params1 = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build();
398     EciesPublicKey publicKey1 =
399         EciesPublicKey.createForCurveX25519(params1, publicKeyBytes, /* idRequirement= */ 123);
400     EciesParameters params2 = paramsBuilder.setVariant(EciesParameters.Variant.TINK).build();
401     EciesPublicKey publicKey2 =
402         EciesPublicKey.createForCurveX25519(params2, publicKeyBytes, /* idRequirement= */ 456);
403 
404     assertThat(publicKey1.equalsKey(publicKey2)).isFalse();
405   }
406 }
407