From 0fe89b5a6919283d7e3bc5c76910f0433572c93c Mon Sep 17 00:00:00 2001
From: wk333 <13474090681@163.com>
Date: Wed, 29 Dec 2021 18:42:20 +0800
Subject: [PATCH] Update to 1.2.4
---
backport-CVE-2020-7226-1.patch | 1261 --------------------------------
backport-CVE-2020-7226-2.patch | 81 --
backport-CVE-2020-7226-3.patch | 22 -
change-version-to-Java8.patch | 37 -
cryptacular.spec | 11 +-
v1.1.0.tar.gz | Bin 157482 -> 0 bytes
v1.2.4.tar.gz | Bin 0 -> 173407 bytes
7 files changed, 5 insertions(+), 1407 deletions(-)
delete mode 100644 backport-CVE-2020-7226-1.patch
delete mode 100644 backport-CVE-2020-7226-2.patch
delete mode 100644 backport-CVE-2020-7226-3.patch
delete mode 100644 change-version-to-Java8.patch
delete mode 100644 v1.1.0.tar.gz
create mode 100644 v1.2.4.tar.gz
diff --git a/backport-CVE-2020-7226-1.patch b/backport-CVE-2020-7226-1.patch
deleted file mode 100644
index 679fffd..0000000
--- a/backport-CVE-2020-7226-1.patch
+++ /dev/null
@@ -1,1261 +0,0 @@
-From 8c6c7528f1e24c6b71f3e36db0cb8a697256ce25 Mon Sep 17 00:00:00 2001
-From: "Marvin S. Addison"
-Date: Tue, 21 Jan 2020 16:59:39 -0500
-Subject: [PATCH] Define new ciphertext header format.
-
-New format does not allocate any memory until HMAC check passes, which
-guards against untrusted input. All encryption components have been
-updated to use the new header, while preserving backward compatibility
-to decrypt messages encrypted with the old format. The decoding process
-for the old header has been hardened to impose reasonable limits on header
-fields: nonce sizes up to 255 bytes, key names up to 500 bytes.
-
-Fixes #52.
----
- .../org/cryptacular/CiphertextHeader.java | 65 +++-
- .../org/cryptacular/CiphertextHeaderV2.java | 307 ++++++++++++++++++
- .../bean/AbstractBlockCipherBean.java | 10 +-
- .../cryptacular/bean/AbstractCipherBean.java | 31 +-
- .../java/org/cryptacular/util/ByteUtil.java | 45 ++-
- .../java/org/cryptacular/util/CipherUtil.java | 90 +++--
- .../org/cryptacular/CiphertextHeaderTest.java | 55 ++++
- .../cryptacular/CiphertextHeaderV2Test.java | 67 ++++
- .../bean/AEADBlockCipherBeanTest.java | 65 ++--
- .../org/cryptacular/util/CipherUtilTest.java | 47 +++
- 10 files changed, 707 insertions(+), 75 deletions(-)
- create mode 100644 src/main/java/org/cryptacular/CiphertextHeaderV2.java
- create mode 100644 src/test/java/org/cryptacular/CiphertextHeaderTest.java
- create mode 100644 src/test/java/org/cryptacular/CiphertextHeaderV2Test.java
-
-diff --git a/src/main/java/org/cryptacular/CiphertextHeader.java b/src/main/java/org/cryptacular/CiphertextHeader.java
-index 93623c9..c17e735 100644
---- a/src/main/java/org/cryptacular/CiphertextHeader.java
-+++ b/src/main/java/org/cryptacular/CiphertextHeader.java
-@@ -34,18 +34,26 @@
- * decrypt outstanding data which will be subsequently re-encrypted with a new key.
- *
- * @author Middleware Services
-+ *
-+ * @deprecated Superseded by {@link CiphertextHeaderV2}
- */
-+@Deprecated
- public class CiphertextHeader
- {
-+ /** Maximum nonce length in bytes. */
-+ protected static final int MAX_NONCE_LEN = 255;
-+
-+ /** Maximum key name length in bytes. */
-+ protected static final int MAX_KEYNAME_LEN = 500;
-
- /** Header nonce field value. */
-- private final byte[] nonce;
-+ protected final byte[] nonce;
-
- /** Header key name field value. */
-- private String keyName;
-+ protected final String keyName;
-
- /** Header length in bytes. */
-- private int length;
-+ protected final int length;
-
-
- /**
-@@ -67,12 +75,17 @@ public CiphertextHeader(final byte[] nonce)
- */
- public CiphertextHeader(final byte[] nonce, final String keyName)
- {
-- this.nonce = nonce;
-- this.length = 8 + nonce.length;
-+ if (nonce.length > 255) {
-+ throw new IllegalArgumentException("Nonce exceeds size limit in bytes (255)");
-+ }
- if (keyName != null) {
-- this.length += 4 + keyName.getBytes().length;
-- this.keyName = keyName;
-+ if (ByteUtil.toBytes(keyName).length > MAX_KEYNAME_LEN) {
-+ throw new IllegalArgumentException("Key name exceeds size limit in bytes (500)");
-+ }
- }
-+ this.nonce = nonce;
-+ this.keyName = keyName;
-+ length = computeLength();
- }
-
- /**
-@@ -127,6 +140,19 @@ public String getKeyName()
- }
-
-
-+ /**
-+ * @return Length of this header encoded as bytes.
-+ */
-+ protected int computeLength()
-+ {
-+ int len = 8 + nonce.length;
-+ if (keyName != null) {
-+ len += 4 + keyName.getBytes().length;
-+ }
-+ return len;
-+ }
-+
-+
- /**
- * Creates a header from encrypted data containing a cleartext header prepended to the start.
- *
-@@ -143,17 +169,20 @@ public static CiphertextHeader decode(final byte[] data) throws EncodingExceptio
-
- final int length = bb.getInt();
- if (length < 0) {
-- throw new EncodingException("Invalid ciphertext header length: " + length);
-+ throw new EncodingException("Bad ciphertext header");
- }
-
- final byte[] nonce;
- int nonceLen = 0;
- try {
- nonceLen = bb.getInt();
-+ if (nonceLen > MAX_NONCE_LEN) {
-+ throw new EncodingException("Bad ciphertext header: maximum nonce length exceeded");
-+ }
- nonce = new byte[nonceLen];
- bb.get(nonce);
- } catch (IndexOutOfBoundsException | BufferUnderflowException e) {
-- throw new EncodingException("Invalid nonce length: " + nonceLen);
-+ throw new EncodingException("Bad ciphertext header");
- }
-
- String keyName = null;
-@@ -162,11 +191,14 @@ public static CiphertextHeader decode(final byte[] data) throws EncodingExceptio
- int keyLen = 0;
- try {
- keyLen = bb.getInt();
-+ if (keyLen > MAX_KEYNAME_LEN) {
-+ throw new EncodingException("Bad ciphertext header: maximum key length exceeded");
-+ }
- b = new byte[keyLen];
- bb.get(b);
- keyName = new String(b);
- } catch (IndexOutOfBoundsException | BufferUnderflowException e) {
-- throw new EncodingException("Invalid key length: " + keyLen);
-+ throw new EncodingException("Bad ciphertext header");
- }
- }
-
-@@ -188,17 +220,20 @@ public static CiphertextHeader decode(final InputStream input) throws EncodingEx
- {
- final int length = ByteUtil.readInt(input);
- if (length < 0) {
-- throw new EncodingException("Invalid ciphertext header length: " + length);
-+ throw new EncodingException("Bad ciphertext header");
- }
-
- final byte[] nonce;
- int nonceLen = 0;
- try {
- nonceLen = ByteUtil.readInt(input);
-+ if (nonceLen > MAX_NONCE_LEN) {
-+ throw new EncodingException("Bad ciphertext header: maximum nonce size exceeded");
-+ }
- nonce = new byte[nonceLen];
- input.read(nonce);
- } catch (ArrayIndexOutOfBoundsException e) {
-- throw new EncodingException("Invalid nonce length: " + nonceLen);
-+ throw new EncodingException("Bad ciphertext header");
- } catch (IOException e) {
- throw new StreamException(e);
- }
-@@ -209,10 +244,13 @@ public static CiphertextHeader decode(final InputStream input) throws EncodingEx
- int keyLen = 0;
- try {
- keyLen = ByteUtil.readInt(input);
-+ if (keyLen > MAX_KEYNAME_LEN) {
-+ throw new EncodingException("Bad ciphertext header: maximum key length exceeded");
-+ }
- b = new byte[keyLen];
- input.read(b);
- } catch (ArrayIndexOutOfBoundsException e) {
-- throw new EncodingException("Invalid key length: " + keyLen);
-+ throw new EncodingException("Bad ciphertext header");
- } catch (IOException e) {
- throw new StreamException(e);
- }
-@@ -221,4 +259,5 @@ public static CiphertextHeader decode(final InputStream input) throws EncodingEx
-
- return new CiphertextHeader(nonce, keyName);
- }
-+
- }
-diff --git a/src/main/java/org/cryptacular/CiphertextHeaderV2.java b/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-new file mode 100644
-index 0000000..8119f4e
---- /dev/null
-+++ b/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-@@ -0,0 +1,307 @@
-+/* See LICENSE for licensing and NOTICE for copyright. */
-+package org.cryptacular;
-+
-+import java.io.ByteArrayOutputStream;
-+import java.io.IOException;
-+import java.io.InputStream;
-+import java.nio.BufferUnderflowException;
-+import java.nio.ByteBuffer;
-+import java.nio.ByteOrder;
-+import java.util.function.BiConsumer;
-+import java.util.function.Function;
-+import javax.crypto.SecretKey;
-+import org.bouncycastle.crypto.digests.SHA256Digest;
-+import org.bouncycastle.crypto.macs.HMac;
-+import org.cryptacular.util.ByteUtil;
-+
-+/**
-+ * Cleartext header prepended to ciphertext providing data required for decryption.
-+ *
-+ * Data format:
-+ *
-+ *
-+ +---------+---------+---+----------+-------+------+
-+ | Version | KeyName | 0 | NonceLen | Nonce | HMAC |
-+ +---------+---------+---+----------+-------+------+
-+ | |
-+ +--- 4 ---+--- x ---+ 1 +--- 1 ----+-- y --+- 32 -+
-+ *
-+ *
-+ * Where fields are defined as follows:
-+ *
-+ *
-+ * Version - Header version format as a negative number (4-byte integer). Current version is -2.
-+ * KeyName - Symbolic key name encoded as UTF-8 bytes (variable length)
-+ * 0 - Null byte signifying the end of the symbolic key name
-+ * NonceLen - Nonce length in bytes (1-byte unsigned integer)
-+ * Nonce - Nonce bytes (variable length)
-+ * HMAC - HMAC-256 over preceding fields (32 bytes)
-+ *
-+ *
-+ * The last two fields provide support for multiple keys at the encryption provider. A common case for multiple
-+ * keys is key rotation; by tagging encrypted data with a key name, an old key may be retrieved by name to decrypt
-+ * outstanding data which will be subsequently re-encrypted with a new key.
-+ *
-+ * @author Middleware Services
-+ */
-+public class CiphertextHeaderV2 extends CiphertextHeader
-+{
-+ /** Header version format. */
-+ private static final int VERSION = -2;
-+
-+ /** Size of HMAC algorithm output in bytes. */
-+ private static final int HMAC_SIZE = 32;
-+
-+ /** Function to resolve a key from a symbolic key name. */
-+ private Function keyLookup;
-+
-+
-+ /**
-+ * Creates a new instance with a nonce and named key.
-+ *
-+ * @param nonce Nonce bytes.
-+ * @param keyName Key name.
-+ */
-+ public CiphertextHeaderV2(final byte[] nonce, final String keyName)
-+ {
-+ super(nonce, keyName);
-+ if (keyName == null || keyName.isEmpty()) {
-+ throw new IllegalArgumentException("Key name is required");
-+ }
-+ }
-+
-+
-+ /**
-+ * Sets the function to resolve keys from {@link #keyName}.
-+ *
-+ * @param keyLookup Key lookup function.
-+ */
-+ public void setKeyLookup(final Function keyLookup)
-+ {
-+ this.keyLookup = keyLookup;
-+ }
-+
-+
-+ @Override
-+ public byte[] encode()
-+ {
-+ final SecretKey key = keyLookup != null ? keyLookup.apply(keyName) : null;
-+ if (key == null) {
-+ throw new IllegalStateException("Could not resolve secret key to generate header HMAC");
-+ }
-+ return encode(key);
-+ }
-+
-+
-+ /**
-+ * Encodes the header into bytes.
-+ *
-+ * @param hmacKey Key used to generate header HMAC.
-+ *
-+ * @return Byte representation of header.
-+ */
-+ public byte[] encode(final SecretKey hmacKey)
-+ {
-+ final ByteBuffer bb = ByteBuffer.allocate(length);
-+ bb.order(ByteOrder.BIG_ENDIAN);
-+ bb.putInt(VERSION);
-+ bb.put(ByteUtil.toBytes(keyName));
-+ bb.put((byte) 0);
-+ bb.put(ByteUtil.toUnsignedByte(nonce.length));
-+ bb.put(nonce);
-+ if (hmacKey != null) {
-+ final byte[] hmac = hmac(bb.array(), 0, bb.limit() - HMAC_SIZE);
-+ bb.put(hmac);
-+ }
-+ return bb.array();
-+ }
-+
-+
-+ /**
-+ * @return Length of this header encoded as bytes.
-+ */
-+ protected int computeLength()
-+ {
-+ return 4 + ByteUtil.toBytes(keyName).length + 2 + nonce.length + HMAC_SIZE;
-+ }
-+
-+
-+ /**
-+ * Creates a header from encrypted data containing a cleartext header prepended to the start.
-+ *
-+ * @param data Encrypted data with prepended header data.
-+ * @param keyLookup Function used to look up the secret key from the symbolic key name in the header.
-+ *
-+ * @return Decoded header.
-+ *
-+ * @throws EncodingException when ciphertext header cannot be decoded.
-+ */
-+ public static CiphertextHeaderV2 decode(final byte[] data, final Function keyLookup)
-+ throws EncodingException
-+ {
-+ final ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
-+ return decodeInternal(
-+ ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN),
-+ keyLookup,
-+ ByteBuffer -> bb.getInt(),
-+ ByteBuffer -> bb.get(),
-+ (ByteBuffer, output) -> bb.get(output));
-+ }
-+
-+
-+ /**
-+ * Creates a header from encrypted data containing a cleartext header prepended to the start.
-+ *
-+ * @param input Input stream that is positioned at the start of ciphertext header data.
-+ * @param keyLookup Function used to look up the secret key from the symbolic key name in the header.
-+ *
-+ * @return Decoded header.
-+ *
-+ * @throws EncodingException when ciphertext header cannot be decoded.
-+ * @throws StreamException on stream IO errors.
-+ */
-+ public static CiphertextHeaderV2 decode(final InputStream input, final Function keyLookup)
-+ throws EncodingException, StreamException
-+ {
-+ return decodeInternal(
-+ input, keyLookup, ByteUtil::readInt, CiphertextHeaderV2::readByte, CiphertextHeaderV2::readInto);
-+ }
-+
-+
-+ /**
-+ * Internal header decoding routine.
-+ *
-+ * @param Type of input source.
-+ * @param source Source of header data (input stream or byte buffer).
-+ * @param keyLookup Function to look up key from symbolic key name in header.
-+ * @param readIntFn Function that produces a 4-byte integer from the input source.
-+ * @param readByteFn Function that produces a byte from the input source.
-+ * @param readBytesConsumer Function that fills a byte array from the input source.
-+ *
-+ * @return Decoded header.
-+ */
-+ private static CiphertextHeaderV2 decodeInternal(
-+ final T source,
-+ final Function keyLookup,
-+ final Function readIntFn,
-+ final Function readByteFn,
-+ final BiConsumer readBytesConsumer)
-+ {
-+ final SecretKey key;
-+ final String keyName;
-+ final byte[] nonce;
-+ final byte[] hmac;
-+ try {
-+ final int version = readIntFn.apply(source);
-+ if (version != VERSION) {
-+ throw new EncodingException("Unsupported ciphertext header version");
-+ }
-+ final ByteArrayOutputStream out = new ByteArrayOutputStream(100);
-+ byte b = 0;
-+ int count = 0;
-+ while ((b = readByteFn.apply(source)) != 0) {
-+ out.write(b);
-+ if (out.size() > MAX_KEYNAME_LEN) {
-+ throw new EncodingException("Bad ciphertext header: maximum nonce length exceeded");
-+ }
-+ count++;
-+ }
-+ keyName = ByteUtil.toString(out.toByteArray(), 0, count);
-+ key = keyLookup.apply(keyName);
-+ if (key == null) {
-+ throw new IllegalStateException("Symbolic key name mentioned in header was not found");
-+ }
-+ final int nonceLen = ByteUtil.toInt(readByteFn.apply(source));
-+ nonce = new byte[nonceLen];
-+ readBytesConsumer.accept(source, nonce);
-+ hmac = new byte[HMAC_SIZE];
-+ readBytesConsumer.accept(source, hmac);
-+ } catch (IndexOutOfBoundsException | BufferUnderflowException e) {
-+ throw new EncodingException("Bad ciphertext header");
-+ }
-+ final CiphertextHeaderV2 header = new CiphertextHeaderV2(nonce, keyName);
-+ final byte[] encoded = header.encode(key);
-+ if (!arraysEqual(hmac, 0, encoded, encoded.length - HMAC_SIZE, HMAC_SIZE)) {
-+ throw new EncodingException("Ciphertext header HMAC verification failed");
-+ }
-+ header.setKeyLookup(keyLookup);
-+ return header;
-+ }
-+
-+
-+ /**
-+ * Generates an HMAC-256 over the given input byte array.
-+ *
-+ * @param input Input bytes.
-+ * @param offset Starting position in input byte array.
-+ * @param length Number of bytes in input to consume.
-+ *
-+ * @return HMAC as byte array.
-+ */
-+ private static byte[] hmac(final byte[] input, final int offset, final int length)
-+ {
-+ final HMac hmac = new HMac(new SHA256Digest());
-+ final byte[] output = new byte[HMAC_SIZE];
-+ hmac.update(input, offset, length);
-+ hmac.doFinal(output, 0);
-+ return output;
-+ }
-+
-+
-+ /**
-+ * Read output.length bytes from the input stream into the output buffer.
-+ *
-+ * @param input Input stream.
-+ * @param output Output buffer.
-+ */
-+ private static void readInto(final InputStream input, final byte[] output)
-+ {
-+ try {
-+ input.read(output);
-+ } catch (IOException e) {
-+ throw new StreamException(e);
-+ }
-+ }
-+
-+
-+ /**
-+ * Read a single byte from the input stream.
-+ *
-+ * @param input Input stream.
-+ *
-+ * @return Byte read from input stream.
-+ */
-+ private static byte readByte(final InputStream input)
-+ {
-+ try {
-+ return (byte) input.read();
-+ } catch (IOException e) {
-+ throw new StreamException(e);
-+ }
-+ }
-+
-+
-+ /**
-+ * Determines if two byte array ranges are equal bytewise.
-+ *
-+ * @param a First array to compare.
-+ * @param aOff Offset into first array.
-+ * @param b Second array to compare.
-+ * @param bOff Offset into second array.
-+ * @param length Number of bytes to compare.
-+ *
-+ * @return True if every byte in the given range is equal, false otherwise.
-+ */
-+ private static boolean arraysEqual(final byte[] a, final int aOff, final byte[] b, final int bOff, final int length)
-+ {
-+ if (length + aOff > a.length || length + bOff > b.length) {
-+ return false;
-+ }
-+ for (int i = 0; i < length; i++) {
-+ if (a[i + aOff] != b[i + bOff]) {
-+ return false;
-+ }
-+ }
-+ return true;
-+ }
-+}
-diff --git a/src/main/java/org/cryptacular/bean/AbstractBlockCipherBean.java b/src/main/java/org/cryptacular/bean/AbstractBlockCipherBean.java
-index 0cd6542..0d06b32 100644
---- a/src/main/java/org/cryptacular/bean/AbstractBlockCipherBean.java
-+++ b/src/main/java/org/cryptacular/bean/AbstractBlockCipherBean.java
-@@ -45,12 +45,12 @@ public AbstractBlockCipherBean(
- protected byte[] process(final CiphertextHeader header, final boolean mode, final byte[] input)
- {
- final BlockCipherAdapter cipher = newCipher(header, mode);
-- final byte[] headerBytes = header.encode();
- int outOff;
- final int inOff;
- final int length;
- final byte[] output;
- if (mode) {
-+ final byte[] headerBytes = header.encode();
- final int outSize = headerBytes.length + cipher.getOutputSize(input.length);
- output = new byte[outSize];
- System.arraycopy(headerBytes, 0, output, 0, headerBytes.length);
-@@ -58,12 +58,12 @@ public AbstractBlockCipherBean(
- outOff = headerBytes.length;
- length = input.length;
- } else {
-- length = input.length - headerBytes.length;
-+ outOff = 0;
-+ inOff = header.getLength();
-+ length = input.length - inOff;
-
- final int outSize = cipher.getOutputSize(length);
- output = new byte[outSize];
-- inOff = headerBytes.length;
-- outOff = 0;
- }
- outOff += cipher.processBytes(input, inOff, length, output, outOff);
- outOff += cipher.doFinal(output, outOff);
-@@ -85,7 +85,7 @@ protected void process(
- {
- final BlockCipherAdapter cipher = newCipher(header, mode);
- final int outSize = cipher.getOutputSize(StreamUtil.CHUNK_SIZE);
-- final byte[] outBuf = new byte[outSize > StreamUtil.CHUNK_SIZE ? outSize : StreamUtil.CHUNK_SIZE];
-+ final byte[] outBuf = new byte[Math.max(outSize, StreamUtil.CHUNK_SIZE)];
- StreamUtil.pipeAll(
- input,
- output,
-diff --git a/src/main/java/org/cryptacular/bean/AbstractCipherBean.java b/src/main/java/org/cryptacular/bean/AbstractCipherBean.java
-index f82a259..fd73763 100644
---- a/src/main/java/org/cryptacular/bean/AbstractCipherBean.java
-+++ b/src/main/java/org/cryptacular/bean/AbstractCipherBean.java
-@@ -8,14 +8,16 @@
- import java.security.KeyStore;
- import javax.crypto.SecretKey;
- import org.cryptacular.CiphertextHeader;
-+import org.cryptacular.CiphertextHeaderV2;
- import org.cryptacular.CryptoException;
- import org.cryptacular.EncodingException;
- import org.cryptacular.StreamException;
- import org.cryptacular.generator.Nonce;
-+import org.cryptacular.util.CipherUtil;
-
- /**
- * Base class for all cipher beans. The base class assumes all ciphertext output will contain a prepended {@link
-- * CiphertextHeader} containing metadata that facilitates decryption.
-+ * CiphertextHeaderV2} containing metadata that facilitates decryption.
- *
- * @author Middleware Services
- */
-@@ -128,14 +130,14 @@ public void setNonce(final Nonce nonce)
- @Override
- public byte[] encrypt(final byte[] input) throws CryptoException
- {
-- return process(new CiphertextHeader(nonce.generate(), keyAlias), true, input);
-+ return process(header(), true, input);
- }
-
-
- @Override
- public void encrypt(final InputStream input, final OutputStream output) throws CryptoException, StreamException
- {
-- final CiphertextHeader header = new CiphertextHeader(nonce.generate(), keyAlias);
-+ final CiphertextHeaderV2 header = header();
- try {
- output.write(header.encode());
- } catch (IOException e) {
-@@ -148,11 +150,7 @@ public void encrypt(final InputStream input, final OutputStream output) throws C
- @Override
- public byte[] decrypt(final byte[] input) throws CryptoException, EncodingException
- {
-- final CiphertextHeader header = CiphertextHeader.decode(input);
-- if (header.getKeyName() == null) {
-- throw new CryptoException("Ciphertext header does not contain required key");
-- }
-- return process(header, false, input);
-+ return process(CipherUtil.decodeHeader(input, this::lookupKey), false, input);
- }
-
-
-@@ -160,11 +158,7 @@ public void encrypt(final InputStream input, final OutputStream output) throws C
- public void decrypt(final InputStream input, final OutputStream output)
- throws CryptoException, EncodingException, StreamException
- {
-- final CiphertextHeader header = CiphertextHeader.decode(input);
-- if (header.getKeyName() == null) {
-- throw new CryptoException("Ciphertext header does not contain required key");
-- }
-- process(header, false, input, output);
-+ process(CipherUtil.decodeHeader(input, this::lookupKey), false, input, output);
- }
-
-
-@@ -211,4 +205,15 @@ protected SecretKey lookupKey(final String alias)
- * @param output Stream that receives output of cipher.
- */
- protected abstract void process(CiphertextHeader header, boolean mode, InputStream input, OutputStream output);
-+
-+
-+ /**
-+ * @return New ciphertext header for a pending encryption or decryption operation performed by this instance.
-+ */
-+ private CiphertextHeaderV2 header()
-+ {
-+ final CiphertextHeaderV2 header = new CiphertextHeaderV2(nonce.generate(), keyAlias);
-+ header.setKeyLookup(this::lookupKey);
-+ return header;
-+ }
- }
-diff --git a/src/main/java/org/cryptacular/util/ByteUtil.java b/src/main/java/org/cryptacular/util/ByteUtil.java
-index 541fbf7..2163639 100644
---- a/src/main/java/org/cryptacular/util/ByteUtil.java
-+++ b/src/main/java/org/cryptacular/util/ByteUtil.java
-@@ -31,7 +31,7 @@ private ByteUtil() {}
- *
- * @param data 4-byte array in big-endian format.
- *
-- * @return Long integer value.
-+ * @return Integer value.
- */
- public static int toInt(final byte[] data)
- {
-@@ -39,6 +39,19 @@ public static int toInt(final byte[] data)
- }
-
-
-+ /**
-+ * Converts an unsigned byte into an integer.
-+ *
-+ * @param unsigned Unsigned byte.
-+ *
-+ * @return Integer value.
-+ */
-+ public static int toInt(final byte unsigned)
-+ {
-+ return 0x000000FF & unsigned;
-+ }
-+
-+
- /**
- * Reads 4-bytes from the input stream and converts to a 32-bit integer.
- *
-@@ -175,6 +188,21 @@ public static String toString(final byte[] bytes)
- }
-
-
-+ /**
-+ * Converts a portion of a byte array into a string in the UTF-8 character set.
-+ *
-+ * @param bytes Byte array to convert.
-+ * @param offset Offset into byte array where string content begins.
-+ * @param length Total number of bytes to convert.
-+ *
-+ * @return UTF-8 string representation of bytes.
-+ */
-+ public static String toString(final byte[] bytes, final int offset, final int length)
-+ {
-+ return new String(bytes, offset, length, DEFAULT_CHARSET);
-+ }
-+
-+
- /**
- * Converts a byte buffer into a string in the UTF-8 character set.
- *
-@@ -226,6 +254,19 @@ public static ByteBuffer toByteBuffer(final String s)
- }
-
-
-+ /**
-+ * Converts an integer into an unsigned byte. All bits above 1 byte are truncated.
-+ *
-+ * @param b Integer value.
-+ *
-+ * @return Unsigned byte as a byte.
-+ */
-+ public static byte toUnsignedByte(final int b)
-+ {
-+ return (byte) (0x000000FF & b);
-+ }
-+
-+
- /**
- * Converts a byte buffer into a byte array.
- *
-@@ -244,4 +285,6 @@ public static ByteBuffer toByteBuffer(final String s)
- buffer.get(array);
- return array;
- }
-+
-+
- }
-diff --git a/src/main/java/org/cryptacular/util/CipherUtil.java b/src/main/java/org/cryptacular/util/CipherUtil.java
-index d460039..cdbac0d 100644
---- a/src/main/java/org/cryptacular/util/CipherUtil.java
-+++ b/src/main/java/org/cryptacular/util/CipherUtil.java
-@@ -4,6 +4,7 @@
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
-+import java.util.function.Function;
- import javax.crypto.SecretKey;
- import org.bouncycastle.crypto.BlockCipher;
- import org.bouncycastle.crypto.modes.AEADBlockCipher;
-@@ -13,6 +14,7 @@
- import org.bouncycastle.crypto.params.KeyParameter;
- import org.bouncycastle.crypto.params.ParametersWithIV;
- import org.cryptacular.CiphertextHeader;
-+import org.cryptacular.CiphertextHeaderV2;
- import org.cryptacular.CryptoException;
- import org.cryptacular.EncodingException;
- import org.cryptacular.StreamException;
-@@ -37,15 +39,15 @@ private CipherUtil() {}
-
-
- /**
-- * Encrypts data using an AEAD cipher. A {@link CiphertextHeader} is prepended to the resulting ciphertext and used as
-- * AAD (Additional Authenticated Data) passed to the AEAD cipher.
-+ * Encrypts data using an AEAD cipher. A {@link CiphertextHeaderV2} is prepended to the resulting ciphertext and
-+ * used as AAD (Additional Authenticated Data) passed to the AEAD cipher.
- *
- * @param cipher AEAD cipher.
- * @param key Encryption key.
- * @param nonce Nonce generator.
- * @param data Plaintext data to be encrypted.
- *
-- * @return Concatenation of encoded {@link CiphertextHeader} and encrypted data that completely fills the returned
-+ * @return Concatenation of encoded {@link CiphertextHeaderV2} and encrypted data that completely fills the returned
- * byte array.
- *
- * @throws CryptoException on encryption errors.
-@@ -54,22 +56,22 @@ private CipherUtil() {}
- throws CryptoException
- {
- final byte[] iv = nonce.generate();
-- final byte[] header = new CiphertextHeader(iv).encode();
-+ final byte[] header = new CiphertextHeaderV2(iv, "1").encode(key);
- cipher.init(true, new AEADParameters(new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, iv, header));
- return encrypt(new AEADBlockCipherAdapter(cipher), header, data);
- }
-
-
- /**
-- * Encrypts data using an AEAD cipher. A {@link CiphertextHeader} is prepended to the resulting ciphertext and used as
-- * AAD (Additional Authenticated Data) passed to the AEAD cipher.
-+ * Encrypts data using an AEAD cipher. A {@link CiphertextHeaderV2} is prepended to the resulting ciphertext and used
-+ * as AAD (Additional Authenticated Data) passed to the AEAD cipher.
- *
- * @param cipher AEAD cipher.
- * @param key Encryption key.
- * @param nonce Nonce generator.
- * @param input Input stream containing plaintext data.
-- * @param output Output stream that receives a {@link CiphertextHeader} followed by ciphertext data produced by the
-- * AEAD cipher in encryption mode.
-+ * @param output Output stream that receives a {@link CiphertextHeaderV2} followed by ciphertext data produced by
-+ * the AEAD cipher in encryption mode.
- *
- * @throws CryptoException on encryption errors.
- * @throws StreamException on IO errors.
-@@ -83,7 +85,7 @@ public static void encrypt(
- throws CryptoException, StreamException
- {
- final byte[] iv = nonce.generate();
-- final byte[] header = new CiphertextHeader(iv).encode();
-+ final byte[] header = new CiphertextHeaderV2(iv, "1").encode(key);
- cipher.init(true, new AEADParameters(new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, iv, header));
- writeHeader(header, output);
- process(new AEADBlockCipherAdapter(cipher), input, output);
-@@ -95,7 +97,7 @@ public static void encrypt(
- *
- * @param cipher AEAD cipher.
- * @param key Encryption key.
-- * @param data Ciphertext data containing a prepended {@link CiphertextHeader}. The header is treated as AAD input
-+ * @param data Ciphertext data containing a prepended {@link CiphertextHeaderV2}. The header is treated as AAD input
- * to the cipher that is verified during decryption.
- *
- * @return Decrypted data that completely fills the returned byte array.
-@@ -106,7 +108,7 @@ public static void encrypt(
- public static byte[] decrypt(final AEADBlockCipher cipher, final SecretKey key, final byte[] data)
- throws CryptoException, EncodingException
- {
-- final CiphertextHeader header = CiphertextHeader.decode(data);
-+ final CiphertextHeader header = decodeHeader(data, String -> key);
- final byte[] nonce = header.getNonce();
- final byte[] hbytes = header.encode();
- cipher.init(false, new AEADParameters(new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, nonce, hbytes));
-@@ -119,7 +121,7 @@ public static void encrypt(
- *
- * @param cipher AEAD cipher.
- * @param key Encryption key.
-- * @param input Input stream containing a {@link CiphertextHeader} followed by ciphertext data. The header is
-+ * @param input Input stream containing a {@link CiphertextHeaderV2} followed by ciphertext data. The header is
- * treated as AAD input to the cipher that is verified during decryption.
- * @param output Output stream that receives plaintext produced by block cipher in decryption mode.
- *
-@@ -134,7 +136,7 @@ public static void decrypt(
- final OutputStream output)
- throws CryptoException, EncodingException, StreamException
- {
-- final CiphertextHeader header = CiphertextHeader.decode(input);
-+ final CiphertextHeader header = decodeHeader(input, String -> key);
- final byte[] nonce = header.getNonce();
- final byte[] hbytes = header.encode();
- cipher.init(false, new AEADParameters(new KeyParameter(key.getEncoded()), MAC_SIZE_BITS, nonce, hbytes));
-@@ -143,7 +145,7 @@ public static void decrypt(
-
-
- /**
-- * Encrypts data using the given block cipher with PKCS5 padding. A {@link CiphertextHeader} is prepended to the
-+ * Encrypts data using the given block cipher with PKCS5 padding. A {@link CiphertextHeaderV2} is prepended to the
- * resulting ciphertext.
- *
- * @param cipher Block cipher.
-@@ -152,7 +154,7 @@ public static void decrypt(
- * cipher block size.
- * @param data Plaintext data to be encrypted.
- *
-- * @return Concatenation of encoded {@link CiphertextHeader} and encrypted data that completely fills the returned
-+ * @return Concatenation of encoded {@link CiphertextHeaderV2} and encrypted data that completely fills the returned
- * byte array.
- *
- * @throws CryptoException on encryption errors.
-@@ -161,7 +163,7 @@ public static void decrypt(
- throws CryptoException
- {
- final byte[] iv = nonce.generate();
-- final byte[] header = new CiphertextHeader(iv).encode();
-+ final byte[] header = new CiphertextHeaderV2(iv, "1").encode(key);
- final PaddedBufferedBlockCipher padded = new PaddedBufferedBlockCipher(cipher, new PKCS7Padding());
- padded.init(true, new ParametersWithIV(new KeyParameter(key.getEncoded()), iv));
- return encrypt(new BufferedBlockCipherAdapter(padded), header, data);
-@@ -191,7 +193,7 @@ public static void encrypt(
- throws CryptoException, StreamException
- {
- final byte[] iv = nonce.generate();
-- final byte[] header = new CiphertextHeader(iv).encode();
-+ final byte[] header = new CiphertextHeaderV2(iv, "1").encode(key);
- final PaddedBufferedBlockCipher padded = new PaddedBufferedBlockCipher(cipher, new PKCS7Padding());
- padded.init(true, new ParametersWithIV(new KeyParameter(key.getEncoded()), iv));
- writeHeader(header, output);
-@@ -214,7 +216,7 @@ public static void encrypt(
- public static byte[] decrypt(final BlockCipher cipher, final SecretKey key, final byte[] data)
- throws CryptoException, EncodingException
- {
-- final CiphertextHeader header = CiphertextHeader.decode(data);
-+ final CiphertextHeader header = decodeHeader(data, String -> key);
- final PaddedBufferedBlockCipher padded = new PaddedBufferedBlockCipher(cipher, new PKCS7Padding());
- padded.init(false, new ParametersWithIV(new KeyParameter(key.getEncoded()), header.getNonce()));
- return decrypt(new BufferedBlockCipherAdapter(padded), data, header.getLength());
-@@ -240,13 +242,62 @@ public static void decrypt(
- final OutputStream output)
- throws CryptoException, EncodingException, StreamException
- {
-- final CiphertextHeader header = CiphertextHeader.decode(input);
-+ final CiphertextHeader header = decodeHeader(input, String -> key);
- final PaddedBufferedBlockCipher padded = new PaddedBufferedBlockCipher(cipher, new PKCS7Padding());
- padded.init(false, new ParametersWithIV(new KeyParameter(key.getEncoded()), header.getNonce()));
- process(new BufferedBlockCipherAdapter(padded), input, output);
- }
-
-
-+ /**
-+ * Decodes the ciphertext header at the start of the given byte array.
-+ * Supports both original (deprecated) and v2 formats.
-+ *
-+ * @param data Ciphertext data with prepended header.
-+ * @param keyLookup Decryption key lookup function.
-+ *
-+ * @return Ciphertext header instance.
-+ */
-+ public static CiphertextHeader decodeHeader(final byte[] data, final Function keyLookup)
-+ {
-+ try {
-+ return CiphertextHeaderV2.decode(data, keyLookup);
-+ } catch (EncodingException e) {
-+ return CiphertextHeader.decode(data);
-+ }
-+ }
-+
-+
-+ /**
-+ * Decodes the ciphertext header at the start of the given input stream.
-+ * Supports both original (deprecated) and v2 formats.
-+ *
-+ * @param in Ciphertext stream that is positioned at the start of the ciphertext header.
-+ * @param keyLookup Decryption key lookup function.
-+ *
-+ * @return Ciphertext header instance.
-+ */
-+ public static CiphertextHeader decodeHeader(final InputStream in, final Function keyLookup)
-+ {
-+ CiphertextHeader header;
-+ try {
-+ // Mark the stream start position so we can try again with old format header
-+ if (in.markSupported()) {
-+ in.mark(4);
-+ }
-+ header = CiphertextHeaderV2.decode(in, keyLookup);
-+ } catch (EncodingException e) {
-+ try {
-+ in.reset();
-+ } catch (IOException ioe) {
-+ throw new StreamException("Stream error trying to process old header format: " + ioe.getMessage());
-+ }
-+ header = CiphertextHeader.decode(in);
-+ }
-+ return header;
-+ }
-+
-+
- /**
- * Encrypts the given data.
- *
-@@ -325,6 +376,7 @@ private static void process(final BlockCipherAdapter cipher, final InputStream i
- }
-
-
-+
- /**
- * Writes a ciphertext header to the output stream.
- *
-diff --git a/src/test/java/org/cryptacular/CiphertextHeaderTest.java b/src/test/java/org/cryptacular/CiphertextHeaderTest.java
-new file mode 100644
-index 0000000..51abfae
---- /dev/null
-+++ b/src/test/java/org/cryptacular/CiphertextHeaderTest.java
-@@ -0,0 +1,55 @@
-+/* See LICENSE for licensing and NOTICE for copyright. */
-+package org.cryptacular;
-+
-+import java.util.Arrays;
-+import org.cryptacular.util.CodecUtil;
-+import org.testng.annotations.Test;
-+import static org.testng.Assert.assertEquals;
-+
-+/**
-+ * Unit test for {@link CiphertextHeader}.
-+ *
-+ * @author Middleware Services
-+ */
-+public class CiphertextHeaderTest
-+{
-+
-+ @Test(
-+ expectedExceptions = IllegalArgumentException.class,
-+ expectedExceptionsMessageRegExp = "Nonce exceeds size limit in bytes.*")
-+ public void testNonceLimitConstructor()
-+ {
-+ new CiphertextHeader(new byte[256], "key2");
-+ }
-+
-+ @Test
-+ public void testEncodeDecodeSuccess()
-+ {
-+ final byte[] nonce = new byte[255];
-+ Arrays.fill(nonce, (byte) 7);
-+ final CiphertextHeader expected = new CiphertextHeader(nonce, "aleph");
-+ final byte[] encoded = expected.encode();
-+ assertEquals(expected.getLength(), encoded.length);
-+ final CiphertextHeader actual = CiphertextHeader.decode(encoded);
-+ assertEquals(expected.getNonce(), actual.getNonce());
-+ assertEquals(expected.getKeyName(), actual.getKeyName());
-+ assertEquals(expected.getLength(), actual.getLength());
-+ }
-+
-+ @Test(
-+ expectedExceptions = EncodingException.class,
-+ expectedExceptionsMessageRegExp = "Bad ciphertext header: maximum nonce length exceeded")
-+ public void testDecodeFailNonceLengthExceeded()
-+ {
-+ // https://github.com/vt-middleware/cryptacular/issues/52
-+ CiphertextHeader.decode(CodecUtil.hex("000000347ffffffd"));
-+ }
-+
-+ @Test(
-+ expectedExceptions = EncodingException.class,
-+ expectedExceptionsMessageRegExp = "Bad ciphertext header: maximum key length exceeded")
-+ public void testDecodeFailKeyLengthExceeded()
-+ {
-+ CiphertextHeader.decode(CodecUtil.hex("000000F300000004DEADBEEF00FFFFFF"));
-+ }
-+}
-diff --git a/src/test/java/org/cryptacular/CiphertextHeaderV2Test.java b/src/test/java/org/cryptacular/CiphertextHeaderV2Test.java
-new file mode 100644
-index 0000000..7313d35
---- /dev/null
-+++ b/src/test/java/org/cryptacular/CiphertextHeaderV2Test.java
-@@ -0,0 +1,67 @@
-+/* See LICENSE for licensing and NOTICE for copyright. */
-+package org.cryptacular;
-+
-+import java.util.Arrays;
-+import javax.crypto.SecretKey;
-+import javax.crypto.spec.SecretKeySpec;
-+import org.cryptacular.generator.sp80038a.RBGNonce;
-+import org.testng.annotations.Test;
-+import static org.testng.Assert.assertEquals;
-+
-+/**
-+ * Unit test for {@link CiphertextHeaderV2}.
-+ *
-+ * @author Middleware Services
-+ */
-+public class CiphertextHeaderV2Test
-+{
-+ /** Test HMAC key. */
-+ private final SecretKey key = new SecretKeySpec(new RBGNonce().generate(), "AES");
-+
-+ @Test(
-+ expectedExceptions = IllegalArgumentException.class,
-+ expectedExceptionsMessageRegExp = "Nonce exceeds size limit in bytes.*")
-+ public void testNonceLimitConstructor()
-+ {
-+ new CiphertextHeaderV2(new byte[256], "key2");
-+ }
-+
-+ @Test
-+ public void testEncodeDecodeSuccess()
-+ {
-+ final byte[] nonce = new byte[255];
-+ Arrays.fill(nonce, (byte) 7);
-+ final CiphertextHeaderV2 expected = new CiphertextHeaderV2(nonce, "aleph");
-+ expected.setKeyLookup(this::getKey);
-+ final byte[] encoded = expected.encode();
-+ assertEquals(expected.getLength(), encoded.length);
-+ final CiphertextHeaderV2 actual = CiphertextHeaderV2.decode(encoded, this::getKey);
-+ assertEquals(expected.getNonce(), actual.getNonce());
-+ assertEquals(expected.getKeyName(), actual.getKeyName());
-+ assertEquals(expected.getLength(), actual.getLength());
-+ }
-+
-+ @Test(
-+ expectedExceptions = EncodingException.class,
-+ expectedExceptionsMessageRegExp = "Ciphertext header HMAC verification failed")
-+ public void testEncodeDecodeFailBadHMAC()
-+ {
-+ final byte[] nonce = new byte[16];
-+ Arrays.fill(nonce, (byte) 3);
-+ final CiphertextHeaderV2 expected = new CiphertextHeaderV2(nonce, "aleph");
-+ // Tamper with computed HMAC
-+ final byte[] encoded = expected.encode(key);
-+ final int index = encoded.length - 3;
-+ final byte b = encoded[index];
-+ encoded[index] = (byte) (b + 1);
-+ CiphertextHeaderV2.decode(encoded, this::getKey);
-+ }
-+
-+ private SecretKey getKey(final String alias)
-+ {
-+ if ("aleph".equals(alias)) {
-+ return key;
-+ }
-+ return null;
-+ }
-+}
-diff --git a/src/test/java/org/cryptacular/bean/AEADBlockCipherBeanTest.java b/src/test/java/org/cryptacular/bean/AEADBlockCipherBeanTest.java
-index fde2c97..a26f341 100644
---- a/src/test/java/org/cryptacular/bean/AEADBlockCipherBeanTest.java
-+++ b/src/test/java/org/cryptacular/bean/AEADBlockCipherBeanTest.java
-@@ -5,13 +5,12 @@
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.security.KeyStore;
--import javax.crypto.SecretKey;
--
- import org.cryptacular.FailListener;
- import org.cryptacular.generator.sp80038d.CounterNonce;
- import org.cryptacular.io.FileResource;
- import org.cryptacular.spec.AEADBlockCipherSpec;
- import org.cryptacular.util.ByteUtil;
-+import org.cryptacular.util.CodecUtil;
- import org.cryptacular.util.StreamUtil;
- import org.testng.annotations.DataProvider;
- import org.testng.annotations.Listeners;
-@@ -25,6 +25,7 @@
- @Listeners(FailListener.class)
- public class AEADBlockCipherBeanTest
- {
-+
- @DataProvider(name = "test-arrays")
- public Object[][] getTestArrays()
- {
-@@ -78,14 +79,7 @@
- public void testEncryptDecryptArray(final String input, final String cipherSpecString)
- throws Exception
- {
-- final AEADBlockCipherBean cipherBean = new AEADBlockCipherBean();
-- final AEADBlockCipherSpec cipherSpec = AEADBlockCipherSpec.parse(cipherSpecString);
-- cipherBean.setNonce(new CounterNonce("vtmw", System.nanoTime()));
-- cipherBean.setKeyAlias("vtcrypt");
-- cipherBean.setKeyPassword("vtcrypt");
-- cipherBean.setKeyStore(getTestKeyStore());
-- cipherBean.setBlockCipherSpec(cipherSpec);
--
-+ final AEADBlockCipherBean cipherBean = newCipherBean(AEADBlockCipherSpec.parse(cipherSpecString));
- final byte[] ciphertext = cipherBean.encrypt(ByteUtil.toBytes(input));
- assertEquals(ByteUtil.toString(cipherBean.decrypt(ciphertext)), input);
- }
-@@ -95,14 +89,7 @@ public void testEncryptDecryptArray(final String input, final String cipherSpecS
- public void testEncryptDecryptStream(final String path, final String cipherSpecString)
- throws Exception
- {
-- final AEADBlockCipherBean cipherBean = new AEADBlockCipherBean();
-- final AEADBlockCipherSpec cipherSpec = AEADBlockCipherSpec.parse(cipherSpecString);
-- cipherBean.setNonce(new CounterNonce("vtmw", System.nanoTime()));
-- cipherBean.setKeyAlias("vtcrypt");
-- cipherBean.setKeyPassword("vtcrypt");
-- cipherBean.setKeyStore(getTestKeyStore());
-- cipherBean.setBlockCipherSpec(cipherSpec);
--
-+ final AEADBlockCipherBean cipherBean = newCipherBean(AEADBlockCipherSpec.parse(cipherSpecString));
- final ByteArrayOutputStream tempOut = new ByteArrayOutputStream(8192);
- cipherBean.encrypt(StreamUtil.makeStream(new File(path)), tempOut);
-
-@@ -113,6 +100,34 @@ public void testEncryptDecryptStream(final String path, final String cipherSpecS
- }
-
-
-+ @Test
-+ public void testDecryptArrayBackwardCompatibleHeader()
-+ {
-+ final AEADBlockCipherBean cipherBean = newCipherBean(new AEADBlockCipherSpec("Twofish", "OCB"));
-+ final String expected = "Have you passed through this night?";
-+ final String v1CiphertextHex =
-+ "0000001f0000000c76746d770002ba17043672d900000007767463727970745a38dee735266e3f5f7aafec8d1c9ed8a0830a2ff9" +
-+ "c3a46c25f89e69b6eb39dbb82fd13da50e32b2544a73f1a4476677b377e6";
-+ final byte[] plaintext = cipherBean.decrypt(CodecUtil.hex(v1CiphertextHex));
-+ assertEquals(expected, ByteUtil.toString(plaintext));
-+ }
-+
-+
-+ @Test
-+ public void testDecryptStreamBackwardCompatibleHeader()
-+ {
-+ final AEADBlockCipherBean cipherBean = newCipherBean(new AEADBlockCipherSpec("Twofish", "OCB"));
-+ final String expected = "Have you passed through this night?";
-+ final String v1CiphertextHex =
-+ "0000001f0000000c76746d770002ba17043672d900000007767463727970745a38dee735266e3f5f7aafec8d1c9ed8a0830a2ff9" +
-+ "c3a46c25f89e69b6eb39dbb82fd13da50e32b2544a73f1a4476677b377e6";
-+ final ByteArrayInputStream in = new ByteArrayInputStream(CodecUtil.hex(v1CiphertextHex));
-+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
-+ cipherBean.decrypt(in, out);
-+ assertEquals(expected, ByteUtil.toString(out.toByteArray()));
-+ }
-+
-+
- private static KeyStore getTestKeyStore()
- {
- final KeyStoreFactoryBean bean = new KeyStoreFactoryBean();
-@@ -122,12 +137,15 @@ private static KeyStore getTestKeyStore()
- return bean.newInstance();
- }
-
-- private static SecretKey getTestKey()
-+
-+ private static AEADBlockCipherBean newCipherBean(final AEADBlockCipherSpec cipherSpec)
- {
-- final KeyStoreBasedKeyFactoryBean secretKeyFactoryBean = new KeyStoreBasedKeyFactoryBean<>();
-- secretKeyFactoryBean.setKeyStore(getTestKeyStore());
-- secretKeyFactoryBean.setPassword("vtcrypt");
-- secretKeyFactoryBean.setAlias("vtcrypt");
-- return secretKeyFactoryBean.newInstance();
-+ final AEADBlockCipherBean cipherBean = new AEADBlockCipherBean();
-+ cipherBean.setNonce(new CounterNonce("vtmw", System.nanoTime()));
-+ cipherBean.setKeyAlias("vtcrypt");
-+ cipherBean.setKeyPassword("vtcrypt");
-+ cipherBean.setKeyStore(getTestKeyStore());
-+ cipherBean.setBlockCipherSpec(cipherSpec);
-+ return cipherBean;
- }
- }
-diff --git a/src/test/java/org/cryptacular/util/CipherUtilTest.java b/src/test/java/org/cryptacular/util/CipherUtilTest.java
-index f2cd7de..886ce54 100644
---- a/src/test/java/org/cryptacular/util/CipherUtilTest.java
-+++ b/src/test/java/org/cryptacular/util/CipherUtilTest.java
-@@ -17,11 +17,14 @@
- import org.bouncycastle.crypto.modes.OCBBlockCipher;
- import org.bouncycastle.crypto.modes.OFBBlockCipher;
- import org.cryptacular.FailListener;
-+import org.cryptacular.bean.KeyStoreBasedKeyFactoryBean;
-+import org.cryptacular.bean.KeyStoreFactoryBean;
- import org.cryptacular.generator.Nonce;
- import org.cryptacular.generator.SecretKeyGenerator;
- import org.cryptacular.generator.sp80038a.LongCounterNonce;
- import org.cryptacular.generator.sp80038a.RBGNonce;
- import org.cryptacular.generator.sp80038d.CounterNonce;
-+import org.cryptacular.io.FileResource;
- import org.testng.annotations.DataProvider;
- import org.testng.annotations.Listeners;
- import org.testng.annotations.Test;
-@@ -35,6 +38,22 @@
- @Listeners(FailListener.class)
- public class CipherUtilTest
- {
-+ /** Static key derived from keystore on resource classpath. */
-+ private static final SecretKey STATIC_KEY;
-+
-+ static
-+ {
-+ final KeyStoreFactoryBean keyStoreFactory = new KeyStoreFactoryBean();
-+ keyStoreFactory.setPassword("vtcrypt");
-+ keyStoreFactory.setResource(new FileResource(new File("src/test/resources/keystores/cipher-bean.jceks")));
-+ keyStoreFactory.setType("JCEKS");
-+ final KeyStoreBasedKeyFactoryBean keyFactory = new KeyStoreBasedKeyFactoryBean<>();
-+ keyFactory.setKeyStore(keyStoreFactory.newInstance());
-+ keyFactory.setAlias("vtcrypt");
-+ keyFactory.setPassword("vtcrypt");
-+ STATIC_KEY = keyFactory.newInstance();
-+ }
-+
- @DataProvider(name = "block-cipher")
- public Object[][] getBlockCipherData()
- {
-@@ -165,4 +184,32 @@ public void testAeadBlockCipherEncryptDecryptStream(final String path)
- CipherUtil.decrypt(cipher, key, tempIn, actual);
- assertEquals(new String(actual.toByteArray()), expected);
- }
-+
-+
-+ @Test
-+ public void testDecryptArrayBackwardCompatibleHeader()
-+ {
-+ final AEADBlockCipher cipher = new OCBBlockCipher(new TwofishEngine(), new TwofishEngine());
-+ final String expected = "Have you passed through this night?";
-+ final String v1CiphertextHex =
-+ "0000001f0000000c76746d770002ba17043672d900000007767463727970745a38dee735266e3f5f7aafec8d1c9ed8a0830a2ff9" +
-+ "c3a46c25f89e69b6eb39dbb82fd13da50e32b2544a73f1a4476677b377e6";
-+ final byte[] plaintext = CipherUtil.decrypt(cipher, STATIC_KEY, CodecUtil.hex(v1CiphertextHex));
-+ assertEquals(expected, ByteUtil.toString(plaintext));
-+ }
-+
-+
-+ @Test
-+ public void testDecryptStreamBackwardCompatibleHeader()
-+ {
-+ final AEADBlockCipher cipher = new OCBBlockCipher(new TwofishEngine(), new TwofishEngine());
-+ final String expected = "Have you passed through this night?";
-+ final String v1CiphertextHex =
-+ "0000001f0000000c76746d770002ba17043672d900000007767463727970745a38dee735266e3f5f7aafec8d1c9ed8a0830a2ff9" +
-+ "c3a46c25f89e69b6eb39dbb82fd13da50e32b2544a73f1a4476677b377e6";
-+ final ByteArrayInputStream in = new ByteArrayInputStream(CodecUtil.hex(v1CiphertextHex));
-+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
-+ CipherUtil.decrypt(cipher, STATIC_KEY, in, out);
-+ assertEquals(expected, ByteUtil.toString(out.toByteArray()));
-+ }
- }
diff --git a/backport-CVE-2020-7226-2.patch b/backport-CVE-2020-7226-2.patch
deleted file mode 100644
index 7c602fb..0000000
--- a/backport-CVE-2020-7226-2.patch
+++ /dev/null
@@ -1,81 +0,0 @@
-From 132f15ead532d78d4c19d2bcb39ec8f319ad6945 Mon Sep 17 00:00:00 2001
-From: "Marvin S. Addison"
-Date: Mon, 27 Jan 2020 14:39:35 -0500
-Subject: [PATCH] Address code review feedback points.
-
----
- src/main/java/org/cryptacular/CiphertextHeader.java | 6 +++---
- .../java/org/cryptacular/CiphertextHeaderV2.java | 12 +++++++-----
- src/main/java/org/cryptacular/util/CipherUtil.java | 1 -
- 3 files changed, 10 insertions(+), 9 deletions(-)
-
-diff --git a/src/main/java/org/cryptacular/CiphertextHeader.java b/src/main/java/org/cryptacular/CiphertextHeader.java
-index c17e735..d43bf9a 100644
---- a/src/main/java/org/cryptacular/CiphertextHeader.java
-+++ b/src/main/java/org/cryptacular/CiphertextHeader.java
-@@ -75,12 +75,12 @@ public CiphertextHeader(final byte[] nonce)
- */
- public CiphertextHeader(final byte[] nonce, final String keyName)
- {
-- if (nonce.length > 255) {
-- throw new IllegalArgumentException("Nonce exceeds size limit in bytes (255)");
-+ if (nonce.length > MAX_NONCE_LEN) {
-+ throw new IllegalArgumentException("Nonce exceeds size limit in bytes (" + MAX_NONCE_LEN + ")");
- }
- if (keyName != null) {
- if (ByteUtil.toBytes(keyName).length > MAX_KEYNAME_LEN) {
-- throw new IllegalArgumentException("Key name exceeds size limit in bytes (500)");
-+ throw new IllegalArgumentException("Key name exceeds size limit in bytes (" + MAX_KEYNAME_LEN + ")");
- }
- }
- this.nonce = nonce;
-diff --git a/src/main/java/org/cryptacular/CiphertextHeaderV2.java b/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-index 8119f4e..1fe095b 100644
---- a/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-+++ b/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-@@ -102,6 +102,9 @@ public void setKeyLookup(final Function keyLookup)
- */
- public byte[] encode(final SecretKey hmacKey)
- {
-+ if (hmacKey == null) {
-+ throw new IllegalArgumentException("Secret key cannot be null");
-+ }
- final ByteBuffer bb = ByteBuffer.allocate(length);
- bb.order(ByteOrder.BIG_ENDIAN);
- bb.putInt(VERSION);
-@@ -109,10 +112,7 @@ public void setKeyLookup(final Function keyLookup)
- bb.put((byte) 0);
- bb.put(ByteUtil.toUnsignedByte(nonce.length));
- bb.put(nonce);
-- if (hmacKey != null) {
-- final byte[] hmac = hmac(bb.array(), 0, bb.limit() - HMAC_SIZE);
-- bb.put(hmac);
-- }
-+ bb.put(hmac(bb.array(), 0, bb.limit() - HMAC_SIZE));
- return bb.array();
- }
-
-@@ -253,8 +253,10 @@ public static CiphertextHeaderV2 decode(final InputStream input, final Function<
- *
- * @param input Input stream.
- * @param output Output buffer.
-+ *
-+ * @throws StreamException on stream IO errors.
- */
-- private static void readInto(final InputStream input, final byte[] output)
-+ private static void readInto(final InputStream input, final byte[] output) throws StreamException
- {
- try {
- input.read(output);
-diff --git a/src/main/java/org/cryptacular/util/CipherUtil.java b/src/main/java/org/cryptacular/util/CipherUtil.java
-index cdbac0d..40ef4d1 100644
---- a/src/main/java/org/cryptacular/util/CipherUtil.java
-+++ b/src/main/java/org/cryptacular/util/CipherUtil.java
-@@ -376,7 +376,6 @@ private static void process(final BlockCipherAdapter cipher, final InputStream i
- }
-
-
--
- /**
- * Writes a ciphertext header to the output stream.
- *
diff --git a/backport-CVE-2020-7226-3.patch b/backport-CVE-2020-7226-3.patch
deleted file mode 100644
index f4758e4..0000000
--- a/backport-CVE-2020-7226-3.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From 00395c232cdc62d4292ce27999c026fc1f076b1d Mon Sep 17 00:00:00 2001
-From: "Marvin S. Addison"
-Date: Wed, 29 Jan 2020 16:51:35 -0500
-Subject: [PATCH] Remove runtime exception from method sig.
-
----
- src/main/java/org/cryptacular/CiphertextHeaderV2.java | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/src/main/java/org/cryptacular/CiphertextHeaderV2.java b/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-index 1fe095b..23d039e 100644
---- a/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-+++ b/src/main/java/org/cryptacular/CiphertextHeaderV2.java
-@@ -256,7 +256,7 @@ public static CiphertextHeaderV2 decode(final InputStream input, final Function<
- *
- * @throws StreamException on stream IO errors.
- */
-- private static void readInto(final InputStream input, final byte[] output) throws StreamException
-+ private static void readInto(final InputStream input, final byte[] output)
- {
- try {
- input.read(output);
diff --git a/change-version-to-Java8.patch b/change-version-to-Java8.patch
deleted file mode 100644
index dc87b44..0000000
--- a/change-version-to-Java8.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From 1972c658289468599bbb832bad03fe0a5a34713d Mon Sep 17 00:00:00 2001
-From: zhanghua1831
-Date: Fri, 26 Feb 2021 12:33:02 +0800
-Subject: [PATCH] fix build error by using Java8
-
-changes of CVE-2020-7226's patches require Java8
----
- pom.xml | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/pom.xml b/pom.xml
-index 1f83d44..9506e54 100644
---- a/pom.xml
-+++ b/pom.xml
-@@ -140,8 +140,8 @@
- true
- true
- -Xlint:unchecked
-- 1.7
-- 1.7
-+ 1.8
-+ 1.8
-
-
-
-@@ -182,7 +182,7 @@
- 2.10.3
-
-
-- http://download.oracle.com/javase/7/docs/api
-+ http://download.oracle.com/javase/8/docs/api
-
- Copyright © 2003-2015 Virginia Tech. All Rights Reserved.]]>
-
---
-2.23.0
-
diff --git a/cryptacular.spec b/cryptacular.spec
index 83739fc..292b6de 100644
--- a/cryptacular.spec
+++ b/cryptacular.spec
@@ -1,14 +1,10 @@
Name: cryptacular
-Version: 1.1.0
-Release: 2
+Version: 1.2.4
+Release: 1
Summary: Java Library that complement to the Bouncy Castle crypto API
License: ASL 2.0 or LGPLv3
URL: http://www.cryptacular.org/
Source0: https://github.com/vt-middleware/cryptacular/archive/v%{version}.tar.gz
-Patch0000: backport-CVE-2020-7226-1.patch
-Patch0001: backport-CVE-2020-7226-2.patch
-Patch0002: backport-CVE-2020-7226-3.patch
-Patch0003: change-version-to-Java8.patch
BuildRequires: maven-local mvn(org.apache.felix:maven-bundle-plugin)
BuildRequires: mvn(org.apache.maven.plugins:maven-assembly-plugin)
BuildRequires: mvn(org.apache.maven.plugins:maven-release-plugin)
@@ -51,6 +47,9 @@ This package contains man pages and other related documents for %{name}.
%license LICENSE LICENSE-apache2 LICENSE-lgpl NOTICE
%changelog
+* Wed Dec 29 2021 wangkai - 1.2.4-1
+* Update to 1.2.4
+
* Thu Feb 25 2021 zhanghua - 1.1.0-2
- fix CVE-2020-7226 and fix build error by using Java8
diff --git a/v1.1.0.tar.gz b/v1.1.0.tar.gz
deleted file mode 100644
index 1cb3839afca73fa6efbf069dcae9a92e5c446ab8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 157482
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