From 2f8ac14cd85a14d43e3b61af95219fd74fef6d24 Mon Sep 17 00:00:00 2001 From: Greg Rubin Date: Thu, 12 Dec 2019 23:44:06 +0000 Subject: [PATCH] Support IEEE-P1363 encoded ECDSA sigs --- CHANGELOG.md | 5 + README.md | 5 + build.gradle | 2 +- .../AmazonCorrettoCryptoProvider.java | 26 +++- .../crypto/provider/EvpSignature.java | 26 +++- .../crypto/provider/EvpSignatureBase.java | 146 ++++++++++++++++++ .../test/EvpSignatureSpecificTest.java | 124 +++++++++++++-- .../provider/test/EvpSignatureTest.java | 56 ++++--- .../crypto/provider/test/TestUtil.java | 2 + 9 files changed, 347 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77912401..0b63ef0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 1.3.0 (Unreleased) + +### Improvements +* Now supports ECDSA signatures in IEEE P1363 Format. (Also known as "raw" or "plain".) [PR #75](https://github.com/corretto/amazon-corretto-crypto-provider/pull/75) + ## 1.2.0 ### Improvements diff --git a/README.md b/README.md index 4cf6398c..b427bf6d 100644 --- a/README.md +++ b/README.md @@ -45,10 +45,15 @@ Signature algorithms: * SHA384withDSA * NONEwithECDSA * SHA1withECDSA +* SHA1withECDSAinP1363Format * SHA224withECDSA +* SHA224withECDSAinP1363Format * SHA256withECDSA +* SHA256withECDSAinP1363Format * SHA384withECDSA +* SHA384withECDSAinP1363Format * SHA512withECDSA +* SHA512withECDSAinP1363Format KeyPairGenerator algorithms: * EC diff --git a/build.gradle b/build.gradle index 9da86d8c..56b7ce8c 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { } group = 'software.amazon.cryptools' -version = '1.2.0' +version = '1.3.0' configurations { jacocoAgent diff --git a/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java b/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java index 14e32ad0..f1b6aae9 100644 --- a/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java +++ b/src/com/amazon/corretto/crypto/provider/AmazonCorrettoCryptoProvider.java @@ -97,7 +97,11 @@ private void addSignatures() { for (final String base : bases) { for (final String hash : hashes) { final String algorithm = format("%swith%s", hash, base); - addService("Signature", algorithm, String.format("EvpSignature$%s", algorithm)); + final String className = String.format("EvpSignature$%s", algorithm); + addService("Signature", algorithm, className); + if (base.equals("ECDSA")) { + addService("Signature", algorithm + "inP1363Format", className); + } } } @@ -121,6 +125,7 @@ private ACCPService addService(final String type, final String algorithm, final private class ACCPService extends Service { private final MethodHandle ctor; + private final MethodHandle algorithmSetter; // @GuardedBy("this") // Restore once replacement for JSR-305 available private boolean failMessagePrinted = false; @@ -148,9 +153,22 @@ public ACCPService( .bindTo(AmazonCorrettoCryptoProvider.this); } ctor = tmpCtor; + + MethodHandle tmpAlgSetter = null; + final MethodType setterSignature = MethodType.methodType(void.class, String.class); + try { + tmpAlgSetter = LOOKUP.findVirtual(klass, "setAlgorithmName", setterSignature); + } catch (final NoSuchMethodException ex) { + if (type.equals("Signature")) { + throw ex; + } + // Just ignore this + } + algorithmSetter = tmpAlgSetter; } catch (Exception e) { throw new RuntimeException(e); } + } @Override public Object newInstance(final Object constructorParameter) throws NoSuchAlgorithmException { @@ -165,7 +183,11 @@ public ACCPService( } try { - return (Object) ctor.invokeExact(); + Object result = (Object) ctor.invokeExact(); + if (algorithmSetter != null) { + algorithmSetter.invoke(result, getAlgorithm()); + } + return result; } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { diff --git a/src/com/amazon/corretto/crypto/provider/EvpSignature.java b/src/com/amazon/corretto/crypto/provider/EvpSignature.java index d7c1fe6d..e004b068 100644 --- a/src/com/amazon/corretto/crypto/provider/EvpSignature.java +++ b/src/com/amazon/corretto/crypto/provider/EvpSignature.java @@ -7,6 +7,7 @@ import java.security.SignatureException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; class EvpSignature extends EvpSignatureBase { /** The number a times a key must be reused prior to keeping it in native memory rather than freeing it each time. **/ @@ -482,7 +483,7 @@ protected synchronized void engineReset() { protected synchronized byte[] engineSign() throws SignatureException { ensureInitialized(true); try { - return signingBuffer.doFinal(); + return maybeConvertSignatureToReturn(signingBuffer.doFinal()); } finally { engineReset(); } @@ -521,18 +522,31 @@ protected synchronized boolean engineVerify(final byte[] sigBytes) throws Signat } @Override - protected synchronized boolean engineVerify(final byte[] sigBytes, final int off, final int len) + protected synchronized boolean engineVerify(byte[] sigBytes, int off, int len) throws SignatureException { ensureInitialized(false); + byte[] tempSig = maybeConvertSignatureToVerify(sigBytes, off, len); + final byte[] finalSigBytes; + final int finalOff; + final int finalLen; + if (tempSig != null) { + finalSigBytes = tempSig; + finalOff = 0; + finalLen = finalSigBytes.length; + } else { + finalSigBytes = sigBytes; + finalOff = off; + finalLen = len; + } try { return verifyingBuffer .withDoFinal((ignored) -> { final boolean result; if (keyUsageCount_ > KEY_REUSE_THRESHOLD) { - result = ctx_.use(c -> verifyFinish(c, sigBytes, off, len, true)); + result = ctx_.use(c -> verifyFinish(c, finalSigBytes, finalOff, finalLen, true)); } else { try { - result = verifyFinish(ctx_.take(), sigBytes, off, len, false); + result = verifyFinish(ctx_.take(), finalSigBytes, finalOff, finalLen, false); } finally { ctx_ = null; } @@ -545,12 +559,12 @@ protected synchronized boolean engineVerify(final byte[] sigBytes, final int off if (ctx_ != null) { result = ctx_.use(c -> verify(keyDer_, new long[]{c}, keyType_.nativeValue, digestName_, paddingType_, null, 0, - src, offset, length, sigBytes, off, len)); + src, offset, length, finalSigBytes, finalOff, finalLen)); } else { final long[] handle = keyUsageCount_ > KEY_REUSE_THRESHOLD ? new long[1] : null; result = verify(keyDer_, handle, keyType_.nativeValue, digestName_, paddingType_, null, 0, - src, offset, length, sigBytes, off, len); + src, offset, length, finalSigBytes, finalOff, finalLen); if (handle != null) { ctx_ = new EvpContext(handle[0]); } diff --git a/src/com/amazon/corretto/crypto/provider/EvpSignatureBase.java b/src/com/amazon/corretto/crypto/provider/EvpSignatureBase.java index e4f21362..1008f08e 100644 --- a/src/com/amazon/corretto/crypto/provider/EvpSignatureBase.java +++ b/src/com/amazon/corretto/crypto/provider/EvpSignatureBase.java @@ -3,6 +3,7 @@ package com.amazon.corretto.crypto.provider; +import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -12,10 +13,14 @@ import java.security.PublicKey; import java.security.SignatureException; import java.security.SignatureSpi; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPublicKey; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; +import java.util.Base64; abstract class EvpSignatureBase extends SignatureSpi { protected static final int RSA_PKCS1_PADDING = 1; @@ -26,6 +31,7 @@ abstract class EvpSignatureBase extends SignatureSpi { protected boolean signMode; protected int keyUsageCount_ = 0; protected EvpContext ctx_ = null; + protected String algorithmName_ = null; EvpSignatureBase( final EvpKeyType keyType, @@ -37,6 +43,11 @@ abstract class EvpSignatureBase extends SignatureSpi { protected abstract void engineReset(); + // Called reflectively upon creation + void setAlgorithmName(String algorithmName) { + this.algorithmName_ = algorithmName; + } + /** * Destroys the native context. * @@ -154,4 +165,139 @@ protected EvpContext(final long ptr) { } } + /** + * Converts and returns the modified signature to verify only if necessary and returns {@code null} otherwise. + * If {@code null} is returned then the passed in parameters should be used for later verification. + * Otherwise, the entire returned array should be used. + * This method has a somewhat odd API since we want to avoid unneccessary array copies/allocations and it is an + * internal API anyway. + * + * @return the converted signature or {@code null} if no conversion is necessary + * @throws SignatureException if the signature is badly malformed + */ + protected byte[] maybeConvertSignatureToVerify(byte[] signature, int offset, int length) throws SignatureException { + if (algorithmName_ != null && algorithmName_.endsWith("withECDSAinP1363Format")) { + final ECKey ecKey = (ECKey) key_; + final int numLen = (ecKey.getParams().getOrder().bitLength() + 7) / 8; + return ieeeP1363toAsn1(signature, offset, length, numLen); + } else { + return null; + } + } + + /** + * Determines if we need to convert the signature we generated and performs said conversion. + * This methods may throw {@link AssertionError} on invalid input so should only be given trusted inputs.. + */ + protected byte[] maybeConvertSignatureToReturn(byte[] signature) throws SignatureException { + if (algorithmName_ != null && algorithmName_.endsWith("withECDSAinP1363Format")) { + final ECKey ecKey = (ECKey) key_; + final int numLen = (ecKey.getParams().getOrder().bitLength() + 7) / 8; + return asn1ToiIeeeP1363(signature, numLen); + } else { + return signature; + } + } + + /** + * This is a trivial conversion from two equal-length concatenated integers to an ASN.1 sequence. + * + * Since the resulting structure is so simple, we do not need a full ASN.1 engine and can cover all cases by hand. + */ + protected static byte[] ieeeP1363toAsn1(byte[] signature, final int offset, final int length, int numLen) throws SignatureException { + if (2 * numLen != length) { + throw new SignatureException(); + } + + // This is the easiest way to trim unneeded zero-bytes + final byte[] r = (new BigInteger(1, Arrays.copyOfRange(signature, offset, offset + numLen))).toByteArray(); + final byte[] s = (new BigInteger(1, Arrays.copyOfRange(signature, offset + numLen, offset + 2 * numLen))).toByteArray(); + + if (r.length > 127 || s.length > 127) { + throw new SignatureException("R or S value is too large"); + } + + // Encode the total sequence length. This might be one or two bytes + final int seqLength = r.length + s.length + 4; + final byte[] encodedSeqLength; + if (seqLength <= 127) { + encodedSeqLength = new byte[]{ (byte) (seqLength & 0xFF) }; + } else if (seqLength <= 256) { + encodedSeqLength = new byte[]{ (byte) 0x81, (byte) (seqLength & 0xFF)}; + } else { + throw new SignatureException("R or S value is too large"); + } + + final byte[] result = new byte[1 + encodedSeqLength.length + seqLength]; + int position = 0; + result[position++] = 0x30; // SEQUENCE + System.arraycopy(encodedSeqLength, 0, result, position, encodedSeqLength.length); + position += encodedSeqLength.length; + result[position++] = 0x02; // INTEGER + result[position++] = (byte) (r.length & 0xFF); // Length of R + System.arraycopy(r, 0, result, position, r.length); + position += r.length; + result[position++] = 0x02; // INTEGER + result[position++] = (byte) (s.length & 0xFF); // Length of S + System.arraycopy(s, 0, result, position, s.length); + position += s.length; + if (position != result.length) { + throw new AssertionError("Final position of " + position + " does not match expected value of " + result.length); + } + + return result; + } + + /** Note: This should only be used on trusted inputs **/ + protected static byte[] asn1ToiIeeeP1363(byte[] signature, int numLen) throws SignatureException { + // Check the ASN.1 for correctness and extract offsets + int position = 0; + if (signature[position++] != 0x30) { + throw new AssertionError(); + } + + // Length may be one or two bytes + int seqLen = Byte.toUnsignedInt(signature[position++]); + if (seqLen == 0x81) { + // Two byte length with second byte being the length + seqLen = Byte.toUnsignedInt(signature[position++]); + } else if (seqLen > 127) { + // Unhandled long, reserved, or indefinite length + throw new AssertionError(); + } + if (seqLen != signature.length - position) { + throw new AssertionError(); + } + + final int rOffset = position; + if (signature[rOffset] != 0x02) { + throw new AssertionError(); + } + int rLen = Byte.toUnsignedInt(signature[rOffset + 1]); + int rStart = rOffset + 2; + + final int sOffset = rStart + rLen; + if (signature[sOffset] != 0x02) { + throw new AssertionError(Base64.getEncoder().encodeToString(signature) + " : " + + + String.format("%x, %x, %x", signature[sOffset - 1], signature[sOffset], signature[sOffset + 1])); + } + int sLen = Byte.toUnsignedInt(signature[sOffset + 1]); + int sStart = sOffset + 2; + + // Remove leading zero bytes + if (signature[rStart] == 0) { + rStart++; + rLen--; + } + if (signature[sStart] == 0) { + sStart++; + sLen--; + } + + byte[] result = new byte[numLen * 2]; + System.arraycopy(signature, rStart, result, numLen - rLen, rLen); + System.arraycopy(signature, sStart, result, numLen + numLen - sLen, sLen); + return result; + } } diff --git a/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureSpecificTest.java b/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureSpecificTest.java index 7f5a92b7..02ae9b57 100644 --- a/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureSpecificTest.java +++ b/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureSpecificTest.java @@ -11,25 +11,17 @@ import static org.junit.Assert.fail; import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; +import java.security.*; import java.security.interfaces.RSAPrivateKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.PSSParameterSpec; -import java.security.spec.RSAKeyGenParameterSpec; -import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.*; +import java.util.Base64; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider; import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.Assert; import org.junit.Test; import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider; @@ -315,6 +307,108 @@ public void ecdsaSignCorruptsErrorState() throws Exception { } } + /** + * This test iterates over every implemented algorithm and ensures that it is compatible with the + * equivalent BouncyCastle implementation. It doesn't check negative cases as the more detailed tests + * cover that for algorithm families. + */ + @Test + public void simpleCorrectnessAllAlgorithms() throws Throwable { + final Pattern namePattern = Pattern.compile("(SHA(\\d+)|NONE)with([A-Z]+)(inP1363Format)?"); + final Set services = AmazonCorrettoCryptoProvider.INSTANCE.getServices(); + final byte[] message = {1, 2, 3, 4, 5, 6, 7, 8}; + for (Provider.Service service : services) { + if (!service.getType().equals("Signature")) { + continue; + } + final String algorithm = service.getAlgorithm(); + String bcAlgorithm = algorithm; + AlgorithmParameterSpec keyGenSpec = null; + String keyGenAlgorithm = null; + final Matcher m = namePattern.matcher(algorithm); + + if (!m.matches()) { + Assert.fail("Unexpected algorithm name: " + algorithm); + } + + final String shaLength = m.group(2); + final String base = m.group(3); + final String ieeeFormat = m.group(4); + + int ffSize = 0; // Finite field size used with RSA and DSA + switch (m.group(1)) { + case "SHA1": + case "SHA224": + case "SHA256": + ffSize = 2048; + break; + case "SHA384": + ffSize = 3072; + break; + case "SHA512": + case "NONE": + ffSize = 4096; + break; + default: + Assert.fail("Unexpected algorithm name: " + algorithm); + } + if ("ECDSA".equals(base)) { + keyGenAlgorithm = "EC"; + if (null == shaLength || "1".equals(shaLength) || "512".equals(shaLength)) { + keyGenSpec = new ECGenParameterSpec("NIST P-521"); + } else { + keyGenSpec = new ECGenParameterSpec("NIST P-" + shaLength); + } + + if (ieeeFormat != null) { + bcAlgorithm = bcAlgorithm.replace("withECDSAinP1363Format", "withPLAIN-ECDSA"); + } + } else { + keyGenAlgorithm = base; + if (base.equals("DSA")) { + ffSize = Math.min(ffSize, 3072); + } + } + + final KeyPairGenerator kg = KeyPairGenerator.getInstance(keyGenAlgorithm); + if (keyGenSpec != null) { + kg.initialize(keyGenSpec); + } else { + kg.initialize(ffSize); + } + final KeyPair pair = kg.generateKeyPair(); + + final Signature nativeSig = Signature.getInstance(algorithm, AmazonCorrettoCryptoProvider.INSTANCE); + final Signature bcSig = Signature.getInstance(bcAlgorithm, TestUtil.BC_PROVIDER); + + try { + // Generate with native and verify with BC + nativeSig.initSign(pair.getPrivate()); + bcSig.initVerify(pair.getPublic()); + nativeSig.update(message); + bcSig.update(message); + byte[] signature = nativeSig.sign(); + assertTrue("Native->BC: " + algorithm, bcSig.verify(signature)); + + // Generate with BC and verify with native + nativeSig.initVerify(pair.getPublic()); + bcSig.initSign(pair.getPrivate()); + nativeSig.update(message); + bcSig.update(message); + signature = bcSig.sign(); + if (algorithm.equals("SHA224withECDSAinP1363Format")) { + System.out.println(Base64.getEncoder().encodeToString(signature)); + Object spi = TestUtil.sneakyGetField(nativeSig, "sigSpi"); + System.out.println(Base64.getEncoder().encodeToString(TestUtil.sneakyInvoke(spi, "maybeConvertSignatureToVerify", signature))); + + } + assertTrue("BC->Native: " + algorithm, nativeSig.verify(signature)); + } catch (SignatureException ex) { + throw new AssertionError(algorithm, ex); + } + } + } + @SuppressWarnings("serial") private static class RawKey implements PublicKey, PrivateKey { private final String algorithm_; diff --git a/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureTest.java b/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureTest.java index d273e65e..56219c8b 100644 --- a/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureTest.java +++ b/tst/com/amazon/corretto/crypto/provider/test/EvpSignatureTest.java @@ -5,7 +5,6 @@ import static com.amazon.corretto.crypto.provider.test.TestUtil.assertThrows; -import static java.lang.String.format; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -26,16 +25,9 @@ import java.security.interfaces.ECKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.RSAKeyGenParameterSpec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.BiFunction; -import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,7 +40,6 @@ @RunWith(Parameterized.class) public class EvpSignatureTest { private static final Provider NATIVE_PROVIDER = AmazonCorrettoCryptoProvider.INSTANCE; - private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); private static final int[] LENGTHS = new int[] { 1, 3, 4, 7, 8, 16, 32, 48, 64, 128, 256, 1024, 1536, 2049 }; private static final List BASES = Arrays.asList("DSA", "RSA", "ECDSA"); private static final List HASHES = Arrays.asList("NONE", "SHA1", "SHA224", "SHA256", "SHA384", "SHA512"); @@ -63,7 +54,7 @@ public class EvpSignatureTest { tmpMap.put("RSA", kg.generateKeyPair()); kg = KeyPairGenerator.getInstance("EC"); - kg.initialize(new ECGenParameterSpec("NIST P-384")); + kg.initialize(new ECGenParameterSpec("NIST P-521")); tmpMap.put("ECDSA", kg.generateKeyPair()); kg = KeyPairGenerator.getInstance("DSA"); @@ -76,7 +67,7 @@ public class EvpSignatureTest { } } - @Parameters(name = "{1}with{0} message_ length {2}. Read-only: {3}, Sliced: {4}") + @Parameters(name = "{1} message_ length {2}. Read-only: {3}, Sliced: {4}") public static Collection data() { final List result = new ArrayList<>(); for (final String base : BASES) { @@ -102,10 +93,18 @@ public static Collection data() { } for (final int length : lengths) { - result.add(new Object[] { base, hash, length, false, false }); - result.add(new Object[] { base, hash, length, true, false }); - result.add(new Object[] { base, hash, length, false, true }); - result.add(new Object[] { base, hash, length, true, true }); + String algorithm = String.format("%swith%s", hash, base); + result.add(new Object[] { base, algorithm, length, false, false }); + result.add(new Object[] { base, algorithm, length, true, false }); + result.add(new Object[] { base, algorithm, length, false, true }); + result.add(new Object[] { base, algorithm, length, true, true }); + if (base.equals("ECDSA") && !hash.equals("NONE")) { + algorithm = algorithm + "inP1363Format"; + result.add(new Object[] { base, algorithm, length, false, false }); + result.add(new Object[] { base, algorithm, length, true, false }); + result.add(new Object[] { base, algorithm, length, false, true }); + result.add(new Object[] { base, algorithm, length, true, true }); + } } } } @@ -113,7 +112,6 @@ public static Collection data() { } private final String base_; - private final String hash_; private final String algorithm_; private final boolean readOnly_; private final boolean slice_; @@ -126,9 +124,8 @@ public static Collection data() { private Signature jceVerifier_; private byte[] goodSignature_; - public EvpSignatureTest(final String base, final String hash, final int length, boolean readOnly, boolean slice) throws GeneralSecurityException { + public EvpSignatureTest(final String base, final String algorithm, final int length, boolean readOnly, boolean slice) throws GeneralSecurityException { base_ = base; - hash_ = hash; readOnly_ = readOnly; slice_ = slice; length_ = length; @@ -138,7 +135,7 @@ public EvpSignatureTest(final String base, final String hash, final int length, } else { keyPair_ = KEY_PAIRS.get(base_); } - algorithm_ = format("%swith%s", hash_, base_); + algorithm_ = algorithm; } @Before @@ -178,7 +175,9 @@ private Signature getNativeSigner() throws NoSuchAlgorithmException { } private Signature getJceSigner() throws NoSuchAlgorithmException { - return Signature.getInstance(algorithm_, BC_PROVIDER); + // BouncyCastle uses a different naming scheme for P1363 schemes + String bcName = algorithm_.replace("withECDSAinP1363Format", "withPLAIN-ECDSA"); + return Signature.getInstance(bcName, TestUtil.BC_PROVIDER); } private void assumeNonByteBufferTestApplicable() { @@ -379,6 +378,20 @@ public void verifySubArray() throws GeneralSecurityException { } } + @Test + public void verifySignatureInLargerArray() throws SignatureException { + assumeNonByteBufferTestApplicable(); + final int offset = 7; + final int length = goodSignature_.length; + final byte[] paddedSignature = new byte[3 * offset + length]; + // Ensure the padding isn't just 0s which might not trigger exceptions + Arrays.fill(paddedSignature, (byte) 0x20); + System.arraycopy(goodSignature_, 0, paddedSignature, offset, length); + + verifier_.update(message_); + assertTrue(verifier_.verify(paddedSignature, offset, length)); + } + @Test public void verifySingleByteBufferWrap() throws GeneralSecurityException { testSingleByteBuffer(false, applyParameters(ByteBuffer.wrap(message_))); @@ -428,6 +441,7 @@ public void corruptedSignatureYieldsException() throws GeneralSecurityException // JCA/JCE standards require that we try to throw an exception if the underlying signature is "corrupt" and not // just invalid. assumeFalse(algorithm_.contains("RSA")); // Does not apply to RSA algorithms + assumeFalse(algorithm_.contains("inP1363Format")); // Does not apply to this format byte[] badSignature = goodSignature_.clone(); for (int x = 0; x < badSignature.length; x++) { badSignature[x] ^= 0x5c; // Arbitrary value to twiddle the bits diff --git a/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java b/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java index f295eca6..3309bac5 100644 --- a/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java +++ b/tst/com/amazon/corretto/crypto/provider/test/TestUtil.java @@ -4,6 +4,7 @@ package com.amazon.corretto.crypto.provider.test; import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.junit.Assume; import static org.junit.Assert.fail; @@ -22,6 +23,7 @@ @SuppressWarnings("unchecked") public class TestUtil { + public static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); private static final File TEST_DIR = new File(System.getProperty("test.data.dir", ".")); public static void assertThrows(Class expected, ThrowingRunnable callable) {