From e000511b7cec8f371ce4dd6f54fde31b7d213a99 Mon Sep 17 00:00:00 2001
From: Paul Schaub 0
).
+ *
+ * @param enabled boolean
+ * @return this
+ */
+ public PublicKeyKeyEncryptionMethodGenerator setUseWildcardKeyID(boolean enabled)
+ {
+ this.useWildcardKeyID = enabled;
+
+ return this;
+ }
+
public byte[][] processSessionInfo(
byte[] encryptedSessionInfo)
throws PGPException
@@ -120,7 +135,16 @@ private byte[] convertToEncodedMPI(byte[] encryptedSessionInfo)
public ContainedPacket generate(int encAlgorithm, byte[] sessionInfo)
throws PGPException
{
- return new PublicKeyEncSessionPacket(pubKey.getKeyID(), pubKey.getAlgorithm(), processSessionInfo(encryptSessionInfo(pubKey, sessionInfo)));
+ long keyId;
+ if (useWildcardKeyID)
+ {
+ keyId = WILDCARD;
+ }
+ else
+ {
+ keyId = pubKey.getKeyID();
+ }
+ return new PublicKeyEncSessionPacket(keyId, pubKey.getAlgorithm(), processSessionInfo(encryptSessionInfo(pubKey, sessionInfo)));
}
abstract protected byte[] encryptSessionInfo(PGPPublicKey pubKey, byte[] sessionInfo)
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/WildcardKeyIDTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/WildcardKeyIDTest.java
new file mode 100644
index 0000000000..f4b65d6895
--- /dev/null
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/WildcardKeyIDTest.java
@@ -0,0 +1,131 @@
+package org.bouncycastle.openpgp.test;
+
+import org.bouncycastle.bcpg.ArmoredInputStream;
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
+import org.bouncycastle.openpgp.PGPEncryptedDataList;
+import org.bouncycastle.openpgp.PGPLiteralData;
+import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
+import org.bouncycastle.openpgp.PGPObjectFactory;
+import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.PublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
+import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
+import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.io.Streams;
+import org.bouncycastle.util.test.SimpleTest;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Date;
+
+public class WildcardKeyIDTest
+ extends SimpleTest
+{
+ private static final String KEY = "" +
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
+ "Comment: AD4E FA47 5E86 2A89 8C5E BC56 5D44 DBEB F4E4 117B\n" +
+ "\n" +
+ "lFgEZEkKfxYJKwYBBAHaRw8BAQdAB0cQLQkEcOU6jo3x6mND+McUV77OPUA2xqKA\n" +
+ "puAmuRYAAPwOLG5loKswF8IEBUm4OwITBEUMuhmhvGQaGu97JZufbQwKiIwEHxYK\n" +
+ "AD4FAmRJCn8JEF1E2+v05BF7FiEErU76R16GKomMXrxWXUTb6/TkEXsCngECmwEF\n" +
+ "FgIDAQAECwkIBwUVCgkICwAAPHQBAJfSmZKxlHDS1Y33umS8HpYjW/B9esRTf3bc\n" +
+ "Ub7/PtM3AP0SjA4rpQ0wNFbDpGfRXC6aBeVpZNzeoBmWBksWT0JSBpxdBGRJCn8S\n" +
+ "CisGAQQBl1UBBQEBB0BbkXZSB5nhn2eJZgej6UcfIcaVPd45upEGqd62QvQaIwMB\n" +
+ "CAcAAP9iwfvRk4aAsOYs5GFBXqslCJssU8W88oiHZivsZgiHCBEbiHUEGBYKAB0F\n" +
+ "AmRJCn8CngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRBdRNvr9OQRe81TAQDcvtfw\n" +
+ "vR5Ki/PTVDRcYrhc4cDw7MI0POfOY2JO25QcTwEAyFCmw5kg95P2XwXVaAraYuR8\n" +
+ "Z7Okda+X8ZczE4Fb8g+cWARkSQp/FgkrBgEEAdpHDwEBB0AOfElGRkuSVLs0yQiv\n" +
+ "pxxg92lBZ46d1tpjfPEQKVOdJgABALGt/JUY7QhnMap4cHLj/jGL4vcqIkfQyBUV\n" +
+ "mYKVhTwhD/aI1QQYFgoAfQUCZEkKfwKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAE\n" +
+ "GRYKAAYFAmRJCn8ACgkQARi+8u3za5yGwAEAkpAi4bXucKmr4jfE6oQMUhGJBkqt\n" +
+ "gp8LEqeFFkKYbhIA/iDA1yKlotXC4ifQ9JheoUcvu0nmwZ6AV1JgLNhL0eILAAoJ\n" +
+ "EF1E2+v05BF79x4BANMB7+I9vZTSQgbUVw/NebThGKHuvJ0tUayWkIM1j0hsAQD6\n" +
+ "yfialgAOd9q4TWt53oD3Z2aDo2dTnjP9GaABP+vJAw==\n" +
+ "=u/tO\n" +
+ "-----END PGP PRIVATE KEY BLOCK-----";
+
+ private static final String PLAINTEXT = "Hello, World!\n";
+
+ @Override
+ public String getName()
+ {
+ return "WildcardKeyIDTest";
+ }
+
+ @Override
+ public void performTest()
+ throws Exception
+ {
+ Charset utf8 = Charset.forName("UTF8");
+ ByteArrayInputStream keyBytesIn = new ByteArrayInputStream(KEY.getBytes(utf8));
+ ArmoredInputStream keyArmorIn = new ArmoredInputStream(keyBytesIn);
+ PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(keyArmorIn, new BcKeyFingerprintCalculator());
+ long encryptionKeyId = 7405306990825650521L;
+
+ // Encrypt message
+ PGPDataEncryptorBuilder dataEncBuilder = new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256);
+ PGPEncryptedDataGenerator encDataGen = new PGPEncryptedDataGenerator(dataEncBuilder);
+ PublicKeyKeyEncryptionMethodGenerator pkeMethodGen = new BcPublicKeyKeyEncryptionMethodGenerator(
+ secretKeys.getPublicKey(encryptionKeyId));
+ pkeMethodGen.setUseWildcardKeyID(true);
+ encDataGen.addMethod(pkeMethodGen);
+ ByteArrayOutputStream cipherTextOut = new ByteArrayOutputStream();
+ ArmoredOutputStream armorOut = new ArmoredOutputStream(cipherTextOut);
+ OutputStream encOut = encDataGen.open(armorOut, new byte[512]);
+ PGPLiteralDataGenerator litDataGen = new PGPLiteralDataGenerator();
+ OutputStream litOut = litDataGen.open(encOut, PGPLiteralDataGenerator.TEXT, "", new Date(0L), new byte[512]);
+
+ litOut.write(PLAINTEXT.getBytes(utf8));
+ litOut.flush();
+ litOut.close();
+
+ encOut.flush();
+ encOut.close();
+
+ armorOut.flush();
+ armorOut.close();
+
+ // Decrpyt
+ PGPSecretKey secretKey = secretKeys.getSecretKey(encryptionKeyId);
+ PGPDigestCalculatorProvider digestCalcProv = new BcPGPDigestCalculatorProvider();
+ PBESecretKeyDecryptor keyDecryptor = new BcPBESecretKeyDecryptorBuilder(digestCalcProv)
+ .build(null);
+ PGPPrivateKey privateKey = secretKey.extractPrivateKey(keyDecryptor);
+
+ ByteArrayInputStream cipherTextIn = new ByteArrayInputStream(cipherTextOut.toByteArray());
+ ArmoredInputStream armorIn = new ArmoredInputStream(cipherTextIn);
+ PGPObjectFactory objectFactory = new BcPGPObjectFactory(armorIn);
+ PGPEncryptedDataList encDataList = (PGPEncryptedDataList) objectFactory.nextObject();
+ PGPPublicKeyEncryptedData pkeData = (PGPPublicKeyEncryptedData) encDataList.get(0);
+
+ isEquals(PublicKeyKeyEncryptionMethodGenerator.WILDCARD, pkeData.getKeyID());
+
+ InputStream decryptedIn = pkeData.getDataStream(new BcPublicKeyDataDecryptorFactory(privateKey));
+ objectFactory = new BcPGPObjectFactory(decryptedIn);
+ PGPLiteralData literalData = (PGPLiteralData) objectFactory.nextObject();
+ ByteArrayOutputStream plainTextOut = new ByteArrayOutputStream();
+ Streams.pipeAll(literalData.getDataStream(), plainTextOut);
+
+ isEquals(PLAINTEXT, plainTextOut.toString());
+ }
+
+ public static void main(String[] args)
+ {
+ runTest(new WildcardKeyIDTest());
+ }
+}
From 8371fd28bd8327abd64ba87a9804e2ab0f9b7e7b Mon Sep 17 00:00:00 2001
From: Paul Schaub 2.1.2 Defects Fixed
+2.1.3 Additional Features and Functionality
+
+
2.2.1 Version
Release: 1.73
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/WildcardKeyIDTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/WildcardKeyIDTest.java
index f4b65d6895..43a4a6de51 100644
--- a/pg/src/test/java/org/bouncycastle/openpgp/test/WildcardKeyIDTest.java
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/WildcardKeyIDTest.java
@@ -1,5 +1,11 @@
package org.bouncycastle.openpgp.test;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Date;
+
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
@@ -23,16 +29,10 @@
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;
+import org.bouncycastle.util.Strings;
import org.bouncycastle.util.io.Streams;
import org.bouncycastle.util.test.SimpleTest;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.Charset;
-import java.util.Date;
-
public class WildcardKeyIDTest
extends SimpleTest
{
@@ -71,8 +71,7 @@ public String getName()
public void performTest()
throws Exception
{
- Charset utf8 = Charset.forName("UTF8");
- ByteArrayInputStream keyBytesIn = new ByteArrayInputStream(KEY.getBytes(utf8));
+ ByteArrayInputStream keyBytesIn = new ByteArrayInputStream(Strings.toByteArray(KEY));
ArmoredInputStream keyArmorIn = new ArmoredInputStream(keyBytesIn);
PGPSecretKeyRing secretKeys = new PGPSecretKeyRing(keyArmorIn, new BcKeyFingerprintCalculator());
long encryptionKeyId = 7405306990825650521L;
@@ -90,7 +89,7 @@ public void performTest()
PGPLiteralDataGenerator litDataGen = new PGPLiteralDataGenerator();
OutputStream litOut = litDataGen.open(encOut, PGPLiteralDataGenerator.TEXT, "", new Date(0L), new byte[512]);
- litOut.write(PLAINTEXT.getBytes(utf8));
+ litOut.write(Strings.toByteArray(PLAINTEXT));
litOut.flush();
litOut.close();
From 9e33fae2e5eb21320ee979e0901808aec35a88cf Mon Sep 17 00:00:00 2001
From: David Hook true if the packet to be decrypted has integrity
* checking enabled.
@@ -24,18 +28,28 @@ PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorit
throws PGPException;
/**
- * Constructs an AEAD data decryptor.
+ * Constructs a data decryptor for {@link AEADEncDataPacket AEAD Encrypted Data} packets.
+ * This method is used with OpenPGP v5 AEAD.
*
- * @param aeadAlgorithm the identifier of the {@link org.bouncycastle.bcpg.AEADAlgorithmTags encryption algorithm} to use.
- * @param iv the initialization vector to build the AEAD nonces from.
- * @param chunkSize the chunksize value for the AEAD encrypted chunks.
- * @param encAlgorithm the identifier of the {@link org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags encryption
- * algorithm} to decrypt with.
- * @param key the bytes of the key for the cipher.
+ * @param aeadEncDataPacket AEAD encrypted data packet
+ * @param sessionKey decrypted session key
+ * @return a data decryptor that can decrypt (and verify) streams of encrypted data.
+ * @throws PGPException if an error occurs initialising the decryption and integrity checking
+ * functions.
+ */
+ PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
+ throws PGPException;
+
+ /**
+ * Constructs a data decryptor for {@link SymmetricEncIntegrityPacket#VERSION_2 v2 SEIPD} packets.
+ * This method is used with OpenPGP v6 AEAD.
+ *
+ * @param seipd version 2 symmetrically encrypted integrity-protected data packet using AEAD.
+ * @param sessionKey decrypted session key
* @return a data decryptor that can decrypt (and verify) streams of encrypted data.
* @throws PGPException if an error occurs initialising the decryption and integrity checking
* functions.
*/
- PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
+ PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
throws PGPException;
}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADUtil.java
new file mode 100644
index 0000000000..1d2c86d3c0
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcAEADUtil.java
@@ -0,0 +1,570 @@
+package org.bouncycastle.openpgp.operator.bc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.bouncycastle.bcpg.AEADAlgorithmTags;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
+import org.bouncycastle.bcpg.AEADUtils;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.AESEngine;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.modes.EAXBlockCipher;
+import org.bouncycastle.crypto.modes.GCMBlockCipher;
+import org.bouncycastle.crypto.modes.OCBBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.openpgp.AEADUtil;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPSessionKey;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.util.Exceptions;
+import org.bouncycastle.util.Pack;
+import org.bouncycastle.util.io.Streams;
+
+public class BcAEADUtil
+ extends AEADUtil
+{
+
+ public static AEADBlockCipher createAEADCipher(int encAlgorithm, int aeadAlgorithm)
+ throws PGPException
+ {
+ if (encAlgorithm != SymmetricKeyAlgorithmTags.AES_128
+ && encAlgorithm != SymmetricKeyAlgorithmTags.AES_192
+ && encAlgorithm != SymmetricKeyAlgorithmTags.AES_256)
+ {
+ // Block Cipher must work on 16 byte blocks
+ throw new PGPException("AEAD only supported for AES based algorithms");
+ }
+
+ switch (aeadAlgorithm)
+ {
+ case AEADAlgorithmTags.EAX:
+ return new EAXBlockCipher(new AESEngine());
+ case AEADAlgorithmTags.OCB:
+ return new OCBBlockCipher(new AESEngine(), new AESEngine());
+ case AEADAlgorithmTags.GCM:
+ return new GCMBlockCipher(new AESEngine());
+ default:
+ throw new PGPException("unrecognised AEAD algorithm: " + aeadAlgorithm);
+ }
+ }
+
+ /**
+ * Create a decryptor for OpenPGP v5 AED (AEAD Encrypted Data) packets.
+ * This is type of packet is used by GnuPG.
+ * For version 2 SEIPD packets used in OpenPGP v6, see
+ * {@link #createOpenPgpV6DataDecryptor(SymmetricEncIntegrityPacket, PGPSessionKey)} instead.
+ *
+ * @param aeadEncDataPacket AEAD encrypted data packet
+ * @param sessionKey session key retrieved from a version 5 symmetric-key encrypted session key packet
+ * or version 3 public-key encrypted session key packet.
+ * @return decryptor for AEAD encrypted data packets
+ * @throws PGPException
+ */
+ static PGPDataDecryptor createOpenPgpV5DataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ final int aeadAlgorithm = aeadEncDataPacket.getAEADAlgorithm();
+ final byte[] iv = aeadEncDataPacket.getIV();
+ final int chunkSize = aeadEncDataPacket.getChunkSize();
+ final int encAlgorithm = sessionKey.getAlgorithm();
+ final byte[] key = sessionKey.getKey();
+ final byte[] aaData = aeadEncDataPacket.getAAData();
+
+ final KeyParameter secretKey = new KeyParameter(key);
+
+ final AEADBlockCipher c = createAEADCipher(encAlgorithm, aeadAlgorithm);
+
+ return new PGPDataDecryptor()
+ {
+ public InputStream getInputStream(InputStream in)
+ {
+ try
+ {
+ return new PGPAeadInputStream(true, in, c, secretKey, iv, encAlgorithm, aeadAlgorithm, chunkSize, aaData);
+ }
+ catch (IOException e)
+ {
+ throw Exceptions.illegalStateException("unable to open stream: " + e.getMessage(), e);
+ }
+ }
+
+ public int getBlockSize()
+ {
+ return c.getUnderlyingCipher().getBlockSize();
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+ };
+ }
+
+ /**
+ * Create a data decryptor for SEIPD v2 packets used in OpenPGP v6.
+ * Those are symmetrically encrypted integrity protected data packets that make use of AEAD.
+ *
+ * @param seipd version 2 symmetrically encrypted integrity-protected data packet
+ * @param sessionKey session key as retrieved from a version 6 symmetric- or public-key-encrypted session key packet.
+ * @return decryptor
+ * @throws PGPException
+ */
+ static PGPDataDecryptor createOpenPgpV6DataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ // We cannot handle v1 SEIPD packets in this method (OpenPGP v4)
+ if (seipd.getVersion() == SymmetricEncIntegrityPacket.VERSION_1)
+ {
+ throw new PGPException("SEIPD packet MUST be of version 2 or greater.");
+ }
+
+ final int cipherAlgo = seipd.getCipherAlgorithm();
+ final int aeadAlgo = seipd.getAeadAlgorithm();
+ final int chunkSize = seipd.getChunkSize();
+ final byte[] aaData = seipd.getAAData();
+
+ byte[][] messageKeyAndIv = deriveAndSplitMessageKeyAndIv(aeadAlgo, cipherAlgo,
+ sessionKey.getKey(), seipd.getSalt(), aaData);
+ byte[] messageKey = messageKeyAndIv[0];
+ final byte[] iv = messageKeyAndIv[1];
+
+ final KeyParameter secretKey = new KeyParameter(messageKey);
+ final AEADBlockCipher c = createAEADCipher(cipherAlgo, aeadAlgo);
+
+ return new PGPDataDecryptor()
+ {
+ public InputStream getInputStream(InputStream in)
+ {
+ try
+ {
+ return new PGPAeadInputStream(false, in, c, secretKey, iv, cipherAlgo, aeadAlgo, chunkSize, aaData);
+ }
+ catch (IOException e)
+ {
+ throw Exceptions.illegalStateException("unable to open stream: " + e.getMessage(), e);
+ }
+ }
+
+ public int getBlockSize()
+ {
+ return c.getUnderlyingCipher().getBlockSize();
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+ };
+ }
+
+ protected static class PGPAeadInputStream
+ extends InputStream
+ {
+ private final InputStream in;
+ private final byte[] buf;
+ private final AEADBlockCipher c;
+ private final KeyParameter secretKey;
+ private final byte[] aaData;
+ private final byte[] iv;
+ private final int chunkLength;
+ private final int tagLen;
+
+ private byte[] data;
+ private int dataOff;
+ private long chunkIndex = 0;
+ private long totalBytes = 0;
+ private final boolean isV5StyleAEAD;
+
+ /**
+ * InputStream for decrypting AEAD encrypted data.
+ *
+ * @param isV5StyleAEAD flavour of AEAD (OpenPGP v5 or v6)
+ * @param in underlying InputStream
+ * @param c decryption cipher
+ * @param secretKey decryption key
+ * @param iv initialization vector
+ * @param encAlgorithm symmetric cipher algorithm
+ * @param aeadAlgorithm AEAD algorithm
+ * @param chunkSize chunk size of the AEAD encryption
+ * @param aaData associated data
+ * @throws IOException
+ */
+ public PGPAeadInputStream(boolean isV5StyleAEAD, InputStream in,
+ AEADBlockCipher c,
+ KeyParameter secretKey,
+ byte[] iv,
+ int encAlgorithm,
+ int aeadAlgorithm,
+ int chunkSize,
+ byte[] aaData)
+ throws IOException
+ {
+ this.isV5StyleAEAD = isV5StyleAEAD;
+ this.in = in;
+ this.iv = iv;
+ this.chunkLength = (int)getChunkLength(chunkSize);
+ this.tagLen = AEADUtils.getAuthTagLength(aeadAlgorithm);
+ this.buf = new byte[chunkLength + tagLen + tagLen]; // allow room for chunk tag and message tag
+ this.c = c;
+ this.secretKey = secretKey;
+ this.aaData = aaData;
+
+ // prime with 2 * tag len bytes.
+ Streams.readFully(in, buf, 0, tagLen + tagLen);
+
+ // load the first block
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ public int read()
+ throws IOException
+ {
+ if (data != null && dataOff == data.length)
+ {
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ if (this.data == null)
+ {
+ return -1;
+ }
+
+ return data[dataOff++] & 0xff;
+ }
+
+ public int read(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (data != null && dataOff == data.length)
+ {
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ if (this.data == null)
+ {
+ return -1;
+ }
+
+ int supplyLen = Math.min(len, available());
+ System.arraycopy(data, dataOff, b, off, supplyLen);
+ dataOff += supplyLen;
+
+ return supplyLen;
+ }
+
+ public long skip(long n)
+ throws IOException
+ {
+ if (n <= 0)
+ {
+ return 0;
+ }
+
+ int skip = (int)Math.min(n, available());
+ dataOff += skip;
+ return skip;
+ }
+
+ public int available()
+ throws IOException
+ {
+ if (data != null && dataOff == data.length)
+ {
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ if (this.data == null)
+ {
+ return -1;
+ }
+
+ return data.length - dataOff;
+ }
+
+ private byte[] readBlock()
+ throws IOException
+ {
+ // we initialise with the first 16 bytes as there is an additional 16 bytes following
+ // the last chunk (which may not be the exact chunklength).
+ int dataLen = Streams.readFully(in, buf, tagLen + tagLen, chunkLength);
+ if (dataLen == 0)
+ {
+ return null;
+ }
+
+ byte[] adata = new byte[isV5StyleAEAD ? 13 : aaData.length];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+
+ if (isV5StyleAEAD)
+ {
+ xorChunkId(adata, chunkIndex);
+ }
+
+ byte[] decData = new byte[dataLen];
+ try
+ {
+ c.init(false, new AEADParameters(secretKey, 128, getNonce(iv, chunkIndex))); // always full tag.
+
+ c.processAADBytes(adata, 0, adata.length);
+
+ int len = c.processBytes(buf, 0, dataLen + tagLen, decData, 0);
+
+ c.doFinal(decData, len);
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException("exception processing chunk " + chunkIndex + ": " + e.getMessage());
+ }
+
+ totalBytes += decData.length;
+ chunkIndex++;
+
+ System.arraycopy(buf, dataLen + tagLen, buf, 0, tagLen); // copy back the "tag"
+
+ if (dataLen != chunkLength) // it's our last block
+ {
+ if (isV5StyleAEAD)
+ {
+ adata = new byte[13];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ xorChunkId(adata, chunkIndex);
+ }
+ else
+ {
+ adata = new byte[aaData.length + 8];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ System.arraycopy(Pack.longToBigEndian(totalBytes), 0, adata, aaData.length, 8);
+ }
+
+ try
+ {
+ c.init(false, new AEADParameters(secretKey, 128, getNonce(iv, chunkIndex))); // always full tag.
+
+ c.processAADBytes(adata, 0, adata.length);
+ if (isV5StyleAEAD)
+ {
+ c.processAADBytes(Pack.longToBigEndian(totalBytes), 0, 8);
+ }
+
+ c.processBytes(buf, 0, tagLen, buf, 0);
+
+ c.doFinal(buf, 0); // check final tag
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException("exception processing final tag: " + e.getMessage());
+ }
+ }
+ else
+ {
+ Streams.readFully(in, buf, tagLen, tagLen); // read the next tag bytes
+ }
+
+ return decData;
+ }
+ }
+
+ protected static class PGPAeadOutputStream
+ extends OutputStream
+ {
+ private final boolean isV5StyleAEAD;
+ private final OutputStream out;
+ private final byte[] data;
+ private final AEADBlockCipher c;
+ private final KeyParameter secretKey;
+ private final byte[] aaData;
+ private final byte[] iv;
+ private final int chunkLength;
+ private final int tagLen;
+
+ private int dataOff;
+ private long chunkIndex = 0;
+ private long totalBytes = 0;
+
+ /**
+ * OutputStream for AEAD encryption.
+ *
+ * @param isV5StyleAEAD flavour of AEAD (OpenPGP v5 or v6)
+ * @param out underlying OutputStream
+ * @param c AEAD cipher
+ * @param secretKey secret key
+ * @param iv initialization vector
+ * @param encAlgorithm encryption algorithm
+ * @param aeadAlgorithm aead algorithm
+ * @param chunkSize chunk size of the AEAD encryption
+ */
+ public PGPAeadOutputStream(boolean isV5StyleAEAD,
+ OutputStream out,
+ AEADBlockCipher c,
+ KeyParameter secretKey,
+ byte[] iv, int encAlgorithm,
+ int aeadAlgorithm,
+ int chunkSize)
+ {
+ this.isV5StyleAEAD = isV5StyleAEAD;
+ this.out = out;
+ this.iv = iv;
+ this.chunkLength = (int)getChunkLength(chunkSize);
+ this.tagLen = AEADUtils.getAuthTagLength(aeadAlgorithm);
+ this.data = new byte[chunkLength];
+ this.c = c;
+ this.secretKey = secretKey;
+
+ aaData = createAAD(isV5StyleAEAD, encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+
+ private byte[] createAAD(boolean isV5StyleAEAD, int encAlgorithm, int aeadAlgorithm, int chunkSize)
+ {
+ if (isV5StyleAEAD)
+ {
+ return AEADEncDataPacket.createAAData(AEADEncDataPacket.VERSION_1, encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+ else
+ {
+ return SymmetricEncIntegrityPacket.createAAData(SymmetricEncIntegrityPacket.VERSION_2, encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ if (dataOff == data.length)
+ {
+ writeBlock();
+ }
+ data[dataOff++] = (byte)b;
+ }
+
+ public void write(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (dataOff == data.length)
+ {
+ writeBlock();
+ }
+
+ if (len < data.length - dataOff)
+ {
+ System.arraycopy(b, off, data, dataOff, len);
+ dataOff += len;
+ }
+ else
+ {
+ int gap = data.length - dataOff;
+ System.arraycopy(b, off, data, dataOff, gap);
+ dataOff += gap;
+ writeBlock();
+
+ len -= gap;
+ off += gap;
+
+ while (len >= data.length)
+ {
+ System.arraycopy(b, off, data, 0, data.length);
+ dataOff = data.length;
+ writeBlock();
+ len -= data.length;
+ off += data.length;
+ }
+
+ if (len > 0)
+ {
+ System.arraycopy(b, off, data, 0, len);
+ dataOff = len;
+ }
+ }
+ }
+
+ public void close()
+ throws IOException
+ {
+ finish();
+ }
+
+ private void writeBlock()
+ throws IOException
+ {
+ boolean v5StyleAEAD = isV5StyleAEAD;
+
+ byte[] adata = v5StyleAEAD ? new byte[13] : new byte[aaData.length];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+
+ if (v5StyleAEAD)
+ {
+ xorChunkId(adata, chunkIndex);
+ }
+
+ try
+ {
+ c.init(true, new AEADParameters(secretKey, 128, getNonce(iv, chunkIndex))); // always full tag.
+ c.processAADBytes(adata, 0, adata.length);
+
+ int len = c.processBytes(data, 0, dataOff, data, 0);
+ out.write(data, 0, len);
+
+ len = c.doFinal(data, 0);
+ out.write(data, 0, len);
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException("exception processing chunk " + chunkIndex + ": " + e.getMessage());
+ }
+
+ totalBytes += dataOff;
+ chunkIndex++;
+ dataOff = 0;
+ }
+
+ private void finish()
+ throws IOException
+ {
+ if (dataOff > 0)
+ {
+ writeBlock();
+ }
+
+
+ byte[] adata;
+ boolean v5StyleAEAD = isV5StyleAEAD;
+ if (v5StyleAEAD)
+ {
+ adata = new byte[13];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ xorChunkId(adata, chunkIndex);
+ }
+ else
+ {
+ adata = new byte[aaData.length + 8];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ System.arraycopy(Pack.longToBigEndian(totalBytes), 0, adata, aaData.length, 8);
+ }
+ try
+ {
+ c.init(true, new AEADParameters(secretKey, 128, getNonce(iv, chunkIndex))); // always full tag.
+ c.processAADBytes(adata, 0, adata.length);
+ if (v5StyleAEAD)
+ {
+ c.processAADBytes(Pack.longToBigEndian(totalBytes), 0, 8);
+ }
+
+ c.doFinal(data, 0);
+ out.write(data, 0, tagLen); // output final tag
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new IOException("exception processing final tag: " + e.getMessage());
+ }
+ out.close();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
index 76ed06940a..c48bdda65b 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java
@@ -1,8 +1,21 @@
package org.bouncycastle.openpgp.operator.bc;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.bouncycastle.bcpg.SymmetricKeyUtils;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
+import org.bouncycastle.crypto.modes.AEADBlockCipher;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.HKDFParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
@@ -24,6 +37,17 @@ public BcPBEDataDecryptorFactory(char[] pass, BcPGPDigestCalculatorProvider calc
super(pass, calculatorProvider);
}
+ /**
+ * Recover the session key from a version 4 SKESK packet used in OpenPGP v4.
+ *
+ * @param keyAlgorithm the {@link SymmetricKeyAlgorithmTags encryption algorithm} used to
+ * encrypt the session data.
+ * @param key the key bytes for the encryption algorithm.
+ * @param secKeyData the encrypted session data to decrypt.
+ * @return session key
+ * @throws PGPException
+ */
+ @Override
public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData)
throws PGPException
{
@@ -58,6 +82,56 @@ public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData
}
}
+ @Override
+ public byte[] recoverAEADEncryptedSessionData(SymmetricKeyEncSessionPacket keyData, byte[] ikm)
+ throws PGPException
+ {
+ if (keyData.getVersion() < SymmetricKeyEncSessionPacket.VERSION_5)
+ {
+ throw new PGPException("SKESK packet MUST be version 5 or later.");
+ }
+
+ byte[] hkdfInfo = keyData.getAAData(); // Between v5 and v6, these bytes differ
+ int kekLen = SymmetricKeyUtils.getKeyLengthInOctets(keyData.getEncAlgorithm());
+ byte[] kek = new byte[kekLen];
+
+ // HKDF
+ // secretKey := HKDF_sha256(ikm, hkdfInfo).generate()
+ HKDFBytesGenerator hkdfGen = new HKDFBytesGenerator(new SHA256Digest());
+ hkdfGen.init(new HKDFParameters(ikm, null, hkdfInfo));
+ hkdfGen.generateBytes(kek, 0, kek.length);
+ final KeyParameter secretKey = new KeyParameter(kek);
+
+ // AEAD
+ AEADBlockCipher aead = BcAEADUtil.createAEADCipher(keyData.getEncAlgorithm(), keyData.getAeadAlgorithm());
+ int aeadMacLen = 128;
+ byte[] authTag = keyData.getAuthTag();
+ byte[] aeadIv = keyData.getIv();
+ byte[] encSessionKey = keyData.getSecKeyData();
+
+ // sessionData := AEAD(secretKey).decrypt(encSessionKey || authTag)
+ AEADParameters parameters = new AEADParameters(secretKey,
+ aeadMacLen, aeadIv, keyData.getAAData());
+ aead.init(false, parameters);
+ int sessionKeyLen = aead.getOutputSize(encSessionKey.length + authTag.length);
+ byte[] sessionData = new byte[sessionKeyLen];
+ int dataLen = aead.processBytes(encSessionKey, 0, encSessionKey.length, sessionData, 0);
+ dataLen += aead.processBytes(authTag, 0, authTag.length, sessionData, dataLen);
+
+ try
+ {
+ aead.doFinal(sessionData, dataLen);
+ }
+ catch (InvalidCipherTextException e)
+ {
+ throw new PGPException("Exception recovering session info", e);
+ }
+
+ return sessionData;
+ }
+
+ // OpenPGP v4
+ @Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
throws PGPException
{
@@ -65,10 +139,20 @@ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int enc
return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key);
}
-
- public PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
+
+ // OpenPGP v5
+ @Override
+ public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
throws PGPException
{
- return BcUtil.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
+ return BcAEADUtil.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey);
+ }
+
+ // OpenPGP v6
+ @Override
+ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return BcAEADUtil.createOpenPgpV6DataDecryptor(seipd, sessionKey);
}
}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
index 94ba85cb3f..7438356a09 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPublicKeyDataDecryptorFactory.java
@@ -3,8 +3,10 @@
import java.io.IOException;
import java.math.BigInteger;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedAsymmetricBlockCipher;
@@ -21,6 +23,7 @@
import org.bouncycastle.crypto.params.X25519PublicKeyParameters;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
+import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
import org.bouncycastle.openpgp.operator.PGPPad;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
@@ -42,6 +45,7 @@ public BcPublicKeyDataDecryptorFactory(PGPPrivateKey pgpPrivKey)
this.pgpPrivKey = pgpPrivKey;
}
+ @Override
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
throws PGPException
{
@@ -178,6 +182,8 @@ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
}
+ // OpenPGP v4
+ @Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
throws PGPException
{
@@ -186,9 +192,19 @@ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int enc
return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key);
}
- public PGPDataDecryptor createDataDecryptor(final int aeadAlgorithm, final byte[] iv, final int chunkSize, final int encAlgorithm, byte[] key)
+ // OpenPGP v5
+ @Override
+ public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
throws PGPException
{
- return BcUtil.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
+ return BcAEADUtil.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey);
+ }
+
+ // OpenPGP v6
+ @Override
+ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return BcAEADUtil.createOpenPgpV6DataDecryptor(seipd, sessionKey);
}
}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcSessionKeyDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcSessionKeyDataDecryptorFactory.java
index f8e876fd45..a6da0028f7 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcSessionKeyDataDecryptorFactory.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcSessionKeyDataDecryptorFactory.java
@@ -1,5 +1,7 @@
package org.bouncycastle.openpgp.operator.bc;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSessionKey;
@@ -19,11 +21,14 @@ public BcSessionKeyDataDecryptorFactory(PGPSessionKey sessionKey)
this.sessionKey = sessionKey;
}
+ @Override
public PGPSessionKey getSessionKey()
{
return sessionKey;
}
+ // OpenPGP v4
+ @Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
throws PGPException
{
@@ -31,10 +36,21 @@ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int enc
return BcUtil.createDataDecryptor(withIntegrityPacket, engine, key);
}
-
- public PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
+
+ // OpenPGP v5
+ @Override
+ public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
throws PGPException
{
- return BcUtil.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
+ return BcAEADUtil.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey);
}
+
+ // OpenPGP v6
+ @Override
+ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return BcAEADUtil.createOpenPgpV6DataDecryptor(seipd, sessionKey);
+ }
+
}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java
new file mode 100644
index 0000000000..21142375d4
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java
@@ -0,0 +1,596 @@
+package org.bouncycastle.openpgp.operator.jcajce;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.bcpg.AEADAlgorithmTags;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
+import org.bouncycastle.bcpg.AEADUtils;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.openpgp.AEADUtil;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPSessionKey;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.util.Exceptions;
+import org.bouncycastle.util.Pack;
+import org.bouncycastle.util.io.Streams;
+
+class JceAEADUtil
+ extends AEADUtil
+{
+
+ private final OperatorHelper helper;
+
+ public JceAEADUtil(OperatorHelper helper)
+ {
+ this.helper = helper;
+ }
+
+ /**
+ * Create a {@link PGPDataDecryptor} for decrypting AEAD encrypted OpenPGP v5 data packets.
+ *
+ * @param aeadEncDataPacket AEAD encrypted data packet
+ * @param sessionKey session key to decrypt the data
+ * @return decryptor
+ * @throws PGPException
+ */
+ PGPDataDecryptor createOpenPgpV5DataDecryptor(AEADEncDataPacket aeadEncDataPacket,
+ PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ final int aeadAlgorithm = aeadEncDataPacket.getAEADAlgorithm();
+ final byte[] iv = aeadEncDataPacket.getIV();
+ final int chunkSize = aeadEncDataPacket.getChunkSize();
+ final int encAlgorithm = sessionKey.getAlgorithm();
+ final byte[] key = sessionKey.getKey();
+ final byte[] aaData = aeadEncDataPacket.getAAData();
+ try
+ {
+ final SecretKey secretKey = new SecretKeySpec(key, PGPUtil.getSymmetricCipherName(encAlgorithm));
+
+ final Cipher c = createAEADCipher(encAlgorithm, aeadAlgorithm);
+
+ return new PGPDataDecryptor()
+ {
+ public InputStream getInputStream(InputStream in)
+ {
+ try
+ {
+ return new JceAEADUtil.PGPAeadInputStream(true, in, c, secretKey, iv, encAlgorithm, aeadAlgorithm, chunkSize, aaData);
+ }
+ catch (IOException e)
+ {
+ throw Exceptions.illegalStateException("unable to open stream: " + e.getMessage(), e);
+ }
+ }
+
+ public int getBlockSize()
+ {
+ return c.getBlockSize();
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+ };
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception creating cipher", e);
+ }
+ }
+
+ /**
+ * Create a {@link PGPDataDecryptor} for decrypting AEAD encrypted OpenPGP v6 data.
+ *
+ * @param seipd version 2 SEIPD packet
+ * @param sessionKey session key to decrypt the data
+ * @return decryptor
+ * @throws PGPException
+ */
+ PGPDataDecryptor createOpenPgpV6DataDecryptor(SymmetricEncIntegrityPacket seipd,
+ PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ final int cipherAlgo = seipd.getCipherAlgorithm();
+ final int aeadAlgo = seipd.getAeadAlgorithm();
+ final int chunkSize = seipd.getChunkSize();
+ final byte[] salt = seipd.getSalt();
+ final byte[] aaData = seipd.getAAData();
+
+
+ final byte[][] messageKeyAndIv = deriveAndSplitMessageKeyAndIv(aeadAlgo, cipherAlgo, sessionKey.getKey(), salt, aaData);
+ final byte[] messageKey = messageKeyAndIv[0];
+ final byte[] iv = messageKeyAndIv[1];
+
+ try
+ {
+ final SecretKey secretKey = new SecretKeySpec(messageKey, PGPUtil.getSymmetricCipherName(cipherAlgo));
+ final Cipher c = createAEADCipher(cipherAlgo, aeadAlgo);
+
+ return new PGPDataDecryptor()
+ {
+ public InputStream getInputStream(InputStream in)
+ {
+ try
+ {
+ return new JceAEADUtil.PGPAeadInputStream(false, in, c, secretKey, iv, cipherAlgo, aeadAlgo, chunkSize, aaData);
+ }
+ catch (IOException e)
+ {
+ throw Exceptions.illegalStateException("unable to open stream: " + e.getMessage(), e);
+ }
+ }
+
+ public int getBlockSize()
+ {
+ return c.getBlockSize();
+ }
+
+ public PGPDigestCalculator getIntegrityCalculator()
+ {
+ return new SHA1PGPDigestCalculator();
+ }
+ };
+ }
+ catch (PGPException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception creating cipher", e);
+ }
+ }
+
+ Cipher createAEADCipher(int encAlgorithm, int aeadAlgorithm)
+ throws PGPException
+ {
+ if (encAlgorithm != SymmetricKeyAlgorithmTags.AES_128
+ && encAlgorithm != SymmetricKeyAlgorithmTags.AES_192
+ && encAlgorithm != SymmetricKeyAlgorithmTags.AES_256)
+ {
+ // Block Cipher must work on 16 byte blocks
+ throw new PGPException("AEAD only supported for AES based algorithms");
+ }
+
+ String mode;
+ switch (aeadAlgorithm)
+ {
+ case AEADAlgorithmTags.EAX:
+ mode = "EAX";
+ break;
+ case AEADAlgorithmTags.OCB:
+ mode = "OCB";
+ break;
+ case AEADAlgorithmTags.GCM:
+ mode = "GCM";
+ break;
+ default:
+ throw new PGPException("encountered unknown AEAD algorithm: " + aeadAlgorithm);
+ }
+
+ String cName = PGPUtil.getSymmetricCipherName(encAlgorithm)
+ + "/" + mode + "/NoPadding";
+
+ return helper.createCipher(cName);
+ }
+
+
+ static class PGPAeadInputStream
+ extends InputStream
+ {
+ private final InputStream in;
+ private final byte[] buf;
+ private final Cipher c;
+ private final SecretKey secretKey;
+ private final byte[] aaData;
+ private final byte[] iv;
+ private final int chunkLength;
+ private final int aeadTagLength;
+
+ private byte[] data;
+ private int dataOff;
+ private long chunkIndex = 0;
+ private long totalBytes = 0;
+ private boolean v5StyleAEAD;
+
+ /**
+ * InputStream for decrypting AEAD encrypted data.
+ *
+ * @param isV5AEAD AEAD flavour (OpenPGP v5 or v6)
+ * @param in underlying input stream
+ * @param c AEAD cipher
+ * @param secretKey secret key
+ * @param iv initialization vector
+ * @param encAlgorithm encryption algorithm
+ * @param aeadAlgorithm AEAD algorithm
+ * @param chunkSize chunk size of the AEAD encryption
+ * @param aaData associated data
+ * @throws IOException
+ */
+ public PGPAeadInputStream(boolean isV5AEAD, InputStream in,
+ Cipher c,
+ SecretKey secretKey,
+ byte[] iv,
+ int encAlgorithm,
+ int aeadAlgorithm,
+ int chunkSize,
+ byte[] aaData)
+ throws IOException
+ {
+ this.v5StyleAEAD = isV5AEAD;
+ this.in = in;
+ this.iv = iv;
+ this.chunkLength = (int)getChunkLength(chunkSize);
+ this.aeadTagLength = AEADUtils.getAuthTagLength(aeadAlgorithm);
+ this.buf = new byte[chunkLength + aeadTagLength + aeadTagLength]; // allow room for chunk tag and message tag
+ this.c = c;
+ this.secretKey = secretKey;
+ this.aaData = aaData;
+
+ // prime with 2 * tag len bytes.
+ Streams.readFully(in, buf, 0, aeadTagLength + aeadTagLength);
+
+ // load the first block
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ public int read()
+ throws IOException
+ {
+ if (data != null && dataOff == data.length)
+ {
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ if (this.data == null)
+ {
+ return -1;
+ }
+
+ return data[dataOff++] & 0xff;
+ }
+
+ public int read(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (data != null && dataOff == data.length)
+ {
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ if (this.data == null)
+ {
+ return -1;
+ }
+
+ int supplyLen = Math.min(len, available());
+ System.arraycopy(data, dataOff, b, off, supplyLen);
+ dataOff += supplyLen;
+
+ return supplyLen;
+ }
+
+ public long skip(long n)
+ throws IOException
+ {
+ if (n <= 0)
+ {
+ return 0;
+ }
+
+ int skip = (int)Math.min(n, available());
+ dataOff += skip;
+ return skip;
+ }
+
+ public int available()
+ throws IOException
+ {
+ if (data != null && dataOff == data.length)
+ {
+ this.data = readBlock();
+ this.dataOff = 0;
+ }
+
+ if (this.data == null)
+ {
+ return -1;
+ }
+
+ return data.length - dataOff;
+ }
+
+ private byte[] readBlock()
+ throws IOException
+ {
+ // we initialise with the first 16 bytes as there is an additional 16 bytes following
+ // the last chunk (which may not be the exact chunklength).
+ int dataLen = Streams.readFully(in, buf, aeadTagLength + aeadTagLength, chunkLength);
+ if (dataLen == 0)
+ {
+ return null;
+ }
+
+ byte[] adata = new byte[v5StyleAEAD ? 13 : aaData.length];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ if (v5StyleAEAD)
+ {
+ xorChunkId(adata, chunkIndex);
+ }
+
+ byte[] decData;
+ try
+ {
+ c.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, getNonce(iv, chunkIndex))); // always full tag.
+ c.updateAAD(adata);
+
+ decData = c.doFinal(buf, 0, dataLen + aeadTagLength);
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new IOException("exception processing chunk " + chunkIndex + ": " + e.getMessage());
+ }
+
+ totalBytes += decData.length;
+ chunkIndex++;
+
+ System.arraycopy(buf, dataLen + aeadTagLength, buf, 0, aeadTagLength); // copy back the "tag"
+
+ if (dataLen != chunkLength) // it's our last block
+ {
+ if (v5StyleAEAD)
+ {
+ adata = new byte[13];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ xorChunkId(adata, chunkIndex);
+ }
+ else
+ {
+ adata = new byte[aaData.length + 8];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ System.arraycopy(Pack.longToBigEndian(totalBytes), 0, adata, aaData.length, 8);
+ }
+ try
+ {
+ c.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, getNonce(iv, chunkIndex)));
+ c.updateAAD(adata);
+ if (v5StyleAEAD)
+ {
+ c.updateAAD(Pack.longToBigEndian(totalBytes));
+ }
+
+ c.doFinal(buf, 0, aeadTagLength); // check final tag
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new IOException("exception processing final tag: " + e.getMessage());
+ }
+ }
+ else
+ {
+ Streams.readFully(in, buf, aeadTagLength, aeadTagLength); // read the next tag bytes
+ }
+
+ return decData;
+ }
+ }
+
+ static class PGPAeadOutputStream
+ extends OutputStream
+ {
+ private final boolean isV5AEAD;
+ private final OutputStream out;
+ private final byte[] data;
+ private final Cipher c;
+ private final SecretKey secretKey;
+ private final byte[] aaData;
+ private final byte[] iv;
+ private final int chunkLength;
+
+ private int dataOff;
+ private long chunkIndex = 0;
+ private long totalBytes = 0;
+
+ /**
+ * OutputStream for AEAD encryption.
+ *
+ * @param isV5AEAD isV5AEAD of AEAD (OpenPGP v5 or v6)
+ * @param out underlying OutputStream
+ * @param c AEAD cipher
+ * @param secretKey secret key
+ * @param iv initialization vector
+ * @param encAlgorithm encryption algorithm
+ * @param aeadAlgorithm AEAD algorithm
+ * @param chunkSize chunk size of the AEAD algorithm
+ */
+ public PGPAeadOutputStream(boolean isV5AEAD,
+ OutputStream out,
+ Cipher c,
+ SecretKey secretKey,
+ byte[] iv,
+ int encAlgorithm,
+ int aeadAlgorithm,
+ int chunkSize)
+ {
+ this.isV5AEAD = isV5AEAD;
+ this.out = out;
+ this.iv = iv;
+ this.chunkLength = (int)getChunkLength(chunkSize);
+ this.data = new byte[chunkLength];
+ this.c = c;
+ this.secretKey = secretKey;
+
+ this.aaData = createAAD(isV5AEAD, encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+
+ /**
+ * Create the associated data for the AEAD encryption.
+ * Since the associated data array differs between OpenPGP v5 and v6, we need to know the flavour.
+ *
+ * @param isV5AEAD true if flavour of AEAD OpenPGP v5
+ * @param encAlgorithm symmetric encryption algorithm
+ * @param aeadAlgorithm AEAD algorithm
+ * @param chunkSize chunk size
+ * @return associated data
+ */
+ private byte[] createAAD(boolean isV5AEAD, int encAlgorithm, int aeadAlgorithm, int chunkSize)
+ {
+ if (isV5AEAD)
+ {
+ return AEADEncDataPacket.createAAData(AEADEncDataPacket.VERSION_1,
+ encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+ else
+ {
+ return SymmetricEncIntegrityPacket.createAAData(SymmetricEncIntegrityPacket.VERSION_2,
+ encAlgorithm, aeadAlgorithm, chunkSize);
+ }
+ }
+
+ public void write(int b)
+ throws IOException
+ {
+ if (dataOff == data.length)
+ {
+ writeBlock();
+ }
+ data[dataOff++] = (byte)b;
+ }
+
+ public void write(byte[] b, int off, int len)
+ throws IOException
+ {
+ if (dataOff == data.length)
+ {
+ writeBlock();
+ }
+
+ if (len < data.length - dataOff)
+ {
+ System.arraycopy(b, off, data, dataOff, len);
+ dataOff += len;
+ }
+ else
+ {
+ int gap = data.length - dataOff;
+ System.arraycopy(b, off, data, dataOff, gap);
+ dataOff += gap;
+ writeBlock();
+
+ len -= gap;
+ off += gap;
+
+ while (len >= data.length)
+ {
+ System.arraycopy(b, off, data, 0, data.length);
+ dataOff = data.length;
+ writeBlock();
+ len -= data.length;
+ off += data.length;
+ }
+
+ if (len > 0)
+ {
+ System.arraycopy(b, off, data, 0, len);
+ dataOff = len;
+ }
+ }
+ }
+
+ public void close()
+ throws IOException
+ {
+ finish();
+ }
+
+ private void writeBlock()
+ throws IOException
+ {
+ boolean v5StyleAEAD = isV5AEAD;
+
+ byte[] adata = v5StyleAEAD ? new byte[13] : new byte[aaData.length];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ if (v5StyleAEAD)
+ {
+ xorChunkId(adata, chunkIndex);
+ }
+
+ try
+ {
+ c.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, getNonce(iv, chunkIndex))); // always full tag.
+ c.updateAAD(adata);
+
+ out.write(c.doFinal(data, 0, dataOff));
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new IOException("exception processing chunk " + chunkIndex + ": " + e.getMessage());
+ }
+
+ totalBytes += dataOff;
+ chunkIndex++;
+ dataOff = 0;
+ }
+
+ private void finish()
+ throws IOException
+ {
+ if (dataOff > 0)
+ {
+ writeBlock();
+ }
+
+ byte[] adata;
+ boolean v5StyleAEAD = isV5AEAD;
+ if (v5StyleAEAD)
+ {
+ adata = new byte[13];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ xorChunkId(adata, chunkIndex);
+ }
+ else
+ {
+ adata = new byte[aaData.length + 8];
+ System.arraycopy(aaData, 0, adata, 0, aaData.length);
+ System.arraycopy(Pack.longToBigEndian(totalBytes), 0, adata, aaData.length, 8);
+ }
+
+ try
+ {
+ c.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, getNonce(iv, chunkIndex)));
+ c.updateAAD(adata);
+ if (v5StyleAEAD)
+ {
+ c.updateAAD(Pack.longToBigEndian(totalBytes));
+ }
+
+ out.write(c.doFinal(aaData, 0, 0)); // output final tag
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new IOException("exception processing final tag: " + e.getMessage());
+ }
+ out.close();
+ }
+ }
+}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
index 80abfba50f..5e9ffce977 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
@@ -1,15 +1,26 @@
package org.bouncycastle.openpgp.operator.jcajce;
+import java.security.GeneralSecurityException;
import java.security.Provider;
import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
+import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.bouncycastle.bcpg.SymmetricKeyUtils;
+import org.bouncycastle.crypto.digests.SHA256Digest;
+import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
+import org.bouncycastle.crypto.params.HKDFParameters;
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
@@ -22,6 +33,7 @@
public class JcePBEDataDecryptorFactoryBuilder
{
private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private JceAEADUtil aeadHelper = new JceAEADUtil(helper);
private PGPDigestCalculatorProvider calculatorProvider;
/**
@@ -77,60 +89,124 @@ public JcePBEDataDecryptorFactoryBuilder setProvider(String providerName)
*/
public PBEDataDecryptorFactory build(char[] passPhrase)
{
- if (calculatorProvider == null)
- {
- try
- {
- calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder(helper).build();
- }
- catch (PGPException e)
- {
- throw new IllegalStateException("digest calculator provider cannot be built with current helper: " + e.getMessage());
- }
- }
- return new PBEDataDecryptorFactory(passPhrase, calculatorProvider)
- {
- public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData)
- throws PGPException
- {
- try
- {
- if (secKeyData != null && secKeyData.length > 0)
- {
- String cipherName = PGPUtil.getSymmetricCipherName(keyAlgorithm);
- Cipher keyCipher = helper.createCipher(cipherName + "/CFB/NoPadding");
-
- keyCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, cipherName), new IvParameterSpec(new byte[keyCipher.getBlockSize()]));
-
- return keyCipher.doFinal(secKeyData);
- }
- else
- {
- byte[] keyBytes = new byte[key.length + 1];
-
- keyBytes[0] = (byte)keyAlgorithm;
- System.arraycopy(key, 0, keyBytes, 1, key.length);
-
- return keyBytes;
- }
- }
- catch (Exception e)
- {
- throw new PGPException("Exception recovering session info", e);
- }
- }
-
- public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
- throws PGPException
- {
- return helper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
- }
-
- public PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
- throws PGPException
- {
- return helper.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
- }
- };
+ if (calculatorProvider == null)
+ {
+ try
+ {
+ calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder(helper).build();
+ }
+ catch (PGPException e)
+ {
+ throw new IllegalStateException("digest calculator provider cannot be built with current helper: " + e.getMessage());
+ }
+ }
+ return new PBEDataDecryptorFactory(passPhrase, calculatorProvider)
+ {
+ @Override
+ public byte[] recoverSessionData(int keyAlgorithm, byte[] key, byte[] secKeyData)
+ throws PGPException
+ {
+ try
+ {
+ if (secKeyData != null && secKeyData.length > 0)
+ {
+ String cipherName = PGPUtil.getSymmetricCipherName(keyAlgorithm);
+ Cipher keyCipher = helper.createCipher(cipherName + "/CFB/NoPadding");
+
+ keyCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, cipherName), new IvParameterSpec(new byte[keyCipher.getBlockSize()]));
+
+ return keyCipher.doFinal(secKeyData);
+ }
+ else
+ {
+ byte[] keyBytes = new byte[key.length + 1];
+
+ keyBytes[0] = (byte)keyAlgorithm;
+ System.arraycopy(key, 0, keyBytes, 1, key.length);
+
+ return keyBytes;
+ }
+ }
+ catch (Exception e)
+ {
+ throw new PGPException("Exception recovering session info", e);
+ }
+ }
+
+ @Override
+ public byte[] recoverAEADEncryptedSessionData(SymmetricKeyEncSessionPacket keyData, byte[] ikm)
+ throws PGPException
+ {
+ if (keyData.getVersion() < SymmetricKeyEncSessionPacket.VERSION_5)
+ {
+ throw new PGPException("SKESK packet MUST be version 5 or later.");
+ }
+
+ byte[] hkdfInfo = keyData.getAAData(); // between v5 and v6, these bytes differ
+ int kekLen = SymmetricKeyUtils.getKeyLengthInOctets(keyData.getEncAlgorithm());
+ byte[] kek = new byte[kekLen];
+
+ // HKDF
+ // secretKey := HKDF_sha256(ikm, hkdfInfo).generate()
+ HKDFBytesGenerator hkdfGen = new HKDFBytesGenerator(new SHA256Digest()); // SHA256 is fixed
+ hkdfGen.init(new HKDFParameters(ikm, null, hkdfInfo));
+ hkdfGen.generateBytes(kek, 0, kek.length);
+ final SecretKey secretKey = new SecretKeySpec(kek, PGPUtil.getSymmetricCipherName(keyData.getEncAlgorithm()));
+
+ // AEAD
+ Cipher aead = aeadHelper.createAEADCipher(keyData.getEncAlgorithm(), keyData.getAeadAlgorithm());
+ int aeadMacLen = 128;
+ byte[] authTag = keyData.getAuthTag();
+ byte[] aeadIv = keyData.getIv();
+ byte[] encSessionKey = keyData.getSecKeyData();
+
+ // buf := encSessionKey || authTag
+ byte[] buf = new byte[encSessionKey.length + authTag.length];
+ System.arraycopy(encSessionKey, 0, buf, 0, encSessionKey.length);
+ System.arraycopy(authTag, 0, buf, encSessionKey.length, authTag.length);
+
+ // sessionData := AEAD(secretKey).decrypt(buf)
+ byte[] sessionData;
+ try
+ {
+ GCMParameterSpec parameters = new GCMParameterSpec(aeadMacLen, aeadIv);
+ aead.init(Cipher.DECRYPT_MODE, secretKey, parameters);
+ aead.updateAAD(hkdfInfo);
+
+ sessionData = aead.doFinal(buf, 0, buf.length);
+ }
+ catch (GeneralSecurityException e)
+ {
+ throw new PGPException("unable to open stream: " + e.getMessage());
+ }
+
+ return sessionData;
+
+ }
+
+ // OpenPGP v4
+ @Override
+ public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
+ throws PGPException
+ {
+ return helper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
+ }
+
+ // OpenPGP v5
+ @Override
+ public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return aeadHelper.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey);
+ }
+
+ // OpenPGP v6
+ @Override
+ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return aeadHelper.createOpenPgpV6DataDecryptor(seipd, sessionKey);
+ }
+ };
}
}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
index 9a860b6aa6..07ec4211c1 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePublicKeyDataDecryptorFactoryBuilder.java
@@ -23,9 +23,11 @@
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParametersHolder;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
import org.bouncycastle.bcpg.ECDHPublicBCPGKey;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.jcajce.spec.UserKeyingMaterialSpec;
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
@@ -34,6 +36,7 @@
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPSessionKey;
import org.bouncycastle.openpgp.operator.PGPDataDecryptor;
import org.bouncycastle.openpgp.operator.PGPPad;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
@@ -46,6 +49,7 @@ public class JcePublicKeyDataDecryptorFactoryBuilder
private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
private OperatorHelper contentHelper = new OperatorHelper(new DefaultJcaJceHelper());
+ private JceAEADUtil aeadHelper = new JceAEADUtil(contentHelper);
private JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
private JcaKeyFingerprintCalculator fingerprintCalculator = new JcaKeyFingerprintCalculator();
@@ -64,6 +68,7 @@ public JcePublicKeyDataDecryptorFactoryBuilder setProvider(Provider provider)
this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
keyConverter.setProvider(provider);
this.contentHelper = helper;
+ this.aeadHelper = new JceAEADUtil(contentHelper);
return this;
}
@@ -79,6 +84,7 @@ public JcePublicKeyDataDecryptorFactoryBuilder setProvider(String providerName)
this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
keyConverter.setProvider(providerName);
this.contentHelper = helper;
+ this.aeadHelper = new JceAEADUtil(contentHelper);
return this;
}
@@ -86,6 +92,7 @@ public JcePublicKeyDataDecryptorFactoryBuilder setProvider(String providerName)
public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provider)
{
this.contentHelper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+ this.aeadHelper = new JceAEADUtil(contentHelper);
return this;
}
@@ -93,6 +100,7 @@ public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(Provider provi
public JcePublicKeyDataDecryptorFactoryBuilder setContentProvider(String providerName)
{
this.contentHelper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+ this.aeadHelper = new JceAEADUtil(contentHelper);
return this;
}
@@ -123,6 +131,7 @@ public PublicKeyDataDecryptorFactory build(final PrivateKey privKey)
{
final int expectedPayLoadSize = getExpectedPayloadSize(privKey);
+ @Override
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
throws PGPException
{
@@ -133,16 +142,28 @@ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
return decryptSessionData(keyAlgorithm, privKey, expectedPayLoadSize, secKeyData);
}
+ // OpenPGP v4
+ @Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
throws PGPException
{
return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
}
- public PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
+ // OpenPGP v5
+ @Override
+ public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
throws PGPException
{
- return contentHelper.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
+ return aeadHelper.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey);
+ }
+
+ // OpenPGP v6
+ @Override
+ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return aeadHelper.createOpenPgpV6DataDecryptor(seipd, sessionKey);
}
};
}
@@ -151,6 +172,7 @@ public PublicKeyDataDecryptorFactory build(final PGPPrivateKey privKey)
{
return new PublicKeyDataDecryptorFactory()
{
+ @Override
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
throws PGPException
{
@@ -164,16 +186,28 @@ public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData)
return decryptSessionData(keyAlgorithm, jcePrivKey, expectedPayLoadSize, secKeyData);
}
+ // OpenPGP v4
+ @Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
throws PGPException
{
return contentHelper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
}
- public PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
+ // OpenPGP v5
+ @Override
+ public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
throws PGPException
{
- return contentHelper.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
+ return aeadHelper.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey);
+ }
+
+ // OpenPGP v6
+ @Override
+ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return aeadHelper.createOpenPgpV6DataDecryptor(seipd, sessionKey);
}
};
}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceSessionKeyDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceSessionKeyDataDecryptorFactoryBuilder.java
index aaf496a018..357d3e9385 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceSessionKeyDataDecryptorFactoryBuilder.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceSessionKeyDataDecryptorFactoryBuilder.java
@@ -2,6 +2,8 @@
import java.security.Provider;
+import org.bouncycastle.bcpg.AEADEncDataPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
@@ -56,29 +58,44 @@ private static class JceSessionKeyDataDecryptorFactory
implements SessionKeyDataDecryptorFactory
{
private final OperatorHelper helper;
+ private final JceAEADUtil aeadHelper;
private final PGPSessionKey sessionKey;
public JceSessionKeyDataDecryptorFactory(OperatorHelper helper, PGPSessionKey sessionKey)
{
this.helper = helper;
+ this.aeadHelper = new JceAEADUtil(helper);
this.sessionKey = sessionKey;
}
+ @Override
public PGPSessionKey getSessionKey()
{
return sessionKey;
}
+ // OpenPGP v4
+ @Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key)
throws PGPException
{
return helper.createDataDecryptor(withIntegrityPacket, encAlgorithm, key);
}
- public PGPDataDecryptor createDataDecryptor(int aeadAlgorithm, byte[] iv, int chunkSize, int encAlgorithm, byte[] key)
+ // OpenPGP v5
+ @Override
+ public PGPDataDecryptor createDataDecryptor(AEADEncDataPacket aeadEncDataPacket, PGPSessionKey sessionKey)
throws PGPException
{
- return helper.createDataDecryptor(aeadAlgorithm, iv, chunkSize, encAlgorithm, key);
+ return aeadHelper.createOpenPgpV5DataDecryptor(aeadEncDataPacket, sessionKey);
+ }
+
+ // OpenPGP v6
+ @Override
+ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, PGPSessionKey sessionKey)
+ throws PGPException
+ {
+ return aeadHelper.createOpenPgpV6DataDecryptor(seipd, sessionKey);
}
}
}
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java
index 852659c562..b0cdfcb5cc 100644
--- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java
@@ -43,7 +43,7 @@
* @see Sample test vectors
*/
public class PGPAeadTest
- extends SimpleTest
+ extends SimpleTest
{
private static final byte[] PLAINTEXT = Strings.toByteArray("Hello, world!");
@@ -105,7 +105,7 @@ public String getName()
@Override
public void performTest()
- throws Exception
+ throws Exception
{
knownV6TestVectorDecryptionTests();
knownV5TestVectorDecryptionTests();
@@ -118,15 +118,16 @@ public void performTest()
private void roundTripEncryptionDecryptionTests()
throws PGPException, IOException
{
- int[] aeadAlgs = new int[]{
- AEADAlgorithmTags.EAX,
- AEADAlgorithmTags.OCB,
- AEADAlgorithmTags.GCM
+
+ int[] aeadAlgs = new int[] {
+ AEADAlgorithmTags.EAX,
+ AEADAlgorithmTags.OCB,
+ AEADAlgorithmTags.GCM
};
- int[] symAlgs = new int[]{
- SymmetricKeyAlgorithmTags.AES_128,
- SymmetricKeyAlgorithmTags.AES_192,
- SymmetricKeyAlgorithmTags.AES_256
+ int[] symAlgs = new int[] {
+ SymmetricKeyAlgorithmTags.AES_128,
+ SymmetricKeyAlgorithmTags.AES_192,
+ SymmetricKeyAlgorithmTags.AES_256
};
// PGPAEADFlavour[] flavours = new PGPAEADFlavour[] {
// PGPAEADFlavour.OPENPGP_V6,
@@ -163,15 +164,15 @@ private void knownV5TestVectorDecryptionTests()
private void knownV6TestVectorDecryptionTests()
throws IOException, PGPException
{
- // Test known-good V6 test vectors
- System.out.println("Test V6 BC Decryption");
- testBcDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
- testBcDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
- testBcDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
- System.out.println("Test V6 JCA Decryption");
- testJceDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
- testJceDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
- testJceDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
+ // Test known-good V6 test vectors TODO: decryption tests should be working...
+// System.out.println("Test V6 BC Decryption");
+// testBcDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
+// testBcDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
+// testBcDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
+// System.out.println("Test V6 JCA Decryption");
+// testJceDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
+// testJceDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
+// testJceDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT);
}
// private void testBcRoundTrip(PGPAEADFlavour flavour, int aeadAlg, int symAlg, byte[] plaintext, char[] password) throws PGPException, IOException {
@@ -180,14 +181,14 @@ private void knownV6TestVectorDecryptionTests()
// System.out.println(armored);
// testBcDecryption(armored, password, plaintext);
// }
-
+//
// private void testJceRoundTrip(PGPAEADFlavour flavour, int aeadAlg, int symAlg, byte[] plaintext, char[] password) throws PGPException, IOException {
// System.out.println("Test JCE RoundTrip " + flavour + " " + algNames(aeadAlg, symAlg));
// String armored = testJceEncryption(flavour, aeadAlg, symAlg, plaintext, password);
// System.out.println(armored);
// testJceDecryption(armored, password, plaintext);
// }
-
+//
// private void testBcJceRoundTrip(PGPAEADFlavour flavour, int aeadAlg, int symAlg, byte[] plaintext, char[] password)
// throws PGPException, IOException {
// System.out.println("Test BC encrypt, JCE decrypt " + flavour + " " + algNames(aeadAlg, symAlg));
@@ -275,21 +276,21 @@ private void knownV6TestVectorDecryptionTests()
// }
private void testBcDecryption(String armoredMessage, char[] password, byte[] expectedPlaintext)
- throws IOException
+ throws IOException
{
ByteArrayInputStream messageIn = new ByteArrayInputStream(Strings.toByteArray(armoredMessage));
ArmoredInputStream armorIn = new ArmoredInputStream(messageIn);
PGPObjectFactory objectFactory = new PGPObjectFactory(armorIn, new BcKeyFingerprintCalculator());
Object o = objectFactory.nextObject();
- PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList)o;
+ PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) o;
for (int i = 0; i < encryptedDataList.size(); i++)
{
PGPEncryptedData encryptedData = encryptedDataList.get(i);
if (encryptedData instanceof PGPPBEEncryptedData)
{
- PGPPBEEncryptedData symEncData = (PGPPBEEncryptedData)encryptedData;
+ PGPPBEEncryptedData symEncData = (PGPPBEEncryptedData) encryptedData;
BcPGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider();
PBEDataDecryptorFactory decryptorFactory = new BcPBEDataDecryptorFactory(password, digestCalculatorProvider);
@@ -299,7 +300,7 @@ private void testBcDecryption(String armoredMessage, char[] password, byte[] exp
objectFactory = new PGPObjectFactory(decryptedIn, new BcKeyFingerprintCalculator());
o = objectFactory.nextObject();
- PGPLiteralData literalData = (PGPLiteralData)o;
+ PGPLiteralData literalData = (PGPLiteralData) o;
ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
Streams.pipeAll(literalData.getDataStream(), plaintextOut);
@@ -327,7 +328,7 @@ private void testBcDecryption(String armoredMessage, char[] password, byte[] exp
}
private void testJceDecryption(String armoredMessage, char[] password, byte[] expectedPlaintext)
- throws IOException, PGPException
+ throws IOException, PGPException
{
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
@@ -336,17 +337,17 @@ private void testJceDecryption(String armoredMessage, char[] password, byte[] ex
PGPObjectFactory objectFactory = new JcaPGPObjectFactory(armorIn);
Object o = objectFactory.nextObject();
- PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList)o;
+ PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) o;
for (int i = 0; i < encryptedDataList.size(); i++)
{
PGPEncryptedData encryptedData = encryptedDataList.get(i);
if (encryptedData instanceof PGPPBEEncryptedData)
{
- PGPPBEEncryptedData symEncData = (PGPPBEEncryptedData)encryptedData;
+ PGPPBEEncryptedData symEncData = (PGPPBEEncryptedData) encryptedData;
JcaPGPDigestCalculatorProviderBuilder digestCalculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder()
- .setProvider(provider);
+ .setProvider(provider);
PGPDigestCalculatorProvider digestCalculatorProvider = digestCalculatorProviderBuilder.build();
PBEDataDecryptorFactory decryptorFactory = new JcePBEDataDecryptorFactoryBuilder(digestCalculatorProvider).build(password);
@@ -356,7 +357,7 @@ private void testJceDecryption(String armoredMessage, char[] password, byte[] ex
objectFactory = new JcaPGPObjectFactory(decryptedIn);
o = objectFactory.nextObject();
- PGPLiteralData literalData = (PGPLiteralData)o;
+ PGPLiteralData literalData = (PGPLiteralData) o;
ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
Streams.pipeAll(literalData.getDataStream(), plaintextOut);
@@ -382,8 +383,7 @@ private void testJceDecryption(String armoredMessage, char[] password, byte[] ex
}
}
- public static void printHex(byte[] bytes)
- {
+ public static void printHex(byte[] bytes) {
boolean separate = true;
boolean prefix = true;
String hex = Hex.toHexString(bytes);
@@ -395,14 +395,10 @@ public static void printHex(byte[] bytes)
sb.append("0x").append(Hex.toHexString(Pack.intToBigEndian(i & 0xFFFFF))).append(" ");
}
sb.append(hex.substring(i * 2, i * 2 + 2));
- if (separate)
- {
- if ((i + 1) % 8 == 0)
- {
+ if (separate) {
+ if ((i + 1) % 8 == 0) {
sb.append('\n');
- }
- else
- {
+ } else {
sb.append(' ');
}
}
@@ -410,37 +406,34 @@ public static void printHex(byte[] bytes)
System.out.println(sb);
}
- private static String algNames(int aeadAlg, int symAlg)
- {
+ private static String algNames(int aeadAlg, int symAlg) {
String out = "";
- switch (aeadAlg)
- {
- case AEADAlgorithmTags.EAX:
- out = "EAX";
- break;
- case AEADAlgorithmTags.OCB:
- out = "OCB";
- break;
- case AEADAlgorithmTags.GCM:
- out = "GCM";
- break;
- default:
- out = "UNKNOWN(" + aeadAlg + ")";
+ switch (aeadAlg) {
+ case AEADAlgorithmTags.EAX:
+ out = "EAX";
+ break;
+ case AEADAlgorithmTags.OCB:
+ out = "OCB";
+ break;
+ case AEADAlgorithmTags.GCM:
+ out = "GCM";
+ break;
+ default:
+ out = "UNKNOWN(" + aeadAlg + ")";
}
- switch (symAlg)
- {
- case SymmetricKeyAlgorithmTags.AES_128:
- out += " AES-128";
- break;
- case SymmetricKeyAlgorithmTags.AES_192:
- out += " AES-192";
- break;
- case SymmetricKeyAlgorithmTags.AES_256:
- out += " AES-256";
- break;
- default:
- out += " UNKNOWN(" + symAlg + ")";
+ switch (symAlg) {
+ case SymmetricKeyAlgorithmTags.AES_128:
+ out += " AES-128";
+ break;
+ case SymmetricKeyAlgorithmTags.AES_192:
+ out += " AES-192";
+ break;
+ case SymmetricKeyAlgorithmTags.AES_256:
+ out += " AES-256";
+ break;
+ default:
+ out += " UNKNOWN(" + symAlg + ")";
}
return out;
}
From 7081abf8066bbfc2a49e8327626fa1092f24938a Mon Sep 17 00:00:00 2001
From: David Hook m
bytes of key and n-8
bytes of IV into
+ * two separate byte arrays.
+ * m
is the key length of the cipher algorithm, while n
is the IV length of the AEAD algorithm.
+ * Note, that the IV is filled with n-8
bytes only, the remainder is left as 0s.
+ * Return an array of both arrays with the key and index 0 and the IV at index 1.
+ *
+ * @param messageKeyAndIv m+n-8
bytes of concatenated message key and IV
+ * @param cipherAlgo symmetric cipher algorithm
+ * @param aeadAlgo AEAD algorithm
+ * @return array of arrays containing message key and IV
+ * @throws PGPException
+ */
+ public static byte[][] splitMessageKeyAndIv(byte[] messageKeyAndIv, int cipherAlgo, int aeadAlgo)
+ throws PGPException
+ {
+ int keyLen = SymmetricKeyUtils.getKeyLengthInOctets(cipherAlgo);
+ int ivLen = AEADUtils.getIVLength(aeadAlgo);
+ byte[] messageKey = new byte[keyLen];
+ byte[] iv = new byte[ivLen];
+ System.arraycopy(messageKeyAndIv, 0, messageKey, 0, messageKey.length);
+ System.arraycopy(messageKeyAndIv, messageKey.length, iv, 0, ivLen - 8);
+
+ return new byte[][]{messageKey, iv};
+ }
+
+}
From 1caa25052b3e2cd7212baef19e160e484a45e394 Mon Sep 17 00:00:00 2001
From: David Hook m
bytes of key and n-8
bytes of IV into
- * two separate byte arrays.
- * m
is the key length of the cipher algorithm, while n
is the IV length of the AEAD algorithm.
- * Note, that the IV is filled with n-8
bytes only, the remainder is left as 0s.
- * Return an array of both arrays with the key and index 0 and the IV at index 1.
- *
- * @param messageKeyAndIv m+n-8
bytes of concatenated message key and IV
- * @param cipherAlgo symmetric cipher algorithm
- * @param aeadAlgo AEAD algorithm
- * @return array of arrays containing message key and IV
- * @throws PGPException
- */
- public static byte[][] splitMessageKeyAndIv(byte[] messageKeyAndIv, int cipherAlgo, int aeadAlgo)
- throws PGPException
- {
- int keyLen = SymmetricKeyUtils.getKeyLengthInOctets(cipherAlgo);
- int ivLen = AEADUtils.getIVLength(aeadAlgo);
- byte[] messageKey = new byte[keyLen];
- byte[] iv = new byte[ivLen];
- System.arraycopy(messageKeyAndIv, 0, messageKey, 0, messageKey.length);
- System.arraycopy(messageKeyAndIv, messageKey.length, iv, 0, ivLen - 8);
-
- return new byte[][]{messageKey, iv};
- }
-
-}
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
index 0d684edd8e..f1da71c928 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPBEEncryptedData.java
@@ -3,8 +3,11 @@
import java.io.InputStream;
import org.bouncycastle.bcpg.InputStreamPacket;
+import org.bouncycastle.bcpg.SymmetricEncDataPacket;
+import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket;
+import org.bouncycastle.bcpg.UnsupportedPacketVersionException;
import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory;
import org.bouncycastle.util.Arrays;
@@ -17,7 +20,7 @@
*
mbytes of key and
n-8bytes of IV into + * two separate byte arrays. + *
mis the key length of the cipher algorithm, while
nis the IV length of the AEAD algorithm. + * Note, that the IV is filled with
n-8bytes only, the remainder is left as 0s. + * Return an array of both arrays with the key and index 0 and the IV at index 1. + * + * @param messageKeyAndIv
m+n-8bytes of concatenated message key and IV + * @param cipherAlgo symmetric cipher algorithm + * @param aeadAlgo AEAD algorithm + * @return array of arrays containing message key and IV + * @throws PGPException + */ + public static byte[][] splitMessageKeyAndIv(byte[] messageKeyAndIv, int cipherAlgo, int aeadAlgo) + throws PGPException + { + int keyLen = SymmetricKeyUtils.getKeyLengthInOctets(cipherAlgo); + int ivLen = AEADUtils.getIVLength(aeadAlgo); + byte[] messageKey = new byte[keyLen]; + byte[] iv = new byte[ivLen]; + System.arraycopy(messageKeyAndIv, 0, messageKey, 0, messageKey.length); + System.arraycopy(messageKeyAndIv, messageKey.length, iv, 0, ivLen - 8); + + return new byte[][]{messageKey, iv}; + } public static AEADBlockCipher createAEADCipher(int encAlgorithm, int aeadAlgorithm) throws PGPException diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java index 21142375d4..ee266c237f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java @@ -15,18 +15,21 @@ import org.bouncycastle.bcpg.AEADUtils; import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; -import org.bouncycastle.openpgp.AEADUtil; +import org.bouncycastle.bcpg.SymmetricKeyUtils; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.generators.HKDFBytesGenerator; +import org.bouncycastle.crypto.params.HKDFParameters; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPSessionKey; import org.bouncycastle.openpgp.PGPUtil; import org.bouncycastle.openpgp.operator.PGPDataDecryptor; import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.util.Arrays; import org.bouncycastle.util.Exceptions; import org.bouncycastle.util.Pack; import org.bouncycastle.util.io.Streams; class JceAEADUtil - extends AEADUtil { private final OperatorHelper helper; @@ -36,6 +39,129 @@ public JceAEADUtil(OperatorHelper helper) this.helper = helper; } + /** + * Generate a nonce by xor-ing the given iv with the chunk index. + * + * @param iv initialization vector + * @param chunkIndex chunk index + * @return nonce + */ + protected static byte[] getNonce(byte[] iv, long chunkIndex) + { + byte[] nonce = Arrays.clone(iv); + + xorChunkId(nonce, chunkIndex); + + return nonce; + } + + /** + * XOR the byte array with the chunk index in-place. + * + * @param nonce byte array + * @param chunkIndex chunk index + */ + protected static void xorChunkId(byte[] nonce, long chunkIndex) + { + int index = nonce.length - 8; + + nonce[index++] ^= (byte)(chunkIndex >> 56); + nonce[index++] ^= (byte)(chunkIndex >> 48); + nonce[index++] ^= (byte)(chunkIndex >> 40); + nonce[index++] ^= (byte)(chunkIndex >> 32); + nonce[index++] ^= (byte)(chunkIndex >> 24); + nonce[index++] ^= (byte)(chunkIndex >> 16); + nonce[index++] ^= (byte)(chunkIndex >> 8); + nonce[index] ^= (byte)(chunkIndex); + } + + /** + * Calculate an actual chunk length from the encoded chunk size. + * + * @param chunkSize encoded chunk size + * @return decoded length + */ + protected static long getChunkLength(int chunkSize) + { + return 1L << (chunkSize + 6); + } + + /** + * Derived a message key and IV from the given session key. + * + * @param aeadAlgo AEAD algorithm + * @param cipherAlgo symmetric cipher algorithm + * @param sessionKey session key + * @param salt salt + * @param hkdfInfo HKDF info + * @return an array of byte arrays, the message key is at index 0, the IV at index 1. + * @throws PGPException + */ + public static byte[][] deriveAndSplitMessageKeyAndIv(int aeadAlgo, + int cipherAlgo, + byte[] sessionKey, + byte[] salt, + byte[] hkdfInfo) + throws PGPException + { + byte[] messageKeyAndIv = deriveMessageKeyAndIv(aeadAlgo, cipherAlgo, sessionKey, salt, hkdfInfo); + return splitMessageKeyAndIv(messageKeyAndIv, cipherAlgo, aeadAlgo); + } + + /** + * Derive a message key and IV from the given session key. + * The result is a byte array containing the key bytes followed by the IV. + * To split them, use {@link #splitMessageKeyAndIv(byte[], int, int)}. + * + * @param aeadAlgo AEAD algorithm + * @param cipherAlgo symmetric cipher algorithm + * @param sessionKey session key + * @param salt salt + * @param hkdfInfo HKDF info + * @return message key and appended IV + * @throws PGPException + */ + public static byte[] deriveMessageKeyAndIv(int aeadAlgo, int cipherAlgo, byte[] sessionKey, byte[] salt, byte[] hkdfInfo) + throws PGPException + { + // TODO: needs to be JCA based. KeyGenerator? + HKDFParameters hkdfParameters = new HKDFParameters(sessionKey, salt, hkdfInfo); + HKDFBytesGenerator hkdfGen = new HKDFBytesGenerator(new SHA256Digest()); + + hkdfGen.init(hkdfParameters); + int keyLen = SymmetricKeyUtils.getKeyLengthInOctets(cipherAlgo); + int ivLen = AEADUtils.getIVLength(aeadAlgo); + byte[] messageKeyAndIv = new byte[keyLen + ivLen - 8]; + hkdfGen.generateBytes(messageKeyAndIv, 0, messageKeyAndIv.length); + return messageKeyAndIv; + } + + /** + * Split a given byte array containing
mbytes of key and
n-8bytes of IV into + * two separate byte arrays. + *
mis the key length of the cipher algorithm, while
nis the IV length of the AEAD algorithm. + * Note, that the IV is filled with
n-8bytes only, the remainder is left as 0s. + * Return an array of both arrays with the key and index 0 and the IV at index 1. + * + * @param messageKeyAndIv
m+n-8bytes of concatenated message key and IV + * @param cipherAlgo symmetric cipher algorithm + * @param aeadAlgo AEAD algorithm + * @return array of arrays containing message key and IV + * @throws PGPException + */ + public static byte[][] splitMessageKeyAndIv(byte[] messageKeyAndIv, int cipherAlgo, int aeadAlgo) + throws PGPException + { + int keyLen = SymmetricKeyUtils.getKeyLengthInOctets(cipherAlgo); + int ivLen = AEADUtils.getIVLength(aeadAlgo); + byte[] messageKey = new byte[keyLen]; + byte[] iv = new byte[ivLen]; + System.arraycopy(messageKeyAndIv, 0, messageKey, 0, messageKey.length); + System.arraycopy(messageKeyAndIv, messageKey.length, iv, 0, ivLen - 8); + + return new byte[][]{messageKey, iv}; + } + /** * Create a {@link PGPDataDecryptor} for decrypting AEAD encrypted OpenPGP v5 data packets. * diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java index b0cdfcb5cc..94e4464267 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPAeadTest.java @@ -165,14 +165,14 @@ private void knownV6TestVectorDecryptionTests() throws IOException, PGPException { // Test known-good V6 test vectors TODO: decryption tests should be working... -// System.out.println("Test V6 BC Decryption"); -// testBcDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); -// testBcDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); -// testBcDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); -// System.out.println("Test V6 JCA Decryption"); -// testJceDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); -// testJceDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); -// testJceDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); + System.out.println("Test V6 BC Decryption"); + testBcDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); + testBcDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); + testBcDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); + System.out.println("Test V6 JCA Decryption"); + testJceDecryption(V6_EAX_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); + testJceDecryption(V6_OCB_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); + testJceDecryption(V6_GCM_PACKET_SEQUENCE, PASSWORD, PLAINTEXT); } // private void testBcRoundTrip(PGPAEADFlavour flavour, int aeadAlg, int symAlg, byte[] plaintext, char[] password) throws PGPException, IOException { @@ -309,7 +309,6 @@ private void testBcDecryption(String armoredMessage, char[] password, byte[] exp o = objectFactory.nextObject(); if (o instanceof PGPPadding) { - System.err.println("hrer"); o = objectFactory.nextObject(); } From 1b9286597f0679aa53fe9a8af5748b9d41352fbc Mon Sep 17 00:00:00 2001 From: David Hook
mbytes of key and
n-8bytes of IV into - * two separate byte arrays. - *
mis the key length of the cipher algorithm, while
nis the IV length of the AEAD algorithm. - * Note, that the IV is filled with
n-8bytes only, the remainder is left as 0s. - * Return an array of both arrays with the key and index 0 and the IV at index 1. - * - * @param messageKeyAndIv
m+n-8bytes of concatenated message key and IV - * @param cipherAlgo symmetric cipher algorithm - * @param aeadAlgo AEAD algorithm - * @return array of arrays containing message key and IV - * @throws PGPException - */ - public static byte[][] splitMessageKeyAndIv(byte[] messageKeyAndIv, int cipherAlgo, int aeadAlgo) - throws PGPException - { - int keyLen = SymmetricKeyUtils.getKeyLengthInOctets(cipherAlgo); - int ivLen = AEADUtils.getIVLength(aeadAlgo); - byte[] messageKey = new byte[keyLen]; - byte[] iv = new byte[ivLen]; - System.arraycopy(messageKeyAndIv, 0, messageKey, 0, messageKey.length); - System.arraycopy(messageKeyAndIv, messageKey.length, iv, 0, ivLen - 8); - return new byte[][]{messageKey, iv}; + return new byte[][] { Arrays.copyOfRange(messageKeyAndIv, 0, keyLen), Arrays.copyOfRange(messageKeyAndIv, keyLen, keyLen + ivLen) }; } - public static AEADBlockCipher createAEADCipher(int encAlgorithm, int aeadAlgorithm) + static AEADBlockCipher createAEADCipher(int encAlgorithm, int aeadAlgorithm) throws PGPException { if (encAlgorithm != SymmetricKeyAlgorithmTags.AES_128 @@ -253,7 +205,7 @@ static PGPDataDecryptor createOpenPgpV6DataDecryptor(SymmetricEncIntegrityPacket final int chunkSize = seipd.getChunkSize(); final byte[] aaData = seipd.getAAData(); - byte[][] messageKeyAndIv = deriveAndSplitMessageKeyAndIv(aeadAlgo, cipherAlgo, + byte[][] messageKeyAndIv = deriveMessageKeyAndIv(aeadAlgo, cipherAlgo, sessionKey.getKey(), seipd.getSalt(), aaData); byte[] messageKey = messageKeyAndIv[0]; final byte[] iv = messageKeyAndIv[1]; diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java index ee266c237f..48b8dbfbd9 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JceAEADUtil.java @@ -31,7 +31,6 @@ class JceAEADUtil { - private final OperatorHelper helper; public JceAEADUtil(OperatorHelper helper) @@ -86,42 +85,19 @@ protected static long getChunkLength(int chunkSize) return 1L << (chunkSize + 6); } - /** - * Derived a message key and IV from the given session key. - * - * @param aeadAlgo AEAD algorithm - * @param cipherAlgo symmetric cipher algorithm - * @param sessionKey session key - * @param salt salt - * @param hkdfInfo HKDF info - * @return an array of byte arrays, the message key is at index 0, the IV at index 1. - * @throws PGPException - */ - public static byte[][] deriveAndSplitMessageKeyAndIv(int aeadAlgo, - int cipherAlgo, - byte[] sessionKey, - byte[] salt, - byte[] hkdfInfo) - throws PGPException - { - byte[] messageKeyAndIv = deriveMessageKeyAndIv(aeadAlgo, cipherAlgo, sessionKey, salt, hkdfInfo); - return splitMessageKeyAndIv(messageKeyAndIv, cipherAlgo, aeadAlgo); - } - /** * Derive a message key and IV from the given session key. - * The result is a byte array containing the key bytes followed by the IV. - * To split them, use {@link #splitMessageKeyAndIv(byte[], int, int)}. + * The result is two byte arrays containing the key bytes and the IV. * * @param aeadAlgo AEAD algorithm * @param cipherAlgo symmetric cipher algorithm * @param sessionKey session key * @param salt salt * @param hkdfInfo HKDF info - * @return message key and appended IV + * @return message key and separate IV * @throws PGPException */ - public static byte[] deriveMessageKeyAndIv(int aeadAlgo, int cipherAlgo, byte[] sessionKey, byte[] salt, byte[] hkdfInfo) + static byte[][] deriveMessageKeyAndIv(int aeadAlgo, int cipherAlgo, byte[] sessionKey, byte[] salt, byte[] hkdfInfo) throws PGPException { // TODO: needs to be JCA based. KeyGenerator? @@ -133,33 +109,8 @@ public static byte[] deriveMessageKeyAndIv(int aeadAlgo, int cipherAlgo, byte[] int ivLen = AEADUtils.getIVLength(aeadAlgo); byte[] messageKeyAndIv = new byte[keyLen + ivLen - 8]; hkdfGen.generateBytes(messageKeyAndIv, 0, messageKeyAndIv.length); - return messageKeyAndIv; - } - - /** - * Split a given byte array containing
mbytes of key and
n-8bytes of IV into - * two separate byte arrays. - *
mis the key length of the cipher algorithm, while
nis the IV length of the AEAD algorithm. - * Note, that the IV is filled with
n-8bytes only, the remainder is left as 0s. - * Return an array of both arrays with the key and index 0 and the IV at index 1. - * - * @param messageKeyAndIv
m+n-8bytes of concatenated message key and IV - * @param cipherAlgo symmetric cipher algorithm - * @param aeadAlgo AEAD algorithm - * @return array of arrays containing message key and IV - * @throws PGPException - */ - public static byte[][] splitMessageKeyAndIv(byte[] messageKeyAndIv, int cipherAlgo, int aeadAlgo) - throws PGPException - { - int keyLen = SymmetricKeyUtils.getKeyLengthInOctets(cipherAlgo); - int ivLen = AEADUtils.getIVLength(aeadAlgo); - byte[] messageKey = new byte[keyLen]; - byte[] iv = new byte[ivLen]; - System.arraycopy(messageKeyAndIv, 0, messageKey, 0, messageKey.length); - System.arraycopy(messageKeyAndIv, messageKey.length, iv, 0, ivLen - 8); - return new byte[][]{messageKey, iv}; + return new byte[][] { Arrays.copyOfRange(messageKeyAndIv, 0, keyLen), Arrays.copyOfRange(messageKeyAndIv, keyLen, keyLen + ivLen) }; } /** @@ -240,7 +191,7 @@ PGPDataDecryptor createOpenPgpV6DataDecryptor(SymmetricEncIntegrityPacket seipd, final byte[] aaData = seipd.getAAData(); - final byte[][] messageKeyAndIv = deriveAndSplitMessageKeyAndIv(aeadAlgo, cipherAlgo, sessionKey.getKey(), salt, aaData); + final byte[][] messageKeyAndIv = deriveMessageKeyAndIv(aeadAlgo, cipherAlgo, sessionKey.getKey(), salt, aaData); final byte[] messageKey = messageKeyAndIv[0]; final byte[] iv = messageKeyAndIv[1]; From 876bd0cf02811282707ddd38e4f0f9212a9f4d1f Mon Sep 17 00:00:00 2001 From: David Hook
diff --git a/pkix/src/main/java/org/bouncycastle/cert/path/validations/BasicConstraintsValidation.java b/pkix/src/main/java/org/bouncycastle/cert/path/validations/BasicConstraintsValidation.java
index e3e9c54e8e..0a40236ef1 100644
--- a/pkix/src/main/java/org/bouncycastle/cert/path/validations/BasicConstraintsValidation.java
+++ b/pkix/src/main/java/org/bouncycastle/cert/path/validations/BasicConstraintsValidation.java
@@ -1,18 +1,15 @@
package org.bouncycastle.cert.path.validations;
-import java.math.BigInteger;
-
+import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.path.CertPathValidation;
import org.bouncycastle.cert.path.CertPathValidationContext;
import org.bouncycastle.cert.path.CertPathValidationException;
-import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.Integers;
import org.bouncycastle.util.Memoable;
-
public class BasicConstraintsValidation
implements CertPathValidation
{
@@ -34,7 +31,6 @@ public BasicConstraintsValidation(boolean isMandatory)
public void validate(CertPathValidationContext context, X509CertificateHolder certificate)
throws CertPathValidationException
{
-
context.addHandledExtension(Extension.basicConstraints);
// verify that the issuing certificate is in fact a CA
@@ -62,17 +58,18 @@ public void validate(CertPathValidationContext context, X509CertificateHolder ce
// § 6.1.4 (m)
// Update maxPathLength if appropriate
- if (bc != null)
+ if (bc != null && bc.isCA())
{
- BigInteger bigPathLen = bc.getPathLenConstraint();
- if (bigPathLen != null)
+ ASN1Integer pathLenConstraint = bc.getPathLenConstraintInteger();
+ if (pathLenConstraint != null)
{
- // use intValueExact to prevent issues with weird certificates that include ridiculous path lengths
- int newPathLength = BigIntegers.intValueExact(bigPathLen);
- maxPathLength = maxPathLength == null ? Integers.valueOf(newPathLength) : Integers.valueOf(Math.min(newPathLength, maxPathLength.intValue()));
+ int newPathLength = pathLenConstraint.intPositiveValueExact();
+ if (maxPathLength == null || newPathLength < maxPathLength.intValue())
+ {
+ maxPathLength = Integers.valueOf(newPathLength);
+ }
}
}
-
}
public Memoable copy()
diff --git a/pkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCertPathReviewer.java b/pkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCertPathReviewer.java
index 4c802838ae..c34bafde0c 100644
--- a/pkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCertPathReviewer.java
+++ b/pkix/src/main/java/org/bouncycastle/pkix/jcajce/PKIXCertPathReviewer.java
@@ -700,26 +700,19 @@ private void checkPathLength()
bc = null;
}
- if (bc != null)
+ if (bc != null && bc.isCA())
{
- BigInteger _pathLengthConstraint = bc.getPathLenConstraint();
-
- if (_pathLengthConstraint != null)
+ ASN1Integer pathLenConstraint = bc.getPathLenConstraintInteger();
+ if (pathLenConstraint != null)
{
- int _plc = _pathLengthConstraint.intValue();
-
- if (_plc < maxPathLength)
- {
- maxPathLength = _plc;
- }
+ maxPathLength = Math.min(maxPathLength, pathLenConstraint.intPositiveValueExact());
}
}
-
}
ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.totalPathLength",
new Object[]{Integers.valueOf(totalPathLength)});
-
+
addNotification(msg);
}
diff --git a/prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java b/prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java
index 72ac260024..2a89e7e3dd 100644
--- a/prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java
+++ b/prov/src/main/java/org/bouncycastle/jcajce/provider/asymmetric/x509/X509CertificateImpl.java
@@ -36,6 +36,7 @@
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1IA5String;
import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
@@ -298,29 +299,21 @@ public List getExtendedKeyUsage()
throw new CertificateParsingException("error processing extended key usage extension");
}
}
-
+
public int getBasicConstraints()
{
- if (basicConstraints != null)
+ if (basicConstraints == null || !basicConstraints.isCA())
{
- if (basicConstraints.isCA())
- {
- if (basicConstraints.getPathLenConstraint() == null)
- {
- return Integer.MAX_VALUE;
- }
- else
- {
- return basicConstraints.getPathLenConstraint().intValue();
- }
- }
- else
- {
- return -1;
- }
+ return -1;
+ }
+
+ ASN1Integer pathLenConstraint = basicConstraints.getPathLenConstraintInteger();
+ if (pathLenConstraint == null)
+ {
+ return Integer.MAX_VALUE;
}
- return -1;
+ return pathLenConstraint.intPositiveValueExact();
}
public Collection getSubjectAlternativeNames()
diff --git a/prov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java b/prov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
index d7d8d411dc..5df9eed861 100644
--- a/prov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
+++ b/prov/src/main/java/org/bouncycastle/jce/provider/RFC3280CertPathUtilities.java
@@ -1,7 +1,6 @@
package org.bouncycastle.jce.provider;
import java.io.IOException;
-import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.cert.CertPath;
@@ -2097,16 +2096,10 @@ protected static int prepareNextCertM(
}
if (bc != null && bc.isCA()) // if there is a path len constraint and we're not a CA, ignore it! (yes, it happens).
{
- BigInteger _pathLengthConstraint = bc.getPathLenConstraint();
-
- if (_pathLengthConstraint != null)
+ ASN1Integer pathLenConstraint = bc.getPathLenConstraintInteger();
+ if (pathLenConstraint != null)
{
- int _plc = _pathLengthConstraint.intValue();
-
- if (_plc < maxPathLength)
- {
- return _plc;
- }
+ maxPathLength = Math.min(maxPathLength, pathLenConstraint.intPositiveValueExact());
}
}
return maxPathLength;
diff --git a/prov/src/main/java/org/bouncycastle/jce/provider/X509CertificateObject.java b/prov/src/main/java/org/bouncycastle/jce/provider/X509CertificateObject.java
index ccf301bd86..17bebc1f9e 100644
--- a/prov/src/main/java/org/bouncycastle/jce/provider/X509CertificateObject.java
+++ b/prov/src/main/java/org/bouncycastle/jce/provider/X509CertificateObject.java
@@ -36,6 +36,7 @@
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1IA5String;
import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
@@ -361,26 +362,18 @@ public List getExtendedKeyUsage()
public int getBasicConstraints()
{
- if (basicConstraints != null)
+ if (basicConstraints == null || !basicConstraints.isCA())
{
- if (basicConstraints.isCA())
- {
- if (basicConstraints.getPathLenConstraint() == null)
- {
- return Integer.MAX_VALUE;
- }
- else
- {
- return basicConstraints.getPathLenConstraint().intValue();
- }
- }
- else
- {
- return -1;
- }
+ return -1;
+ }
+
+ ASN1Integer pathLenConstraint = basicConstraints.getPathLenConstraintInteger();
+ if (pathLenConstraint == null)
+ {
+ return Integer.MAX_VALUE;
}
- return -1;
+ return pathLenConstraint.intPositiveValueExact();
}
public Collection getSubjectAlternativeNames()
diff --git a/prov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java b/prov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java
index 43ee7ddf8d..3c3aa62e85 100644
--- a/prov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java
+++ b/prov/src/main/java/org/bouncycastle/x509/PKIXCertPathReviewer.java
@@ -668,11 +668,8 @@ private void checkPathLength()
X509Certificate cert = null;
- int i;
for (int index = certs.size() - 1; index > 0; index--)
{
- i = n - index;
-
cert = (X509Certificate) certs.get(index);
// l)
@@ -693,8 +690,7 @@ private void checkPathLength()
BasicConstraints bc;
try
{
- bc = BasicConstraints.getInstance(getExtensionValue(cert,
- BASIC_CONSTRAINTS));
+ bc = BasicConstraints.getInstance(getExtensionValue(cert, BASIC_CONSTRAINTS));
}
catch (AnnotatedException ae)
{
@@ -703,21 +699,14 @@ private void checkPathLength()
bc = null;
}
- if (bc != null)
+ if (bc != null && bc.isCA())
{
- BigInteger _pathLengthConstraint = bc.getPathLenConstraint();
-
- if (_pathLengthConstraint != null)
+ ASN1Integer pathLenConstraint = bc.getPathLenConstraintInteger();
+ if (pathLenConstraint != null)
{
- int _plc = _pathLengthConstraint.intValue();
-
- if (_plc < maxPathLength)
- {
- maxPathLength = _plc;
- }
+ maxPathLength = Math.min(maxPathLength, pathLenConstraint.intPositiveValueExact());
}
}
-
}
ErrorBundle msg = new ErrorBundle(RESOURCE_NAME,"CertPathReviewer.totalPathLength",
diff --git a/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java b/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
index fb3fdfc7bb..eac5600b8e 100644
--- a/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
+++ b/prov/src/test/java/org/bouncycastle/jce/provider/test/CertPathValidatorTest.java
@@ -991,26 +991,18 @@ public List getExtendedKeyUsage()
public int getBasicConstraints()
{
- if (basicConstraints != null)
+ if (basicConstraints == null || !basicConstraints.isCA())
{
- if (basicConstraints.isCA())
- {
- if (basicConstraints.getPathLenConstraint() == null)
- {
- return Integer.MAX_VALUE;
- }
- else
- {
- return basicConstraints.getPathLenConstraint().intValue();
- }
- }
- else
- {
- return -1;
- }
+ return -1;
+ }
+
+ ASN1Integer pathLenConstraint = basicConstraints.getPathLenConstraintInteger();
+ if (pathLenConstraint == null)
+ {
+ return Integer.MAX_VALUE;
}
- return -1;
+ return pathLenConstraint.intPositiveValueExact();
}
public Collection getSubjectAlternativeNames()
From ea011adeb8488a46d4841d56e73e0e75d74fd7c3 Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Fri, 5 May 2023 20:02:30 +0700
Subject: [PATCH 032/290] Use Longs.numberOfTrailingZeros
---
.../bouncycastle/crypto/modes/OCBBlockCipher.java | 14 ++------------
1 file changed, 2 insertions(+), 12 deletions(-)
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
index 42667197e1..e2d0c801e7 100644
--- a/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
@@ -11,6 +11,7 @@
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Longs;
/**
* An implementation of RFC 7253 on The OCB
@@ -570,18 +571,7 @@ protected static void OCB_extend(byte[] block, int pos)
protected static int OCB_ntz(long x)
{
- if (x == 0)
- {
- return 64;
- }
-
- int n = 0;
- while ((x & 1L) == 0L)
- {
- ++n;
- x >>>= 1;
- }
- return n;
+ return Longs.numberOfTrailingZeros(x);
}
protected static int shiftLeft(byte[] block, byte[] output)
From 4c749ab09e4421f87b59b1d2117c7173958dbb98 Mon Sep 17 00:00:00 2001
From: Paul Schaub
Date: Tue, 9 May 2023 22:06:15 +0200
Subject: [PATCH 033/290] JcePBEDataDecryptorFactoryBuilder.setProvider():
Inject provider into JceAEADUtil instance
---
.../operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
index 5e9ffce977..4c99f4ac05 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java
@@ -64,6 +64,7 @@ public JcePBEDataDecryptorFactoryBuilder(PGPDigestCalculatorProvider calculatorP
public JcePBEDataDecryptorFactoryBuilder setProvider(Provider provider)
{
this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+ this.aeadHelper = new JceAEADUtil(helper);
return this;
}
@@ -77,6 +78,7 @@ public JcePBEDataDecryptorFactoryBuilder setProvider(Provider provider)
public JcePBEDataDecryptorFactoryBuilder setProvider(String providerName)
{
this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+ this.aeadHelper = new JceAEADUtil(helper);
return this;
}
From 5c2783385b53095f2586890d366744bc84c48f12 Mon Sep 17 00:00:00 2001
From: Paul Schaub
Date: Sat, 6 May 2023 17:06:28 +0200
Subject: [PATCH 034/290] Fix wrong PGPSignature tests
---
.../org/bouncycastle/openpgp/test/PGPSignatureTest.java | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java
index 8c7ab719a0..5c8023cbbb 100644
--- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java
+++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPSignatureTest.java
@@ -561,7 +561,7 @@ public void performTest()
//
sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1).setProvider("BC"));
- sGen.init(PGPSignature.SUBKEY_BINDING, pgpPrivDSAKey);
+ sGen.init(PGPSignature.DEFAULT_CERTIFICATION, pgpPrivDSAKey);
sGen.setHashedSubpackets(null);
sGen.setUnhashedSubpackets(null);
@@ -572,7 +572,7 @@ public void performTest()
if (!sig.verifyCertification(TEST_USER_ID, secretKey.getPublicKey()))
{
- fail("subkey binding verification failed.");
+ fail("user-id verification failed.");
}
hashedPcks = sig.getHashedSubPackets();
@@ -605,7 +605,7 @@ public void performTest()
//
sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(PublicKeyAlgorithmTags.DSA, HashAlgorithmTags.SHA1).setProvider("BC"));
- sGen.init(PGPSignature.SUBKEY_BINDING, pgpPrivDSAKey);
+ sGen.init(PGPSignature.DEFAULT_CERTIFICATION, pgpPrivDSAKey);
hashedGen = new PGPSignatureSubpacketGenerator();
@@ -625,7 +625,7 @@ public void performTest()
if (!sig.verifyCertification(TEST_USER_ID, secretKey.getPublicKey()))
{
- fail("subkey binding verification failed.");
+ fail("user-id verification failed.");
}
hashedPcks = sig.getHashedSubPackets();
From 45a0aecc58fb6e138da8930842c70f12b5cd9da1 Mon Sep 17 00:00:00 2001
From: Paul Schaub
Date: Thu, 11 May 2023 10:22:15 +0200
Subject: [PATCH 035/290] PGPSignature.verify*(): Check signature types
---
.../bouncycastle/openpgp/PGPSignature.java | 26 ++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java
index 300f5e8ea1..ee8fec3ab2 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPSignature.java
@@ -290,6 +290,12 @@ public boolean verifyCertification(
throw new PGPException("PGPSignature not initialised - call init().");
}
+ if (!PGPSignature.isCertification(signatureType)
+ && PGPSignature.CERTIFICATION_REVOCATION != signatureType)
+ {
+ throw new PGPException("signature is neither a certification signature nor a certification revocation.");
+ }
+
updateWithPublicKey(key);
//
@@ -334,6 +340,12 @@ public boolean verifyCertification(
throw new PGPException("PGPSignature not initialised - call init().");
}
+ if (!PGPSignature.isCertification(signatureType)
+ && PGPSignature.CERTIFICATION_REVOCATION != signatureType)
+ {
+ throw new PGPException("signature is neither a certification signature nor a certification revocation.");
+ }
+
updateWithPublicKey(key);
//
@@ -365,6 +377,12 @@ public boolean verifyCertification(
throw new PGPException("PGPSignature not initialised - call init().");
}
+ if (!PGPSignature.isCertification(signatureType)
+ && PGPSignature.CERTIFICATION_REVOCATION != signatureType)
+ {
+ throw new PGPException("signature is neither a certification signature nor a certification revocation.");
+ }
+
updateWithPublicKey(key);
//
@@ -396,6 +414,13 @@ public boolean verifyCertification(
throw new PGPException("PGPSignature not initialised - call init().");
}
+ if (PGPSignature.SUBKEY_BINDING != signatureType
+ && PGPSignature.PRIMARYKEY_BINDING != signatureType
+ && PGPSignature.SUBKEY_REVOCATION != signatureType)
+ {
+ throw new PGPException("signature is not a key binding signature.");
+ }
+
updateWithPublicKey(masterKey);
updateWithPublicKey(pubKey);
@@ -435,7 +460,6 @@ public boolean verifyCertification(
}
if (this.getSignatureType() != KEY_REVOCATION
- && this.getSignatureType() != SUBKEY_REVOCATION
&& this.getSignatureType() != DIRECT_KEY)
{
throw new PGPException("signature is not a key signature");
From 75317d38d05d32d2bf6aa4e19426ec4c68d3b11d Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Thu, 11 May 2023 17:54:16 +0700
Subject: [PATCH 036/290] Refactoring in Picnic
---
.../pqc/crypto/picnic/PicnicEngine.java | 470 +++++++++---------
.../pqc/crypto/picnic/Signature.java | 20 +-
.../bouncycastle/pqc/crypto/picnic/Tape.java | 9 +-
.../bouncycastle/pqc/crypto/picnic/Utils.java | 31 ++
.../bouncycastle/pqc/crypto/picnic/View.java | 10 +-
.../main/java/org/bouncycastle/util/Pack.java | 19 +
6 files changed, 310 insertions(+), 249 deletions(-)
diff --git a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/PicnicEngine.java b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/PicnicEngine.java
index 89861b6f45..8530b39714 100644
--- a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/PicnicEngine.java
+++ b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/PicnicEngine.java
@@ -7,6 +7,7 @@
import org.bouncycastle.crypto.digests.SHAKEDigest;
import org.bouncycastle.math.raw.Bits;
import org.bouncycastle.util.Arrays;
+import org.bouncycastle.util.Integers;
import org.bouncycastle.util.Pack;
class PicnicEngine
@@ -340,19 +341,19 @@ public boolean crypto_sign_open(byte[] m, byte[] sm, byte[] pk)
private int picnic_verify(byte[] pk, byte[] message, byte[] signature, int sigLen)
{
- byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- byte[] ciphertext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- picnic_read_public_key(ciphertext_bytes, plaintext_bytes, pk);
int[] ciphertext = new int[stateSizeWords];
int[] plaintext = new int[stateSizeWords];
-
- Pack.littleEndianToInt(plaintext_bytes, 0, plaintext);
- Pack.littleEndianToInt(ciphertext_bytes, 0, ciphertext);
+ picnic_read_public_key(ciphertext, plaintext, pk);
if(is_picnic3(parameters))
{
Signature2 sig = new Signature2(this);
- deserializeSignature2(sig, signature, sigLen, message.length + 4);
+ int ret = deserializeSignature2(sig, signature, sigLen, message.length + 4);
+ if (ret != 0)
+ {
+ LOG.fine("Error couldn't deserialize signature (2)!");
+ return -1;
+ }
return verify_picnic3(sig, ciphertext, plaintext, message);
}
@@ -395,9 +396,13 @@ private int verify(Signature sig, int[] pubKey, int[] plaintext, byte[] message)
view1s[i] = new View(this);
view2s[i] = new View(this);
- verifyProof(proofs[i], view1s[i], view2s[i],
- getChallenge(received_challengebits, i), sig.salt, i,
- tmp, plaintext, tape);
+ if (!verifyProof(proofs[i], view1s[i], view2s[i],
+ getChallenge(received_challengebits, i), sig.salt, i,
+ tmp, plaintext, tape))
+ {
+ LOG.fine(("Invalid signature. Did not verify"));
+ return -1;
+ }
// create ordered array of commitments with order computed based on the challenge
// check commitments of the two opened views
@@ -416,7 +421,7 @@ private int verify(Signature sig, int[] pubKey, int[] plaintext, byte[] message)
viewOutputs[i][challenge] = view1s[i].outputShare;
viewOutputs[i][(challenge + 1) % 3] = view2s[i].outputShare;
int[] view3Output = new int[stateSizeWords]; /* pointer into the slab to the current 3rd view */
- xor_three(view3Output, view1s[i].outputShare, view2s[i].outputShare, pubKey, stateSizeBytes);
+ xor_three(view3Output, view1s[i].outputShare, view2s[i].outputShare, pubKey);
viewOutputs[i][(challenge + 2) % 3] = view3Output;
}
@@ -436,7 +441,7 @@ private int verify(Signature sig, int[] pubKey, int[] plaintext, byte[] message)
}
boolean verifyProof(Signature.Proof proof, View view1, View view2, int challenge, byte[] salt,
- int roundNumber, byte[] tmp, int[] plaintext, Tape tape)
+ int roundNumber, byte[] tmp, int[] plaintext, Tape tape)
{
System.arraycopy(proof.communicatedBits, 0, view2.communicatedBits, 0, andSizeBytes);
tape.pos = 0;
@@ -461,6 +466,7 @@ boolean verifyProof(Signature.Proof proof, View view1, View view2, int challenge
{
break;
}
+
Pack.littleEndianToInt(tmp, 0, view2.inputShare);//todo check
System.arraycopy(tmp, stateSizeBytes, tape.tapes[1], 0, andSizeBytes);
@@ -477,12 +483,13 @@ boolean verifyProof(Signature.Proof proof, View view1, View view2, int challenge
System.arraycopy(tmp, stateSizeBytes, tape.tapes[0], 0, andSizeBytes);
status = status && createRandomTape(proof.seed2, 0, salt, roundNumber,
2, tape.tapes[1], andSizeBytes);
+
if (!status)
{
break;
}
- System.arraycopy(proof.inputShare, 0, view2.inputShare, 0, stateSizeBytes);
+ System.arraycopy(proof.inputShare, 0, view2.inputShare, 0, stateSizeWords);
break;
case 2:
@@ -490,12 +497,14 @@ boolean verifyProof(Signature.Proof proof, View view1, View view2, int challenge
// it is not computable from the seed. We just need to compute view2's input from
// its seed
status = createRandomTape(proof.seed1, 0, salt, roundNumber, 2, tape.tapes[0], andSizeBytes);
- System.arraycopy(proof.inputShare, 0, view1.inputShare, 0, stateSizeBytes);
+ System.arraycopy(proof.inputShare, 0, view1.inputShare, 0, stateSizeWords);
status = status && createRandomTape(proof.seed2, 0, salt, roundNumber, 0, tmp, stateSizeBytes + andSizeBytes);
+
if (!status)
{
break;
}
+
Pack.littleEndianToInt(tmp, 0, view2.inputShare);//todo check
System.arraycopy(tmp, stateSizeBytes, tape.tapes[1], 0, andSizeBytes);
break;
@@ -510,18 +519,9 @@ boolean verifyProof(Signature.Proof proof, View view1, View view2, int challenge
LOG.fine("Failed to generate random tapes, signature verification will fail (but signature may actually be valid)");
return false;
}
- /* When input shares are read from the tapes, and the length is not a whole number of bytes, the trailing bits must be zero */
- byte[] view_bytes = new byte[stateSizeBytes * 4];
- Pack.intToLittleEndian(view1.inputShare, view_bytes, 0);
- Arrays.fill(view_bytes, stateSizeBytes, view_bytes.length, (byte) 0); //todo have correct size: reduce view.inputshare by /4
- zeroTrailingBits(view_bytes, stateSizeBits);
- Pack.littleEndianToInt(view_bytes, 0, view1.inputShare);
- Pack.intToLittleEndian(view2.inputShare, view_bytes, 0);
- Arrays.fill(view_bytes, stateSizeBytes, view_bytes.length, (byte) 0);
- zeroTrailingBits(view_bytes, stateSizeBits);
-
- Pack.littleEndianToInt(view_bytes, 0, view2.inputShare);
+ Utils.zeroTrailingBits(view1.inputShare, stateSizeBits);
+ Utils.zeroTrailingBits(view2.inputShare, stateSizeBits);
int[] tmp_ints = Pack.littleEndianToInt(tmp, 0, tmp.length/4);
mpc_LowMC_verify(view1, view2, tape, tmp_ints, plaintext, challenge);
@@ -542,7 +542,7 @@ void mpc_LowMC_verify(View view1, View view2, Tape tapes, int[] tmp, int[] plain
view2.inputShare, 0,
current.getData(), current.getMatrixPointer());
- mpc_xor(tmp, tmp, stateSizeWords, 2);
+ mpc_xor(tmp, tmp, 2);
for (int r = 1; r <= numRounds; ++r)
{
@@ -563,7 +563,7 @@ void mpc_LowMC_verify(View view1, View view2, Tape tapes, int[] tmp, int[] plain
current = lowmcConstants.RConstant(this, r - 1);
mpc_xor_constant_verify(tmp, current.getData(), current.getMatrixPointer(), stateSizeWords, challenge);
- mpc_xor(tmp, tmp, stateSizeWords, 2);
+ mpc_xor(tmp, tmp, 2);
}
System.arraycopy(tmp, 2*stateSizeWords, view1.outputShare, 0, stateSizeWords);
@@ -609,10 +609,14 @@ void mpc_substitution_verify(int[] state, Tape rand, View view1, View view2)
void mpc_AND_verify(int[] in1, int[] in2, int[] out, Tape rand, View view1, View view2)
{
- int[] r = { Utils.getBit(rand.tapes[0], rand.pos), Utils.getBit(rand.tapes[1], rand.pos) };
+ int r0 = Utils.getBit(rand.tapes[0], rand.pos);
+ int r1 = Utils.getBit(rand.tapes[1], rand.pos);
+
+ int a0 = in1[0], a1 = in1[1];
+ int b0 = in2[0], b1 = in2[1];
- out[0] = (in1[0] & in2[1]) ^ (in1[1] & in2[0]) ^ (in1[0] & in2[0]) ^ r[0] ^ r[1];
- Utils.setBit(view1.communicatedBits, rand.pos, (byte) (out[0] & 0xff));
+ out[0] = (a0 & b1) ^ (a1 & b0) ^ (a0 & b0) ^ r0 ^ r1;
+ Utils.setBit(view1.communicatedBits, rand.pos, (byte)out[0]);
out[1] = Utils.getBit(view2.communicatedBits, rand.pos);
rand.pos++;
@@ -636,49 +640,52 @@ else if (challenge == 2)
}
for (int i = 0; i < length; i++)
{
- state[i + offset] = state[i + offset] ^ in[i + inOffset];
+ state[i + offset] ^= in[i + inOffset];
}
}
-
private int deserializeSignature(Signature sig, byte[] sigBytes, int sigBytesLen, int sigBytesOffset)
{
Signature.Proof[] proofs = sig.proofs;
byte[] challengeBits = sig.challengeBits;
+ int challengesLength = Utils.numBytes(2 * numMPCRounds);
/* Validate input buffer is large enough */
- if (sigBytesLen < Utils.numBytes(2 * numMPCRounds))
+ if (sigBytesLen < challengesLength)
{ /* ensure the input has at least the challenge */
return -1;
}
- int inputShareSize = computeInputShareSize(sigBytes, stateSizeBytes);
- int bytesExpected = Utils.numBytes(2 * numMPCRounds) + saltSizeBytes +
- numMPCRounds * (2 * seedSizeBytes + andSizeBytes + digestSizeBytes) + inputShareSize;
+ // NOTE: This also validates that there are no challenges > 2
+ int numNonZeroChallenges = countNonZeroChallenges(sigBytes, sigBytesOffset);
+ if (numNonZeroChallenges < 0)
+ return -1;
+
+ int inputShareSize = numNonZeroChallenges * stateSizeBytes;
+ int bytesRequired = challengesLength + saltSizeBytes +
+ numMPCRounds * (2 * seedSizeBytes + andSizeBytes + digestSizeBytes) + inputShareSize;
if (transform == TRANSFORM_UR)
{
- bytesExpected += UnruhGWithoutInputBytes * numMPCRounds;
+ bytesRequired += UnruhGWithInputBytes * (numMPCRounds - numNonZeroChallenges);
+ bytesRequired += UnruhGWithoutInputBytes * numNonZeroChallenges;
}
- if (sigBytesLen < bytesExpected)
+
+ if (sigBytesLen != bytesRequired)
{
+ LOG.fine("sigBytesLen = " + sigBytesLen + ", expected bytesRequired = " + bytesRequired);
return -1;
}
- System.arraycopy(sigBytes, sigBytesOffset, challengeBits, 0, Utils.numBytes(2 * numMPCRounds));
- sigBytesOffset += Utils.numBytes(2 * numMPCRounds);
+ System.arraycopy(sigBytes, sigBytesOffset, challengeBits, 0, challengesLength);
+ sigBytesOffset += challengesLength;
- if (!isChallengeValid(challengeBits))
- {
- return -1;
- }
System.arraycopy(sigBytes, sigBytesOffset, sig.salt, 0, saltSizeBytes);
sigBytesOffset += saltSizeBytes;
for (int i = 0; i < numMPCRounds; i++)
{
-
int challenge = getChallenge(challengeBits, i);
System.arraycopy(sigBytes, sigBytesOffset, proofs[i].view3Commitment, 0, digestSizeBytes);
@@ -710,7 +717,8 @@ private int deserializeSignature(Signature sig, byte[] sigBytes, int sigBytesLen
}
sigBytesOffset += stateSizeBytes;
- if(!arePaddingBitsZero(Pack.intToLittleEndian(proofs[i].inputShare), stateSizeBits))
+
+ if (!arePaddingBitsZero(proofs[i].inputShare, stateSizeBits))
{
return -1;
}
@@ -721,43 +729,50 @@ private int deserializeSignature(Signature sig, byte[] sigBytes, int sigBytesLen
return 0;
}
- private boolean isChallengeValid(byte[] challengeBits)
- {
- for (int i = 0; i < numMPCRounds; i++)
- {
- int challenge = getChallenge(challengeBits, i);
- if (challenge > 2)
- {
- return false;
- }
- }
- return true;
- }
-
- private int computeInputShareSize(byte[] challengeBits, int stateSizeBytes)
+ private int countNonZeroChallenges(byte[] challengeBits, int challengeBitsOffset)
{
/* When the FS transform is used, the input share is included in the proof
- * only when the challenge is 1 or 2. When dersializing, to compute the
+ * only when the challenge is 1 or 2. When deserializing, to compute the
* number of bytes expected, we must check how many challenge values are 1
- * or 2. The parameter stateSizeBytes is the size of an input share. */
- int inputShareSize = 0;
+ * or 2. We also check that no challenges have the invalid value 3. */
+ int count = 0;
+ int challenges3 = 0;
- for (int i = 0; i < numMPCRounds; i++)
+ int i = 0;
+ while (i + 16 <= numMPCRounds)
{
- int challenge = getChallenge(challengeBits, i);
- if (challenge == 1 || challenge == 2)
- {
- inputShareSize += stateSizeBytes;
- }
+ int challenges = Pack.littleEndianToInt(challengeBits, challengeBitsOffset + (i >>> 2));
+ challenges3 |= challenges & (challenges >>> 1);
+ count += Integers.bitCount((challenges ^ (challenges >>> 1)) & 0x55555555);
+ i += 16;
}
- return inputShareSize;
+
+ int remainingBits = (numMPCRounds - i) * 2;
+ if (remainingBits > 0)
+ {
+ int remainingBytes = (remainingBits + 7) / 8;
+ int challenges = Pack.littleEndianToInt_Low(challengeBits, challengeBitsOffset + (i >>> 2), remainingBytes);
+ challenges &= Utils.getTrailingBitsMask(remainingBits);
+ challenges3 |= challenges & (challenges >>> 1);
+ count += Integers.bitCount((challenges ^ (challenges >>> 1)) & 0x55555555);
+ }
+
+ return (challenges3 & 0x55555555) == 0 ? count : -1;
}
- private int picnic_read_public_key(byte[] ciphertext, byte[] plaintext, byte[] pk)
+ private void picnic_read_public_key(int[] ciphertext, int[] plaintext, byte[] pk)
{
- System.arraycopy(pk, 1, ciphertext, 0, stateSizeBytes);
- System.arraycopy(pk, 1 + stateSizeBytes, plaintext, 0, stateSizeBytes);
- return 0;
+ int ciphertextPos = 1, plaintextPos = 1 + stateSizeBytes;
+ int fullWords = stateSizeBytes / 4;
+ Pack.littleEndianToInt(pk, ciphertextPos, ciphertext, 0, fullWords);
+ Pack.littleEndianToInt(pk, plaintextPos, plaintext, 0, fullWords);
+
+ if (fullWords < stateSizeWords)
+ {
+ int fullWordBytes = fullWords * 4, partialWordBytes = stateSizeBytes - fullWordBytes;
+ ciphertext[fullWords] = Pack.littleEndianToInt_Low(pk, ciphertextPos + fullWordBytes, partialWordBytes);
+ plaintext[fullWords] = Pack.littleEndianToInt_Low(pk, plaintextPos + fullWordBytes, partialWordBytes);
+ }
}
private int verify_picnic3(Signature2 sig, int[] pubKey, int[] plaintext, byte[] message)
@@ -984,7 +999,7 @@ private int deserializeSignature2(Signature2 sig, byte[] sigBytes, int sigLen,
/* Fail if the signature does not have the exact number of bytes we expect */
if (sigLen != bytesRequired)
{
- LOG.fine("sigBytesLen = " + sigBytes.length + ", expected bytesRequired = " + bytesRequired);
+ LOG.fine("sigLen = " + sigLen + ", expected bytesRequired = " + bytesRequired);
return -1;
}
@@ -1054,6 +1069,16 @@ private boolean arePaddingBitsZero(byte[] data, int bitLength)
return true;
}
+ private boolean arePaddingBitsZero(int[] data, int bitLength)
+ {
+ int partialWord = bitLength & 31;
+ if (partialWord == 0)
+ return true;
+
+ int mask = Utils.getTrailingBitsMask(bitLength);
+ return (data[bitLength >>> 5] & ~mask) == 0;
+ }
+
public void crypto_sign(byte[] sm, byte[] m, byte[] sk)
{
boolean ret = picnic_sign(sk, m, sm);
@@ -1062,27 +1087,27 @@ public void crypto_sign(byte[] sm, byte[] m, byte[] sk)
return; // throw error?
}
System.arraycopy(m, 0, sm, 4, m.length);
-// sm = Arrays.copyOfRange(sm, 0, signatureLength);
}
private boolean picnic_sign(byte[] sk, byte[] message, byte[] signature)
{
- //todo unify conversion
-
- byte[] data_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- System.arraycopy(sk, 1, data_bytes, 0, stateSizeBytes);
- byte[] ciphertext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- System.arraycopy(sk, 1 + stateSizeBytes, ciphertext_bytes, 0, stateSizeBytes);
- byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- System.arraycopy(sk, 1 + 2 * stateSizeBytes, plaintext_bytes, 0, stateSizeBytes);
int[] data = new int[stateSizeWords];
int[] ciphertext = new int[stateSizeWords];
int[] plaintext = new int[stateSizeWords];
- Pack.littleEndianToInt(data_bytes, 0, data);
- Pack.littleEndianToInt(plaintext_bytes, 0, plaintext);
- Pack.littleEndianToInt(ciphertext_bytes, 0, ciphertext);
+ int dataPos = 1, ciphertextPos = 1 + stateSizeBytes, plaintextPos = 1 + 2 * stateSizeBytes;
+ int fullWords = stateSizeBytes / 4;
+ Pack.littleEndianToInt(sk, dataPos, data, 0, fullWords);
+ Pack.littleEndianToInt(sk, ciphertextPos, ciphertext, 0, fullWords);
+ Pack.littleEndianToInt(sk, plaintextPos, plaintext, 0, fullWords);
+ if (fullWords < stateSizeWords)
+ {
+ int fullWordBytes = fullWords * 4, partialWordBytes = stateSizeBytes - fullWordBytes;
+ data[fullWords] = Pack.littleEndianToInt_Low(sk, dataPos + fullWordBytes, partialWordBytes);
+ ciphertext[fullWords] = Pack.littleEndianToInt_Low(sk, ciphertextPos + fullWordBytes, partialWordBytes);
+ plaintext[fullWords] = Pack.littleEndianToInt_Low(sk, plaintextPos + fullWordBytes, partialWordBytes);
+ }
if(!is_picnic3(parameters))
{
@@ -1096,11 +1121,12 @@ private boolean picnic_sign(byte[] sk, byte[] message, byte[] signature)
}
int len = serializeSignature(sig, signature, message.length + 4);
- if (len == -1)
+ if (len < 0)
{
LOG.fine("Failed to serialize signature");
return false;
}
+
signatureLength = len;
Pack.intToLittleEndian(len, signature, 0);
return true;
@@ -1116,7 +1142,7 @@ private boolean picnic_sign(byte[] sk, byte[] message, byte[] signature)
}
int len = serializeSignature2(sig, signature, message.length + 4);
- if (len == -1)
+ if (len < 0)
{
LOG.fine("Failed to serialize signature");
return false;
@@ -1155,7 +1181,6 @@ int serializeSignature(Signature sig, byte[] sigBytes, int sigOffset)
sigByteIndex += Utils.numBytes(2 * numMPCRounds);
System.arraycopy(sig.salt, 0, sigBytes, sigByteIndex,saltSizeBytes);
-
sigByteIndex += saltSizeBytes;
for (int i = 0; i < numMPCRounds; i++)
@@ -1194,12 +1219,11 @@ int serializeSignature(Signature sig, byte[] sigBytes, int sigOffset)
int getChallenge(byte[] challenge, int round)
{
- return (Utils.getBit(challenge, 2 * round + 1) << 1) | Utils.getBit(challenge, 2 * round);
+ return Utils.getCrumbAligned(challenge, round);
}
private int serializeSignature2(Signature2 sig, byte[] sigBytes, int sigOffset)
{
-
/* Compute the number of bytes required for the signature */
int bytesRequired = digestSizeBytes + saltSizeBytes; /* challenge and salt */
@@ -1289,7 +1313,6 @@ private int sign_picnic1(int[] privateKey, int[] pubKey, int[] plaintext, byte[]
Tape tape = new Tape(this);
byte[] tmp = new byte[Math.max(9 * stateSizeBytes, stateSizeBytes + andSizeBytes)];
- byte[] view_byte = new byte[stateSizeBytes*4];
for (int k = 0; k < numMPCRounds; k++)
{
@@ -1306,9 +1329,11 @@ private int sign_picnic1(int[] privateKey, int[] pubKey, int[] plaintext, byte[]
LOG.fine("createRandomTape failed");
return -1;
}
- System.arraycopy(tmp, 0, view_byte, 0, stateSizeBytes);
- zeroTrailingBits(view_byte, stateSizeBits);
- Pack.littleEndianToInt(view_byte, 0, views[k][j].inputShare);
+
+ int[] inputShare = views[k][j].inputShare;
+ Pack.littleEndianToInt(tmp, 0, inputShare);
+ Utils.zeroTrailingBits(inputShare, stateSizeBits);
+
System.arraycopy(tmp, stateSizeBytes, tape.tapes[j], 0, andSizeBytes);
}
@@ -1322,7 +1347,7 @@ private int sign_picnic1(int[] privateKey, int[] pubKey, int[] plaintext, byte[]
return -1;
}
- xor_three(views[k][2].inputShare, privateKey, views[k][0].inputShare, views[k][1].inputShare, stateSizeBytes);
+ xor_three(views[k][2].inputShare, privateKey, views[k][0].inputShare, views[k][1].inputShare);
tape.pos = 0;
int[] tmp_int = Pack.littleEndianToInt(tmp, 0, tmp.length/4);
@@ -1331,8 +1356,7 @@ private int sign_picnic1(int[] privateKey, int[] pubKey, int[] plaintext, byte[]
Pack.intToLittleEndian(tmp_int, tmp, 0);
int[] temp = new int[LOWMC_MAX_WORDS];
- xor_three(temp, views[k][0].outputShare, views[k][1].outputShare, views[k][2].outputShare, stateSizeBytes);
-
+ xor_three(temp, views[k][0].outputShare, views[k][1].outputShare, views[k][2].outputShare);
if(!subarrayEquals(temp, pubKey, stateSizeWords))
{
@@ -1354,16 +1378,8 @@ private int sign_picnic1(int[] privateKey, int[] pubKey, int[] plaintext, byte[]
}
//Generating challenges
- int[][][] viewOutputs = new int[numMPCRounds][3][stateSizeBytes];
- for (int i = 0; i < numMPCRounds; i++)
- {
- for (int j = 0; j < 3; j++)
- {
- viewOutputs[i][j] = views[i][j].outputShare;
- }
- }
- H3(pubKey, plaintext, viewOutputs, as, sig.challengeBits, sig.salt, message, gs);
+ H3(pubKey, plaintext, views, as, sig.challengeBits, sig.salt, message, gs);
//Packing Z
for (int i = 0; i < numMPCRounds; i++)
@@ -1373,7 +1389,6 @@ private int sign_picnic1(int[] privateKey, int[] pubKey, int[] plaintext, byte[]
views[i], as[i], (transform != TRANSFORM_UR) ? null : gs[i]); //todo check if
}
-
return 0;
}
@@ -1399,12 +1414,14 @@ else if (challenge == 2)
else
{
LOG.fine("Invalid challenge");
+ throw new IllegalArgumentException("challenge");
}
if (challenge == 1 || challenge == 2)
{
- System.arraycopy(views[2].inputShare, 0, proof.inputShare, 0, stateSizeBytes);
+ System.arraycopy(views[2].inputShare, 0, proof.inputShare, 0, stateSizeWords);
}
+
System.arraycopy(views[(challenge + 1) % 3].communicatedBits, 0, proof.communicatedBits, 0, andSizeBytes);
System.arraycopy(commitments[(challenge + 2) % 3], 0, proof.view3Commitment, 0, digestSizeBytes);
@@ -1414,28 +1431,56 @@ else if (challenge == 2)
System.arraycopy(gs[(challenge + 2) % 3], 0, proof.view3UnruhG, 0, view3UnruhLength);
}
}
-
- void H3(int[] circuitOutput, int[] plaintext, int[][][] viewOutputs,
- byte[][][] as, byte[] challengeBits, byte[] salt,
- byte[] message, byte[][][] gs)
+
+ private void H3(int[] circuitOutput, int[] plaintext, View[][] views, byte[][][] as, byte[] challengeBits,
+ byte[] salt, byte[] message, byte[][][] gs)
{
- byte[] hash = new byte[digestSizeBytes];
+ digest.update((byte) 1);
- /* Depending on the number of rounds, we might not set part of the last
- * byte, make sure it's always zero. */
- challengeBits[Utils.numBytes(numMPCRounds * 2) - 1] = 0;
+ byte[] tmp = new byte[stateSizeWords * 4];
+
+ /* Hash the output share from each view */
+ for (int i = 0; i < numMPCRounds; i++)
+ {
+ for (int j = 0; j < 3; j++)
+ {
+ Pack.intToLittleEndian(views[i][j].outputShare, tmp, 0);
+ digest.update(tmp, 0, stateSizeBytes);
+ }
+ }
+ implH3(circuitOutput, plaintext, as, challengeBits, salt, message, gs);
+ }
+
+ private void H3(int[] circuitOutput, int[] plaintext, int[][][] viewOutputs, byte[][][] as, byte[] challengeBits,
+ byte[] salt, byte[] message, byte[][][] gs)
+ {
digest.update((byte) 1);
+ byte[] tmp = new byte[stateSizeWords * 4];
+
/* Hash the output share from each view */
for (int i = 0; i < numMPCRounds; i++)
{
for (int j = 0; j < 3; j++)
{
- digest.update( Pack.intToLittleEndian(viewOutputs[i][j]), 0, stateSizeBytes);
+ Pack.intToLittleEndian(viewOutputs[i][j], tmp, 0);
+ digest.update(tmp, 0, stateSizeBytes);
}
}
+ implH3(circuitOutput, plaintext, as, challengeBits, salt, message, gs);
+ }
+
+ private void implH3(int[] circuitOutput, int[] plaintext, byte[][][] as, byte[] challengeBits, byte[] salt,
+ byte[] message, byte[][][] gs)
+ {
+ byte[] hash = new byte[digestSizeBytes];
+
+ /* Depending on the number of rounds, we might not set part of the last
+ * byte, make sure it's always zero. */
+ challengeBits[Utils.numBytes(numMPCRounds * 2) - 1] = 0;
+
/* Hash all the commitments C */
for (int i = 0; i < numMPCRounds; i++)
{
@@ -1551,7 +1596,7 @@ private void mpc_LowMC(Tape tapes, View[] views, int[] plaintext, int[] slab)
current.getData(), current.getMatrixPointer());
}
- mpc_xor(slab, slab, stateSizeWords, 3);
+ mpc_xor(slab, slab, 3);
for (int r = 1; r <= numRounds; r++)
{
@@ -1574,7 +1619,7 @@ private void mpc_LowMC(Tape tapes, View[] views, int[] plaintext, int[] slab)
mpc_xor_constant(slab, 3*stateSizeWords,
current.getData(), current.getMatrixPointer(), stateSizeWords);
- mpc_xor(slab, slab, stateSizeWords, 3);
+ mpc_xor(slab, slab, 3);
}
for (int i = 0; i < 3; i++)
@@ -1638,37 +1683,31 @@ private void mpc_substitution(int[] state, Tape rand, View[] views)
/*** Functions implementing Sign ***/
private void mpc_AND(int[] in1, int[] in2, int[] out, Tape rand, View[] views)
{
- int[] r = new int[]{
- Utils.getBit(rand.tapes[0], rand.pos),
- Utils.getBit(rand.tapes[1], rand.pos),
- Utils.getBit(rand.tapes[2], rand.pos)
- };
+ int r0 = Utils.getBit(rand.tapes[0], rand.pos);
+ int r1 = Utils.getBit(rand.tapes[1], rand.pos);
+ int r2 = Utils.getBit(rand.tapes[2], rand.pos);
- for (int i = 0; i < 3; i++)
- {
- out[i] = (in1[i] & in2[(i + 1) % 3]) ^ (in1[(i + 1) % 3] & in2[i])
- ^ (in1[i] & in2[i]) ^ r[i] ^ r[(i + 1) % 3];
+ out[0] = (in1[0] & in2[1]) ^ (in1[1] & in2[0]) ^ (in1[0] & in2[0]) ^ r0 ^ r1;
+ out[1] = (in1[1] & in2[2]) ^ (in1[2] & in2[1]) ^ (in1[1] & in2[1]) ^ r1 ^ r2;
+ out[2] = (in1[2] & in2[0]) ^ (in1[0] & in2[2]) ^ (in1[2] & in2[2]) ^ r2 ^ r0;
+
+ Utils.setBit(views[0].communicatedBits, rand.pos, (byte)out[0]);
+ Utils.setBit(views[1].communicatedBits, rand.pos, (byte)out[1]);
+ Utils.setBit(views[2].communicatedBits, rand.pos, (byte)out[2]);
- Utils.setBit(views[i].communicatedBits, rand.pos, (byte) (out[i] & 0xff));
- }
rand.pos++;
}
-
- private void mpc_xor(int[] state, int[] in, int len, int players)
+ private void mpc_xor(int[] state, int[] in, int players)
{
- for (int player = 0; player < players; player++)
+ for (int i = 0, count = stateSizeWords * players; i < count; ++i)
{
- for (int i = 0; i < len; i++)
- {
- state[i + (players+player)*stateSizeWords] = state[i + (players+player)*stateSizeWords] ^ in[i + player*stateSizeWords];
- }
+ state[players * stateSizeWords + i] ^= in[i];
}
}
-
private void mpc_matrix_mul(int[] output, int outputOffset, int[] state, int stateOffset,
- int[] matrix, int matrixOffset, int players)
+ int[] matrix, int matrixOffset, int players)
{
for (int player = 0; player < players; player++)
{
@@ -1683,7 +1722,7 @@ private void mpc_xor_constant(int[] state, int stateOffset, int[] in, int inOffs
{
for (int i = 0; i < len; i++)
{
- state[i + stateOffset] = state[i + stateOffset] ^ in[i + inOffset];
+ state[i + stateOffset] ^= in[i + inOffset];
}
}
@@ -1716,11 +1755,12 @@ private boolean createRandomTape(byte[] seed, int seedOffset, byte[] salt, int r
private byte[] computeSeeds(int[] privateKey, int[] publicKey, int[] plaintext, byte[] message)
{
byte[] allSeeds = new byte[seedSizeBytes * (numMPCParties * numMPCRounds) + saltSizeBytes];
+ byte[] temp = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- digest.update(Pack.intToLittleEndian(privateKey), 0, stateSizeBytes);
+ updateDigest(privateKey, temp);
digest.update(message, 0, message.length);
- digest.update(Pack.intToLittleEndian(publicKey), 0, stateSizeBytes);
- digest.update(Pack.intToLittleEndian(plaintext), 0, stateSizeBytes);
+ updateDigest(publicKey, temp);
+ updateDigest(plaintext, temp);
digest.update(Pack.intToLittleEndian(stateSizeBits), 0, 2);
// Derive the N*T seeds + 1 salt
@@ -1781,7 +1821,7 @@ private boolean sign_picnic3(int[] privateKey, int[] pubKey, int[] plaintext, by
{
msgs[t] = new Msg(this);
int[] maskedKey = Pack.littleEndianToInt(inputs[t], 0, stateSizeWords);
- xor_array(maskedKey, maskedKey, privateKey, 0, stateSizeWords);
+ xor_array(maskedKey, maskedKey, privateKey, 0);
int rv = simulateOnline(maskedKey, tapes[t], tmp_shares, msgs[t], plaintext, pubKey);
if (rv != 0)
{
@@ -1864,8 +1904,6 @@ static int indexOf(int[] list, int len, int value)
return -1;
}
-
-
private int[] getMissingLeavesList(int[] challengeC)
{
int missingLeavesSize = numMPCRounds - numOpenedRounds;
@@ -1884,7 +1922,6 @@ private int[] getMissingLeavesList(int[] challengeC)
return missingLeaves;
}
-
private void HCP(byte[] challengeHash, int[] challengeC, int[] challengeP, byte[][] Ch,
byte[] hCv, byte[] salt, int[] pubKey, int[] plaintext, byte[] message)
{
@@ -1895,10 +1932,12 @@ private void HCP(byte[] challengeHash, int[] challengeC, int[] challengeP, byte[
digest.update(Ch[t], 0, digestSizeBytes);
}
+ byte[] temp = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
+
digest.update(hCv, 0, digestSizeBytes);
digest.update(salt, 0, saltSizeBytes);
- digest.update(Pack.intToLittleEndian(pubKey), 0, stateSizeBytes);
- digest.update(Pack.intToLittleEndian(plaintext), 0, stateSizeBytes);
+ updateDigest(pubKey, temp);
+ updateDigest(plaintext, temp);
digest.update(message, 0, message.length);
digest.doFinal(challengeHash, 0, digestSizeBytes);
@@ -1914,7 +1953,8 @@ static int bitsToChunks(int chunkLenBits, byte[] input, int inputLen, int[] chun
{
return 0;
}
- int chunkCount = ((inputLen * 8) / chunkLenBits);
+
+ int chunkCount = (inputLen * 8) / chunkLenBits;
for (int i = 0; i < chunkCount; i++)
{
@@ -1948,8 +1988,6 @@ static int appendUnique(int[] list, int value, int position)
return position + 1;
}
-
-
private void expandChallengeHash(byte[] challengeHash, int[] challengeC, int[] challengeP)
{
// Populate C
@@ -2026,8 +2064,6 @@ private void commit_v(byte[] digest_arr, byte[] input, Msg msg)
digest.doFinal(digest_arr, 0, digestSizeBytes);
}
-
-
private int simulateOnline(int[] maskedKey, Tape tape, int[] tmp_shares,
Msg msg, int[] plaintext, int[] pubKey)
{
@@ -2037,7 +2073,7 @@ private int simulateOnline(int[] maskedKey, Tape tape, int[] tmp_shares,
KMatricesWithPointer current = lowmcConstants.KMatrix(this,0);
matrix_mul(roundKey, maskedKey, current.getData(), current.getMatrixPointer()); // roundKey = maskedKey * KMatrix[0]
- xor_array(state, roundKey, plaintext,0, stateSizeWords); // state = plaintext + roundKey
+ xor_array(state, roundKey, plaintext, 0); // state = plaintext + roundKey
for (int r = 1; r <= numRounds; r++)
{
@@ -2048,11 +2084,11 @@ private int simulateOnline(int[] maskedKey, Tape tape, int[] tmp_shares,
matrix_mul(state, state, current.getData(), current.getMatrixPointer()); // state = state * LMatrix (r-1)
current = lowmcConstants.RConstant(this,r - 1);
- xor_array(state, state, current.getData(), current.getMatrixPointer(), stateSizeWords); // state += RConstant
+ xor_array(state, state, current.getData(), current.getMatrixPointer()); // state += RConstant
current = lowmcConstants.KMatrix(this, r);
matrix_mul(roundKey, maskedKey, current.getData(), current.getMatrixPointer());
- xor_array(state, roundKey, state, 0, stateSizeWords); // state += roundKey
+ xor_array(state, roundKey, state, 0); // state += roundKey
}
if(!(subarrayEquals(state, pubKey, stateSizeWords)))
@@ -2078,26 +2114,34 @@ private void createRandomTapes(Tape tape, byte[][] seeds, int seedsOffset, byte[
private static boolean subarrayEquals(byte[] a, byte[] b, int length)
{
- if(a.length < length || b.length < length)
+ if (a.length < length || b.length < length)
+ {
return false;
+ }
for (int i = 0; i < length; i++)
{
- if(a[i] != b[i])
+ if (a[i] != b[i])
+ {
return false;
+ }
}
return true;
}
private static boolean subarrayEquals(int[] a, int[] b, int length)
{
- if(a.length < length || b.length < length)
+ if (a.length < length || b.length < length)
+ {
return false;
+ }
for (int i = 0; i < length; i++)
{
if(a[i] != b[i])
+ {
return false;
+ }
}
return true;
}
@@ -2191,7 +2235,6 @@ private void aux_mpc_AND(int mask_a, int mask_b, int fresh_output_mask, Tape tap
Utils.setBit(tape.tapes[lastParty], tape.pos - 1, (byte) (aux_bit & 0xff));
}
-
private boolean contains(int[] list, int len, int value)
{
for (int i = 0; i < len; i++)
@@ -2214,15 +2257,16 @@ private void tapesToWords(int[] shares, Tape tape)
private void getAuxBits(byte[] output, Tape tape)
{
- int last = numMPCParties - 1;
- int pos = 0;
- int n = stateSizeBits;
+ byte[] lastTape = tape.tapes[numMPCParties - 1];
+ int n = stateSizeBits, pos = 0, tapePos = 0;
for(int j = 0; j < numRounds; j++)
{
+ tapePos += n;
+
for(int i = 0; i < n; i++)
{
- Utils.setBit(output, pos++, Utils.getBit(tape.tapes[last], n + n*2*j + i));
+ Utils.setBit(output, pos++, Utils.getBit(lastTape, tapePos++));
}
}
}
@@ -2243,46 +2287,38 @@ private void commit(byte[] digest_arr, byte[] seed, byte[] aux, byte[] salt, int
private void computeSaltAndRootSeed(byte[] saltAndRoot, int[] privateKey, int[] pubKey, int[] plaintext, byte[] message)
{
- //todo unify conversion
- //copy back to byte array
- byte[] privatekey_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- byte[] pubkey_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- Pack.intToLittleEndian(privateKey, privatekey_bytes, 0);
- Pack.intToLittleEndian(pubKey, pubkey_bytes, 0);
- Pack.intToLittleEndian(plaintext, plaintext_bytes, 0);
- privatekey_bytes = Arrays.copyOfRange(privatekey_bytes, 0, stateSizeBytes);
- pubkey_bytes = Arrays.copyOfRange(pubkey_bytes, 0, stateSizeBytes);
- plaintext_bytes = Arrays.copyOfRange(plaintext_bytes, 0, stateSizeBytes);
- //
+ byte[] temp = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
// init done in constructor
- digest.update(privatekey_bytes, 0, stateSizeBytes);
+ updateDigest(privateKey, temp);
digest.update(message, 0, message.length);
- digest.update(pubkey_bytes, 0, stateSizeBytes);
- digest.update(plaintext_bytes, 0, stateSizeBytes);
- digest.update(Pack.shortToLittleEndian((short)(stateSizeBits & 0xffff)), 0, 2);
+ updateDigest(pubKey, temp);
+ updateDigest(plaintext, temp);
+ Pack.shortToLittleEndian((short)stateSizeBits, temp, 0);
+ digest.update(temp, 0, 2);
digest.doFinal(saltAndRoot, 0, saltAndRoot.length);
}
+ private void updateDigest(int[] block, byte[] temp)
+ {
+ Pack.intToLittleEndian(block, temp, 0);
+ digest.update(temp, 0, stateSizeBytes);
+ }
+
static boolean is_picnic3(int params)
{
- if (params == 7/*Picnic3_L1*/ ||
- params == 8/*Picnic3_L3*/ ||
- params == 9/*Picnic3_L5*/ )
- {
- return true;
- }
- return false;
+ return params == 7/*Picnic3_L1*/
+ || params == 8/*Picnic3_L3*/
+ || params == 9/*Picnic3_L5*/;
}
//todo return int;
public void crypto_sign_keypair(byte[] pk, byte[] sk, SecureRandom random)
{
// set array sizes
- byte[] plaintext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- byte[] ciphertext_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
- byte[] data_bytes = new byte[PICNIC_MAX_LOWMC_BLOCK_SIZE];
+ byte[] plaintext_bytes = new byte[stateSizeWords * 4];
+ byte[] ciphertext_bytes = new byte[stateSizeWords * 4];
+ byte[] data_bytes = new byte[stateSizeWords * 4];
picnic_keygen(plaintext_bytes, ciphertext_bytes, data_bytes, random);
picnic_write_public_key(ciphertext_bytes, plaintext_bytes, pk);
@@ -2325,28 +2361,16 @@ private void picnic_keygen(byte[] plaintext_bytes, byte[] ciphertext_bytes, byte
int[] data = new int[data_bytes.length/4];
int[] plaintext = new int[plaintext_bytes.length/4];
int[] ciphertext = new int[ciphertext_bytes.length/4];
- byte[] temp = new byte[stateSizeBytes];
// generate a private key
- random.nextBytes(temp);
- zeroTrailingBits(temp, stateSizeBits);
- System.arraycopy(temp, 0, data_bytes, 0, temp.length);
-
- for (int i = 0; i < data.length; i++)
- {
- data[i] = Pack.littleEndianToInt(data_bytes, i * 4);
- }
-
+ random.nextBytes(data_bytes);
+ Pack.littleEndianToInt(data_bytes, 0, data);
+ Utils.zeroTrailingBits(data, stateSizeBits);
// generate a plaintext block
- random.nextBytes(temp);
- zeroTrailingBits(temp, stateSizeBits);
- System.arraycopy(temp, 0, plaintext_bytes, 0, temp.length);
-
- for (int i = 0; i < plaintext.length; i++)
- {
- plaintext[i] = Pack.littleEndianToInt(plaintext_bytes, i * 4);
- }
+ random.nextBytes(plaintext_bytes);
+ Pack.littleEndianToInt(plaintext_bytes, 0, plaintext);
+ Utils.zeroTrailingBits(plaintext, stateSizeBits);
// computer ciphertext
LowMCEnc(plaintext, ciphertext, data);
@@ -2371,7 +2395,7 @@ private void LowMCEnc(int[] plaintext, int[] output, int[] key)
KMatricesWithPointer current = lowmcConstants.KMatrix(this,0);
matrix_mul(roundKey, key, current.getData(), current.getMatrixPointer());
- xor_array(output, output, roundKey, 0, stateSizeWords);
+ xor_array(output, output, roundKey, 0);
for (int r = 1; r <= numRounds; r++)
{
@@ -2384,8 +2408,8 @@ private void LowMCEnc(int[] plaintext, int[] output, int[] key)
matrix_mul(output, output, current.getData(), current.getMatrixPointer());
current = lowmcConstants.RConstant(this,r-1);
- xor_array(output, output, current.getData(), current.getMatrixPointer(), stateSizeWords);
- xor_array(output, output, roundKey, 0, stateSizeWords);
+ xor_array(output, output, current.getData(), current.getMatrixPointer());
+ xor_array(output, output, roundKey, 0);
}
}
@@ -2404,18 +2428,17 @@ private void substitution(int[] state)
}
}
- private void xor_three(int[] output, int[] in1, int[] in2, int[] in3, int lenBytes)
+ private void xor_three(int[] output, int[] in1, int[] in2, int[] in3)
{
- int wholeWords = stateSizeWords;
- for(int i = 0; i < wholeWords; i++)
+ for(int i = 0; i < stateSizeWords; i++)
{
output[i] = in1[i] ^ in2[i] ^ in3[i];
}
}
- protected void xor_array(int[] out, int[] in1, int[] in2, int in2_offset, int length)
+ protected void xor_array(int[] out, int[] in1, int[] in2, int in2_offset)
{
- for (int i = 0; i < length; i++)
+ for (int i = 0; i < stateSizeWords; i++)
{
out[i] = in1[i] ^ in2[i + in2_offset];
}
@@ -2464,13 +2487,4 @@ protected void matrix_mul_offset(int[] output, int outputOffset,
System.arraycopy(temp, 0, output, outputOffset, stateSizeWords);
}
-
- private void zeroTrailingBits(byte[] data, int bitLength)
- {
- int byteLength = Utils.numBytes(bitLength);
- for (int i = bitLength; i < byteLength * 8; i++)
- {
- Utils.setBit(data, i, (byte) 0);
- }
- }
}
diff --git a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Signature.java b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Signature.java
index d04d4e8f75..b99f1b55f5 100644
--- a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Signature.java
+++ b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Signature.java
@@ -2,9 +2,9 @@
class Signature
{
- byte[] challengeBits;
- byte[] salt;
- Proof[] proofs;
+ final byte[] challengeBits;
+ final byte[] salt;
+ final Proof[] proofs;
Signature(PicnicEngine engine)
{
salt = new byte[PicnicEngine.saltSizeBytes];
@@ -18,18 +18,18 @@ class Signature
public static class Proof
{
- byte[] seed1;
- byte[] seed2;
- int[] inputShare; // Input share of the party which does not derive it from the seed (not included if challenge is 0)
- byte[] communicatedBits;
- byte[] view3Commitment;
- byte[] view3UnruhG; // we include the max length, but we will only serialize the bytes we use
+ final byte[] seed1;
+ final byte[] seed2;
+ final int[] inputShare; // Input share of the party which does not derive it from the seed (not included if challenge is 0)
+ final byte[] communicatedBits;
+ final byte[] view3Commitment;
+ final byte[] view3UnruhG; // we include the max length, but we will only serialize the bytes we use
Proof(PicnicEngine engine)
{
seed1 = new byte[engine.seedSizeBytes];
seed2 = new byte[engine.seedSizeBytes];
- inputShare = new int[engine.stateSizeBytes];
+ inputShare = new int[engine.stateSizeWords];
communicatedBits = new byte[engine.andSizeBytes];
view3Commitment = new byte[engine.digestSizeBytes];
if (engine.UnruhGWithInputBytes > 0)
diff --git a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Tape.java b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Tape.java
index 1b67dae274..3badebdd25 100644
--- a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Tape.java
+++ b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Tape.java
@@ -1,6 +1,5 @@
package org.bouncycastle.pqc.crypto.picnic;
-import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;
class Tape
@@ -61,19 +60,17 @@ protected void computeAuxTape(byte[] inputs)
// for (int i = 0; i < key0.length; i++)
// {System.out.printf("%08x ", key[i]);}System.out.println();
-
- if(inputs != null)
+ if (inputs != null)
{
- Pack.intToLittleEndian(Arrays.copyOf(key, engine.stateSizeWords), inputs, 0);
+ Pack.intToLittleEndian(key, 0, engine.stateSizeWords, inputs, 0);
}
-
for (int r = engine.numRounds; r > 0; r--)
{
current = engine.lowmcConstants.KMatrix(engine, r);
engine.matrix_mul(roundKey, key, current.getData(), current.getMatrixPointer()); // roundKey = key * KMatrix(r)
- engine.xor_array(x, x, roundKey, 0, engine.stateSizeWords);
+ engine.xor_array(x, x, roundKey, 0);
current = engine.lowmcConstants.LMatrixInv(engine, r-1);
engine.matrix_mul(y, x, current.getData(), current.getMatrixPointer());
diff --git a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Utils.java b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Utils.java
index 3a7b0541e3..cb0014ec18 100644
--- a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Utils.java
+++ b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/Utils.java
@@ -93,6 +93,14 @@ protected static byte getBit(byte[] array, int bitNumber)
return (byte)((array[arrayPos] >>> bitPos) & 1);
}
+ /* Get a crumb (i.e. two bits) from a byte array. */
+ protected static byte getCrumbAligned(byte[] array, int crumbNumber)
+ {
+ int arrayPos = crumbNumber >>> 2, bitPos = ((crumbNumber << 1) & 6) ^ 6;
+ int b = (int)array[arrayPos] >>> bitPos;
+ return (byte)((b & 1) << 1 | (b & 2) >> 1);
+ }
+
protected static int getBit(int word, int bitNumber)
{
int bitPos = bitNumber ^ 7;
@@ -132,4 +140,27 @@ protected static void setBit(int[] array, int bitNumber, int val)
t |= val << bitPos;
array[arrayPos] = t;
}
+
+ protected static void zeroTrailingBits(int[] data, int bitLength)
+ {
+ int partialWord = bitLength & 31;
+ if (partialWord != 0)
+ {
+ data[bitLength >>> 5] &= getTrailingBitsMask(bitLength);
+ }
+ }
+
+ protected static int getTrailingBitsMask(int bitLength)
+ {
+ int partialShift = bitLength & ~7;
+ int mask = ~(0xFFFFFFFF << partialShift);
+
+ int partialByte = bitLength & 7;
+ if (partialByte != 0)
+ {
+ mask ^= ((0xFF00 >>> partialByte) & 0xFF) << partialShift;
+ }
+
+ return mask;
+ }
}
diff --git a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/View.java b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/View.java
index d0537ade32..538bfa6b13 100644
--- a/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/View.java
+++ b/core/src/main/java/org/bouncycastle/pqc/crypto/picnic/View.java
@@ -2,13 +2,13 @@
class View
{
- int[] inputShare;
- byte[] communicatedBits;
- int[] outputShare;
+ final int[] inputShare;
+ final byte[] communicatedBits;
+ final int[] outputShare;
public View(PicnicEngine engine)
{
- inputShare = new int[engine.stateSizeBytes] ;
+ inputShare = new int[engine.stateSizeWords] ;
communicatedBits = new byte[engine.andSizeBytes];
- outputShare = new int[engine.stateSizeBytes] ;
+ outputShare = new int[engine.stateSizeWords] ;
}
}
diff --git a/core/src/main/java/org/bouncycastle/util/Pack.java b/core/src/main/java/org/bouncycastle/util/Pack.java
index 54237f143d..a2ce48f90e 100644
--- a/core/src/main/java/org/bouncycastle/util/Pack.java
+++ b/core/src/main/java/org/bouncycastle/util/Pack.java
@@ -174,6 +174,25 @@ public static int littleEndianToInt(byte[] bs, int off)
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)
From ee3cf8ee72305cd8c47bd16f891e08143f4019f9 Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Thu, 11 May 2023 19:34:41 +0700
Subject: [PATCH 037/290] Refactoring in math.ec.rfc8032
---
.../org/bouncycastle/math/ec/rfc8032/Ed25519.java | 12 +-----------
.../java/org/bouncycastle/math/ec/rfc8032/Ed448.java | 12 +-----------
2 files changed, 2 insertions(+), 22 deletions(-)
diff --git a/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java b/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java
index b12899241d..673aa49e25 100644
--- a/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java
+++ b/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed25519.java
@@ -667,16 +667,6 @@ private static void invertDoubleZs(PointExtended[] points)
F.copy(u, 0, points[0].z, 0);
}
-// private static boolean isNeutralElementVar(int[] x, int[] y)
-// {
-// return F.isZeroVar(x) && F.isOneVar(y);
-// }
-
- private static boolean isNeutralElementVar(int[] x, int[] y, int[] z)
- {
- return F.isZeroVar(x) && F.areEqualVar(y, z);
- }
-
private static void normalizeToAffine(PointAccum p, PointAffine r)
{
F.inv(p.z, r.y);
@@ -692,7 +682,7 @@ private static boolean normalizeToNeutralElementVar(PointAccum p)
F.normalize(p.y);
F.normalize(p.z);
- return isNeutralElementVar(p.x, p.y, p.z);
+ return F.isZeroVar(p.x) && F.areEqualVar(p.y, p.z);
}
private static void pointAdd(PointExtended p, PointExtended q, PointExtended r, PointTemp t)
diff --git a/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java b/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java
index 7d1113105b..37153e2462 100644
--- a/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java
+++ b/core/src/main/java/org/bouncycastle/math/ec/rfc8032/Ed448.java
@@ -607,16 +607,6 @@ private static void invertZs(PointProjective[] points)
F.copy(u, 0, points[0].z, 0);
}
-// private static boolean isNeutralElementVar(int[] x, int[] y)
-// {
-// return F.isZeroVar(x) && F.isOneVar(y);
-// }
-
- private static boolean isNeutralElementVar(int[] x, int[] y, int[] z)
- {
- return F.isZeroVar(x) && F.areEqualVar(y, z);
- }
-
private static void normalizeToAffine(PointProjective p, PointAffine r)
{
F.inv(p.z, r.y);
@@ -632,7 +622,7 @@ private static boolean normalizeToNeutralElementVar(PointProjective p)
F.normalize(p.y);
F.normalize(p.z);
- return isNeutralElementVar(p.x, p.y, p.z);
+ return F.isZeroVar(p.x) && F.areEqualVar(p.y, p.z);
}
private static void pointAdd(PointAffine p, PointProjective r, PointTemp t)
From 9a1eaf30d77d041b9aa15c4661e20819e3f37718 Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Thu, 11 May 2023 20:36:52 +0700
Subject: [PATCH 038/290] BCJSSE: Simplify logging calls
---
.../jsse/provider/PropertyUtils.java | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/PropertyUtils.java b/tls/src/main/java/org/bouncycastle/jsse/provider/PropertyUtils.java
index fce1f4e7da..bc258f9eba 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/PropertyUtils.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/PropertyUtils.java
@@ -47,17 +47,17 @@ static boolean getBooleanSecurityProperty(String propertyName, boolean defaultVa
{
if ("true".equalsIgnoreCase(propertyValue))
{
- LOG.log(Level.INFO, "Found boolean security property [" + propertyName + "]: " + true);
+ LOG.info("Found boolean security property [" + propertyName + "]: " + true);
return true;
}
if ("false".equalsIgnoreCase(propertyValue))
{
- LOG.log(Level.INFO, "Found boolean security property [" + propertyName + "]: " + false);
+ LOG.info("Found boolean security property [" + propertyName + "]: " + false);
return false;
}
- LOG.log(Level.WARNING, "Unrecognized value for boolean security property [" + propertyName + "]: " + propertyValue);
+ LOG.warning("Unrecognized value for boolean security property [" + propertyName + "]: " + propertyValue);
}
- LOG.log(Level.FINE, "Boolean security property [" + propertyName + "] defaulted to: " + defaultValue);
+ LOG.fine("Boolean security property [" + propertyName + "] defaulted to: " + defaultValue);
return defaultValue;
}
@@ -68,17 +68,17 @@ static boolean getBooleanSystemProperty(String propertyName, boolean defaultValu
{
if ("true".equalsIgnoreCase(propertyValue))
{
- LOG.log(Level.INFO, "Found boolean system property [" + propertyName + "]: " + true);
+ LOG.info("Found boolean system property [" + propertyName + "]: " + true);
return true;
}
if ("false".equalsIgnoreCase(propertyValue))
{
- LOG.log(Level.INFO, "Found boolean system property [" + propertyName + "]: " + false);
+ LOG.info("Found boolean system property [" + propertyName + "]: " + false);
return false;
}
- LOG.log(Level.WARNING, "Unrecognized value for boolean system property [" + propertyName + "]: " + propertyValue);
+ LOG.warning("Unrecognized value for boolean system property [" + propertyName + "]: " + propertyValue);
}
- LOG.log(Level.FINE, "Boolean system property [" + propertyName + "] defaulted to: " + defaultValue);
+ LOG.fine("Boolean system property [" + propertyName + "] defaulted to: " + defaultValue);
return defaultValue;
}
@@ -92,21 +92,21 @@ static int getIntegerSystemProperty(String propertyName, int defaultValue, int m
int parsedValue = Integer.parseInt(propertyValue);
if (parsedValue >= minimumValue && parsedValue <= maximumValue)
{
- LOG.log(Level. INFO, "Found integer system property [" + propertyName + "]: " + parsedValue);
+ LOG.info("Found integer system property [" + propertyName + "]: " + parsedValue);
return parsedValue;
}
if (LOG.isLoggable(Level.WARNING))
{
String range = getRangeString(minimumValue, maximumValue);
- LOG.log(Level.WARNING, "Out-of-range (" + range + ") integer system property [" + propertyName + "]: " + propertyValue);
+ LOG.warning("Out-of-range (" + range + ") integer system property [" + propertyName + "]: " + propertyValue);
}
}
catch (Exception e)
{
- LOG.log(Level.WARNING, "Unrecognized value for integer system property [" + propertyName + "]: " + propertyValue);
+ LOG.warning("Unrecognized value for integer system property [" + propertyName + "]: " + propertyValue);
}
}
- LOG.log(Level.FINE, "Integer system property [" + propertyName + "] defaulted to: " + defaultValue);
+ LOG.fine("Integer system property [" + propertyName + "] defaulted to: " + defaultValue);
return defaultValue;
}
@@ -126,7 +126,7 @@ static String getStringSecurityProperty(String propertyName)
String propertyValue = getSecurityProperty(propertyName);
if (null != propertyValue)
{
- LOG.log(Level.INFO, "Found string security property [" + propertyName + "]: " + propertyValue);
+ LOG.info("Found string security property [" + propertyName + "]: " + propertyValue);
return propertyValue;
}
return null;
@@ -137,10 +137,10 @@ static String getStringSecurityProperty(String propertyName, String defaultValue
String propertyValue = getSecurityProperty(propertyName);
if (null != propertyValue)
{
- LOG.log(Level.INFO, "Found string security property [" + propertyName + "]: " + propertyValue);
+ LOG.info("Found string security property [" + propertyName + "]: " + propertyValue);
return propertyValue;
}
- LOG.log(Level.WARNING, "String security property [" + propertyName + "] defaulted to: " + defaultValue);
+ LOG.warning("String security property [" + propertyName + "] defaulted to: " + defaultValue);
return defaultValue;
}
@@ -149,7 +149,7 @@ static String getStringSystemProperty(String propertyName)
String propertyValue = getSystemProperty(propertyName);
if (null != propertyValue)
{
- LOG.log(Level.INFO, "Found string system property [" + propertyName + "]: " + propertyValue);
+ LOG.info("Found string system property [" + propertyName + "]: " + propertyValue);
return propertyValue;
}
return null;
From 28ae595643a620a6b964aec31af40e9aa474dd1d Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Thu, 11 May 2023 20:39:26 +0700
Subject: [PATCH 039/290] TLS: Add TlsPeer.notifyConnectionClosed callback
---
tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java | 4 ++++
tls/src/main/java/org/bouncycastle/tls/TlsPeer.java | 5 +++++
tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java | 2 ++
3 files changed, 11 insertions(+)
diff --git a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java
index 99dd0e0bd4..7ff63d33bb 100644
--- a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java
+++ b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java
@@ -139,6 +139,10 @@ public void notifyAlertReceived(short alertLevel, short alertDescription)
{
}
+ public void notifyConnectionClosed()
+ {
+ }
+
public void notifyHandshakeComplete() throws IOException
{
}
diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java b/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java
index 3f68bf9f7c..ff5f0a757d 100644
--- a/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java
+++ b/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java
@@ -139,6 +139,11 @@ public interface TlsPeer
*/
void notifyAlertReceived(short alertLevel, short alertDescription);
+ /**
+ * Notifies the peer that the connection has been closed.
+ */
+ void notifyConnectionClosed();
+
/**
* Notifies the peer that the handshake has been successfully completed.
*/
diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java b/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java
index bf005097db..bcfe342b58 100644
--- a/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java
+++ b/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java
@@ -299,6 +299,8 @@ protected void handleClose(boolean user_canceled)
raiseAlertWarning(AlertDescription.close_notify, "Connection closed");
closeConnection();
+
+ getPeer().notifyConnectionClosed();
}
}
From a7f5ffa17d7b498e4424c77634246694d2c4f50f Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Thu, 11 May 2023 20:43:34 +0700
Subject: [PATCH 040/290] BCJSSE: Improved logging of connection events
---
.../bouncycastle/jsse/BCSSLConnection.java | 7 +
.../bouncycastle/jsse/provider/JsseUtils.java | 31 +++-
.../jsse/provider/ProvSSLConnection.java | 39 +++--
.../jsse/provider/ProvSSLEngine.java | 9 +-
.../jsse/provider/ProvSSLEngine_8.java | 4 +-
.../jsse/provider/ProvSSLSocketDirect.java | 5 +
.../jsse/provider/ProvSSLSocketWrap.java | 5 +
.../jsse/provider/ProvTlsClient.java | 123 ++++++++++++----
.../jsse/provider/ProvTlsManager.java | 2 +
.../jsse/provider/ProvTlsPeer.java | 8 +
.../jsse/provider/ProvTlsServer.java | 137 ++++++++++++++----
.../jsse/provider/ProvSSLEngine_9.java | 4 +-
12 files changed, 300 insertions(+), 74 deletions(-)
diff --git a/tls/src/main/java/org/bouncycastle/jsse/BCSSLConnection.java b/tls/src/main/java/org/bouncycastle/jsse/BCSSLConnection.java
index e8e2421922..430b7ec5e3 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/BCSSLConnection.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/BCSSLConnection.java
@@ -26,6 +26,13 @@ public interface BCSSLConnection
*/
byte[] getChannelBinding(String channelBinding);
+ /**
+ * Return a String that uniquely identifies this connection during its lifetime within the current process. It
+ * can be used e.g. for logging but its contents and format are subject to change and should not be relied upon.
+ * @return A String identifying this connection.
+ */
+ String getID();
+
/**
* Returns the SSL session in use by this connection
* @return The {@link BCExtendedSSLSession}.
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/JsseUtils.java b/tls/src/main/java/org/bouncycastle/jsse/provider/JsseUtils.java
index 1b3b3523fd..5acc37fbdf 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/JsseUtils.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/JsseUtils.java
@@ -107,6 +107,28 @@ static boolean allowLegacyResumption()
return provTlsAllowLegacyResumption;
}
+ static String getPeerID(String root, ProvTlsManager manager)
+ {
+ long connectionID = ProvSSLConnection.allocateConnectionID();
+ int transportID = manager.getTransportID();
+
+ return "[" + root + " #" + connectionID + " @" + Integer.toHexString(transportID) + "]";
+ }
+
+ static String getPeerReport(ProvTlsManager manager)
+ {
+ String peerHost = manager.getPeerHost();
+ if (peerHost == null)
+ {
+ peerHost = "(unknown)";
+ }
+
+ int peerPort = manager.getPeerPort();
+ String peerPortStr = peerPort < 0 ? "(unknown)" : Integer.toString(peerPort);
+
+ return peerHost + ":" + peerPortStr;
+ }
+
static String getSignatureAlgorithmsReport(String title, List signatureSchemes)
{
String[] names = SignatureSchemeInfo.getJcaSignatureAlgorithmsBC(signatureSchemes);
@@ -609,9 +631,14 @@ static X500Principal getSubject(JcaTlsCrypto crypto, Certificate certificateMess
}
}
- static String getAlertLogMessage(String root, short alertLevel, short alertDescription)
+ static String getAlertRaisedLogMessage(String id, short alertLevel, short alertDescription)
+ {
+ return id + " raised " + AlertLevel.getText(alertLevel) + " " + AlertDescription.getText(alertDescription) + " alert";
+ }
+
+ static String getAlertReceivedLogMessage(String id, short alertLevel, short alertDescription)
{
- return root + " " + AlertLevel.getText(alertLevel) + " " + AlertDescription.getText(alertDescription) + " alert";
+ return id + " received " + AlertLevel.getText(alertLevel) + " " + AlertDescription.getText(alertDescription) + " alert";
}
static String getKeyAlgorithm(Key key)
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLConnection.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLConnection.java
index 65f73175ae..9ff9d52fb8 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLConnection.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLConnection.java
@@ -1,5 +1,7 @@
package org.bouncycastle.jsse.provider;
+import java.util.concurrent.atomic.AtomicLong;
+
import org.bouncycastle.jsse.BCSSLConnection;
import org.bouncycastle.tls.ChannelBinding;
import org.bouncycastle.tls.TlsContext;
@@ -7,47 +9,62 @@
class ProvSSLConnection
implements BCSSLConnection
{
- protected final TlsContext tlsContext;
- protected final ProvSSLSession session;
+ private static final AtomicLong CONNECTION_IDS = new AtomicLong(0L);
+
+ static long allocateConnectionID()
+ {
+ return CONNECTION_IDS.incrementAndGet();
+ }
+
+ protected final ProvTlsPeer tlsPeer;
- ProvSSLConnection(TlsContext tlsContext, ProvSSLSession session)
+ ProvSSLConnection(ProvTlsPeer tlsPeer)
{
- this.tlsContext = tlsContext;
- this.session = session;
+ this.tlsPeer = tlsPeer;
}
public byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length)
{
- return tlsContext.exportKeyingMaterial(asciiLabel, context_value, length);
+ return getTlsContext().exportKeyingMaterial(asciiLabel, context_value, length);
}
public String getApplicationProtocol()
{
- return JsseUtils.getApplicationProtocol(tlsContext.getSecurityParametersConnection());
+ return JsseUtils.getApplicationProtocol(getTlsContext().getSecurityParametersConnection());
}
public byte[] getChannelBinding(String channelBinding)
{
if (channelBinding.equals("tls-exporter"))
{
- return tlsContext.exportChannelBinding(ChannelBinding.tls_exporter);
+ return getTlsContext().exportChannelBinding(ChannelBinding.tls_exporter);
}
if (channelBinding.equals("tls-server-end-point"))
{
- return tlsContext.exportChannelBinding(ChannelBinding.tls_server_end_point);
+ return getTlsContext().exportChannelBinding(ChannelBinding.tls_server_end_point);
}
if (channelBinding.equals("tls-unique"))
{
- return tlsContext.exportChannelBinding(ChannelBinding.tls_unique);
+ return getTlsContext().exportChannelBinding(ChannelBinding.tls_unique);
}
throw new UnsupportedOperationException();
}
+ public String getID()
+ {
+ return tlsPeer.getID();
+ }
+
public ProvSSLSession getSession()
{
- return session;
+ return tlsPeer.getSession();
+ }
+
+ protected TlsContext getTlsContext()
+ {
+ return tlsPeer.getTlsContext();
}
}
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine.java
index 68067c7280..b91505afcc 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine.java
@@ -66,9 +66,9 @@ protected ProvSSLEngine(ContextData contextData)
this(contextData, null, -1);
}
- protected ProvSSLEngine(ContextData contextData, String host, int port)
+ protected ProvSSLEngine(ContextData contextData, String peerHost, int peerPort)
{
- super(host, port);
+ super(peerHost, peerPort);
this.contextData = contextData;
this.sslParameters = contextData.getContext().getDefaultSSLParameters(useClientMode);
@@ -328,6 +328,11 @@ public synchronized String[] getSupportedProtocols()
return contextData.getContext().getSupportedProtocols();
}
+ public int getTransportID()
+ {
+ return System.identityHashCode(this);
+ }
+
@Override
public synchronized boolean getUseClientMode()
{
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine_8.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine_8.java
index ef842624aa..b3cfd477a1 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine_8.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLEngine_8.java
@@ -13,9 +13,9 @@ protected ProvSSLEngine_8(ContextData contextData)
super(contextData);
}
- protected ProvSSLEngine_8(ContextData contextData, String host, int port)
+ protected ProvSSLEngine_8(ContextData contextData, String peerHost, int peerPort)
{
- super(contextData, host, port);
+ super(contextData, peerHost, peerPort);
}
// An SSLEngine method from JDK 9 (and then 8u251)
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketDirect.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketDirect.java
index 3d93079b1e..7ad23ed082 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketDirect.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketDirect.java
@@ -308,6 +308,11 @@ public synchronized String[] getSupportedProtocols()
return contextData.getContext().getSupportedProtocols();
}
+ public int getTransportID()
+ {
+ return System.identityHashCode(this);
+ }
+
@Override
public synchronized boolean getUseClientMode()
{
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketWrap.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketWrap.java
index 2170a0b940..a729b36ccf 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketWrap.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvSSLSocketWrap.java
@@ -396,6 +396,11 @@ public int getTrafficClass() throws SocketException
return wrapSocket.getTrafficClass();
}
+ public int getTransportID()
+ {
+ return System.identityHashCode(this);
+ }
+
@Override
public synchronized boolean getUseClientMode()
{
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsClient.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsClient.java
index 92d34a7ef8..364973d5da 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsClient.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsClient.java
@@ -33,6 +33,7 @@
import org.bouncycastle.tls.SignatureAlgorithm;
import org.bouncycastle.tls.SignatureAndHashAlgorithm;
import org.bouncycastle.tls.TlsAuthentication;
+import org.bouncycastle.tls.TlsContext;
import org.bouncycastle.tls.TlsCredentials;
import org.bouncycastle.tls.TlsDHGroupVerifier;
import org.bouncycastle.tls.TlsExtensionsUtils;
@@ -67,6 +68,7 @@ class ProvTlsClient
private static final boolean provEnableSNIExtension = PropertyUtils
.getBooleanSystemProperty("jsse.enableSNIExtension", true);
+ protected final String clientID;
protected final ProvTlsManager manager;
protected final ProvSSLParameters sslParameters;
protected final JsseSecurityParameters jsseSecurityParameters = new JsseSecurityParameters();
@@ -78,10 +80,26 @@ class ProvTlsClient
{
super(manager.getContextData().getCrypto());
+ this.clientID = JsseUtils.getPeerID("client", manager);
this.manager = manager;
this.sslParameters = sslParameters.copyForConnection();
}
+ public String getID()
+ {
+ return clientID;
+ }
+
+ public ProvSSLSession getSession()
+ {
+ return sslSession;
+ }
+
+ public TlsContext getTlsContext()
+ {
+ return context;
+ }
+
@Override
protected Vector getCertificateAuthorities()
{
@@ -166,7 +184,7 @@ protected Vector getSNIServerNames()
}
catch (RuntimeException e)
{
- LOG.fine("Failed to add peer host as default SNI host_name: " + peerHostSNI);
+ LOG.fine(clientID + ": Failed to add peer host as default SNI host_name: " + peerHostSNI);
}
}
}
@@ -295,12 +313,12 @@ public TlsCredentials getClientCredentials(CertificateRequest certificateRequest
if (LOG.isLoggable(Level.FINEST))
{
- LOG.finest(JsseUtils.getSignatureAlgorithmsReport("Peer signature_algorithms",
+ LOG.finest(JsseUtils.getSignatureAlgorithmsReport(clientID + " peer signature_algorithms",
jsseSecurityParameters.peerSigSchemes));
if (jsseSecurityParameters.peerSigSchemesCert != jsseSecurityParameters.peerSigSchemes)
{
- LOG.finest(JsseUtils.getSignatureAlgorithmsReport("Peer signature_algorithms_cert",
+ LOG.finest(JsseUtils.getSignatureAlgorithmsReport(clientID + " peer signature_algorithms_cert",
jsseSecurityParameters.peerSigSchemesCert));
}
}
@@ -425,7 +443,7 @@ public void notifyAlertRaised(short alertLevel, short alertDescription, String m
if (LOG.isLoggable(level))
{
- String msg = JsseUtils.getAlertLogMessage("Client raised", alertLevel, alertDescription);
+ String msg = JsseUtils.getAlertRaisedLogMessage(clientID, alertLevel, alertDescription);
if (message != null)
{
msg = msg + ": " + message;
@@ -445,17 +463,33 @@ public void notifyAlertReceived(short alertLevel, short alertDescription)
if (LOG.isLoggable(level))
{
- String msg = JsseUtils.getAlertLogMessage("Client received", alertLevel, alertDescription);
+ String msg = JsseUtils.getAlertReceivedLogMessage(clientID, alertLevel, alertDescription);
LOG.log(level, msg);
}
}
+ @Override
+ public void notifyConnectionClosed()
+ {
+ super.notifyConnectionClosed();
+
+ if (LOG.isLoggable(Level.INFO))
+ {
+ LOG.info(clientID + " disconnected from " + JsseUtils.getPeerReport(manager));
+ }
+ }
+
@Override
public void notifyHandshakeBeginning() throws IOException
{
super.notifyHandshakeBeginning();
+ if (LOG.isLoggable(Level.INFO))
+ {
+ LOG.info(clientID + " opening connection to " + JsseUtils.getPeerReport(manager));
+ }
+
ContextData contextData = manager.getContextData();
ProtocolVersion[] activeProtocolVersions = getProtocolVersions();
@@ -469,6 +503,11 @@ public synchronized void notifyHandshakeComplete() throws IOException
this.handshakeComplete = true;
+ if (LOG.isLoggable(Level.INFO))
+ {
+ LOG.info(clientID + " established connection with " + JsseUtils.getPeerReport(manager));
+ }
+
TlsSession connectionTlsSession = context.getSession();
if (null == sslSession || sslSession.getTlsSession() != connectionTlsSession)
@@ -485,7 +524,7 @@ public synchronized void notifyHandshakeComplete() throws IOException
jsseSessionParameters, addToCache);
}
- manager.notifyHandshakeComplete(new ProvSSLConnection(context, sslSession));
+ manager.notifyHandshakeComplete(new ProvSSLConnection(this));
}
@Override
@@ -509,7 +548,10 @@ public void notifySelectedCipherSuite(int selectedCipherSuite)
String selectedCipherSuiteName = manager.getContextData().getContext()
.validateNegotiatedCipherSuite(sslParameters, selectedCipherSuite);
- LOG.fine("Client notified of selected cipher suite: " + selectedCipherSuiteName);
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(clientID + " notified of selected cipher suite: " + selectedCipherSuiteName);
+ }
super.notifySelectedCipherSuite(selectedCipherSuite);
}
@@ -520,7 +562,10 @@ public void notifyServerVersion(ProtocolVersion serverVersion) throws IOExceptio
String serverVersionName = manager.getContextData().getContext().validateNegotiatedProtocol(sslParameters,
serverVersion);
- LOG.fine("Client notified of selected protocol version: " + serverVersionName);
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(clientID + " notified of selected protocol version: " + serverVersionName);
+ }
super.notifyServerVersion(serverVersion);
}
@@ -544,21 +589,27 @@ public void notifySessionID(byte[] sessionID)
if (isResumed)
{
- // -DM Hex.toHexString
- LOG.fine("Server resumed session: " + Hex.toHexString(sessionID));
+ if (LOG.isLoggable(Level.FINE))
+ {
+ // -DM Hex.toHexString
+ LOG.fine(clientID + ": Server resumed session: " + Hex.toHexString(sessionID));
+ }
}
else
{
this.sslSession = null;
- if (TlsUtils.isNullOrEmpty(sessionID))
- {
- LOG.fine("Server did not specify a session ID");
- }
- else
+ if (LOG.isLoggable(Level.FINE))
{
- // -DM Hex.toHexString
- LOG.fine("Server specified new session: " + Hex.toHexString(sessionID));
+ if (TlsUtils.isNullOrEmpty(sessionID))
+ {
+ LOG.fine(clientID + ": Server did not specify a session ID");
+ }
+ else
+ {
+ // -DM Hex.toHexString
+ LOG.fine(clientID + ": Server specified new session: " + Hex.toHexString(sessionID));
+ }
}
JsseUtils.checkSessionCreationEnabled(manager);
@@ -579,7 +630,10 @@ public void processServerExtensions(@SuppressWarnings("rawtypes") Hashtable serv
{
boolean sniAccepted = TlsExtensionsUtils.hasServerNameExtensionServer(serverExtensions);
- LOG.finer("Server accepted SNI?: " + sniAccepted);
+ if (LOG.isLoggable(Level.FINER))
+ {
+ LOG.finer(clientID + ": Server accepted SNI?: " + sniAccepted);
+ }
}
}
@@ -646,8 +700,11 @@ protected SessionParameters getResumableSessionParameters(ProvSSLSession provSSL
String sessionEndpointID = jsseSessionParameters.getEndpointIDAlgorithm();
if (!connectionEndpointID.equalsIgnoreCase(sessionEndpointID))
{
- LOG.finer("Session not resumable - endpoint ID algorithm mismatch; connection: "
- + connectionEndpointID + ", session: " + sessionEndpointID);
+ if (LOG.isLoggable(Level.FINER))
+ {
+ LOG.finer(clientID + ": Session not resumable - endpoint ID algorithm mismatch; connection: "
+ + connectionEndpointID + ", session: " + sessionEndpointID);
+ }
return null;
}
}
@@ -691,7 +748,10 @@ protected TlsCredentials selectClientCredentials12(Principal[] issuers, short[]
if (keyTypeMap.isEmpty())
{
- LOG.fine("Client (1.2) found no usable signature schemes");
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(clientID + " (1.2) found no usable signature schemes");
+ }
return null;
}
@@ -701,7 +761,10 @@ protected TlsCredentials selectClientCredentials12(Principal[] issuers, short[]
if (null == x509Key)
{
handleKeyManagerMisses(keyTypeMap, null);
- LOG.fine("Client (1.2) did not select any credentials");
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(clientID + " (1.2) did not select any credentials");
+ }
return null;
}
@@ -716,7 +779,7 @@ protected TlsCredentials selectClientCredentials12(Principal[] issuers, short[]
if (LOG.isLoggable(Level.FINE))
{
- LOG.fine("Client (1.2) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ LOG.fine(clientID + " (1.2) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ "' (keyType '" + selectedKeyType + "'), with private key algorithm '"
+ JsseUtils.getPrivateKeyAlgorithm(x509Key.getPrivateKey()) + "'");
}
@@ -748,7 +811,10 @@ protected TlsCredentials selectClientCredentials13(Principal[] issuers, byte[] c
if (keyTypeMap.isEmpty())
{
- LOG.fine("Client (1.3) found no usable signature schemes");
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(clientID + " (1.3) found no usable signature schemes");
+ }
return null;
}
@@ -758,7 +824,10 @@ protected TlsCredentials selectClientCredentials13(Principal[] issuers, byte[] c
if (null == x509Key)
{
handleKeyManagerMisses(keyTypeMap, null);
- LOG.fine("Client (1.3) did not select any credentials");
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(clientID + " (1.3) did not select any credentials");
+ }
return null;
}
@@ -773,7 +842,7 @@ protected TlsCredentials selectClientCredentials13(Principal[] issuers, byte[] c
if (LOG.isLoggable(Level.FINE))
{
- LOG.fine("Client (1.3) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ LOG.fine(clientID + " (1.3) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ "' (keyType '" + selectedKeyType + "'), with private key algorithm '"
+ JsseUtils.getPrivateKeyAlgorithm(x509Key.getPrivateKey()) + "'");
}
@@ -814,7 +883,7 @@ private void handleKeyManagerMisses(LinkedHashMap k
{
SignatureSchemeInfo signatureSchemeInfo = entry.getValue();
- LOG.finer("Client found no credentials for signature scheme '" + signatureSchemeInfo
+ LOG.finer(clientID + " found no credentials for signature scheme '" + signatureSchemeInfo
+ "' (keyType '" + keyType + "')");
}
}
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsManager.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsManager.java
index 83136a3347..bea7262ca9 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsManager.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsManager.java
@@ -28,6 +28,8 @@ interface ProvTlsManager
int getPeerPort();
+ int getTransportID();
+
void notifyHandshakeComplete(ProvSSLConnection connection);
void notifyHandshakeSession(ProvSSLSessionContext sslSessionContext, SecurityParameters securityParameters,
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsPeer.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsPeer.java
index ceabd8dba4..084ffb98df 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsPeer.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsPeer.java
@@ -1,6 +1,14 @@
package org.bouncycastle.jsse.provider;
+import org.bouncycastle.tls.TlsContext;
+
interface ProvTlsPeer
{
+ String getID();
+
+ ProvSSLSession getSession();
+
+ TlsContext getTlsContext();
+
boolean isHandshakeComplete();
}
diff --git a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsServer.java b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsServer.java
index 888bb4c99f..c0be6feeec 100644
--- a/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsServer.java
+++ b/tls/src/main/java/org/bouncycastle/jsse/provider/ProvTlsServer.java
@@ -36,6 +36,7 @@
import org.bouncycastle.tls.ServerName;
import org.bouncycastle.tls.SessionParameters;
import org.bouncycastle.tls.SignatureAndHashAlgorithm;
+import org.bouncycastle.tls.TlsContext;
import org.bouncycastle.tls.TlsCredentials;
import org.bouncycastle.tls.TlsDHUtils;
import org.bouncycastle.tls.TlsExtensionsUtils;
@@ -164,6 +165,7 @@ private static BigInteger parseDHParameter(String s, int beginIndex, int endInde
return new BigInteger(s.substring(beginIndex, endIndex), 16);
}
+ protected final String serverID;
protected final ProvTlsManager manager;
protected final ProvSSLParameters sslParameters;
protected final JsseSecurityParameters jsseSecurityParameters = new JsseSecurityParameters();
@@ -178,10 +180,26 @@ private static BigInteger parseDHParameter(String s, int beginIndex, int endInde
{
super(manager.getContextData().getCrypto());
+ this.serverID = JsseUtils.getPeerID("server", manager);
this.manager = manager;
this.sslParameters = sslParameters.copyForConnection();
}
+ public String getID()
+ {
+ return serverID;
+ }
+
+ public ProvSSLSession getSession()
+ {
+ return sslSession;
+ }
+
+ public TlsContext getTlsContext()
+ {
+ return context;
+ }
+
@Override
protected boolean allowCertificateStatus()
{
@@ -252,7 +270,10 @@ protected boolean selectCipherSuite(int cipherSuite) throws IOException
if (null == cipherSuiteCredentials)
{
String cipherSuiteName = ProvSSLContextSpi.getCipherSuiteName(cipherSuite);
- LOG.finer("Server found no credentials for cipher suite: " + cipherSuiteName);
+ if (LOG.isLoggable(Level.FINER))
+ {
+ LOG.finer(serverID + " found no credentials for cipher suite: " + cipherSuiteName);
+ }
return false;
}
}
@@ -528,12 +549,12 @@ public int getSelectedCipherSuite() throws IOException
if (LOG.isLoggable(Level.FINEST))
{
- LOG.finest(JsseUtils.getSignatureAlgorithmsReport("Peer signature_algorithms",
+ LOG.finest(JsseUtils.getSignatureAlgorithmsReport(serverID + " peer signature_algorithms",
jsseSecurityParameters.peerSigSchemes));
if (jsseSecurityParameters.peerSigSchemesCert != jsseSecurityParameters.peerSigSchemes)
{
- LOG.finest(JsseUtils.getSignatureAlgorithmsReport("Peer signature_algorithms_cert",
+ LOG.finest(JsseUtils.getSignatureAlgorithmsReport(serverID + " peer signature_algorithms_cert",
jsseSecurityParameters.peerSigSchemesCert));
}
}
@@ -554,7 +575,10 @@ public int getSelectedCipherSuite() throws IOException
String selectedCipherSuiteName = manager.getContextData().getContext()
.validateNegotiatedCipherSuite(sslParameters, selectedCipherSuite);
- LOG.fine("Server selected cipher suite: " + selectedCipherSuiteName);
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " selected cipher suite: " + selectedCipherSuiteName);
+ }
return selectedCipherSuite;
}
@@ -624,21 +648,27 @@ public void notifySession(TlsSession session)
boolean isResumed = (null != sslSession && sslSession.getTlsSession() == session);
if (isResumed)
{
- // -DM Hex.toHexString
- LOG.fine("Server resumed session: " + Hex.toHexString(sessionID));
+ if (LOG.isLoggable(Level.FINE))
+ {
+ // -DM Hex.toHexString
+ LOG.fine(serverID + " resumed session: " + Hex.toHexString(sessionID));
+ }
}
else
{
this.sslSession = null;
- if (TlsUtils.isNullOrEmpty(sessionID))
- {
- LOG.fine("Server did not specify a session ID");
- }
- else
+ if (LOG.isLoggable(Level.FINE))
{
- // -DM Hex.toHexString
- LOG.fine("Server specified new session: " + Hex.toHexString(sessionID));
+ if (TlsUtils.isNullOrEmpty(sessionID))
+ {
+ LOG.fine(serverID + " did not specify a session ID");
+ }
+ else
+ {
+ // -DM Hex.toHexString
+ LOG.fine(serverID + " specified new session: " + Hex.toHexString(sessionID));
+ }
}
JsseUtils.checkSessionCreationEnabled(manager);
@@ -657,7 +687,7 @@ public void notifyAlertRaised(short alertLevel, short alertDescription, String m
if (LOG.isLoggable(level))
{
- String msg = JsseUtils.getAlertLogMessage("Server raised", alertLevel, alertDescription);
+ String msg = JsseUtils.getAlertRaisedLogMessage(serverID, alertLevel, alertDescription);
if (message != null)
{
msg = msg + ": " + message;
@@ -677,7 +707,7 @@ public void notifyAlertReceived(short alertLevel, short alertDescription)
if (LOG.isLoggable(level))
{
- String msg = JsseUtils.getAlertLogMessage("Server received", alertLevel, alertDescription);
+ String msg = JsseUtils.getAlertReceivedLogMessage(serverID, alertLevel, alertDescription);
LOG.log(level, msg);
}
@@ -691,7 +721,10 @@ public ProtocolVersion getServerVersion() throws IOException
String serverVersionName = manager.getContextData().getContext().validateNegotiatedProtocol(sslParameters,
serverVersion);
- LOG.fine("Server selected protocol version: " + serverVersionName);
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " selected protocol version: " + serverVersionName);
+ }
return serverVersion;
}
@@ -730,6 +763,28 @@ public void notifyClientCertificate(Certificate clientCertificate) throws IOExce
}
}
+ @Override
+ public void notifyConnectionClosed()
+ {
+ super.notifyConnectionClosed();
+
+ if (LOG.isLoggable(Level.INFO))
+ {
+ LOG.info(serverID + " disconnected from " + JsseUtils.getPeerReport(manager));
+ }
+ }
+
+ @Override
+ public void notifyHandshakeBeginning() throws IOException
+ {
+ super.notifyHandshakeBeginning();
+
+ if (LOG.isLoggable(Level.INFO))
+ {
+ LOG.info(serverID + " accepting connection from " + JsseUtils.getPeerReport(manager));
+ }
+ }
+
@Override
public synchronized void notifyHandshakeComplete() throws IOException
{
@@ -737,6 +792,11 @@ public synchronized void notifyHandshakeComplete() throws IOException
this.handshakeComplete = true;
+ if (LOG.isLoggable(Level.INFO))
+ {
+ LOG.info(serverID + " established connection with " + JsseUtils.getPeerReport(manager));
+ }
+
TlsSession connectionTlsSession = context.getSession();
if (null == sslSession || sslSession.getTlsSession() != connectionTlsSession)
@@ -753,7 +813,7 @@ public synchronized void notifyHandshakeComplete() throws IOException
jsseSessionParameters, addToCache);
}
- manager.notifyHandshakeComplete(new ProvSSLConnection(context, sslSession));
+ manager.notifyHandshakeComplete(new ProvSSLConnection(this));
}
@Override
@@ -788,7 +848,10 @@ public void processClientExtensions(@SuppressWarnings("rawtypes") Hashtable clie
Collection sniMatchers = sslParameters.getSNIMatchers();
if (null == sniMatchers || sniMatchers.isEmpty())
{
- LOG.fine("Server ignored SNI (no matchers specified)");
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " ignored SNI (no matchers specified)");
+ }
}
else
{
@@ -798,7 +861,10 @@ public void processClientExtensions(@SuppressWarnings("rawtypes") Hashtable clie
throw new TlsFatalAlert(AlertDescription.unrecognized_name);
}
- LOG.fine("Server accepted SNI: " + matchedSNIServerName);
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " accepted SNI: " + matchedSNIServerName);
+ }
}
}
@@ -899,8 +965,11 @@ protected boolean isResumable(ProvSSLSession provSSLSession, TlsSession tlsSessi
if (!JsseUtils.equals(connectionSNI, sessionSNI))
{
- LOG.finest(
- "Session not resumable - SNI mismatch; connection: " + connectionSNI + ", session: " + sessionSNI);
+ if (LOG.isLoggable(Level.FINEST))
+ {
+ LOG.finest(serverID + ": Session not resumable - SNI mismatch; connection: " + connectionSNI
+ + ", session: " + sessionSNI);
+ }
return false;
}
}
@@ -980,7 +1049,10 @@ protected TlsCredentials selectServerCredentials12(Principal[] issuers, int keyE
if (keyTypeMap.isEmpty())
{
- LOG.fine("Server (1.2) has no key types to try for KeyExchangeAlgorithm " + keyExchangeAlgorithm);
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " (1.2) has no key types to try for KeyExchangeAlgorithm " + keyExchangeAlgorithm);
+ }
return null;
}
@@ -990,7 +1062,10 @@ protected TlsCredentials selectServerCredentials12(Principal[] issuers, int keyE
if (null == x509Key)
{
handleKeyManagerMisses(keyTypeMap, null);
- LOG.fine("Server (1.2) did not select any credentials for KeyExchangeAlgorithm " + keyExchangeAlgorithm);
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " (1.2) did not select any credentials for KeyExchangeAlgorithm " + keyExchangeAlgorithm);
+ }
return null;
}
@@ -1005,7 +1080,7 @@ protected TlsCredentials selectServerCredentials12(Principal[] issuers, int keyE
if (LOG.isLoggable(Level.FINE))
{
- LOG.fine("Server (1.2) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ LOG.fine(serverID + " (1.2) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ "' (keyType '" + selectedKeyType + "'), with private key algorithm '"
+ JsseUtils.getPrivateKeyAlgorithm(x509Key.getPrivateKey()) + "'");
}
@@ -1043,7 +1118,10 @@ protected TlsCredentials selectServerCredentials13(Principal[] issuers, byte[] c
if (keyTypeMap.isEmpty())
{
- LOG.fine("Server (1.3) found no usable signature schemes");
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " (1.3) found no usable signature schemes");
+ }
return null;
}
@@ -1053,7 +1131,10 @@ protected TlsCredentials selectServerCredentials13(Principal[] issuers, byte[] c
if (null == x509Key)
{
handleKeyManagerMisses(keyTypeMap, null);
- LOG.fine("Server (1.3) did not select any credentials");
+ if (LOG.isLoggable(Level.FINE))
+ {
+ LOG.fine(serverID + " (1.3) did not select any credentials");
+ }
return null;
}
@@ -1068,7 +1149,7 @@ protected TlsCredentials selectServerCredentials13(Principal[] issuers, byte[] c
if (LOG.isLoggable(Level.FINE))
{
- LOG.fine("Server (1.3) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ LOG.fine(serverID + " (1.3) selected credentials for signature scheme '" + selectedSignatureSchemeInfo
+ "' (keyType '" + selectedKeyType + "'), with private key algorithm '"
+ JsseUtils.getPrivateKeyAlgorithm(x509Key.getPrivateKey()) + "'");
}
@@ -1117,7 +1198,7 @@ private void handleKeyManagerMisses(LinkedHashMap k
{
SignatureSchemeInfo signatureSchemeInfo = entry.getValue();
- LOG.finer("Server found no credentials for signature scheme '" + signatureSchemeInfo
+ LOG.finer(serverID + " found no credentials for signature scheme '" + signatureSchemeInfo
+ "' (keyType '" + keyType + "')");
}
}
diff --git a/tls/src/main/jdk1.9/org/bouncycastle/jsse/provider/ProvSSLEngine_9.java b/tls/src/main/jdk1.9/org/bouncycastle/jsse/provider/ProvSSLEngine_9.java
index 44ecb0252a..5c22c6db34 100644
--- a/tls/src/main/jdk1.9/org/bouncycastle/jsse/provider/ProvSSLEngine_9.java
+++ b/tls/src/main/jdk1.9/org/bouncycastle/jsse/provider/ProvSSLEngine_9.java
@@ -8,8 +8,8 @@ protected ProvSSLEngine_9(ContextData contextData)
super(contextData);
}
- protected ProvSSLEngine_9(ContextData contextData, String host, int port)
+ protected ProvSSLEngine_9(ContextData contextData, String peerHost, int peerPort)
{
- super(contextData, host, port);
+ super(contextData, peerHost, peerPort);
}
}
From 46d3f724216526c46dcb5646badf70d5e3066895 Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Thu, 11 May 2023 21:23:06 +0700
Subject: [PATCH 041/290] Add casts to fix faulty comparisons
- see https://github.com/bcgit/bc-java/issues/1389
---
core/src/main/j2me/java/math/BigInteger.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/core/src/main/j2me/java/math/BigInteger.java b/core/src/main/j2me/java/math/BigInteger.java
index 66fd2a7202..7b33e0ce1c 100644
--- a/core/src/main/j2me/java/math/BigInteger.java
+++ b/core/src/main/j2me/java/math/BigInteger.java
@@ -426,13 +426,13 @@ private int[] makeMagnitude(byte[] bval, int sign)
// strip leading -1's
for (firstSignificant = 0; firstSignificant < bval.length - 1
- && bval[firstSignificant] == 0xff; firstSignificant++);
+ && bval[firstSignificant] == (byte)0xff; firstSignificant++);
int nBytes = bval.length;
boolean leadingByte = false;
// check for -2^(n-1)
- if (bval[firstSignificant] == 0x80) {
+ if (bval[firstSignificant] == (byte)0x80) {
for (i = firstSignificant + 1; i < bval.length; i++) {
if (bval[i] != 0) {
break;
From f7105f50fc204fcf58ac150793c611808d98e916 Mon Sep 17 00:00:00 2001
From: Peter Dettman
Date: Fri, 12 May 2023 15:14:51 +0700
Subject: [PATCH 042/290] DTLS: make initial handshake resend time configurable
---
.../java/org/bouncycastle/tls/AbstractTlsPeer.java | 5 +++++
.../org/bouncycastle/tls/DTLSClientProtocol.java | 2 +-
.../java/org/bouncycastle/tls/DTLSRecordLayer.java | 2 +-
.../org/bouncycastle/tls/DTLSReliableHandshake.java | 10 ++++++----
.../org/bouncycastle/tls/DTLSServerProtocol.java | 2 +-
tls/src/main/java/org/bouncycastle/tls/TlsPeer.java | 12 ++++++++++++
6 files changed, 26 insertions(+), 7 deletions(-)
diff --git a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java
index 7ff63d33bb..f3e92e2760 100644
--- a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java
+++ b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsPeer.java
@@ -62,6 +62,11 @@ public int getHandshakeTimeoutMillis()
return 0;
}
+ public int getHandshakeResendTimeMillis()
+ {
+ return 1000;
+ }
+
public boolean allowLegacyResumption()
{
return false;
diff --git a/tls/src/main/java/org/bouncycastle/tls/DTLSClientProtocol.java b/tls/src/main/java/org/bouncycastle/tls/DTLSClientProtocol.java
index eb58742486..559c0b9a34 100644
--- a/tls/src/main/java/org/bouncycastle/tls/DTLSClientProtocol.java
+++ b/tls/src/main/java/org/bouncycastle/tls/DTLSClientProtocol.java
@@ -106,7 +106,7 @@ protected DTLSTransport clientHandshake(ClientHandshakeState state, DTLSRecordLa
{
SecurityParameters securityParameters = state.clientContext.getSecurityParametersHandshake();
DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.clientContext, recordLayer,
- state.client.getHandshakeTimeoutMillis(), null);
+ state.client.getHandshakeTimeoutMillis(), state.client.getHandshakeResendTimeMillis(), null);
byte[] clientHelloBody = generateClientHello(state);
diff --git a/tls/src/main/java/org/bouncycastle/tls/DTLSRecordLayer.java b/tls/src/main/java/org/bouncycastle/tls/DTLSRecordLayer.java
index 0def458e5f..28a269b04d 100644
--- a/tls/src/main/java/org/bouncycastle/tls/DTLSRecordLayer.java
+++ b/tls/src/main/java/org/bouncycastle/tls/DTLSRecordLayer.java
@@ -283,7 +283,7 @@ public int receive(byte[] buf, int off, int len, int waitMillis)
heartbeat.generatePayload());
this.heartbeatTimeout = new Timeout(heartbeat.getTimeoutMillis(), currentTimeMillis);
- this.heartbeatResendMillis = DTLSReliableHandshake.INITIAL_RESEND_MILLIS;
+ this.heartbeatResendMillis = peer.getHandshakeResendTimeMillis();
this.heartbeatResendTimeout = new Timeout(heartbeatResendMillis, currentTimeMillis);
sendHeartbeatMessage(heartbeatInFlight);
diff --git a/tls/src/main/java/org/bouncycastle/tls/DTLSReliableHandshake.java b/tls/src/main/java/org/bouncycastle/tls/DTLSReliableHandshake.java
index d64fe510ae..2c2e73c056 100644
--- a/tls/src/main/java/org/bouncycastle/tls/DTLSReliableHandshake.java
+++ b/tls/src/main/java/org/bouncycastle/tls/DTLSReliableHandshake.java
@@ -15,7 +15,6 @@ class DTLSReliableHandshake
private static final int MAX_RECEIVE_AHEAD = 16;
private static final int MESSAGE_HEADER_LENGTH = 12;
- static final int INITIAL_RESEND_MILLIS = 1000;
private static final int MAX_RESEND_MILLIS = 60000;
static DTLSRequest readClientRequest(byte[] data, int dataOff, int dataLen, OutputStream dtlsOutput)
@@ -99,20 +98,23 @@ static void sendHelloVerifyRequest(DatagramSender sender, long recordSeq, byte[]
private Hashtable previousInboundFlight = null;
private Vector outboundFlight = new Vector();
+ private int initialResendMillis;
private int resendMillis = -1;
private Timeout resendTimeout = null;
private int next_send_seq = 0, next_receive_seq = 0;
- DTLSReliableHandshake(TlsContext context, DTLSRecordLayer transport, int timeoutMillis, DTLSRequest request)
+ DTLSReliableHandshake(TlsContext context, DTLSRecordLayer transport, int timeoutMillis, int initialResendMillis,
+ DTLSRequest request)
{
this.recordLayer = transport;
this.handshakeHash = new DeferredHash(context);
this.handshakeTimeout = Timeout.forWaitMillis(timeoutMillis);
+ this.initialResendMillis = initialResendMillis;
if (null != request)
{
- resendMillis = INITIAL_RESEND_MILLIS;
+ resendMillis = initialResendMillis;
resendTimeout = new Timeout(resendMillis);
long recordSeq = request.getRecordSeq();
@@ -322,7 +324,7 @@ private Message implReceiveMessage()
if (null == resendTimeout)
{
- resendMillis = INITIAL_RESEND_MILLIS;
+ resendMillis = initialResendMillis;
resendTimeout = new Timeout(resendMillis, currentTimeMillis);
prepareInboundFlight(new Hashtable());
diff --git a/tls/src/main/java/org/bouncycastle/tls/DTLSServerProtocol.java b/tls/src/main/java/org/bouncycastle/tls/DTLSServerProtocol.java
index ba7d437429..5bb87077c2 100644
--- a/tls/src/main/java/org/bouncycastle/tls/DTLSServerProtocol.java
+++ b/tls/src/main/java/org/bouncycastle/tls/DTLSServerProtocol.java
@@ -95,7 +95,7 @@ protected DTLSTransport serverHandshake(ServerHandshakeState state, DTLSRecordLa
{
SecurityParameters securityParameters = state.serverContext.getSecurityParametersHandshake();
DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.serverContext, recordLayer,
- state.server.getHandshakeTimeoutMillis(), request);
+ state.server.getHandshakeTimeoutMillis(), state.server.getHandshakeResendTimeMillis(), request);
DTLSReliableHandshake.Message clientMessage = null;
diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java b/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java
index ff5f0a757d..777d61857d 100644
--- a/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java
+++ b/tls/src/main/java/org/bouncycastle/tls/TlsPeer.java
@@ -39,6 +39,18 @@ public interface TlsPeer
*/
int getHandshakeTimeoutMillis();
+ /**
+ *
+ * NOTE: Currently only respected by DTLS protocols.
+ *
+ *
+ * Specify the time, in milliseconds, after which a handshake packet is resent.
+ *
+ *
+ * @return the handshake resend time, in milliseconds.
+ */
+ int getHandshakeResendTimeMillis();
+
boolean allowLegacyResumption();
int getMaxCertificateChainLength();
From 7d5598d6dfd002ad25be8d9b2a40075c83971cb5 Mon Sep 17 00:00:00 2001
From: Paul Schaub
Date: Sat, 13 May 2023 20:22:52 +0200
Subject: [PATCH 043/290] Modernize DSAElGamalKeyRingGenerator example
---
.../examples/DSAElGamalKeyRingGenerator.java | 124 +++++++++++++-----
1 file changed, 88 insertions(+), 36 deletions(-)
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
index 299666f13a..316bd140bf 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/DSAElGamalKeyRingGenerator.java
@@ -4,25 +4,31 @@
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
-import java.security.InvalidKeyException;
+import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
-import java.security.SignatureException;
import java.util.Date;
import javax.crypto.spec.DHParameterSpec;
import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.sig.Features;
+import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
@@ -37,13 +43,21 @@
*
* Where identity is the name to be associated with the public key. The keys are placed
* in the files pub.[asc|bpg] and secret.[asc|bpg].
- *
- * Note: this example encrypts the secret key using AES_256, many PGP products still
- * do not support this, if you are having problems importing keys try changing the algorithm
- * id to PGPEncryptedData.CAST5. CAST5 is more widely supported.
*/
public class DSAElGamalKeyRingGenerator
{
+
+ private static final int SIG_HASH = HashAlgorithmTags.SHA512;
+ private static final int[] HASH_PREFERENCES = new int[] {
+ HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA224
+ };
+ private static final int[] SYM_PREFERENCES = new int[] {
+ SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128
+ };
+ private static final int[] COMP = new int[] {
+ CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.UNCOMPRESSED
+ };
+
private static void exportKeyPair(
OutputStream secretOut,
OutputStream publicOut,
@@ -52,7 +66,7 @@ private static void exportKeyPair(
String identity,
char[] passPhrase,
boolean armor)
- throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException
+ throws IOException, PGPException
{
if (armor)
{
@@ -62,10 +76,21 @@ private static void exportKeyPair(
PGPKeyPair dsaKeyPair = new JcaPGPKeyPair(PGPPublicKey.DSA, dsaKp, new Date());
PGPKeyPair elgKeyPair = new JcaPGPKeyPair(PGPPublicKey.ELGAMAL_ENCRYPT, elgKp, new Date());
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
+
+ PGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(
+ dsaKeyPair.getPublicKey().getAlgorithm(), SIG_HASH);
+ PBESecretKeyEncryptor secretKeyEncryptor = new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256, sha1Calc)
+ .setProvider("BC")
+ .build(passPhrase);
+
+ PGPSignatureSubpacketGenerator primaryHashedSubpackets = getPrimaryKeyHashedSubpackets();
+ primaryHashedSubpackets.setIssuerFingerprint(false, dsaKeyPair.getPublicKey());
PGPKeyRingGenerator keyRingGen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, dsaKeyPair,
- identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(dsaKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, sha1Calc).setProvider("BC").build(passPhrase));
-
- keyRingGen.addSubKey(elgKeyPair);
+ identity, sha1Calc, primaryHashedSubpackets.generate(), null, contentSignerBuilder, secretKeyEncryptor);
+
+ PGPSignatureSubpacketGenerator subkeyHashedSubpackets = getEncryptionKeyHashedSubpackets();
+ subkeyHashedSubpackets.setIssuerFingerprint(false, dsaKeyPair.getPublicKey());
+ keyRingGen.addSubKey(elgKeyPair, subkeyHashedSubpackets.generate(), null);
keyRingGen.generateSecretKeyRing().encode(secretOut);
@@ -80,7 +105,53 @@ private static void exportKeyPair(
publicOut.close();
}
-
+
+ private static PGPSignatureSubpacketGenerator getEncryptionKeyHashedSubpackets() {
+ PGPSignatureSubpacketGenerator gen = new PGPSignatureSubpacketGenerator();
+ gen.setKeyFlags(true, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
+ return gen;
+ }
+
+ private static PGPSignatureSubpacketGenerator getPrimaryKeyHashedSubpackets() {
+ PGPSignatureSubpacketGenerator gen = new PGPSignatureSubpacketGenerator();
+ gen.setPreferredHashAlgorithms(false, HASH_PREFERENCES);
+ gen.setPreferredSymmetricAlgorithms(false, SYM_PREFERENCES);
+ gen.setPreferredCompressionAlgorithms(false, COMP);
+ gen.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
+ gen.setKeyFlags(true, KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA);
+ return gen;
+ }
+
+ private static KeyPair generateElGamalKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
+ KeyPairGenerator elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "BC");
+ // 3072 bit parameters from https://datatracker.ietf.org/doc/html/rfc3526#section-4
+ BigInteger g = new BigInteger("2", 16);
+ BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", 16);
+
+ DHParameterSpec elParams = new DHParameterSpec(p, g);
+
+ elgKpg.initialize(elParams);
+
+ //
+ // this is quicker because we are using pregenerated parameters.
+ //
+ KeyPair elgKp = elgKpg.generateKeyPair();
+ return elgKp;
+ }
+
+ private static KeyPair generateDSAKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException {
+ KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance("DSA", "BC");
+
+ dsaKpg.initialize(3072);
+
+ //
+ // this takes a while as the key generator has to generate some DSA params
+ // before it generates the key.
+ //
+ KeyPair dsaKp = dsaKpg.generateKeyPair();
+ return dsaKp;
+ }
+
public static void main(
String[] args)
throws Exception
@@ -92,30 +163,11 @@ public static void main(
System.out.println("DSAElGamalKeyRingGenerator [-a] identity passPhrase");
System.exit(0);
}
-
- KeyPairGenerator dsaKpg = KeyPairGenerator.getInstance("DSA", "BC");
-
- dsaKpg.initialize(1024);
-
- //
- // this takes a while as the key generator has to generate some DSA params
- // before it generates the key.
- //
- KeyPair dsaKp = dsaKpg.generateKeyPair();
-
- KeyPairGenerator elgKpg = KeyPairGenerator.getInstance("ELGAMAL", "BC");
- BigInteger g = new BigInteger("153d5d6172adb43045b68ae8e1de1070b6137005686d29d3d73a7749199681ee5b212c9b96bfdcfa5b20cd5e3fd2044895d609cf9b410b7a0f12ca1cb9a428cc", 16);
- BigInteger p = new BigInteger("9494fec095f3b85ee286542b3836fc81a5dd0a0349b4c239dd38744d488cf8e31db8bcb7d33b41abb9e5a33cca9144b1cef332c94bf0573bf047a3aca98cdf3b", 16);
-
- DHParameterSpec elParams = new DHParameterSpec(p, g);
-
- elgKpg.initialize(elParams);
-
- //
- // this is quicker because we are using pregenerated parameters.
- //
- KeyPair elgKp = elgKpg.generateKeyPair();
-
+
+ KeyPair dsaKp = generateDSAKeyPair();
+
+ KeyPair elgKp = generateElGamalKeyPair();
+
if (args[0].equals("-a"))
{
if (args.length < 3)
From d618caa203e218e2e5115ab3632373460d341071 Mon Sep 17 00:00:00 2001
From: Paul Schaub
Date: Sat, 13 May 2023 20:47:52 +0200
Subject: [PATCH 044/290] Modernize RSAKeyPairGenerator
---
.../openpgp/examples/RSAKeyPairGenerator.java | 121 +++++++++++++-----
1 file changed, 89 insertions(+), 32 deletions(-)
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
index f4734428e8..1c54fe78c7 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/RSAKeyPairGenerator.java
@@ -1,33 +1,44 @@
package org.bouncycastle.openpgp.examples;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.InvalidKeyException;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.NoSuchProviderException;
-import java.security.Security;
-import java.security.SignatureException;
-import java.util.Date;
-
import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.sig.Features;
+import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
-import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
/**
- * A simple utility class that generates a RSA PGPPublicKey/PGPSecretKey pair.
+ * A simple utility class that generates an RSA key ring.
*
* usage: RSAKeyPairGenerator [-a] identity passPhrase
*
@@ -36,25 +47,71 @@
*/
public class RSAKeyPairGenerator
{
- private static void exportKeyPair(
+
+ private static final int SIG_HASH = HashAlgorithmTags.SHA512;
+ private static final int[] HASH_PREFERENCES = new int[] {
+ HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA224
+ };
+ private static final int[] SYM_PREFERENCES = new int[] {
+ SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128
+ };
+ private static final int[] COMP_PREFERENCES = new int[] {
+ CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.UNCOMPRESSED
+ };
+
+ private static void generateAndExportKeyRing(
OutputStream secretOut,
OutputStream publicOut,
- KeyPair pair,
String identity,
char[] passPhrase,
boolean armor)
- throws IOException, InvalidKeyException, NoSuchProviderException, SignatureException, PGPException
- {
+ throws IOException, NoSuchProviderException, PGPException, NoSuchAlgorithmException {
if (armor)
{
secretOut = new ArmoredOutputStream(secretOut);
}
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
- PGPKeyPair keyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, pair, new Date());
- PGPSecretKey secretKey = new PGPSecretKey(PGPSignature.DEFAULT_CERTIFICATION, keyPair, identity, sha1Calc, null, null, new JcaPGPContentSignerBuilder(keyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256), new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc).setProvider("BC").build(passPhrase));
-
- secretKey.encode(secretOut);
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ PGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(PublicKeyAlgorithmTags.RSA_GENERAL, SIG_HASH);
+ PBESecretKeyEncryptor secretKeyEncryptor = new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256, sha1Calc)
+ .build(passPhrase);
+
+ Date now = new Date();
+
+ kpg.initialize(3072);
+ KeyPair primaryKP = kpg.generateKeyPair();
+ PGPKeyPair primaryKey = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, primaryKP, now);
+ PGPSignatureSubpacketGenerator primarySubpackets = new PGPSignatureSubpacketGenerator();
+ primarySubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER);
+ primarySubpackets.setPreferredHashAlgorithms(false, HASH_PREFERENCES);
+ primarySubpackets.setPreferredSymmetricAlgorithms(false, SYM_PREFERENCES);
+ primarySubpackets.setPreferredCompressionAlgorithms(false, COMP_PREFERENCES);
+ primarySubpackets.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
+ primarySubpackets.setIssuerFingerprint(false, primaryKey.getPublicKey());
+
+ kpg.initialize(3072);
+ KeyPair signingKP = kpg.generateKeyPair();
+ PGPKeyPair signingKey = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, signingKP, now);
+ PGPSignatureSubpacketGenerator signingKeySubpacket = new PGPSignatureSubpacketGenerator();
+ signingKeySubpacket.setKeyFlags(true, KeyFlags.SIGN_DATA);
+ signingKeySubpacket.setIssuerFingerprint(false, primaryKey.getPublicKey());
+
+ kpg.initialize(3072);
+ KeyPair encryptionKP = kpg.generateKeyPair();
+ PGPKeyPair encryptionKey = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, encryptionKP, now);
+ PGPSignatureSubpacketGenerator encryptionKeySubpackets = new PGPSignatureSubpacketGenerator();
+ encryptionKeySubpackets.setKeyFlags(true, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
+ encryptionKeySubpackets.setIssuerFingerprint(false, primaryKey.getPublicKey());
+
+ PGPKeyRingGenerator gen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, primaryKey, identity,
+ sha1Calc, primarySubpackets.generate(), null, contentSignerBuilder, secretKeyEncryptor);
+ gen.addSubKey(signingKey, signingKeySubpacket.generate(), null, contentSignerBuilder);
+ gen.addSubKey(encryptionKey, encryptionKeySubpackets.generate(), null);
+
+ PGPSecretKeyRing secretKeys = gen.generateSecretKeyRing();
+ secretKeys.encode(secretOut);
secretOut.close();
@@ -63,9 +120,15 @@ private static void exportKeyPair(
publicOut = new ArmoredOutputStream(publicOut);
}
- PGPPublicKey key = secretKey.getPublicKey();
+ List publicKeyList = new ArrayList<>();
+ Iterator it = secretKeys.getPublicKeys();
+ while (it.hasNext()) {
+ publicKeyList.add(it.next());
+ }
+
+ PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(publicKeyList);
- key.encode(publicOut);
+ publicKeys.encode(publicOut);
publicOut.close();
}
@@ -75,12 +138,6 @@ public static void main(
throws Exception
{
Security.addProvider(new BouncyCastleProvider());
-
- KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
-
- kpg.initialize(1024);
-
- KeyPair kp = kpg.generateKeyPair();
if (args.length < 2)
{
@@ -99,14 +156,14 @@ public static void main(
FileOutputStream out1 = new FileOutputStream("secret.asc");
FileOutputStream out2 = new FileOutputStream("pub.asc");
- exportKeyPair(out1, out2, kp, args[1], args[2].toCharArray(), true);
+ generateAndExportKeyRing(out1, out2, args[1], args[2].toCharArray(), true);
}
else
{
FileOutputStream out1 = new FileOutputStream("secret.bpg");
FileOutputStream out2 = new FileOutputStream("pub.bpg");
- exportKeyPair(out1, out2, kp, args[0], args[1].toCharArray(), false);
+ generateAndExportKeyRing(out1, out2, args[0], args[1].toCharArray(), false);
}
}
}
From 812da3eb001f575999975018c7650c9ab0a5f6ea Mon Sep 17 00:00:00 2001
From: Paul Schaub
Date: Sat, 13 May 2023 21:12:15 +0200
Subject: [PATCH 045/290] Modernize PBEFileProcessor
---
.../openpgp/examples/PBEFileProcessor.java | 154 +++++++++---------
1 file changed, 79 insertions(+), 75 deletions(-)
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
index 9219b6f671..3f3c350675 100644
--- a/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java
@@ -1,21 +1,11 @@
package org.bouncycastle.openpgp.examples;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.security.Security;
-
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
-import org.bouncycastle.openpgp.PGPEncryptedData;
+import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
@@ -23,19 +13,30 @@
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
+import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.util.io.Streams;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.security.Security;
+
/**
* A simple utility class that encrypts/decrypts password based
* encryption files.
*
- * To encrypt a file: PBEFileProcessor -e [-ai] fileName passPhrase.
+ * To encrypt a file: PBEFileProcessor -e [-a] fileName passPhrase.
* If -a is specified the output file will be "ascii-armored".
- * If -i is specified the output file will be "integrity protected".
*
* To decrypt: PBEFileProcessor -d fileName passPhrase.
*
@@ -46,7 +47,7 @@
public class PBEFileProcessor
{
private static void decryptFile(String inputFileName, char[] passPhrase)
- throws IOException, NoSuchProviderException, PGPException
+ throws IOException, PGPException
{
InputStream in = new BufferedInputStream(new FileInputStream(inputFileName));
decryptFile(in, passPhrase);
@@ -57,16 +58,16 @@ private static void decryptFile(String inputFileName, char[] passPhrase)
* decrypt the passed in message stream
*/
private static void decryptFile(
- InputStream in,
- char[] passPhrase)
- throws IOException, NoSuchProviderException, PGPException
+ InputStream in,
+ char[] passPhrase)
+ throws IOException, PGPException
{
in = PGPUtil.getDecoderStream(in);
-
- JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
- PGPEncryptedDataList enc;
- Object o = pgpF.nextObject();
-
+
+ JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
+ PGPEncryptedDataList enc;
+ Object o = pgpF.nextObject();
+
//
// the first object might be a PGP marker packet.
//
@@ -79,27 +80,32 @@ private static void decryptFile(
enc = (PGPEncryptedDataList)pgpF.nextObject();
}
- PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
+ PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
+ if (!pbe.isIntegrityProtected())
+ {
+ throw new PGPException("Message is not integrity protected!");
+ }
- InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase));
-
- JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(clear);
+ JcePBEDataDecryptorFactoryBuilder decryptorFactoryBuilder = new JcePBEDataDecryptorFactoryBuilder(
+ new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build())
+ .setProvider("BC");
+
+ InputStream clear = pbe.getDataStream(decryptorFactoryBuilder.build(passPhrase));
+ pgpF = new JcaPGPObjectFactory(clear);
//
// if we're trying to read a file generated by someone other than us
// the data might not be compressed, so we check the return type from
// the factory and behave accordingly.
//
- o = pgpFact.nextObject();
+ o = pgpF.nextObject();
if (o instanceof PGPCompressedData)
{
PGPCompressedData cData = (PGPCompressedData)o;
-
- pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
-
- o = pgpFact.nextObject();
+ pgpF = new JcaPGPObjectFactory(cData.getDataStream());
+ o = pgpF.nextObject();
}
-
+
PGPLiteralData ld = (PGPLiteralData)o;
InputStream unc = ld.getInputStream();
@@ -108,66 +114,68 @@ private static void decryptFile(
Streams.pipeAll(unc, fOut, 8192);
fOut.close();
-
- if (pbe.isIntegrityProtected())
+ if (!pbe.verify())
{
- if (!pbe.verify())
- {
- System.err.println("message failed integrity check");
- }
- else
- {
- System.err.println("message integrity check passed");
- }
+ System.err.println("message failed integrity check");
}
else
{
- System.err.println("no message integrity check");
+ System.err.println("message integrity check passed");
}
}
private static void encryptFile(
- String outputFileName,
- String inputFileName,
- char[] passPhrase,
- boolean armor,
- boolean withIntegrityCheck)
- throws IOException, NoSuchProviderException
+ String outputFileName,
+ String inputFileName,
+ char[] passPhrase,
+ boolean armor)
+ throws IOException
{
OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName));
- encryptFile(out, inputFileName, passPhrase, armor, withIntegrityCheck);
+ encryptToStream(out, inputFileName, passPhrase, armor);
out.close();
}
- private static void encryptFile(
- OutputStream out,
- String fileName,
- char[] passPhrase,
- boolean armor,
- boolean withIntegrityCheck)
- throws IOException, NoSuchProviderException
+ private static void encryptToStream(
+ OutputStream out,
+ String fileName,
+ char[] passPhrase,
+ boolean armor)
+ throws IOException
{
if (armor)
{
out = new ArmoredOutputStream(out);
}
+ File file = new File(fileName);
+
try
{
- byte[] compressedData = PGPExampleUtil.compressFile(fileName, CompressionAlgorithmTags.ZIP);
-
- PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
- .setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(new SecureRandom()).setProvider("BC"));
-
+ // Encryption
+ PGPDataEncryptorBuilder dataEncBuilder = new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256)
+ .setProvider("BC")
+ .setWithIntegrityPacket(true)
+ .setSecureRandom(new SecureRandom());
+ PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(dataEncBuilder);
encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase).setProvider("BC"));
+ OutputStream encOut = encGen.open(out, new byte[8192]);
+
+ // Compression
+ PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
+ OutputStream compOut = compGen.open(encOut);
- OutputStream encOut = encGen.open(out, compressedData.length);
+ // Literal Data
+ PGPUtil.writeFileToLiteralData(compOut, PGPLiteralData.BINARY, file);
- encOut.write(compressedData);
+ compOut.flush();
+ compOut.close();
+ encOut.flush();
encOut.close();
if (armor)
{
+ out.flush();
out.close();
}
}
@@ -182,24 +190,20 @@ private static void encryptFile(
}
public static void main(
- String[] args)
- throws Exception
+ String[] args)
+ throws Exception
{
Security.addProvider(new BouncyCastleProvider());
if (args[0].equals("-e"))
{
- if (args[1].equals("-a") || args[1].equals("-ai") || args[1].equals("-ia"))
- {
- encryptFile(args[2] + ".asc", args[2], args[3].toCharArray(), true, (args[1].indexOf('i') > 0));
- }
- else if (args[1].equals("-i"))
+ if (args[1].equals("-a"))
{
- encryptFile(args[2] + ".bpg", args[2], args[3].toCharArray(), false, true);
+ encryptFile(args[2] + ".asc", args[2], args[3].toCharArray(), true);
}
else
{
- encryptFile(args[1] + ".bpg", args[1], args[2].toCharArray(), false, false);
+ encryptFile(args[1] + ".bpg", args[1], args[2].toCharArray(), false);
}
}
else if (args[0].equals("-d"))
@@ -208,7 +212,7 @@ else if (args[0].equals("-d"))
}
else
{
- System.err.println("usage: PBEFileProcessor -e [-ai]|-d file passPhrase");
+ System.err.println("usage: PBEFileProcessor -e [-a]|-d file passPhrase");
}
}
}
From 49731b3b26c3feecb888339b4df149cb966fc72b Mon Sep 17 00:00:00 2001
From: Paul Schaub
Date: Sun, 14 May 2023 13:55:04 +0200
Subject: [PATCH 046/290] Add EllipticCurveKeyPairGenerator example
---
.../EllipticCurveKeyPairGenerator.java | 175 ++++++++++++++++++
1 file changed, 175 insertions(+)
create mode 100644 pg/src/main/java/org/bouncycastle/openpgp/examples/EllipticCurveKeyPairGenerator.java
diff --git a/pg/src/main/java/org/bouncycastle/openpgp/examples/EllipticCurveKeyPairGenerator.java b/pg/src/main/java/org/bouncycastle/openpgp/examples/EllipticCurveKeyPairGenerator.java
new file mode 100644
index 0000000000..84f3cd3c7e
--- /dev/null
+++ b/pg/src/main/java/org/bouncycastle/openpgp/examples/EllipticCurveKeyPairGenerator.java
@@ -0,0 +1,175 @@
+package org.bouncycastle.openpgp.examples;
+
+import org.bouncycastle.bcpg.ArmoredOutputStream;
+import org.bouncycastle.bcpg.CompressionAlgorithmTags;
+import org.bouncycastle.bcpg.HashAlgorithmTags;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
+import org.bouncycastle.bcpg.sig.Features;
+import org.bouncycastle.bcpg.sig.KeyFlags;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveGenParameterSpec;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.PGPKeyPair;
+import org.bouncycastle.openpgp.PGPKeyRingGenerator;
+import org.bouncycastle.openpgp.PGPPublicKey;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPSecretKeyRing;
+import org.bouncycastle.openpgp.PGPSignature;
+import org.bouncycastle.openpgp.PGPSignatureGenerator;
+import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
+import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
+import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
+import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A simple utility class that generates an RSA key ring.
+ *
+ * usage: RSAKeyPairGenerator [-a] identity passPhrase
+ *
+ * Where identity is the name to be associated with the public key. The keys are placed
+ * in the files pub.[asc|bpg] and secret.[asc|bpg].
+ */
+public class EllipticCurveKeyPairGenerator
+{
+
+ private static final int SIG_HASH = HashAlgorithmTags.SHA512;
+ private static final int[] HASH_PREFERENCES = new int[] {
+ HashAlgorithmTags.SHA512, HashAlgorithmTags.SHA384, HashAlgorithmTags.SHA256, HashAlgorithmTags.SHA224
+ };
+ private static final int[] SYM_PREFERENCES = new int[] {
+ SymmetricKeyAlgorithmTags.AES_256, SymmetricKeyAlgorithmTags.AES_192, SymmetricKeyAlgorithmTags.AES_128
+ };
+ private static final int[] COMP_PREFERENCES = new int[] {
+ CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.BZIP2, CompressionAlgorithmTags.ZLIB, CompressionAlgorithmTags.UNCOMPRESSED
+ };
+
+ private static void generateAndExportKeyRing(
+ OutputStream secretOut,
+ OutputStream publicOut,
+ String identity,
+ char[] passPhrase,
+ boolean armor)
+ throws IOException, NoSuchProviderException, PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ if (armor)
+ {
+ secretOut = new ArmoredOutputStream(secretOut);
+ }
+
+ PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1);
+
+ KeyPairGenerator eddsaGen = KeyPairGenerator.getInstance("EdDSA", "BC");
+ KeyPairGenerator xdhGen = KeyPairGenerator.getInstance("XDH", "BC");
+
+ PGPContentSignerBuilder contentSignerBuilder = new JcaPGPContentSignerBuilder(PublicKeyAlgorithmTags.EDDSA_LEGACY, SIG_HASH);
+ PBESecretKeyEncryptor secretKeyEncryptor = new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256, sha1Calc)
+ .build(passPhrase);
+
+ Date now = new Date();
+
+ eddsaGen.initialize(new ECNamedCurveGenParameterSpec("ed25519"));
+ KeyPair primaryKP = eddsaGen.generateKeyPair();
+ PGPKeyPair primaryKey = new JcaPGPKeyPair(PGPPublicKey.EDDSA_LEGACY, primaryKP, now);
+ PGPSignatureSubpacketGenerator primarySubpackets = new PGPSignatureSubpacketGenerator();
+ primarySubpackets.setKeyFlags(true, KeyFlags.CERTIFY_OTHER);
+ primarySubpackets.setPreferredHashAlgorithms(false, HASH_PREFERENCES);
+ primarySubpackets.setPreferredSymmetricAlgorithms(false, SYM_PREFERENCES);
+ primarySubpackets.setPreferredCompressionAlgorithms(false, COMP_PREFERENCES);
+ primarySubpackets.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
+ primarySubpackets.setIssuerFingerprint(false, primaryKey.getPublicKey());
+
+ eddsaGen.initialize(new ECNamedCurveGenParameterSpec("ed25519"));
+ KeyPair signingKP = eddsaGen.generateKeyPair();
+ PGPKeyPair signingKey = new JcaPGPKeyPair(PGPPublicKey.EDDSA_LEGACY, signingKP, now);
+ PGPSignatureSubpacketGenerator signingKeySubpacket = new PGPSignatureSubpacketGenerator();
+ signingKeySubpacket.setKeyFlags(true, KeyFlags.SIGN_DATA);
+ signingKeySubpacket.setIssuerFingerprint(false, primaryKey.getPublicKey());
+
+ xdhGen.initialize(new ECNamedCurveGenParameterSpec("X25519"));
+ KeyPair encryptionKP = xdhGen.generateKeyPair();
+ PGPKeyPair encryptionKey = new JcaPGPKeyPair(PGPPublicKey.ECDH, encryptionKP, now);
+ PGPSignatureSubpacketGenerator encryptionKeySubpackets = new PGPSignatureSubpacketGenerator();
+ encryptionKeySubpackets.setKeyFlags(true, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
+ encryptionKeySubpackets.setIssuerFingerprint(false, primaryKey.getPublicKey());
+
+ PGPKeyRingGenerator gen = new PGPKeyRingGenerator(PGPSignature.POSITIVE_CERTIFICATION, primaryKey, identity,
+ sha1Calc, primarySubpackets.generate(), null, contentSignerBuilder, secretKeyEncryptor);
+ gen.addSubKey(signingKey, signingKeySubpacket.generate(), null, contentSignerBuilder);
+ gen.addSubKey(encryptionKey, encryptionKeySubpackets.generate(), null);
+
+ PGPSecretKeyRing secretKeys = gen.generateSecretKeyRing();
+ secretKeys.encode(secretOut);
+
+ secretOut.close();
+
+ if (armor)
+ {
+ publicOut = new ArmoredOutputStream(publicOut);
+ }
+
+ List publicKeyList = new ArrayList<>();
+ Iterator it = secretKeys.getPublicKeys();
+ while (it.hasNext()) {
+ publicKeyList.add(it.next());
+ }
+
+ PGPPublicKeyRing publicKeys = new PGPPublicKeyRing(publicKeyList);
+
+ publicKeys.encode(publicOut);
+
+ publicOut.close();
+ }
+
+ public static void main(
+ String[] args)
+ throws Exception
+ {
+ Security.addProvider(new BouncyCastleProvider());
+
+ if (args.length < 2)
+ {
+ System.out.println("EllipticCurveKeyPairGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ if (args[0].equals("-a"))
+ {
+ if (args.length < 3)
+ {
+ System.out.println("EllipticCurveKeyPairGenerator [-a] identity passPhrase");
+ System.exit(0);
+ }
+
+ FileOutputStream out1 = new FileOutputStream("secret.asc");
+ FileOutputStream out2 = new FileOutputStream("pub.asc");
+
+ generateAndExportKeyRing(out1, out2, args[1], args[2].toCharArray(), true);
+ }
+ else
+ {
+ FileOutputStream out1 = new FileOutputStream("secret.bpg");
+ FileOutputStream out2 = new FileOutputStream("pub.bpg");
+
+ generateAndExportKeyRing(out1, out2, args[0], args[1].toCharArray(), false);
+ }
+ }
+}
From 7efa739cab070aa54bcc293d18a09028150a1660 Mon Sep 17 00:00:00 2001
From: David Hook
Date: Mon, 15 May 2023 19:29:10 +1000
Subject: [PATCH 047/290] removed troublesome jar files
---
libs/README.md | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 libs/README.md
diff --git a/libs/README.md b/libs/README.md
new file mode 100644
index 0000000000..9f573c254f
--- /dev/null
+++ b/libs/README.md
@@ -0,0 +1,11 @@
+
+The current set of lib files to go in this directory can be found at:
+
+https://downloads.bouncycastle.org/test-libs-java/activation.jar
+https://downloads.bouncycastle.org/test-libs-java/jakarta.activation-api-2.0.0.jar
+https://downloads.bouncycastle.org/test-libs-java/jakarta.mail-2.0.1.jar
+https://downloads.bouncycastle.org/test-libs-java/junit.jar
+https://downloads.bouncycastle.org/test-libs-java/mail.jar
+https://downloads.bouncycastle.org/test-libs-java/unboundid-ldapsdk-6.0.8.jar
+
+These are required for building and running the tests in the distribution.
From 54b1c38ea4f396b53f45a3f478c427146932b78c Mon Sep 17 00:00:00 2001
From: David Hook
Date: Mon, 15 May 2023 19:30:44 +1000
Subject: [PATCH 048/290] removed troublesome jar files
---
libs/activation.jar | Bin 69409 -> 0 bytes
libs/jakarta.activation-api-2.0.0.jar | Bin 47669 -> 0 bytes
libs/jakarta.mail-2.0.1.jar | Bin 689294 -> 0 bytes
libs/junit.jar | Bin 314932 -> 0 bytes
libs/mail.jar | Bin 356519 -> 0 bytes
libs/unboundid-ldapsdk-6.0.8.jar | Bin 5649572 -> 0 bytes
6 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 libs/activation.jar
delete mode 100644 libs/jakarta.activation-api-2.0.0.jar
delete mode 100644 libs/jakarta.mail-2.0.1.jar
delete mode 100644 libs/junit.jar
delete mode 100644 libs/mail.jar
delete mode 100644 libs/unboundid-ldapsdk-6.0.8.jar
diff --git a/libs/activation.jar b/libs/activation.jar
deleted file mode 100644
index 1b703ab283e0cddabf9c1b5e28658f9198c0def4..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 69409
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 h+T
zAa>%IGA7t)R4d+Ja8#VECCzk3(rnt%H(GtTky44i7{isNG!B8F1G?3?Zd_`(8wbgG
z2Ze?C%n9mK6A8{*v;FML#TSGQk2AY1=DKcrM$Ye?C@u^!bJh_sen88@Z0;u=z27^b
z6xX#yhe|M$bsf7@l1e*o(m{HB(a7vID5Q+r_QwOe%W6c)M4gD`M094N*ud4A#HH0g
zM|%?(OQK02A`kR72p9eo*Me{@fALW)o?j<9#m?LweP79RYLasyvyp1=w10tKZvkh^yhi8D&&S_DpZI1_;Epk7+KbB4UEQ1x8()$47iZ!g&7*kgfNo$vkV5BeVo)mDBT%*~k3
zP{003Fem>f(H#3fCz{(CS=pKx0U1oq%#B=aU9u96&rF!wo@`yY-%q0jzJRDP6axjxe_UkW9aP;;kk=_n>*zZ^!0S9yq+q
z@rZ`d{)4!B?uFc$Godzy1c4St&qaYQtvOf$2E|3Pc~&(f?GG
zJ|IW>dJI{xq_dNHzkd0!UdDr-tMi?FW{&~N-)m6zv94pZfnUER|L|OLc>FRG3RVX$
zEMgsyN(8#wdpgi2WL~r&AL~Z`1pe;=gMc*tmudxwe*`vhumk-65itLTaCWu-Z%}Ce
z31wvBV&!J!V&(AP84&)z4V=s@tejn(tjwJM&tfqDo3s~8X(=@%2uSxoe3<@6TUjf6
zGgSv0GkYs9GbaWUTO()Z6!i%QGz;YKn1<)|Y2lzgSSd+ZhYgZ+ivU4kG&D=%y}95b
zUg?H9jyuZd>ZT5Ht0k2sGGzb;jj!T3DA9PJv|%}EQL<9Da6z1k823)K
z4OJi^&BTt$apv0;$3y1K*WQ|j0r)B`Sv33O1UAe-*cY;P0_uPg6GCVm>d4@Et7aGK
zFtU8OGt~ec9%cm!PU)bJ_V8~;+>p`+BhgtvsKyW@jqFGeC)Kf|qnq%s4f8Y_?!4H<
zJiprmM$xnzov^8gdRGCZb2(EIzAN2UI+y6}Mr=k-1x-|Jj!dkL
z)kvcz^@h*S(zRd=>V@5;P?qZBNL&)2CWlLD9C8fb4I96Fys~-d3>(5ls9Sk@?tCSa
zD@`dZF-z6)u$m0kN2H=tJ$IoCV(FC6JOi#2+tsOtDvIy?ytzxP!OCS3kllRRD)cLr
za$m=4<}4(l^4hWxl5NWG+!)MTOub1$c7n&cV;(*@K7Wd*I=q$=(_e{sbgxGKc1!3XNg4gsMXVqXgMJCyOq7^KW)mI62y<%enhGfsd)Sgm&1UA}k2LwQQH7t}A%OOjrFDiV_1KegEoI
zInVgqKp^Tmk2nS)zwr1wnM+&N0@_(SM~js+cNvb*DnsAL7ZSGwW704!h>lZhvBD73
z8O~`Gjr&^yGA-EM%Q_V>?m1;O9G&D^F)WHy)KI=O<3!Dlev({WtyQEs@UuUzV1ohC
zHV#Ue?nqttfq`PO(3ZawKSpNF(8}{6l3kOBIv`yXv`KLmL0%1y=fi;pf?y$uF-tG2
zp7x?-b#@k>n({(iJX3)0YVQ_uz?xD;$Y5D61^eLObT_Ra#sF7m;=5>cmAmrH40lTEppI_jK#Um^$h2DUF}QUI
z9k}d(`0=uyKKq;7m=xT0x)kE2EiE`3MbCBD+>uCclh*k4#jLFmA_`<^#U-e&|ND+*yXrlC7T$guDe^uc^;_65HRh
zA9v|UyBgf#RExh#7o#i|C4{(zpB5GP{uU4&%{X!&JDK3_L+ke0mvsEdb?)?Tr7+tZ
z*8jRZX!e|JyfLpLQYf#O@F4nd>^<7h4ABt6J$CQN{>>(c@^~BhO~8ovW3P6Dvug?J
z+9JlXC4LvP#agCHVK5hL7S_$pNy#9z+!seUcOxyBta@tV`#;sI{mWsYa&azf01g6z
z1q}ki@IS|02MZNfAke|d<==v?+L`i#BKnuTP6j*F9BEYRp1u+u`pZ64y%oJB$_quZ
z4Pp{|HmjH~x-aPW>j*P=?DQ)LfARsBRFSr_#CMkWjaK(Vc9OS!_xI~PVlN{ChA(G|
zC_tWyDTsw;dLN%?rlB=&pOk@mv|Mr*J!%eu;lV3pNR8dV#E~z8DVho>G6X@1AxptH
z^^OC{fw75ts+kz|lo;Saj@)voEX555-ezVpG<#8PbymqjOTl-tR*!BphG7MJiQsIE
z_BJRn6?vwQ5?&HV4>LG?ML|8A!a~bz002l^Y-=`3CXKI&4QMmsC(Q&hA9pj`fQhsf
z=q=kkMcSk5IS&ElItqB|O_y~?lG!L#%QPyQ50v|VG9jNzePq%7YDFKdQXY5uvTRe|
zH_ePrrXWA$nEFdQ;y2aEi@&EFt5^|Wz1%A12dm2d#KNTA7{p%&*>R12RAlApyPB6{
z#E9r!rywHWMl)*F8q*o1h3L##UCTEjtQWYg-fi-f1}HAf5AY0}zT6$m
zEU0v9VL+_E&k2RxJlTcDmEX+|lE5W2Q3!aXT(ET*?m&RcvGj_l^%OR6#aw{VR{X2N*
z;-y-;I`*)B+N)O7*OLQDFGKB=-wen%_OCVGIbMQoK5zpG&ZhhhnZ5)jH?|M9u9RN4
zpPQKGE#;>VsUVs;0!M5&5}}%O=%w5bkh#Nyv&c)AD0pDCCDSJ+)N^@4qu^LafOo5t0Q2xnWX7q*G4~3
zpEbw{tI4E#t*zlB4s2$ZXV)erBde;Y9aP47m2$;GbKvFIE(V~eJvo98l3Aq+{h1~C
zFW+86x+K!#dP1w+6^)6Iqx{G-Z|euUz~CNb#%v##5Z>DX?U9n;8|o+ucHhzH**{;^
ztw$^#D+>2mAM1
z+rM6sB%cTD2~a^m9?3vJ0RN+_%KhszK-|jK>|cGgN)z5k^(aN)WLxUk+$l*G3^X(>
znlwHh7L*7Z4TYqWRH_%^*DjenCC6AVZ$Gbx)m6K#uc}_hd2_*4DE)G!y7gT`d`Y)9
z?UL(-x^+dBLxot2f{ky}_}5IAJ0;85?hp5ggN~g~!jq5OZ>}A#lWCP)zZ)Y`?Os?c
zyZiz9_uosH
znJd222
zf2!F{+bQ|G3`v&)k>o>u5`W^!hT-dx1-GW3um+b$)f}^V
z*?2LuV(Jx-#NniD-XSrY)7mU2?9y5Z%2I1yIaB!+kI-Q%N%xXr{TKy^I-kVK%;LMT
zp!2fMNz7Vj)*M<3RZHDJcf2tbx{bm5ORJ@hO~Vgt)pH(!QCRoX^88CrZj(qIN)3g7fCbxxqG97gMhQDipBT~C8Eqr5EOH`!L
z7^DgVbdJAuf({as;Z!2Xs`Y%>=WwqkdS?kit#c=FZ)i%E!!fec9`y_BJXYvK(i_{D
zxO$YpVJ|u~JsKLoq?o3Df5yyXE;|{Cb|@NlZoIPwb#P1i^?6As-sJf4!1>vglk_vWYZi>F6c`ST
z_>!!qU)KtZC%EQHKyo`ie(I__b&J^B=eASQ?2Z2kSA?_Y(OtPhE#86hL%b$79q~o6
z6gk8-QWCAWHVNy@2Wo}XFlNY-i-Zz+qYLN$qI1i#i#vDBm3!quiyJ_RY{1T63m@qr
z{m9vqy2j)Bj)}{!Gb=xukdR&y$0I;?{%a4vEpMq?3AkhFYIk*Jp$-@{>V$a4qpF-Z
zeI_e7^FacHh?qjsXQw&-nx624v}IB_N$Hf{R=;Qrv&S1@=x>N62Ug=Z4Ufrjb2vv$
z+jtTnu%_x|&m5>zwVZY4%&fF!a}Yb5jp$|rc4J6wlac_7Z=}^rZ^qrCCrI6_VySH&
zR&>WZR%U7QOo6r2@B%VSrrEYhGk$ftrL3UW^C(a&XD&7A7mME_jHczh*NUU8-wz=W
z*&S`r96Qk-uD6}~hDXGC3vs8gRyjew)sfO{2+#a02FzOU-ty!GNU0zD|
z6Iyt1AEarZ&&xa%h-&(!Xh}hViSZq~PbStC9_hzy10^kBlogNmHz_*iLsSW;Bl+?+
zLq4X`fWz_GMpqe~4RsHV#^!3fst5Cl5Ga0|TtJsD`Zkk)^2=Kpn~8eWnJFUymKoI{
z3|H-~qeigSS9h~Bi>hJy>*bZ)Tu!rTZm|8s_I}d{H>O&4a&M{$%Ui78R1Ca6Ow#{i
z>>HzN3D-1ZdJox!u##tEcx```50jAGN;yeos9r
zbprD!-^K8|i^6OuA&<#O^dtg3?dx?$7Z4RWYx=1%K|`~Qpix|Z4FBV#(_k$MOiHXG
zPf6!tE5{@BaW!@yGy1;6FKakaXB~no0}m^5CTT#2!Z5a4YO@^p)!-zHC`Z&iolIkvbF6u^
zD6PzDZ@QMC@eM;eb|mvG#v^DukZ8AQJq$0s+b4L0$U6o59HF|~CzrX=QSQ{Cc_4{Z
zB}E38paRuAT%i+B7*iv+f#Y@1G`~7PMvdM-k{U-r~{{Pe@FF
z-O5ZeD%^skECXBR8D2Y52Red6rqlr(d`cL#QbiS~hVALc3(Sqd)j=DY;4f|29A{vf
zjh$w!S)o&vbtfy;4k6+(EUEdRL%;c7dO#s>cwm!HUE
zldyWK#RxrIr~o`dung
ztZ_30z*z0cyfXq#(WIoCv0ffe->B-B8_Ay+0QPiv-3P~VKB~-Aqhk1@lu4!h
z;^8QExhMoxsf?pY9Pp)1^f%8C%MDqV?8&K*8P0dZkoWDyZw=d%OXj1v0<8DV9H9?T
zZzbEKOyqcHifryJ+XE=iXf^{qS}CqLTXxxfZtwVBKPGWjAcc=;VxYUJ1qM?szY^U>B44;_0Ywr4#40Io
zx5zD4;|;B8(V|~-E7CYB;xp4Yz4&xi#DYb0E&4Kd_+er)?%^SjIucIA*@zl~U5}Os
z)-pe$y(Ah11gf^i3V~9vrPh?$oYU^90k8-y(j&X@`y9->s;_RdG~bSM{Jjcn%0o*L%Oa3gm++(Gw$nx
z0&D{`n-j$CA;vcbo;2DM^DiLUeM(n$pE#Afq|WG?edq3U+QW;r8CF}gok9NYG(t~k
zXYVMNhbvE5pR5;K-kq?Xm)k8*-=3*})2Lf-@5G(~8T~^H?^vDQ(+jT28-ruQFP`rx
zJUL`<#k3(_S=4*@B%#mN`aqw;$~}Q`w@-s3L~rr4Ba=kBZ^f_&y(5WF#v|jeHhaqN
zKWzayDqydmM!ethZkamr*+(f!Xm2P-5IyCcGc{#!-dfbVtcqDXiW6_tP9)#LRr`eJ
z7@y7UsXKBrM?2*gZ!YIxUtR5C-=)v@Ig98$72171$~}`eWvsV4iyLoVPQJXdH^(uH
zAFl>a_|5Vc9ZaBZx_N3yX5)W6N4(IuvvfpYZ2{;zVsrSFZ1LP_wxw=n|DK|hDvgT4
z=bk7>?|!`TKROphz*&{3An0Rv~p%v;QS-OQe6Eq
z)Vj6+H&2Wh!7=+<7(g4=U-q24)BY~7_$JzJJ&{5oF2`_wsIFMGl9pDF+H~P$oTMZn
z1GIL^`hbWurNEMYK~J)Dd0%H*Y?51s8$l8a__8_*#aMoHWM()aC#5l(oPmzg@KTsI
z&_K^)W$+1NAP;`YD6qedES@MB#K<74KE{Yt90%CIDM0EY{584!{!L69;jd30Dlh#u
zPGqiBT;g--i)lvjTvWCy@@=G0H$x=E>>*>E#3&(4@t5fD13y-FeS>0G4ol(_cH#C1
zF*WD1=-vf@4{>M=YNFV29W1(5(47N)1LjX{%un>S_0BW+Yva>xx1<6Mgj}Hb%|`2TlB2@C+^0r16z(VB7nvOiEY$fJ_vAkaJ3*WEkVE(A3iYBL)X
zHWQ8|^s34FspW53tD6{*%7<6&9}r@-D8++QaVQ;4_y?08d)$-=Y03*yeXT$m)SVw)
z<}VX#Hw+_pN?LSOPHC8QgJ8vCPcjG8(WNp;S-UITQY568pE9Rwyu+EuyyPdo-BjU>
z$m+UgT1cvt&lKpUUM6W?`=V#xbKFQ67UKIC1E^1`w^Dj45cukUFYgHQXF
zVJX$*MPi$c*ItFzY7shI8j_`h{nu$MvL0
z1>mx;4FEIl*#Mk`hz@vESH8NKX!Jccp@EoR+usaN`i<^jzzpyDZciKU`e6@=XOkA*
zn}IJ{#_f&|K+jX%pE3SE7bGWc7k}(Yt<7Ea+FROxM&c0~(Q__B1jv+6-Je$M)(CRi
z^{x~x22v6G`s(Zo>$0n(r>3Msg>O*oVmy^yg0TvaI%`F%5fbQV>}y)!FDQ!`B9M9g
zw`}LjGC`GO@6Hw)E#l_AW
z$e%Ez^+r?)i(^$PGb31s$WQM!G**#E3_o8@Qb&hL%#vJ1o{S}_3SI|!R#29t{AMwx
zxlyO%s!2s213C059`W>J*~e--Y68Nf>#Z<$-$VU2kD}
z+#5BpB~E4bBt=Ewjg|OzA*HGZ!qU1j1W9S3NB72(5^BSZ#uylk$x9=V*q|3}tNc@<
zC5>VldIz}N5(X2n4h%LZG^h~Aj+EW&Kt*SzizD1xbhYM^E$zx}eM^gztL4Qkc{ekL
zIQp&R+nsSXTgrMmahE_#7KQBEmDcOGwL6O@$u=nD?fMKEVrOldetD&EOG-r^u+&$9
z^q00Y8pWKGN(E`1?DI7yq%aB%3~HRdV`AJx1wp#xQd(1yd8}Jz$I@q@V8wMwQ*1E&
zgsg1Vs_ypk66}E6hKc2hxk18FY1i0UHKb2vo!0VLemGd5K7}ALL{75Xr
zn8cP%dL9MgHG6g_0&Fz4QgS|KR0`@d;!WAIcTSHLY!oPqwwgn^n%rRx6v1)w^ps{Y
zVzwn9E)sgoSeybw3$DUL4XH@0f49R!@@U5|v3T-IDwPV4((emX2ov&AvXUpuTpd*K
zn^MbpC>tivXO2Qr{wgdW@3aOg5~9gGv$LBPIDZ`(e$j)pr%jkV(L|N%WzlF3YjCX}
z7FtI`2)bS@F$BPRg6E*;A~u=+Vcc*~Y}UCX+LrgfA?WS^i9y&=@ZaJ497g;E9^_T^
z_X&NKB7U91mSpyrvTQg}HnDa%b~ed5GoG1bn?^~jL(>ZU2<7H;$2ifNllV241x*&w
zj=CFutr=Jk;F8#j1nY*jo8aifwA$|53*Fpm-H&sN^?e
zSun7spW_FCJltqVWCa_Fq0m0dBip$n+qr8#!nSfJsjdS2K$IV%S~iLrE>WppqSLRg
z=*rZnhqH)oh?yW1Iou4_6l!5=IyU{!(}An$y&n5y70IA+B&V4TmtCTemfA+Q!?g{|
zwq15|a77tym}LYV`Dnd2cl*eY#X+v-5DJKvKnh6Kka7rtyx)14>w>3{L5Q5R)nx5x
z{C{w0BIURPcsTes=39Z0PES&Xf0L-qsbPymALWQZEuasf$DH~`qh90?6Ik98os&($-1r%?eWC0MofT78zE{9GN;ISq4u33
z=#T$eu=GBAA~*;_9A=sRYG`Fgw9E?NMYQA%xndyZnoB|8L2L_57t*9ms3(I0Q*eFY
z3MNdE^FRgEcX~LD0WokWb*Ht*G%W7H63|*C#Q+(5C?zAPc)0Y|lzBxTe}iJ$w^M#a
zsyro4D{g=}Qh@1SnCv$}Z#KagWZw*NfLUd=1=|@EctyqSYjJz${G2*)^F*WF^KeC(
z*<;r483gV0yE(XN4fWXt?F_@)rS1&%(Fe3KK#+Ld6F>EE-2s@8ABA9_qfCJJMS2&3
zo*UpfdvC57s&dxa+?=-@Ln5b}tLWD6v#z5&LOGs@S%dKI_kN}L*pXRV(KhgSRTI;{
z{xRo9{{Y^&!c$}P(YNr*s`dK0TKcCJG{1Ut}^0DL68ZTCd!h%zE%V@FY
z>e;5|To;rc@NKHGZ;#pnzNcYN&+5pw#euDVU}c72((HnYf?a8*zhY9rt_nd6IGlix
zlHiT}IP{Y>VQj#SY}};v%7_WOEzk;FgCN2+r=ozz9(QZbz4%i~TorPgJtRE2wm$SK
z;K8&qhK%q;4y#Hfm?)rw_%fLjzI~7j2rfe~*?X}*^rgnlGBD&DsjKuCPoN+`Q$Dsv
zP`I$1-&Q^%YbeDgf&ZuVRv>UbCO1K(wp_r#AvXy@vlPx^M?!PtulBm69cm60sB$&*
z0xAeKB7x@t+AOz7nGC9UC?25$&(`r)9=!|4d*1(BPn5!ScfI~Wcv(O4|Nq}|CH3RZ
zQ~Yt~30XVZyZuxBm%p+2QUAGV$JQGsmZ_)DGN-3GR*IKip+P92#6k0x;3)&VFwLIT
zcT5xF3kUwHKv@HWegk}y-F2s|n{Ch{ww@e!drWh`5cB$YeE{y_m%CBvQ-*L^PsmQ{
zbHW2_kX(yWrL|Jpkw$o^T{57Loy9V)P8r+`I(%r)I=sY-f^=~RMEi)l{)(W}9qs((
zC#ubGnQWqALYd?h?~Q~T=a@$h^ySTTdRuzs^lq+t>haf$7V+LolPNxiz_@kialj8N
z25990$!?#RX)DTAW{_yopoJK$Qz{vYA2CZj2ABf7SRz$x?j*!8U9WB=VYx%OpbBnz
zJjv~&N_IgbpNVFLDS?jLc=RTyVDzNkkLsK8#SghbmT%2Jh!}c8RbMp+_s)Is>ORzZ
zHwH<_Tf0%#Zy)YgDTEr(>~B%YP@
zFxeg`v3r26wbwt3T|~M4ckL9@3gmQLMzZ%)ilZ5Umx9yQJM90xc)+F|D(rq>s@X39
z0J{G_O#SEL`FAxsN%_MeaS6kF`glVk8GbY<(9*!%vYaHtQkh&|6+d3rSC<7sEYG6p
zEM<#!EO_d=9SuhHYKq`VnZGIuO20`8T3#$j>vwpwI(n8gXH$yVci7i#+O%UaW6=!z
zjfyE1
z8xKm!avcg%*)koGW5-S&KrkDSGh3oZ(@uyGkk3pSup`xW*)IWlj%%gYgHn8o!uo*m
znGB722WKcV^?ao9yPy0jWM?ui904(}1YVP5I^Gc$8%B>#<2qRV6(z!$#Wnk>hCBU|8yg)C9@S$zTm#yci=c
zhU8`av~18+CXqw9eVVA@a5j^?nY|A0jIN8b;hcjF$>aUKt+OFnk+bN>
z(ls5Uz9fzJPxlv|4S6C^OEVKjnNDnHa!Ei~mob-nZ@U$7h*u86#Jxp?QT0{#Hy!Iy
zmQ-c_%yGr0n?Pji4&V^Ttgca`!
zeUuJFqjz-VTpctphj$oKWg9ygC6j}3cA`g`S<`1*D*TB#jrP2x>l33(8j1I?pw7}9WI><_R3^gU_BO)>yD`ig
z1?J1zcZiKoCnqXOoWgKP{puAZDsI6ldPbgf3L9b
zJKG0l1}1urSSb>uvdD678j&iWiF#Fk)E#yIvWmgzK9o~3y`d*z#>~-S>X!M|F!*GE
z99fpH)%L|muW%i-z)NXO7r=Z#%*@2#PB(}eHs}l&RHHnu#)jI6+|!q4c@}7qQ4g?Y
zuFO8Q)<0Hmi7yYioQD$zYPjvRAx8_Pu%-*8FsoHb+`G4{J307#r0sJ7D5?W%&i+q(
zJTSIhSq)d?jn>hBJwX9o?mO|0JU^=bfa957$ccbsntDP{X%8f1EiOe_VYlc~x|i+Y
zo}cASTP?PO>J(jCBw80YOYpx`f3X1FiEc
zW!+$S7IqQJ%REn?jTAZMF<^x&3Xw6ct*qbK@)1yKEO$??Yr;5Yw(jUv6}zIB@A-m|
z7o5}07bw_;P=M2UvLcjxS4ZyE1((9GO;Jj@GUx_P-%_%`l$o7-SQwa6$Qx$NZ68nm
zBC9%D#5pDOIgV;DcVu1E!>_3(-(m8UlM5(>QrM>By7he@Om$mf3~i|b6{>!2zO{lx
z9{(lR%FqsV_O$}+N=kKE5tl+xmNz`D(O@6>h)6TRWN5ESXi(Zco*kq2wVCwPpb+{u
zAdCEL@0Al<0p1hX3nq44fq)v}1p;=@x_!Tc(%U4p3apQkn!_%tYT>>$ay&7Ginm(l
zc;a0HWibK+6SKzZ6NN5P
zBS($;25>qY!9J7uV!|gAfKvrWwhzcW@jDto&V9vtV%CcB9ANL*oV2y~sw`8{
z9B_MvN4#?GsDJMVobPyI{{_d}?`b8vY^8Gaq@uVBEA77#!uk4*c_%U#$Qy?0NwK^?
zvv2(=^tfB258;C}uvo8_lqKC&RUWG)xFH@PysF9>#yc+9urSYvelW@Z!7H4&Y`)me
zzh(RDh5zyhHtMtk!2oK*=zXe)&SXHj3-Y84Afqwy0JYIt82L3;zGW7XW~!|CEuy
z`mf=Ovzd{Lk^O(pDQsC08st+CyH6=nHVYm@2mV1FDt*fASoe#6^n74
zIr`FA8pZt%H-l8{O9B!{`z@Obg$xBDosb7Z#AwHG=3h#-U)>5#vm3qJPY~!(DdO{D
zb7v#l!x?rq`~0|jcII`n<*}pH?QxUw`Sw!N1Kf(n%2e5#jY%bv6WjIY8FH;=Ukr9>
zwcyT{Z-AeRm{UNgCs@5uv5;93sV+>Q9xHZBZ?5jpRd7JgP>`8&5Op77Y-!i5mEs=r
zSWgByyAm`GX53nIv^E$4Tj4G;{6LPYLJu-KK{q>QKM>^8ztv4{R1BXs>@qPtMX57?
zUkP-Hd;pi}9!lA#!G&~){)78VMb2gV5IM02N8Czj2%
z(ywdV`9|)7jru98Aj_V!Yo>R`LncQ~a>50R*5u5eDh8KTeW<$)Nx$lghvJnz!-mMB
zfjVx7RfSp#?QtT5Rp2U4u9bT5^`;b!iLV8x7@DH7Z?}F*dE1*oR5Sp+aA)usyYebUg?)V-}N2L5-OTcN;R#nr$*n{@{hchcI--Je;@Hy)w`sOz%e}U)A
zhvE*fFdkuBr?uW^THo;M7q3p@vt;-uzp6^e;h(G)Uo|ny3@g^^mXjO903QU~Py4`f
z>SZ2FUN^HsSk20}GO5MAc0U_Ss~CJ1~&15R^qGkct?|UEb50d1^_D@
z&%0HfD00LXA51Y6Ivt>trR1y3NG5Ner0sj`W8n}-qD0;4^ERgA58Sh`S=TFMzEM?8
zs^{nx<-5(?Lb)M`?2mQD>uBXRh}>t3f&7u~gXWRpwtS`Ms)*8@ZVZ#vj~{7q1FB#m
z0|MQ^t%>&6vA_CQDH-vdUayaDl3jn<2LYB5r*WvdJWZO=cu5fe|rVHZ*&LQnsu^
zT#rJUeU8JyqBI9@HY?dc#}8JWoOyvFGZjKtx~I5T5liBV{2FhW(|O~vkCKgvAw4tC
zzK5^_r?P2-ED~z-qrk_<-%Fl_6;v0N+j2oF7get=C=WfouIO%Kndd|x){Vj3{k`w3-}q)X_sC
zl*xz7@CvA#FRJHXt16)!+Hs=Wp>VXvBQb$|@m!I(PGoYUAWjjM+Zf4ws>*YE()&c!
z!oeM4BH{3IGE_qnNS$lB#x`aWO7G7L5?a)HJ*hDg0$Y<86i_G(9VReFECD%y4GK?9
z`kj_VrY}t}IN@-Se}d2vTr4+PF!crt9j;17;euRO8mfvAL907_elACKX3=^8ps@`1&yBhQcVo&~D3*$vK;C<}j
z+X5s0F(!3KD}{xj&hD@9DdmRA!Yd5N%fqf2cwx{UHk#=l9JQ$gEsWwwi|{rTSrGiL
zF+0U(ZilN;7o}v{i{XBbU;SYAWH*cgsarGZlDlc4(fW=;aBT;)Ht7TCxTacW^1vXw
zd&KX&{OiLwl^755QP!&{(C=9w2^L
zVYLPz1YiV%$=T>D^mgKo4Ho(@ydLxXc+-6m<}b2?tgCfpaec{kP6wTnY>(oCs~o-IjJbEZ7aiLK>~wCbP!7RN#iDr?I4-6#`~dF0l#uagKr=673Y9
zPAZ+q)sz%i_sl`{ZJsq3*_F^E1#uvg6fkK63U}NzI66=^>v8!O;oC#oLEYrN`rO@NMGuBc;1)
z;gxmq5Zq+6LCoq0LgTYa;E=@T9TK?;6r$)2fffx$gGDPAQRK9O2=b75@Wppj;jI#X
z3Mb!fwn!Q|2al|MQ?DF@LeHHu?H@1tATwd=c4Sk^Dd;QmWD_POV)ltaM%+cnbiOwh$NKNb?6%)RLZo;rXDUdHZxUkqSC=l1oTtG6APo4rK
zktuEv72Mf3j4q;MU`gGgNqwf&imTkJs>;f}REmHKQ(wV~sd@XQ>WSw^-jD=F5^^Ty0IC1AFxm`wiQpZXhrC0?&_&%jCcIDv
z@~p&(izI~c?G=Uxt6vQ`XW#&zOK#j9U5p84p@C4J3A_*!x>F?C2KLd7!XllcpxhKylW
z@NDeSIwDZgIC!MaB{nseMtv
zlibGH#mlff=e#L5rt-`}?EZmvB$TUNI9c=tt`HoJLECp8k@Dau<>*#^7*gRwYS
zj$0$?IeXWmr(Die(}?B1fpg(NTe7|cWyO4+sUw&b>-7UIa*ptpvyNWIc+Hq7uSZ~+
ztVWG!y5UX77_MHXGPR&^h@)VL0rxYF(s$5V<_=MfWG6NVwJCEQxE0CzojwvRe@IwY
z>-ZqgCF`BMV>+8}0Eu>Rg_LFh&3YX7fz&4iuW_YG3;A;|vi=I6<6=n>i$glfz@}VJ
zjaZ`l{GiOO0+N?5F3OBW?q#Hh+Ahi`=@1#nWZ`v#4m;Sy|Yj#niF4(#2`k>AoE0WI$ujHpf=Lap9
z!Tyf^ywB|phR&ok7t+i=2F^^Hq_ot)+thU^^R6I8ez=onUA66IxZ~^cxq5UN=q-^)
zE7lVQ8<=YDAea=j%ESV57KJIk!bAu&p@Xyh3hUP=+VPP49pYPul&<;G47PMo4NwI1yyrE6!ibM-u2uI4dMA3<>Kuv%T+({LIhF
zDpzXRE2N;|z!20KeDv#1sF=XXI&7qCba)n?kG(jk#{7%{gh95d&3JAB1!
zA4^S3Dyc|}a>r~%rGc+4gT=N45=s6F7Hb
z&pgO_`>jmjzf@S@+Yj7f#S_cLo;KfaSlYVI;+g-PC;nX5yuaBXcE9%gdB()*E*4dr
zyt5QE+Lz>t08@>v__-8M*dShotzv#OC{G!Ztj^xDI6haTEKS>F?^~9{7-<2KKFQl}
z&9@;0iCJgTuQ1!cPGk@A^8r1C8b!*5hJYMJ(+vbpP({uTUSkg$x+miD;{yT}L_sV<
zm;B8ZMUWkiM7B2+KOJABph{v%y69vLRP-^hcS!yV0wkcX7dbhhxw4_Kv$V35n=J?>
ze;LHyAF@($kUg9YeG(QdDAltcvXY~^@)ExjYy#4SAarVUq=>8+_3OGlvPYBVmmu2y`WJ+kYZ$o&oAk2G5%7$fuQH4PKMn_}^kK~xf{n^{yDc##65|QB0`rSJq
zN}D)Efb&cN7?6sf+6%M7?88kX%HBZD7SW>hP&(gVVw-5$evY)RdUJQ_jq7lSE9}G$
z)=o>u8W|&@Ro6hOEv`5CJROAMrR1t3b>0u%D&pa!M8qP*4p+Dqx8UYN1*f5~61>
zTIMcV7;~;=#ciI2N)osIR2Y3$+f)5mcbN}WdnY^MjKM}tkhDvj)N*E?T2o!!fHce$alUOh!p%I
zLtgjwx;cmMIi_0@JRSUxrE6iOiZtbVpPpQS-!A#sU05qpor}Xwc(+4Y=Qib0uQHVK
zP4BIe9Qklm!d!SV6bUK2%&hw}3w?^y{7M)o75IwpV|w;1{N^BL6(f^$rt4)YvA~~}8@=@~w`;f6Bb?)MPR?M$ycu4aGiCa^
zbB~9d7lPmi)Sz{{+WTHLmOH1+-m0iFp3~A{88tm(h(gGfc7z%+839B#&0C^olI)@qp*Po1&+tzr3_;FxNM{nxbZ
zym>nj8bs1lLo8Z}7CG5+Ws^OAR)c91Wn>oyr=Zrn+YpU*I9O&amSG}TNspZ1qg>1B
zez${8zXG#*uRS>CE-YBaPGCT4lVBhn9}T-9b}r_!obvjR8M)Ur
zju9jgV6V0yFH{SbE(=nGGZ5I%eSK;K+Ool9Tc#+LZSpB_yQ
z%CaeDL7(~@hUqO@&yF!Is-YPCp`sQD2n6Ims07IH-9jRtIEj^1cm)m7bgbshH5IJu
z9e*N7syq3AtB!!%n_5abquiMjnHR`}Wy
z%Dx%=8p|b!eq<#pbE`tOo42)#d59UdgF2f^qbMSS(^Uf5!5zXh;pwl<^#r8-K+6mS
zb>+}3eBa1vDGlIKHqp3wz{|TL+`iHn)GGax_rZ_n7T%E;9KOkdKaAsVi~hPiocT!$
zBnAjLX}&(?^a!7$hsG@1$uZqTt)R7Qae%rca04v2YMk6`fGwD?_ef12vCruI63*tP
z=bC-_T$Gw!c#Zcus2H{09s#bV$dj)1;2*{)a0<88fKIcPcJr?wW1S=yIdFpM-l@^j
zW|#}>N(Kzf0Co*b6VA1Bk6@HaBt@4U38_i16lAUt!M`uZ2S^8fZD{V@3e
z=6cS0uK#jC|G)YG!oQCBFPe}3Z8kC}UuMPr%*L6YcSiqXHvZcs|HI+@|LRN<)N|Am
zw$yX}*Hf;dl?=8LvbVAGT9A<`xhz?ga$)Bs1Cr_!+3`q_Ls~Pu9r-Q
zlM^4jPk>#f?a*8-+QvL+w4zp`p$K`&u>do2V|9A0KBLJXdc3_ciTPK1bZ7*}-$=qN
z{*d=V%ti4@+`Z7v-JjT>hSq|E9J>}LS<-UWn{<=s|>7aDPc(z!J_;ASzB&=A#*v5(^=|NKk
zDdvNPDs5KvyHe+e&(~A>%beGAi;Y)Omvr>|rqni(VYdJMa)zROF5iEYCigKy`M^-
zGd!2MA#7$}svnuYVZ8~dVLuVxB%lV$umza*NUNWPZbk_Fqy2^93kJ=!@`4vrpiSad
zFf4EW=hMmJ+NF@fmC|HQUBz)?>949Hvdqo);Dk9Z@6x&p*ZA*S>#xs%S)-EH1*3*V
z_sOL-hU%-`9gG+`9_cGMph&>|J417DbmRI_X~knCQH;8+0|x9c#S34cI_pmi~p^mVcB_I0shz#*I2qZghhfE7iq
zxJ~}i6dg}+Ju~T6!+X3cDdZbnM2Sm_7TbgH
zRG&S>`Q@c|?)B0wA-aqDg;d&b5CWPBdV64=TfH}Y?wi>c19u<6;vb&T^Bjd~%n4#3
z{H1loLlZ?&p@YMwTQ0>Vy@Jak%m?}}R*fva(WfRq7Be`SY(mXEX=@StE9K5^tIe^l
z!!7sDE=Y|kbar7&W?U?lxj+En_`5GXJjx%NsR>G9(Ih?+>AFeg%E*W%Jef_A50le<
z{ZTBhFxyxwC`;D3k2=8ioq+3;C~3-5#!5%ncW7rJ>7l@6eBE7A$05A@gY?QLI5<0w
z!sJ~R2Q(ZtGvn)NtFw&G7{ZOfIY{2X0UAI4M)|KlS1Qr>Hl0&9T;CD6KDBo~_tHKd
z2XvA#CAW>3A&@c0ft+w0*?TJ^%%Kg9=i#?P*`hw&YnyRCn7BdLd1q`Va4FFyOSar4
zm;67j5wwTOap4$2q~hgy*-_z1%mK{!;FBb)|LhSo%d0ZQ{_zLu{0vHh|7x%a*jQQV
zSsRL38{7OF@$99j@ektptC*U~3QhewH#C>5s0bMprOh=@_BVbNh(LdY@p^S_;TG`~
zYM2cP$+p*npU!Rt2|<3?92)K;FUHK}ymMaFn&I(fn#0?)%k<<;&yF_$`j9_(6hu2d
zAyKj(d_Q{Jue*G>0>o-pTNDGtVQ$-lbTq_jiB^0)|GuVz!+03#2@TH>5eChT&`L~p
zrHxd5+Y$z@oS28oep%is^K!cgJvv!4^)d|gf>}9ksqAheC58PUOZl;_ld(vG_%rfs
zwF7pgPdrMk;o@|YQ)YuJVb^h4m)5*0jqfTob@nj}9DyM)BbCktPz-2SAxyp9f1ai1T_PUWsk@XMOG1<;ZLU&PLhBhM
z1(&$uQxw-y%0BTC#fgcddARn^FSFJQ@^;ZiJ)W|i7UaB3u=oHEhoMN%tlSVj7DR#04DEIO3Ir
z&C*Pt`{(SvszPboP~4;e8cpo7{T@gwpPpWrhA-#H5rS8PJmC%OjDAKz#Y6ql*$WSe
z`33v^XA5_|pO;!PPhgSr4iJA{!UG)wCy(|K6gHvSdIe#9O!6J>#l7VMAX~r{2O`BU
zvRC3l;oU=c-htu^6woaUurt45*Kh4P<-X%PRzAJHvMbauLLjJ48ESHwiJ0l1=82
zLE(G^v>^aev(QvplVQSnmjP^yK$igh9sJ*^@qZ+JZs$p6{eD2h;SXrQ|8M?VQa@#$
z|ExH)`iZ?Gds{=S{YqJxN2`=AZ4CY;Mwu&A+Z3!{2ARuG)vQFDkx`p^$&euhtMeEC
zee6tSeOgx7_>z_LK<5_xl@@!Zt0PuLdlKg5XwxD4A=6{JZ9?b!>kHf$Yc+fq7nRAg
zde0lgN;PpH0D?xp4jAn=C|D6hk6hY-+~26u^i@9F6GfQh&qF!Lt2$=o*cGe2D_MvW
zyJy8-T<{1?ms;Gxk$*;Ggpjq-f@g*vUDHs`y|_PC^;=>`u@!OoNCOO^uwROJfw);?
zIg@(CTm>B>sPz$&SedjlP8!a{7IL*2UEs0ggrf~(qtM7Qy1y7FC3?F;F>&(v5bR#%
zeH4(`yL@vWw8bX<(KZ^`mQ=&wY!2GBC*xnrxjF@)Q`#mb?C5M$Qa3Qcqg(IJRfc{t
z&f*S-8vRf!GyYqI0x%}cduDW+mRbpih?5}33!#Sn&`ewGxTh`o*n~NC$`$&m&&$;U
zgD@I(_KaR-byen#Nrx1Z%X))QM!w((W4X#}%ZfrKzl9~yW@_=0VMqZqP`4J@(qJ@v
zzaE2B-e@PU#}fJ@6MyzM4(W$HjG;?J+Ib^Q2JAj9{HTQHBdCck8FSD*y2-|IEsLI~
z;zQjlACue2UXCr-bs1vfUC%`sF98k#?F2?mPL|O3YdA$~22Cq2)%q>a4of484?@{C
z{v#$6YN+qP}n
zNyWBpqhj0Eif!ZOZD*fz_dVbK?vJ_J`a4GJqt7||BlM7R3dv@Le3qv~tYH@?Xalj~
zr)U0QVraO{VoFKJ2EFSG3J4gjVNhrob!;HPg&|U>zcDTLtEy#EO7^7Xe&5^x>u*@k
z*{W{=e)x@pN9J__i5q#zq-{826L2YXVAw+U{$&OtqkA3}yqET?mF+N4IF=pAs
z(RsekH(u64TcBc&LpCido-$(LU5OW_2KB+)>3m?=dS^v2-^L0OFdSuvN~X2GdZPn@
zEIf9oUd8}tUCwqT)-4%227Q#iXc9ZDorz)}<*w$dFEtl}x?eZ7U8m1EtUgswyHf`RlPs+ZO
zzVmDfmKxCn*fDrZWIoMXNCDtWK;IqOl8vA4<3GiEe0jDe`t&O4#3L9+
z
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