55
66import org .bouncycastle .crypto .AsymmetricCipherKeyPair ;
77import org .bouncycastle .crypto .AsymmetricCipherKeyPairGenerator ;
8- import org .bouncycastle .crypto .BasicAgreement ;
98import org .bouncycastle .crypto .CryptoServicesRegistrar ;
9+ import org .bouncycastle .crypto .RawAgreement ;
10+ import org .bouncycastle .crypto .agreement .BasicRawAgreement ;
1011import org .bouncycastle .crypto .agreement .ECDHCBasicAgreement ;
11- import org .bouncycastle .crypto .agreement .XDHBasicAgreement ;
12+ import org .bouncycastle .crypto .agreement .X25519Agreement ;
13+ import org .bouncycastle .crypto .agreement .X448Agreement ;
1214import org .bouncycastle .crypto .ec .CustomNamedCurves ;
1315import org .bouncycastle .crypto .generators .ECKeyPairGenerator ;
1416import org .bouncycastle .crypto .generators .X25519KeyPairGenerator ;
2729import org .bouncycastle .math .ec .ECPoint ;
2830import org .bouncycastle .math .ec .FixedPointCombMultiplier ;
2931import org .bouncycastle .math .ec .WNafUtil ;
32+ import org .bouncycastle .math .ec .rfc7748 .X25519 ;
33+ import org .bouncycastle .math .ec .rfc7748 .X448 ;
3034import org .bouncycastle .util .Arrays ;
3135import org .bouncycastle .util .BigIntegers ;
3236import org .bouncycastle .util .Pack ;
@@ -37,7 +41,7 @@ class DHKEM
3741{
3842 private AsymmetricCipherKeyPairGenerator kpGen ;
3943
40- private BasicAgreement agreement ;
44+ private RawAgreement rawAgreement ;
4145
4246 // kem ids
4347 private final short kemId ;
@@ -59,7 +63,7 @@ protected DHKEM(short kemid)
5963 case HPKE .kem_P256_SHA256 :
6064 this .hkdf = new HKDF (HPKE .kdf_HKDF_SHA256 );
6165 domainParams = getDomainParameters ("P-256" );
62- this . agreement = new ECDHCBasicAgreement ();
66+ rawAgreement = new BasicRawAgreement ( new ECDHCBasicAgreement () );
6367 bitmask = (byte )0xff ;
6468 Nsk = 32 ;
6569 Nsecret = 32 ;
@@ -72,7 +76,7 @@ protected DHKEM(short kemid)
7276 case HPKE .kem_P384_SHA348 :
7377 this .hkdf = new HKDF (HPKE .kdf_HKDF_SHA384 );
7478 domainParams = getDomainParameters ("P-384" );
75- this . agreement = new ECDHCBasicAgreement ();
79+ rawAgreement = new BasicRawAgreement ( new ECDHCBasicAgreement () );
7680 bitmask = (byte )0xff ;
7781 Nsk = 48 ;
7882 Nsecret = 48 ;
@@ -85,7 +89,7 @@ protected DHKEM(short kemid)
8589 case HPKE .kem_P521_SHA512 :
8690 this .hkdf = new HKDF (HPKE .kdf_HKDF_SHA512 );
8791 domainParams = getDomainParameters ("P-521" );
88- this . agreement = new ECDHCBasicAgreement ();
92+ rawAgreement = new BasicRawAgreement ( new ECDHCBasicAgreement () );
8993 bitmask = 0x01 ;
9094 Nsk = 66 ;
9195 Nsecret = 64 ;
@@ -97,7 +101,7 @@ protected DHKEM(short kemid)
97101 break ;
98102 case HPKE .kem_X25519_SHA256 :
99103 this .hkdf = new HKDF (HPKE .kdf_HKDF_SHA256 );
100- this . agreement = new XDHBasicAgreement ();
104+ rawAgreement = new X25519Agreement ();
101105 Nsecret = 32 ;
102106 Nsk = 32 ;
103107 Nenc = 32 ;
@@ -108,7 +112,7 @@ protected DHKEM(short kemid)
108112 break ;
109113 case HPKE .kem_X448_SHA512 :
110114 this .hkdf = new HKDF (HPKE .kdf_HKDF_SHA512 );
111- this . agreement = new XDHBasicAgreement ();
115+ rawAgreement = new X448Agreement ();
112116 Nsecret = 64 ;
113117 Nsk = 56 ;
114118 Nenc = 56 ;
@@ -129,6 +133,10 @@ public byte[] SerializePublicKey(AsymmetricKeyParameter key)
129133 case HPKE .kem_P256_SHA256 :
130134 case HPKE .kem_P384_SHA348 :
131135 case HPKE .kem_P521_SHA512 :
136+ /*
137+ * RFC 9180 7.1.1. For P-256, P-384, and P-521, the SerializePublicKey() function of the KEM performs
138+ * the uncompressed Elliptic-Curve-Point-to-Octet-String conversion according to [SECG].
139+ */
132140 return ((ECPublicKeyParameters )key ).getQ ().getEncoded (false );
133141 case HPKE .kem_X448_SHA512 :
134142 return ((X448PublicKeyParameters )key ).getEncoded ();
@@ -146,30 +154,74 @@ public byte[] SerializePrivateKey(AsymmetricKeyParameter key)
146154 case HPKE .kem_P256_SHA256 :
147155 case HPKE .kem_P384_SHA348 :
148156 case HPKE .kem_P521_SHA512 :
157+ {
158+ /*
159+ * RFC 9180 7.1.2. For P-256, P-384, and P-521, the SerializePrivateKey() function of the KEM
160+ * performs the Field-Element-to-Octet-String conversion according to [SECG].
161+ */
149162 return BigIntegers .asUnsignedByteArray (Nsk , ((ECPrivateKeyParameters )key ).getD ());
163+ }
150164 case HPKE .kem_X448_SHA512 :
151- return ((X448PrivateKeyParameters )key ).getEncoded ();
165+ {
166+ /*
167+ * RFC 9180 7.1.2. For [..] X448 [..]. The SerializePrivateKey() function MUST clamp its output
168+ * [..].
169+ *
170+ * NOTE: Our X448 implementation clamps generated keys, but de-serialized keys are preserved as is
171+ * (clamping applied only during usage).
172+ */
173+ byte [] encoded = ((X448PrivateKeyParameters )key ).getEncoded ();
174+ X448 .clampPrivateKey (encoded );
175+ return encoded ;
176+ }
152177 case HPKE .kem_X25519_SHA256 :
153- return ((X25519PrivateKeyParameters )key ).getEncoded ();
178+ {
179+ /*
180+ * RFC 9180 7.1.2. For X25519 [..]. The SerializePrivateKey() function MUST clamp its output [..].
181+ *
182+ * NOTE: Our X25519 implementation clamps generated keys, but de-serialized keys are preserved as
183+ * is (clamping applied only during usage).
184+ */
185+ byte [] encoded = ((X25519PrivateKeyParameters )key ).getEncoded ();
186+ X25519 .clampPrivateKey (encoded );
187+ return encoded ;
188+ }
154189 default :
155190 throw new IllegalStateException ("invalid kem id" );
156191 }
157192 }
158193
159- public AsymmetricKeyParameter DeserializePublicKey (byte [] encoded )
194+ public AsymmetricKeyParameter DeserializePublicKey (byte [] pkEncoded )
160195 {
196+ if (pkEncoded == null )
197+ {
198+ throw new NullPointerException ("'pkEncoded' cannot be null" );
199+ }
200+ if (pkEncoded .length != Nenc )
201+ {
202+ throw new IllegalArgumentException ("'pkEncoded' has invalid length" );
203+ }
204+
161205 switch (kemId )
162206 {
163207 case HPKE .kem_P256_SHA256 :
164208 case HPKE .kem_P384_SHA348 :
165209 case HPKE .kem_P521_SHA512 :
166- // TODO Does the encoding have to be uncompressed? (i.e. encoded.length MUST be Nenc?)
167- ECPoint G = domainParams .getCurve ().decodePoint (encoded );
210+ /*
211+ * RFC 9180 7.1.1. For P-256, P-384, and P-521 [..]. DeserializePublicKey() performs the
212+ * uncompressed Octet-String-to-Elliptic-Curve-Point conversion.
213+ */
214+ if (pkEncoded [0 ] != 0x04 ) // "0x04" is the marker for an uncompressed encoding
215+ {
216+ throw new IllegalArgumentException ("'pkEncoded' has invalid format" );
217+ }
218+
219+ ECPoint G = domainParams .getCurve ().decodePoint (pkEncoded );
168220 return new ECPublicKeyParameters (G , domainParams );
169221 case HPKE .kem_X448_SHA512 :
170- return new X448PublicKeyParameters (encoded );
222+ return new X448PublicKeyParameters (pkEncoded );
171223 case HPKE .kem_X25519_SHA256 :
172- return new X25519PublicKeyParameters (encoded );
224+ return new X25519PublicKeyParameters (pkEncoded );
173225 default :
174226 throw new IllegalStateException ("invalid kem id" );
175227 }
@@ -198,6 +250,10 @@ public AsymmetricCipherKeyPair DeserializePrivateKey(byte[] skEncoded, byte[] pk
198250 case HPKE .kem_P256_SHA256 :
199251 case HPKE .kem_P384_SHA348 :
200252 case HPKE .kem_P521_SHA512 :
253+ /*
254+ * RFC 9180 7.1.2. For P-256, P-384, and P-521 [..]. DeserializePrivateKey() performs the Octet-
255+ * String-to-Field-Element conversion according to [SECG].
256+ */
201257 BigInteger d = new BigInteger (1 , skEncoded );
202258 ECPrivateKeyParameters ec = new ECPrivateKeyParameters (d , domainParams );
203259
@@ -317,7 +373,7 @@ protected byte[][] Encap(AsymmetricKeyParameter pkR, AsymmetricCipherKeyPair kpE
317373 byte [][] output = new byte [2 ][];
318374
319375 // DH
320- byte [] secret = calculateAgreement ( agreement , kpE .getPrivate (), pkR );
376+ byte [] secret = calculateRawAgreement ( rawAgreement , kpE .getPrivate (), pkR );
321377
322378 byte [] enc = SerializePublicKey (kpE .getPublic ());
323379 byte [] pkRm = SerializePublicKey (pkR );
@@ -335,7 +391,7 @@ protected byte[] Decap(byte[] enc, AsymmetricCipherKeyPair kpR)
335391 AsymmetricKeyParameter pkE = DeserializePublicKey (enc );
336392
337393 // DH
338- byte [] secret = calculateAgreement ( agreement , kpR .getPrivate (), pkE );
394+ byte [] secret = calculateRawAgreement ( rawAgreement , kpR .getPrivate (), pkE );
339395
340396 byte [] pkRm = SerializePublicKey (kpR .getPublic ());
341397 byte [] KEMContext = Arrays .concatenate (enc , pkRm );
@@ -350,12 +406,22 @@ protected byte[][] AuthEncap(AsymmetricKeyParameter pkR, AsymmetricCipherKeyPair
350406 AsymmetricCipherKeyPair kpE = kpGen .generateKeyPair (); // todo: can be replaced with deriveKeyPair(random)
351407
352408 // DH(skE, pkR)
353- byte [] secret1 = calculateAgreement (agreement , kpE .getPrivate (), pkR );
409+ rawAgreement .init (kpE .getPrivate ());
410+ int agreementSize = rawAgreement .getAgreementSize ();
411+
412+ byte [] secret = new byte [agreementSize * 2 ];
413+
414+ rawAgreement .calculateAgreement (pkR , secret , 0 );
354415
355416 // DH(skS, pkR)
356- byte [] secret2 = calculateAgreement (agreement , kpS .getPrivate (), pkR );
417+ rawAgreement .init (kpS .getPrivate ());
418+ if (agreementSize != rawAgreement .getAgreementSize ())
419+ {
420+ throw new IllegalStateException ();
421+ }
422+
423+ rawAgreement .calculateAgreement (pkR , secret , agreementSize );
357424
358- byte [] secret = Arrays .concatenate (secret1 , secret2 );
359425 byte [] enc = SerializePublicKey (kpE .getPublic ());
360426
361427 byte [] pkRm = SerializePublicKey (pkR );
@@ -373,13 +439,16 @@ protected byte[] AuthDecap(byte[] enc, AsymmetricCipherKeyPair kpR, AsymmetricKe
373439 {
374440 AsymmetricKeyParameter pkE = DeserializePublicKey (enc );
375441
442+ rawAgreement .init (kpR .getPrivate ());
443+
444+ int agreementSize = rawAgreement .getAgreementSize ();
445+ byte [] secret = new byte [agreementSize * 2 ];
446+
376447 // DH(skR, pkE)
377- byte [] secret1 = calculateAgreement (agreement , kpR . getPrivate (), pkE );
448+ rawAgreement . calculateAgreement (pkE , secret , 0 );
378449
379450 // DH(skR, pkS)
380- byte [] secret2 = calculateAgreement (agreement , kpR .getPrivate (), pkS );
381-
382- byte [] secret = Arrays .concatenate (secret1 , secret2 );
451+ rawAgreement .calculateAgreement (pkS , secret , agreementSize );
383452
384453 byte [] pkRm = SerializePublicKey (kpR .getPublic ());
385454 byte [] pkSm = SerializePublicKey (pkS );
@@ -397,12 +466,13 @@ private byte[] ExtractAndExpand(byte[] dh, byte[] kemContext)
397466 return hkdf .LabeledExpand (eae_prk , suiteID , "shared_secret" , kemContext , Nsecret );
398467 }
399468
400- private static byte [] calculateAgreement ( BasicAgreement agreement , AsymmetricKeyParameter privateKey ,
469+ private static byte [] calculateRawAgreement ( RawAgreement rawAgreement , AsymmetricKeyParameter privateKey ,
401470 AsymmetricKeyParameter publicKey )
402471 {
403- agreement .init (privateKey );
404- BigInteger z = agreement .calculateAgreement (publicKey );
405- return BigIntegers .asUnsignedByteArray (agreement .getFieldSize (), z );
472+ rawAgreement .init (privateKey );
473+ byte [] z = new byte [rawAgreement .getAgreementSize ()];
474+ rawAgreement .calculateAgreement (publicKey , z , 0 );
475+ return z ;
406476 }
407477
408478 private static ECDomainParameters getDomainParameters (String curveName )
0 commit comments