1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.gordianknot.impl.jca;
18
19 import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementKDF;
20 import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementSpec;
21 import io.github.tonywasher.joceanus.gordianknot.api.agree.GordianAgreementType;
22 import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
23 import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
24 import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
25 import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPair;
26 import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairSpec;
27 import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianKeyPairType;
28 import io.github.tonywasher.joceanus.gordianknot.api.keypair.GordianNTRUPrimeSpec;
29 import io.github.tonywasher.joceanus.gordianknot.impl.core.agree.GordianCoreAgreementEngine;
30 import io.github.tonywasher.joceanus.gordianknot.impl.core.agree.GordianCoreAgreementFactory;
31 import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianBaseData;
32 import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianCryptoException;
33 import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;
34 import io.github.tonywasher.joceanus.gordianknot.impl.core.keypair.GordianPrivateKey;
35 import io.github.tonywasher.joceanus.gordianknot.impl.core.keypair.GordianPublicKey;
36 import io.github.tonywasher.joceanus.gordianknot.impl.jca.JcaKeyPair.JcaPrivateKey;
37 import io.github.tonywasher.joceanus.gordianknot.impl.jca.JcaKeyPair.JcaPublicKey;
38 import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
39 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
40 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
41 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
42 import org.bouncycastle.jcajce.SecretKeyWithEncapsulation;
43 import org.bouncycastle.jcajce.spec.DHUParameterSpec;
44 import org.bouncycastle.jcajce.spec.KEMExtractSpec;
45 import org.bouncycastle.jcajce.spec.KEMGenerateSpec;
46 import org.bouncycastle.jcajce.spec.MQVParameterSpec;
47 import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
48
49 import javax.crypto.KeyAgreement;
50 import javax.crypto.KeyGenerator;
51 import java.security.InvalidAlgorithmParameterException;
52 import java.security.InvalidKeyException;
53 import java.security.NoSuchAlgorithmException;
54 import java.security.Provider;
55 import java.security.PublicKey;
56
57
58
59
60 public final class JcaAgreement {
61
62
63
64 private static final String ERR_AGREEMENT = "Failed Agreement";
65
66
67
68
69 private JcaAgreement() {
70 }
71
72
73
74
75 public static class JcaPostQuantumEngine
76 extends JcaAgreementBase {
77
78
79
80 private final KeyGenerator theGenerator;
81
82
83
84
85
86
87
88
89 JcaPostQuantumEngine(final GordianCoreAgreementFactory pFactory,
90 final GordianAgreementSpec pSpec,
91 final KeyGenerator pGenerator) throws GordianException {
92
93 super(pFactory, pSpec);
94
95
96 theGenerator = pGenerator;
97 }
98
99 @Override
100 public void buildClientHello() throws GordianException {
101
102 try {
103
104 final JcaPublicKey myPublic = (JcaPublicKey) getPublicKey(getServerKeyPair());
105 final KEMGenerateSpec mySpec = new KEMGenerateSpec.Builder(myPublic.getPublicKey(),
106 GordianSymKeyType.AES.toString(), GordianLength.LEN_256.getLength()).withKdfAlgorithm(derivationAlgorithmId()).build();
107 theGenerator.init(mySpec, getRandom());
108 final SecretKeyWithEncapsulation mySecret = (SecretKeyWithEncapsulation) theGenerator.generateKey();
109
110
111 setEncapsulated(mySecret.getEncapsulation());
112
113
114 storeSecret(mySecret.getEncoded());
115
116 } catch (InvalidAlgorithmParameterException e) {
117 throw new GordianCryptoException(ERR_AGREEMENT, e);
118 }
119 }
120
121 @Override
122 public void processClientHello() throws GordianException {
123
124 try {
125
126 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getServerKeyPair());
127 final KEMExtractSpec mySpec = new KEMExtractSpec.Builder(myPrivate.getPrivateKey(), getEncapsulated(),
128 GordianSymKeyType.AES.toString(), GordianLength.LEN_256.getLength()).withKdfAlgorithm(derivationAlgorithmId()).build();
129 theGenerator.init(mySpec);
130
131
132 final SecretKeyWithEncapsulation mySecret = (SecretKeyWithEncapsulation) theGenerator.generateKey();
133 storeSecret(mySecret.getEncoded());
134
135 } catch (InvalidAlgorithmParameterException e) {
136 throw new GordianCryptoException(ERR_AGREEMENT, e);
137 }
138 }
139 }
140
141
142
143
144 public static class JcaNewHopeEngine
145 extends JcaAgreementBase {
146
147
148
149 private final KeyAgreement theAgreement;
150
151
152
153
154
155
156
157
158 JcaNewHopeEngine(final GordianCoreAgreementFactory pFactory,
159 final GordianAgreementSpec pSpec,
160 final KeyAgreement pAgreement) throws GordianException {
161
162 super(pFactory, pSpec);
163
164
165 theAgreement = pAgreement;
166 }
167
168 @Override
169 public void buildClientHello() throws GordianException {
170
171 try {
172
173 theAgreement.init(null, getRandom());
174 final JcaPublicKey myTarget = (JcaPublicKey) getPublicKey(getServerKeyPair());
175 final PublicKey myKey = (PublicKey) theAgreement.doPhase(myTarget.getPublicKey(), true);
176
177
178 final GordianKeyPairSpec mySpec = getSpec().getKeyPairSpec();
179 final JcaPublicKey myPublic = new JcaPublicKey(mySpec, myKey);
180 final JcaKeyPair myEphemeral = new JcaKeyPair(myPublic);
181 setClientEphemeralAsEncapsulated(myEphemeral);
182
183
184 storeSecret(theAgreement.generateSecret());
185
186 } catch (InvalidKeyException e) {
187 throw new GordianCryptoException(ERR_AGREEMENT, e);
188 }
189 }
190
191 @Override
192 public void processClientHello() throws GordianException {
193
194 try {
195
196 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getServerKeyPair());
197 final JcaPublicKey myPublic = (JcaPublicKey) getPublicKey(getClientEphemeral());
198 theAgreement.init(myPrivate.getPrivateKey());
199 theAgreement.doPhase(myPublic.getPublicKey(), true);
200
201
202 storeSecret(theAgreement.generateSecret());
203
204 } catch (InvalidKeyException e) {
205 throw new GordianCryptoException(ERR_AGREEMENT, e);
206 }
207 }
208 }
209
210
211
212
213 public static class JcaAnonEngine
214 extends JcaAgreementBase {
215
216
217
218 private KeyAgreement theAgreement;
219
220
221
222
223
224
225
226
227 JcaAnonEngine(final GordianCoreAgreementFactory pFactory,
228 final GordianAgreementSpec pSpec,
229 final KeyAgreement pAgreement) throws GordianException {
230
231 super(pFactory, pSpec);
232
233
234 theAgreement = pAgreement;
235 }
236
237 @Override
238 public void buildClientHello() throws GordianException {
239
240 try {
241
242 final JcaPublicKey myPublic = (JcaPublicKey) getPublicKey(getServerKeyPair());
243 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getClientEphemeral());
244 theAgreement = adjustAgreement(theAgreement, getServerKeyPair());
245
246
247 initAgreement(theAgreement, myPrivate);
248 theAgreement.doPhase(myPublic.getPublicKey(), true);
249 storeSecret(theAgreement.generateSecret());
250
251 } catch (InvalidKeyException e) {
252 throw new GordianCryptoException(ERR_AGREEMENT, e);
253 }
254 }
255
256 @Override
257 public void processClientHello() throws GordianException {
258
259 try {
260
261 final JcaPublicKey myPublic = (JcaPublicKey) getPublicKey(getClientEphemeral());
262 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getServerKeyPair());
263 theAgreement = adjustAgreement(theAgreement, getServerKeyPair());
264
265
266 initAgreement(theAgreement, myPrivate);
267 theAgreement.doPhase(myPublic.getPublicKey(), true);
268 storeSecret(theAgreement.generateSecret());
269
270 } catch (InvalidKeyException e) {
271 throw new GordianCryptoException(ERR_AGREEMENT, e);
272 }
273 }
274 }
275
276
277
278
279 public static class JcaBasicEngine
280 extends JcaAgreementBase {
281
282
283
284 private KeyAgreement theAgreement;
285
286
287
288
289
290
291
292
293 JcaBasicEngine(final GordianCoreAgreementFactory pFactory,
294 final GordianAgreementSpec pSpec,
295 final KeyAgreement pAgreement) throws GordianException {
296
297 super(pFactory, pSpec);
298
299
300 theAgreement = pAgreement;
301 }
302
303 @Override
304 public void processClientHello() throws GordianException {
305
306 try {
307
308 final JcaPublicKey myPublic = (JcaPublicKey) getPublicKey(getClientKeyPair());
309 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getServerKeyPair());
310 theAgreement = adjustAgreement(theAgreement, getServerKeyPair());
311
312
313 initAgreement(theAgreement, myPrivate);
314 theAgreement.doPhase(myPublic.getPublicKey(), true);
315 storeSecret(theAgreement.generateSecret());
316
317 } catch (InvalidKeyException e) {
318 throw new GordianCryptoException(ERR_AGREEMENT, e);
319 }
320 }
321
322 @Override
323 public void processServerHello() throws GordianException {
324
325 try {
326
327 final JcaPublicKey myPublic = (JcaPublicKey) getPublicKey(getServerKeyPair());
328 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getClientKeyPair());
329 theAgreement = adjustAgreement(theAgreement, getServerKeyPair());
330
331
332 initAgreement(theAgreement, myPrivate);
333 theAgreement.doPhase(myPublic.getPublicKey(), true);
334 storeSecret(theAgreement.generateSecret());
335
336 } catch (InvalidKeyException e) {
337 throw new GordianCryptoException(ERR_AGREEMENT, e);
338 }
339 }
340 }
341
342
343
344
345 public static class JcaUnifiedEngine
346 extends JcaAgreementBase {
347
348
349
350 private KeyAgreement theAgreement;
351
352
353
354
355
356
357
358
359 JcaUnifiedEngine(final GordianCoreAgreementFactory pFactory,
360 final GordianAgreementSpec pSpec,
361 final KeyAgreement pAgreement) throws GordianException {
362
363 super(pFactory, pSpec);
364
365
366 theAgreement = pAgreement;
367 }
368
369 @Override
370 public void processClientHello() throws GordianException {
371
372 try {
373
374 final JcaPublicKey myClientPublic = (JcaPublicKey) getPublicKey(getClientKeyPair());
375 final JcaPublicKey myClientEphPublic = (JcaPublicKey) getPublicKey(getClientEphemeral());
376 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getServerKeyPair());
377 final JcaPublicKey myEphPublic = (JcaPublicKey) getPublicKey(getServerEphemeral());
378 final JcaPrivateKey myEphPrivate = (JcaPrivateKey) getPrivateKey(getServerEphemeral());
379 theAgreement = adjustAgreement(theAgreement, getServerKeyPair());
380
381
382 final DHUParameterSpec myParams = new DHUParameterSpec(myEphPublic.getPublicKey(),
383 myEphPrivate.getPrivateKey(), myClientEphPublic.getPublicKey(), getAdditional());
384 theAgreement.init(myPrivate.getPrivateKey(), myParams, getRandom());
385 theAgreement.doPhase(myClientPublic.getPublicKey(), true);
386 storeSecret(theAgreement.generateSecret());
387
388 } catch (InvalidKeyException
389 | InvalidAlgorithmParameterException e) {
390 throw new GordianCryptoException(ERR_AGREEMENT, e);
391 }
392 }
393
394 @Override
395 public void processServerHello() throws GordianException {
396
397 try {
398
399 final JcaPublicKey myServerPublic = (JcaPublicKey) getPublicKey(getServerKeyPair());
400 final JcaPublicKey myServerEphPublic = (JcaPublicKey) getPublicKey(getServerEphemeral());
401 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getClientKeyPair());
402 final JcaPublicKey myEphPublic = (JcaPublicKey) getPublicKey(getClientEphemeral());
403 final JcaPrivateKey myEphPrivate = (JcaPrivateKey) getPrivateKey(getClientEphemeral());
404 theAgreement = adjustAgreement(theAgreement, getServerKeyPair());
405
406
407 final DHUParameterSpec myParams = new DHUParameterSpec(myEphPublic.getPublicKey(),
408 myEphPrivate.getPrivateKey(), myServerEphPublic.getPublicKey(), getAdditional());
409 theAgreement.init(myPrivate.getPrivateKey(), myParams);
410 theAgreement.doPhase(myServerPublic.getPublicKey(), true);
411 storeSecret(theAgreement.generateSecret());
412
413 } catch (InvalidKeyException
414 | InvalidAlgorithmParameterException e) {
415 throw new GordianCryptoException(ERR_AGREEMENT, e);
416 }
417 }
418 }
419
420
421
422
423 public static class JcaMQVEngine
424 extends JcaAgreementBase {
425
426
427
428 private final KeyAgreement theAgreement;
429
430
431
432
433
434
435
436
437 JcaMQVEngine(final GordianCoreAgreementFactory pFactory,
438 final GordianAgreementSpec pSpec,
439 final KeyAgreement pAgreement) throws GordianException {
440
441 super(pFactory, pSpec);
442
443
444 theAgreement = pAgreement;
445 }
446
447 @Override
448 public void processClientHello() throws GordianException {
449
450 try {
451
452 final JcaPublicKey myClientPublic = (JcaPublicKey) getPublicKey(getClientKeyPair());
453 final JcaPublicKey myClientEphPublic = (JcaPublicKey) getPublicKey(getClientEphemeral());
454 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getServerKeyPair());
455 final JcaPublicKey myEphPublic = (JcaPublicKey) getPublicKey(getServerEphemeral());
456 final JcaPrivateKey myEphPrivate = (JcaPrivateKey) getPrivateKey(getServerEphemeral());
457
458
459 final MQVParameterSpec myParams = new MQVParameterSpec(myEphPublic.getPublicKey(),
460 myEphPrivate.getPrivateKey(), myClientEphPublic.getPublicKey(), getAdditional());
461 theAgreement.init(myPrivate.getPrivateKey(), myParams, getRandom());
462 theAgreement.doPhase(myClientPublic.getPublicKey(), true);
463 storeSecret(theAgreement.generateSecret());
464
465 } catch (InvalidKeyException
466 | InvalidAlgorithmParameterException e) {
467 throw new GordianCryptoException(ERR_AGREEMENT, e);
468 }
469 }
470
471 @Override
472 public void processServerHello() throws GordianException {
473
474 try {
475
476 final JcaPublicKey myServerPublic = (JcaPublicKey) getPublicKey(getServerKeyPair());
477 final JcaPublicKey myServerEphPublic = (JcaPublicKey) getPublicKey(getServerEphemeral());
478 final JcaPrivateKey myPrivate = (JcaPrivateKey) getPrivateKey(getClientKeyPair());
479 final JcaPublicKey myEphPublic = (JcaPublicKey) getPublicKey(getClientEphemeral());
480 final JcaPrivateKey myEphPrivate = (JcaPrivateKey) getPrivateKey(getClientEphemeral());
481
482
483 final MQVParameterSpec myParams = new MQVParameterSpec(myEphPublic.getPublicKey(),
484 myEphPrivate.getPrivateKey(), myServerEphPublic.getPublicKey(), getAdditional());
485 theAgreement.init(myPrivate.getPrivateKey(), myParams);
486 theAgreement.doPhase(myServerPublic.getPublicKey(), true);
487 storeSecret(theAgreement.generateSecret());
488
489 } catch (InvalidKeyException
490 | InvalidAlgorithmParameterException e) {
491 throw new GordianCryptoException(ERR_AGREEMENT, e);
492 }
493 }
494 }
495
496
497
498
499 public abstract static class JcaAgreementBase
500 extends GordianCoreAgreementEngine {
501
502
503
504 private static final byte[] EMPTY = new byte[0];
505
506
507
508
509
510
511
512
513 JcaAgreementBase(final GordianCoreAgreementFactory pFactory,
514 final GordianAgreementSpec pSpec) throws GordianException {
515
516 super(pFactory, pSpec);
517 }
518
519 @Override
520 protected GordianPublicKey getPublicKey(final GordianKeyPair pKeyPair) throws GordianException {
521
522 if (!(pKeyPair instanceof JcaKeyPair)) {
523
524 throw new GordianDataException("Invalid KeyPair");
525 }
526
527
528 return super.getPublicKey(pKeyPair);
529 }
530
531 @Override
532 protected GordianPrivateKey getPrivateKey(final GordianKeyPair pKeyPair) throws GordianException {
533
534 if (!(pKeyPair instanceof JcaKeyPair)) {
535
536 throw new GordianDataException("Invalid KeyPair");
537 }
538
539
540 return super.getPrivateKey(pKeyPair);
541 }
542
543
544
545
546
547
548
549 void initAgreement(final KeyAgreement pAgreement,
550 final JcaPrivateKey pPrivate) throws GordianException {
551
552 try {
553 if (getSpec().getKDFType() == GordianAgreementKDF.NONE) {
554 pAgreement.init(pPrivate.getPrivateKey(), getRandom());
555 } else {
556 pAgreement.init(pPrivate.getPrivateKey(), new UserKeyingMaterialSpec(getAdditional()), getRandom());
557 }
558 } catch (InvalidKeyException
559 | InvalidAlgorithmParameterException e) {
560 throw new GordianCryptoException(ERR_AGREEMENT, e);
561 }
562 }
563
564
565
566
567
568
569 byte[] getAdditional() {
570 final byte[] myAdditional = this.getBuilder().getState().getAdditionalData();
571 return myAdditional == null ? EMPTY : myAdditional;
572 }
573
574
575
576
577
578
579
580
581
582 KeyAgreement adjustAgreement(final KeyAgreement pCurrent,
583 final GordianKeyPair pKeyPair) throws GordianException {
584
585 if (getSpec().getKeyPairSpec().getKeyPairType().equals(GordianKeyPairType.XDH)) {
586 final String myBase = pKeyPair.getKeyPairSpec().toString();
587 final String myXtra = GordianAgreementType.UNIFIED.equals(getSpec().getAgreementType())
588 ? "U" : "";
589 final String myName = getFullAgreementName(myBase + myXtra, getSpec());
590 return getJavaKeyAgreement(myName, false);
591 }
592
593
594 return pCurrent;
595 }
596
597
598
599
600
601
602 AlgorithmIdentifier derivationAlgorithmId() {
603 final GordianAgreementSpec mySpec = getSpec();
604 switch (mySpec.getKDFType()) {
605 case SHA256KDF:
606 return new AlgorithmIdentifier(X9ObjectIdentifiers.id_kdf_kdf2, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256));
607 case SHA512KDF:
608 return new AlgorithmIdentifier(X9ObjectIdentifiers.id_kdf_kdf2, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512));
609 case SHA256CKDF:
610 return new AlgorithmIdentifier(X9ObjectIdentifiers.id_kdf_kdf3, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256));
611 case SHA512CKDF:
612 return new AlgorithmIdentifier(X9ObjectIdentifiers.id_kdf_kdf3, new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha512));
613 case SHA256HKDF:
614 return new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hkdf_with_sha256, null);
615 case SHA512HKDF:
616 return new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hkdf_with_sha512, null);
617 case KMAC128:
618 return new AlgorithmIdentifier(NISTObjectIdentifiers.id_Kmac128, null);
619 case KMAC256:
620 return new AlgorithmIdentifier(NISTObjectIdentifiers.id_Kmac256, null);
621 case SHAKE256:
622 return new AlgorithmIdentifier(NISTObjectIdentifiers.id_shake256, null);
623 case NONE:
624 default:
625 return null;
626 }
627 }
628 }
629
630
631
632
633
634
635
636
637 static KeyGenerator getJavaKeyGenerator(final GordianKeyPairSpec pSpec) throws GordianException {
638
639 try {
640
641 String myName = pSpec.getKeyPairType().toString();
642 switch (pSpec.getKeyPairType()) {
643 case NTRUPRIME:
644 final GordianNTRUPrimeSpec myNTRUSpec = pSpec.getNTRUPrimeKeySpec();
645 myName = myNTRUSpec.getType() + "PRIME";
646 break;
647 case MLKEM:
648 myName = "ML-KEM";
649 break;
650 default:
651 break;
652 }
653
654
655 final Provider myProvider = pSpec.getKeyPairType().isStandardJca() ? JcaProvider.BCPROV : JcaProvider.BCPQPROV;
656
657
658 return KeyGenerator.getInstance(myName, myProvider);
659
660
661 } catch (NoSuchAlgorithmException e) {
662
663 throw new GordianCryptoException("Failed to create KeyGenerator", e);
664 }
665 }
666
667
668
669
670
671
672
673
674
675 static KeyAgreement getJavaKeyAgreement(final String pAlgorithm,
676 final boolean postQuantum) throws GordianException {
677
678 try {
679
680 return KeyAgreement.getInstance(pAlgorithm, postQuantum
681 ? JcaProvider.BCPQPROV
682 : JcaProvider.BCPROV);
683
684
685 } catch (NoSuchAlgorithmException e) {
686
687 throw new GordianCryptoException("Failed to create KeyAgreement", e);
688 }
689 }
690
691
692
693
694
695
696
697
698
699 static String getFullAgreementName(final String pBase,
700 final GordianAgreementSpec pAgreementSpec) throws GordianException {
701 switch (pAgreementSpec.getKDFType()) {
702 case NONE:
703 return pBase;
704 case SHA256KDF:
705 return pBase + "withSHA256KDF";
706 case SHA512KDF:
707 return pBase + "withSHA512KDF";
708 case SHA256CKDF:
709 return pBase + "withSHA256CKDF";
710 case SHA512CKDF:
711 return pBase + "withSHA512CKDF";
712 case SHA256HKDF:
713 return pBase + "withSHA256HKDF";
714 case SHA512HKDF:
715 return pBase + "withSHA512HKDF";
716 default:
717 throw new GordianDataException(GordianBaseData.getInvalidText(pAgreementSpec));
718 }
719 }
720 }