From 1705b6e25e4c5978d6e9c0cd5cf62a08bfa08348 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 13 Jun 2025 08:48:34 -0400 Subject: [PATCH 1/3] BouncyCastle 1.80 source files, unmodified, as a base for future merges will not compile --- .../crypto/AsymmetricCipherKeyPair.java | 61 ++ .../AsymmetricCipherKeyPairGenerator.java | 22 + .../bouncycastle/crypto/CipherParameters.java | 8 + .../bouncycastle/crypto/CryptoException.java | 48 ++ .../crypto/CryptoServiceProperties.java | 12 + .../crypto/CryptoServicePurpose.java | 15 + .../crypto/CryptoServicesRegistrar.java | 541 ++++++++++++++++++ .../crypto/DataLengthException.java | 29 + .../src/org/bouncycastle/crypto/Digest.java | 51 ++ .../bouncycastle/crypto/ExtendedDigest.java | 13 + .../crypto/KeyGenerationParameters.java | 48 ++ .../crypto/RuntimeCryptoException.java | 26 + .../crypto/SecretWithEncapsulation.java | 24 + .../java/src/org/bouncycastle/crypto/Xof.java | 30 + .../crypto/digests/EncodableDigest.java | 17 + .../crypto/digests/KeccakDigest.java | 443 ++++++++++++++ .../crypto/digests/LongDigest.java | 426 ++++++++++++++ .../crypto/digests/SHA3Digest.java | 87 +++ .../crypto/digests/SHA512Digest.java | 149 +++++ .../crypto/digests/SHAKEDigest.java | 150 +++++ .../crypto/digests/package-info.java | 4 + .../org/bouncycastle/crypto/package-info.java | 4 + .../crypto/params/AsymmetricKeyParameter.java | 20 + .../crypto/params/ParametersWithContext.java | 49 ++ .../crypto/params/ParametersWithRandom.java | 37 ++ .../crypto/params/package-info.java | 4 + .../pqc/crypto/KEMParameters.java | 8 + .../bouncycastle/pqc/crypto/mlkem/CBD.java | 84 +++ .../pqc/crypto/mlkem/MLKEMEngine.java | 326 +++++++++++ .../pqc/crypto/mlkem/MLKEMExtractor.java | 31 + .../pqc/crypto/mlkem/MLKEMGenerator.java | 42 ++ .../pqc/crypto/mlkem/MLKEMIndCpa.java | 444 ++++++++++++++ .../mlkem/MLKEMKeyGenerationParameters.java | 24 + .../crypto/mlkem/MLKEMKeyPairGenerator.java | 57 ++ .../pqc/crypto/mlkem/MLKEMKeyParameters.java | 23 + .../pqc/crypto/mlkem/MLKEMParameters.java | 37 ++ .../mlkem/MLKEMPrivateKeyParameters.java | 109 ++++ .../mlkem/MLKEMPublicKeyParameters.java | 44 ++ .../bouncycastle/pqc/crypto/mlkem/Ntt.java | 100 ++++ .../bouncycastle/pqc/crypto/mlkem/Poly.java | 354 ++++++++++++ .../pqc/crypto/mlkem/PolyVec.java | 272 +++++++++ .../bouncycastle/pqc/crypto/mlkem/Reduce.java | 35 ++ .../pqc/crypto/mlkem/Symmetric.java | 94 +++ .../util/SecretWithEncapsulationImpl.java | 64 +++ .../src/org/bouncycastle/util/Memoable.java | 27 + .../java/src/org/bouncycastle/util/Pack.java | 419 ++++++++++++++ .../org/bouncycastle/util/package-info.java | 4 + 47 files changed, 4916 insertions(+) create mode 100644 router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java create mode 100644 router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java create mode 100644 router/java/src/org/bouncycastle/crypto/CipherParameters.java create mode 100644 router/java/src/org/bouncycastle/crypto/CryptoException.java create mode 100644 router/java/src/org/bouncycastle/crypto/CryptoServiceProperties.java create mode 100644 router/java/src/org/bouncycastle/crypto/CryptoServicePurpose.java create mode 100644 router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java create mode 100644 router/java/src/org/bouncycastle/crypto/DataLengthException.java create mode 100644 router/java/src/org/bouncycastle/crypto/Digest.java create mode 100644 router/java/src/org/bouncycastle/crypto/ExtendedDigest.java create mode 100644 router/java/src/org/bouncycastle/crypto/KeyGenerationParameters.java create mode 100644 router/java/src/org/bouncycastle/crypto/RuntimeCryptoException.java create mode 100644 router/java/src/org/bouncycastle/crypto/SecretWithEncapsulation.java create mode 100644 router/java/src/org/bouncycastle/crypto/Xof.java create mode 100644 router/java/src/org/bouncycastle/crypto/digests/EncodableDigest.java create mode 100644 router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java create mode 100644 router/java/src/org/bouncycastle/crypto/digests/LongDigest.java create mode 100644 router/java/src/org/bouncycastle/crypto/digests/SHA3Digest.java create mode 100644 router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java create mode 100644 router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java create mode 100644 router/java/src/org/bouncycastle/crypto/digests/package-info.java create mode 100644 router/java/src/org/bouncycastle/crypto/package-info.java create mode 100644 router/java/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java create mode 100644 router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java create mode 100644 router/java/src/org/bouncycastle/crypto/params/ParametersWithRandom.java create mode 100644 router/java/src/org/bouncycastle/crypto/params/package-info.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/KEMParameters.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyGenerationParameters.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyParameters.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMParameters.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/Reduce.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/Symmetric.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java create mode 100644 router/java/src/org/bouncycastle/util/Memoable.java create mode 100644 router/java/src/org/bouncycastle/util/Pack.java create mode 100644 router/java/src/org/bouncycastle/util/package-info.java diff --git a/router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java b/router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java new file mode 100644 index 0000000000..ddee701914 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java @@ -0,0 +1,61 @@ +package org.bouncycastle.crypto; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +/** + * a holding class for public/private parameter pairs. + */ +public class AsymmetricCipherKeyPair +{ + private AsymmetricKeyParameter publicParam; + private AsymmetricKeyParameter privateParam; + + /** + * basic constructor. + * + * @param publicParam a public key parameters object. + * @param privateParam the corresponding private key parameters. + */ + public AsymmetricCipherKeyPair( + AsymmetricKeyParameter publicParam, + AsymmetricKeyParameter privateParam) + { + this.publicParam = publicParam; + this.privateParam = privateParam; + } + + /** + * basic constructor. + * + * @param publicParam a public key parameters object. + * @param privateParam the corresponding private key parameters. + * @deprecated use AsymmetricKeyParameter + */ + public AsymmetricCipherKeyPair( + CipherParameters publicParam, + CipherParameters privateParam) + { + this.publicParam = (AsymmetricKeyParameter)publicParam; + this.privateParam = (AsymmetricKeyParameter)privateParam; + } + + /** + * return the public key parameters. + * + * @return the public key parameters. + */ + public AsymmetricKeyParameter getPublic() + { + return publicParam; + } + + /** + * return the private key parameters. + * + * @return the private key parameters. + */ + public AsymmetricKeyParameter getPrivate() + { + return privateParam; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java b/router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java new file mode 100644 index 0000000000..919db199db --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto; + +/** + * interface that a public/private key pair generator should conform to. + */ +public interface AsymmetricCipherKeyPairGenerator +{ + /** + * intialise the key pair generator. + * + * @param param the parameters the key pair is to be initialised with. + */ + public void init(KeyGenerationParameters param); + + /** + * return an AsymmetricCipherKeyPair containing the generated keys. + * + * @return an AsymmetricCipherKeyPair containing the generated keys. + */ + public AsymmetricCipherKeyPair generateKeyPair(); +} + diff --git a/router/java/src/org/bouncycastle/crypto/CipherParameters.java b/router/java/src/org/bouncycastle/crypto/CipherParameters.java new file mode 100644 index 0000000000..5be873047d --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/CipherParameters.java @@ -0,0 +1,8 @@ +package org.bouncycastle.crypto; + +/** + * all parameter classes implement this. + */ +public interface CipherParameters +{ +} diff --git a/router/java/src/org/bouncycastle/crypto/CryptoException.java b/router/java/src/org/bouncycastle/crypto/CryptoException.java new file mode 100644 index 0000000000..352c5569bf --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/CryptoException.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto; + +/** + * the foundation class for the hard exceptions thrown by the crypto packages. + */ +public class CryptoException + extends Exception +{ + private Throwable cause; + + /** + * base constructor. + */ + public CryptoException() + { + } + + /** + * create a CryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public CryptoException( + String message) + { + super(message); + } + + /** + * Create a CryptoException with the given message and underlying cause. + * + * @param message message describing exception. + * @param cause the throwable that was the underlying cause. + */ + public CryptoException( + String message, + Throwable cause) + { + super(message); + + this.cause = cause; + } + + public Throwable getCause() + { + return cause; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/CryptoServiceProperties.java b/router/java/src/org/bouncycastle/crypto/CryptoServiceProperties.java new file mode 100644 index 0000000000..07a54406cd --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/CryptoServiceProperties.java @@ -0,0 +1,12 @@ +package org.bouncycastle.crypto; + +public interface CryptoServiceProperties +{ + int bitsOfSecurity(); + + String getServiceName(); + + CryptoServicePurpose getPurpose(); + + Object getParams(); +} diff --git a/router/java/src/org/bouncycastle/crypto/CryptoServicePurpose.java b/router/java/src/org/bouncycastle/crypto/CryptoServicePurpose.java new file mode 100644 index 0000000000..2789326f95 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/CryptoServicePurpose.java @@ -0,0 +1,15 @@ +package org.bouncycastle.crypto; + +public enum CryptoServicePurpose +{ + AGREEMENT, + ENCRYPTION, + DECRYPTION, + KEYGEN, + SIGNING, // for signatures (and digests) + VERIFYING, + AUTHENTICATION, // for MACs (and digests) + VERIFICATION, + PRF, + ANY +} diff --git a/router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java b/router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java new file mode 100644 index 0000000000..90d3c85348 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java @@ -0,0 +1,541 @@ +package org.bouncycastle.crypto; + +import java.math.BigInteger; +import java.security.AccessController; +import java.security.Permission; +import java.security.PrivilegedAction; +import java.security.SecureRandom; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Logger; + +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.params.DHParameters; +import org.bouncycastle.crypto.params.DHValidationParameters; +import org.bouncycastle.crypto.params.DSAParameters; +import org.bouncycastle.crypto.params.DSAValidationParameters; +import org.bouncycastle.util.Properties; +import org.bouncycastle.util.Strings; +import org.bouncycastle.util.encoders.Hex; + +/** + * Basic registrar class for providing defaults for cryptography services in this module. + */ +public final class CryptoServicesRegistrar +{ + private static final Logger LOG = Logger.getLogger(CryptoServicesRegistrar.class.getName()); + + private static final Permission CanSetDefaultProperty = new CryptoServicesPermission(CryptoServicesPermission.GLOBAL_CONFIG); + private static final Permission CanSetThreadProperty = new CryptoServicesPermission(CryptoServicesPermission.THREAD_LOCAL_CONFIG); + private static final Permission CanSetDefaultRandom = new CryptoServicesPermission(CryptoServicesPermission.DEFAULT_RANDOM); + private static final Permission CanSetConstraints = new CryptoServicesPermission(CryptoServicesPermission.CONSTRAINTS); + + private static final ThreadLocal> threadProperties = new ThreadLocal>(); + private static final Map globalProperties = Collections.synchronizedMap(new HashMap()); + private static final SecureRandomProvider defaultRandomProviderImpl = new ThreadLocalSecureRandomProvider(); + + private static final CryptoServicesConstraints noConstraintsImpl = new CryptoServicesConstraints() + { + public void check(CryptoServiceProperties service) + { + // anything goes. + } + }; + + private static final AtomicReference defaultSecureRandomProvider = new AtomicReference(); + private static final boolean preconfiguredConstraints; + private static final AtomicReference servicesConstraints = new AtomicReference(); + + static + { + // default domain parameters for DSA and Diffie-Hellman + + DSAParameters def512Params = new DSAParameters( + new BigInteger("fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17", 16), + new BigInteger("962eddcc369cba8ebb260ee6b6a126d9346e38c5", 16), + new BigInteger("678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4", 16), + new DSAValidationParameters(Hex.decodeStrict("b869c82b35d70e1b1ff91b28e37a62ecdc34409b"), 123)); + + DSAParameters def768Params = new DSAParameters( + new BigInteger("e9e642599d355f37c97ffd3567120b8e25c9cd43e927b3a9670fbec5" + + "d890141922d2c3b3ad2480093799869d1e846aab49fab0ad26d2ce6a" + + "22219d470bce7d777d4a21fbe9c270b57f607002f3cef8393694cf45" + + "ee3688c11a8c56ab127a3daf", 16), + new BigInteger("9cdbd84c9f1ac2f38d0f80f42ab952e7338bf511", 16), + new BigInteger("30470ad5a005fb14ce2d9dcd87e38bc7d1b1c5facbaecbe95f190aa7" + + "a31d23c4dbbcbe06174544401a5b2c020965d8c2bd2171d366844577" + + "1f74ba084d2029d83c1c158547f3a9f1a2715be23d51ae4d3e5a1f6a" + + "7064f316933a346d3f529252", 16), + new DSAValidationParameters(Hex.decodeStrict("77d0f8c4dad15eb8c4f2f8d6726cefd96d5bb399"), 263)); + + DSAParameters def1024Params = new DSAParameters( + new BigInteger("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80" + + "b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b" + + "801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c6" + + "1bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675" + + "f3ae2b61d72aeff22203199dd14801c7", 16), + new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16), + new BigInteger("f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b" + + "3d0782675159578ebad4594fe67107108180b449167123e84c281613" + + "b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f" + + "0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06" + + "928b665e807b552564014c3bfecf492a", 16), + new DSAValidationParameters(Hex.decodeStrict("8d5155894229d5e689ee01e6018a237e2cae64cd"), 92)); + + DSAParameters def2048Params = new DSAParameters( + new BigInteger("95475cf5d93e596c3fcd1d902add02f427f5f3c7210313bb45fb4d5b" + + "b2e5fe1cbd678cd4bbdd84c9836be1f31c0777725aeb6c2fc38b85f4" + + "8076fa76bcd8146cc89a6fb2f706dd719898c2083dc8d896f84062e2" + + "c9c94d137b054a8d8096adb8d51952398eeca852a0af12df83e475aa" + + "65d4ec0c38a9560d5661186ff98b9fc9eb60eee8b030376b236bc73b" + + "e3acdbd74fd61c1d2475fa3077b8f080467881ff7e1ca56fee066d79" + + "506ade51edbb5443a563927dbc4ba520086746175c8885925ebc64c6" + + "147906773496990cb714ec667304e261faee33b3cbdf008e0c3fa906" + + "50d97d3909c9275bf4ac86ffcb3d03e6dfc8ada5934242dd6d3bcca2" + + "a406cb0b", 16), + new BigInteger("f8183668ba5fc5bb06b5981e6d8b795d30b8978d43ca0ec572e37e09939a9773", 16), + new BigInteger("42debb9da5b3d88cc956e08787ec3f3a09bba5f48b889a74aaf53174" + + "aa0fbe7e3c5b8fcd7a53bef563b0e98560328960a9517f4014d3325f" + + "c7962bf1e049370d76d1314a76137e792f3f0db859d095e4a5b93202" + + "4f079ecf2ef09c797452b0770e1350782ed57ddf794979dcef23cb96" + + "f183061965c4ebc93c9c71c56b925955a75f94cccf1449ac43d586d0" + + "beee43251b0b2287349d68de0d144403f13e802f4146d882e057af19" + + "b6f6275c6676c8fa0e3ca2713a3257fd1b27d0639f695e347d8d1cf9" + + "ac819a26ca9b04cb0eb9b7b035988d15bbac65212a55239cfc7e58fa" + + "e38d7250ab9991ffbc97134025fe8ce04c4399ad96569be91a546f49" + + "78693c7a", 16), + new DSAValidationParameters(Hex.decodeStrict("b0b4417601b59cbc9d8ac8f935cadaec4f5fbb2f23785609ae466748d9b5a536"), 497)); + + localSetGlobalProperty(Property.DSA_DEFAULT_PARAMS, def512Params, def768Params, def1024Params, def2048Params); + localSetGlobalProperty(Property.DH_DEFAULT_PARAMS, toDH(def512Params), toDH(def768Params), toDH(def1024Params), toDH(def2048Params)); + + servicesConstraints.set(getDefaultConstraints()); + preconfiguredConstraints = (servicesConstraints.get() != noConstraintsImpl); + } + + private CryptoServicesRegistrar() + { + + } + + /** + * Return the default source of randomness. + * + * @return the default SecureRandom + */ + public static SecureRandom getSecureRandom() + { + defaultSecureRandomProvider.compareAndSet(null, defaultRandomProviderImpl); + + return defaultSecureRandomProvider.get().get(); + } + + /** + * Return either the passed-in SecureRandom, or if it is null, then the default source of randomness. + * + * @param secureRandom the SecureRandom to use if it is not null. + * @return the SecureRandom parameter if it is not null, or else the default SecureRandom + */ + public static SecureRandom getSecureRandom(SecureRandom secureRandom) + { + return null == secureRandom ? getSecureRandom() : secureRandom; + } + + /** + * Set a default secure random to be used where none is otherwise provided. + * + * @param secureRandom the SecureRandom to use as the default. + */ + public static void setSecureRandom(final SecureRandom secureRandom) + { + checkPermission(CanSetDefaultRandom); + + if (secureRandom == null) + { + defaultSecureRandomProvider.set(defaultRandomProviderImpl); + } + else + { + defaultSecureRandomProvider.set(new SecureRandomProvider() + { + public SecureRandom get() + { + return secureRandom; + } + }); + } + } + + /** + * Set a default secure random provider to be used where none is otherwise provided. + * + * @param secureRandomProvider a provider SecureRandom to use when a default SecureRandom is requested. + */ + public static void setSecureRandomProvider(SecureRandomProvider secureRandomProvider) + { + checkPermission(CanSetDefaultRandom); + + defaultSecureRandomProvider.set(secureRandomProvider); + } + + /** + * Return the current algorithm/services constraints. + * + * @return the algorithm/services constraints. + */ + public static CryptoServicesConstraints getServicesConstraints() + { + return servicesConstraints.get(); + } + + /** + * Check a service to make sure it meets the current constraints. + * + * @param cryptoService the service to be checked. + * @throws CryptoServiceConstraintsException if the service violates the current constraints. + */ + public static void checkConstraints(CryptoServiceProperties cryptoService) + { + servicesConstraints.get().check(cryptoService); + } + + /** + * Set the current algorithm constraints. + */ + public static void setServicesConstraints(CryptoServicesConstraints constraints) + { + checkPermission(CanSetConstraints); + + CryptoServicesConstraints newConstraints = (constraints == null) ? noConstraintsImpl : constraints; + + if (preconfiguredConstraints) + { + if (Properties.isOverrideSet("org.bouncycastle.constraints.allow_override")) + { + servicesConstraints.set(newConstraints); + } + else + { + LOG.warning("attempt to override pre-configured constraints ignored"); + } + } + else + { + // TODO: should this only be allowed once? + servicesConstraints.set(newConstraints); + } + } + + /** + * Return the default value for a particular property if one exists. The look up is done on the thread's local + * configuration first and then on the global configuration in no local configuration exists. + * + * @param property the property to look up. + * @param the type to be returned + * @return null if the property is not set, the default value otherwise, + */ + public static T getProperty(Property property) + { + Object[] values = lookupProperty(property); + + if (values != null) + { + return (T)values[0]; + } + + return null; + } + + private static Object[] lookupProperty(Property property) + { + Map properties = threadProperties.get(); + Object[] values; + + if (properties == null || !properties.containsKey(property.name)) + { + values = globalProperties.get(property.name); + } + else + { + values = properties.get(property.name); + } + return values; + } + + /** + * Return an array representing the current values for a sized property such as DH_DEFAULT_PARAMS or + * DSA_DEFAULT_PARAMS. + * + * @param property the name of the property to look up. + * @param the base type of the array to be returned. + * @return null if the property is not set, an array of the current values otherwise. + */ + public static T[] getSizedProperty(Property property) + { + Object[] values = lookupProperty(property); + + if (values == null) + { + return null; + } + + return (T[])values.clone(); + } + + /** + * Return the value for a specific size for a sized property such as DH_DEFAULT_PARAMS or + * DSA_DEFAULT_PARAMS. + * + * @param property the name of the property to look up. + * @param size the size (in bits) of the defining value in the property type. + * @param the type of the value to be returned. + * @return the current value for the size, null if there is no value set, + */ + public static T getSizedProperty(Property property, int size) + { + Object[] values = lookupProperty(property); + + if (values == null) + { + return null; + } + + if (property.type.isAssignableFrom(DHParameters.class)) + { + for (int i = 0; i != values.length; i++) + { + DHParameters params = (DHParameters)values[i]; + + if (params.getP().bitLength() == size) + { + return (T)params; + } + } + } + else if (property.type.isAssignableFrom(DSAParameters.class)) + { + for (int i = 0; i != values.length; i++) + { + DSAParameters params = (DSAParameters)values[i]; + + if (params.getP().bitLength() == size) + { + return (T)params; + } + } + } + + return null; + } + + /** + * Set the value of the the passed in property on the current thread only. More than + * one value can be passed in for a sized property. If more than one value is provided the + * first value in the argument list becomes the default value. + * + * @param property the name of the property to set. + * @param propertyValue the values to assign to the property. + * @param the base type of the property value. + */ + public static void setThreadProperty(Property property, T... propertyValue) + { + checkPermission(CanSetThreadProperty); + + if (!property.type.isAssignableFrom(propertyValue[0].getClass())) + { + throw new IllegalArgumentException("Bad property value passed"); + } + + localSetThread(property, propertyValue.clone()); + } + + /** + * Set the value of the the passed in property globally in the JVM. More than + * one value can be passed in for a sized property. If more than one value is provided the + * first value in the argument list becomes the default value. + * + * @param property the name of the property to set. + * @param propertyValue the values to assign to the property. + * @param the base type of the property value. + */ + public static void setGlobalProperty(Property property, T... propertyValue) + { + checkPermission(CanSetDefaultProperty); + + localSetGlobalProperty(property, propertyValue.clone()); + } + + private static void localSetThread(Property property, T[] propertyValue) + { + Map properties = threadProperties.get(); + + if (properties == null) + { + properties = new HashMap(); + threadProperties.set(properties); + } + + properties.put(property.name, propertyValue); + } + + private static void localSetGlobalProperty(Property property, T... propertyValue) + { + if (!property.type.isAssignableFrom(propertyValue[0].getClass())) + { + throw new IllegalArgumentException("Bad property value passed"); + } + + // set the property for the current thread as well to avoid mass confusion + localSetThread(property, propertyValue); + + globalProperties.put(property.name, propertyValue); + } + + /** + * Clear the global value for the passed in property. + * + * @param property the property to be cleared. + * @param the base type of the property value + * @return an array of T if a value was previously set, null otherwise. + */ + public static T[] clearGlobalProperty(Property property) + { + checkPermission(CanSetDefaultProperty); + + // clear the property for the current thread as well to avoid confusion + localClearThreadProperty(property); + + return (T[])globalProperties.remove(property.name); + } + + /** + * Clear the thread local value for the passed in property. + * + * @param property the property to be cleared. + * @param the base type of the property value + * @return an array of T if a value was previously set, null otherwise. + */ + public static T[] clearThreadProperty(Property property) + { + checkPermission(CanSetThreadProperty); + + return (T[])localClearThreadProperty(property); + } + + private static Object[] localClearThreadProperty(Property property) + { + Map properties = threadProperties.get(); + + if (properties == null) + { + properties = new HashMap(); + threadProperties.set(properties); + } + + return properties.remove(property.name); + } + + private static void checkPermission(final Permission permission) + { + final SecurityManager securityManager = System.getSecurityManager(); + + if (securityManager != null) + { + AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + securityManager.checkPermission(permission); + + return null; + } + }); + } + } + + private static DHParameters toDH(DSAParameters dsaParams) + { + int pSize = dsaParams.getP().bitLength(); + int m = chooseLowerBound(pSize); + return new DHParameters(dsaParams.getP(), dsaParams.getG(), dsaParams.getQ(), m, 0, null, + new DHValidationParameters(dsaParams.getValidationParameters().getSeed(), dsaParams.getValidationParameters().getCounter())); + } + + // based on lower limit of at least 2^{2 * bits_of_security} + private static int chooseLowerBound(int pSize) + { + int m = 160; + if (pSize > 1024) + { + if (pSize <= 2048) + { + m = 224; + } + else if (pSize <= 3072) + { + m = 256; + } + else if (pSize <= 7680) + { + m = 384; + } + else + { + m = 512; + } + } + return m; + } + + private static CryptoServicesConstraints getDefaultConstraints() + { + // TODO: return one based on system/security properties if set. + + return noConstraintsImpl; + } + + /** + * Available properties that can be set. + */ + public static final class Property + { + /** + * The parameters to be used for processing implicitlyCA X9.62 parameters + */ + public static final Property EC_IMPLICITLY_CA = new Property("ecImplicitlyCA", X9ECParameters.class); + /** + * The default parameters for a particular size of Diffie-Hellman key.This is a sized property. + */ + public static final Property DH_DEFAULT_PARAMS = new Property("dhDefaultParams", DHParameters.class); + /** + * The default parameters for a particular size of DSA key. This is a sized property. + */ + public static final Property DSA_DEFAULT_PARAMS = new Property("dsaDefaultParams", DSAParameters.class); + private final String name; + private final Class type; + + private Property(String name, Class type) + { + this.name = name; + this.type = type; + } + } + + private static class ThreadLocalSecureRandomProvider + implements SecureRandomProvider + { + final ThreadLocal defaultRandoms = new ThreadLocal(); + + public SecureRandom get() + { + if (defaultRandoms.get() == null) + { + defaultRandoms.set(new SecureRandom()); + } + + return defaultRandoms.get(); + } + } +} diff --git a/router/java/src/org/bouncycastle/crypto/DataLengthException.java b/router/java/src/org/bouncycastle/crypto/DataLengthException.java new file mode 100644 index 0000000000..fbf047cf52 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/DataLengthException.java @@ -0,0 +1,29 @@ +package org.bouncycastle.crypto; + +/** + * this exception is thrown if a buffer that is meant to have output + * copied into it turns out to be too short, or if we've been given + * insufficient input. In general this exception will get thrown rather + * than an ArrayOutOfBounds exception. + */ +public class DataLengthException + extends RuntimeCryptoException +{ + /** + * base constructor. + */ + public DataLengthException() + { + } + + /** + * create a DataLengthException with the given message. + * + * @param message the message to be carried with the exception. + */ + public DataLengthException( + String message) + { + super(message); + } +} diff --git a/router/java/src/org/bouncycastle/crypto/Digest.java b/router/java/src/org/bouncycastle/crypto/Digest.java new file mode 100644 index 0000000000..f44fad0d21 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/Digest.java @@ -0,0 +1,51 @@ +package org.bouncycastle.crypto; + +/** + * interface that a message digest conforms to. + */ +public interface Digest +{ + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName(); + + /** + * return the size, in bytes, of the digest produced by this message digest. + * + * @return the size, in bytes, of the digest produced by this message digest. + */ + public int getDigestSize(); + + /** + * update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + public void update(byte in); + + /** + * update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param inOff the offset into the byte array where the data starts. + * @param len the length of the data. + */ + public void update(byte[] in, int inOff, int len); + + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOff); + + /** + * reset the digest back to it's initial state. + */ + public void reset(); +} diff --git a/router/java/src/org/bouncycastle/crypto/ExtendedDigest.java b/router/java/src/org/bouncycastle/crypto/ExtendedDigest.java new file mode 100644 index 0000000000..c5e9e8b0c5 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/ExtendedDigest.java @@ -0,0 +1,13 @@ +package org.bouncycastle.crypto; + +public interface ExtendedDigest + extends Digest +{ + /** + * Return the size in bytes of the internal buffer the digest applies it's compression + * function to. + * + * @return byte length of the digests internal buffer. + */ + public int getByteLength(); +} diff --git a/router/java/src/org/bouncycastle/crypto/KeyGenerationParameters.java b/router/java/src/org/bouncycastle/crypto/KeyGenerationParameters.java new file mode 100644 index 0000000000..ac90123177 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/KeyGenerationParameters.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto; + +import java.security.SecureRandom; + +/** + * The base class for parameters to key generators. + */ +public class KeyGenerationParameters +{ + private SecureRandom random; + private int strength; + + /** + * initialise the generator with a source of randomness + * and a strength (in bits). + * + * @param random the random byte source. + * @param strength the size, in bits, of the keys we want to produce. + */ + public KeyGenerationParameters( + SecureRandom random, + int strength) + { + this.random = CryptoServicesRegistrar.getSecureRandom(random); + this.strength = strength; + } + + /** + * return the random source associated with this + * generator. + * + * @return the generators random source. + */ + public SecureRandom getRandom() + { + return random; + } + + /** + * return the bit strength for keys produced by this generator, + * + * @return the strength of the keys this generator produces (in bits). + */ + public int getStrength() + { + return strength; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/RuntimeCryptoException.java b/router/java/src/org/bouncycastle/crypto/RuntimeCryptoException.java new file mode 100644 index 0000000000..c1572020b8 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/RuntimeCryptoException.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto; + +/** + * the foundation class for the exceptions thrown by the crypto packages. + */ +public class RuntimeCryptoException + extends RuntimeException +{ + /** + * base constructor. + */ + public RuntimeCryptoException() + { + } + + /** + * create a RuntimeCryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public RuntimeCryptoException( + String message) + { + super(message); + } +} diff --git a/router/java/src/org/bouncycastle/crypto/SecretWithEncapsulation.java b/router/java/src/org/bouncycastle/crypto/SecretWithEncapsulation.java new file mode 100644 index 0000000000..15dabba651 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/SecretWithEncapsulation.java @@ -0,0 +1,24 @@ +package org.bouncycastle.crypto; + +import javax.security.auth.Destroyable; + +/** + * Interface describing secret with encapsulation details. + */ +public interface SecretWithEncapsulation + extends Destroyable +{ + /** + * Return the secret associated with the encapsulation. + * + * @return the secret the encapsulation is for. + */ + byte[] getSecret(); + + /** + * Return the data that carries the secret in its encapsulated form. + * + * @return the encapsulation of the secret. + */ + byte[] getEncapsulation(); +} diff --git a/router/java/src/org/bouncycastle/crypto/Xof.java b/router/java/src/org/bouncycastle/crypto/Xof.java new file mode 100644 index 0000000000..fe85206bfd --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/Xof.java @@ -0,0 +1,30 @@ +package org.bouncycastle.crypto; + +/** + * With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes. + * This interface provides the extra method required to support variable output on an extended digest implementation. + */ +public interface Xof + extends ExtendedDigest +{ + /** + * Output the results of the final calculation for this digest to outLen number of bytes. + * + * @param out output array to write the output bytes to. + * @param outOff offset to start writing the bytes at. + * @param outLen the number of output bytes requested. + * @return the number of bytes written + */ + int doFinal(byte[] out, int outOff, int outLen); + + /** + * Start outputting the results of the final calculation for this digest. Unlike doFinal, this method + * will continue producing output until the Xof is explicitly reset, or signals otherwise. + * + * @param out output array to write the output bytes to. + * @param outOff offset to start writing the bytes at. + * @param outLen the number of output bytes requested. + * @return the number of bytes written + */ + int doOutput(byte[] out, int outOff, int outLen); +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/EncodableDigest.java b/router/java/src/org/bouncycastle/crypto/digests/EncodableDigest.java new file mode 100644 index 0000000000..d79fece88a --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/EncodableDigest.java @@ -0,0 +1,17 @@ +package org.bouncycastle.crypto.digests; + +/** + * Encodable digests allow you to download an encoded copy of their internal state. This is useful for the situation where + * you need to generate a signature on an external device and it allows for "sign with last round", so a copy of the + * internal state of the digest, plus the last few blocks of the message are all that needs to be sent, rather than the + * entire message. + */ +public interface EncodableDigest +{ + /** + * Return an encoded byte array for the digest's internal state + * + * @return an encoding of the digests internal state. + */ + byte[] getEncodedState(); +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java b/router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java new file mode 100644 index 0000000000..f53552e550 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java @@ -0,0 +1,443 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.CryptoServiceProperties; +import org.bouncycastle.crypto.CryptoServicePurpose; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Pack; + +/** + * implementation of Keccak based on following KeccakNISTInterface.c from https://keccak.noekeon.org/ + *

+ * Following the naming conventions used in the C source code to enable easy review of the implementation. + */ +public class KeccakDigest + implements ExtendedDigest +{ + private static long[] KeccakRoundConstants = new long[]{ 0x0000000000000001L, 0x0000000000008082L, + 0x800000000000808aL, 0x8000000080008000L, 0x000000000000808bL, 0x0000000080000001L, 0x8000000080008081L, + 0x8000000000008009L, 0x000000000000008aL, 0x0000000000000088L, 0x0000000080008009L, 0x000000008000000aL, + 0x000000008000808bL, 0x800000000000008bL, 0x8000000000008089L, 0x8000000000008003L, 0x8000000000008002L, + 0x8000000000000080L, 0x000000000000800aL, 0x800000008000000aL, 0x8000000080008081L, 0x8000000000008080L, + 0x0000000080000001L, 0x8000000080008008L }; + protected final CryptoServicePurpose purpose; + + protected long[] state = new long[25]; + protected byte[] dataQueue = new byte[192]; + protected int rate; + protected int bitsInQueue; + protected int fixedOutputLength; + protected boolean squeezing; + + public KeccakDigest() + { + this(288, CryptoServicePurpose.ANY); + } + + public KeccakDigest(CryptoServicePurpose purpose) + { + this(288, purpose); + } + + public KeccakDigest(int bitLength) + { + this(bitLength, CryptoServicePurpose.ANY); + } + + public KeccakDigest(int bitLength, CryptoServicePurpose purpose) + { + this.purpose = purpose; + init(bitLength); + + CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties()); + } + + public KeccakDigest(KeccakDigest source) + { + this.purpose = source.purpose; + System.arraycopy(source.state, 0, this.state, 0, source.state.length); + System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length); + this.rate = source.rate; + this.bitsInQueue = source.bitsInQueue; + this.fixedOutputLength = source.fixedOutputLength; + this.squeezing = source.squeezing; + + CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties()); + } + + public String getAlgorithmName() + { + return "Keccak-" + fixedOutputLength; + } + + public int getDigestSize() + { + return fixedOutputLength / 8; + } + + public void update(byte in) + { + absorb(in); + } + + public void update(byte[] in, int inOff, int len) + { + absorb(in, inOff, len); + } + + public int doFinal(byte[] out, int outOff) + { + squeeze(out, outOff, fixedOutputLength); + + reset(); + + return getDigestSize(); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits) + { + if (partialBits > 0) + { + absorbBits(partialByte, partialBits); + } + + squeeze(out, outOff, fixedOutputLength); + + reset(); + + return getDigestSize(); + } + + public void reset() + { + init(fixedOutputLength); + } + + /** + * Return the size of block that the compression function is applied to in bytes. + * + * @return internal byte length of a block. + */ + public int getByteLength() + { + return rate / 8; + } + + private void init(int bitLength) + { + switch (bitLength) + { + case 128: + case 224: + case 256: + case 288: + case 384: + case 512: + initSponge(1600 - (bitLength << 1)); + break; + default: + throw new IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512."); + } + } + + private void initSponge(int rate) + { + if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0)) + { + throw new IllegalStateException("invalid rate value"); + } + + this.rate = rate; + for (int i = 0; i < state.length; ++i) + { + state[i] = 0L; + } + Arrays.fill(this.dataQueue, (byte)0); + this.bitsInQueue = 0; + this.squeezing = false; + this.fixedOutputLength = (1600 - rate) / 2; + } + + protected void absorb(byte data) + { + if ((bitsInQueue % 8) != 0) + { + throw new IllegalStateException("attempt to absorb with odd length queue"); + } + if (squeezing) + { + throw new IllegalStateException("attempt to absorb while squeezing"); + } + + dataQueue[bitsInQueue >>> 3] = data; + if ((bitsInQueue += 8) == rate) + { + KeccakAbsorb(dataQueue, 0); + bitsInQueue = 0; + } + } + + protected void absorb(byte[] data, int off, int len) + { + if ((bitsInQueue % 8) != 0) + { + throw new IllegalStateException("attempt to absorb with odd length queue"); + } + if (squeezing) + { + throw new IllegalStateException("attempt to absorb while squeezing"); + } + + int bytesInQueue = bitsInQueue >>> 3; + int rateBytes = rate >>> 3; + + int available = rateBytes - bytesInQueue; + if (len < available) + { + System.arraycopy(data, off, dataQueue, bytesInQueue, len); + this.bitsInQueue += len << 3; + return; + } + + int count = 0; + if (bytesInQueue > 0) + { + System.arraycopy(data, off, dataQueue, bytesInQueue, available); + count += available; + KeccakAbsorb(dataQueue, 0); + } + + int remaining; + while ((remaining = (len - count)) >= rateBytes) + { + KeccakAbsorb(data, off + count); + count += rateBytes; + } + + System.arraycopy(data, off + count, dataQueue, 0, remaining); + this.bitsInQueue = remaining << 3; + } + + protected void absorbBits(int data, int bits) + { + if (bits < 1 || bits > 7) + { + throw new IllegalArgumentException("'bits' must be in the range 1 to 7"); + } + if ((bitsInQueue % 8) != 0) + { + throw new IllegalStateException("attempt to absorb with odd length queue"); + } + if (squeezing) + { + throw new IllegalStateException("attempt to absorb while squeezing"); + } + + int mask = (1 << bits) - 1; + dataQueue[bitsInQueue >>> 3] = (byte)(data & mask); + + // NOTE: After this, bitsInQueue is no longer a multiple of 8, so no more absorbs will work + bitsInQueue += bits; + } + + private void padAndSwitchToSqueezingPhase() + { + dataQueue[bitsInQueue >>> 3] |= (byte)(1 << (bitsInQueue & 7)); + + if (++bitsInQueue == rate) + { + KeccakAbsorb(dataQueue, 0); + } + else + { + int full = bitsInQueue >>> 6, partial = bitsInQueue & 63; + int off = 0; + for (int i = 0; i < full; ++i) + { + state[i] ^= Pack.littleEndianToLong(dataQueue, off); + off += 8; + } + + if (partial > 0) + { + long mask = (1L << partial) - 1L; + state[full] ^= Pack.littleEndianToLong(dataQueue, off) & mask; + } + } + + state[(rate - 1) >>> 6] ^= (1L << 63); + + bitsInQueue = 0; + squeezing = true; + } + + protected void squeeze(byte[] output, int offset, long outputLength) + { + if (!squeezing) + { + padAndSwitchToSqueezingPhase(); + } + + if ((outputLength % 8) != 0) + { + throw new IllegalStateException("outputLength not a multiple of 8"); + } + + long i = 0; + while (i < outputLength) + { + if (bitsInQueue == 0) + { + KeccakExtract(); + } + int partialBlock = (int)Math.min((long)bitsInQueue, outputLength - i); + System.arraycopy(dataQueue, (rate - bitsInQueue) / 8, output, offset + (int)(i / 8), partialBlock / 8); + bitsInQueue -= partialBlock; + i += partialBlock; + } + } + + private void KeccakAbsorb(byte[] data, int off) + { +// assert 0 == bitsInQueue || (dataQueue == data && 0 == off); + + int count = rate >>> 6; + for (int i = 0; i < count; ++i) + { + state[i] ^= Pack.littleEndianToLong(data, off); + off += 8; + } + + KeccakPermutation(); + } + + private void KeccakExtract() + { +// assert 0 == bitsInQueue; + + KeccakPermutation(); + + Pack.longToLittleEndian(state, 0, rate >>> 6, dataQueue, 0); + + this.bitsInQueue = rate; + } + + private void KeccakPermutation() + { + long[] A = state; + + long a00 = A[ 0], a01 = A[ 1], a02 = A[ 2], a03 = A[ 3], a04 = A[ 4]; + long a05 = A[ 5], a06 = A[ 6], a07 = A[ 7], a08 = A[ 8], a09 = A[ 9]; + long a10 = A[10], a11 = A[11], a12 = A[12], a13 = A[13], a14 = A[14]; + long a15 = A[15], a16 = A[16], a17 = A[17], a18 = A[18], a19 = A[19]; + long a20 = A[20], a21 = A[21], a22 = A[22], a23 = A[23], a24 = A[24]; + + for (int i = 0; i < 24; i++) + { + // theta + long c0 = a00 ^ a05 ^ a10 ^ a15 ^ a20; + long c1 = a01 ^ a06 ^ a11 ^ a16 ^ a21; + long c2 = a02 ^ a07 ^ a12 ^ a17 ^ a22; + long c3 = a03 ^ a08 ^ a13 ^ a18 ^ a23; + long c4 = a04 ^ a09 ^ a14 ^ a19 ^ a24; + + long d1 = (c1 << 1 | c1 >>> -1) ^ c4; + long d2 = (c2 << 1 | c2 >>> -1) ^ c0; + long d3 = (c3 << 1 | c3 >>> -1) ^ c1; + long d4 = (c4 << 1 | c4 >>> -1) ^ c2; + long d0 = (c0 << 1 | c0 >>> -1) ^ c3; + + a00 ^= d1; a05 ^= d1; a10 ^= d1; a15 ^= d1; a20 ^= d1; + a01 ^= d2; a06 ^= d2; a11 ^= d2; a16 ^= d2; a21 ^= d2; + a02 ^= d3; a07 ^= d3; a12 ^= d3; a17 ^= d3; a22 ^= d3; + a03 ^= d4; a08 ^= d4; a13 ^= d4; a18 ^= d4; a23 ^= d4; + a04 ^= d0; a09 ^= d0; a14 ^= d0; a19 ^= d0; a24 ^= d0; + + // rho/pi + c1 = a01 << 1 | a01 >>> 63; + a01 = a06 << 44 | a06 >>> 20; + a06 = a09 << 20 | a09 >>> 44; + a09 = a22 << 61 | a22 >>> 3; + a22 = a14 << 39 | a14 >>> 25; + a14 = a20 << 18 | a20 >>> 46; + a20 = a02 << 62 | a02 >>> 2; + a02 = a12 << 43 | a12 >>> 21; + a12 = a13 << 25 | a13 >>> 39; + a13 = a19 << 8 | a19 >>> 56; + a19 = a23 << 56 | a23 >>> 8; + a23 = a15 << 41 | a15 >>> 23; + a15 = a04 << 27 | a04 >>> 37; + a04 = a24 << 14 | a24 >>> 50; + a24 = a21 << 2 | a21 >>> 62; + a21 = a08 << 55 | a08 >>> 9; + a08 = a16 << 45 | a16 >>> 19; + a16 = a05 << 36 | a05 >>> 28; + a05 = a03 << 28 | a03 >>> 36; + a03 = a18 << 21 | a18 >>> 43; + a18 = a17 << 15 | a17 >>> 49; + a17 = a11 << 10 | a11 >>> 54; + a11 = a07 << 6 | a07 >>> 58; + a07 = a10 << 3 | a10 >>> 61; + a10 = c1; + + // chi + c0 = a00 ^ (~a01 & a02); + c1 = a01 ^ (~a02 & a03); + a02 ^= ~a03 & a04; + a03 ^= ~a04 & a00; + a04 ^= ~a00 & a01; + a00 = c0; + a01 = c1; + + c0 = a05 ^ (~a06 & a07); + c1 = a06 ^ (~a07 & a08); + a07 ^= ~a08 & a09; + a08 ^= ~a09 & a05; + a09 ^= ~a05 & a06; + a05 = c0; + a06 = c1; + + c0 = a10 ^ (~a11 & a12); + c1 = a11 ^ (~a12 & a13); + a12 ^= ~a13 & a14; + a13 ^= ~a14 & a10; + a14 ^= ~a10 & a11; + a10 = c0; + a11 = c1; + + c0 = a15 ^ (~a16 & a17); + c1 = a16 ^ (~a17 & a18); + a17 ^= ~a18 & a19; + a18 ^= ~a19 & a15; + a19 ^= ~a15 & a16; + a15 = c0; + a16 = c1; + + c0 = a20 ^ (~a21 & a22); + c1 = a21 ^ (~a22 & a23); + a22 ^= ~a23 & a24; + a23 ^= ~a24 & a20; + a24 ^= ~a20 & a21; + a20 = c0; + a21 = c1; + + // iota + a00 ^= KeccakRoundConstants[i]; + } + + A[ 0] = a00; A[ 1] = a01; A[ 2] = a02; A[ 3] = a03; A[ 4] = a04; + A[ 5] = a05; A[ 6] = a06; A[ 7] = a07; A[ 8] = a08; A[ 9] = a09; + A[10] = a10; A[11] = a11; A[12] = a12; A[13] = a13; A[14] = a14; + A[15] = a15; A[16] = a16; A[17] = a17; A[18] = a18; A[19] = a19; + A[20] = a20; A[21] = a21; A[22] = a22; A[23] = a23; A[24] = a24; + } + + protected CryptoServiceProperties cryptoServiceProperties() + { + return Utils.getDefaultProperties(this, getDigestSize() * 8, purpose); + } +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/LongDigest.java b/router/java/src/org/bouncycastle/crypto/digests/LongDigest.java new file mode 100644 index 0000000000..9fba7aae76 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/LongDigest.java @@ -0,0 +1,426 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.CryptoServiceProperties; +import org.bouncycastle.crypto.CryptoServicePurpose; +import org.bouncycastle.crypto.ExtendedDigest; +import org.bouncycastle.util.Memoable; +import org.bouncycastle.util.Pack; + +/** + * Base class for SHA-384 and SHA-512. + */ +public abstract class LongDigest + implements ExtendedDigest, Memoable, EncodableDigest +{ + private static final int BYTE_LENGTH = 128; + + protected final CryptoServicePurpose purpose; + + private byte[] xBuf = new byte[8]; + private int xBufOff; + + private long byteCount1; + private long byteCount2; + + protected long H1, H2, H3, H4, H5, H6, H7, H8; + + private long[] W = new long[80]; + private int wOff; + + /** + * Constructor for variable length word + */ + protected LongDigest() + { + this(CryptoServicePurpose.ANY); + } + + /** + * Constructor for variable length word + */ + protected LongDigest(CryptoServicePurpose purpose) + { + this.purpose = purpose; + + xBufOff = 0; + + reset(); + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + */ + protected LongDigest(LongDigest t) + { + this.purpose = t.purpose; + + copyIn(t); + } + + protected void copyIn(LongDigest t) + { + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); + + xBufOff = t.xBufOff; + byteCount1 = t.byteCount1; + byteCount2 = t.byteCount2; + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + H6 = t.H6; + H7 = t.H7; + H8 = t.H8; + + System.arraycopy(t.W, 0, W, 0, t.W.length); + wOff = t.wOff; + } + + protected void populateState(byte[] state) + { + System.arraycopy(xBuf, 0, state, 0, xBufOff); + Pack.intToBigEndian(xBufOff, state, 8); + Pack.longToBigEndian(byteCount1, state, 12); + Pack.longToBigEndian(byteCount2, state, 20); + Pack.longToBigEndian(H1, state, 28); + Pack.longToBigEndian(H2, state, 36); + Pack.longToBigEndian(H3, state, 44); + Pack.longToBigEndian(H4, state, 52); + Pack.longToBigEndian(H5, state, 60); + Pack.longToBigEndian(H6, state, 68); + Pack.longToBigEndian(H7, state, 76); + Pack.longToBigEndian(H8, state, 84); + + Pack.intToBigEndian(wOff, state, 92); + for (int i = 0; i < wOff; i++) + { + Pack.longToBigEndian(W[i], state, 96 + (i * 8)); + } + } + + protected void restoreState(byte[] encodedState) + { + xBufOff = Pack.bigEndianToInt(encodedState, 8); + System.arraycopy(encodedState, 0, xBuf, 0, xBufOff); + byteCount1 = Pack.bigEndianToLong(encodedState, 12); + byteCount2 = Pack.bigEndianToLong(encodedState, 20); + + H1 = Pack.bigEndianToLong(encodedState, 28); + H2 = Pack.bigEndianToLong(encodedState, 36); + H3 = Pack.bigEndianToLong(encodedState, 44); + H4 = Pack.bigEndianToLong(encodedState, 52); + H5 = Pack.bigEndianToLong(encodedState, 60); + H6 = Pack.bigEndianToLong(encodedState, 68); + H7 = Pack.bigEndianToLong(encodedState, 76); + H8 = Pack.bigEndianToLong(encodedState, 84); + + wOff = Pack.bigEndianToInt(encodedState, 92); + for (int i = 0; i < wOff; i++) + { + W[i] = Pack.bigEndianToLong(encodedState, 96 + (i * 8)); + } + } + + protected int getEncodedStateSize() + { + return 96 + (wOff * 8); + } + + public void update( + byte in) + { + xBuf[xBufOff++] = in; + + if (xBufOff == xBuf.length) + { + processWord(xBuf, 0); + xBufOff = 0; + } + + byteCount1++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len >= xBuf.length) + { + processWord(in, inOff); + + inOff += xBuf.length; + len -= xBuf.length; + byteCount1 += xBuf.length; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + public void finish() + { + adjustByteCounts(); + + long lowBitLength = byteCount1 << 3; + long hiBitLength = byteCount2; + + // + // add the pad bytes. + // + update((byte)128); + + while (xBufOff != 0) + { + update((byte)0); + } + + processLength(lowBitLength, hiBitLength); + + processBlock(); + } + + public void reset() + { + byteCount1 = 0; + byteCount2 = 0; + + xBufOff = 0; + for (int i = 0; i < xBuf.length; i++) + { + xBuf[i] = 0; + } + + wOff = 0; + for (int i = 0; i != W.length; i++) + { + W[i] = 0; + } + } + + public int getByteLength() + { + return BYTE_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + W[wOff] = Pack.bigEndianToLong(in, inOff); + + if (++wOff == 16) + { + processBlock(); + } + } + + /** + * adjust the byte counts so that byteCount2 represents the + * upper long (less 3 bits) word of the byte count. + */ + private void adjustByteCounts() + { + if (byteCount1 > 0x1fffffffffffffffL) + { + byteCount2 += (byteCount1 >>> 61); + byteCount1 &= 0x1fffffffffffffffL; + } + } + + protected void processLength( + long lowW, + long hiW) + { + if (wOff > 14) + { + processBlock(); + } + + W[14] = hiW; + W[15] = lowW; + } + + protected void processBlock() + { + adjustByteCounts(); + + // + // expand 16 word block into 80 word blocks. + // + for (int t = 16; t <= 79; t++) + { + W[t] = Sigma1(W[t - 2]) + W[t - 7] + Sigma0(W[t - 15]) + W[t - 16]; + } + + // + // set up working variables. + // + long a = H1; + long b = H2; + long c = H3; + long d = H4; + long e = H5; + long f = H6; + long g = H7; + long h = H8; + + int t = 0; + for(int i = 0; i < 10; i ++) + { + // t = 8 * i + h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++]; + d += h; + h += Sum0(a) + Maj(a, b, c); + + // t = 8 * i + 1 + g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++]; + c += g; + g += Sum0(h) + Maj(h, a, b); + + // t = 8 * i + 2 + f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++]; + b += f; + f += Sum0(g) + Maj(g, h, a); + + // t = 8 * i + 3 + e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++]; + a += e; + e += Sum0(f) + Maj(f, g, h); + + // t = 8 * i + 4 + d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++]; + h += d; + d += Sum0(e) + Maj(e, f, g); + + // t = 8 * i + 5 + c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++]; + g += c; + c += Sum0(d) + Maj(d, e, f); + + // t = 8 * i + 6 + b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++]; + f += b; + b += Sum0(c) + Maj(c, d, e); + + // t = 8 * i + 7 + a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++]; + e += a; + a += Sum0(b) + Maj(b, c, d); + } + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + H5 += e; + H6 += f; + H7 += g; + H8 += h; + + // + // reset the offset and clean out the word buffer. + // + wOff = 0; + for (int i = 0; i < 16; i++) + { + W[i] = 0; + } + } + + /* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */ + private long Ch( + long x, + long y, + long z) + { + return ((x & y) ^ ((~x) & z)); + } + + private long Maj( + long x, + long y, + long z) + { + return ((x & y) ^ (x & z) ^ (y & z)); + } + + private long Sum0( + long x) + { + return ((x << 36)|(x >>> 28)) ^ ((x << 30)|(x >>> 34)) ^ ((x << 25)|(x >>> 39)); + } + + private long Sum1( + long x) + { + return ((x << 50)|(x >>> 14)) ^ ((x << 46)|(x >>> 18)) ^ ((x << 23)|(x >>> 41)); + } + + private long Sigma0( + long x) + { + return ((x << 63)|(x >>> 1)) ^ ((x << 56)|(x >>> 8)) ^ (x >>> 7); + } + + private long Sigma1( + long x) + { + return ((x << 45)|(x >>> 19)) ^ ((x << 3)|(x >>> 61)) ^ (x >>> 6); + } + + /* SHA-384 and SHA-512 Constants + * (represent the first 64 bits of the fractional parts of the + * cube roots of the first sixty-four prime numbers) + */ + static final long K[] = { +0x428a2f98d728ae22L, 0x7137449123ef65cdL, 0xb5c0fbcfec4d3b2fL, 0xe9b5dba58189dbbcL, +0x3956c25bf348b538L, 0x59f111f1b605d019L, 0x923f82a4af194f9bL, 0xab1c5ed5da6d8118L, +0xd807aa98a3030242L, 0x12835b0145706fbeL, 0x243185be4ee4b28cL, 0x550c7dc3d5ffb4e2L, +0x72be5d74f27b896fL, 0x80deb1fe3b1696b1L, 0x9bdc06a725c71235L, 0xc19bf174cf692694L, +0xe49b69c19ef14ad2L, 0xefbe4786384f25e3L, 0x0fc19dc68b8cd5b5L, 0x240ca1cc77ac9c65L, +0x2de92c6f592b0275L, 0x4a7484aa6ea6e483L, 0x5cb0a9dcbd41fbd4L, 0x76f988da831153b5L, +0x983e5152ee66dfabL, 0xa831c66d2db43210L, 0xb00327c898fb213fL, 0xbf597fc7beef0ee4L, +0xc6e00bf33da88fc2L, 0xd5a79147930aa725L, 0x06ca6351e003826fL, 0x142929670a0e6e70L, +0x27b70a8546d22ffcL, 0x2e1b21385c26c926L, 0x4d2c6dfc5ac42aedL, 0x53380d139d95b3dfL, +0x650a73548baf63deL, 0x766a0abb3c77b2a8L, 0x81c2c92e47edaee6L, 0x92722c851482353bL, +0xa2bfe8a14cf10364L, 0xa81a664bbc423001L, 0xc24b8b70d0f89791L, 0xc76c51a30654be30L, +0xd192e819d6ef5218L, 0xd69906245565a910L, 0xf40e35855771202aL, 0x106aa07032bbd1b8L, +0x19a4c116b8d2d0c8L, 0x1e376c085141ab53L, 0x2748774cdf8eeb99L, 0x34b0bcb5e19b48a8L, +0x391c0cb3c5c95a63L, 0x4ed8aa4ae3418acbL, 0x5b9cca4f7763e373L, 0x682e6ff3d6b2b8a3L, +0x748f82ee5defb2fcL, 0x78a5636f43172f60L, 0x84c87814a1f0ab72L, 0x8cc702081a6439ecL, +0x90befffa23631e28L, 0xa4506cebde82bde9L, 0xbef9a3f7b2c67915L, 0xc67178f2e372532bL, +0xca273eceea26619cL, 0xd186b8c721c0c207L, 0xeada7dd6cde0eb1eL, 0xf57d4f7fee6ed178L, +0x06f067aa72176fbaL, 0x0a637dc5a2c898a6L, 0x113f9804bef90daeL, 0x1b710b35131c471bL, +0x28db77f523047d84L, 0x32caab7b40c72493L, 0x3c9ebe0a15c9bebcL, 0x431d67c49c100d4cL, +0x4cc5d4becb3e42b6L, 0x597f299cfc657e2aL, 0x5fcb6fab3ad6faecL, 0x6c44198c4a475817L + }; + + protected abstract CryptoServiceProperties cryptoServiceProperties(); +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHA3Digest.java b/router/java/src/org/bouncycastle/crypto/digests/SHA3Digest.java new file mode 100644 index 0000000000..8c93fb0282 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/SHA3Digest.java @@ -0,0 +1,87 @@ +package org.bouncycastle.crypto.digests; + + +import org.bouncycastle.crypto.CryptoServicePurpose; + +/** + * implementation of SHA-3 based on following KeccakNISTInterface.c from https://keccak.noekeon.org/ + *

+ * Following the naming conventions used in the C source code to enable easy review of the implementation. + */ +public class SHA3Digest + extends KeccakDigest +{ + private static int checkBitLength(int bitLength) + { + switch (bitLength) + { + case 224: + case 256: + case 384: + case 512: + return bitLength; + default: + throw new IllegalArgumentException("'bitLength' " + bitLength + " not supported for SHA-3"); + } + } + + public SHA3Digest() + { + this(256, CryptoServicePurpose.ANY); + } + + public SHA3Digest(CryptoServicePurpose purpose) + { + this(256, purpose); + } + + public SHA3Digest(int bitLength) + { + super(checkBitLength(bitLength), CryptoServicePurpose.ANY); + } + + public SHA3Digest(int bitLength, CryptoServicePurpose purpose) + { + super(checkBitLength(bitLength), purpose); + } + + public SHA3Digest(SHA3Digest source) + { + super(source); + } + + public String getAlgorithmName() + { + return "SHA3-" + fixedOutputLength; + } + + public int doFinal(byte[] out, int outOff) + { + absorbBits(0x02, 2); + + return super.doFinal(out, outOff); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + { + throw new IllegalArgumentException("'partialBits' must be in the range [0,7]"); + } + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x02 << partialBits); + int finalBits = partialBits + 2; + + if (finalBits >= 8) + { + absorb((byte)finalInput); + finalBits -= 8; + finalInput >>>= 8; + } + + return super.doFinal(out, outOff, (byte)finalInput, finalBits); + } +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java b/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java new file mode 100644 index 0000000000..0fab188b98 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java @@ -0,0 +1,149 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.CryptoServiceProperties; +import org.bouncycastle.crypto.CryptoServicePurpose; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.util.Memoable; +import org.bouncycastle.util.Pack; + + +/** + * FIPS 180-2 implementation of SHA-512. + * + *

+ *         block  word  digest
+ * SHA-1   512    32    160
+ * SHA-256 512    32    256
+ * SHA-384 1024   64    384
+ * SHA-512 1024   64    512
+ * 
+ */ +public class SHA512Digest + extends LongDigest +{ + private static final int DIGEST_LENGTH = 64; + + /** + * Standard constructor + */ + public SHA512Digest() + { + this(CryptoServicePurpose.ANY); + } + + /** + * Standard constructor, with purpose + */ + public SHA512Digest(CryptoServicePurpose purpose) + { + super(purpose); + + CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties()); + + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA512Digest(SHA512Digest t) + { + super(t); + + CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties()); + } + + /** + * State constructor - create a digest initialised with the state of a previous one. + * + * @param encodedState the encoded state from the originating digest. + */ + public SHA512Digest(byte[] encodedState) + { + super(CryptoServicePurpose.values()[encodedState[encodedState.length - 1]]); + + restoreState(encodedState); + + CryptoServicesRegistrar.checkConstraints(cryptoServiceProperties()); + } + + public String getAlgorithmName() + { + return "SHA-512"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + Pack.longToBigEndian(H1, out, outOff); + Pack.longToBigEndian(H2, out, outOff + 8); + Pack.longToBigEndian(H3, out, outOff + 16); + Pack.longToBigEndian(H4, out, outOff + 24); + Pack.longToBigEndian(H5, out, outOff + 32); + Pack.longToBigEndian(H6, out, outOff + 40); + Pack.longToBigEndian(H7, out, outOff + 48); + Pack.longToBigEndian(H8, out, outOff + 56); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + /* SHA-512 initial hash value + * The first 64 bits of the fractional parts of the square roots + * of the first eight prime numbers + */ + H1 = 0x6a09e667f3bcc908L; + H2 = 0xbb67ae8584caa73bL; + H3 = 0x3c6ef372fe94f82bL; + H4 = 0xa54ff53a5f1d36f1L; + H5 = 0x510e527fade682d1L; + H6 = 0x9b05688c2b3e6c1fL; + H7 = 0x1f83d9abfb41bd6bL; + H8 = 0x5be0cd19137e2179L; + } + + public Memoable copy() + { + return new SHA512Digest(this); + } + + public void reset(Memoable other) + { + SHA512Digest d = (SHA512Digest)other; + + copyIn(d); + } + + public byte[] getEncodedState() + { + byte[] encoded = new byte[getEncodedStateSize() + 1]; + super.populateState(encoded); + + encoded[encoded.length - 1] = (byte)purpose.ordinal(); + + return encoded; + } + + protected CryptoServiceProperties cryptoServiceProperties() + { + return Utils.getDefaultProperties(this, 256, purpose); + } +} + diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java b/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java new file mode 100644 index 0000000000..4b30c0e150 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java @@ -0,0 +1,150 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.CryptoServiceProperties; +import org.bouncycastle.crypto.CryptoServicePurpose; +import org.bouncycastle.crypto.Xof; + + +/** + * implementation of SHAKE based on following KeccakNISTInterface.c from https://keccak.noekeon.org/ + *

+ * Following the naming conventions used in the C source code to enable easy review of the implementation. + */ +public class SHAKEDigest + extends KeccakDigest + implements Xof +{ + private static int checkBitLength(int bitStrength) + { + switch (bitStrength) + { + case 128: + case 256: + return bitStrength; + default: + throw new IllegalArgumentException("'bitStrength' " + bitStrength + " not supported for SHAKE"); + } + } + + public SHAKEDigest() + { + this(128); + } + + public SHAKEDigest(CryptoServicePurpose purpose) + { + this(128, purpose); + } + + /** + * Base constructor. + * + * @param bitStrength the security strength in bits of the XOF. + */ + public SHAKEDigest(int bitStrength) + { + super(checkBitLength(bitStrength), CryptoServicePurpose.ANY); + } + + /** + * Base constructor. + * + * @param bitStrength the security strength in bits of the XOF. + * @param purpose the purpose of the digest will be used for. + */ + public SHAKEDigest(int bitStrength, CryptoServicePurpose purpose) + { + super(checkBitLength(bitStrength), purpose); + } + + /** + * Clone constructor + * + * @param source the other digest to be copied. + */ + public SHAKEDigest(SHAKEDigest source) + { + super(source); + } + + public String getAlgorithmName() + { + return "SHAKE" + fixedOutputLength; + } + + public int getDigestSize() + { + return fixedOutputLength / 4; + } + + public int doFinal(byte[] out, int outOff) + { + return doFinal(out, outOff, getDigestSize()); + } + + public int doFinal(byte[] out, int outOff, int outLen) + { + int length = doOutput(out, outOff, outLen); + + reset(); + + return length; + } + + public int doOutput(byte[] out, int outOff, int outLen) + { + if (!squeezing) + { + absorbBits(0x0F, 4); + } + + squeeze(out, outOff, ((long)outLen) * 8); + + return outLen; + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits) + { + return doFinal(out, outOff, getDigestSize(), partialByte, partialBits); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected int doFinal(byte[] out, int outOff, int outLen, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + { + throw new IllegalArgumentException("'partialBits' must be in the range [0,7]"); + } + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x0F << partialBits); + int finalBits = partialBits + 4; + + if (finalBits >= 8) + { + absorb((byte)finalInput); + finalBits -= 8; + finalInput >>>= 8; + } + + if (finalBits > 0) + { + absorbBits(finalInput, finalBits); + } + + squeeze(out, outOff, ((long)outLen) * 8); + + reset(); + + return outLen; + } + + protected CryptoServiceProperties cryptoServiceProperties() + { + return Utils.getDefaultProperties(this, purpose); + } +} diff --git a/router/java/src/org/bouncycastle/crypto/digests/package-info.java b/router/java/src/org/bouncycastle/crypto/digests/package-info.java new file mode 100644 index 0000000000..a8e7db290d --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/digests/package-info.java @@ -0,0 +1,4 @@ +/** + * Message digest classes. + */ +package org.bouncycastle.crypto.digests; diff --git a/router/java/src/org/bouncycastle/crypto/package-info.java b/router/java/src/org/bouncycastle/crypto/package-info.java new file mode 100644 index 0000000000..73d7c3c6a3 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/package-info.java @@ -0,0 +1,4 @@ +/** + * Base classes for the lightweight API. + */ +package org.bouncycastle.crypto; diff --git a/router/java/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java b/router/java/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java new file mode 100644 index 0000000000..03ba7253b5 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java @@ -0,0 +1,20 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class AsymmetricKeyParameter + implements CipherParameters +{ + boolean privateKey; + + public AsymmetricKeyParameter( + boolean privateKey) + { + this.privateKey = privateKey; + } + + public boolean isPrivate() + { + return privateKey; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java b/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java new file mode 100644 index 0000000000..0f6a3c8524 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java @@ -0,0 +1,49 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.util.Arrays; + +public class ParametersWithContext + implements CipherParameters +{ + private CipherParameters parameters; + private byte[] context; + + public ParametersWithContext( + CipherParameters parameters, + byte[] context) + { + if (context == null) + { + throw new NullPointerException("'context' cannot be null"); + } + + this.parameters = parameters; + this.context = Arrays.clone(context); + } + + public void copyContextTo(byte[] buf, int off, int len) + { + if (context.length != len) + { + throw new IllegalArgumentException("len"); + } + + System.arraycopy(context, 0, buf, off, len); + } + + public byte[] getContext() + { + return Arrays.clone(context); + } + + public int getContextLength() + { + return context.length; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/params/ParametersWithRandom.java b/router/java/src/org/bouncycastle/crypto/params/ParametersWithRandom.java new file mode 100644 index 0000000000..9492f5a3ee --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/ParametersWithRandom.java @@ -0,0 +1,37 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoServicesRegistrar; + +public class ParametersWithRandom + implements CipherParameters +{ + private SecureRandom random; + private CipherParameters parameters; + + public ParametersWithRandom( + CipherParameters parameters, + SecureRandom random) + { + this.random = CryptoServicesRegistrar.getSecureRandom(random); + this.parameters = parameters; + } + + public ParametersWithRandom( + CipherParameters parameters) + { + this(parameters, null); + } + + public SecureRandom getRandom() + { + return random; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/router/java/src/org/bouncycastle/crypto/params/package-info.java b/router/java/src/org/bouncycastle/crypto/params/package-info.java new file mode 100644 index 0000000000..04fc776863 --- /dev/null +++ b/router/java/src/org/bouncycastle/crypto/params/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes for parameter objects for ciphers and generators. + */ +package org.bouncycastle.crypto.params; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/KEMParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/KEMParameters.java new file mode 100644 index 0000000000..48a1084475 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/KEMParameters.java @@ -0,0 +1,8 @@ +package org.bouncycastle.pqc.crypto; + +import org.bouncycastle.crypto.CipherParameters; + +public interface KEMParameters + extends CipherParameters +{ +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java new file mode 100644 index 0000000000..a7df3c7fc8 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/CBD.java @@ -0,0 +1,84 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +final class CBD +{ + + public static void mlkemCBD(Poly r, byte[] bytes, int eta) + { + long t, d; + int a, b; + + switch (eta) + { + case 3: + for (int i = 0; i < MLKEMEngine.KyberN / 4; i++) + { + t = convertByteTo24BitUnsignedInt(bytes, 3 * i); + d = t & 0x00249249; + d = d + ((t >> 1) & 0x00249249); + d = d + ((t >> 2) & 0x00249249); + for (int j = 0; j < 4; j++) + { + a = (short)((d >> (6 * j + 0)) & 0x7); + b = (short)((d >> (6 * j + 3)) & 0x7); + // System.out.printf("a = %d, b = %d\n", a, b); + r.setCoeffIndex(4 * i + j, (short)(a - b)); + } + } + break; + default: + // Only for Kyber512 where eta = 2 + for (int i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + t = convertByteTo32BitUnsignedInt(bytes, 4 * i); // ? Problem + d = t & 0x55555555; + d = d + ((t >> 1) & 0x55555555); + for (int j = 0; j < 8; j++) + { + a = (short)((d >> (4 * j + 0)) & 0x3); + b = (short)((d >> (4 * j + eta)) & 0x3); + r.setCoeffIndex(8 * i + j, (short)(a - b)); + } + } + } + } + + /** + * Converts an Array of Bytes to a 32-bit Unsigned Integer + * Returns a 32-bit unsigned integer as a long + * + * @param x + * @return + */ + private static long convertByteTo32BitUnsignedInt(byte[] x, int offset) + { + // Convert first byte to an unsigned integer + // byte x & 0xFF allows us to grab the last 8 bits + long r = (long)(x[offset] & 0xFF); + + // Perform the same operation then left bit shift to store the next 8 bits without + // altering the previous bits + r = r | (long)((long)(x[offset + 1] & 0xFF) << 8); + r = r | (long)((long)(x[offset + 2] & 0xFF) << 16); + r = r | (long)((long)(x[offset + 3] & 0xFF) << 24); + return r; + } + + /** + * Converts an Array of Bytes to a 24-bit Unsigned Integer + * Returns a 24-bit unsigned integer as a long from byte x + * + * @param x + * @return + */ + private static long convertByteTo24BitUnsignedInt(byte[] x, int offset) + { + // Refer to convertByteTo32-BitUnsignedInt for explanation + long r = (long)(x[offset] & 0xFF); + r = r | (long)((long)(x[offset + 1] & 0xFF) << 8); + r = r | (long)((long)(x[offset + 2] & 0xFF) << 16); + return r; + } + + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java new file mode 100644 index 0000000000..b41116e7a2 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java @@ -0,0 +1,326 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; + +import org.bouncycastle.util.Arrays; + +class MLKEMEngine +{ + private SecureRandom random; + private MLKEMIndCpa indCpa; + + // constant parameters + public final static int KyberN = 256; + public final static int KyberQ = 3329; + public final static int KyberQinv = 62209; + + public final static int KyberSymBytes = 32; // Number of bytes for Hashes and Seeds + private final static int KyberSharedSecretBytes = 32; // Number of Bytes for Shared Secret + + public final static int KyberPolyBytes = 384; + + private final static int KyberEta2 = 2; + + private final static int KyberIndCpaMsgBytes = KyberSymBytes; + + + // parameters for Kyber{k} + private final int KyberK; + private final int KyberPolyVecBytes; + private final int KyberPolyCompressedBytes; + private final int KyberPolyVecCompressedBytes; + private final int KyberEta1; + private final int KyberIndCpaPublicKeyBytes; + private final int KyberIndCpaSecretKeyBytes; + private final int KyberIndCpaBytes; + private final int KyberPublicKeyBytes; + private final int KyberSecretKeyBytes; + private final int KyberCipherTextBytes; + + // Crypto + private final int CryptoBytes; + private final int CryptoSecretKeyBytes; + private final int CryptoPublicKeyBytes; + private final int CryptoCipherTextBytes; + + private final int sessionKeyLength; + private final Symmetric symmetric; + + public Symmetric getSymmetric() + { + return symmetric; + } + public static int getKyberEta2() + { + return KyberEta2; + } + + public static int getKyberIndCpaMsgBytes() + { + return KyberIndCpaMsgBytes; + } + + public int getCryptoCipherTextBytes() + { + return CryptoCipherTextBytes; + } + + public int getCryptoPublicKeyBytes() + { + return CryptoPublicKeyBytes; + } + + public int getCryptoSecretKeyBytes() + { + return CryptoSecretKeyBytes; + } + + public int getCryptoBytes() + { + return CryptoBytes; + } + + public int getKyberCipherTextBytes() + { + return KyberCipherTextBytes; + } + + public int getKyberSecretKeyBytes() + { + return KyberSecretKeyBytes; + } + + public int getKyberIndCpaPublicKeyBytes() + { + return KyberIndCpaPublicKeyBytes; + } + + + public int getKyberIndCpaSecretKeyBytes() + { + return KyberIndCpaSecretKeyBytes; + } + + public int getKyberIndCpaBytes() + { + return KyberIndCpaBytes; + } + + public int getKyberPublicKeyBytes() + { + return KyberPublicKeyBytes; + } + + public int getKyberPolyCompressedBytes() + { + return KyberPolyCompressedBytes; + } + + public int getKyberK() + { + return KyberK; + } + + public int getKyberPolyVecBytes() + { + return KyberPolyVecBytes; + } + + public int getKyberPolyVecCompressedBytes() + { + return KyberPolyVecCompressedBytes; + } + + public int getKyberEta1() + { + return KyberEta1; + } + + public MLKEMEngine(int k) + { + this.KyberK = k; + switch (k) + { + case 2: + KyberEta1 = 3; + KyberPolyCompressedBytes = 128; + KyberPolyVecCompressedBytes = k * 320; + sessionKeyLength = 32; + break; + case 3: + KyberEta1 = 2; + KyberPolyCompressedBytes = 128; + KyberPolyVecCompressedBytes = k * 320; + sessionKeyLength = 32; + break; + case 4: + KyberEta1 = 2; + KyberPolyCompressedBytes = 160; + KyberPolyVecCompressedBytes = k * 352; + sessionKeyLength = 32; + break; + default: + throw new IllegalArgumentException("K: " + k + " is not supported for Crystals Kyber"); + } + + this.KyberPolyVecBytes = k * KyberPolyBytes; + this.KyberIndCpaPublicKeyBytes = KyberPolyVecBytes + KyberSymBytes; + this.KyberIndCpaSecretKeyBytes = KyberPolyVecBytes; + this.KyberIndCpaBytes = KyberPolyVecCompressedBytes + KyberPolyCompressedBytes; + this.KyberPublicKeyBytes = KyberIndCpaPublicKeyBytes; + this.KyberSecretKeyBytes = KyberIndCpaSecretKeyBytes + KyberIndCpaPublicKeyBytes + 2 * KyberSymBytes; + this.KyberCipherTextBytes = KyberIndCpaBytes; + + // Define Crypto Params + this.CryptoBytes = KyberSharedSecretBytes; + this.CryptoSecretKeyBytes = KyberSecretKeyBytes; + this.CryptoPublicKeyBytes = KyberPublicKeyBytes; + this.CryptoCipherTextBytes = KyberCipherTextBytes; + + this.symmetric = new Symmetric.ShakeSymmetric(); + + this.indCpa = new MLKEMIndCpa(this); + } + + public void init(SecureRandom random) + { + this.random = random; + } + + public byte[][] generateKemKeyPair() + { + byte[] d = new byte[KyberSymBytes]; + byte[] z = new byte[KyberSymBytes]; + random.nextBytes(d); + random.nextBytes(z); + + return generateKemKeyPairInternal(d, z); + } + + //Internal functions are deterministic. No randomness is sampled inside them + public byte[][] generateKemKeyPairInternal(byte[] d, byte[] z) + { + byte[][] indCpaKeyPair = indCpa.generateKeyPair(d); + + byte[] s = new byte[KyberIndCpaSecretKeyBytes]; + + System.arraycopy(indCpaKeyPair[1], 0, s, 0, KyberIndCpaSecretKeyBytes); + + byte[] hashedPublicKey = new byte[32]; + + symmetric.hash_h(hashedPublicKey, indCpaKeyPair[0], 0); + + byte[] outputPublicKey = new byte[KyberIndCpaPublicKeyBytes]; + System.arraycopy(indCpaKeyPair[0], 0, outputPublicKey, 0, KyberIndCpaPublicKeyBytes); + return new byte[][] + { + Arrays.copyOfRange(outputPublicKey, 0, outputPublicKey.length - 32), + Arrays.copyOfRange(outputPublicKey, outputPublicKey.length - 32, outputPublicKey.length), + s, + hashedPublicKey, + z, + Arrays.concatenate(d, z) + }; + } + + public byte[][] kemEncryptInternal(byte[] publicKeyInput, byte[] randBytes) + { + byte[] outputCipherText; + + byte[] buf = new byte[2 * KyberSymBytes]; + byte[] kr = new byte[2 * KyberSymBytes]; + + System.arraycopy(randBytes, 0, buf, 0, KyberSymBytes); + + // SHA3-256 Public Key + symmetric.hash_h(buf, publicKeyInput, KyberSymBytes); + + // SHA3-512( SHA3-256(RandBytes) || SHA3-256(PublicKey) ) + symmetric.hash_g(kr, buf); + + // IndCpa Encryption + outputCipherText = indCpa.encrypt(publicKeyInput, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, 32, kr.length)); + + byte[] outputSharedSecret = new byte[sessionKeyLength]; + + System.arraycopy(kr, 0, outputSharedSecret, 0, outputSharedSecret.length); + + byte[][] outBuf = new byte[2][]; + outBuf[0] = outputSharedSecret; + outBuf[1] = outputCipherText; + return outBuf; + } + + public byte[] kemDecryptInternal(byte[] secretKey, byte[] cipherText) + { + byte[] buf = new byte[2 * KyberSymBytes], + kr = new byte[2 * KyberSymBytes]; + + byte[] publicKey = Arrays.copyOfRange(secretKey, KyberIndCpaSecretKeyBytes, secretKey.length); + + System.arraycopy(indCpa.decrypt(secretKey, cipherText), 0, buf, 0, KyberSymBytes); + + System.arraycopy(secretKey, KyberSecretKeyBytes - 2 * KyberSymBytes, buf, KyberSymBytes, KyberSymBytes); + + symmetric.hash_g(kr, buf); + + byte[] implicit_rejection = new byte[KyberSymBytes + KyberCipherTextBytes]; + + System.arraycopy(secretKey, KyberSecretKeyBytes - KyberSymBytes, implicit_rejection, 0, KyberSymBytes); + + System.arraycopy(cipherText, 0, implicit_rejection, KyberSymBytes, KyberCipherTextBytes); + + symmetric.kdf(implicit_rejection, implicit_rejection ); // J(z||c) + + byte[] cmp = indCpa.encrypt(publicKey, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, KyberSymBytes, kr.length)); + + boolean fail = !(Arrays.constantTimeAreEqual(cipherText, cmp)); + + cmov(kr, implicit_rejection, KyberSymBytes, fail); + + return Arrays.copyOfRange(kr, 0, sessionKeyLength); + } + + public byte[][] kemEncrypt(byte[] publicKeyInput, byte[] randBytes) + { + //TODO: do input validation elsewhere? + // Input validation (6.2 ML-KEM Encaps) + // Type Check + if (publicKeyInput.length != KyberIndCpaPublicKeyBytes) + { + throw new IllegalArgumentException("Input validation Error: Type check failed for ml-kem encapsulation"); + } + // Modulus Check + PolyVec polyVec = new PolyVec(this); + byte[] seed = indCpa.unpackPublicKey(polyVec, publicKeyInput); + byte[] ek = indCpa.packPublicKey(polyVec, seed); + if (!Arrays.areEqual(ek, publicKeyInput)) + { + throw new IllegalArgumentException("Input validation: Modulus check failed for ml-kem encapsulation"); + } + + return kemEncryptInternal(publicKeyInput, randBytes); + } + public byte[] kemDecrypt(byte[] secretKey, byte[] cipherText) + { + //TODO: do input validation + return kemDecryptInternal(secretKey, cipherText); + } + + private void cmov(byte[] r, byte[] x, int xlen, boolean b) + { + if (b) + { + System.arraycopy(x, 0, r, 0, xlen); + } + else + { + System.arraycopy(r, 0, r, 0, xlen); + } + } + + public void getRandomBytes(byte[] buf) + { + this.random.nextBytes(buf); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java new file mode 100644 index 0000000000..6f19f835e6 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java @@ -0,0 +1,31 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.crypto.EncapsulatedSecretExtractor; + +public class MLKEMExtractor + implements EncapsulatedSecretExtractor +{ + private final MLKEMPrivateKeyParameters privateKey; + private final MLKEMEngine engine; + + public MLKEMExtractor(MLKEMPrivateKeyParameters privateKey) + { + if (privateKey == null) + { + throw new NullPointerException("'privateKey' cannot be null"); + } + + this.privateKey = privateKey; + this.engine = privateKey.getParameters().getEngine(); + } + + public byte[] extractSecret(byte[] encapsulation) + { + return engine.kemDecrypt(privateKey.getEncoded(), encapsulation); + } + + public int getEncapsulationLength() + { + return engine.getCryptoCipherTextBytes(); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java new file mode 100644 index 0000000000..61c693724d --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java @@ -0,0 +1,42 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.EncapsulatedSecretGenerator; +import org.bouncycastle.crypto.SecretWithEncapsulation; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.pqc.crypto.util.SecretWithEncapsulationImpl; + +public class MLKEMGenerator + implements EncapsulatedSecretGenerator +{ + // the source of randomness + private final SecureRandom sr; + + public MLKEMGenerator(SecureRandom random) + { + this.sr = random; + } + + public SecretWithEncapsulation generateEncapsulated(AsymmetricKeyParameter recipientKey) + { + MLKEMPublicKeyParameters key = (MLKEMPublicKeyParameters)recipientKey; + MLKEMEngine engine = key.getParameters().getEngine(); + engine.init(sr); + + byte[] randBytes = new byte[32]; + engine.getRandomBytes(randBytes); + + byte[][] kemEncrypt = engine.kemEncrypt(key.getEncoded(), randBytes); + return new SecretWithEncapsulationImpl(kemEncrypt[0], kemEncrypt[1]); + } + public SecretWithEncapsulation internalGenerateEncapsulated(AsymmetricKeyParameter recipientKey, byte[] randBytes) + { + MLKEMPublicKeyParameters key = (MLKEMPublicKeyParameters)recipientKey; + MLKEMEngine engine = key.getParameters().getEngine(); + engine.init(sr); + + byte[][] kemEncrypt = engine.kemEncryptInternal(key.getEncoded(), randBytes); + return new SecretWithEncapsulationImpl(kemEncrypt[0], kemEncrypt[1]); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java new file mode 100644 index 0000000000..c5d7527c33 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java @@ -0,0 +1,444 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.util.Arrays; + +class MLKEMIndCpa +{ + private MLKEMEngine engine; + private int kyberK; + private int indCpaPublicKeyBytes; + private int polyVecBytes; + private int indCpaBytes; + private int polyVecCompressedBytes; + private int polyCompressedBytes; + + private Symmetric symmetric; + + public MLKEMIndCpa(MLKEMEngine engine) + { + this.engine = engine; + this.kyberK = engine.getKyberK(); + this.indCpaPublicKeyBytes = engine.getKyberPublicKeyBytes(); + this.polyVecBytes = engine.getKyberPolyVecBytes(); + this.indCpaBytes = engine.getKyberIndCpaBytes(); + this.polyVecCompressedBytes = engine.getKyberPolyVecCompressedBytes(); + this.polyCompressedBytes = engine.getKyberPolyCompressedBytes(); + this.symmetric = engine.getSymmetric(); + + KyberGenerateMatrixNBlocks = + ( + ( + 12 * MLKEMEngine.KyberN + / 8 * (1 << 12) + / MLKEMEngine.KyberQ + symmetric.xofBlockBytes + ) + / symmetric.xofBlockBytes + ); + } + + + /** + * Generates IndCpa Key Pair + * + * @return KeyPair where each key is represented as bytes + */ + byte[][] generateKeyPair(byte[] d) + { + PolyVec secretKey = new PolyVec(engine), + publicKey = new PolyVec(engine), + e = new PolyVec(engine); + + // (p, sigma) <- G(d || k) + + byte[] buf = new byte[64]; + symmetric.hash_g(buf, Arrays.append(d, (byte)kyberK)); + + byte[] publicSeed = new byte[32]; // p in docs + byte[] noiseSeed = new byte[32]; // sigma in docs + System.arraycopy(buf, 0, publicSeed, 0, 32); + System.arraycopy(buf, 32, noiseSeed, 0, 32); + + byte count = (byte)0; + + // Helper.printByteArray(buf); + + + PolyVec[] aMatrix = new PolyVec[kyberK]; + + int i; + for (i = 0; i < kyberK; i++) + { + aMatrix[i] = new PolyVec(engine); + } + + generateMatrix(aMatrix, publicSeed, false); + + // System.out.println("aMatrix = "); + // for(i = 0; i < kyberK; i++) { + // System.out.print("["); + // for (int j = 0; j < kyberK; j++) { + // System.out.print("["); + // for (int k = 0; k < KyberEngine.KyberN; k++) { + // System.out.printf("%d ,", aMatrix[i].getVectorIndex(j).getCoeffIndex(k)); + // } + // System.out.print("], \n"); + // } + // System.out.print("]\n"); + // } + + for (i = 0; i < kyberK; i++) + { + secretKey.getVectorIndex(i).getEta1Noise(noiseSeed, count); + + // System.out.print("SecretKeyPolyVec["+i+"] = ["); + // for (int j =0; j < KyberEngine.KyberN; j++) { + // System.out.print(secretKey.getVectorIndex(i).getCoeffIndex(j) + ", "); + // } + // System.out.println("]"); + count = (byte)(count + (byte)1); + } + + for (i = 0; i < kyberK; i++) + { + e.getVectorIndex(i).getEta1Noise(noiseSeed, count); + count = (byte)(count + (byte)1); + } + + secretKey.polyVecNtt(); + + // System.out.print("SecretKeyPolyVec = ["); + // for (i = 0; i < kyberK; i++) { + // System.out.print("["); + // for (int j =0; j < KyberEngine.KyberN; j++) { + // System.out.print(secretKey.getVectorIndex(i).getCoeffIndex(j) + ", "); + // } + // System.out.println("],"); + // } + // System.out.println("]"); + + + e.polyVecNtt(); + + for (i = 0; i < kyberK; i++) + { + PolyVec.pointwiseAccountMontgomery(publicKey.getVectorIndex(i), aMatrix[i], secretKey, engine); + publicKey.getVectorIndex(i).convertToMont(); + } + + // System.out.print("PublicKey PolyVec = ["); + // Helper.printPolyVec(publicKey, kyberK); + + publicKey.addPoly(e); + publicKey.reducePoly(); + + return new byte[][]{packPublicKey(publicKey, publicSeed), packSecretKey(secretKey)}; + } + + public byte[] encrypt(byte[] publicKeyInput, byte[] msg, byte[] coins) + { + int i; + byte[] seed; + byte nonce = (byte)0; + PolyVec sp = new PolyVec(engine), + publicKeyPolyVec = new PolyVec(engine), + errorPolyVector = new PolyVec(engine), + bp = new PolyVec(engine); + PolyVec[] aMatrixTranspose = new PolyVec[engine.getKyberK()]; + Poly errorPoly = new Poly(engine), + v = new Poly(engine), + k = new Poly(engine); + + + // System.out.print("publickeyinput = "); + // Helper.printByteArray(publicKeyInput); + // System.out.println(); + + seed = unpackPublicKey(publicKeyPolyVec, publicKeyInput); + + // System.out.print("publickeyPolyVec = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(publicKeyPolyVec.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + // System.out.print("seed = "); + // Helper.printByteArray(seed); + // System.out.println(); + + k.fromMsg(msg); + + for (i = 0; i < kyberK; i++) + { + aMatrixTranspose[i] = new PolyVec(engine); + } + + generateMatrix(aMatrixTranspose, seed, true); + + // System.out.print("matrix transposed = "); + // for (i = 0; i < kyberK; i++) { + // System.out.print("["); + // for(int j = 0; j < kyberK; j++) { + // System.out.print("["); + // for (int l = 0; l < 256; l++) { + // System.out.printf("%d ,", aMatrixTranspose[i].getVectorIndex(j).getCoeffIndex(l)); + // } + // System.out.print("] ,\n"); + // } + // System.out.println("] ,"); + // } + + + for (i = 0; i < kyberK; i++) + { + sp.getVectorIndex(i).getEta1Noise(coins, nonce); + nonce = (byte)(nonce + (byte)1); + } + + + for (i = 0; i < kyberK; i++) + { + errorPolyVector.getVectorIndex(i).getEta2Noise(coins, nonce); + nonce = (byte)(nonce + (byte)1); + } + errorPoly.getEta2Noise(coins, nonce); + + sp.polyVecNtt(); + + // System.out.print("sp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(sp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + + // System.out.print("sp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(sp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + for (i = 0; i < kyberK; i++) + { + + PolyVec.pointwiseAccountMontgomery(bp.getVectorIndex(i), aMatrixTranspose[i], sp, engine); + } + // System.out.print("bp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + PolyVec.pointwiseAccountMontgomery(v, publicKeyPolyVec, sp, engine); + + bp.polyVecInverseNttToMont(); + + v.polyInverseNttToMont(); + + bp.addPoly(errorPolyVector); + + + v.addCoeffs(errorPoly); + v.addCoeffs(k); + + bp.reducePoly(); + v.reduce(); + + // System.out.print("bp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + + // System.out.print("v = "); + // Helper.printShortArray(v.getCoeffs()); + // System.out.println(); + + + byte[] outputCipherText = packCipherText(bp, v); + + return outputCipherText; + } + + private byte[] packCipherText(PolyVec b, Poly v) + { + byte[] outBuf = new byte[indCpaBytes]; + System.arraycopy(b.compressPolyVec(), 0, outBuf, 0, polyVecCompressedBytes); + System.arraycopy(v.compressPoly(), 0, outBuf, polyVecCompressedBytes, polyCompressedBytes); + // System.out.print("outBuf = ["); + // Helper.printByteArray(outBuf); + return outBuf; + } + + private void unpackCipherText(PolyVec b, Poly v, byte[] cipherText) + { + byte[] compressedPolyVecCipherText = Arrays.copyOfRange(cipherText, 0, engine.getKyberPolyVecCompressedBytes()); + b.decompressPolyVec(compressedPolyVecCipherText); + + byte[] compressedPolyCipherText = Arrays.copyOfRange(cipherText, engine.getKyberPolyVecCompressedBytes(), cipherText.length); + v.decompressPoly(compressedPolyCipherText); + } + + public byte[] packPublicKey(PolyVec publicKeyPolyVec, byte[] seed) + { + byte[] buf = new byte[indCpaPublicKeyBytes]; + System.arraycopy(publicKeyPolyVec.toBytes(), 0, buf, 0, polyVecBytes); + System.arraycopy(seed, 0, buf, polyVecBytes, MLKEMEngine.KyberSymBytes); + return buf; + } + + public byte[] unpackPublicKey(PolyVec publicKeyPolyVec, byte[] publicKey) + { + byte[] outputSeed = new byte[MLKEMEngine.KyberSymBytes]; + publicKeyPolyVec.fromBytes(publicKey); + System.arraycopy(publicKey, polyVecBytes, outputSeed, 0, MLKEMEngine.KyberSymBytes); + return outputSeed; + } + + public byte[] packSecretKey(PolyVec secretKeyPolyVec) + { + return secretKeyPolyVec.toBytes(); + } + + public void unpackSecretKey(PolyVec secretKeyPolyVec, byte[] secretKey) + { + secretKeyPolyVec.fromBytes(secretKey); + } + + public final int KyberGenerateMatrixNBlocks; + + public void generateMatrix(PolyVec[] aMatrix, byte[] seed, boolean transposed) + { + int i, j, k, ctr, off; + byte[] buf = new byte[KyberGenerateMatrixNBlocks * symmetric.xofBlockBytes + 2]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < kyberK; j++) + { + if (transposed) + { + symmetric.xofAbsorb(seed, (byte) i, (byte) j); + } + else + { + symmetric.xofAbsorb(seed, (byte) j, (byte) i); + } + symmetric.xofSqueezeBlocks(buf, 0, symmetric.xofBlockBytes * KyberGenerateMatrixNBlocks); + + int buflen = KyberGenerateMatrixNBlocks * symmetric.xofBlockBytes; + ctr = rejectionSampling(aMatrix[i].getVectorIndex(j), 0, MLKEMEngine.KyberN, buf, buflen); + + while (ctr < MLKEMEngine.KyberN) + { + off = buflen % 3; + for (k = 0; k < off; k++) + { + buf[k] = buf[buflen - off + k]; + } + symmetric.xofSqueezeBlocks(buf, off, symmetric.xofBlockBytes * 2); + buflen = off + symmetric.xofBlockBytes; + // Error in code Section Unsure + ctr += rejectionSampling(aMatrix[i].getVectorIndex(j), ctr, MLKEMEngine.KyberN - ctr, buf, buflen); + } + } + } + + } + + private static int rejectionSampling(Poly outputBuffer, int coeffOff, int len, byte[] inpBuf, int inpBufLen) + { + int ctr, pos; + short val0, val1; + ctr = pos = 0; + while (ctr < len && pos + 3 <= inpBufLen) + { + val0 = (short)(((((short)(inpBuf[pos] & 0xFF)) >> 0) | (((short)(inpBuf[pos + 1] & 0xFF)) << 8)) & 0xFFF); + val1 = (short)(((((short)(inpBuf[pos + 1] & 0xFF)) >> 4) | (((short)(inpBuf[pos + 2] & 0xFF)) << 4)) & 0xFFF); + pos = pos + 3; + if (val0 < (short)MLKEMEngine.KyberQ) + { + outputBuffer.setCoeffIndex(coeffOff + ctr, (short)val0); + ctr++; + } + if (ctr < len && val1 < (short)MLKEMEngine.KyberQ) + { + outputBuffer.setCoeffIndex(coeffOff + ctr, (short)val1); + ctr++; + } + } + return ctr; + + } + + public byte[] decrypt(byte[] secretKey, byte[] cipherText) + { + byte[] outputMessage = new byte[MLKEMEngine.getKyberIndCpaMsgBytes()]; + + PolyVec bp = new PolyVec(engine), secretKeyPolyVec = new PolyVec(engine); + Poly v = new Poly(engine), mp = new Poly(engine); + + unpackCipherText(bp, v, cipherText); + + // System.out.print("bp = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + + // System.out.print("v = "); + // Helper.printShortArray(v.getCoeffs()); + // System.out.println(); + + unpackSecretKey(secretKeyPolyVec, secretKey); + + // System.out.print("decrypt secretkey = ");; + // Helper.printByteArray(secretKey); + + // System.out.print("SecretKeyPolyVec = ["); + // for (i = 0; i < kyberK; i++) { + // System.out.print("["); + // for (int j =0; j < KyberEngine.KyberN; j++) { + // System.out.print(secretKeyPolyVec.getVectorIndex(i).getCoeffIndex(j) + ", "); + // } + // System.out.println("],"); + // } + // System.out.println("]"); + + // System.out.print("bp before ntt = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + bp.polyVecNtt(); + + // System.out.print("bp after ntt = ["); + // for (i = 0; i < kyberK; i++) { + // Helper.printShortArray(bp.getVectorIndex(i).getCoeffs()); + // System.out.print("], \n"); + // } + // System.out.println("]"); + + PolyVec.pointwiseAccountMontgomery(mp, secretKeyPolyVec, bp, engine); + + + mp.polyInverseNttToMont(); + + mp.polySubtract(v); + + mp.reduce(); + + outputMessage = mp.toMsg(); + + return outputMessage; + } + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyGenerationParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyGenerationParameters.java new file mode 100644 index 0000000000..7d92602345 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyGenerationParameters.java @@ -0,0 +1,24 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class MLKEMKeyGenerationParameters + extends KeyGenerationParameters +{ + private final MLKEMParameters params; + + public MLKEMKeyGenerationParameters( + SecureRandom random, + MLKEMParameters mlkemParameters) + { + super(random, 256); + this.params = mlkemParameters; + } + + public MLKEMParameters getParameters() + { + return params; + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java new file mode 100644 index 0000000000..22c1d686db --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java @@ -0,0 +1,57 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.AsymmetricCipherKeyPair; +import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; +import org.bouncycastle.crypto.KeyGenerationParameters; + +public class MLKEMKeyPairGenerator + implements AsymmetricCipherKeyPairGenerator +{ + private MLKEMParameters mlkemParams; + + private SecureRandom random; + + private void initialize( + KeyGenerationParameters param) + { + this.mlkemParams = ((MLKEMKeyGenerationParameters)param).getParameters(); + this.random = param.getRandom(); + + } + + private AsymmetricCipherKeyPair genKeyPair() + { + MLKEMEngine engine = mlkemParams.getEngine(); + + engine.init(random); + + byte[][] keyPair = engine.generateKemKeyPair(); + + MLKEMPublicKeyParameters pubKey = new MLKEMPublicKeyParameters(mlkemParams, keyPair[0], keyPair[1]); + MLKEMPrivateKeyParameters privKey = new MLKEMPrivateKeyParameters(mlkemParams, keyPair[2], keyPair[3], keyPair[4], keyPair[0], keyPair[1], keyPair[5]); + + return new AsymmetricCipherKeyPair(pubKey, privKey); + } + + public void init(KeyGenerationParameters param) + { + this.initialize(param); + } + + public AsymmetricCipherKeyPair generateKeyPair() + { + return genKeyPair(); + } + + public AsymmetricCipherKeyPair internalGenerateKeyPair(byte[] d, byte[] z) + { + byte[][] keyPair = mlkemParams.getEngine().generateKemKeyPairInternal(d, z); + + MLKEMPublicKeyParameters pubKey = new MLKEMPublicKeyParameters(mlkemParams, keyPair[0], keyPair[1]); + MLKEMPrivateKeyParameters privKey = new MLKEMPrivateKeyParameters(mlkemParams, keyPair[2], keyPair[3], keyPair[4], keyPair[0], keyPair[1], keyPair[5]); + + return new AsymmetricCipherKeyPair(pubKey, privKey); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyParameters.java new file mode 100644 index 0000000000..227a46c65a --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyParameters.java @@ -0,0 +1,23 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +public class MLKEMKeyParameters + extends AsymmetricKeyParameter +{ + private MLKEMParameters params; + + public MLKEMKeyParameters( + boolean isPrivate, + MLKEMParameters params) + { + super(isPrivate); + this.params = params; + } + + public MLKEMParameters getParameters() + { + return params; + } + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMParameters.java new file mode 100644 index 0000000000..0dbc69bece --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMParameters.java @@ -0,0 +1,37 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.pqc.crypto.KEMParameters; + +public class MLKEMParameters + implements KEMParameters +{ + public static final MLKEMParameters ml_kem_512 = new MLKEMParameters("ML-KEM-512", 2, 256); + public static final MLKEMParameters ml_kem_768 = new MLKEMParameters("ML-KEM-768", 3, 256); + public static final MLKEMParameters ml_kem_1024 = new MLKEMParameters("ML-KEM-1024", 4, 256); + + private final String name; + private final int k; + private final int sessionKeySize; + + private MLKEMParameters(String name, int k, int sessionKeySize) + { + this.name = name; + this.k = k; + this.sessionKeySize = sessionKeySize; + } + + public String getName() + { + return name; + } + + public MLKEMEngine getEngine() + { + return new MLKEMEngine(k); + } + + public int getSessionKeySize() + { + return sessionKeySize; + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java new file mode 100644 index 0000000000..b59198a6f1 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java @@ -0,0 +1,109 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.util.Arrays; + +public class MLKEMPrivateKeyParameters + extends MLKEMKeyParameters +{ + final byte[] s; + final byte[] hpk; + final byte[] nonce; + final byte[] t; + final byte[] rho; + final byte[] seed; + + public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho) + { + this(params, s, hpk, nonce, t, rho, null); + } + + public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] s, byte[] hpk, byte[] nonce, byte[] t, byte[] rho, byte[] seed) + { + super(true, params); + + this.s = Arrays.clone(s); + this.hpk = Arrays.clone(hpk); + this.nonce = Arrays.clone(nonce); + this.t = Arrays.clone(t); + this.rho = Arrays.clone(rho); + this.seed = Arrays.clone(seed); + } + + public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] encoding) + { + super(true, params); + + MLKEMEngine eng = params.getEngine(); + if (encoding.length == MLKEMEngine.KyberSymBytes * 2) + { + byte[][] keyData = eng.generateKemKeyPairInternal( + Arrays.copyOfRange(encoding, 0, MLKEMEngine.KyberSymBytes), + Arrays.copyOfRange(encoding, MLKEMEngine.KyberSymBytes, encoding.length)); + this.s = keyData[2]; + this.hpk = keyData[3]; + this.nonce = keyData[4]; + this.t = keyData[0]; + this.rho = keyData[1]; + this.seed = keyData[5]; + } + else + { + int index = 0; + this.s = Arrays.copyOfRange(encoding, 0, eng.getKyberIndCpaSecretKeyBytes()); + index += eng.getKyberIndCpaSecretKeyBytes(); + this.t = Arrays.copyOfRange(encoding, index, index + eng.getKyberIndCpaPublicKeyBytes() - MLKEMEngine.KyberSymBytes); + index += eng.getKyberIndCpaPublicKeyBytes() - MLKEMEngine.KyberSymBytes; + this.rho = Arrays.copyOfRange(encoding, index, index + 32); + index += 32; + this.hpk = Arrays.copyOfRange(encoding, index, index + 32); + index += 32; + this.nonce = Arrays.copyOfRange(encoding, index, index + MLKEMEngine.KyberSymBytes); + this.seed = null; + } + } + + public byte[] getEncoded() + { + return Arrays.concatenate(new byte[][]{ s, t, rho, hpk, nonce }); + } + + public byte[] getHPK() + { + return Arrays.clone(hpk); + } + + public byte[] getNonce() + { + return Arrays.clone(nonce); + } + + public byte[] getPublicKey() + { + return MLKEMPublicKeyParameters.getEncoded(t, rho); + } + + public MLKEMPublicKeyParameters getPublicKeyParameters() + { + return new MLKEMPublicKeyParameters(getParameters(), t, rho); + } + + public byte[] getRho() + { + return Arrays.clone(rho); + } + + public byte[] getS() + { + return Arrays.clone(s); + } + + public byte[] getT() + { + return Arrays.clone(t); + } + + public byte[] getSeed() + { + return Arrays.clone(seed); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java new file mode 100644 index 0000000000..5f2676d730 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java @@ -0,0 +1,44 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.util.Arrays; + +public class MLKEMPublicKeyParameters + extends MLKEMKeyParameters +{ + static byte[] getEncoded(byte[] t, byte[] rho) + { + return Arrays.concatenate(t, rho); + } + + final byte[] t; + final byte[] rho; + + public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] t, byte[] rho) + { + super(false, params); + this.t = Arrays.clone(t); + this.rho = Arrays.clone(rho); + } + + public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] encoding) + { + super(false, params); + this.t = Arrays.copyOfRange(encoding, 0, encoding.length - MLKEMEngine.KyberSymBytes); + this.rho = Arrays.copyOfRange(encoding, encoding.length - MLKEMEngine.KyberSymBytes, encoding.length); + } + + public byte[] getEncoded() + { + return getEncoded(t, rho); + } + + public byte[] getRho() + { + return Arrays.clone(rho); + } + + public byte[] getT() + { + return Arrays.clone(t); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java new file mode 100644 index 0000000000..100647de08 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Ntt.java @@ -0,0 +1,100 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +class Ntt +{ + + public static final short[] nttZetas = new short[]{ + 2285, 2571, 2970, 1812, 1493, 1422, 287, 202, 3158, 622, 1577, 182, 962, + 2127, 1855, 1468, 573, 2004, 264, 383, 2500, 1458, 1727, 3199, 2648, 1017, + 732, 608, 1787, 411, 3124, 1758, 1223, 652, 2777, 1015, 2036, 1491, 3047, + 1785, 516, 3321, 3009, 2663, 1711, 2167, 126, 1469, 2476, 3239, 3058, 830, + 107, 1908, 3082, 2378, 2931, 961, 1821, 2604, 448, 2264, 677, 2054, 2226, + 430, 555, 843, 2078, 871, 1550, 105, 422, 587, 177, 3094, 3038, 2869, 1574, + 1653, 3083, 778, 1159, 3182, 2552, 1483, 2727, 1119, 1739, 644, 2457, 349, + 418, 329, 3173, 3254, 817, 1097, 603, 610, 1322, 2044, 1864, 384, 2114, 3193, + 1218, 1994, 2455, 220, 2142, 1670, 2144, 1799, 2051, 794, 1819, 2475, 2459, + 478, 3221, 3021, 996, 991, 958, 1869, 1522, 1628}; + + public static final short[] nttZetasInv = new short[]{ + 1701, 1807, 1460, 2371, 2338, 2333, 308, 108, 2851, 870, 854, 1510, 2535, + 1278, 1530, 1185, 1659, 1187, 3109, 874, 1335, 2111, 136, 1215, 2945, 1465, + 1285, 2007, 2719, 2726, 2232, 2512, 75, 156, 3000, 2911, 2980, 872, 2685, + 1590, 2210, 602, 1846, 777, 147, 2170, 2551, 246, 1676, 1755, 460, 291, 235, + 3152, 2742, 2907, 3224, 1779, 2458, 1251, 2486, 2774, 2899, 1103, 1275, 2652, + 1065, 2881, 725, 1508, 2368, 398, 951, 247, 1421, 3222, 2499, 271, 90, 853, + 1860, 3203, 1162, 1618, 666, 320, 8, 2813, 1544, 282, 1838, 1293, 2314, 552, + 2677, 2106, 1571, 205, 2918, 1542, 2721, 2597, 2312, 681, 130, 1602, 1871, + 829, 2946, 3065, 1325, 2756, 1861, 1474, 1202, 2367, 3147, 1752, 2707, 171, + 3127, 3042, 1907, 1836, 1517, 359, 758, 1441}; + + public static short[] ntt(short[] inp) + { + short[] r = new short[MLKEMEngine.KyberN]; + System.arraycopy(inp, 0, r, 0, r.length); + int len, start, j, k; + short t, zeta; + + k = 1; + for (len = 128; len >= 2; len >>= 1) + { + for (start = 0; start < 256; start = j + len) + { + zeta = nttZetas[k++]; + for (j = start; j < start + len; ++j) + { + t = factorQMulMont(zeta, r[j + len]); + r[j + len] = (short)(r[j] - t); + r[j] = (short)(r[j] + t); + } + } + } + return r; + } + + public static short[] invNtt(short[] inp) + { + short[] r = new short[MLKEMEngine.KyberN]; + System.arraycopy(inp, 0, r, 0, MLKEMEngine.KyberN); + int len, start, j, k; + short t, zeta; + k = 0; + for (len = 2; len <= 128; len <<= 1) + { + for (start = 0; start < 256; start = j + len) + { + zeta = nttZetasInv[k++]; + for (j = start; j < start + len; ++j) + { + t = r[j]; + r[j] = Reduce.barretReduce((short)(t + r[j + len])); + r[j + len] = (short)(t - r[j + len]); + r[j + len] = factorQMulMont(zeta, r[j + len]); + + } + } + } + + for (j = 0; j < 256; ++j) + { + r[j] = factorQMulMont(r[j], Ntt.nttZetasInv[127]); + } + return r; + } + + public static short factorQMulMont(short a, short b) + { + return Reduce.montgomeryReduce((int)(a * b)); + } + + public static void baseMult(Poly outPoly, int outIndex, short a0, short a1, short b0, short b1, short zeta) + { + short outVal0 = factorQMulMont(a1, b1); + outVal0 = factorQMulMont(outVal0, zeta); + outVal0 += factorQMulMont(a0, b0); + outPoly.setCoeffIndex(outIndex, outVal0); + + short outVal1 = factorQMulMont(a0, b1); + outVal1 += factorQMulMont(a1, b0); + outPoly.setCoeffIndex(outIndex + 1, outVal1); + } +} \ No newline at end of file diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java new file mode 100644 index 0000000000..d7402a5ea7 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Poly.java @@ -0,0 +1,354 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +class Poly +{ + private short[] coeffs; + private MLKEMEngine engine; + private int polyCompressedBytes; + private int eta1; + private int eta2; + + private Symmetric symmetric; + + public Poly(MLKEMEngine engine) + { + this.coeffs = new short[MLKEMEngine.KyberN]; + this.engine = engine; + polyCompressedBytes = engine.getKyberPolyCompressedBytes(); + this.eta1 = engine.getKyberEta1(); + this.eta2 = MLKEMEngine.getKyberEta2(); + this.symmetric = engine.getSymmetric(); + } + + public short getCoeffIndex(int i) + { + return this.coeffs[i]; + } + + public short[] getCoeffs() + { + return this.coeffs; + } + + public void setCoeffIndex(int i, short val) + { + this.coeffs[i] = val; + } + + public void setCoeffs(short[] coeffs) + { + this.coeffs = coeffs; + } + + public void polyNtt() + { + this.setCoeffs(Ntt.ntt(this.getCoeffs())); + this.reduce(); + } + + public void polyInverseNttToMont() + { + this.setCoeffs(Ntt.invNtt(this.getCoeffs())); + } + + public void reduce() + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, Reduce.barretReduce(this.getCoeffIndex(i))); + } + } + + public static void baseMultMontgomery(Poly r, Poly a, Poly b) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN / 4; i++) + { + Ntt.baseMult(r, 4 * i, + a.getCoeffIndex(4 * i), a.getCoeffIndex(4 * i + 1), + b.getCoeffIndex(4 * i), b.getCoeffIndex(4 * i + 1), + Ntt.nttZetas[64 + i]); + Ntt.baseMult(r, 4 * i + 2, + a.getCoeffIndex(4 * i + 2), a.getCoeffIndex(4 * i + 3), + b.getCoeffIndex(4 * i + 2), b.getCoeffIndex(4 * i + 3), + (short)(-1 * Ntt.nttZetas[64 + i])); + } + } + + public void addCoeffs(Poly b) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, (short)(this.getCoeffIndex(i) + b.getCoeffIndex(i))); + } + } + + public void convertToMont() + { + int i; + final short f = (short)(((long)1 << 32) % MLKEMEngine.KyberQ); + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, Reduce.montgomeryReduce(this.getCoeffIndex(i) * f)); + } + } + + public byte[] compressPoly() + { + int i, j; + byte[] t = new byte[8]; + byte[] r = new byte[polyCompressedBytes]; + int count = 0; + this.conditionalSubQ(); + + // System.out.print("v = ["); + // Helper.printShortArray(this.coeffs); + // System.out.print("]\n"); + + if (polyCompressedBytes == 128) + { + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + for (j = 0; j < 8; j++) + { + /*t[j] = + (byte)((((((short)this.getCoeffIndex(8 * i + j)) << 4) + + + (KyberEngine.KyberQ / 2) + ) / KyberEngine.KyberQ) + & 15);*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + int t_j = this.getCoeffIndex(8 * i + j); + t_j <<= 4; + t_j += 1665; + t_j *= 80635; + t_j >>= 28; + t_j &= 15; + t[j] = (byte)t_j; + } + + r[count + 0] = (byte)(t[0] | (t[1] << 4)); + r[count + 1] = (byte)(t[2] | (t[3] << 4)); + r[count + 2] = (byte)(t[4] | (t[5] << 4)); + r[count + 3] = (byte)(t[6] | (t[7] << 4)); + count += 4; + } + } + else if (polyCompressedBytes == 160) + { + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + for (j = 0; j < 8; j++) + { + /*t[j] = + (byte)(((((this.getCoeffIndex(8 * i + j) << 5)) + + + (KyberEngine.KyberQ / 2) + ) / KyberEngine.KyberQ + ) & 31 + );*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + int t_j = this.getCoeffIndex(8 * i + j); + t_j <<= 5; + t_j += 1664; + t_j *= 40318; + t_j >>= 27; + t_j &= 31; + t[j] = (byte)t_j; + } + r[count + 0] = (byte)((t[0] >> 0) | (t[1] << 5)); + r[count + 1] = (byte)((t[1] >> 3) | (t[2] << 2) | (t[3] << 7)); + r[count + 2] = (byte)((t[3] >> 1) | (t[4] << 4)); + r[count + 3] = (byte)((t[4] >> 4) | (t[5] << 1) | (t[6] << 6)); + r[count + 4] = (byte)((t[6] >> 2) | (t[7] << 3)); + count += 5; + } + } + else + { + throw new RuntimeException("PolyCompressedBytes is neither 128 or 160!"); + } + + // System.out.print("r = "); + // Helper.printByteArray(r); + // System.out.println(); + + return r; + } + + public void decompressPoly(byte[] compressedPolyCipherText) + { + int i, count = 0; + + if (engine.getKyberPolyCompressedBytes() == 128) + { + for (i = 0; i < MLKEMEngine.KyberN / 2; i++) + { + this.setCoeffIndex(2 * i + 0, (short)((((short)((compressedPolyCipherText[count] & 0xFF) & 15) * MLKEMEngine.KyberQ) + 8) >> 4)); + this.setCoeffIndex(2 * i + 1, (short)((((short)((compressedPolyCipherText[count] & 0xFF) >> 4) * MLKEMEngine.KyberQ) + 8) >> 4)); + count += 1; + } + } + else if (engine.getKyberPolyCompressedBytes() == 160) + { + int j; + byte[] t = new byte[8]; + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + t[0] = (byte)((compressedPolyCipherText[count + 0] & 0xFF) >> 0); + t[1] = (byte)(((compressedPolyCipherText[count + 0] & 0xFF) >> 5) | ((compressedPolyCipherText[count + 1] & 0xFF) << 3)); + t[2] = (byte)((compressedPolyCipherText[count + 1] & 0xFF) >> 2); + t[3] = (byte)(((compressedPolyCipherText[count + 1] & 0xFF) >> 7) | ((compressedPolyCipherText[count + 2] & 0xFF) << 1)); + t[4] = (byte)(((compressedPolyCipherText[count + 2] & 0xFF) >> 4) | ((compressedPolyCipherText[count + 3] & 0xFF) << 4)); + t[5] = (byte)((compressedPolyCipherText[count + 3] & 0xFF) >> 1); + t[6] = (byte)(((compressedPolyCipherText[count + 3] & 0xFF) >> 6) | ((compressedPolyCipherText[count + 4] & 0xFF) << 2)); + t[7] = (byte)((compressedPolyCipherText[count + 4] & 0xFF) >> 3); + count += 5; + for (j = 0; j < 8; j++) + { + this.setCoeffIndex(8 * i + j, (short)(((t[j] & 31) * MLKEMEngine.KyberQ + 16) >> 5)); + } + } + } + else + { + throw new RuntimeException("PolyCompressedBytes is neither 128 or 160!"); + } + + } + + public byte[] toBytes() + { + byte[] r = new byte[MLKEMEngine.KyberPolyBytes]; + short t0, t1; + this.conditionalSubQ(); + for (int i = 0; i < MLKEMEngine.KyberN / 2; i++) + { + t0 = this.getCoeffIndex(2 * i); + t1 = this.getCoeffIndex(2 * i + 1); + r[3 * i] = (byte)(t0 >> 0); + r[3 * i + 1] = (byte)((t0 >> 8) | (t1 << 4)); + r[3 * i + 2] = (byte)(t1 >> 4); + } + + return r; + + } + + public void fromBytes(byte[] inpBytes) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN / 2; i++) + { + this.setCoeffIndex(2 * i, (short)( + ( + ((inpBytes[3 * i + 0] & 0xFF) >> 0) + | ((inpBytes[3 * i + 1] & 0xFF) << 8) + ) & 0xFFF) + ); + this.setCoeffIndex(2 * i + 1, (short)( + ( + ((inpBytes[3 * i + 1] & 0xFF) >> 4) + | (long)((inpBytes[3 * i + 2] & 0xFF) << 4) + ) & 0xFFF) + ); + } + } + + public byte[] toMsg() + { + int LOWER = MLKEMEngine.KyberQ >>> 2; + int UPPER = MLKEMEngine.KyberQ - LOWER; + + byte[] outMsg = new byte[MLKEMEngine.getKyberIndCpaMsgBytes()]; + + this.conditionalSubQ(); + + for (int i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + outMsg[i] = 0; + for (int j = 0; j < 8; j++) + { + int c_j = this.getCoeffIndex(8 * i + j); + + // KyberSlash: division by Q is not constant time. +// int t = (((c_j << 1) + (KyberEngine.KyberQ / 2)) / KyberEngine.KyberQ) & 1; + int t = ((LOWER - c_j) & (c_j - UPPER)) >>> 31; + + outMsg[i] |= (byte)(t << j); + } + } + return outMsg; + } + + public void fromMsg(byte[] msg) + { + int i, j; + short mask; + if (msg.length != MLKEMEngine.KyberN / 8) + { + throw new RuntimeException("KYBER_INDCPA_MSGBYTES must be equal to KYBER_N/8 bytes!"); + } + for (i = 0; i < MLKEMEngine.KyberN / 8; i++) + { + for (j = 0; j < 8; j++) + { + mask = (short)((-1) * (short)(((msg[i] & 0xFF) >> j) & 1)); + this.setCoeffIndex(8 * i + j, (short)(mask & (short)((MLKEMEngine.KyberQ + 1) / 2))); + } + } + } + + public void conditionalSubQ() + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, Reduce.conditionalSubQ(this.getCoeffIndex(i))); + } + } + + public void getEta1Noise(byte[] seed, byte nonce) + { + byte[] buf = new byte[MLKEMEngine.KyberN * eta1 / 4]; + symmetric.prf(buf, seed, nonce); + CBD.mlkemCBD(this, buf, eta1); + } + + public void getEta2Noise(byte[] seed, byte nonce) + { + byte[] buf = new byte[MLKEMEngine.KyberN * eta2 / 4]; + symmetric.prf(buf, seed, nonce); + CBD.mlkemCBD(this, buf, eta2); + } + + public void polySubtract(Poly b) + { + int i; + for (i = 0; i < MLKEMEngine.KyberN; i++) + { + this.setCoeffIndex(i, (short)(b.getCoeffIndex(i) - this.getCoeffIndex(i))); + } + } + + public String toString() + { + StringBuffer out = new StringBuffer(); + out.append("["); + for (int i = 0; i < coeffs.length; i++) + { + out.append(coeffs[i]); + if (i != coeffs.length - 1) + { + out.append(", "); + } + } + out.append("]"); + return out.toString(); + } +} + diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java new file mode 100644 index 0000000000..d6613e904f --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java @@ -0,0 +1,272 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.util.Arrays; + +class PolyVec +{ + Poly[] vec; + private MLKEMEngine engine; + private int kyberK; + private int polyVecBytes; + + public PolyVec(MLKEMEngine engine) + { + this.engine = engine; + this.kyberK = engine.getKyberK(); + this.polyVecBytes = engine.getKyberPolyVecBytes(); + + this.vec = new Poly[kyberK]; + for (int i = 0; i < kyberK; i++) + { + vec[i] = new Poly(engine); + } + } + + public PolyVec() + throws Exception + { + throw new Exception("Requires Parameter"); + } + + public Poly getVectorIndex(int i) + { + return vec[i]; + } + + public void polyVecNtt() + { + int i; + for (i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).polyNtt(); + } + } + + public void polyVecInverseNttToMont() + { + for (int i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).polyInverseNttToMont(); + } + } + + public byte[] compressPolyVec() + { + int i, j, k; + + this.conditionalSubQ(); + short[] t; + byte[] r = new byte[engine.getKyberPolyVecCompressedBytes()]; + int count = 0; + if (engine.getKyberPolyVecCompressedBytes() == kyberK * 320) + { + t = new short[4]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 4; j++) + { + for (k = 0; k < 4; k++) + { + /*t[k] = (short) + ( + ( + ((this.getVectorIndex(i).getCoeffIndex(4 * j + k) << 10) + + (KyberEngine.KyberQ / 2)) + / KyberEngine.KyberQ) + & 0x3ff);*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + long t_k = this.getVectorIndex(i).getCoeffIndex(4 * j + k); + t_k <<= 10; + t_k += 1665; + t_k *= 1290167; + t_k >>= 32; + t_k &= 0x3ff; + t[k] = (short)t_k; + } + r[count + 0] = (byte)(t[0] >> 0); + r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 2)); + r[count + 2] = (byte)((t[1] >> 6) | (t[2] << 4)); + r[count + 3] = (byte)((t[2] >> 4) | (t[3] << 6)); + r[count + 4] = (byte)((t[3] >> 2)); + count += 5; + } + } + } + else if (engine.getKyberPolyVecCompressedBytes() == kyberK * 352) + { + t = new short[8]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 8; j++) + { + for (k = 0; k < 8; k++) + { + /*t[k] = (short) + ( + ( + ((this.getVectorIndex(i).getCoeffIndex(8 * j + k) << 11) + + (KyberEngine.KyberQ / 2)) + / KyberEngine.KyberQ) + & 0x7ff);*/ + // Fix for KyberSlash2: division by KyberQ above is not + // constant time. + long t_k = this.getVectorIndex(i).getCoeffIndex(8 * j + k); + t_k <<= 11; + t_k += 1664; + t_k *= 645084; + t_k >>= 31; + t_k &= 0x7ff; + t[k] = (short)t_k; + } + r[count + 0] = (byte)((t[0] >> 0)); + r[count + 1] = (byte)((t[0] >> 8) | (t[1] << 3)); + r[count + 2] = (byte)((t[1] >> 5) | (t[2] << 6)); + r[count + 3] = (byte)((t[2] >> 2)); + r[count + 4] = (byte)((t[2] >> 10) | (t[3] << 1)); + r[count + 5] = (byte)((t[3] >> 7) | (t[4] << 4)); + r[count + 6] = (byte)((t[4] >> 4) | (t[5] << 7)); + r[count + 7] = (byte)((t[5] >> 1)); + r[count + 8] = (byte)((t[5] >> 9) | (t[6] << 2)); + r[count + 9] = (byte)((t[6] >> 6) | (t[7] << 5)); + r[count + 10] = (byte)((t[7] >> 3)); + count += 11; + } + } + } + else + { + throw new RuntimeException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!"); + } + return r; + } + + public void decompressPolyVec(byte[] compressedPolyVecCipherText) + { + int i, j, k, count = 0; + + if (engine.getKyberPolyVecCompressedBytes() == (kyberK * 320)) + { + short[] t = new short[4]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 4; j++) + { + t[0] = (short)(((compressedPolyVecCipherText[count] & 0xFF) >> 0) | (short)((compressedPolyVecCipherText[count + 1] & 0xFF) << 8)); + t[1] = (short)(((compressedPolyVecCipherText[count + 1] & 0xFF) >> 2) | (short)((compressedPolyVecCipherText[count + 2] & 0xFF) << 6)); + t[2] = (short)(((compressedPolyVecCipherText[count + 2] & 0xFF) >> 4) | (short)((compressedPolyVecCipherText[count + 3] & 0xFF) << 4)); + t[3] = (short)(((compressedPolyVecCipherText[count + 3] & 0xFF) >> 6) | (short)((compressedPolyVecCipherText[count + 4] & 0xFF) << 2)); + count += 5; + for (k = 0; k < 4; k++) + { + this.vec[i].setCoeffIndex(4 * j + k, (short)(((t[k] & 0x3FF) * MLKEMEngine.KyberQ + 512) >> 10)); + } + } + + } + + } + else if (engine.getKyberPolyVecCompressedBytes() == (kyberK * 352)) + { + short[] t = new short[8]; + for (i = 0; i < kyberK; i++) + { + for (j = 0; j < MLKEMEngine.KyberN / 8; j++) + { + t[0] = (short)(((compressedPolyVecCipherText[count] & 0xFF) >> 0) | ((short)(compressedPolyVecCipherText[count + 1] & 0xFF) << 8)); + t[1] = (short)(((compressedPolyVecCipherText[count + 1] & 0xFF) >> 3) | ((short)(compressedPolyVecCipherText[count + 2] & 0xFF) << 5)); + t[2] = (short)(((compressedPolyVecCipherText[count + 2] & 0xFF) >> 6) | ((short)(compressedPolyVecCipherText[count + 3] & 0xFF) << 2) | ((short)((compressedPolyVecCipherText[count + 4] & 0xFF) << 10))); + t[3] = (short)(((compressedPolyVecCipherText[count + 4] & 0xFF) >> 1) | ((short)(compressedPolyVecCipherText[count + 5] & 0xFF) << 7)); + t[4] = (short)(((compressedPolyVecCipherText[count + 5] & 0xFF) >> 4) | ((short)(compressedPolyVecCipherText[count + 6] & 0xFF) << 4)); + t[5] = (short)(((compressedPolyVecCipherText[count + 6] & 0xFF) >> 7) | ((short)(compressedPolyVecCipherText[count + 7] & 0xFF) << 1) | ((short)((compressedPolyVecCipherText[count + 8] & 0xFF) << 9))); + t[6] = (short)(((compressedPolyVecCipherText[count + 8] & 0xFF) >> 2) | ((short)(compressedPolyVecCipherText[count + 9] & 0xFF) << 6)); + t[7] = (short)(((compressedPolyVecCipherText[count + 9] & 0xFF) >> 5) | ((short)(compressedPolyVecCipherText[count + 10] & 0xFF) << 3)); + count += 11; + for (k = 0; k < 8; k++) + { + this.vec[i].setCoeffIndex(8 * j + k, (short)(((t[k] & 0x7FF) * MLKEMEngine.KyberQ + 1024) >> 11)); + } + } + } + } + else + { + throw new RuntimeException("Kyber PolyVecCompressedBytes neither 320 * KyberK or 352 * KyberK!"); + } + } + + public static void pointwiseAccountMontgomery(Poly out, PolyVec inp1, PolyVec inp2, MLKEMEngine engine) + { + int i; + Poly t = new Poly(engine); + + Poly.baseMultMontgomery(out, inp1.getVectorIndex(0), inp2.getVectorIndex(0)); + for (i = 1; i < engine.getKyberK(); i++) + { + Poly.baseMultMontgomery(t, inp1.getVectorIndex(i), inp2.getVectorIndex(i)); + out.addCoeffs(t); + } + out.reduce(); + } + + public void reducePoly() + { + int i; + for (i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).reduce(); + } + } + + public void addPoly(PolyVec b) + { + int i; + for (i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).addCoeffs(b.getVectorIndex(i)); + } + } + + public byte[] toBytes() + { + byte[] r = new byte[polyVecBytes]; + for (int i = 0; i < kyberK; i++) + { + System.arraycopy(this.vec[i].toBytes(), 0, r, i * MLKEMEngine.KyberPolyBytes, MLKEMEngine.KyberPolyBytes); + } + + return r; + } + + public void fromBytes(byte[] inputBytes) + { + for (int i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).fromBytes(Arrays.copyOfRange(inputBytes, i * MLKEMEngine.KyberPolyBytes, (i + 1) * MLKEMEngine.KyberPolyBytes)); + } + } + + public void conditionalSubQ() + { + for (int i = 0; i < kyberK; i++) + { + this.getVectorIndex(i).conditionalSubQ(); + } + } + + public String toString() + { + StringBuffer out = new StringBuffer(); + out.append("["); + for (int i = 0; i < kyberK; i++) + { + out.append(vec[i].toString()); + if (i != kyberK - 1) + { + out.append(", "); + } + } + out.append("]"); + return out.toString(); + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Reduce.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Reduce.java new file mode 100644 index 0000000000..c0b51d2c07 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Reduce.java @@ -0,0 +1,35 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +class Reduce +{ + + public static short montgomeryReduce(int a) + { + int t; + short u; + + u = (short)(a * MLKEMEngine.KyberQinv); + t = (int)(u * MLKEMEngine.KyberQ); + t = a - t; + t >>= 16; + return (short)t; + } + + public static short barretReduce(short a) + { + short t; + long shift = (((long)1) << 26); + short v = (short)((shift + (MLKEMEngine.KyberQ / 2)) / MLKEMEngine.KyberQ); + t = (short)((v * a) >> 26); + t = (short)(t * MLKEMEngine.KyberQ); + return (short)(a - t); + } + + public static short conditionalSubQ(short a) + { + a -= MLKEMEngine.KyberQ; + a += (a >> 15) & MLKEMEngine.KyberQ; + return a; + } + +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Symmetric.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Symmetric.java new file mode 100644 index 0000000000..40c309dedf --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/Symmetric.java @@ -0,0 +1,94 @@ +package org.bouncycastle.pqc.crypto.mlkem; + +import org.bouncycastle.crypto.digests.SHA3Digest; +import org.bouncycastle.crypto.digests.SHAKEDigest; + +abstract class Symmetric +{ + + final int xofBlockBytes; + + abstract void hash_h(byte[] out, byte[] in, int outOffset); + + abstract void hash_g(byte[] out, byte[] in); + + abstract void xofAbsorb(byte[] seed, byte x, byte y); + + abstract void xofSqueezeBlocks(byte[] out, int outOffset, int outLen); + + abstract void prf(byte[] out, byte[] key, byte nonce); + + abstract void kdf(byte[] out, byte[] in); + + Symmetric(int blockBytes) + { + this.xofBlockBytes = blockBytes; + } + + + static class ShakeSymmetric + extends Symmetric + { + private final SHAKEDigest xof; + private final SHA3Digest sha3Digest512; + private final SHA3Digest sha3Digest256; + private final SHAKEDigest shakeDigest; + + ShakeSymmetric() + { + super(168); + this.xof = new SHAKEDigest(128); + this.shakeDigest = new SHAKEDigest(256); + this.sha3Digest256 = new SHA3Digest(256); + this.sha3Digest512 = new SHA3Digest(512); + } + + @Override + void hash_h(byte[] out, byte[] in, int outOffset) + { + sha3Digest256.update(in, 0, in.length); + sha3Digest256.doFinal(out, outOffset); + } + + @Override + void hash_g(byte[] out, byte[] in) + { + sha3Digest512.update(in, 0, in.length); + sha3Digest512.doFinal(out, 0); + } + + @Override + void xofAbsorb(byte[] seed, byte a, byte b) + { + xof.reset(); + byte[] buf = new byte[seed.length + 2]; + System.arraycopy(seed, 0, buf, 0, seed.length); + buf[seed.length] = a; + buf[seed.length + 1] = b; + xof.update(buf, 0, seed.length + 2); + } + + @Override + void xofSqueezeBlocks(byte[] out, int outOffset, int outLen) + { + xof.doOutput(out, outOffset, outLen); + } + + @Override + void prf(byte[] out, byte[] seed, byte nonce) + { + byte[] extSeed = new byte[seed.length + 1]; + System.arraycopy(seed, 0, extSeed, 0, seed.length); + extSeed[seed.length] = nonce; + shakeDigest.update(extSeed, 0, extSeed.length); + shakeDigest.doFinal(out, 0, out.length); + } + + @Override + void kdf(byte[] out, byte[] in) + { + shakeDigest.update(in, 0, in.length); + shakeDigest.doFinal(out, 0, out.length); + } + } +} diff --git a/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java b/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java new file mode 100644 index 0000000000..ab60d77409 --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java @@ -0,0 +1,64 @@ +package org.bouncycastle.pqc.crypto.util; + +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.security.auth.DestroyFailedException; + +import org.bouncycastle.crypto.SecretWithEncapsulation; +import org.bouncycastle.util.Arrays; + +public class SecretWithEncapsulationImpl + implements SecretWithEncapsulation +{ + private final AtomicBoolean hasBeenDestroyed = new AtomicBoolean(false); + + private final byte[] sessionKey; + private final byte[] cipher_text; + + public SecretWithEncapsulationImpl(byte[] sessionKey, byte[] cipher_text) + { + this.sessionKey = sessionKey; + this.cipher_text = cipher_text; + } + + public byte[] getSecret() + { + byte[] clone = Arrays.clone(sessionKey); + + checkDestroyed(); + + return clone; + } + + public byte[] getEncapsulation() + { + byte[] clone = Arrays.clone(cipher_text); + + checkDestroyed(); + + return clone; + } + + public void destroy() + throws DestroyFailedException + { + if (!hasBeenDestroyed.getAndSet(true)) + { + Arrays.clear(sessionKey); + Arrays.clear(cipher_text); + } + } + + public boolean isDestroyed() + { + return hasBeenDestroyed.get(); + } + + void checkDestroyed() + { + if (isDestroyed()) + { + throw new IllegalStateException("data has been destroyed"); + } + } +} diff --git a/router/java/src/org/bouncycastle/util/Memoable.java b/router/java/src/org/bouncycastle/util/Memoable.java new file mode 100644 index 0000000000..a6eede90c1 --- /dev/null +++ b/router/java/src/org/bouncycastle/util/Memoable.java @@ -0,0 +1,27 @@ +package org.bouncycastle.util; + +/** + * Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state + * via the copy() method and then resetting the object back to that state later using the reset() method. + */ +public interface Memoable +{ + /** + * Produce a copy of this object with its configuration and in its current state. + *

+ * The returned object may be used simply to store the state, or may be used as a similar object + * starting from the copied state. + */ + Memoable copy(); + + /** + * Restore a copied object state into this object. + *

+ * Implementations of this method should try to avoid or minimise memory allocation to perform the reset. + * + * @param other an object originally {@link #copy() copied} from an object of the same type as this instance. + * @throws ClassCastException if the provided object is not of the correct type. + * @throws MemoableResetException if the other parameter is in some other way invalid. + */ + void reset(Memoable other); +} diff --git a/router/java/src/org/bouncycastle/util/Pack.java b/router/java/src/org/bouncycastle/util/Pack.java new file mode 100644 index 0000000000..d3a360975f --- /dev/null +++ b/router/java/src/org/bouncycastle/util/Pack.java @@ -0,0 +1,419 @@ +package org.bouncycastle.util; + +/** + * Utility methods for converting byte arrays into ints and longs, and back again. + */ +public abstract class Pack +{ + public static short bigEndianToShort(byte[] bs, int off) + { + int n = (bs[off] & 0xff) << 8; + n |= (bs[++off] & 0xff); + return (short)n; + } + + public static int bigEndianToInt(byte[] bs, int off) + { + int n = bs[off] << 24; + n |= (bs[++off] & 0xff) << 16; + n |= (bs[++off] & 0xff) << 8; + n |= (bs[++off] & 0xff); + return n; + } + + public static void bigEndianToInt(byte[] bs, int off, int[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = bigEndianToInt(bs, off); + off += 4; + } + } + + public static void bigEndianToInt(byte[] bs, int off, int[] ns, int nsOff, int nsLen) + { + for (int i = 0; i < nsLen; ++i) + { + ns[nsOff + i] = bigEndianToInt(bs, off); + off += 4; + } + } + + public static byte[] intToBigEndian(int n) + { + byte[] bs = new byte[4]; + intToBigEndian(n, bs, 0); + return bs; + } + + public static void intToBigEndian(int n, byte[] bs, int off) + { + bs[off] = (byte)(n >>> 24); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n); + } + + public static byte[] intToBigEndian(int[] ns) + { + byte[] bs = new byte[4 * ns.length]; + intToBigEndian(ns, bs, 0); + return bs; + } + + public static void intToBigEndian(int[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + intToBigEndian(ns[i], bs, off); + off += 4; + } + } + + public static void intToBigEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) + { + for (int i = 0; i < nsLen; ++i) + { + intToBigEndian(ns[nsOff + i], bs, bsOff); + bsOff += 4; + } + } + + public static long bigEndianToLong(byte[] bs, int off) + { + int hi = bigEndianToInt(bs, off); + int lo = bigEndianToInt(bs, off + 4); + return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); + } + + public static void bigEndianToLong(byte[] bs, int off, long[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = bigEndianToLong(bs, off); + off += 8; + } + } + + public static void bigEndianToLong(byte[] bs, int bsOff, long[] ns, int nsOff, int nsLen) + { + for (int i = 0; i < nsLen; ++i) + { + ns[nsOff + i] = bigEndianToLong(bs, bsOff); + bsOff += 8; + } + } + + public static long bigEndianToLong(byte[] bs, int off, int len) + { + long x = 0; + for (int i = 0; i < len; ++i) + { + x |= (bs[i + off] & 0xFFL) << ((7 - i) << 3); + } + return x; + } + + public static byte[] longToBigEndian(long n) + { + byte[] bs = new byte[8]; + longToBigEndian(n, bs, 0); + return bs; + } + + public static void longToBigEndian(long n, byte[] bs, int off) + { + intToBigEndian((int)(n >>> 32), bs, off); + intToBigEndian((int)(n & 0xffffffffL), bs, off + 4); + } + + public static byte[] longToBigEndian(long[] ns) + { + byte[] bs = new byte[8 * ns.length]; + longToBigEndian(ns, bs, 0); + return bs; + } + + public static void longToBigEndian(long[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + longToBigEndian(ns[i], bs, off); + off += 8; + } + } + + public static void longToBigEndian(long[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) + { + for (int i = 0; i < nsLen; ++i) + { + longToBigEndian(ns[nsOff + i], bs, bsOff); + bsOff += 8; + } + } + + /** + * @param value The number + * @param bs The target. + * @param off Position in target to start. + * @param bytes number of bytes to write. + * @deprecated Will be removed + */ + public static void longToBigEndian(long value, byte[] bs, int off, int bytes) + { + for (int i = bytes - 1; i >= 0; i--) + { + bs[i + off] = (byte)(value & 0xff); + value >>>= 8; + } + } + + public static short littleEndianToShort(byte[] bs, int off) + { + int n = bs[off] & 0xff; + n |= (bs[++off] & 0xff) << 8; + return (short)n; + } + + public static int littleEndianToInt(byte[] bs, int off) + { + int n = bs[off] & 0xff; + n |= (bs[++off] & 0xff) << 8; + n |= (bs[++off] & 0xff) << 16; + n |= bs[++off] << 24; + return n; + } + + public static int littleEndianToInt_High(byte[] bs, int off, int len) + { + return littleEndianToInt_Low(bs, off, len) << ((4 - len) << 3); + } + + public static int littleEndianToInt_Low(byte[] bs, int off, int len) + { +// assert 1 <= len && len <= 4; + + int result = bs[off] & 0xff; + int pos = 0; + for (int i = 1; i < len; ++i) + { + pos += 8; + result |= (bs[off + i] & 0xff) << pos; + } + return result; + } + + public static void littleEndianToInt(byte[] bs, int off, int[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = littleEndianToInt(bs, off); + off += 4; + } + } + + public static void littleEndianToInt(byte[] bs, int bOff, int[] ns, int nOff, int count) + { + for (int i = 0; i < count; ++i) + { + ns[nOff + i] = littleEndianToInt(bs, bOff); + bOff += 4; + } + } + + public static int[] littleEndianToInt(byte[] bs, int off, int count) + { + int[] ns = new int[count]; + for (int i = 0; i < ns.length; ++i) + { + ns[i] = littleEndianToInt(bs, off); + off += 4; + } + return ns; + } + + public static byte[] shortToLittleEndian(short n) + { + byte[] bs = new byte[2]; + shortToLittleEndian(n, bs, 0); + return bs; + } + + public static void shortToLittleEndian(short n, byte[] bs, int off) + { + bs[off] = (byte)(n); + bs[++off] = (byte)(n >>> 8); + } + + + public static byte[] shortToBigEndian(short n) + { + byte[] r = new byte[2]; + shortToBigEndian(n, r, 0); + return r; + } + + public static void shortToBigEndian(short n, byte[] bs, int off) + { + bs[off] = (byte)(n >>> 8); + bs[++off] = (byte)(n); + } + + + public static byte[] intToLittleEndian(int n) + { + byte[] bs = new byte[4]; + intToLittleEndian(n, bs, 0); + return bs; + } + + public static void intToLittleEndian(int n, byte[] bs, int off) + { + bs[off] = (byte)(n); + bs[++off] = (byte)(n >>> 8); + bs[++off] = (byte)(n >>> 16); + bs[++off] = (byte)(n >>> 24); + } + + public static byte[] intToLittleEndian(int[] ns) + { + byte[] bs = new byte[4 * ns.length]; + intToLittleEndian(ns, bs, 0); + return bs; + } + + public static void intToLittleEndian(int[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + intToLittleEndian(ns[i], bs, off); + off += 4; + } + } + + public static void intToLittleEndian(int[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) + { + for (int i = 0; i < nsLen; ++i) + { + intToLittleEndian(ns[nsOff + i], bs, bsOff); + bsOff += 4; + } + } + + public static long littleEndianToLong(byte[] bs, int off) + { + int lo = littleEndianToInt(bs, off); + int hi = littleEndianToInt(bs, off + 4); + return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); + } + + public static void littleEndianToLong(byte[] bs, int off, long[] ns) + { + for (int i = 0; i < ns.length; ++i) + { + ns[i] = littleEndianToLong(bs, off); + off += 8; + } + } + + public static long littleEndianToLong(byte[] input, int off, int len) + { + long result = 0; + for (int i = 0; i < len; ++i) + { + result |= (input[off + i] & 0xFFL) << (i << 3); + } + return result; + } + + public static void littleEndianToLong(byte[] bs, int bsOff, long[] ns, int nsOff, int nsLen) + { + for (int i = 0; i < nsLen; ++i) + { + ns[nsOff + i] = littleEndianToLong(bs, bsOff); + bsOff += 8; + } + } + + public static void longToLittleEndian_High(long n, byte[] bs, int off, int len) + { + //Debug.Assert(1 <= len && len <= 8); + int pos = 56; + bs[off] = (byte)(n >>> pos); + for (int i = 1; i < len; ++i) + { + pos -= 8; + bs[off + i] = (byte)(n >>> pos); + } + } + + public static void longToLittleEndian(long n, byte[] bs, int off, int len) + { + for (int i = 0; i < len; ++i) + { + bs[off + i] = (byte)(n >>> (i << 3)); + } + } + +// public static void longToLittleEndian_Low(long n, byte[] bs, int off, int len) +// { +// longToLittleEndian_High(n << ((8 - len) << 3), bs, off, len); +// } + + public static long littleEndianToLong_High(byte[] bs, int off, int len) + { + return littleEndianToLong_Low(bs, off, len) << ((8 - len) << 3); + } + + public static long littleEndianToLong_Low(byte[] bs, int off, int len) + { + //Debug.Assert(1 <= len && len <= 8); + long result = bs[off] & 0xFF; + for (int i = 1; i < len; ++i) + { + result <<= 8; + result |= bs[off + i] & 0xFF; + } + return result; + } + + public static byte[] longToLittleEndian(long n) + { + byte[] bs = new byte[8]; + longToLittleEndian(n, bs, 0); + return bs; + } + + public static void longToLittleEndian(long n, byte[] bs, int off) + { + intToLittleEndian((int)(n & 0xffffffffL), bs, off); + intToLittleEndian((int)(n >>> 32), bs, off + 4); + } + + public static byte[] longToLittleEndian(long[] ns) + { + byte[] bs = new byte[8 * ns.length]; + longToLittleEndian(ns, bs, 0); + return bs; + } + + public static void longToLittleEndian(long[] ns, byte[] bs, int off) + { + for (int i = 0; i < ns.length; ++i) + { + longToLittleEndian(ns[i], bs, off); + off += 8; + } + } + + public static void longToLittleEndian(long[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) + { + for (int i = 0; i < nsLen; ++i) + { + longToLittleEndian(ns[nsOff + i], bs, bsOff); + bsOff += 8; + } + } + + +} diff --git a/router/java/src/org/bouncycastle/util/package-info.java b/router/java/src/org/bouncycastle/util/package-info.java new file mode 100644 index 0000000000..2454b1dfeb --- /dev/null +++ b/router/java/src/org/bouncycastle/util/package-info.java @@ -0,0 +1,4 @@ +/** + * General purpose utility classes used throughout the APIs. + */ +package org.bouncycastle.util; From d664af0a321b3dceec9d97d3483d9bc3de9ed51d Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 13 Jun 2025 08:51:34 -0400 Subject: [PATCH 2/3] Patches to BouncyCastle 1.80 source files to minimize dependencies --- .../crypto/CryptoServicesRegistrar.java | 529 +----------------- .../crypto/digests/KeccakDigest.java | 5 +- .../crypto/digests/SHA512Digest.java | 3 +- .../crypto/digests/SHAKEDigest.java | 2 +- .../crypto/params/ParametersWithContext.java | 6 +- .../src/org/bouncycastle/package-info.java | 6 + .../pqc/crypto/mlkem/MLKEMEngine.java | 9 +- .../pqc/crypto/mlkem/MLKEMExtractor.java | 3 - .../pqc/crypto/mlkem/MLKEMGenerator.java | 2 - .../pqc/crypto/mlkem/MLKEMIndCpa.java | 6 +- .../crypto/mlkem/MLKEMKeyPairGenerator.java | 2 - .../mlkem/MLKEMPrivateKeyParameters.java | 30 +- .../mlkem/MLKEMPublicKeyParameters.java | 14 +- .../pqc/crypto/mlkem/PolyVec.java | 2 +- .../pqc/crypto/mlkem/package-info.java | 6 + .../util/SecretWithEncapsulationImpl.java | 11 +- .../src/org/bouncycastle/util/Memoable.java | 1 - .../java/src/org/bouncycastle/util/Util.java | 52 ++ 18 files changed, 117 insertions(+), 572 deletions(-) create mode 100644 router/java/src/org/bouncycastle/package-info.java create mode 100644 router/java/src/org/bouncycastle/pqc/crypto/mlkem/package-info.java create mode 100644 router/java/src/org/bouncycastle/util/Util.java diff --git a/router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java b/router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java index 90d3c85348..2589bc21f5 100644 --- a/router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java +++ b/router/java/src/org/bouncycastle/crypto/CryptoServicesRegistrar.java @@ -1,541 +1,20 @@ package org.bouncycastle.crypto; -import java.math.BigInteger; -import java.security.AccessController; -import java.security.Permission; -import java.security.PrivilegedAction; import java.security.SecureRandom; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Logger; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.crypto.params.DHParameters; -import org.bouncycastle.crypto.params.DHValidationParameters; -import org.bouncycastle.crypto.params.DSAParameters; -import org.bouncycastle.crypto.params.DSAValidationParameters; -import org.bouncycastle.util.Properties; -import org.bouncycastle.util.Strings; -import org.bouncycastle.util.encoders.Hex; +public class CryptoServicesRegistrar { -/** - * Basic registrar class for providing defaults for cryptography services in this module. - */ -public final class CryptoServicesRegistrar -{ - private static final Logger LOG = Logger.getLogger(CryptoServicesRegistrar.class.getName()); + public static void checkConstraints(CryptoServiceProperties csp) {} - private static final Permission CanSetDefaultProperty = new CryptoServicesPermission(CryptoServicesPermission.GLOBAL_CONFIG); - private static final Permission CanSetThreadProperty = new CryptoServicesPermission(CryptoServicesPermission.THREAD_LOCAL_CONFIG); - private static final Permission CanSetDefaultRandom = new CryptoServicesPermission(CryptoServicesPermission.DEFAULT_RANDOM); - private static final Permission CanSetConstraints = new CryptoServicesPermission(CryptoServicesPermission.CONSTRAINTS); - - private static final ThreadLocal> threadProperties = new ThreadLocal>(); - private static final Map globalProperties = Collections.synchronizedMap(new HashMap()); - private static final SecureRandomProvider defaultRandomProviderImpl = new ThreadLocalSecureRandomProvider(); - - private static final CryptoServicesConstraints noConstraintsImpl = new CryptoServicesConstraints() - { - public void check(CryptoServiceProperties service) - { - // anything goes. - } - }; - - private static final AtomicReference defaultSecureRandomProvider = new AtomicReference(); - private static final boolean preconfiguredConstraints; - private static final AtomicReference servicesConstraints = new AtomicReference(); - - static - { - // default domain parameters for DSA and Diffie-Hellman - - DSAParameters def512Params = new DSAParameters( - new BigInteger("fca682ce8e12caba26efccf7110e526db078b05edecbcd1eb4a208f3ae1617ae01f35b91a47e6df63413c5e12ed0899bcd132acd50d99151bdc43ee737592e17", 16), - new BigInteger("962eddcc369cba8ebb260ee6b6a126d9346e38c5", 16), - new BigInteger("678471b27a9cf44ee91a49c5147db1a9aaf244f05a434d6486931d2d14271b9e35030b71fd73da179069b32e2935630e1c2062354d0da20a6c416e50be794ca4", 16), - new DSAValidationParameters(Hex.decodeStrict("b869c82b35d70e1b1ff91b28e37a62ecdc34409b"), 123)); - - DSAParameters def768Params = new DSAParameters( - new BigInteger("e9e642599d355f37c97ffd3567120b8e25c9cd43e927b3a9670fbec5" + - "d890141922d2c3b3ad2480093799869d1e846aab49fab0ad26d2ce6a" + - "22219d470bce7d777d4a21fbe9c270b57f607002f3cef8393694cf45" + - "ee3688c11a8c56ab127a3daf", 16), - new BigInteger("9cdbd84c9f1ac2f38d0f80f42ab952e7338bf511", 16), - new BigInteger("30470ad5a005fb14ce2d9dcd87e38bc7d1b1c5facbaecbe95f190aa7" + - "a31d23c4dbbcbe06174544401a5b2c020965d8c2bd2171d366844577" + - "1f74ba084d2029d83c1c158547f3a9f1a2715be23d51ae4d3e5a1f6a" + - "7064f316933a346d3f529252", 16), - new DSAValidationParameters(Hex.decodeStrict("77d0f8c4dad15eb8c4f2f8d6726cefd96d5bb399"), 263)); - - DSAParameters def1024Params = new DSAParameters( - new BigInteger("fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80" + - "b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b" + - "801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c6" + - "1bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675" + - "f3ae2b61d72aeff22203199dd14801c7", 16), - new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16), - new BigInteger("f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b" + - "3d0782675159578ebad4594fe67107108180b449167123e84c281613" + - "b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f" + - "0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06" + - "928b665e807b552564014c3bfecf492a", 16), - new DSAValidationParameters(Hex.decodeStrict("8d5155894229d5e689ee01e6018a237e2cae64cd"), 92)); - - DSAParameters def2048Params = new DSAParameters( - new BigInteger("95475cf5d93e596c3fcd1d902add02f427f5f3c7210313bb45fb4d5b" + - "b2e5fe1cbd678cd4bbdd84c9836be1f31c0777725aeb6c2fc38b85f4" + - "8076fa76bcd8146cc89a6fb2f706dd719898c2083dc8d896f84062e2" + - "c9c94d137b054a8d8096adb8d51952398eeca852a0af12df83e475aa" + - "65d4ec0c38a9560d5661186ff98b9fc9eb60eee8b030376b236bc73b" + - "e3acdbd74fd61c1d2475fa3077b8f080467881ff7e1ca56fee066d79" + - "506ade51edbb5443a563927dbc4ba520086746175c8885925ebc64c6" + - "147906773496990cb714ec667304e261faee33b3cbdf008e0c3fa906" + - "50d97d3909c9275bf4ac86ffcb3d03e6dfc8ada5934242dd6d3bcca2" + - "a406cb0b", 16), - new BigInteger("f8183668ba5fc5bb06b5981e6d8b795d30b8978d43ca0ec572e37e09939a9773", 16), - new BigInteger("42debb9da5b3d88cc956e08787ec3f3a09bba5f48b889a74aaf53174" + - "aa0fbe7e3c5b8fcd7a53bef563b0e98560328960a9517f4014d3325f" + - "c7962bf1e049370d76d1314a76137e792f3f0db859d095e4a5b93202" + - "4f079ecf2ef09c797452b0770e1350782ed57ddf794979dcef23cb96" + - "f183061965c4ebc93c9c71c56b925955a75f94cccf1449ac43d586d0" + - "beee43251b0b2287349d68de0d144403f13e802f4146d882e057af19" + - "b6f6275c6676c8fa0e3ca2713a3257fd1b27d0639f695e347d8d1cf9" + - "ac819a26ca9b04cb0eb9b7b035988d15bbac65212a55239cfc7e58fa" + - "e38d7250ab9991ffbc97134025fe8ce04c4399ad96569be91a546f49" + - "78693c7a", 16), - new DSAValidationParameters(Hex.decodeStrict("b0b4417601b59cbc9d8ac8f935cadaec4f5fbb2f23785609ae466748d9b5a536"), 497)); - - localSetGlobalProperty(Property.DSA_DEFAULT_PARAMS, def512Params, def768Params, def1024Params, def2048Params); - localSetGlobalProperty(Property.DH_DEFAULT_PARAMS, toDH(def512Params), toDH(def768Params), toDH(def1024Params), toDH(def2048Params)); - - servicesConstraints.set(getDefaultConstraints()); - preconfiguredConstraints = (servicesConstraints.get() != noConstraintsImpl); - } - - private CryptoServicesRegistrar() - { - - } + private static final SecureRandom sr = new SecureRandom(); /** * Return the default source of randomness. * * @return the default SecureRandom */ - public static SecureRandom getSecureRandom() - { - defaultSecureRandomProvider.compareAndSet(null, defaultRandomProviderImpl); - - return defaultSecureRandomProvider.get().get(); - } - - /** - * Return either the passed-in SecureRandom, or if it is null, then the default source of randomness. - * - * @param secureRandom the SecureRandom to use if it is not null. - * @return the SecureRandom parameter if it is not null, or else the default SecureRandom - */ public static SecureRandom getSecureRandom(SecureRandom secureRandom) { - return null == secureRandom ? getSecureRandom() : secureRandom; - } - - /** - * Set a default secure random to be used where none is otherwise provided. - * - * @param secureRandom the SecureRandom to use as the default. - */ - public static void setSecureRandom(final SecureRandom secureRandom) - { - checkPermission(CanSetDefaultRandom); - - if (secureRandom == null) - { - defaultSecureRandomProvider.set(defaultRandomProviderImpl); - } - else - { - defaultSecureRandomProvider.set(new SecureRandomProvider() - { - public SecureRandom get() - { - return secureRandom; - } - }); - } - } - - /** - * Set a default secure random provider to be used where none is otherwise provided. - * - * @param secureRandomProvider a provider SecureRandom to use when a default SecureRandom is requested. - */ - public static void setSecureRandomProvider(SecureRandomProvider secureRandomProvider) - { - checkPermission(CanSetDefaultRandom); - - defaultSecureRandomProvider.set(secureRandomProvider); - } - - /** - * Return the current algorithm/services constraints. - * - * @return the algorithm/services constraints. - */ - public static CryptoServicesConstraints getServicesConstraints() - { - return servicesConstraints.get(); - } - - /** - * Check a service to make sure it meets the current constraints. - * - * @param cryptoService the service to be checked. - * @throws CryptoServiceConstraintsException if the service violates the current constraints. - */ - public static void checkConstraints(CryptoServiceProperties cryptoService) - { - servicesConstraints.get().check(cryptoService); - } - - /** - * Set the current algorithm constraints. - */ - public static void setServicesConstraints(CryptoServicesConstraints constraints) - { - checkPermission(CanSetConstraints); - - CryptoServicesConstraints newConstraints = (constraints == null) ? noConstraintsImpl : constraints; - - if (preconfiguredConstraints) - { - if (Properties.isOverrideSet("org.bouncycastle.constraints.allow_override")) - { - servicesConstraints.set(newConstraints); - } - else - { - LOG.warning("attempt to override pre-configured constraints ignored"); - } - } - else - { - // TODO: should this only be allowed once? - servicesConstraints.set(newConstraints); - } - } - - /** - * Return the default value for a particular property if one exists. The look up is done on the thread's local - * configuration first and then on the global configuration in no local configuration exists. - * - * @param property the property to look up. - * @param the type to be returned - * @return null if the property is not set, the default value otherwise, - */ - public static T getProperty(Property property) - { - Object[] values = lookupProperty(property); - - if (values != null) - { - return (T)values[0]; - } - - return null; - } - - private static Object[] lookupProperty(Property property) - { - Map properties = threadProperties.get(); - Object[] values; - - if (properties == null || !properties.containsKey(property.name)) - { - values = globalProperties.get(property.name); - } - else - { - values = properties.get(property.name); - } - return values; - } - - /** - * Return an array representing the current values for a sized property such as DH_DEFAULT_PARAMS or - * DSA_DEFAULT_PARAMS. - * - * @param property the name of the property to look up. - * @param the base type of the array to be returned. - * @return null if the property is not set, an array of the current values otherwise. - */ - public static T[] getSizedProperty(Property property) - { - Object[] values = lookupProperty(property); - - if (values == null) - { - return null; - } - - return (T[])values.clone(); - } - - /** - * Return the value for a specific size for a sized property such as DH_DEFAULT_PARAMS or - * DSA_DEFAULT_PARAMS. - * - * @param property the name of the property to look up. - * @param size the size (in bits) of the defining value in the property type. - * @param the type of the value to be returned. - * @return the current value for the size, null if there is no value set, - */ - public static T getSizedProperty(Property property, int size) - { - Object[] values = lookupProperty(property); - - if (values == null) - { - return null; - } - - if (property.type.isAssignableFrom(DHParameters.class)) - { - for (int i = 0; i != values.length; i++) - { - DHParameters params = (DHParameters)values[i]; - - if (params.getP().bitLength() == size) - { - return (T)params; - } - } - } - else if (property.type.isAssignableFrom(DSAParameters.class)) - { - for (int i = 0; i != values.length; i++) - { - DSAParameters params = (DSAParameters)values[i]; - - if (params.getP().bitLength() == size) - { - return (T)params; - } - } - } - - return null; - } - - /** - * Set the value of the the passed in property on the current thread only. More than - * one value can be passed in for a sized property. If more than one value is provided the - * first value in the argument list becomes the default value. - * - * @param property the name of the property to set. - * @param propertyValue the values to assign to the property. - * @param the base type of the property value. - */ - public static void setThreadProperty(Property property, T... propertyValue) - { - checkPermission(CanSetThreadProperty); - - if (!property.type.isAssignableFrom(propertyValue[0].getClass())) - { - throw new IllegalArgumentException("Bad property value passed"); - } - - localSetThread(property, propertyValue.clone()); - } - - /** - * Set the value of the the passed in property globally in the JVM. More than - * one value can be passed in for a sized property. If more than one value is provided the - * first value in the argument list becomes the default value. - * - * @param property the name of the property to set. - * @param propertyValue the values to assign to the property. - * @param the base type of the property value. - */ - public static void setGlobalProperty(Property property, T... propertyValue) - { - checkPermission(CanSetDefaultProperty); - - localSetGlobalProperty(property, propertyValue.clone()); - } - - private static void localSetThread(Property property, T[] propertyValue) - { - Map properties = threadProperties.get(); - - if (properties == null) - { - properties = new HashMap(); - threadProperties.set(properties); - } - - properties.put(property.name, propertyValue); - } - - private static void localSetGlobalProperty(Property property, T... propertyValue) - { - if (!property.type.isAssignableFrom(propertyValue[0].getClass())) - { - throw new IllegalArgumentException("Bad property value passed"); - } - - // set the property for the current thread as well to avoid mass confusion - localSetThread(property, propertyValue); - - globalProperties.put(property.name, propertyValue); - } - - /** - * Clear the global value for the passed in property. - * - * @param property the property to be cleared. - * @param the base type of the property value - * @return an array of T if a value was previously set, null otherwise. - */ - public static T[] clearGlobalProperty(Property property) - { - checkPermission(CanSetDefaultProperty); - - // clear the property for the current thread as well to avoid confusion - localClearThreadProperty(property); - - return (T[])globalProperties.remove(property.name); - } - - /** - * Clear the thread local value for the passed in property. - * - * @param property the property to be cleared. - * @param the base type of the property value - * @return an array of T if a value was previously set, null otherwise. - */ - public static T[] clearThreadProperty(Property property) - { - checkPermission(CanSetThreadProperty); - - return (T[])localClearThreadProperty(property); - } - - private static Object[] localClearThreadProperty(Property property) - { - Map properties = threadProperties.get(); - - if (properties == null) - { - properties = new HashMap(); - threadProperties.set(properties); - } - - return properties.remove(property.name); - } - - private static void checkPermission(final Permission permission) - { - final SecurityManager securityManager = System.getSecurityManager(); - - if (securityManager != null) - { - AccessController.doPrivileged(new PrivilegedAction() - { - public Object run() - { - securityManager.checkPermission(permission); - - return null; - } - }); - } - } - - private static DHParameters toDH(DSAParameters dsaParams) - { - int pSize = dsaParams.getP().bitLength(); - int m = chooseLowerBound(pSize); - return new DHParameters(dsaParams.getP(), dsaParams.getG(), dsaParams.getQ(), m, 0, null, - new DHValidationParameters(dsaParams.getValidationParameters().getSeed(), dsaParams.getValidationParameters().getCounter())); - } - - // based on lower limit of at least 2^{2 * bits_of_security} - private static int chooseLowerBound(int pSize) - { - int m = 160; - if (pSize > 1024) - { - if (pSize <= 2048) - { - m = 224; - } - else if (pSize <= 3072) - { - m = 256; - } - else if (pSize <= 7680) - { - m = 384; - } - else - { - m = 512; - } - } - return m; - } - - private static CryptoServicesConstraints getDefaultConstraints() - { - // TODO: return one based on system/security properties if set. - - return noConstraintsImpl; - } - - /** - * Available properties that can be set. - */ - public static final class Property - { - /** - * The parameters to be used for processing implicitlyCA X9.62 parameters - */ - public static final Property EC_IMPLICITLY_CA = new Property("ecImplicitlyCA", X9ECParameters.class); - /** - * The default parameters for a particular size of Diffie-Hellman key.This is a sized property. - */ - public static final Property DH_DEFAULT_PARAMS = new Property("dhDefaultParams", DHParameters.class); - /** - * The default parameters for a particular size of DSA key. This is a sized property. - */ - public static final Property DSA_DEFAULT_PARAMS = new Property("dsaDefaultParams", DSAParameters.class); - private final String name; - private final Class type; - - private Property(String name, Class type) - { - this.name = name; - this.type = type; - } - } - - private static class ThreadLocalSecureRandomProvider - implements SecureRandomProvider - { - final ThreadLocal defaultRandoms = new ThreadLocal(); - - public SecureRandom get() - { - if (defaultRandoms.get() == null) - { - defaultRandoms.set(new SecureRandom()); - } - - return defaultRandoms.get(); - } + return null == secureRandom ? sr : secureRandom; } } diff --git a/router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java b/router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java index f53552e550..933f14f697 100644 --- a/router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java +++ b/router/java/src/org/bouncycastle/crypto/digests/KeccakDigest.java @@ -1,10 +1,11 @@ package org.bouncycastle.crypto.digests; +import java.util.Arrays; + import org.bouncycastle.crypto.CryptoServiceProperties; import org.bouncycastle.crypto.CryptoServicePurpose; import org.bouncycastle.crypto.CryptoServicesRegistrar; import org.bouncycastle.crypto.ExtendedDigest; -import org.bouncycastle.util.Arrays; import org.bouncycastle.util.Pack; /** @@ -438,6 +439,6 @@ public class KeccakDigest protected CryptoServiceProperties cryptoServiceProperties() { - return Utils.getDefaultProperties(this, getDigestSize() * 8, purpose); + return null; } } diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java b/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java index 0fab188b98..85753972ae 100644 --- a/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java +++ b/router/java/src/org/bouncycastle/crypto/digests/SHA512Digest.java @@ -143,7 +143,8 @@ public class SHA512Digest protected CryptoServiceProperties cryptoServiceProperties() { - return Utils.getDefaultProperties(this, 256, purpose); + return null; + //return Utils.getDefaultProperties(this, 256, purpose); } } diff --git a/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java b/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java index 4b30c0e150..998f1286ab 100644 --- a/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java +++ b/router/java/src/org/bouncycastle/crypto/digests/SHAKEDigest.java @@ -145,6 +145,6 @@ public class SHAKEDigest protected CryptoServiceProperties cryptoServiceProperties() { - return Utils.getDefaultProperties(this, purpose); + return null; } } diff --git a/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java b/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java index 0f6a3c8524..e8778cbe20 100644 --- a/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java +++ b/router/java/src/org/bouncycastle/crypto/params/ParametersWithContext.java @@ -1,7 +1,7 @@ package org.bouncycastle.crypto.params; import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Util; public class ParametersWithContext implements CipherParameters @@ -19,7 +19,7 @@ public class ParametersWithContext } this.parameters = parameters; - this.context = Arrays.clone(context); + this.context = Util.clone(context); } public void copyContextTo(byte[] buf, int off, int len) @@ -34,7 +34,7 @@ public class ParametersWithContext public byte[] getContext() { - return Arrays.clone(context); + return Util.clone(context); } public int getContextLength() diff --git a/router/java/src/org/bouncycastle/package-info.java b/router/java/src/org/bouncycastle/package-info.java new file mode 100644 index 0000000000..ad1c9ca40e --- /dev/null +++ b/router/java/src/org/bouncycastle/package-info.java @@ -0,0 +1,6 @@ +/** + * This is a small portion of bouncycastle 1.80 for MLKEM, modified to reduce dependencies. + * + * @since 0.9.67 + */ +package org.bouncycastle; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java index b41116e7a2..6c5b49f36a 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMEngine.java @@ -1,8 +1,9 @@ package org.bouncycastle.pqc.crypto.mlkem; import java.security.SecureRandom; +import java.util.Arrays; -import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Util; class MLKEMEngine { @@ -219,7 +220,7 @@ class MLKEMEngine s, hashedPublicKey, z, - Arrays.concatenate(d, z) + Util.concatenate(d, z) }; } @@ -274,7 +275,7 @@ class MLKEMEngine byte[] cmp = indCpa.encrypt(publicKey, Arrays.copyOfRange(buf, 0, KyberSymBytes), Arrays.copyOfRange(kr, KyberSymBytes, kr.length)); - boolean fail = !(Arrays.constantTimeAreEqual(cipherText, cmp)); + boolean fail = !(Util.constantTimeAreEqual(cipherText, cmp)); cmov(kr, implicit_rejection, KyberSymBytes, fail); @@ -294,7 +295,7 @@ class MLKEMEngine PolyVec polyVec = new PolyVec(this); byte[] seed = indCpa.unpackPublicKey(polyVec, publicKeyInput); byte[] ek = indCpa.packPublicKey(polyVec, seed); - if (!Arrays.areEqual(ek, publicKeyInput)) + if (!Arrays.equals(ek, publicKeyInput)) { throw new IllegalArgumentException("Input validation: Modulus check failed for ml-kem encapsulation"); } diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java index 6f19f835e6..e36c3c6ae3 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMExtractor.java @@ -1,9 +1,6 @@ package org.bouncycastle.pqc.crypto.mlkem; -import org.bouncycastle.crypto.EncapsulatedSecretExtractor; - public class MLKEMExtractor - implements EncapsulatedSecretExtractor { private final MLKEMPrivateKeyParameters privateKey; private final MLKEMEngine engine; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java index 61c693724d..cfeb318009 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMGenerator.java @@ -2,13 +2,11 @@ package org.bouncycastle.pqc.crypto.mlkem; import java.security.SecureRandom; -import org.bouncycastle.crypto.EncapsulatedSecretGenerator; import org.bouncycastle.crypto.SecretWithEncapsulation; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.pqc.crypto.util.SecretWithEncapsulationImpl; public class MLKEMGenerator - implements EncapsulatedSecretGenerator { // the source of randomness private final SecureRandom sr; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java index c5d7527c33..3e30319137 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMIndCpa.java @@ -1,6 +1,8 @@ package org.bouncycastle.pqc.crypto.mlkem; -import org.bouncycastle.util.Arrays; +import java.util.Arrays; + +import org.bouncycastle.util.Util; class MLKEMIndCpa { @@ -51,7 +53,7 @@ class MLKEMIndCpa // (p, sigma) <- G(d || k) byte[] buf = new byte[64]; - symmetric.hash_g(buf, Arrays.append(d, (byte)kyberK)); + symmetric.hash_g(buf, Util.append(d, (byte)kyberK)); byte[] publicSeed = new byte[32]; // p in docs byte[] noiseSeed = new byte[32]; // sigma in docs diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java index 22c1d686db..9f1d6d9dc2 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMKeyPairGenerator.java @@ -3,11 +3,9 @@ package org.bouncycastle.pqc.crypto.mlkem; import java.security.SecureRandom; import org.bouncycastle.crypto.AsymmetricCipherKeyPair; -import org.bouncycastle.crypto.AsymmetricCipherKeyPairGenerator; import org.bouncycastle.crypto.KeyGenerationParameters; public class MLKEMKeyPairGenerator - implements AsymmetricCipherKeyPairGenerator { private MLKEMParameters mlkemParams; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java index b59198a6f1..43d19653ba 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPrivateKeyParameters.java @@ -1,6 +1,8 @@ package org.bouncycastle.pqc.crypto.mlkem; -import org.bouncycastle.util.Arrays; +import java.util.Arrays; + +import org.bouncycastle.util.Util; public class MLKEMPrivateKeyParameters extends MLKEMKeyParameters @@ -21,12 +23,12 @@ public class MLKEMPrivateKeyParameters { super(true, params); - this.s = Arrays.clone(s); - this.hpk = Arrays.clone(hpk); - this.nonce = Arrays.clone(nonce); - this.t = Arrays.clone(t); - this.rho = Arrays.clone(rho); - this.seed = Arrays.clone(seed); + this.s = Util.clone(s); + this.hpk = Util.clone(hpk); + this.nonce = Util.clone(nonce); + this.t = Util.clone(t); + this.rho = Util.clone(rho); + this.seed = Util.clone(seed); } public MLKEMPrivateKeyParameters(MLKEMParameters params, byte[] encoding) @@ -64,17 +66,17 @@ public class MLKEMPrivateKeyParameters public byte[] getEncoded() { - return Arrays.concatenate(new byte[][]{ s, t, rho, hpk, nonce }); + return Util.concatenate(new byte[][]{ s, t, rho, hpk, nonce }); } public byte[] getHPK() { - return Arrays.clone(hpk); + return Util.clone(hpk); } public byte[] getNonce() { - return Arrays.clone(nonce); + return Util.clone(nonce); } public byte[] getPublicKey() @@ -89,21 +91,21 @@ public class MLKEMPrivateKeyParameters public byte[] getRho() { - return Arrays.clone(rho); + return Util.clone(rho); } public byte[] getS() { - return Arrays.clone(s); + return Util.clone(s); } public byte[] getT() { - return Arrays.clone(t); + return Util.clone(t); } public byte[] getSeed() { - return Arrays.clone(seed); + return Util.clone(seed); } } diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java index 5f2676d730..e2cc7afe2b 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/MLKEMPublicKeyParameters.java @@ -1,13 +1,15 @@ package org.bouncycastle.pqc.crypto.mlkem; -import org.bouncycastle.util.Arrays; +import java.util.Arrays; + +import org.bouncycastle.util.Util; public class MLKEMPublicKeyParameters extends MLKEMKeyParameters { static byte[] getEncoded(byte[] t, byte[] rho) { - return Arrays.concatenate(t, rho); + return Util.concatenate(t, rho); } final byte[] t; @@ -16,8 +18,8 @@ public class MLKEMPublicKeyParameters public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] t, byte[] rho) { super(false, params); - this.t = Arrays.clone(t); - this.rho = Arrays.clone(rho); + this.t = Util.clone(t); + this.rho = Util.clone(rho); } public MLKEMPublicKeyParameters(MLKEMParameters params, byte[] encoding) @@ -34,11 +36,11 @@ public class MLKEMPublicKeyParameters public byte[] getRho() { - return Arrays.clone(rho); + return Util.clone(rho); } public byte[] getT() { - return Arrays.clone(t); + return Util.clone(t); } } diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java index d6613e904f..d802840dd9 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/PolyVec.java @@ -1,6 +1,6 @@ package org.bouncycastle.pqc.crypto.mlkem; -import org.bouncycastle.util.Arrays; +import java.util.Arrays; class PolyVec { diff --git a/router/java/src/org/bouncycastle/pqc/crypto/mlkem/package-info.java b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/package-info.java new file mode 100644 index 0000000000..3bcbfa374f --- /dev/null +++ b/router/java/src/org/bouncycastle/pqc/crypto/mlkem/package-info.java @@ -0,0 +1,6 @@ +/** + * This is a small portion of bouncycastle 1.80 for MLKEM, modified to reduce dependencies. + * MLDSA and common classes are in i2p.jar (core). + * @since 0.9.67 + */ +package org.bouncycastle.pqc.crypto.mlkem; diff --git a/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java b/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java index ab60d77409..9e07bd654d 100644 --- a/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java +++ b/router/java/src/org/bouncycastle/pqc/crypto/util/SecretWithEncapsulationImpl.java @@ -1,11 +1,12 @@ package org.bouncycastle.pqc.crypto.util; +import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import javax.security.auth.DestroyFailedException; import org.bouncycastle.crypto.SecretWithEncapsulation; -import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Util; public class SecretWithEncapsulationImpl implements SecretWithEncapsulation @@ -23,7 +24,7 @@ public class SecretWithEncapsulationImpl public byte[] getSecret() { - byte[] clone = Arrays.clone(sessionKey); + byte[] clone = Util.clone(sessionKey); checkDestroyed(); @@ -32,7 +33,7 @@ public class SecretWithEncapsulationImpl public byte[] getEncapsulation() { - byte[] clone = Arrays.clone(cipher_text); + byte[] clone = Util.clone(cipher_text); checkDestroyed(); @@ -44,8 +45,8 @@ public class SecretWithEncapsulationImpl { if (!hasBeenDestroyed.getAndSet(true)) { - Arrays.clear(sessionKey); - Arrays.clear(cipher_text); + Arrays.fill(sessionKey, (byte) 0); + Arrays.fill(cipher_text, (byte) 0); } } diff --git a/router/java/src/org/bouncycastle/util/Memoable.java b/router/java/src/org/bouncycastle/util/Memoable.java index a6eede90c1..3ee7a0cce1 100644 --- a/router/java/src/org/bouncycastle/util/Memoable.java +++ b/router/java/src/org/bouncycastle/util/Memoable.java @@ -21,7 +21,6 @@ public interface Memoable * * @param other an object originally {@link #copy() copied} from an object of the same type as this instance. * @throws ClassCastException if the provided object is not of the correct type. - * @throws MemoableResetException if the other parameter is in some other way invalid. */ void reset(Memoable other); } diff --git a/router/java/src/org/bouncycastle/util/Util.java b/router/java/src/org/bouncycastle/util/Util.java new file mode 100644 index 0000000000..20873b3e79 --- /dev/null +++ b/router/java/src/org/bouncycastle/util/Util.java @@ -0,0 +1,52 @@ +package org.bouncycastle.util; + +import java.util.Arrays; + +import net.i2p.data.DataHelper; + +public class Util +{ + public static byte[] clone(byte[] a) + { + return Arrays.copyOf(a, a.length); + } + + public static byte[] concatenate(byte[][] arrays) + { + int size = 0; + for (int i = 0; i != arrays.length; i++) + { + size += arrays[i].length; + } + + byte[] rv = new byte[size]; + + int offSet = 0; + for (int i = 0; i != arrays.length; i++) + { + System.arraycopy(arrays[i], 0, rv, offSet, arrays[i].length); + offSet += arrays[i].length; + } + + return rv; + } + + public static byte[] concatenate(byte[] a, byte[] b) + { + byte[] rv = Arrays.copyOf(a, a.length + b.length); + System.arraycopy(b, 0, rv, a.length, b.length); + return rv; + } + + public static byte[] append(byte[] a, byte b) + { + byte[] rv = Arrays.copyOf(a, a.length + 1); + rv[a.length] = b; + return rv; + } + + public static boolean constantTimeAreEqual(byte[] a, byte[] b) + { + return a.length == b.length && DataHelper.eqCT(a, 0, b, 0, a.length); + } +} From e3b176b676ec0c7e12c173b8dda194afec354fe0 Mon Sep 17 00:00:00 2001 From: zzz Date: Fri, 13 Jun 2025 08:56:52 -0400 Subject: [PATCH 3/3] I2P changes for ratchet --- LICENSE.txt | 4 + apps/i2ptunnel/jsp/editClient.jsi | 23 +- apps/i2ptunnel/jsp/editServer.jsi | 23 +- build.xml | 2 +- core/java/src/net/i2p/crypto/EncAlgo.java | 9 +- core/java/src/net/i2p/crypto/EncType.java | 99 ++++++- .../java/src/net/i2p/crypto/KeyGenerator.java | 8 + licenses/LICENSE-Bouncycastle.txt | 21 ++ .../noise/protocol/HandshakeState.java | 241 ++++++++++++++- .../noise/protocol/MLKEMDHState.java | 275 ++++++++++++++++++ .../southernstorm/noise/protocol/Pattern.java | 27 ++ .../noise/protocol/SymmetricState.java | 25 ++ .../java/src/net/i2p/router/LeaseSetKeys.java | 87 ++++++ .../router/client/ClientConnectionRunner.java | 30 +- .../crypto/ratchet/ECIESAEADEngine.java | 238 +++++++++++++-- .../router/crypto/ratchet/MuxedPQEngine.java | 98 +++++++ .../i2p/router/crypto/ratchet/MuxedPQSKM.java | 231 +++++++++++++++ .../i2p/router/crypto/ratchet/RatchetSKM.java | 35 ++- .../router/message/GarlicMessageBuilder.java | 19 +- .../router/message/GarlicMessageParser.java | 32 +- .../router/message/GarlicMessageReceiver.java | 15 +- .../OutboundClientMessageJobHelper.java | 2 +- .../kademlia/IterativeSearchJob.java | 2 +- .../networkdb/kademlia/MessageWrapper.java | 3 + .../router/tunnel/pool/BuildRequestor.java | 4 + .../net/i2p/router/tunnel/pool/TestJob.java | 3 + 26 files changed, 1483 insertions(+), 73 deletions(-) create mode 100644 licenses/LICENSE-Bouncycastle.txt create mode 100644 router/java/src/com/southernstorm/noise/protocol/MLKEMDHState.java create mode 100644 router/java/src/net/i2p/router/crypto/ratchet/MuxedPQEngine.java create mode 100644 router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java diff --git a/LICENSE.txt b/LICENSE.txt index 8af9cfc4cf..861c68cf4f 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -128,6 +128,10 @@ Public domain except as listed below: Copyright (C) 2006 The Android Open Source Project See licenses/LICENSE-Apache2.0.txt + ML-KEM: + Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org) + See licenses/LICENSE-Bouncycastle.txt + Installer: (not included in distribution packages) diff --git a/apps/i2ptunnel/jsp/editClient.jsi b/apps/i2ptunnel/jsp/editClient.jsi index 3f2d3d7df1..10daa693bc 100644 --- a/apps/i2ptunnel/jsp/editClient.jsi +++ b/apps/i2ptunnel/jsp/editClient.jsi @@ -604,6 +604,9 @@ <% boolean has0 = editBean.hasEncType(curTunnel, 0); boolean has4 = editBean.hasEncType(curTunnel, 4); + boolean has5 = editBean.hasEncType(curTunnel, 5); + boolean has6 = editBean.hasEncType(curTunnel, 6); + boolean has7 = editBean.hasEncType(curTunnel, 7); %> > @@ -617,7 +620,25 @@ + ECIES-X25519 + ElGamal-2048 +<% + if (editBean.isAdvanced()) { +%> + + + + + + +<% + } // isAdvanced() +%> diff --git a/apps/i2ptunnel/jsp/editServer.jsi b/apps/i2ptunnel/jsp/editServer.jsi index 3012a15fae..60a8b40139 100644 --- a/apps/i2ptunnel/jsp/editServer.jsi +++ b/apps/i2ptunnel/jsp/editServer.jsi @@ -676,6 +676,9 @@ <% boolean has0 = editBean.hasEncType(curTunnel, 0); boolean has4 = editBean.hasEncType(curTunnel, 4); + boolean has5 = editBean.hasEncType(curTunnel, 5); + boolean has6 = editBean.hasEncType(curTunnel, 6); + boolean has7 = editBean.hasEncType(curTunnel, 7); String edisabled = canChangeEncType ? "" : " disabled=\"disabled\" "; %> @@ -690,7 +693,25 @@ + ECIES-X25519 + ElGamal-2048 +<% + if (editBean.isAdvanced()) { +%> + + + + + + +<% + } // isAdvanced() +%> diff --git a/build.xml b/build.xml index de8b84837f..9da1445fe2 100644 --- a/build.xml +++ b/build.xml @@ -887,7 +887,7 @@ windowtitle="I2P Anonymous Network - Java Documentation - API Version ${api.version}"> - + diff --git a/core/java/src/net/i2p/crypto/EncAlgo.java b/core/java/src/net/i2p/crypto/EncAlgo.java index cc2c533820..bc73da1da4 100644 --- a/core/java/src/net/i2p/crypto/EncAlgo.java +++ b/core/java/src/net/i2p/crypto/EncAlgo.java @@ -11,7 +11,14 @@ public enum EncAlgo { EC("EC"), /** @since 0.9.38 */ - ECIES("ECIES"); + ECIES("ECIES"), + + /** @since 0.9.67 */ + ECIES_MLKEM("ECIES-MLKEM"), + + /** @since 0.9.67 */ + ECIES_MLKEM_INT("ECIES-MLKEM-Internal"); + private final String name; diff --git a/core/java/src/net/i2p/crypto/EncType.java b/core/java/src/net/i2p/crypto/EncType.java index dda7f0d821..f9699d433a 100644 --- a/core/java/src/net/i2p/crypto/EncType.java +++ b/core/java/src/net/i2p/crypto/EncType.java @@ -52,14 +52,83 @@ public enum EncType { * Pubkey 32 bytes; privkey 32 bytes * @since 0.9.38 */ - ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38"); + ECIES_X25519(4, 32, 32, EncAlgo.ECIES, "EC/None/NoPadding", X25519_SPEC, "0.9.38"), + + /** + * Proposal 169. + * Pubkey 32 bytes; privkey 32 bytes + * @since 0.9.67 + */ + MLKEM512_X25519(5, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * Proposal 169. + * Pubkey 32 bytes; privkey 32 bytes + * @since 0.9.67 + */ + MLKEM768_X25519(6, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * Proposal 169. + * Pubkey 32 bytes; privkey 32 bytes + * @since 0.9.67 + */ + MLKEM1024_X25519(7, 32, 32, EncAlgo.ECIES_MLKEM, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * For internal use only (Alice side) + * Proposal 169. + * Pubkey 800 bytes; privkey 1632 bytes + * @since 0.9.67 + */ + MLKEM512_X25519_INT(100005, 800, 1632, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * For internal use only (Alice side) + * Proposal 169. + * Pubkey 1184 bytes; privkey 2400 bytes + * @since 0.9.67 + */ + MLKEM768_X25519_INT(100006, 1184, 2400, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * For internal use only (Alice side) + * Proposal 169. + * Pubkey 1568 bytes; privkey 3168 bytes + * @since 0.9.67 + */ + MLKEM1024_X25519_INT(100007, 1568, 3168, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * For internal use only (Bob side ciphertext) + * Proposal 169. + * Pubkey 768 bytes; privkey 0 + * @since 0.9.67 + */ + MLKEM512_X25519_CT(100008, 768, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * For internal use only (Bob side ciphertext) + * Proposal 169. + * Pubkey 1088 bytes; privkey 0 + * @since 0.9.67 + */ + MLKEM768_X25519_CT(100009, 1088, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"), + + /** + * For internal use only (Bob side ciphertext) + * Proposal 169. + * Pubkey 1568 bytes; privkey 0 + * @since 0.9.67 + */ + MLKEM1024_X25519_CT(100010, 1568, 0, EncAlgo.ECIES_MLKEM_INT, "EC/None/NoPadding", X25519_SPEC, "0.9.67"); private final int code, pubkeyLen, privkeyLen; private final EncAlgo base; private final String algoName, since; private final AlgorithmParameterSpec params; - private final boolean isAvail; + private final boolean isAvail, isPQ; /** * @@ -68,7 +137,7 @@ public enum EncType { */ EncType(int cod, int pubLen, int privLen, EncAlgo baseAlgo, String transformation, AlgorithmParameterSpec pSpec, String supportedSince) { - if (pubLen > 256) + if (pubLen > 256 && baseAlgo != EncAlgo.ECIES_MLKEM_INT) throw new IllegalArgumentException("fixup PublicKey for longer keys"); code = cod; pubkeyLen = pubLen; @@ -78,6 +147,7 @@ public enum EncType { params = pSpec; since = supportedSince; isAvail = x_isAvailable(); + isPQ = base == EncAlgo.ECIES_MLKEM; } /** the unique identifier for this type */ @@ -120,11 +190,16 @@ public enum EncType { } private boolean x_isAvailable() { - if (ELGAMAL_2048 == this) - return true; - // EC types are placeholders for now - if (base == EncAlgo.EC) - return false; + switch (base) { + case ELGAMAL: + return true; + + // EC types are placeholders for now + case EC: + // internal types + case ECIES_MLKEM_INT: + return false; + } try { getParams(); } catch (InvalidParameterSpecException e) { @@ -154,6 +229,14 @@ public enum EncType { return type.isAvailable(); } + /** + * @since 0.9.67 + * @return true if this is a PQ type + */ + public boolean isPQ() { + return isPQ; + } + private static final EncType[] BY_CODE; static { diff --git a/core/java/src/net/i2p/crypto/KeyGenerator.java b/core/java/src/net/i2p/crypto/KeyGenerator.java index 497807f4f4..d7364f4167 100644 --- a/core/java/src/net/i2p/crypto/KeyGenerator.java +++ b/core/java/src/net/i2p/crypto/KeyGenerator.java @@ -196,6 +196,9 @@ public final class KeyGenerator { break; case ECIES_X25519: + case MLKEM512_X25519: + case MLKEM768_X25519: + case MLKEM1024_X25519: byte[] bpriv = new byte[32]; do { _context.random().nextBytes(bpriv); @@ -238,6 +241,9 @@ public final class KeyGenerator { break; case ECIES_X25519: + case MLKEM512_X25519: + case MLKEM768_X25519: + case MLKEM1024_X25519: data = new byte[32]; Curve25519.eval(data, 0, priv.getData(), null); break; @@ -342,6 +348,7 @@ public final class KeyGenerator { } java.security.PublicKey pubkey = kp.getPublic(); java.security.PrivateKey privkey = kp.getPrivate(); + SimpleDataStructure[] keys = new SimpleDataStructure[2]; keys[0] = SigUtil.fromJavaKey(pubkey, type); keys[1] = SigUtil.fromJavaKey(privkey, type); @@ -476,6 +483,7 @@ public final class KeyGenerator { System.out.println(type + " private-to-public test PASSED"); else System.out.println(type + " private-to-public test FAILED"); + //System.out.println("privkey " + keys[1]); MessageDigest md = type.getDigestInstance(); for (int i = 0; i < runs; i++) { diff --git a/licenses/LICENSE-Bouncycastle.txt b/licenses/LICENSE-Bouncycastle.txt new file mode 100644 index 0000000000..bec737cc38 --- /dev/null +++ b/licenses/LICENSE-Bouncycastle.txt @@ -0,0 +1,21 @@ +/** + * The Bouncy Castle License + * + * Copyright (c) 2000-2023 The Legion Of The Bouncy Castle Inc. (https://www.bouncycastle.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ diff --git a/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java b/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java index 0a35ddcc22..0957aa6192 100644 --- a/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java +++ b/router/java/src/com/southernstorm/noise/protocol/HandshakeState.java @@ -39,12 +39,15 @@ public class HandshakeState implements Destroyable, Cloneable { private final boolean isInitiator; private DHState localKeyPair; private DHState localEphemeral; + private DHState localHybrid; private DHState remotePublicKey; private DHState remoteEphemeral; + private DHState remoteHybrid; private int action; private final int requirements; private int patternIndex; private boolean wasCloned; + private boolean isDestroyed; /** * Enumerated value that indicates that the handshake object @@ -139,6 +142,14 @@ public class HandshakeState implements Destroyable, Cloneable { public static final String protocolName3 = "Noise_N_25519_ChaChaPoly_SHA256"; /** SSU2 */ public static final String protocolName4 = "Noise_XKchaobfse+hs1+hs2+hs3_25519_ChaChaPoly_SHA256"; + /** + * Hybrid Ratchet + * @since 0.9.67 + */ + public static final String protocolName5 = "Noise_IKhfselg2_25519+MLKEM512_ChaChaPoly_SHA256"; + public static final String protocolName6 = "Noise_IKhfselg2_25519+MLKEM768_ChaChaPoly_SHA256"; + public static final String protocolName7 = "Noise_IKhfselg2_25519+MLKEM1024_ChaChaPoly_SHA256"; + private static final String prefix; private final String patternId; /** NTCP2 */ @@ -151,13 +162,24 @@ public class HandshakeState implements Destroyable, Cloneable { public static final String PATTERN_ID_N_NO_RESPONSE = "N!"; /** SSU2 */ public static final String PATTERN_ID_XK_SSU2 = "XK-SSU2"; - private static String dh; + /** Hybrid Base */ + private static final String PATTERN_ID_IKHFS = "IKhfs"; + /** + * Hybrid Ratchet + * @since 0.9.67 + */ + public static final String PATTERN_ID_IKHFS_512 = "IKhfs512"; + public static final String PATTERN_ID_IKHFS_768 = "IKhfs768"; + public static final String PATTERN_ID_IKHFS_1024 = "IKhfs1024"; + + private static final String dh; private static final String cipher; private static final String hash; private final short[] pattern; private static final short[] PATTERN_XK; private static final short[] PATTERN_IK; private static final short[] PATTERN_N; + private static final short[] PATTERN_IKHFS; static { // Parse the protocol name into its components. @@ -200,11 +222,28 @@ public class HandshakeState implements Destroyable, Cloneable { id = components[1].substring(0, 2); if (!PATTERN_ID_XK.equals(id)) throw new IllegalArgumentException(); + // IK Hybrid + components = protocolName5.split("_"); + id = components[1].substring(0, 5); + if (!PATTERN_ID_IKHFS.equals(id)) + throw new IllegalArgumentException(); + PATTERN_IKHFS = Pattern.lookup(id); + if (PATTERN_IKHFS == null) + throw new IllegalArgumentException("Handshake pattern is not recognized"); + components = protocolName6.split("_"); + id = components[1].substring(0, 5); + if (!PATTERN_ID_IKHFS.equals(id)) + throw new IllegalArgumentException(); + components = protocolName7.split("_"); + id = components[1].substring(0, 5); + if (!PATTERN_ID_IKHFS.equals(id)) + throw new IllegalArgumentException(); } /** * Creates a new Noise handshake. * Noise protocol name is hardcoded. + * Not for PQ Alice side. * * @param patternId XK, IK, or N * @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER. @@ -217,6 +256,26 @@ public class HandshakeState implements Destroyable, Cloneable { * that is specified in the protocolName is not supported. */ public HandshakeState(String patternId, int role, KeyFactory xdh) throws NoSuchAlgorithmException + { + this(patternId, role, xdh, null); + } + + /** + * Creates a new Noise handshake. + * Noise protocol name is hardcoded. + * + * @param patternId XK, IK, or N + * @param role The role, HandshakeState.INITIATOR or HandshakeState.RESPONDER. + * @param xdh The key pair factory for ephemeral keys + * @param hdh The key pair factory for hybrid keys, Alice side only, or null for Bob or non-hybrid + * + * @throws IllegalArgumentException The protocolName is not + * formatted correctly, or the role is not recognized. + * + * @throws NoSuchAlgorithmException One of the cryptographic algorithms + * that is specified in the protocolName is not supported. + */ + public HandshakeState(String patternId, int role, KeyFactory xdh, KeyFactory hdh) throws NoSuchAlgorithmException { this.patternId = patternId; if (patternId.equals(PATTERN_ID_XK)) @@ -229,6 +288,10 @@ public class HandshakeState implements Destroyable, Cloneable { pattern = PATTERN_N; else if (patternId.equals(PATTERN_ID_XK_SSU2)) pattern = PATTERN_XK; + else if (patternId.equals(PATTERN_ID_IKHFS_512) || + patternId.equals(PATTERN_ID_IKHFS_768) || + patternId.equals(PATTERN_ID_IKHFS_1024)) + pattern = PATTERN_IKHFS; else throw new IllegalArgumentException("Handshake pattern is not recognized"); short flags = pattern[0]; @@ -256,10 +319,18 @@ public class HandshakeState implements Destroyable, Cloneable { localKeyPair = new Curve25519DHState(xdh); if ((flags & Pattern.FLAG_LOCAL_EPHEMERAL) != 0) localEphemeral = new Curve25519DHState(xdh); + if ((flags & Pattern.FLAG_LOCAL_HYBRID) != 0) { + if (isInitiator && hdh == null) + throw new IllegalArgumentException("Hybrid patterns require hybrid key generator"); + localHybrid = isInitiator ? new MLKEMDHState(hdh, patternId) : new MLKEMDHState(false, patternId); + } if ((flags & Pattern.FLAG_REMOTE_STATIC) != 0) remotePublicKey = new Curve25519DHState(xdh); if ((flags & Pattern.FLAG_REMOTE_EPHEMERAL) != 0) remoteEphemeral = new Curve25519DHState(xdh); + if ((flags & Pattern.FLAG_REMOTE_HYBRID) != 0) { + remoteHybrid = new MLKEMDHState(!isInitiator, patternId); + } } @@ -294,6 +365,23 @@ public class HandshakeState implements Destroyable, Cloneable { remotePublicKey = o.remotePublicKey.clone(); if (o.remoteEphemeral != null) remoteEphemeral = o.remoteEphemeral.clone(); + if (o.localHybrid != null) { + if (isInitiator) { + // always save Alice's local keys + localHybrid = o.localHybrid.clone(); + } else { + if (o.wasCloned) { + // new keys after first time for Bob + localHybrid = o.localHybrid.clone(); + } else { + // first time for Bob, use the eph. keys previously generated + localHybrid = o.localHybrid; + o.wasCloned = true; + } + } + } + if (o.remoteHybrid != null) + remoteHybrid = o.remoteHybrid.clone(); action = o.action; if (action == SPLIT || action == COMPLETE) throw new CloneNotSupportedException("clone after NSR"); @@ -419,6 +507,32 @@ public class HandshakeState implements Destroyable, Cloneable { return false; } + /** + * Gets the keypair object for the local hybrid key. + * + * I2P + * + * @return The keypair, or null if a local hybrid key is not required or has not been generated. + * @since 0.9.67 + */ + public DHState getLocalHybridKeyPair() + { + return localHybrid; + } + + /** + * Gets the keypair object for the remote hybrid key. + * + * I2P + * + * @return The keypair, or null if a remote hybrid key is not required or has not been generated. + * @since 0.9.67 + */ + public DHState getRemoteHybridKeyPair() + { + return remoteHybrid; + } + // Empty value for when the prologue is not supplied. private static final byte[] emptyPrologue = new byte [0]; @@ -472,17 +586,11 @@ public class HandshakeState implements Destroyable, Cloneable { if (isInitiator) { if ((requirements & LOCAL_PREMSG) != 0) symmetric.mixPublicKey(localKeyPair); - if ((requirements & FALLBACK_PREMSG) != 0) { - symmetric.mixPublicKey(remoteEphemeral); - } if ((requirements & REMOTE_PREMSG) != 0) symmetric.mixPublicKey(remotePublicKey); } else { if ((requirements & REMOTE_PREMSG) != 0) symmetric.mixPublicKey(remotePublicKey); - if ((requirements & FALLBACK_PREMSG) != 0) { - symmetric.mixPublicKey(localEphemeral); - } if ((requirements & LOCAL_PREMSG) != 0) symmetric.mixPublicKey(localKeyPair); } @@ -675,6 +783,54 @@ public class HandshakeState implements Destroyable, Cloneable { } break; + case Pattern.F: + { + // Generate a local hybrid keypair and add the public + // key to the message. If we are running fixed vector tests, + // then a fixed hybrid key may have already been provided. + if (localHybrid == null) + throw new IllegalStateException("Pattern definition error"); + byte[] shared = null; + if (isInitiator) { + // Only Alice generates a keypair + localHybrid.generateKeyPair(); + } else { + // Only Bob. We have to do the FF part here, + // so we split up mixDH() + // and do the localHybrid.calculate() first + // and the mixKey() after. + // mixDH(localHybrid, remoteHybrid) + // First part + len = localHybrid.getSharedKeyLength(); + shared = new byte [len]; + // this creates the ciphertext and puts it in localHybrid.publicKey + // IllegalArgumentException will be thrown here on bad remote key + localHybrid.calculate(shared, 0, remoteHybrid); + } + len = localHybrid.getPublicKeyLength(); + macLen = symmetric.getMACLength(); + if (space < (len + macLen)) + throw new ShortBufferException(); + localHybrid.getPublicKey(message, messagePosn); + messagePosn += symmetric.encryptAndHash(message, messagePosn, message, messagePosn, len); + if (!isInitiator) { + // Second part + // We do the rest of the FF part here while we have the shared key + symmetric.mixKey(shared, 0, shared.length); + Noise.destroy(shared); + } + } + break; + + + case Pattern.FF: + { + // DH operation with initiator and responder hybrid keys. + // We are Bob. + // This is a NOOP, we did the mixDH() in Pattern.F above. + } + break; + default: { // Unknown token code. Abort. @@ -857,6 +1013,35 @@ public class HandshakeState implements Destroyable, Cloneable { mixDH(localKeyPair, remotePublicKey); } break; + + case Pattern.F: + { + // Decrypt and read the remote hybrid ephemeral key. + if (remoteHybrid == null) + throw new IllegalStateException("Pattern definition error"); + len = remoteHybrid.getPublicKeyLength(); + macLen = symmetric.getMACLength(); + if (space < (len + macLen)) + throw new ShortBufferException(); + byte[] temp = new byte [len]; + try { + if (symmetric.decryptAndHash(message, messageOffset, temp, 0, len + macLen) != len) + throw new ShortBufferException(); + remoteHybrid.setPublicKey(temp, 0); + } finally { + Noise.destroy(temp); + } + messageOffset += len + macLen; + } + break; + + case Pattern.FF: + { + // DH operation with initiator and responder hybrid keys. + // We are Alice. + mixDH(localHybrid, remoteHybrid); + } + break; default: { @@ -953,17 +1138,22 @@ public class HandshakeState implements Destroyable, Cloneable { } @Override - public void destroy() { + public synchronized void destroy() { + isDestroyed = true; if (symmetric != null) symmetric.destroy(); if (localKeyPair != null) localKeyPair.destroy(); if (localEphemeral != null) localEphemeral.destroy(); + if (localHybrid != null) + localHybrid.destroy(); if (remotePublicKey != null) remotePublicKey.destroy(); if (remoteEphemeral != null) remoteEphemeral.destroy(); + if (remoteHybrid != null) + remoteHybrid.destroy(); } /** @@ -992,11 +1182,6 @@ public class HandshakeState implements Destroyable, Cloneable { requirements |= REMOTE_REQUIRED; requirements |= REMOTE_PREMSG; } - if ((flags & (Pattern.FLAG_REMOTE_EPHEM_REQ | - Pattern.FLAG_LOCAL_EPHEM_REQ)) != 0) { - if (isFallback) - requirements |= FALLBACK_PREMSG; - } return requirements; } @@ -1022,6 +1207,8 @@ public class HandshakeState implements Destroyable, Cloneable { */ @Override public synchronized HandshakeState clone() throws CloneNotSupportedException { + if (isDestroyed) + throw new IllegalStateException("destroyed"); return new HandshakeState(this); } @@ -1085,6 +1272,34 @@ public class HandshakeState implements Destroyable, Cloneable { } buf.append('\n'); + dh = localHybrid; + if (dh != null) { + buf.append("Local hybrid public key (e1/ekem1) : "); + if (dh != null && dh.hasPublicKey()) { + tmp = new byte[dh.getPublicKeyLength()]; + dh.getPublicKey(tmp, 0); + buf.append(tmp.length).append(" bytes "); + buf.append(net.i2p.data.Base64.encode(tmp)); + } else { + buf.append("null"); + } + buf.append('\n'); + } + + dh = remoteHybrid; + if (dh != null) { + buf.append("Remote hybrid public key (e1/ekem1) : "); + if (dh != null && dh.hasPublicKey()) { + tmp = new byte[dh.getPublicKeyLength()]; + dh.getPublicKey(tmp, 0); + buf.append(tmp.length).append(" bytes "); + buf.append(net.i2p.data.Base64.encode(tmp)); + } else { + buf.append("null"); + } + buf.append('\n'); + } + return buf.toString(); } } diff --git a/router/java/src/com/southernstorm/noise/protocol/MLKEMDHState.java b/router/java/src/com/southernstorm/noise/protocol/MLKEMDHState.java new file mode 100644 index 0000000000..9811886e92 --- /dev/null +++ b/router/java/src/com/southernstorm/noise/protocol/MLKEMDHState.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2016 Southern Storm Software, Pty Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package com.southernstorm.noise.protocol; + +import java.security.GeneralSecurityException; +import java.util.Arrays; + +import net.i2p.crypto.KeyFactory; +import net.i2p.crypto.KeyPair; +import net.i2p.crypto.EncType; +import net.i2p.router.crypto.pqc.MLKEM; + +/** + * Implementation of the MLKEM algorithm for the Noise protocol. + * + * @since 0.9.67 + */ +class MLKEMDHState implements DHState, Cloneable { + + private final EncType type; + private final byte[] publicKey; + private final byte[] privateKey; + private int mode; + private final KeyFactory _hdh; + + /** + * Bob local/remote or Alice remote side, do not call generateKeyPair() + * @param isAlice true for Bob remote side, false for Bob local side and Alice remote side + */ + public MLKEMDHState(boolean isAlice, String patternId) + { + this(isAlice, null, patternId); + } + + /** + * Alice local side + */ + public MLKEMDHState(KeyFactory hdh, String patternId) + { + this(true, hdh, patternId); + } + + /** + * Internal + */ + private MLKEMDHState(boolean isAlice, KeyFactory hdh, String patternId) + { + if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) { + type = isAlice ? EncType.MLKEM512_X25519_INT : EncType.MLKEM512_X25519_CT; + } else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) { + type = isAlice ? EncType.MLKEM768_X25519_INT : EncType.MLKEM768_X25519_CT; + } else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) { + type = isAlice ? EncType.MLKEM1024_X25519_INT : EncType.MLKEM1024_X25519_CT; + } else { + throw new IllegalArgumentException("Handshake pattern is not recognized"); + } + publicKey = new byte [type.getPubkeyLen()]; + privateKey = isAlice ? new byte [type.getPrivkeyLen()] : null; + mode = 0; + _hdh = hdh; + } + + @Override + public void destroy() { + clearKey(); + } + + @Override + public String getDHName() { + return "MLKEM"; + } + + /** + * Note: Alice/Bob sizes are different + */ + @Override + public int getPublicKeyLength() { + return type.getPubkeyLen(); + } + + /** + * Note: Alice/Bob sizes are different + * @return 0 for Bob + * @deprecated + */ + @Deprecated + @Override + public int getPrivateKeyLength() { + return type.getPrivkeyLen(); + } + + @Override + public int getSharedKeyLength() { + return 32; + } + + /** + * Alice local side ONLY + */ + @Override + public void generateKeyPair() { + if (_hdh == null) + throw new IllegalStateException("Don't keygen PQ on Bob side"); + KeyPair kp = _hdh.getKeys(); + System.arraycopy(kp.getPrivate().getData(), 0, privateKey, 0, type.getPrivkeyLen()); + System.arraycopy(kp.getPublic().getData(), 0, publicKey, 0, type.getPubkeyLen()); + mode = 0x03; + } + + @Override + public void getPublicKey(byte[] key, int offset) { + System.arraycopy(publicKey, 0, key, offset, type.getPubkeyLen()); + } + + @Override + public void setPublicKey(byte[] key, int offset) { + System.arraycopy(key, offset, publicKey, 0, type.getPubkeyLen()); + if (privateKey != null) + Arrays.fill(privateKey, (byte)0); + mode = 0x01; + } + + /** + * @deprecated + */ + @Deprecated + @Override + public void getPrivateKey(byte[] key, int offset) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated + */ + @Deprecated + @Override + public void setPrivateKey(byte[] key, int offset) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated + */ + @Deprecated + @Override + public void setKeys(byte[] privkey, int privoffset, byte[] pubkey, int puboffset) { + throw new UnsupportedOperationException(); + } + + @Override + public void setToNullPublicKey() { + Arrays.fill(publicKey, (byte)0); + if (privateKey != null) + Arrays.fill(privateKey, (byte)0); + mode = 0x01; + } + + @Override + public void clearKey() { + Noise.destroy(publicKey); + if (privateKey != null) + Noise.destroy(privateKey); + mode = 0; + } + + @Override + public boolean hasPublicKey() { + return (mode & 0x01) != 0; + } + + @Override + public boolean hasPrivateKey() { + return (mode & 0x02) != 0; + } + + @Override + public boolean isNullPublicKey() { + if ((mode & 0x01) == 0) + return false; + int temp = 0; + for (int index = 0; index < publicKey.length; ++index) + temp |= publicKey[index]; + return temp == 0; + } + + /** + * I2P + */ + @Override + public boolean hasEncodedPublicKey() { + return false; + } + + /** + * @deprecated + */ + @Deprecated + @Override + public void getEncodedPublicKey(byte[] key, int offset) { + throw new UnsupportedOperationException(); + } + + /** + * Side effect: If we are Bob, copies the ciphertext to our public key + * so it may be written out in the message. + * + * @throws IllegalArgumentException on bad public key modulus + */ + @Override + public void calculate(byte[] sharedKey, int offset, DHState publicDH) { + if (!(publicDH instanceof MLKEMDHState)) + throw new IllegalArgumentException("Incompatible DH algorithms"); + try { + if (hasPrivateKey()) { + // we are Alice + byte[] sk = MLKEM.decaps(type, ((MLKEMDHState)publicDH).publicKey, privateKey); + System.arraycopy(sk, 0, sharedKey, offset, sk.length); + } else if (!hasPublicKey()) { + // we are Bob + byte[][] rv = MLKEM.encaps(type, ((MLKEMDHState)publicDH).publicKey); + byte[] ct = rv[0]; + byte[] sk = rv[1]; + System.arraycopy(sk, 0, sharedKey, offset, sk.length); + setPublicKey(ct, 0); + } else { + throw new IllegalStateException(); + } + System.out.println("Calculated shared PQ key: " + net.i2p.data.Base64.encode(sharedKey, offset, 32)); + } catch (GeneralSecurityException gse) { + throw new IllegalArgumentException(gse); + } + } + + @Override + public void copyFrom(DHState other) { + if (!(other instanceof MLKEMDHState)) + throw new IllegalStateException("Mismatched DH key objects"); + if (other == this) + return; + MLKEMDHState dh = (MLKEMDHState)other; + if (dh.privateKey != null) + System.arraycopy(dh.privateKey, 0, privateKey, 0, type.getPrivkeyLen()); + if (dh.publicKey != null) + System.arraycopy(dh.publicKey, 0, publicKey, 0, type.getPubkeyLen()); + mode = dh.mode; + } + + /** + * I2P + */ + @Override + public MLKEMDHState clone() throws CloneNotSupportedException { + return (MLKEMDHState) super.clone(); + } +} diff --git a/router/java/src/com/southernstorm/noise/protocol/Pattern.java b/router/java/src/com/southernstorm/noise/protocol/Pattern.java index 2b205e4450..d809a23f36 100644 --- a/router/java/src/com/southernstorm/noise/protocol/Pattern.java +++ b/router/java/src/com/southernstorm/noise/protocol/Pattern.java @@ -97,6 +97,31 @@ class Pattern { SE }; + /** + * @since 0.9.67 + */ + private static final short[] noise_pattern_IKhfs = { + FLAG_LOCAL_STATIC | + FLAG_LOCAL_EPHEMERAL | + FLAG_LOCAL_HYBRID | + FLAG_REMOTE_STATIC | + FLAG_REMOTE_EPHEMERAL | + FLAG_REMOTE_HYBRID | + FLAG_REMOTE_REQUIRED, + + E, + ES, + F, + S, + SS, + FLIP_DIR, + E, + EE, + F, + FF, + SE + }; + /** * Look up the description information for a pattern. * @@ -111,6 +136,8 @@ class Pattern { return noise_pattern_XK; else if (name.equals("IK")) return noise_pattern_IK; + else if (name.equals("IKhfs")) + return noise_pattern_IKhfs; return null; } diff --git a/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java b/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java index 68f641e899..82864cedd6 100644 --- a/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java +++ b/router/java/src/com/southernstorm/noise/protocol/SymmetricState.java @@ -41,17 +41,26 @@ class SymmetricState implements Destroyable, Cloneable { private static final byte[] INIT_CK_IK; private static final byte[] INIT_CK_N; private static final byte[] INIT_CK_XK_SSU2; + private static final byte[] INIT_CK_IKHFS_512; + private static final byte[] INIT_CK_IKHFS_768; + private static final byte[] INIT_CK_IKHFS_1024; // precalculated hash of the hash of the Noise name = mixHash(nullPrologue) private static final byte[] INIT_HASH_XK = new byte[32]; private static final byte[] INIT_HASH_IK = new byte[32]; private static final byte[] INIT_HASH_N = new byte[32]; private static final byte[] INIT_HASH_XK_SSU2 = new byte[32]; + private static final byte[] INIT_HASH_IKHFS_512 = new byte[32]; + private static final byte[] INIT_HASH_IKHFS_768 = new byte[32]; + private static final byte[] INIT_HASH_IKHFS_1024 = new byte[32]; static { INIT_CK_XK = initHash(HandshakeState.protocolName); INIT_CK_IK = initHash(HandshakeState.protocolName2); INIT_CK_N = initHash(HandshakeState.protocolName3); INIT_CK_XK_SSU2 = initHash(HandshakeState.protocolName4); + INIT_CK_IKHFS_512 = initHash(HandshakeState.protocolName5); + INIT_CK_IKHFS_768 = initHash(HandshakeState.protocolName6); + INIT_CK_IKHFS_1024 = initHash(HandshakeState.protocolName7); try { MessageDigest md = Noise.createHash("SHA256"); md.update(INIT_CK_XK, 0, 32); @@ -62,6 +71,12 @@ class SymmetricState implements Destroyable, Cloneable { md.digest(INIT_HASH_N, 0, 32); md.update(INIT_CK_XK_SSU2, 0, 32); md.digest(INIT_HASH_XK_SSU2, 0, 32); + md.update(INIT_CK_IKHFS_512, 0, 32); + md.digest(INIT_HASH_IKHFS_512, 0, 32); + md.update(INIT_CK_IKHFS_768, 0, 32); + md.digest(INIT_HASH_IKHFS_768, 0, 32); + md.update(INIT_CK_IKHFS_1024, 0, 32); + md.digest(INIT_HASH_IKHFS_1024, 0, 32); Noise.releaseHash(md); } catch (Exception e) { throw new IllegalStateException(e); @@ -136,6 +151,15 @@ class SymmetricState implements Destroyable, Cloneable { } else if (patternId.equals(HandshakeState.PATTERN_ID_XK_SSU2)) { initCK = INIT_CK_XK_SSU2; initHash = INIT_HASH_XK_SSU2; + } else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_512)) { + initCK = INIT_CK_IKHFS_512; + initHash = INIT_HASH_IKHFS_512; + } else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_768)) { + initCK = INIT_CK_IKHFS_768; + initHash = INIT_HASH_IKHFS_768; + } else if (patternId.equals(HandshakeState.PATTERN_ID_IKHFS_1024)) { + initCK = INIT_CK_IKHFS_1024; + initHash = INIT_HASH_IKHFS_1024; } else { throw new IllegalArgumentException("Handshake pattern is not recognized"); } @@ -319,6 +343,7 @@ class SymmetricState implements Destroyable, Cloneable { */ public int decryptAndHash(byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException { + // NOTE: This updates the hash, even on failure System.arraycopy(h, 0, prev_h, 0, h.length); mixHash(ciphertext, ciphertextOffset, length); return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length); diff --git a/router/java/src/net/i2p/router/LeaseSetKeys.java b/router/java/src/net/i2p/router/LeaseSetKeys.java index ca55f9f5cb..f45460b64b 100644 --- a/router/java/src/net/i2p/router/LeaseSetKeys.java +++ b/router/java/src/net/i2p/router/LeaseSetKeys.java @@ -26,6 +26,7 @@ public class LeaseSetKeys { private final SigningPrivateKey _revocationKey; private final PrivateKey _decryptionKey; private final PrivateKey _decryptionKeyEC; + private final PrivateKey _decryptionKeyPQ; /** * Unmodifiable, ElGamal only @@ -43,6 +44,41 @@ public class LeaseSetKeys { */ public static final Set SET_BOTH = Collections.unmodifiableSet(EnumSet.of(EncType.ELGAMAL_2048, EncType.ECIES_X25519)); private static final Set SET_NONE = Collections.emptySet(); + /** + * Unmodifiable, PQ only + * @since public since 0.9.67 + */ + public static final Set SET_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM512_X25519)); + /** + * Unmodifiable, PQ only + * @since public since 0.9.67 + */ + public static final Set SET_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM768_X25519)); + /** + * Unmodifiable, PQ only + * @since public since 0.9.67 + */ + public static final Set SET_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.MLKEM1024_X25519)); + /** + * Unmodifiable, ECIES-X25519 and PQ only + * @since public since 0.9.67 + */ + public static final Set SET_EC_PQ1 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519)); + /** + * Unmodifiable, ECIES-X25519 and PQ only + * @since public since 0.9.67 + */ + public static final Set SET_EC_PQ2 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM768_X25519)); + /** + * Unmodifiable, ECIES-X25519 and PQ only + * @since public since 0.9.67 + */ + public static final Set SET_EC_PQ3 = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM1024_X25519)); + /** + * Unmodifiable, ECIES-X25519 and PQ only + * @since public since 0.9.67 + */ + public static final Set SET_EC_PQ_ALL = Collections.unmodifiableSet(EnumSet.of(EncType.ECIES_X25519, EncType.MLKEM512_X25519, EncType.MLKEM768_X25519, EncType.MLKEM1024_X25519)); /** * Client with a single key @@ -57,9 +93,15 @@ public class LeaseSetKeys { if (type == EncType.ELGAMAL_2048) { _decryptionKey = decryptionKey; _decryptionKeyEC = null; + _decryptionKeyPQ = null; } else if (type == EncType.ECIES_X25519) { _decryptionKey = null; _decryptionKeyEC = decryptionKey; + _decryptionKeyPQ = null; + } else if (type.isPQ()) { + _decryptionKey = null; + _decryptionKeyEC =null; + _decryptionKeyPQ = decryptionKey; } else { throw new IllegalArgumentException("Unknown type " + type); } @@ -68,9 +110,13 @@ public class LeaseSetKeys { /** * Client with multiple keys * + * The ONLY valid combinations are X25519 + ElG or X25519 + (MLKEM512 OR MLKEM768 OR MLKEM1024). + * Other combinations will throw IllegalArgumentException. + * * @param dest unused * @param revocationKey unused, may be null * @param decryptionKeys non-null, non-empty + * @throws IllegalArgumentException * @since 0.9.44 */ public LeaseSetKeys(Destination dest, SigningPrivateKey revocationKey, List decryptionKeys) { @@ -79,22 +125,32 @@ public class LeaseSetKeys { _revocationKey = revocationKey; PrivateKey elg = null; PrivateKey ec = null; + PrivateKey pq = null; for (PrivateKey pk : decryptionKeys) { EncType type = pk.getType(); if (type == EncType.ELGAMAL_2048) { if (elg != null) throw new IllegalArgumentException("Multiple keys same type"); + if (pq != null) + throw new IllegalArgumentException("Invalid combination ElG + PQ"); elg = pk; } else if (type == EncType.ECIES_X25519) { if (ec != null) throw new IllegalArgumentException("Multiple keys same type"); ec = pk; + } else if (type.isPQ()) { + if (pq != null) + throw new IllegalArgumentException("Multiple keys same type"); + if (elg != null) + throw new IllegalArgumentException("Invalid combination ElG + PQ"); + pq = pk; } else { throw new IllegalArgumentException("Unknown type " + type); } } _decryptionKey = elg; _decryptionKeyEC = ec; + _decryptionKeyPQ = pq; } /** @@ -128,9 +184,17 @@ public class LeaseSetKeys { return _decryptionKey; if (type == EncType.ECIES_X25519) return _decryptionKeyEC; + if (type.isPQ() && _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type) + return _decryptionKeyPQ; return null; } + /** + * @return PQ key (any type) or null if the LS does not support PQ + * @since 0.9.67 + */ + public PrivateKey getPQDecryptionKey() { return _decryptionKeyPQ; } + /** * Do we support this type of encryption? * @@ -141,6 +205,8 @@ public class LeaseSetKeys { return _decryptionKey != null; if (type == EncType.ECIES_X25519) return _decryptionKeyEC != null; + if (type.isPQ()) + return _decryptionKeyPQ != null && _decryptionKeyPQ.getType() == type; return false; } @@ -152,6 +218,27 @@ public class LeaseSetKeys { public Set getSupportedEncryption() { if (_decryptionKey != null) return (_decryptionKeyEC != null) ? SET_BOTH : SET_ELG; + if (_decryptionKeyPQ != null) { + if (_decryptionKeyEC != null) { + switch (_decryptionKeyPQ.getType()) { + case MLKEM512_X25519: + return SET_EC_PQ1; + case MLKEM768_X25519: + return SET_EC_PQ2; + case MLKEM1024_X25519: + return SET_EC_PQ3; + } + } else { + switch (_decryptionKeyPQ.getType()) { + case MLKEM512_X25519: + return SET_PQ1; + case MLKEM768_X25519: + return SET_PQ2; + case MLKEM1024_X25519: + return SET_PQ3; + } + } + } return (_decryptionKeyEC != null) ? SET_EC : SET_NONE; } } diff --git a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java index 3ae5442b67..0dff4a7a4f 100644 --- a/router/java/src/net/i2p/router/client/ClientConnectionRunner.java +++ b/router/java/src/net/i2p/router/client/ClientConnectionRunner.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import net.i2p.client.I2PClient; +import net.i2p.crypto.EncType; import net.i2p.crypto.SessionKeyManager; import net.i2p.data.DatabaseEntry; import net.i2p.data.DataHelper; @@ -50,6 +51,7 @@ import net.i2p.router.JobImpl; import net.i2p.router.RouterContext; import net.i2p.router.crypto.TransientSessionKeyManager; import net.i2p.router.crypto.ratchet.RatchetSKM; +import net.i2p.router.crypto.ratchet.MuxedPQSKM; import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; import net.i2p.util.ConcurrentHashSet; @@ -605,6 +607,8 @@ class ClientConnectionRunner { int thresh = TransientSessionKeyManager.LOW_THRESHOLD; boolean hasElg = false; boolean hasEC = false; + boolean hasPQ = false; + int pqType = 0; // router may be null in unit tests, avoid NPEs in ratchet // we won't actually be using any SKM anyway if (opts != null && _context.router() != null) { @@ -620,10 +624,18 @@ class ClientConnectionRunner { if (senc != null) { String[] senca = DataHelper.split(senc, ","); for (String sencaa : senca) { - if (sencaa.equals("0")) + if (sencaa.equals("0")) { hasElg = true; - else if (sencaa.equals("4")) + } else if (sencaa.equals("4")) { hasEC = true; + } else if (sencaa.equals("5") || sencaa.equals("6") || sencaa.equals("7")) { + if (hasPQ) { + _log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32()); + return SessionStatusMessage.STATUS_INVALID; + } + pqType = Integer.parseInt(sencaa); + hasPQ = true; + } } } else { hasElg = true; @@ -632,6 +644,10 @@ class ClientConnectionRunner { hasElg = true; } if (hasElg) { + if (hasPQ) { + _log.error("Bad encryption type combination in i2cp.leaseSetEncType for " + dest.toBase32()); + return SessionStatusMessage.STATUS_INVALID; + } TransientSessionKeyManager tskm = new TransientSessionKeyManager(_context, tags, thresh); if (hasEC) { RatchetSKM rskm = new RatchetSKM(_context, dest); @@ -639,6 +655,16 @@ class ClientConnectionRunner { } else { _sessionKeyManager = tskm; } + } else if (hasPQ) { + if (hasEC) { + // ECIES + RatchetSKM rskm1 = new RatchetSKM(_context, dest); + // PQ + RatchetSKM rskm2 = new RatchetSKM(_context, dest, EncType.getByCode(pqType)); + _sessionKeyManager = new MuxedPQSKM(rskm1, rskm2); + } else { + _sessionKeyManager = new RatchetSKM(_context, dest, EncType.getByCode(pqType)); + } } else { if (hasEC) { _sessionKeyManager = new RatchetSKM(_context, dest); diff --git a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java index 54ac7825b6..f608f445cb 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/ECIESAEADEngine.java @@ -16,6 +16,7 @@ import com.southernstorm.noise.protocol.HandshakeState; import net.i2p.crypto.EncType; import net.i2p.crypto.HKDF; +import net.i2p.crypto.KeyFactory; import net.i2p.crypto.SessionKeyManager; import net.i2p.data.Base64; import net.i2p.data.Certificate; @@ -33,6 +34,7 @@ import net.i2p.data.SessionTag; import net.i2p.data.i2np.DatabaseStoreMessage; import net.i2p.data.i2np.GarlicClove; import net.i2p.data.i2np.I2NPMessage; +import net.i2p.router.crypto.pqc.MLKEM; import static net.i2p.router.crypto.ratchet.RatchetPayload.*; import net.i2p.router.LeaseSetKeys; import net.i2p.router.Router; @@ -53,6 +55,7 @@ public final class ECIESAEADEngine { private final RouterContext _context; private final Log _log; private final MuxedEngine _muxedEngine; + private final MuxedPQEngine _muxedPQEngine; private final HKDF _hkdf; private final Elg2KeyFactory _edhThread; private boolean _isRunning; @@ -86,6 +89,24 @@ public final class ECIESAEADEngine { private static final String INFO_0 = "SessionReplyTags"; private static final String INFO_6 = "AttachPayloadKDF"; + // These are the min sizes for the MLKEM New Session Message. + // It contains an extra MLKEM key and MAC. + // 112 + private static final int NS_MLKEM_OVERHEAD = NS_OVERHEAD + MACLEN; + // 800 + 112 + 7 = 919 + private static final int MIN_NS_MLKEM512_SIZE = EncType.MLKEM512_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE; + // 1184 + 112 + 7 = 1303 + private static final int MIN_NS_MLKEM768_SIZE = EncType.MLKEM768_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE; + // 1568 + 112 + 7 = 1687 + private static final int MIN_NS_MLKEM1024_SIZE = EncType.MLKEM1024_X25519_INT.getPubkeyLen() + NS_MLKEM_OVERHEAD + DATETIME_SIZE; + // 856 + private static final int MIN_NSR_MLKEM512_SIZE = EncType.MLKEM512_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN; + // 1176 + private static final int MIN_NSR_MLKEM768_SIZE = EncType.MLKEM768_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN; + // 1656 + private static final int MIN_NSR_MLKEM1024_SIZE = EncType.MLKEM1024_X25519_CT.getPubkeyLen() + MIN_NSR_SIZE + MACLEN; + + /** * Caller MUST call startup() to get threaded generation. * Will still work without, will just generate inline. @@ -96,6 +117,7 @@ public final class ECIESAEADEngine { _context = ctx; _log = _context.logManager().getLog(ECIESAEADEngine.class); _muxedEngine = new MuxedEngine(ctx); + _muxedPQEngine = new MuxedPQEngine(ctx); _hkdf = new HKDF(ctx); _edhThread = new Elg2KeyFactory(ctx); @@ -147,6 +169,18 @@ public final class ECIESAEADEngine { return _muxedEngine.decrypt(data, elgKey, ecKey, keyManager); } + /** + * Try to decrypt the message with one or both of the given private keys + * + * @param ecKey must be EC, non-null + * @param pqKey must be PQ, non-null + * @return decrypted data or null on failure + * @since 0.9.67 + */ + public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException { + return _muxedPQEngine.decrypt(data, ecKey, pqKey, keyManager); + } + /** * Decrypt the message using the given private key * and using tags from the specified key manager. @@ -175,8 +209,7 @@ public final class ECIESAEADEngine { private CloveSet x_decrypt(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager) throws DataFormatException { - if (targetPrivateKey.getType() != EncType.ECIES_X25519) - throw new IllegalArgumentException(); + checkType(targetPrivateKey.getType()); if (data == null) { if (_log.shouldLog(Log.ERROR)) _log.error("Null data being decrypted?"); return null; @@ -267,14 +300,22 @@ public final class ECIESAEADEngine { if (shouldDebug) _log.debug("Decrypting ES with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes"); decrypted = decryptExistingSession(tag, data, key, targetPrivateKey, keyManager); - } else if (data.length >= MIN_NSR_SIZE) { - if (shouldDebug) - _log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes"); - decrypted = decryptNewSessionReply(tag, data, state, keyManager); } else { - decrypted = null; - if (_log.shouldWarn()) - _log.warn("ECIES decrypt fail, tag found but no state and too small for NSR: " + data.length + " bytes"); + // it's important not to attempt decryption for too-short packets, + // because Noise will destroy() the handshake state on failure, + // and we don't clone() on the first one, so it's fatal. + EncType type = targetPrivateKey.getType(); + int min = getMinNSRSize(type); + if (data.length >= min) { + if (shouldDebug) + _log.debug("Decrypting NSR with tag: " + st.toBase64() + " key: " + key + ": " + data.length + " bytes"); + decrypted = decryptNewSessionReply(tag, data, state, keyManager); + } else { + decrypted = null; + if (_log.shouldWarn()) + _log.warn("NSR decrypt fail, tag: " + st.toBase64() + " but packet too small: " + data.length + " bytes, min is " + + min + " on state " + state); + } } if (decrypted != null) { _context.statManager().updateFrequency("crypto.eciesAEAD.decryptExistingSession"); @@ -316,8 +357,10 @@ public final class ECIESAEADEngine { private CloveSet x_decryptSlow(byte data[], PrivateKey targetPrivateKey, RatchetSKM keyManager) throws DataFormatException { CloveSet decrypted; + EncType type = targetPrivateKey.getType(); + int minns = getMinNSSize(type); boolean isRouter = keyManager.getDestination() == null; - if (data.length >= MIN_NS_SIZE || (isRouter && data.length >= MIN_NS_N_SIZE)) { + if (data.length >= minns || (isRouter && data.length >= MIN_NS_N_SIZE)) { if (isRouter) decrypted = decryptNewSession_N(data, targetPrivateKey, keyManager); else @@ -333,11 +376,116 @@ public final class ECIESAEADEngine { } else { decrypted = null; if (_log.shouldDebug()) - _log.debug("ECIES decrypt fail, too small for NS: " + data.length + " bytes"); + _log.debug("ECIES decrypt fail, too small for NS: " + data.length + " bytes, min is " + + minns + " for type " + type); } return decrypted; } + /** + * @throws IllegalArgumentException if unsupported + * @since 0.9.67 + */ + private static void checkType(EncType type) { + switch(type) { + case ECIES_X25519: + case MLKEM512_X25519: + case MLKEM768_X25519: + case MLKEM1024_X25519: + return; + default: + throw new IllegalArgumentException("Unsupported key type " + type); + } + } + + /** + * @since 0.9.67 + */ + private static String getNoisePattern(EncType type) { + switch(type) { + case ECIES_X25519: + return HandshakeState.PATTERN_ID_IK; + case MLKEM512_X25519: + return HandshakeState.PATTERN_ID_IKHFS_512; + case MLKEM768_X25519: + return HandshakeState.PATTERN_ID_IKHFS_768; + case MLKEM1024_X25519: + return HandshakeState.PATTERN_ID_IKHFS_1024; + default: + throw new IllegalArgumentException("No pattern for " + type); + } + } + + /** + * @since 0.9.67 + */ + private static KeyFactory getHybridKeyFactory(EncType type) { + switch(type) { + case MLKEM512_X25519: + return MLKEM.MLKEM512KeyFactory; + case MLKEM768_X25519: + return MLKEM.MLKEM768KeyFactory; + case MLKEM1024_X25519: + return MLKEM.MLKEM1024KeyFactory; + default: + return null; + } + } + + /** + * @since 0.9.67 + */ + private static int getMinNSSize(EncType type) { + switch(type) { + case ECIES_X25519: + return MIN_NS_SIZE; + case MLKEM512_X25519: + return MIN_NS_MLKEM512_SIZE; + case MLKEM768_X25519: + return MIN_NS_MLKEM768_SIZE; + case MLKEM1024_X25519: + return MIN_NS_MLKEM1024_SIZE; + default: + throw new IllegalArgumentException("No pattern for " + type); + } + } + + /** + * @since 0.9.67 + */ + private static int getMinNSRSize(EncType type) { + switch(type) { + case ECIES_X25519: + return MIN_NSR_SIZE; + case MLKEM512_X25519: + return MIN_NSR_MLKEM512_SIZE; + case MLKEM768_X25519: + return MIN_NSR_MLKEM768_SIZE; + case MLKEM1024_X25519: + return MIN_NSR_MLKEM1024_SIZE; + default: + throw new IllegalArgumentException("No pattern for " + type); + } + } + + /** + * @since 0.9.67 + */ + private static Set getEncTypeSet(EncType type) { + switch(type) { + case ECIES_X25519: + return LeaseSetKeys.SET_EC; + case MLKEM512_X25519: + return LeaseSetKeys.SET_PQ1; + case MLKEM768_X25519: + return LeaseSetKeys.SET_PQ2; + case MLKEM1024_X25519: + return LeaseSetKeys.SET_PQ3; + default: + throw new IllegalArgumentException("No pattern for " + type); + } + } + /** * scenario 1: New Session Message * @@ -383,8 +531,12 @@ public final class ECIESAEADEngine { System.arraycopy(pk.getData(), 0, data, 0, KEYLEN); HandshakeState state; + EncType type = targetPrivateKey.getType(); try { - state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.RESPONDER, _edhThread); + String pattern = getNoisePattern(type); + // Bob does not need a key factory + //state = new HandshakeState(pattern, HandshakeState.RESPONDER, _edhThread, getHybridKeyFactory(type)); + state = new HandshakeState(pattern, HandshakeState.RESPONDER, _edhThread); } catch (GeneralSecurityException gse) { throw new IllegalStateException("bad proto", gse); } @@ -392,9 +544,13 @@ public final class ECIESAEADEngine { targetPrivateKey.toPublic().getData(), 0); state.start(); if (_log.shouldDebug()) - _log.debug("State before decrypt new session: " + state); + _log.debug("State before decrypt new session (" + data.length + " bytes) " + state); int payloadlen = data.length - (KEYLEN + KEYLEN + MACLEN + MACLEN); + DHState hyb = state.getRemoteHybridKeyPair(); + if (hyb != null) { + payloadlen -= hyb.getPublicKeyLength() + MACLEN; + } byte[] payload = new byte[payloadlen]; try { state.readMessage(data, 0, data.length, payload, 0); @@ -402,7 +558,7 @@ public final class ECIESAEADEngine { // we'll get this a lot on muxed SKM // logged at INFO in caller if (_log.shouldDebug()) - _log.debug("Decrypt fail NS, state at failure: " + state, gse); + _log.debug("Decrypt fail NS " + data.length + " bytes, state at failure: " + state, gse); // restore original data for subsequent ElG attempt System.arraycopy(xx, 0, data, 0, KEYLEN - 1); data[KEYLEN - 1] = xx31; @@ -467,7 +623,7 @@ public final class ECIESAEADEngine { state.destroy(); } else { // tell the SKM - PublicKey alice = new PublicKey(EncType.ECIES_X25519, alicePK); + PublicKey alice = new PublicKey(type, alicePK); keyManager.createSession(alice, null, state, null); setResponseTimerNS(alice, pc.cloveSet, keyManager); } @@ -638,8 +794,13 @@ public final class ECIESAEADEngine { state.mixHash(tag, 0, TAGLEN); if (_log.shouldDebug()) _log.debug("State after mixhash tag before decrypt new session reply: " + state); + int tmplen = 48; + DHState hyb = state.getRemoteHybridKeyPair(); + if (hyb != null) { + tmplen += hyb.getPublicKeyLength() + MACLEN; + } try { - state.readMessage(data, 8, 48, ZEROLEN, 0); + state.readMessage(data, 8, tmplen, ZEROLEN, 0); } catch (GeneralSecurityException gse) { if (_log.shouldWarn()) { _log.warn("Decrypt fail NSR part 1", gse); @@ -667,9 +828,16 @@ public final class ECIESAEADEngine { byte[] encpayloadkey = new byte[32]; _hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey); rcvr.initializeKey(encpayloadkey, 0); - byte[] payload = new byte[data.length - (TAGLEN + KEYLEN + MACLEN + MACLEN)]; + int off = TAGLEN + KEYLEN + MACLEN; + int plen = data.length - (TAGLEN + KEYLEN + MACLEN + MACLEN); + if (hyb != null) { + int len = hyb.getPublicKeyLength() + MACLEN; + off += len; + plen -= len; + } + byte[] payload = new byte[plen]; try { - rcvr.decryptWithAd(hash, data, TAGLEN + KEYLEN + MACLEN, payload, 0, payload.length + MACLEN); + rcvr.decryptWithAd(hash, data, off, payload, 0, plen + MACLEN); } catch (GeneralSecurityException gse) { if (_log.shouldWarn()) { _log.warn("Decrypt fail NSR part 2", gse); @@ -714,7 +882,7 @@ public final class ECIESAEADEngine { } // tell the SKM - PublicKey bob = new PublicKey(EncType.ECIES_X25519, bobPK); + PublicKey bob = new PublicKey(keyManager.getType(), bobPK); keyManager.updateSession(bob, oldState, state, null, split); if (pc == null) @@ -872,8 +1040,7 @@ public final class ECIESAEADEngine { private byte[] x_encrypt(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv, RatchetSKM keyManager, ReplyCallback callback) { - if (target.getType() != EncType.ECIES_X25519) - throw new IllegalArgumentException(); + checkType(target.getType()); if (Arrays.equals(target.getData(), NULLPK)) { if (_log.shouldWarn()) _log.warn("Zero static key target"); @@ -904,6 +1071,7 @@ public final class ECIESAEADEngine { } if (_log.shouldDebug()) _log.debug("Encrypting as NSR to " + target + " with tag " + re.tag.toBase64()); +// trash old state if this throws IAE??? return encryptNewSessionReply(cloves, target, state, re.tag, keyManager, callback); } byte rv[] = encryptExistingSession(cloves, target, re, callback, keyManager); @@ -943,9 +1111,13 @@ public final class ECIESAEADEngine { private byte[] encryptNewSession(CloveSet cloves, PublicKey target, Destination to, PrivateKey priv, RatchetSKM keyManager, ReplyCallback callback) { + EncType type = target.getType(); + if (type != priv.getType()) + throw new IllegalArgumentException("Key mismatch " + target + ' ' + priv); HandshakeState state; try { - state = new HandshakeState(HandshakeState.PATTERN_ID_IK, HandshakeState.INITIATOR, _edhThread); + String pattern = getNoisePattern(target.getType()); + state = new HandshakeState(pattern, HandshakeState.INITIATOR, _edhThread, getHybridKeyFactory(type)); } catch (GeneralSecurityException gse) { throw new IllegalStateException("bad proto", gse); } @@ -962,7 +1134,12 @@ public final class ECIESAEADEngine { byte[] payload = createPayload(cloves, cloves.getExpiration(), NS_OVERHEAD); - byte[] enc = new byte[KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN]; + int enclen = KEYLEN + KEYLEN + MACLEN + payload.length + MACLEN; + DHState hyb = state.getLocalHybridKeyPair(); + if (hyb != null) { + enclen += hyb.getPublicKeyLength() + MACLEN; + } + byte[] enc = new byte[enclen]; try { state.writeMessage(enc, 0, payload, 0, payload.length); } catch (GeneralSecurityException gse) { @@ -1071,7 +1248,12 @@ public final class ECIESAEADEngine { byte[] payload = createPayload(cloves, 0, NSR_OVERHEAD); // part 1 - tag and empty payload - byte[] enc = new byte[TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN]; + int enclen = TAGLEN + KEYLEN + MACLEN + payload.length + MACLEN; + DHState hyb = state.getLocalHybridKeyPair(); + if (hyb != null) { + enclen += hyb.getPublicKeyLength() + MACLEN; + } + byte[] enc = new byte[enclen]; System.arraycopy(tag, 0, enc, 0, TAGLEN); try { state.writeMessage(enc, TAGLEN, ZEROLEN, 0, 0); @@ -1103,8 +1285,12 @@ public final class ECIESAEADEngine { byte[] encpayloadkey = new byte[32]; _hkdf.calculate(split.k_ba.getData(), ZEROLEN, INFO_6, encpayloadkey); sender.initializeKey(encpayloadkey, 0); + int off = TAGLEN + KEYLEN + MACLEN; + if (hyb != null) { + off += hyb.getPublicKeyLength() + MACLEN; + } try { - sender.encryptWithAd(hash, payload, 0, enc, TAGLEN + KEYLEN + MACLEN, payload.length); + sender.encryptWithAd(hash, payload, 0, enc, off, payload.length); } catch (GeneralSecurityException gse) { if (_log.shouldWarn()) _log.warn("Encrypt fail NSR part 2", gse); @@ -1488,7 +1674,7 @@ public final class ECIESAEADEngine { return; if (!ls2.isCurrent(Router.CLOCK_FUDGE_FACTOR)) continue; - PublicKey pk = ls2.getEncryptionKey(LeaseSetKeys.SET_EC); + PublicKey pk = ls2.getEncryptionKey(getEncTypeSet(from.getType())); if (!from.equals(pk)) continue; if (!ls2.verifySignature()) diff --git a/router/java/src/net/i2p/router/crypto/ratchet/MuxedPQEngine.java b/router/java/src/net/i2p/router/crypto/ratchet/MuxedPQEngine.java new file mode 100644 index 0000000000..2d9eaec23b --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/ratchet/MuxedPQEngine.java @@ -0,0 +1,98 @@ +package net.i2p.router.crypto.ratchet; + +import net.i2p.crypto.EncAlgo; +import net.i2p.crypto.EncType; +import net.i2p.data.DataFormatException; +import net.i2p.data.PrivateKey; +import net.i2p.router.RouterContext; +import net.i2p.router.message.CloveSet; +import net.i2p.util.Log; + +/** + * Both EC and PQ + * + * Handles the actual decryption using the + * supplied keys and data. + * + * @since 0.9.67 + */ +final class MuxedPQEngine { + private final RouterContext _context; + private final Log _log; + + public MuxedPQEngine(RouterContext ctx) { + _context = ctx; + _log = _context.logManager().getLog(MuxedPQEngine.class); + } + + /** + * Decrypt the message with the given private keys + * + * @param ecKey must be EC, non-null + * @param pqKey must be PQ, non-null + * @return decrypted data or null on failure + */ + public CloveSet decrypt(byte data[], PrivateKey ecKey, PrivateKey pqKey, MuxedPQSKM keyManager) throws DataFormatException { + if (ecKey.getType() != EncType.ECIES_X25519 || + pqKey.getType().getBaseAlgorithm() != EncAlgo.ECIES_MLKEM) + throw new IllegalArgumentException(); + final boolean debug = _log.shouldDebug(); + CloveSet rv = null; + // Try in-order from fastest to slowest + boolean preferRatchet = keyManager.preferRatchet(); + if (preferRatchet) { + // Ratchet Tag + rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM()); + if (rv != null) + return rv; + if (debug) + _log.debug("Ratchet tag not found before PQ"); + } + // PQ + // Ratchet Tag + rv = _context.eciesEngine().decryptFast(data, pqKey, keyManager.getPQSKM()); + if (rv != null) + return rv; + if (debug) + _log.debug("PQ tag not found"); + if (!preferRatchet) { + // Ratchet Tag + rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM()); + if (rv != null) + return rv; + if (debug) + _log.debug("Ratchet tag not found after PQ"); + } + + if (preferRatchet) { + // Ratchet DH + rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM()); + boolean ok = rv != null; + keyManager.reportDecryptResult(true, ok); + if (ok) + return rv; + if (debug) + _log.debug("Ratchet NS decrypt failed before PQ"); + } + + // PQ DH + // Minimum size checks for the larger New Session message are in ECIESAEADEngine.x_decryptSlow(). + rv = _context.eciesEngine().decryptSlow(data, pqKey, keyManager.getPQSKM()); + boolean isok = rv != null; + keyManager.reportDecryptResult(false, isok); + if (isok) + return rv; + if (debug) + _log.debug("PQ NS decrypt failed"); + + if (!preferRatchet) { + // Ratchet DH + rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM()); + boolean ok = rv != null; + keyManager.reportDecryptResult(true, ok); + if (!ok && debug) + _log.debug("Ratchet NS decrypt failed after PQ"); + } + return rv; + } +} diff --git a/router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java b/router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java new file mode 100644 index 0000000000..dfb4ee7d76 --- /dev/null +++ b/router/java/src/net/i2p/router/crypto/ratchet/MuxedPQSKM.java @@ -0,0 +1,231 @@ +package net.i2p.router.crypto.ratchet; + +import java.io.IOException; +import java.io.Writer; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import net.i2p.I2PAppContext; +import net.i2p.crypto.EncType; +import net.i2p.crypto.TagSetHandle; +import net.i2p.crypto.SessionKeyManager; +import net.i2p.data.PublicKey; +import net.i2p.data.SessionKey; +import net.i2p.data.SessionTag; + +/** + * Both EC and PQ + * + * @since 0.9.67 + */ +public class MuxedPQSKM extends SessionKeyManager { + + private final RatchetSKM _ec; + private final RatchetSKM _pq; + private final AtomicInteger _ecCounter = new AtomicInteger(); + private final AtomicInteger _pqCounter = new AtomicInteger(); + // PQ is about this much slower than EC + private static final int PQ_SLOW_FACTOR = 2; + private static final int RESTART_COUNTERS = 500; + + public MuxedPQSKM(RatchetSKM ec, RatchetSKM pq) { + _ec = ec; + _pq = pq; + } + + public RatchetSKM getECSKM() { return _ec; } + + public RatchetSKM getPQSKM() { return _pq; } + + /** + * Should we try the Ratchet slow decrypt before PQ slow decrypt? + * Adaptive test based on previous mix of traffic for this SKM, + * as reported by reportDecryptResult(). + */ + boolean preferRatchet() { + int ec = _ecCounter.get(); + int pq = _pqCounter.get(); + if (ec > RESTART_COUNTERS / 10 && + pq > RESTART_COUNTERS / 10 && + ec + pq > RESTART_COUNTERS) { + _ecCounter.set(0); + _pqCounter.set(0); + return true; + } + return ec >= pq / PQ_SLOW_FACTOR; + } + + /** + * Report the result of a slow decrypt attempt. + * + * @param isRatchet true for EC, false for PQ + * @param success true for successful decrypt + */ + void reportDecryptResult(boolean isRatchet, boolean success) { + if (success) { + if (isRatchet) + _ecCounter.incrementAndGet(); + else + _pqCounter.incrementAndGet(); + } + } + + /** + * ElG only + */ + @Override + public SessionKey getCurrentKey(PublicKey target) { + return null; + } + + /** + * ElG only + */ + @Override + public SessionKey getCurrentOrNewKey(PublicKey target) { + return null; + } + + /** + * ElG only + */ + @Override + public void createSession(PublicKey target, SessionKey key) { + } + + /** + * ElG only + */ + @Override + public SessionKey createSession(PublicKey target) { + return null; + } + + /** + * ElG only + */ + @Override + public SessionTag consumeNextAvailableTag(PublicKey target, SessionKey key) { + return null; + } + + /** + * EC/PQ + */ + public RatchetEntry consumeNextAvailableTag(PublicKey target) { + EncType type = target.getType(); + if (type == EncType.ECIES_X25519) + return _ec.consumeNextAvailableTag(target); + else + return _pq.consumeNextAvailableTag(target); + } + + @Override + public int getTagsToSend() { return 0; }; + + @Override + public int getLowThreshold() { return 0; }; + + /** + * ElG only + */ + @Override + public boolean shouldSendTags(PublicKey target, SessionKey key) { + return false; + } + + /** + * ElG only + */ + @Override + public boolean shouldSendTags(PublicKey target, SessionKey key, int lowThreshold) { + return false; + } + + @Override + public int getAvailableTags(PublicKey target, SessionKey key) { + EncType type = target.getType(); + if (type == EncType.ECIES_X25519) + return _ec.getAvailableTags(target, key); + else + return _pq.getAvailableTags(target, key); + } + + @Override + public long getAvailableTimeLeft(PublicKey target, SessionKey key) { + EncType type = target.getType(); + if (type == EncType.ECIES_X25519) + return _ec.getAvailableTimeLeft(target, key); + else + return _pq.getAvailableTimeLeft(target, key); + } + + /** + * ElG only + */ + @Override + public TagSetHandle tagsDelivered(PublicKey target, SessionKey key, Set sessionTags) { + return null; + } + + /** + * ElG only + */ + @Override + public void tagsReceived(SessionKey key, Set sessionTags) { + } + + /** + * ElG only + */ + @Override + public void tagsReceived(SessionKey key, Set sessionTags, long expire) { + } + + /** + * EC only. + * One time session + * We do not support PQ one-time sessions on MuxedPQSKM. + * + * @param expire time from now + */ + public void tagsReceived(SessionKey key, RatchetSessionTag tag, long expire) { + _ec.tagsReceived(key, tag, expire); + } + + @Override + public SessionKey consumeTag(SessionTag tag) { + RatchetSessionTag rstag = new RatchetSessionTag(tag.getData()); + SessionKey rv = _ec.consumeTag(rstag); + if (rv == null) { + rv = _pq.consumeTag(rstag); + } + return rv; + } + + @Override + public void shutdown() { + _ec.shutdown(); + _pq.shutdown(); + } + + @Override + public void renderStatusHTML(Writer out) throws IOException { + _ec.renderStatusHTML(out); + _pq.renderStatusHTML(out); + } + + /** + * ElG only + */ + @Override + public void failTags(PublicKey target, SessionKey key, TagSetHandle ts) { + } + + /** + * ElG only + */ + @Override + public void tagsAcked(PublicKey target, SessionKey key, TagSetHandle ts) { + } +} diff --git a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java index d0e74d230a..ff6730ebd8 100644 --- a/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java +++ b/router/java/src/net/i2p/router/crypto/ratchet/RatchetSKM.java @@ -54,6 +54,7 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener private final HKDF _hkdf; private final DecayingHashSet _replayFilter; private final Destination _destination; + private final EncType _type; /** * Let outbound session tags sit around for this long before expiring them. @@ -83,7 +84,17 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener * @since 0.9.48 */ public RatchetSKM(RouterContext context) { - this(context, null); + this(context, null, EncType.ECIES_X25519); + } + + /** + * ECIES only. + * + * @param dest null for router's SKM only + * @since 0.9.48 + */ + public RatchetSKM(RouterContext context, Destination dest) { + this(context, dest, EncType.ECIES_X25519); } /** @@ -91,12 +102,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener * client manager. * * @param dest null for router's SKM only + * @param type the encryption type + * @since 0.9.67 */ - public RatchetSKM(RouterContext context, Destination dest) { + public RatchetSKM(RouterContext context, Destination dest, EncType type) { super(context); _log = context.logManager().getLog(RatchetSKM.class); _context = context; _destination = dest; + _type = type; _outboundSessions = new ConcurrentHashMap(64); _pendingOutboundSessions = new HashMap>(64); _inboundTagSets = new ConcurrentHashMap(128); @@ -146,6 +160,15 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener return _destination; } + /** + * The EncType for this SKM + * + * @since 0.9.67 + */ + public EncType getType() { + return _type; + } + /** RatchetTagSet */ private Set getRatchetTagSets() { synchronized (_inboundTagSets) { @@ -201,8 +224,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener */ boolean createSession(PublicKey target, Destination d, HandshakeState state, ReplyCallback callback) { EncType type = target.getType(); - if (type != EncType.ECIES_X25519) - throw new IllegalArgumentException("Bad public key type " + type); + if (type != _type) + throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type); OutboundSession sess = new OutboundSession(target, d, null, state, callback); boolean isInbound = state.getRole() == HandshakeState.RESPONDER; if (isInbound) { @@ -247,8 +270,8 @@ public class RatchetSKM extends SessionKeyManager implements SessionTagListener boolean updateSession(PublicKey target, HandshakeState oldState, HandshakeState state, ReplyCallback callback, SplitKeys split) { EncType type = target.getType(); - if (type != EncType.ECIES_X25519) - throw new IllegalArgumentException("Bad public key type " + type); + if (type != _type) + throw new IllegalArgumentException("Bad public key type " + type + " expected " + _type); boolean isInbound = state.getRole() == HandshakeState.RESPONDER; if (isInbound) { // we are Bob, NSR sent diff --git a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java index 7527d9a1fa..d10df77ee2 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageBuilder.java @@ -32,6 +32,7 @@ import net.i2p.data.router.RouterIdentity; import net.i2p.data.router.RouterInfo; import net.i2p.router.LeaseSetKeys; import net.i2p.router.RouterContext; +import net.i2p.router.crypto.ratchet.MuxedPQSKM; import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.crypto.ratchet.RatchetSKM; import net.i2p.router.crypto.ratchet.RatchetSessionTag; @@ -46,13 +47,13 @@ import net.i2p.util.Log; public class GarlicMessageBuilder { /** - * ELGAMAL_2048 only. + * ELGAMAL_2048 only; returns false for others * * @param local non-null; do not use this method for the router's SessionKeyManager * @param minTagOverride 0 for no override, > 0 to override SKM's settings */ static boolean needsTags(RouterContext ctx, PublicKey key, Hash local, int minTagOverride) { - if (key.getType() == EncType.ECIES_X25519) + if (LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType())) return false; SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(local); if (skm == null) @@ -275,7 +276,7 @@ public class GarlicMessageBuilder { } /** - * ECIES_X25519 only. + * ECIES_X25519 and PQ only. * Called by OCMJH only. * * @param ctx scope @@ -289,7 +290,8 @@ public class GarlicMessageBuilder { Hash from, Destination to, SessionKeyManager skm, ReplyCallback callback) { PublicKey key = config.getRecipientPublicKey(); - if (key.getType() != EncType.ECIES_X25519) + EncType type = key.getType(); + if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(type)) throw new IllegalArgumentException(); Log log = ctx.logManager().getLog(GarlicMessageBuilder.class); GarlicMessage msg = new GarlicMessage(ctx); @@ -300,7 +302,7 @@ public class GarlicMessageBuilder { log.warn("No LSK for " + from.toBase32()); return null; } - PrivateKey priv = lsk.getDecryptionKey(EncType.ECIES_X25519); + PrivateKey priv = lsk.getDecryptionKey(type); if (priv == null) { if (log.shouldWarn()) log.warn("No key for " + from.toBase32()); @@ -312,6 +314,9 @@ public class GarlicMessageBuilder { rskm = (RatchetSKM) skm; } else if (skm instanceof MuxedSKM) { rskm = ((MuxedSKM) skm).getECSKM(); + } else if (skm instanceof MuxedPQSKM) { + MuxedPQSKM mskm = (MuxedPQSKM) skm; + rskm = type.isPQ() ? mskm.getPQSKM() : mskm.getECSKM(); } else { if (log.shouldWarn()) log.warn("No SKM for " + from.toBase32()); @@ -338,7 +343,7 @@ public class GarlicMessageBuilder { /** * Encrypt from an anonymous source. - * ECIES_X25519 only. + * ECIES_X25519 only. PQ not supported. * Called by MessageWrapper only. * * @param ctx scope @@ -348,7 +353,7 @@ public class GarlicMessageBuilder { */ public static GarlicMessage buildECIESMessage(RouterContext ctx, GarlicConfig config) { PublicKey key = config.getRecipientPublicKey(); - if (key.getType() != EncType.ECIES_X25519) + if (!LeaseSetKeys.SET_EC_PQ_ALL.contains(key.getType())) throw new IllegalArgumentException(); Log log = ctx.logManager().getLog(GarlicMessageBuilder.class); GarlicMessage msg = new GarlicMessage(ctx); diff --git a/router/java/src/net/i2p/router/message/GarlicMessageParser.java b/router/java/src/net/i2p/router/message/GarlicMessageParser.java index 7aec9cafc0..0ddc911d70 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageParser.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageParser.java @@ -19,6 +19,7 @@ import net.i2p.data.PrivateKey; import net.i2p.data.i2np.GarlicClove; import net.i2p.data.i2np.GarlicMessage; import net.i2p.router.RouterContext; +import net.i2p.router.crypto.ratchet.MuxedPQSKM; import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.crypto.ratchet.RatchetSKM; import net.i2p.util.Log; @@ -44,7 +45,7 @@ public class GarlicMessageParser { } /** - * Supports both ELGAMAL_2048 and ECIES_X25519. + * Supports ELGAMAL_2048, ECIES_X25519, and PQ * * @param encryptionKey either type * @param skm use tags from this session key manager @@ -65,6 +66,8 @@ public class GarlicMessageParser { rskm = (RatchetSKM) skm; } else if (skm instanceof MuxedSKM) { rskm = ((MuxedSKM) skm).getECSKM(); + } else if (skm instanceof MuxedPQSKM) { + rskm = ((MuxedPQSKM) skm).getECSKM(); } else { if (_log.shouldWarn()) _log.warn("No SKM to decrypt ECIES"); @@ -80,6 +83,27 @@ public class GarlicMessageParser { _log.warn("ECIES decrypt fail"); return null; } + } else if (type.isPQ()) { + RatchetSKM rskm; + if (skm instanceof RatchetSKM) { + rskm = (RatchetSKM) skm; + } else if (skm instanceof MuxedPQSKM) { + rskm = ((MuxedPQSKM) skm).getPQSKM(); + } else { + if (_log.shouldWarn()) + _log.warn("No SKM to decrypt PQ"); + return null; + } + CloveSet rv = _context.eciesEngine().decrypt(encData, encryptionKey, rskm); + if (rv != null) { + if (_log.shouldDebug()) + _log.debug("PQ decrypt success, cloves: " + rv.getCloveCount()); + return rv; + } else { + if (_log.shouldWarn()) + _log.warn("PQ decrypt fail"); + return null; + } } else { if (_log.shouldWarn()) _log.warn("Can't decrypt with key type " + type); @@ -112,7 +136,7 @@ public class GarlicMessageParser { /** * Supports both ELGAMAL_2048 and ECIES_X25519. * - * @param elgKey must be ElG, non-null + * @param elgKey must be ElG OR PQ, non-null * @param ecKey must be EC, non-null * @param skm use tags from this session key manager * @return null on error @@ -125,6 +149,10 @@ public class GarlicMessageParser { if (skm instanceof MuxedSKM) { MuxedSKM mskm = (MuxedSKM) skm; rv = _context.eciesEngine().decrypt(encData, elgKey, ecKey, mskm); + } else if (skm instanceof MuxedPQSKM) { + MuxedPQSKM mskm = (MuxedPQSKM) skm; + // EC is first + rv = _context.eciesEngine().decrypt(encData, ecKey, elgKey, mskm); } else if (skm instanceof RatchetSKM) { // unlikely, if we have two keys we should have a MuxedSKM RatchetSKM rskm = (RatchetSKM) skm; diff --git a/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java b/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java index d2a1600cf7..6105f9c0de 100644 --- a/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java +++ b/router/java/src/net/i2p/router/message/GarlicMessageReceiver.java @@ -65,15 +65,24 @@ public class GarlicMessageReceiver { if (keys != null && skm != null) { decryptionKey = keys.getDecryptionKey(); decryptionKey2 = keys.getDecryptionKey(EncType.ECIES_X25519); - if (decryptionKey == null && decryptionKey2 == null) { + // this will return any of the PQ types + PrivateKey decryptionKey3 = keys.getPQDecryptionKey(); + if (decryptionKey == null && decryptionKey2 == null && decryptionKey3 == null) { if (_log.shouldWarn()) _log.warn("No key to decrypt for " + _clientDestination.toBase32()); return; } + // ElG + PQ disallowed if (decryptionKey == null) { // swap - decryptionKey = decryptionKey2; - decryptionKey2 = null; + if (decryptionKey3 != null) { + // PQ first if present + decryptionKey = decryptionKey3; + } else { + // EC only + decryptionKey = decryptionKey2; + decryptionKey2 = null; + } } } else { if (_log.shouldLog(Log.WARN)) diff --git a/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java b/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java index 0be6229041..e0fe82b31e 100644 --- a/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java +++ b/router/java/src/net/i2p/router/message/OutboundClientMessageJobHelper.java @@ -124,7 +124,7 @@ class OutboundClientMessageJobHelper { SessionKeyManager skm = ctx.clientManager().getClientSessionKeyManager(from); if (skm == null) return null; - boolean isECIES = recipientPK.getType() == EncType.ECIES_X25519; + boolean isECIES = recipientPK.getType() != EncType.ELGAMAL_2048; // force ack off if ECIES boolean ackInGarlic = isECIES ? false : requireAck; GarlicConfig config = createGarlicConfig(ctx, replyToken, expiration, recipientPK, dataClove, diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java b/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java index 844e3edd0a..f5d2cd8958 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/IterativeSearchJob.java @@ -359,7 +359,7 @@ public class IterativeSearchJob extends FloodSearchJob { } LeaseSetKeys lsk = ctx.keyManager().getKeys(_fromLocalDest); supportsRatchet = lsk != null && - lsk.isSupported(EncType.ECIES_X25519) && + (lsk.isSupported(EncType.ECIES_X25519) || lsk.getPQDecryptionKey() != null) && DatabaseLookupMessage.supportsRatchetReplies(ri); supportsElGamal = !supportsRatchet && lsk != null && diff --git a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java index 8d0468801f..63e0cee7a0 100644 --- a/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java +++ b/router/java/src/net/i2p/router/networkdb/kademlia/MessageWrapper.java @@ -17,6 +17,7 @@ import net.i2p.data.i2np.I2NPMessage; import net.i2p.data.router.RouterInfo; import net.i2p.router.RouterContext; import net.i2p.router.crypto.TransientSessionKeyManager; +import net.i2p.router.crypto.ratchet.MuxedPQSKM; import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.crypto.ratchet.RatchetSKM; import net.i2p.router.crypto.ratchet.RatchetSessionTag; @@ -242,6 +243,8 @@ public class MessageWrapper { rskm = (RatchetSKM) skm; } else if (skm instanceof MuxedSKM) { rskm = ((MuxedSKM) skm).getECSKM(); + } else if (skm instanceof MuxedPQSKM) { + rskm = ((MuxedPQSKM) skm).getECSKM(); } else { throw new IllegalStateException("skm not a ratchet " + skm); } diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java index 59285498b5..237fa6dafa 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildRequestor.java @@ -27,6 +27,7 @@ import net.i2p.router.TunnelInfo; import net.i2p.router.TunnelManagerFacade; import net.i2p.router.TunnelPoolSettings; import net.i2p.router.crypto.ratchet.RatchetSKM; +import net.i2p.router.crypto.ratchet.MuxedPQSKM; import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.networkdb.kademlia.MessageWrapper; import net.i2p.router.networkdb.kademlia.MessageWrapper.OneTimeSession; @@ -318,6 +319,9 @@ abstract class BuildRequestor { } else if (replySKM instanceof MuxedSKM) { MuxedSKM mskm = (MuxedSKM) replySKM; mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT); + } else if (replySKM instanceof MuxedPQSKM) { + MuxedPQSKM mskm = (MuxedPQSKM) replySKM; + mskm.tagsReceived(ots.key, ots.rtag, 2 * BUILD_MSG_TIMEOUT); } else { // non-EC client, shouldn't happen, checked at top of createTunnelBuildMessage() below if (log.shouldWarn()) diff --git a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java index f6a3528ef6..37a7f3abac 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java @@ -13,6 +13,7 @@ import net.i2p.router.OutNetMessage; import net.i2p.router.ReplyJob; import net.i2p.router.RouterContext; import net.i2p.router.TunnelInfo; +import net.i2p.router.crypto.ratchet.MuxedPQSKM; import net.i2p.router.crypto.ratchet.MuxedSKM; import net.i2p.router.crypto.ratchet.RatchetSessionTag; import net.i2p.router.crypto.ratchet.RatchetSKM; @@ -368,6 +369,8 @@ class TestJob extends JobImpl { rskm = (RatchetSKM) skm; } else if (skm instanceof MuxedSKM) { rskm = ((MuxedSKM) skm).getECSKM(); + } else if (skm instanceof MuxedPQSKM) { + rskm = ((MuxedPQSKM) skm).getECSKM(); } else { // shouldn't happen rskm = null;