Skip to content

Commit ee70b26

Browse files
committed
updated encoding of LMS public keys.
dropped inner OCTET STRING off NTRU public keys (appears back out of fashion).
1 parent bce7c71 commit ee70b26

File tree

5 files changed

+126
-51
lines changed

5 files changed

+126
-51
lines changed

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

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.bouncycastle.pqc.crypto.util;
22

3-
import java.io.ByteArrayInputStream;
43
import java.io.IOException;
54
import java.io.InputStream;
65

@@ -12,7 +11,6 @@
1211
import org.bouncycastle.asn1.ASN1OctetString;
1312
import org.bouncycastle.asn1.ASN1Primitive;
1413
import org.bouncycastle.asn1.ASN1Sequence;
15-
import org.bouncycastle.asn1.BERTags;
1614
import org.bouncycastle.asn1.DEROctetString;
1715
import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
1816
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
@@ -156,7 +154,8 @@ else if (algOID.equals(PQCObjectIdentifiers.newHope))
156154
}
157155
else if (algOID.equals(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig))
158156
{
159-
byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePrivateKey()).getOctets();
157+
ASN1OctetString lmsKey = parseOctetString(keyInfo.getPrivateKey(), 64);
158+
byte[] keyEnc = lmsKey.getOctets();
160159
ASN1BitString pubKey = keyInfo.getPublicKeyData();
161160

162161
if (Pack.bigEndianToInt(keyEnc, 0) == 1)
@@ -466,6 +465,7 @@ else if (algOID.equals(PQCObjectIdentifiers.mcElieceCca2))
466465
* So it seems for the new PQC algorithms, there's a couple of approaches to what goes in the OCTET STRING
467466
*/
468467
private static ASN1OctetString parseOctetString(ASN1OctetString octStr, int expectedLength)
468+
throws IOException
469469
{
470470
byte[] data = octStr.getOctets();
471471
//
@@ -478,37 +478,15 @@ private static ASN1OctetString parseOctetString(ASN1OctetString octStr, int expe
478478

479479
//
480480
// possible internal OCTET STRING, possibly long form with or without the internal OCTET STRING
481-
ByteArrayInputStream bIn = new ByteArrayInputStream(data);
482-
483-
int tag = bIn.read();
484-
int len = readLen(bIn);
485-
if (tag == BERTags.OCTET_STRING)
481+
data = Utils.readOctetString(data);
482+
if (data != null)
486483
{
487-
if (len == bIn.available())
488-
{
489-
return ASN1OctetString.getInstance(data);
490-
}
484+
return new DEROctetString(data);
491485
}
492486

493487
return octStr;
494488
}
495-
496-
private static int readLen(ByteArrayInputStream bIn)
497-
{
498-
int length = bIn.read();
499-
if (length != (length & 0x7f))
500-
{
501-
int count = length & 0x7f;
502-
length = 0;
503-
while (count-- != 0)
504-
{
505-
length = (length << 8) + bIn.read();
506-
}
507-
}
508-
509-
return length;
510-
}
511-
489+
512490
private static short[] convert(byte[] octets)
513491
{
514492
short[] rv = new short[octets.length / 2];

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

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.bouncycastle.pqc.crypto.hqc.HQCParameters;
4040
import org.bouncycastle.pqc.crypto.hqc.HQCPublicKeyParameters;
4141
import org.bouncycastle.pqc.crypto.lms.HSSPublicKeyParameters;
42+
import org.bouncycastle.pqc.crypto.lms.LMSKeyParameters;
4243
import org.bouncycastle.pqc.crypto.lms.LMSPublicKeyParameters;
4344
import org.bouncycastle.pqc.crypto.mldsa.MLDSAParameters;
4445
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
@@ -131,7 +132,7 @@ public class PublicKeyFactory
131132
converters.put(BCObjectIdentifiers.sphincsPlus_shake_256s, new SPHINCSPlusConverter());
132133
converters.put(BCObjectIdentifiers.sphincsPlus_shake_256f, new SPHINCSPlusConverter());
133134
converters.put(new ASN1ObjectIdentifier("1.3.9999.6.4.10"), new SPHINCSPlusConverter());
134-
135+
135136
converters.put(BCObjectIdentifiers.mceliece348864_r3, new CMCEConverter());
136137
converters.put(BCObjectIdentifiers.mceliece348864f_r3, new CMCEConverter());
137138
converters.put(BCObjectIdentifiers.mceliece460896_r3, new CMCEConverter());
@@ -437,8 +438,20 @@ private static class LMSConverter
437438
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
438439
throws IOException
439440
{
440-
byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePublicKey()).getOctets();
441+
byte[] keyEnc = keyInfo.getPublicKeyData().getOctets();
442+
byte[] data = Utils.readOctetString(keyEnc);
443+
444+
if (data != null)
445+
{
446+
return getLmsKeyParameters(data);
447+
}
448+
449+
return getLmsKeyParameters(keyEnc);
450+
}
441451

452+
private LMSKeyParameters getLmsKeyParameters(byte[] keyEnc)
453+
throws IOException
454+
{
442455
if (Pack.bigEndianToInt(keyEnc, 0) == 1)
443456
{
444457
return LMSPublicKeyParameters.getInstance(Arrays.copyOfRange(keyEnc, 4, keyEnc.length));
@@ -495,7 +508,7 @@ AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Obje
495508
return new CMCEPublicKeyParameters(spParams, keyEnc);
496509
}
497510
catch (Exception e)
498-
{
511+
{
499512
byte[] keyEnc = keyInfo.getPublicKeyData().getOctets();
500513

501514
CMCEParameters spParams = Utils.mcElieceParamsLookup(keyInfo.getAlgorithm().getAlgorithm());
@@ -506,13 +519,13 @@ AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Obje
506519
}
507520

508521
private static class SABERConverter
509-
extends SubjectPublicKeyInfoConverter
522+
extends SubjectPublicKeyInfoConverter
510523
{
511524
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
512-
throws IOException
525+
throws IOException
513526
{
514527
byte[] keyEnc = ASN1OctetString.getInstance(
515-
ASN1Sequence.getInstance(keyInfo.parsePublicKey()).getObjectAt(0)).getOctets();
528+
ASN1Sequence.getInstance(keyInfo.parsePublicKey()).getObjectAt(0)).getOctets();
516529

517530
SABERParameters saberParams = Utils.saberParamsLookup(keyInfo.getAlgorithm().getAlgorithm());
518531

@@ -533,10 +546,10 @@ AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Obje
533546
}
534547

535548
private static class FrodoConverter
536-
extends SubjectPublicKeyInfoConverter
549+
extends SubjectPublicKeyInfoConverter
537550
{
538551
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
539-
throws IOException
552+
throws IOException
540553
{
541554
byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePublicKey()).getOctets();
542555

@@ -547,10 +560,10 @@ AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Obje
547560
}
548561

549562
private static class PicnicConverter
550-
extends SubjectPublicKeyInfoConverter
563+
extends SubjectPublicKeyInfoConverter
551564
{
552565
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
553-
throws IOException
566+
throws IOException
554567
{
555568
byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePublicKey()).getOctets();
556569

@@ -566,7 +579,19 @@ private static class NtruConverter
566579
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
567580
throws IOException
568581
{
569-
byte[] keyEnc = ASN1OctetString.getInstance(keyInfo.parsePublicKey()).getOctets();
582+
byte[] keyEnc = keyInfo.getPublicKeyData().getOctets();
583+
byte[] data = Utils.readOctetString(keyEnc);
584+
585+
if (data != null)
586+
{
587+
return getNtruPublicKeyParameters(keyInfo, data);
588+
}
589+
590+
return getNtruPublicKeyParameters(keyInfo, keyEnc);
591+
}
592+
593+
private NTRUPublicKeyParameters getNtruPublicKeyParameters(SubjectPublicKeyInfo keyInfo, byte[] keyEnc)
594+
{
570595

571596
NTRUParameters ntruParams = Utils.ntruParamsLookup(keyInfo.getAlgorithm().getAlgorithm());
572597

@@ -640,7 +665,7 @@ AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Obje
640665
return new SNTRUPrimePublicKeyParameters(ntruLPRimeParams, keyEnc);
641666
}
642667
}
643-
668+
644669
static class DilithiumConverter
645670
extends SubjectPublicKeyInfoConverter
646671
{
@@ -720,10 +745,10 @@ static MLDSAPublicKeyParameters getPublicKeyParams(MLDSAParameters dilithiumPara
720745
}
721746

722747
private static class BIKEConverter
723-
extends SubjectPublicKeyInfoConverter
748+
extends SubjectPublicKeyInfoConverter
724749
{
725750
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
726-
throws IOException
751+
throws IOException
727752
{
728753
try
729754
{
@@ -745,10 +770,10 @@ AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Obje
745770
}
746771

747772
private static class HQCConverter
748-
extends SubjectPublicKeyInfoConverter
773+
extends SubjectPublicKeyInfoConverter
749774
{
750775
AsymmetricKeyParameter getPublicKeyParameters(SubjectPublicKeyInfo keyInfo, Object defaultParams)
751-
throws IOException
776+
throws IOException
752777
{
753778
try
754779
{

core/src/main/java/org/bouncycastle/pqc/crypto/util/SubjectPublicKeyInfoFactory.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ else if (publicKey instanceof LMSPublicKeyParameters)
9191
byte[] encoding = Composer.compose().u32str(1).bytes(params).build();
9292

9393
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig);
94-
return new SubjectPublicKeyInfo(algorithmIdentifier, new DEROctetString(encoding));
94+
return new SubjectPublicKeyInfo(algorithmIdentifier, encoding);
9595
}
9696
else if (publicKey instanceof HSSPublicKeyParameters)
9797
{
@@ -100,7 +100,7 @@ else if (publicKey instanceof HSSPublicKeyParameters)
100100
byte[] encoding = Composer.compose().u32str(params.getL()).bytes(params.getLMSPublicKey()).build();
101101

102102
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.id_alg_hss_lms_hashsig);
103-
return new SubjectPublicKeyInfo(algorithmIdentifier, new DEROctetString(encoding));
103+
return new SubjectPublicKeyInfo(algorithmIdentifier, encoding);
104104
}
105105
else if (publicKey instanceof SLHDSAPublicKeyParameters)
106106
{
@@ -215,7 +215,7 @@ else if (publicKey instanceof NTRUPublicKeyParameters)
215215
byte[] encoding = params.getEncoded();
216216

217217
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(Utils.ntruOidLookup(params.getParameters()));
218-
return new SubjectPublicKeyInfo(algorithmIdentifier, new DEROctetString(encoding));
218+
return new SubjectPublicKeyInfo(algorithmIdentifier, encoding);
219219
}
220220
else if (publicKey instanceof FalconPublicKeyParameters)
221221
{

core/src/main/java/org/bouncycastle/pqc/crypto/util/Utils.java

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package org.bouncycastle.pqc.crypto.util;
22

3+
import java.io.ByteArrayInputStream;
4+
import java.io.IOException;
35
import java.util.HashMap;
46
import java.util.Map;
57

68
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
9+
import org.bouncycastle.asn1.BERTags;
710
import org.bouncycastle.asn1.DERNull;
811
import org.bouncycastle.asn1.bc.BCObjectIdentifiers;
912
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
@@ -35,6 +38,7 @@
3538
import org.bouncycastle.pqc.crypto.xmss.XMSSKeyParameters;
3639
import org.bouncycastle.pqc.legacy.crypto.qtesla.QTESLASecurityCategory;
3740
import org.bouncycastle.util.Integers;
41+
import org.bouncycastle.util.io.Streams;
3842

3943
class Utils
4044
{
@@ -101,7 +105,7 @@ class Utils
101105

102106
static final Map shldsaOids = new HashMap<ASN1ObjectIdentifier, SLHDSAParameters>();
103107
static final Map shldsaParams = new HashMap<SLHDSAParameters, ASN1ObjectIdentifier>();
104-
108+
105109
static
106110
{
107111
categories.put(PQCObjectIdentifiers.qTESLA_p_I, Integers.valueOf(QTESLASecurityCategory.PROVABLY_SECURE_I));
@@ -230,7 +234,7 @@ class Utils
230234

231235
mlkemOids.put(MLKEMParameters.ml_kem_512, NISTObjectIdentifiers.id_alg_ml_kem_512);
232236
mlkemOids.put(MLKEMParameters.ml_kem_768, NISTObjectIdentifiers.id_alg_ml_kem_768);
233-
mlkemOids.put(MLKEMParameters.ml_kem_1024,NISTObjectIdentifiers.id_alg_ml_kem_1024);
237+
mlkemOids.put(MLKEMParameters.ml_kem_1024, NISTObjectIdentifiers.id_alg_ml_kem_1024);
234238

235239
mlkemParams.put(NISTObjectIdentifiers.id_alg_ml_kem_512, MLKEMParameters.ml_kem_512);
236240
mlkemParams.put(NISTObjectIdentifiers.id_alg_ml_kem_768, MLKEMParameters.ml_kem_768);
@@ -478,7 +482,7 @@ static SLHDSAParameters slhdsaParamsLookup(ASN1ObjectIdentifier oid)
478482
{
479483
return (SLHDSAParameters)shldsaParams.get(oid);
480484
}
481-
485+
482486
static int qTeslaLookupSecurityCategory(AlgorithmIdentifier algorithm)
483487
{
484488
return ((Integer)categories.get(algorithm.getAlgorithm())).intValue();
@@ -778,4 +782,45 @@ static RainbowParameters rainbowParamsLookup(ASN1ObjectIdentifier oid)
778782
{
779783
return (RainbowParameters)rainbowParams.get(oid);
780784
}
785+
786+
static byte[] readOctetString(byte[] data)
787+
throws IOException
788+
{
789+
if (data[0] == BERTags.OCTET_STRING)
790+
{
791+
ByteArrayInputStream bIn = new ByteArrayInputStream(data);
792+
793+
int tag = bIn.read();
794+
int len = readLen(bIn);
795+
if (len == bIn.available())
796+
{
797+
return Streams.readAll(bIn);
798+
}
799+
}
800+
801+
return null;
802+
}
803+
804+
/**
805+
* ASN.1 length reader.
806+
*/
807+
static int readLen(ByteArrayInputStream bIn)
808+
{
809+
int length = bIn.read();
810+
if (length < 0)
811+
{
812+
return -1;
813+
}
814+
if (length != (length & 0x7f))
815+
{
816+
int count = length & 0x7f;
817+
length = 0;
818+
while (count-- != 0)
819+
{
820+
length = (length << 8) + bIn.read();
821+
}
822+
}
823+
824+
return length;
825+
}
781826
}

prov/src/test/java/org/bouncycastle/pqc/jcajce/provider/test/LMSTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@
2323
import org.bouncycastle.pqc.jcajce.spec.LMSKeyGenParameterSpec;
2424
import org.bouncycastle.util.Arrays;
2525
import org.bouncycastle.util.Strings;
26+
import org.bouncycastle.util.encoders.Base64;
2627

2728
/**
2829
* LMS is now promoted to the BC provider.
2930
*/
3031
public class LMSTest
3132
extends TestCase
3233
{
34+
private static final byte[] nestedPublicKey = Base64.decode("MFAwDQYLKoZIhvcNAQkQAxEDPwAEPAAAAAEAAAAFAAAAARmSUd5GHVvFNVl0JBcv+GJX8+FaUrz1mNrCHGZ1z8c4j9kgSBhaEYlu+//bc2yOhQ==");
35+
private static final byte[] nestedPrivateKey = Base64.decode("MIGhAgEBMA0GCyqGSIb3DQEJEAMRBE4ETAAAAAEAAAAAAAAABQAAAAEZklHeRh1bxTVZdCQXL/hiAAAAAAAAACAAAAAgXs4Bdu2gpyoEccTNWwAA81qLeSqn2yW+LWYVAi2hadyBPQAAAAABAAAABQAAAAEZklHeRh1bxTVZdCQXL/hiV/PhWlK89Zjawhxmdc/HOI/ZIEgYWhGJbvv/23NsjoU=");
36+
3337
public void setUp()
3438
{
3539
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null)
@@ -82,6 +86,29 @@ private void trySigning(KeyPair keyPair)
8286
assertTrue(signer.verify(sig));
8387
}
8488

89+
public void testKeyEncoding()
90+
throws Exception
91+
{
92+
93+
KeyFactory kf = KeyFactory.getInstance("LMS", "BC");
94+
95+
PublicKey oldLmsPub = kf.generatePublic(new X509EncodedKeySpec(nestedPublicKey));
96+
PrivateKey oldLmsPriv = kf.generatePrivate(new PKCS8EncodedKeySpec(nestedPrivateKey));
97+
98+
trySigning(new KeyPair(oldLmsPub, oldLmsPriv));
99+
100+
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("LMS", "BC");
101+
102+
kpGen.initialize(new LMSKeyGenParameterSpec(LMSigParameters.lms_sha256_n32_h5, LMOtsParameters.sha256_n32_w1));
103+
104+
KeyPair kp = kpGen.generateKeyPair();
105+
106+
PublicKey newLmsPub = kf.generatePublic(new X509EncodedKeySpec(kp.getPublic().getEncoded()));
107+
PrivateKey newLmsPriv = kf.generatePrivate(new PKCS8EncodedKeySpec(kp.getPrivate().getEncoded()));
108+
109+
trySigning(new KeyPair(newLmsPub, newLmsPriv));
110+
}
111+
85112
public void testKeyFactoryLMSKey()
86113
throws Exception
87114
{

0 commit comments

Comments
 (0)