Skip to content

Commit 623e997

Browse files
committed
update PQC key encoding/decoding in Java 4
1 parent 9f2c518 commit 623e997

File tree

5 files changed

+462
-138
lines changed

5 files changed

+462
-138
lines changed

core/src/main/jdk1.4/org/bouncycastle/pqc/crypto/util/PrivateKeyFactory.java

Lines changed: 117 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
4444
import org.bouncycastle.pqc.crypto.mlkem.MLKEMParameters;
4545
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
46+
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
4647
import org.bouncycastle.pqc.crypto.newhope.NHPrivateKeyParameters;
4748
import org.bouncycastle.pqc.crypto.ntru.NTRUParameters;
4849
import org.bouncycastle.pqc.crypto.ntru.NTRUPrivateKeyParameters;
@@ -147,22 +148,12 @@ else if (algOID.on(BCObjectIdentifiers.sphincsPlus) || algOID.on(BCObjectIdentif
147148
return new SPHINCSPlusPrivateKeyParameters(spParams, ASN1OctetString.getInstance(obj).getOctets());
148149
}
149150
}
150-
else if (Utils.shldsaParams.containsKey(algOID))
151+
else if (Utils.slhdsaParams.containsKey(algOID))
151152
{
152153
SLHDSAParameters spParams = Utils.slhdsaParamsLookup(algOID);
154+
ASN1OctetString slhdsaKey = parseOctetString(keyInfo.getPrivateKey(), spParams.getN() * 4);
153155

154-
ASN1Encodable obj = keyInfo.parsePrivateKey();
155-
if (obj instanceof ASN1Sequence)
156-
{
157-
SPHINCSPLUSPrivateKey spKey = SPHINCSPLUSPrivateKey.getInstance(obj);
158-
SPHINCSPLUSPublicKey publicKey = spKey.getPublicKey();
159-
return new SLHDSAPrivateKeyParameters(spParams, spKey.getSkseed(), spKey.getSkprf(),
160-
publicKey.getPkseed(), publicKey.getPkroot());
161-
}
162-
else
163-
{
164-
return new SLHDSAPrivateKeyParameters(spParams, ASN1OctetString.getInstance(obj).getOctets());
165-
}
156+
return new SLHDSAPrivateKeyParameters(spParams, slhdsaKey.getOctets());
166157
}
167158
else if (algOID.on(BCObjectIdentifiers.picnic))
168159
{
@@ -203,10 +194,37 @@ else if (algOID.equals(NISTObjectIdentifiers.id_alg_ml_kem_512) ||
203194
algOID.equals(NISTObjectIdentifiers.id_alg_ml_kem_768) ||
204195
algOID.equals(NISTObjectIdentifiers.id_alg_ml_kem_1024))
205196
{
206-
ASN1OctetString kyberKey = ASN1OctetString.getInstance(keyInfo.parsePrivateKey());
207-
MLKEMParameters kyberParams = Utils.mlkemParamsLookup(algOID);
197+
ASN1Primitive mlkemKey = parsePrimitiveString(keyInfo.getPrivateKey(), 64);
198+
MLKEMParameters mlkemParams = Utils.mlkemParamsLookup(algOID);
208199

209-
return new MLKEMPrivateKeyParameters(kyberParams, kyberKey.getOctets());
200+
MLKEMPublicKeyParameters pubParams = null;
201+
if (keyInfo.getPublicKeyData() != null)
202+
{
203+
pubParams = PublicKeyFactory.MLKEMConverter.getPublicKeyParams(mlkemParams, keyInfo.getPublicKeyData());
204+
}
205+
206+
if (mlkemKey instanceof ASN1OctetString)
207+
{
208+
// TODO This should be explicitly EXPANDED_KEY or SEED (tag already removed) but is length-flexible
209+
return new MLKEMPrivateKeyParameters(mlkemParams, ((ASN1OctetString)mlkemKey).getOctets(), pubParams);
210+
}
211+
else if (mlkemKey instanceof ASN1Sequence)
212+
{
213+
ASN1Sequence keySeq = (ASN1Sequence)mlkemKey;
214+
byte[] seed = ASN1OctetString.getInstance(keySeq.getObjectAt(0)).getOctets();
215+
byte[] encoding = ASN1OctetString.getInstance(keySeq.getObjectAt(1)).getOctets();
216+
217+
// TODO This should only allow seed but is length-flexible
218+
MLKEMPrivateKeyParameters mlkemPriv = new MLKEMPrivateKeyParameters(mlkemParams, seed, pubParams);
219+
if (!Arrays.constantTimeAreEqual(mlkemPriv.getEncoded(), encoding))
220+
{
221+
throw new IllegalArgumentException("inconsistent " + mlkemParams.getName() + " private key");
222+
}
223+
224+
return mlkemPriv;
225+
}
226+
227+
throw new IllegalArgumentException("invalid " + mlkemParams.getName() + " private key");
210228
}
211229
else if (algOID.on(BCObjectIdentifiers.pqc_kem_ntrulprime))
212230
{
@@ -235,58 +253,37 @@ else if (algOID.on(BCObjectIdentifiers.pqc_kem_sntruprime))
235253
}
236254
else if (Utils.mldsaParams.containsKey(algOID))
237255
{
238-
ASN1Encodable keyObj = keyInfo.parsePrivateKey();
239-
MLDSAParameters spParams = Utils.mldsaParamsLookup(algOID);
256+
ASN1Encodable mldsaKey = parsePrimitiveString(keyInfo.getPrivateKey(), 32);
257+
MLDSAParameters mldsaParams = Utils.mldsaParamsLookup(algOID);
240258

241-
if (keyObj instanceof ASN1Sequence)
259+
MLDSAPublicKeyParameters pubParams = null;
260+
if (keyInfo.getPublicKeyData() != null)
242261
{
243-
ASN1Sequence keyEnc = ASN1Sequence.getInstance(keyObj);
244-
245-
int version = ASN1Integer.getInstance(keyEnc.getObjectAt(0)).intValueExact();
246-
if (version != 0)
247-
{
248-
throw new IOException("unknown private key version: " + version);
249-
}
250-
251-
if (keyInfo.getPublicKeyData() != null)
252-
{
253-
MLDSAPublicKeyParameters pubParams = PublicKeyFactory.MLDSAConverter.getPublicKeyParams(spParams, keyInfo.getPublicKeyData());
262+
pubParams = PublicKeyFactory.MLDSAConverter.getPublicKeyParams(mldsaParams, keyInfo.getPublicKeyData());
263+
}
254264

255-
return new MLDSAPrivateKeyParameters(spParams,
256-
ASN1BitString.getInstance(keyEnc.getObjectAt(1)).getOctets(),
257-
ASN1BitString.getInstance(keyEnc.getObjectAt(2)).getOctets(),
258-
ASN1BitString.getInstance(keyEnc.getObjectAt(3)).getOctets(),
259-
ASN1BitString.getInstance(keyEnc.getObjectAt(4)).getOctets(),
260-
ASN1BitString.getInstance(keyEnc.getObjectAt(5)).getOctets(),
261-
ASN1BitString.getInstance(keyEnc.getObjectAt(6)).getOctets(),
262-
pubParams.getT1()); // encT1
263-
}
264-
else
265-
{
266-
return new MLDSAPrivateKeyParameters(spParams,
267-
ASN1BitString.getInstance(keyEnc.getObjectAt(1)).getOctets(),
268-
ASN1BitString.getInstance(keyEnc.getObjectAt(2)).getOctets(),
269-
ASN1BitString.getInstance(keyEnc.getObjectAt(3)).getOctets(),
270-
ASN1BitString.getInstance(keyEnc.getObjectAt(4)).getOctets(),
271-
ASN1BitString.getInstance(keyEnc.getObjectAt(5)).getOctets(),
272-
ASN1BitString.getInstance(keyEnc.getObjectAt(6)).getOctets(),
273-
null);
274-
}
265+
if (mldsaKey instanceof ASN1OctetString)
266+
{
267+
// TODO This should be explicitly EXPANDED_KEY or SEED (tag already removed) but is length-flexible
268+
return new MLDSAPrivateKeyParameters(mldsaParams, ((ASN1OctetString)mldsaKey).getOctets(), pubParams);
275269
}
276-
else if (keyObj instanceof DEROctetString)
270+
else if (mldsaKey instanceof ASN1Sequence)
277271
{
278-
byte[] data = ASN1OctetString.getInstance(keyObj).getOctets();
279-
if (keyInfo.getPublicKeyData() != null)
272+
ASN1Sequence keySeq = (ASN1Sequence)mldsaKey;
273+
byte[] seed = ASN1OctetString.getInstance(keySeq.getObjectAt(0)).getOctets();
274+
byte[] encoding = ASN1OctetString.getInstance(keySeq.getObjectAt(1)).getOctets();
275+
276+
// TODO This should only allow seed but is length-flexible
277+
MLDSAPrivateKeyParameters mldsaPriv = new MLDSAPrivateKeyParameters(mldsaParams, seed, pubParams);
278+
if (!Arrays.constantTimeAreEqual(mldsaPriv.getEncoded(), encoding))
280279
{
281-
MLDSAPublicKeyParameters pubParams = PublicKeyFactory.MLDSAConverter.getPublicKeyParams(spParams, keyInfo.getPublicKeyData());
282-
return new MLDSAPrivateKeyParameters(spParams, data, pubParams);
280+
throw new IllegalArgumentException("inconsistent " + mldsaParams.getName() + " private key");
283281
}
284-
return new MLDSAPrivateKeyParameters(spParams, data);
285-
}
286-
else
287-
{
288-
throw new IOException("not supported");
282+
283+
return mldsaPriv;
289284
}
285+
286+
throw new IllegalArgumentException("invalid " + mldsaParams.getName() + " private key");
290287
}
291288
else if (algOID.equals(BCObjectIdentifiers.dilithium2)
292289
|| algOID.equals(BCObjectIdentifiers.dilithium3) || algOID.equals(BCObjectIdentifiers.dilithium5))
@@ -380,6 +377,66 @@ else if (algOID.equals(PQCObjectIdentifiers.mcElieceCca2))
380377
}
381378
}
382379

380+
/**
381+
* So it seems for the new PQC algorithms, there's a couple of approaches to what goes in the OCTET STRING
382+
*/
383+
private static ASN1OctetString parseOctetString(ASN1OctetString octStr, int expectedLength)
384+
throws IOException
385+
{
386+
byte[] data = octStr.getOctets();
387+
//
388+
// it's the right length for a RAW encoding, just return it.
389+
//
390+
if (data.length == expectedLength)
391+
{
392+
return octStr;
393+
}
394+
395+
//
396+
// possible internal OCTET STRING, possibly long form with or without the internal OCTET STRING
397+
ASN1OctetString obj = Utils.parseOctetData(data);
398+
399+
if (obj != null)
400+
{
401+
return ASN1OctetString.getInstance(obj);
402+
}
403+
404+
return octStr;
405+
}
406+
407+
/**
408+
* So it seems for the new PQC algorithms, there's a couple of approaches to what goes in the OCTET STRING
409+
* and in this case there may also be SEQUENCE.
410+
*/
411+
private static ASN1Primitive parsePrimitiveString(ASN1OctetString octStr, int expectedLength)
412+
throws IOException
413+
{
414+
byte[] data = octStr.getOctets();
415+
//
416+
// it's the right length for a RAW encoding, just return it.
417+
//
418+
if (data.length == expectedLength)
419+
{
420+
return octStr;
421+
}
422+
423+
//
424+
// possible internal OCTET STRING, possibly long form with or without the internal OCTET STRING
425+
// or possible SEQUENCE
426+
ASN1Encodable obj = Utils.parseData(data);
427+
428+
if (obj instanceof ASN1OctetString)
429+
{
430+
return ASN1OctetString.getInstance(obj);
431+
}
432+
if (obj instanceof ASN1Sequence)
433+
{
434+
return ASN1Sequence.getInstance(obj);
435+
}
436+
437+
return octStr;
438+
}
439+
383440
private static short[] convert(byte[] octets)
384441
{
385442
short[] rv = new short[octets.length / 2];

core/src/main/jdk1.4/org/bouncycastle/pqc/crypto/util/PrivateKeyInfoFactory.java

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
import java.io.IOException;
44

55
import org.bouncycastle.asn1.ASN1EncodableVector;
6+
import org.bouncycastle.asn1.ASN1Sequence;
67
import org.bouncycastle.asn1.ASN1Set;
78
import org.bouncycastle.asn1.DEROctetString;
89
import org.bouncycastle.asn1.DERSequence;
10+
import org.bouncycastle.asn1.DERTaggedObject;
911
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
1012
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
1113
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
@@ -55,7 +57,8 @@ private PrivateKeyInfoFactory()
5557
* @return the appropriate PrivateKeyInfo
5658
* @throws java.io.IOException on an error encoding the key
5759
*/
58-
public static PrivateKeyInfo createPrivateKeyInfo(AsymmetricKeyParameter privateKey) throws IOException
60+
public static PrivateKeyInfo createPrivateKeyInfo(AsymmetricKeyParameter privateKey)
61+
throws IOException
5962
{
6063
return createPrivateKeyInfo(privateKey, null);
6164
}
@@ -68,7 +71,8 @@ public static PrivateKeyInfo createPrivateKeyInfo(AsymmetricKeyParameter private
6871
* @return the appropriate PrivateKeyInfo
6972
* @throws java.io.IOException on an error encoding the key
7073
*/
71-
public static PrivateKeyInfo createPrivateKeyInfo(AsymmetricKeyParameter privateKey, ASN1Set attributes) throws IOException
74+
public static PrivateKeyInfo createPrivateKeyInfo(AsymmetricKeyParameter privateKey, ASN1Set attributes)
75+
throws IOException
7276
{
7377
if (privateKey instanceof SPHINCSPrivateKeyParameters)
7478
{
@@ -97,7 +101,7 @@ else if (privateKey instanceof NHPrivateKeyParameters)
97101
else if (privateKey instanceof SPHINCSPlusPrivateKeyParameters)
98102
{
99103
SPHINCSPlusPrivateKeyParameters params = (SPHINCSPlusPrivateKeyParameters)privateKey;
100-
104+
101105
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(Utils.sphincsPlusOidLookup(params.getParameters()));
102106

103107
return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(params.getEncoded()), attributes, params.getPublicKey());
@@ -108,7 +112,7 @@ else if (privateKey instanceof SLHDSAPrivateKeyParameters)
108112

109113
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(Utils.slhdsaOidLookup(params.getParameters()));
110114

111-
return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(params.getEncoded()), attributes, params.getPublicKey());
115+
return new PrivateKeyInfo(algorithmIdentifier, params.getEncoded(), attributes);
112116
}
113117
else if (privateKey instanceof PicnicPrivateKeyParameters)
114118
{
@@ -184,7 +188,7 @@ else if (privateKey instanceof FalconPrivateKeyParameters)
184188
else if (privateKey instanceof MLKEMPrivateKeyParameters)
185189
{
186190
MLKEMPrivateKeyParameters params = (MLKEMPrivateKeyParameters)privateKey;
187-
191+
188192
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(Utils.mlkemOidLookup(params.getParameters()));
189193

190194
byte[] seed = params.getSeed();
@@ -234,19 +238,15 @@ else if (privateKey instanceof MLDSAPrivateKeyParameters)
234238

235239
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(Utils.mldsaOidLookup(params.getParameters()));
236240

237-
byte[] seed = params.getSeed();
238-
if (seed == null)
241+
if (params.getPreferredFormat() == MLDSAPrivateKeyParameters.SEED_ONLY)
239242
{
240-
MLDSAPublicKeyParameters pubParams = params.getPublicKeyParameters();
241-
242-
return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(params.getEncoded()), attributes, pubParams.getEncoded());
243+
return new PrivateKeyInfo(algorithmIdentifier, new DERTaggedObject(false, 0, new DEROctetString(params.getSeed())), attributes);
243244
}
244-
else
245+
else if (params.getPreferredFormat() == MLDSAPrivateKeyParameters.EXPANDED_KEY)
245246
{
246-
MLDSAPublicKeyParameters pubParams = params.getPublicKeyParameters();
247-
248-
return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(params.getSeed()), attributes);
247+
return new PrivateKeyInfo(algorithmIdentifier, new DEROctetString(params.getEncoded()), attributes);
249248
}
249+
return new PrivateKeyInfo(algorithmIdentifier, getBasicPQCEncoding(params.getSeed(), params.getEncoded()), attributes);
250250
}
251251
else if (privateKey instanceof DilithiumPrivateKeyParameters)
252252
{
@@ -277,4 +277,15 @@ else if (privateKey instanceof HQCPrivateKeyParameters)
277277
throw new IOException("key parameters not recognized");
278278
}
279279
}
280+
281+
private static ASN1Sequence getBasicPQCEncoding(byte[] seed, byte[] expanded)
282+
{
283+
ASN1EncodableVector v = new ASN1EncodableVector(2);
284+
285+
v.add(new DEROctetString(seed));
286+
287+
v.add(new DEROctetString(expanded));
288+
289+
return new DERSequence(v);
290+
}
280291
}

core/src/main/jdk1.4/org/bouncycastle/pqc/crypto/util/PublicKeyFactory.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,56 @@ AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Obje
472472
}
473473
}
474474

475+
static class MLKEMConverter
476+
extends SubjectPublicKeyInfoConverter
477+
{
478+
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
479+
throws IOException
480+
{
481+
MLKEMParameters parameters = Utils.mlkemParamsLookup(keyInfo.getAlgorithm().getAlgorithm());
482+
483+
try
484+
{
485+
ASN1Primitive obj = keyInfo.parsePublicKey();
486+
KyberPublicKey kyberKey = KyberPublicKey.getInstance(obj);
487+
488+
return new MLKEMPublicKeyParameters(parameters, kyberKey.getT(), kyberKey.getRho());
489+
}
490+
catch (Exception e)
491+
{
492+
// we're a raw encoding
493+
return new MLKEMPublicKeyParameters(parameters, keyInfo.getPublicKeyData().getOctets());
494+
}
495+
}
496+
497+
static MLKEMPublicKeyParameters getPublicKeyParams(MLKEMParameters parameters, ASN1BitString publicKeyData)
498+
{
499+
try
500+
{
501+
ASN1Primitive obj = ASN1Primitive.fromByteArray(publicKeyData.getOctets());
502+
if (obj instanceof ASN1Sequence)
503+
{
504+
ASN1Sequence keySeq = ASN1Sequence.getInstance(obj);
505+
506+
return new MLKEMPublicKeyParameters(parameters,
507+
ASN1OctetString.getInstance(keySeq.getObjectAt(0)).getOctets(),
508+
ASN1OctetString.getInstance(keySeq.getObjectAt(1)).getOctets());
509+
}
510+
else
511+
{
512+
byte[] encKey = ASN1OctetString.getInstance(obj).getOctets();
513+
514+
return new MLKEMPublicKeyParameters(parameters, encKey);
515+
}
516+
}
517+
catch (Exception e)
518+
{
519+
// we're a raw encoding
520+
return new MLKEMPublicKeyParameters(parameters, publicKeyData.getOctets());
521+
}
522+
}
523+
}
524+
475525
private static class KyberConverter
476526
extends SubjectPublicKeyInfoConverter
477527
{

0 commit comments

Comments
 (0)