aboutsummaryrefslogtreecommitdiff
path: root/keystore-cts/java/com/google/security/wycheproof
diff options
context:
space:
mode:
Diffstat (limited to 'keystore-cts/java/com/google/security/wycheproof')
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/CertificateUtil.java83
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/EcUtil.java525
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/JsonUtil.java91
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/TestUtil.java71
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java984
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java286
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java251
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java1072
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java417
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java303
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java242
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java209
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java332
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java966
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java398
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java225
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java390
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java568
-rw-r--r--keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java1323
19 files changed, 8736 insertions, 0 deletions
diff --git a/keystore-cts/java/com/google/security/wycheproof/CertificateUtil.java b/keystore-cts/java/com/google/security/wycheproof/CertificateUtil.java
new file mode 100644
index 0000000..d5d343a
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/CertificateUtil.java
@@ -0,0 +1,83 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import javax.security.auth.x500.X500Principal;
+
+/** Certificate utilities */
+public class CertificateUtil {
+
+ public static X509Certificate createCertificate(
+ KeyPair keyPair, X500Principal subject, X500Principal issuer)
+ throws OperatorCreationException, CertificateException, IOException {
+ // Make the certificate valid for two days.
+ long millisPerDay = 24 * 60 * 60 * 1000;
+ long now = System.currentTimeMillis();
+ Date start = new Date(now - millisPerDay);
+ Date end = new Date(now + millisPerDay);
+
+ // Assign a random serial number.
+ byte[] serialBytes = new byte[16];
+ new SecureRandom().nextBytes(serialBytes);
+ BigInteger serialNumber = new BigInteger(1, serialBytes);
+
+ // Create the certificate builder
+ X509v3CertificateBuilder x509cg =
+ new X509v3CertificateBuilder(
+ X500Name.getInstance(issuer.getEncoded()),
+ serialNumber,
+ start,
+ end,
+ X500Name.getInstance(subject.getEncoded()),
+ SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));
+
+ // Choose a signature algorithm matching the key format.
+ String keyAlgorithm = keyPair.getPrivate().getAlgorithm();
+ String signatureAlgorithm;
+ if (keyAlgorithm.equals("RSA")) {
+ signatureAlgorithm = "SHA256withRSA";
+ } else if (keyAlgorithm.equals("EC")) {
+ signatureAlgorithm = "SHA256withECDSA";
+ } else {
+ throw new IllegalArgumentException("Unknown key algorithm " + keyAlgorithm);
+ }
+
+ // Sign the certificate and generate it.
+ X509CertificateHolder x509holder =
+ x509cg.build(
+ new JcaContentSignerBuilder(signatureAlgorithm)
+ .build(keyPair.getPrivate()));
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate x509c =
+ (X509Certificate)
+ certFactory.generateCertificate(
+ new ByteArrayInputStream(x509holder.getEncoded()));
+ return x509c;
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/EcUtil.java b/keystore-cts/java/com/google/security/wycheproof/EcUtil.java
new file mode 100644
index 0000000..56c6548
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/EcUtil.java
@@ -0,0 +1,525 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import java.math.BigInteger;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECField;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidParameterSpecException;
+import java.util.Arrays;
+
+/**
+ * Some utilities for testing Elliptic curve crypto. This code is for testing only and hasn't been
+ * reviewed for production.
+ */
+public class EcUtil {
+ /**
+ * Returns the ECParameterSpec for a named curve. Not every provider implements the
+ * AlgorithmParameters. Therefore, most tests use alternative functions.
+ */
+ public static ECParameterSpec getCurveSpec(String name)
+ throws NoSuchAlgorithmException, InvalidParameterSpecException {
+ AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
+ parameters.init(new ECGenParameterSpec(name));
+ return parameters.getParameterSpec(ECParameterSpec.class);
+ }
+
+ public static void printParameters(ECParameterSpec spec) {
+ System.out.println("cofactor:" + spec.getCofactor());
+ EllipticCurve curve = spec.getCurve();
+ System.out.println("A:" + curve.getA());
+ System.out.println("B:" + curve.getB());
+ ECField field = curve.getField();
+ System.out.println("field size:" + field.getFieldSize());
+ if (field instanceof ECFieldFp) {
+ ECFieldFp fp = (ECFieldFp) field;
+ System.out.println("P:" + fp.getP());
+ }
+ ECPoint generator = spec.getGenerator();
+ System.out.println("Gx:" + generator.getAffineX());
+ System.out.println("Gy:" + generator.getAffineY());
+ System.out.println("order:" + spec.getOrder());
+ }
+
+ /** Returns the bit size of a given curve. TODO(bleichen): add all curves that are tested. */
+ public static int getCurveSize(String name) throws NoSuchAlgorithmException {
+ name = name.toLowerCase();
+ if (name.equals("secp224r1")) {
+ return 224;
+ } else if (name.equals("secp256r1")) {
+ return 256;
+ } else if (name.equals("secp384r1")) {
+ return 384;
+ } else if (name.equals("secp521r1")) {
+ return 521;
+ } else if (name.equals("secp256k1")) {
+ return 256;
+ } else if (name.equals("brainpoolp224r1")) {
+ return 224;
+ } else if (name.equals("brainpoolp224t1")) {
+ return 224;
+ } else if (name.equals("brainpoolp256r1")) {
+ return 256;
+ } else if (name.equals("brainpoolp256t1")) {
+ return 256;
+ } else if (name.equals("brainpoolp320r1")) {
+ return 320;
+ } else if (name.equals("brainpoolp320t1")) {
+ return 320;
+ } else if (name.equals("brainpoolp384r1")) {
+ return 384;
+ } else if (name.equals("brainpoolp384t1")) {
+ return 384;
+ } else if (name.equals("brainpoolp512r1")) {
+ return 512;
+ } else if (name.equals("brainpoolp512t1")) {
+ return 512;
+ } else {
+ throw new NoSuchAlgorithmException("Curve not implemented:" + name);
+ }
+ }
+
+ /**
+ * Returns the ECParameterSpec for a named curve. Only a handful curves that are used in the tests
+ * are implemented.
+ */
+ public static ECParameterSpec getCurveSpecRef(String name) throws NoSuchAlgorithmException {
+ if (name.equals("secp224r1")) {
+ return getNistP224Params();
+ } else if (name.equals("secp256r1")) {
+ return getNistP256Params();
+ } else if (name.equals("secp384r1")) {
+ return getNistP384Params();
+ } else if (name.equals("secp521r1")) {
+ return getNistP521Params();
+ } else if (name.equals("brainpoolp224r1")) {
+ return getBrainpoolP224r1Params();
+ } else if (name.equals("brainpoolp256r1")) {
+ return getBrainpoolP256r1Params();
+ } else {
+ throw new NoSuchAlgorithmException("Curve not implemented:" + name);
+ }
+ }
+
+ public static ECParameterSpec getNistCurveSpec(
+ String decimalP, String decimalN, String hexB, String hexGX, String hexGY) {
+ final BigInteger p = new BigInteger(decimalP);
+ final BigInteger n = new BigInteger(decimalN);
+ final BigInteger three = new BigInteger("3");
+ final BigInteger a = p.subtract(three);
+ final BigInteger b = new BigInteger(hexB, 16);
+ final BigInteger gx = new BigInteger(hexGX, 16);
+ final BigInteger gy = new BigInteger(hexGY, 16);
+ final int h = 1;
+ ECFieldFp fp = new ECFieldFp(p);
+ java.security.spec.EllipticCurve curveSpec = new java.security.spec.EllipticCurve(fp, a, b);
+ ECPoint g = new ECPoint(gx, gy);
+ ECParameterSpec ecSpec = new ECParameterSpec(curveSpec, g, n, h);
+ return ecSpec;
+ }
+
+ public static ECParameterSpec getNistP224Params() {
+ return getNistCurveSpec(
+ "26959946667150639794667015087019630673557916260026308143510066298881",
+ "26959946667150639794667015087019625940457807714424391721682722368061",
+ "b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4",
+ "b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21",
+ "bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34");
+ }
+
+ public static ECParameterSpec getNistP256Params() {
+ return getNistCurveSpec(
+ "115792089210356248762697446949407573530086143415290314195533631308867097853951",
+ "115792089210356248762697446949407573529996955224135760342422259061068512044369",
+ "5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
+ "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+ "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5");
+ }
+
+ public static ECParameterSpec getNistP384Params() {
+ return getNistCurveSpec(
+ "3940200619639447921227904010014361380507973927046544666794829340"
+ + "4245721771496870329047266088258938001861606973112319",
+ "3940200619639447921227904010014361380507973927046544666794690527"
+ + "9627659399113263569398956308152294913554433653942643",
+ "b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875a"
+ + "c656398d8a2ed19d2a85c8edd3ec2aef",
+ "aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a38"
+ + "5502f25dbf55296c3a545e3872760ab7",
+ "3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c0"
+ + "0a60b1ce1d7e819d7a431d7c90ea0e5f");
+ }
+
+ public static ECParameterSpec getNistP521Params() {
+ return getNistCurveSpec(
+ "6864797660130609714981900799081393217269435300143305409394463459"
+ + "18554318339765605212255964066145455497729631139148085803712198"
+ + "7999716643812574028291115057151",
+ "6864797660130609714981900799081393217269435300143305409394463459"
+ + "18554318339765539424505774633321719753296399637136332111386476"
+ + "8612440380340372808892707005449",
+ "051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef10"
+ + "9e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00",
+ "c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3d"
+ + "baa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66",
+ "11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e6"
+ + "62c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650");
+ }
+
+ public static ECParameterSpec getBrainpoolP224r1Params() {
+ // name = "brainpoolP224r1",
+ // oid = '2b2403030208010105',
+ // ref = "RFC 5639",
+ BigInteger p = new BigInteger("D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF", 16);
+ BigInteger a = new BigInteger("68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43", 16);
+ BigInteger b = new BigInteger("2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B", 16);
+ BigInteger x = new BigInteger("0D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D", 16);
+ BigInteger y = new BigInteger("58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD", 16);
+ BigInteger n = new BigInteger("D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F", 16);
+ final int h = 1;
+ ECFieldFp fp = new ECFieldFp(p);
+ EllipticCurve curve = new EllipticCurve(fp, a, b);
+ ECPoint g = new ECPoint(x, y);
+ return new ECParameterSpec(curve, g, n, h);
+ }
+
+ public static ECParameterSpec getBrainpoolP256r1Params() {
+ BigInteger p =
+ new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377", 16);
+ BigInteger a =
+ new BigInteger("7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9", 16);
+ BigInteger b =
+ new BigInteger("26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6", 16);
+ BigInteger x =
+ new BigInteger("8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262", 16);
+ BigInteger y =
+ new BigInteger("547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997", 16);
+ BigInteger n =
+ new BigInteger("A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7", 16);
+ final int h = 1;
+ ECFieldFp fp = new ECFieldFp(p);
+ EllipticCurve curve = new EllipticCurve(fp, a, b);
+ ECPoint g = new ECPoint(x, y);
+ return new ECParameterSpec(curve, g, n, h);
+ }
+
+ /**
+ * Compute the Legendre symbol of x mod p. This implementation is slow. Faster would be the
+ * computation for the Jacobi symbol.
+ *
+ * @param x an integer
+ * @param p a prime modulus
+ * @returns 1 if x is a quadratic residue, -1 if x is a non-quadratic residue and 0 if x and p are
+ * not coprime.
+ * @throws GeneralSecurityException when the computation shows that p is not prime.
+ */
+ public static int legendre(BigInteger x, BigInteger p) throws GeneralSecurityException {
+ BigInteger q = p.subtract(BigInteger.ONE).shiftRight(1);
+ BigInteger t = x.modPow(q, p);
+ if (t.equals(BigInteger.ONE)) {
+ return 1;
+ } else if (t.equals(BigInteger.ZERO)) {
+ return 0;
+ } else if (t.add(BigInteger.ONE).equals(p)) {
+ return -1;
+ } else {
+ throw new GeneralSecurityException("p is not prime");
+ }
+ }
+
+ /**
+ * Computes a modular square root. Timing and exceptions can leak information about the inputs.
+ * Therefore this method must only be used in tests.
+ *
+ * @param x the square
+ * @param p the prime modulus
+ * @returns a value s such that s^2 mod p == x mod p
+ * @throws GeneralSecurityException if the square root could not be found.
+ */
+ public static BigInteger modSqrt(BigInteger x, BigInteger p) throws GeneralSecurityException {
+ if (p.signum() != 1) {
+ throw new GeneralSecurityException("p must be positive");
+ }
+ x = x.mod(p);
+ BigInteger squareRoot = null;
+ // Special case for x == 0.
+ // This check is necessary for Cipolla's algorithm.
+ if (x.equals(BigInteger.ZERO)) {
+ return x;
+ }
+ if (p.testBit(0) && p.testBit(1)) {
+ // Case p % 4 == 3
+ // q = (p + 1) / 4
+ BigInteger q = p.add(BigInteger.ONE).shiftRight(2);
+ squareRoot = x.modPow(q, p);
+ } else if (p.testBit(0) && !p.testBit(1)) {
+ // Case p % 4 == 1
+ // For this case we use Cipolla's algorithm.
+ // This alogorithm is preferrable to Tonelli-Shanks for primes p where p-1 is divisible by
+ // a large power of 2, which is a frequent choice since it simplifies modular reduction.
+ BigInteger a = BigInteger.ONE;
+ BigInteger d = null;
+ while (true) {
+ d = a.multiply(a).subtract(x).mod(p);
+ // Computes the Legendre symbol. Using the Jacobi symbol would be a faster. Using Legendre
+ // has the advantage, that it detects a non prime p with high probability.
+ // On the other hand if p = q^2 then the Jacobi (d/p)==1 for almost all d's and thus
+ // using the Jacobi symbol here can result in an endless loop with invalid inputs.
+ int t = legendre(d, p);
+ if (t == -1) {
+ break;
+ } else {
+ a = a.add(BigInteger.ONE);
+ }
+ }
+ // Since d = a^2 - n is a non-residue modulo p, we have
+ // a - sqrt(d) == (a+sqrt(d))^p (mod p),
+ // and hence
+ // n == (a + sqrt(d))(a - sqrt(d) == (a+sqrt(d))^(p+1) (mod p).
+ // Thus if n is square then (a+sqrt(d))^((p+1)/2) (mod p) is a square root of n.
+ BigInteger q = p.add(BigInteger.ONE).shiftRight(1);
+ BigInteger u = a;
+ BigInteger v = BigInteger.ONE;
+ for (int bit = q.bitLength() - 2; bit >= 0; bit--) {
+ // Compute (u + v sqrt(d))^2
+ BigInteger tmp = u.multiply(v);
+ u = u.multiply(u).add(v.multiply(v).mod(p).multiply(d)).mod(p);
+ v = tmp.add(tmp).mod(p);
+ if (q.testBit(bit)) {
+ tmp = u.multiply(a).add(v.multiply(d)).mod(p);
+ v = a.multiply(v).add(u).mod(p);
+ u = tmp;
+ }
+ }
+ squareRoot = u;
+ }
+ // The methods used to compute the square root only guarantee a correct result if the
+ // preconditions (i.e. p prime and x is a square) are satisfied. Otherwise the value is
+ // undefined. Hence, it is important to verify that squareRoot is indeed a square root.
+ if (squareRoot != null && squareRoot.multiply(squareRoot).mod(p).compareTo(x) != 0) {
+ throw new GeneralSecurityException("Could not find square root");
+ }
+ return squareRoot;
+ }
+
+ /**
+ * Returns the modulus of the field used by the curve specified in ecParams.
+ *
+ * @param curve must be a prime order elliptic curve
+ * @return the order of the finite field over which curve is defined.
+ */
+ public static BigInteger getModulus(EllipticCurve curve) throws GeneralSecurityException {
+ java.security.spec.ECField field = curve.getField();
+ if (field instanceof java.security.spec.ECFieldFp) {
+ return ((java.security.spec.ECFieldFp) field).getP();
+ } else {
+ throw new GeneralSecurityException("Only curves over prime order fields are supported");
+ }
+ }
+
+ /**
+ * Returns the size of an element of the field over which the curve is defined.
+ *
+ * @param curve must be a prime order elliptic curve
+ * @return the size of an element in bits
+ */
+ public static int fieldSizeInBits(EllipticCurve curve) throws GeneralSecurityException {
+ return getModulus(curve).subtract(BigInteger.ONE).bitLength();
+ }
+
+ /**
+ * Returns the size of an element of the field over which the curve is defined.
+ *
+ * @param curve must be a prime order elliptic curve
+ * @return the size of an element in bytes.
+ */
+ public static int fieldSizeInBytes(EllipticCurve curve) throws GeneralSecurityException {
+ return (fieldSizeInBits(curve) + 7) / 8;
+ }
+
+ /**
+ * Checks that a point is on a given elliptic curve. This method implements the partial public key
+ * validation routine from Section 5.6.2.6 of NIST SP 800-56A
+ * http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf A partial
+ * public key validation is sufficient for curves with cofactor 1. See Section B.3 of
+ * http://www.nsa.gov/ia/_files/SuiteB_Implementer_G-113808.pdf The point validations above are
+ * taken from recommendations for ECDH, because parameter checks in ECDH are much more important
+ * than for the case of ECDSA. Performing this test for ECDSA keys is mainly a sanity check.
+ *
+ * @param point the point that needs verification
+ * @param ec the elliptic curve. This must be a curve over a prime order field.
+ * @throws GeneralSecurityException if the field is binary or if the point is not on the curve.
+ */
+ public static void checkPointOnCurve(ECPoint point, EllipticCurve ec)
+ throws GeneralSecurityException {
+ BigInteger p = getModulus(ec);
+ BigInteger x = point.getAffineX();
+ BigInteger y = point.getAffineY();
+ if (x == null || y == null) {
+ throw new GeneralSecurityException("point is at infinity");
+ }
+ // Check 0 <= x < p and 0 <= y < p.
+ if (x.signum() == -1 || x.compareTo(p) != -1) {
+ throw new GeneralSecurityException("x is out of range");
+ }
+ if (y.signum() == -1 || y.compareTo(p) != -1) {
+ throw new GeneralSecurityException("y is out of range");
+ }
+ // Check y^2 == x^3 + a x + b (mod p)
+ BigInteger lhs = y.multiply(y).mod(p);
+ BigInteger rhs = x.multiply(x).add(ec.getA()).multiply(x).add(ec.getB()).mod(p);
+ if (!lhs.equals(rhs)) {
+ throw new GeneralSecurityException("Point is not on curve");
+ }
+ }
+
+ /**
+ * Checks a public key. I.e. this checks that the point defining the public key is on the curve.
+ *
+ * @param key must be a key defined over a curve using a prime order field.
+ * @throws GeneralSecurityException if the key is not valid.
+ */
+ public static void checkPublicKey(ECPublicKey key) throws GeneralSecurityException {
+ checkPointOnCurve(key.getW(), key.getParams().getCurve());
+ }
+
+ /**
+ * Decompress a point
+ *
+ * @param x The x-coordinate of the point
+ * @param bit0 true if the least significant bit of y is set.
+ * @param ecParams contains the curve of the point. This must be over a prime order field.
+ */
+ public static ECPoint getPoint(BigInteger x, boolean bit0, ECParameterSpec ecParams)
+ throws GeneralSecurityException {
+ EllipticCurve ec = ecParams.getCurve();
+ ECField field = ec.getField();
+ if (!(field instanceof ECFieldFp)) {
+ throw new GeneralSecurityException("Only curves over prime order fields are supported");
+ }
+ BigInteger p = ((java.security.spec.ECFieldFp) field).getP();
+ if (x.compareTo(BigInteger.ZERO) == -1 || x.compareTo(p) != -1) {
+ throw new GeneralSecurityException("x is out of range");
+ }
+ // Compute rhs == x^3 + a x + b (mod p)
+ BigInteger rhs = x.multiply(x).add(ec.getA()).multiply(x).add(ec.getB()).mod(p);
+ BigInteger y = modSqrt(rhs, p);
+ if (bit0 != y.testBit(0)) {
+ y = p.subtract(y).mod(p);
+ }
+ return new ECPoint(x, y);
+ }
+
+ /**
+ * Decompress a point on an elliptic curve.
+ *
+ * @param bytes The compressed point. Its representation is z || x where z is 2+lsb(y) and x is
+ * using a unsigned fixed length big-endian representation.
+ * @param ecParams the specification of the curve. Only Weierstrass curves over prime order fields
+ * are implemented.
+ */
+ public static ECPoint decompressPoint(byte[] bytes, ECParameterSpec ecParams)
+ throws GeneralSecurityException {
+ EllipticCurve ec = ecParams.getCurve();
+ ECField field = ec.getField();
+ if (!(field instanceof ECFieldFp)) {
+ throw new GeneralSecurityException("Only curves over prime order fields are supported");
+ }
+ BigInteger p = ((java.security.spec.ECFieldFp) field).getP();
+ int expectedLength = 1 + (p.bitLength() + 7) / 8;
+ if (bytes.length != expectedLength) {
+ throw new GeneralSecurityException("compressed point has wrong length");
+ }
+ boolean lsb;
+ switch (bytes[0]) {
+ case 2:
+ lsb = false;
+ break;
+ case 3:
+ lsb = true;
+ break;
+ default:
+ throw new GeneralSecurityException("Invalid format");
+ }
+ BigInteger x = new BigInteger(1, Arrays.copyOfRange(bytes, 1, bytes.length));
+ if (x.compareTo(BigInteger.ZERO) == -1 || x.compareTo(p) != -1) {
+ throw new GeneralSecurityException("x is out of range");
+ }
+ // Compute rhs == x^3 + a x + b (mod p)
+ BigInteger rhs = x.multiply(x).add(ec.getA()).multiply(x).add(ec.getB()).mod(p);
+ BigInteger y = modSqrt(rhs, p);
+ if (lsb != y.testBit(0)) {
+ y = p.subtract(y).mod(p);
+ }
+ return new ECPoint(x, y);
+ }
+
+ /**
+ * Returns a weak public key of order 3 such that the public key point is on the curve specified
+ * in ecParams. This method is used to check ECC implementations for missing step in the
+ * verification of the public key. E.g. implementations of ECDH must verify that the public key
+ * contains a point on the curve as well as public and secret key are using the same curve.
+ *
+ * @param ecParams the parameters of the key to attack. This must be a curve in Weierstrass form
+ * over a prime order field.
+ * @return a weak EC group with a genrator of order 3.
+ */
+ public static ECPublicKeySpec getWeakPublicKey(ECParameterSpec ecParams)
+ throws GeneralSecurityException {
+ EllipticCurve curve = ecParams.getCurve();
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ keyGen.initialize(ecParams);
+ BigInteger p = getModulus(curve);
+ BigInteger three = new BigInteger("3");
+ while (true) {
+ // Generate a point on the original curve
+ KeyPair keyPair = keyGen.generateKeyPair();
+ ECPublicKey pub = (ECPublicKey) keyPair.getPublic();
+ ECPoint w = pub.getW();
+ BigInteger x = w.getAffineX();
+ BigInteger y = w.getAffineY();
+ // Find the curve parameters a,b such that 3*w = infinity.
+ // This is the case if the following equations are satisfied:
+ // 3x == l^2 (mod p)
+ // l == (3x^2 + a) / 2*y (mod p)
+ // y^2 == x^3 + ax + b (mod p)
+ BigInteger l;
+ try {
+ l = modSqrt(x.multiply(three), p);
+ } catch (GeneralSecurityException ex) {
+ continue;
+ }
+ BigInteger xSqr = x.multiply(x).mod(p);
+ BigInteger a = l.multiply(y.add(y)).subtract(xSqr.multiply(three)).mod(p);
+ BigInteger b = y.multiply(y).subtract(x.multiply(xSqr.add(a))).mod(p);
+ EllipticCurve newCurve = new EllipticCurve(curve.getField(), a, b);
+ // Just a sanity check.
+ checkPointOnCurve(w, newCurve);
+ // Cofactor and order are of course wrong.
+ ECParameterSpec spec = new ECParameterSpec(newCurve, w, p, 1);
+ return new ECPublicKeySpec(w, spec);
+ }
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/JsonUtil.java b/keystore-cts/java/com/google/security/wycheproof/JsonUtil.java
new file mode 100644
index 0000000..6ff9e2f
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/JsonUtil.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.stream.JsonReader;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.math.BigInteger;
+
+/** Utilities for reading test vectors in JSON format */
+public class JsonUtil {
+
+ /**
+ * Reads a set of test vectors from a file.
+ * @param filename the name of the file, local to the directory with the
+ * the test vectors.
+ * @return a JsonObject with a test
+ * @throws IOException if the test vectors could not be read.
+ * @throws JsonParseException if the file is not valid JSON.
+ */
+ public static JsonObject getTestVectors(Class ref, String filename) throws
+ IOException {
+ // The directory where the test vectors are.
+ String testVectorsDir = "/";
+ try (InputStream is = ref.getResourceAsStream(testVectorsDir + filename)) {
+ JsonReader reader = new JsonReader(new InputStreamReader(is, UTF_8));
+ JsonParser parser = new JsonParser();
+ JsonElement elem = parser.parse(reader);
+ return elem.getAsJsonObject();
+ }
+ }
+
+ /**
+ * Converts a JsonElement into a byte array.
+ * @param element a JsonElement containing an encoded byte array.
+ * Wycheproof represents byte arrays as hexadeciamal strings.
+ * @throws ClassCastException if element is not a valid string value.
+ * @throws IllegalStateException - if element contains an array.
+ */
+ public static byte[] asByteArray(JsonElement element) {
+ String hex = element.getAsString();
+ return TestUtil.hexToBytes(hex);
+ }
+
+ /**
+ * Converts a JsonElement into a BigInteger.
+ * @param element a JsonElement containing a BigInteger.
+ * Wycheproof represents BigIntegers as hexadecimal strings using
+ * twos complement representation.
+ * <p> E.g., 31 is represented as "1f", -1 is represented as "f", and
+ * 255 is represented as "0ff".
+ * @throws ClassCastException if element is not a valid string value.
+ * @throws IllegalStateException if element contains an array.
+ * @throws NumberFormatException if representation of the BigInteger is invalid.
+ */
+ public static BigInteger asBigInteger(JsonElement element) {
+ String hex = element.getAsString();
+ return asBigInteger(hex);
+ }
+ public static BigInteger asBigInteger(String hex) {
+ // TODO(bleichen): Consider to change the representation of BigIntegers in
+ // Wycheproof as hexadecimal string with a sign.
+ if (hex.length() % 2 == 1) {
+ if (hex.charAt(0) >= '0' && hex.charAt(0) <= '7') {
+ hex = "0" + hex;
+ } else {
+ hex = "f" + hex;
+ }
+ }
+ return new BigInteger(TestUtil.hexToBytes(hex));
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/TestUtil.java b/keystore-cts/java/com/google/security/wycheproof/TestUtil.java
new file mode 100644
index 0000000..213bf5e
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/TestUtil.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import java.nio.ByteBuffer;
+import java.security.Provider;
+import java.security.Security;
+
+/** Test utilities */
+public class TestUtil {
+ public static final String EXPECTED_PROVIDER_NAME = "AndroidKeyStore";
+ public static final String EXPECTED_CRYPTO_OP_PROVIDER_NAME = "AndroidKeyStoreBCWorkaround";
+
+ public static String bytesToHex(byte[] bytes) {
+ // bytesToHex is used to convert output from Cipher.
+ // cipher.update can return null, which is equivalent to returning
+ // no plaitext rsp. ciphertext.
+ if (bytes == null) {
+ return "";
+ }
+ String chars = "0123456789abcdef";
+ StringBuilder result = new StringBuilder(2 * bytes.length);
+ for (byte b : bytes) {
+ // convert to unsigned
+ int val = b & 0xff;
+ result.append(chars.charAt(val / 16));
+ result.append(chars.charAt(val % 16));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a hexadecimal representation of the bytes written to ByteBuffer (i.e. all the bytes
+ * before position()).
+ */
+ public static String byteBufferToHex(ByteBuffer buffer) {
+ ByteBuffer tmp = buffer.duplicate();
+ tmp.flip();
+ byte[] bytes = new byte[tmp.remaining()];
+ tmp.get(bytes);
+ return bytesToHex(bytes);
+ }
+
+ public static byte[] hexToBytes(String hex) throws IllegalArgumentException {
+ if (hex.length() % 2 != 0) {
+ throw new IllegalArgumentException("Expected a string of even length");
+ }
+ int size = hex.length() / 2;
+ byte[] result = new byte[size];
+ for (int i = 0; i < size; i++) {
+ int hi = Character.digit(hex.charAt(2 * i), 16);
+ int lo = Character.digit(hex.charAt(2 * i + 1), 16);
+ if ((hi == -1) || (lo == -1)) {
+ throw new IllegalArgumentException("input is not hexadecimal");
+ }
+ result[i] = (byte) (16 * hi + lo);
+ }
+ return result;
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java
new file mode 100644
index 0000000..d02b4f1
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/AesGcmTest.java
@@ -0,0 +1,984 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import java.nio.ByteBuffer;
+import java.security.AlgorithmParameterGenerator;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.ShortBufferException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Test;
+import org.junit.Ignore;
+import org.junit.Before;
+import android.security.keystore.KeyProtection;
+import android.security.keystore.KeyProperties;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.UnrecoverableKeyException;
+
+// TODO(bleichen):
+// - For EAX I was able to derive some special cases by inverting OMAC.
+// Not sure if that is possible here.
+/**
+ * Testing AES-GCM
+ *
+ * <p>Other tests using AES-GCM are: CipherInputStreamTest.java CipherOutputStreamTest.java
+ */
+public class AesGcmTest {
+ private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
+ private KeyStore keyStore;
+
+ @Before
+ public void setup() throws Exception {
+ keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ for (GcmTestVector test : GCM_TEST_VECTORS) {
+ setKeystoreEntry(test.alias, test.key);
+ }
+ }
+
+ private SecretKey setKeystoreEntry(String alias, SecretKeySpec key)
+ throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {
+ keyStore.setEntry(
+ alias,
+ new KeyStore.SecretKeyEntry(key),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_ECB, KeyProperties.BLOCK_MODE_GCM)
+ .setRandomizedEncryptionRequired(false)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ // Key imported, obtain a reference to it.
+ return (SecretKey) keyStore.getKey(alias, null);
+ }
+
+ /** Test vectors */
+ public static class GcmTestVector {
+ final byte[] pt;
+ final byte[] aad;
+ final byte[] ct;
+ final String ptHex;
+ final String ctHex;
+ final GCMParameterSpec parameters;
+ final SecretKeySpec key;
+ final int nonceLengthInBits;
+ final int tagLengthInBits;
+ final String alias;
+
+ public GcmTestVector(
+ String message,
+ String keyMaterial,
+ String nonce,
+ String aad,
+ String ciphertext,
+ String tag,
+ String alias) {
+ this.ptHex = message;
+ this.pt = TestUtil.hexToBytes(message);
+ this.aad = TestUtil.hexToBytes(aad);
+ this.ct = TestUtil.hexToBytes(ciphertext + tag);
+ this.ctHex = ciphertext + tag;
+ this.tagLengthInBits = 4 * tag.length();
+ this.nonceLengthInBits = 4 * nonce.length();
+ this.parameters = new GCMParameterSpec(tagLengthInBits, TestUtil.hexToBytes(nonce));
+ this.key = new SecretKeySpec(TestUtil.hexToBytes(keyMaterial), "AES");
+ this.alias = alias;
+ }
+ };
+
+ private static final GcmTestVector[] GCM_TEST_VECTORS = {
+ new GcmTestVector(
+ "001d0c231287c1182784554ca3a21908",
+ "5b9604fe14eadba931b0ccf34843dab9",
+ "028318abc1824029138141a2",
+ "",
+ "26073cc1d851beff176384dc9896d5ff",
+ "0a3ea7a5487cb5f7d70fb6c58d038554", "Key1"),
+ new GcmTestVector(
+ "001d0c231287c1182784554ca3a21908",
+ "5b9604fe14eadba931b0ccf34843dab9",
+ "921d2507fa8007b7bd067d34",
+ "00112233445566778899aabbccddeeff",
+ "49d8b9783e911913d87094d1f63cc765",
+ "1e348ba07cca2cf04c618cb4", "Key2"),
+ new GcmTestVector(
+ "2035af313d1346ab00154fea78322105",
+ "aa023d0478dcb2b2312498293d9a9129",
+ "0432bc49ac34412081288127",
+ "aac39231129872a2",
+ "eea945f3d0f98cc0fbab472a0cf24e87",
+ "4bb9b4812519dadf9e1232016d068133", "Key3"),
+ new GcmTestVector(
+ "2035af313d1346ab00154fea78322105",
+ "aa023d0478dcb2b2312498293d9a9129",
+ "0432bc49ac344120",
+ "aac39231129872a2",
+ "64c36bb3b732034e3a7d04efc5197785",
+ "b7d0dd70b00d65b97cfd080ff4b819d1", "Key4"),
+ new GcmTestVector(
+ "02efd2e5782312827ed5d230189a2a342b277ce048462193",
+ "2034a82547276c83dd3212a813572bce",
+ "3254202d854734812398127a3d134421",
+ "1a0293d8f90219058902139013908190bc490890d3ff12a3",
+ "64069c2d58690561f27ee199e6b479b6369eec688672bde9",
+ "9b7abadd6e69c1d9ec925786534f5075", "Key5"),
+ // GCM uses GHASH to compute the initial counter J0 if the nonce is not 12 bytes long.
+ // The counter is incremented modulo 2^32 in counter mode. The following test vectors verify
+ // the behavior of an implementation for initial counter values J0 close to a 2^32 limit.
+ // J0:00000000000000000000000000000000
+ new GcmTestVector(
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "00112233445566778899aabbccddeeff",
+ "7b95b8c356810a84711d68150a1b7750",
+ "",
+ "84d4c9c08b4f482861e3a9c6c35bc4d91df927374513bfd49f436bd73f325285daef4ff7e13d46a6",
+ "213a3cb93855d18e69337eee66aeec07", "Key6"),
+ // J0:ffffffffffffffffffffffffffffffff
+ new GcmTestVector(
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "00112233445566778899aabbccddeeff",
+ "1a552e67cdc4dc1a33b824874ebf0bed",
+ "",
+ "948ca37a8e6649e88aeffb1c598f3607007702417ea0e0bc3c60ad5a949886de968cf53ea6462aed",
+ "99b381bfa2af9751c39d1b6e86d1be6a", "Key6"),
+ // J0:000102030405060708090a0bffffffff
+ new GcmTestVector(
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "00112233445566778899aabbccddeeff",
+ "99821c2dd5daecded07300f577f7aff1",
+ "",
+ "127af9b39ecdfc57bb11a2847c7c2d3d8f938f40f877e0c4af37d0fe9af033052bd537c4ae978f60",
+ "07eb2fe4a958f8434d40684899507c7c", "Key7"),
+ // J0:000102030405060708090a0bfffffffe
+ new GcmTestVector(
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "00112233445566778899aabbccddeeff",
+ "5e4a3900142358d1c774d8d124d8d27d",
+ "",
+ "0cf6ae47156b14dce03c8a07a2e172b1127af9b39ecdfc57bb11a2847c7c2d3d8f938f40f877e0c4",
+ "f145c2dcaf339eede427be934357eac0", "Key8"),
+ };
+
+ /**
+ * Returns the GCM test vectors supported by the current provider. This is necessary since not
+ * every provider supports all parameters sizes. For example SUNJCE does not support 8 byte tags
+ * and Conscrypt only supports 12 byte nonces. Such restrictions are often made because AES-GCM is
+ * a relatively weak algorithm and especially small parameter sizes can lead to easy attacks.
+ * Avoiding such small parameter sizes should not be seen as a bug in the library.
+ *
+ * <p>The only assumption we make here is that all test vectors with 128 bit tags and nonces with
+ * at least 96 bits are supported.
+ */
+ private Iterable<GcmTestVector> getTestVectors() throws Exception {
+ ArrayList<GcmTestVector> supported = new ArrayList<GcmTestVector>();
+ for (GcmTestVector test : GCM_TEST_VECTORS) {
+ if (test.nonceLengthInBits != 96 || test.tagLengthInBits != 128) {
+ try {
+ // Checks whether the parameter size is supported.
+ // It would be nice if there was a way to check this without trying to encrypt.
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException ex) {
+ // Not supported
+ continue;
+ }
+ }
+ supported.add(test);
+ }
+ return supported;
+ }
+
+ @Test
+ public void testVectors() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(test.aad);
+ byte[] ct = cipher.doFinal(test.pt);
+ assertEquals(test.ctHex, TestUtil.bytesToHex(ct));
+ }
+ }
+
+ /** Test encryption when update and doFinal are done with empty byte arrays. */
+ @Test
+ public void testEncryptWithEmptyArrays() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ // Encryption
+ byte[] empty = new byte[0];
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.pt.length);
+ ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(empty);
+ cipher.updateAAD(test.aad);
+ byte[] res = cipher.update(empty);
+ if (res != null) {
+ ctBuffer.put(res);
+ }
+ res = cipher.update(test.pt);
+ if (res != null) {
+ ctBuffer.put(res);
+ }
+ res = cipher.doFinal(empty);
+ if (res != null) {
+ ctBuffer.put(res);
+ }
+ assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
+ }
+ }
+
+ @Test
+ public void testDecryptWithEmptyArrays() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ byte[] empty = new byte[0];
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.ct.length);
+ ByteBuffer ptBuffer = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(empty);
+ cipher.updateAAD(test.aad);
+ byte[] res = cipher.update(empty);
+ if (res != null) {
+ ptBuffer.put(res);
+ }
+ res = cipher.update(test.ct);
+ if (res != null) {
+ ptBuffer.put(res);
+ }
+ res = cipher.doFinal(empty);
+ if (res != null) {
+ ptBuffer.put(res);
+ }
+ assertEquals(test.ptHex, TestUtil.byteBufferToHex(ptBuffer));
+
+ // Simple test that a modified ciphertext fails.
+ ptBuffer.clear();
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(empty);
+ cipher.updateAAD(test.aad);
+ cipher.updateAAD(new byte[1]);
+ res = cipher.update(empty);
+ if (res != null) {
+ ptBuffer.put(res);
+ }
+ res = cipher.update(test.ct);
+ if (res != null) {
+ ptBuffer.put(res);
+ }
+ try {
+ cipher.doFinal(empty);
+ fail("Accepted modified ciphertext.");
+ } catch (GeneralSecurityException ex) {
+ // Expected
+ }
+ }
+ }
+
+ /**
+ * Typically one should always call updateAAD before any call to update. This test checks what
+ * happens if the order is reversed. The test expects that a correct implementation either
+ * computes the tag correctly or throws an exception.
+ *
+ * <p>For example, OpenJdk did compute incorrect tags in this case. The bug has been fixed in
+ * http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/89c06ca1e6cc
+ *
+ * <p>For example BouncyCastle computes correct tags if the calls are reversed, SunJCE and OpenJdk
+ * now throw exceptions.
+ */
+ @Test
+ public void testLateUpdateAAD() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ byte[] c0 = cipher.update(test.pt);
+ try {
+ cipher.updateAAD(test.aad);
+ } catch (java.lang.IllegalStateException ex) {
+ // Throwing an exception is valid behaviour.
+ continue;
+ }
+ byte[] c1 = cipher.doFinal();
+ String result = TestUtil.bytesToHex(c0) + TestUtil.bytesToHex(c1);
+ assertEquals(test.ctHex, result);
+ }
+ }
+
+ /**
+ * JCE has a dangerous feature: after a doFinal the cipher is typically reinitialized using the
+ * previous IV. This "feature" can easily break AES-GCM usages, because encrypting twice with the
+ * same key and IV leaks the authentication key. Hence any reasonable implementation of AES-GCM
+ * should not allow this. The expected behaviour of OpenJDK can be derived from the tests in
+ * jdk/test/com/sun/crypto/provider/Cipher/AES/TestGCMKeyAndIvCheck.java. OpenJDK does not allow
+ * two consecutive initializations for encryption with the same key and IV.
+ *
+ * <p>The test here is weaker than the restrictions in OpenJDK. The only requirement here is that
+ * reusing a Cipher without an explicit init() is caught.
+ *
+ * <p>BouncyCastle 1.52 failed this test
+ *
+ * <p>Conscrypt failed this test
+ */
+ @Test
+ public void testIvReuse() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(test.aad);
+ byte[] ct1 = cipher.doFinal(test.pt);
+ try {
+ byte[] ct2 = cipher.doFinal(test.pt);
+ fail(
+ "It should not possible to reuse an IV."
+ + " ct1:"
+ + TestUtil.bytesToHex(ct1)
+ + " ct2:"
+ + TestUtil.bytesToHex(ct2));
+ } catch (java.lang.IllegalStateException ex) {
+ // This is expected.
+ }
+ }
+ }
+
+ /**
+ * Checks whether the implementation requires larger ByteBuffers than necessary. This test has
+ * been added mostly for debugging. E.g., conscrypt failed during decryption with ByteBuffers
+ * simply because the necessary outputSize was computed incorrectly.
+ */
+ @Test
+ public void testByteBufferSize() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ // Encryption
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.pt.length);
+ assertEquals("plaintext size:" + test.pt.length, test.ct.length, outputSize);
+ // Decryption
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ outputSize = cipher.getOutputSize(test.ct.length);
+ assertEquals("ciphertext size:" + test.ct.length, test.pt.length, outputSize);
+ }
+ }
+
+ /** Encryption with ByteBuffers. */
+ @Test
+ public void testByteBuffer() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ // Encryption
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.pt.length);
+ ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ptBuffer, ctBuffer);
+ assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
+
+ // Decryption
+ ctBuffer.flip();
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ outputSize = cipher.getOutputSize(test.ct.length);
+ ByteBuffer decrypted = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ctBuffer, decrypted);
+ assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted));
+ }
+ }
+
+ /** Encryption with ByteBuffers should be copy-safe. */
+ @Test
+ public void testByteBufferAlias() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ // Encryption
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.pt.length);
+ byte[] backingArray = new byte[outputSize];
+ ByteBuffer ptBuffer = ByteBuffer.wrap(backingArray);
+ ptBuffer.put(test.pt);
+ ptBuffer.flip();
+ ByteBuffer ctBuffer = ByteBuffer.wrap(backingArray);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ptBuffer, ctBuffer);
+ assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
+
+ // Decryption
+ ByteBuffer decrypted = ByteBuffer.wrap(backingArray);
+ ctBuffer.flip();
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ctBuffer, decrypted);
+ assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted));
+ }
+ }
+
+ /** Encryption and decryption with large arrays should be copy-safe. */
+ @Test
+ public void testLargeArrayAlias() throws Exception {
+ byte[] ptVector = new byte[8192];
+
+ // this offset is relative to the start of the input, not the start of the buffer.
+ for (int outputOffset = -32; outputOffset <= 32; outputOffset++) {
+ // try with doFinal directly as well as with update followed by doFinal
+ for (int useUpdate = 0; useUpdate <= 1; useUpdate++) {
+ SecretKey secretKey = null;
+ try {
+ String alias = "TestKey" + 1;
+ SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES");
+ secretKey = setKeystoreEntry(alias, keySpec);
+ } catch (Exception e) {
+ fail("Failed to set secret key entry in KeyStore.");
+ }
+ GCMParameterSpec parameters = new GCMParameterSpec(128, new byte[12]);
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameters);
+
+ // these offsets are relative to the start of the buffer
+ int inputOffsetInBuffer = 32;
+ int outputOffsetInBuffer = inputOffsetInBuffer + outputOffset;
+ int sliceLength = cipher.getOutputSize(ptVector.length);
+
+ byte[] inBuf = new byte[sliceLength + Math.max(inputOffsetInBuffer, outputOffsetInBuffer)];
+ byte[] outBuf = inBuf;
+
+ System.arraycopy(ptVector, 0, inBuf, inputOffsetInBuffer, ptVector.length);
+
+ try {
+ int ctLength = 0;
+ if (useUpdate > 0) {
+ ctLength +=
+ cipher.update(
+ inBuf, inputOffsetInBuffer, ptVector.length, outBuf, outputOffsetInBuffer);
+ ctLength += cipher.doFinal(inBuf, 0, 0, outBuf, outputOffsetInBuffer + ctLength);
+ } else {
+ ctLength +=
+ cipher.doFinal(
+ inBuf, inputOffsetInBuffer, ptVector.length, outBuf, outputOffsetInBuffer);
+ }
+
+ System.arraycopy(outBuf, outputOffsetInBuffer, inBuf, inputOffsetInBuffer, ctLength);
+
+ cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, parameters);
+
+ int resultPtLength = 0;
+ if (useUpdate > 0) {
+ resultPtLength +=
+ cipher.update(inBuf, inputOffsetInBuffer, ctLength, outBuf, outputOffsetInBuffer);
+ resultPtLength +=
+ cipher.doFinal(inBuf, 0, 0, outBuf, outputOffsetInBuffer + resultPtLength);
+ } else {
+ resultPtLength +=
+ cipher.doFinal(inBuf, inputOffsetInBuffer, ctLength, outBuf, outputOffsetInBuffer);
+ }
+
+ assertEquals(resultPtLength, ptVector.length);
+ assertArrayEquals(
+ ptVector,
+ Arrays.copyOfRange(
+ outBuf, outputOffsetInBuffer, outputOffsetInBuffer + resultPtLength));
+ } catch (Throwable t) {
+ throw new AssertionError(
+ "testLargeByteBufferAlias failed with outputOffset=" + outputOffset, t);
+ }
+ }
+ }
+ }
+
+ /**
+ * Encryption with ByteBuffers should be copy-safe even if the buffers have different starting
+ * offsets and/or do not make the backing array visible.
+ *
+ * <p>Note that bugs in this often require a sizeable input to reproduce; the default
+ * implementation of engineUpdate(ByteBuffer, ByteBuffer) copies through 4KB bounce buffers, so we
+ * need to use something larger to see any problems - 8KB is what we use here.
+ *
+ * @see https://bugs.openjdk.java.net/browse/JDK-8181386
+ */
+ @Test
+ public void testByteBufferShiftedAlias() throws Exception {
+ byte[] ptVector = new byte[8192];
+
+ for (int i = 0; i < 3; i++) {
+ // outputOffset = offset relative to start of input.
+ for (int outputOffset = -1; outputOffset <= 1; outputOffset++) {
+
+ SecretKey secretKey = null;
+ try {
+ String alias = "TestKey" + 1;
+ SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES");
+ secretKey = setKeystoreEntry(alias, keySpec);
+ } catch (Exception e) {
+ fail("Failed to set secret key entry in KeyStore.");
+ }
+ GCMParameterSpec parameters = new GCMParameterSpec(128, new byte[12]);
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameters);
+
+ ByteBuffer output, input, inputRO;
+
+ // We'll try three scenarios: Ordinary array backed buffers, array backed buffers where one
+ // is read-only, and direct byte buffers.
+ String mode;
+ // offsets relative to start of buffer
+ int inputOffsetInBuffer = 1;
+ int outputOffsetInBuffer = inputOffsetInBuffer + outputOffset;
+ int sliceLength = cipher.getOutputSize(ptVector.length);
+ int bufferSize = sliceLength + Math.max(inputOffsetInBuffer, outputOffsetInBuffer);
+ switch (i) {
+ case 0:
+ case 1:
+ {
+ byte[] buffer = new byte[bufferSize];
+ // It's important to slice() here as otherwise later when we flip() position will be
+ // reset to 0.
+ output = ByteBuffer.wrap(buffer, outputOffsetInBuffer, sliceLength).slice();
+ input = ByteBuffer.wrap(buffer, inputOffsetInBuffer, sliceLength).slice();
+
+ if (i == 1) {
+ mode = "array backed buffers with RO buffer";
+ inputRO = input.asReadOnlyBuffer();
+ } else {
+ mode = "array backed buffers";
+ inputRO = input.duplicate();
+ }
+
+ break;
+ }
+ case 2:
+ {
+ mode = "direct buffers";
+ ByteBuffer buf = ByteBuffer.allocateDirect(bufferSize);
+ output = buf.duplicate();
+ output.position(outputOffsetInBuffer);
+ output.limit(sliceLength + outputOffsetInBuffer);
+ output = output.slice();
+
+ input = buf.duplicate();
+ input.position(inputOffsetInBuffer);
+ input.limit(sliceLength + inputOffsetInBuffer);
+ input = input.slice();
+
+ inputRO = input.duplicate();
+ break;
+ }
+ default:
+ {
+ throw new AssertionError("Unknown test index " + i);
+ }
+ }
+
+ // Now that we have our overlapping 'input' and 'output' buffers, we can write our plaintext
+ // into the input buffer.
+ input.put(ptVector);
+ input.flip();
+ // Make sure the RO input buffer has the same limit in case the plaintext is shorter than
+ // sliceLength (which it generally will be for anything other than ECB or CTR mode)
+ inputRO.limit(input.limit());
+
+ try {
+ int ctSize = cipher.doFinal(inputRO, output);
+
+ // Now flip the buffers around and undo everything
+ byte[] tmp = new byte[ctSize];
+ output.flip();
+ output.get(tmp);
+
+ output.clear();
+ input.clear();
+ inputRO.clear();
+
+ input.put(tmp);
+ input.flip();
+ inputRO.limit(input.limit());
+
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, parameters);
+ cipher.doFinal(inputRO, output);
+
+ output.flip();
+ assertEquals(ByteBuffer.wrap(ptVector), output);
+ } catch (Throwable t) {
+ throw new AssertionError(
+ "Overlapping buffers test failed with buffer type: "
+ + mode
+ + " and output offset "
+ + outputOffset,
+ t);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testReadOnlyByteBuffer() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ // Encryption
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt).asReadOnlyBuffer();
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.pt.length);
+ ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ptBuffer, ctBuffer);
+ assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
+
+ // Decryption
+ ctBuffer.flip();
+ ctBuffer = ctBuffer.asReadOnlyBuffer();
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ outputSize = cipher.getOutputSize(test.ct.length);
+ ByteBuffer decrypted = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ctBuffer, decrypted);
+ assertEquals(test.ptHex, TestUtil.byteBufferToHex(decrypted));
+ }
+ }
+
+ /**
+ * If a ByteBuffer is backed by an array and not readonly, then it is possible to access the data
+ * through the .array() method. An implementation using this possibility must ensure that it
+ * considers the offset.
+ */
+ @Test
+ public void testByteBufferWithOffset() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ // Encryption
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ ByteBuffer ptBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]);
+ ptBuffer.position(5);
+ ptBuffer = ptBuffer.slice();
+ ptBuffer.put(test.pt);
+ ptBuffer.flip();
+
+ ByteBuffer ctBuffer = ByteBuffer.wrap(new byte[test.ct.length + 50]);
+ ctBuffer.position(8);
+ ctBuffer = ctBuffer.slice();
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ptBuffer, ctBuffer);
+ assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
+ ctBuffer.flip();
+
+ // Decryption
+ ByteBuffer decBuffer = ByteBuffer.wrap(new byte[test.pt.length + 50]);
+ decBuffer.position(6);
+ decBuffer = decBuffer.slice();
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(test.aad);
+ cipher.doFinal(ctBuffer, decBuffer);
+ assertEquals(test.ptHex, TestUtil.byteBufferToHex(decBuffer));
+ }
+ }
+
+ @Test
+ public void testByteBufferTooShort() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ // Encryption
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt);
+ ByteBuffer ctBuffer = ByteBuffer.allocate(test.ct.length - 1);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(test.aad);
+ try {
+ cipher.doFinal(ptBuffer, ctBuffer);
+ fail("This should not work");
+ } catch (ShortBufferException ex) {
+ // expected
+ }
+
+ // Decryption
+ ctBuffer = ByteBuffer.wrap(test.ct);
+ ByteBuffer decrypted = ByteBuffer.allocate(test.pt.length - 1);
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(test.aad);
+ try {
+ cipher.doFinal(ctBuffer, decrypted);
+ fail("This should not work");
+ } catch (ShortBufferException ex) {
+ // expected
+ }
+ }
+ }
+
+ /**
+ * Test encryption when update and doFinal are done with empty ByteBuffers. Conscrypt ignored
+ * calls to doFinal() when the ByteBuffer was empty.
+ */
+ @Test
+ public void testEncryptWithEmptyByteBuffer() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ // Encryption
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ ByteBuffer empty = ByteBuffer.allocate(0);
+ ByteBuffer ptBuffer = ByteBuffer.wrap(test.pt);
+ cipher.init(Cipher.ENCRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.pt.length);
+ ByteBuffer ctBuffer = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(empty);
+ cipher.updateAAD(test.aad);
+ cipher.update(empty, ctBuffer);
+ cipher.update(ptBuffer, ctBuffer);
+ cipher.doFinal(empty, ctBuffer);
+ assertEquals(test.ctHex, TestUtil.byteBufferToHex(ctBuffer));
+ }
+ }
+
+ @Test
+ public void testDecryptWithEmptyBuffer() throws Exception {
+ for (GcmTestVector test : getTestVectors()) {
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ ByteBuffer empty = ByteBuffer.allocate(0);
+ ByteBuffer ctBuffer = ByteBuffer.wrap(test.ct);
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ int outputSize = cipher.getOutputSize(test.ct.length);
+ ByteBuffer ptBuffer = ByteBuffer.allocate(outputSize);
+ cipher.updateAAD(empty);
+ cipher.updateAAD(test.aad);
+ cipher.update(empty, ptBuffer);
+ cipher.update(ctBuffer, ptBuffer);
+ cipher.doFinal(empty, ptBuffer);
+ assertEquals(test.ptHex, TestUtil.byteBufferToHex(ptBuffer));
+
+ // Simple test that a modified ciphertext fails.
+ ctBuffer.flip();
+ ptBuffer.clear();
+ cipher.init(Cipher.DECRYPT_MODE, (SecretKey) keyStore.getKey(test.alias, null), test.parameters);
+ cipher.updateAAD(empty);
+ cipher.updateAAD(test.aad);
+ cipher.updateAAD(new byte[1]);
+ cipher.update(empty, ptBuffer);
+ cipher.update(ctBuffer, ptBuffer);
+ try {
+ cipher.doFinal(empty, ptBuffer);
+ fail("Accepted modified ciphertext.");
+ } catch (GeneralSecurityException ex) {
+ // Expected
+ }
+ }
+ }
+
+ /**
+ * The default authentication tag size should be 128-bit by default for the following reasons:
+ * <br>
+ * (1) Security: Ferguson, N., Authentication Weaknesses in GCM, Natl. Inst. Stand. Technol. [Web
+ * page], http://www.csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/
+ * CWC-GCM/Ferguson2.pdf, May 20, 2005. This paper points out that a n-bit tag has lower strength
+ * than expected. <br>
+ * (2) Compatibility: Assume an implementer tests some code using one provider than switches to
+ * another provider. Such a switch should ideally not lower the security. <br>
+ * Conscrypt used to have only 12-byte authentication tag (b/26186727).
+ */
+ @Test
+ @Ignore // IvParameterSpec is not supported in AndroidKeyStore AES/GCM cipher
+ public void testDefaultTagSizeIvParameterSpec() throws Exception {
+ byte[] counter = new byte[12];
+ byte[] input = new byte[16];
+ SecretKey secretKey = null;
+ try {
+ String alias = "TestKey" + 1;
+ SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES");
+ secretKey = setKeystoreEntry(alias, keySpec);
+ } catch (Exception e) {
+ fail("Failed to set secret key entry in KeyStore.");
+ }
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(counter));
+ } catch (InvalidAlgorithmParameterException ex) {
+ // OpenJDK8 does not support IvParameterSpec for GCM.
+ throw ex;
+ }
+ byte[] output = cipher.doFinal(input);
+ assertEquals(input.length + 16, output.length);
+ }
+
+ /**
+ * The default authentication tag size should be 128-bit by default for the following reasons:
+ * <br>
+ * (1) Security: Ferguson, N., Authentication Weaknesses in GCM, Natl. Inst. Stand. Technol. [Web
+ * page], http://www.csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/
+ * CWC-GCM/Ferguson2.pdf, May 20, 2005. This paper points out that a n-bit tag has lower strength
+ * than expected. <br>
+ * (2) Compatibility: Assume an implementer tests some code using one provider than switches to
+ * another provider. Such a switch should ideally not lower the security. <br>
+ * BouncyCastle used to have only 12-byte authentication tag (b/26186727).
+ */
+ @Test
+ @Ignore // GCM AlgorithmParameterGenerator is not available.
+ public void testDefaultTagSizeAlgorithmParameterGenerator() throws Exception {
+ byte[] input = new byte[10];
+ SecretKey secretKey = null;
+ try {
+ String alias = "TestKey" + 1;
+ SecretKeySpec keySpec = new SecretKeySpec(new byte[16], "AES");
+ secretKey = setKeystoreEntry(alias, keySpec);
+ } catch (Exception e) {
+ fail("Failed to set secret key entry in KeyStore.");
+ }
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ try {
+ AlgorithmParameterGenerator.getInstance("GCM");
+ } catch (NoSuchAlgorithmException ex) {
+ // Conscrypt does not support AlgorithmParameterGenerator for GCM.
+ throw ex;
+ }
+ AlgorithmParameters param = AlgorithmParameterGenerator.getInstance("GCM").generateParameters();
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, param);
+ byte[] output = cipher.doFinal(input);
+ assertEquals(input.length + 16, output.length);
+ }
+
+ /**
+ * Test AES-GCM wrapped around counter bug which leaks plaintext and authentication key. Let's
+ * consider 12-byte IV, counter = IV || 0^31 || 1. For each encryption block, the last 4 bytes of
+ * the counter is increased by 1. After 2^32 blocks, the counter will be wrapped around causing
+ * counter collision and hence, leaking plaintext and authentication key as explained below. The
+ * library must make a check to make sure that the plaintext's length never exceeds 2^32 - 2
+ * blocks. Note that this is different from usual IV collisions because it happens even if users
+ * use different IVs. <br>
+ * We have: <br>
+ * J0 = IV || 0^31 || 1 <br>
+ * Plaintext: P[0], P[1], P[2], .... <br>
+ * Ciphertext: <br>
+ * C[0] = Enc(K, (J0 + 1) % 2^32) XOR P[0] <br>
+ * C[1] = Enc(K, (J0 + 2) % 2^32) XOR P[1] <br>
+ * C[2] = Enc(K, (J0 + 3) % 2^32) XOR P[2] <br>
+ * ... <br>
+ * C[2^32 - 1] = Enc(K, J0) XOR P[2^32 - 1] <br>
+ * C[2^32] = Enc(K, (J0 + 1)% 2^32) XOR P[2^32] <br>
+ * It means that after 2^32 blocks, the counter is wrapped around causing counter collisions. In
+ * counter mode, once the counter is collided then it's reasonable to assume that the plaintext is
+ * leaked. As the ciphertext is already known to attacker, Enc(K, J0) is leaked. <br>
+ * Now, as the authentication tag T is computed as GHASH(H, {}, C) XOR E(K, J0), the attacker can
+ * learn GHASH(H, {}, C}. It essentially means that the attacker finds a polynomial where H is the
+ * root (see Joux attack http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/Joux_comments.pdf).
+ * Solving polynomial equation in GF(2^128) is enough to extract the authentication key.
+ *
+ * <p>BouncyCastle used to have this bug (CVE-2015-6644).
+ *
+ * <p>OpenJDK8 used to have this bug (http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/0c3ed12cdaf5)
+ *
+ * <p>The test is slow as we have to encrypt 2^32 blocks.
+ */
+ @Test
+ @Ignore // This test takes very long time and CTS is timed out hence ignored for now.
+ public void testWrappedAroundCounter() throws Exception {
+ try {
+ byte[] iv = new byte[12];
+ byte[] input = new byte[16];
+ byte[] key = new byte[16];
+ (new SecureRandom()).nextBytes(key);
+ SecretKey secretKey = null;
+ try {
+ String alias = "TestKey" + 1;
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+ secretKey = setKeystoreEntry(alias, keySpec);
+ } catch (Exception e) {
+ fail("Failed to set secret key entry in KeyStore.");
+ }
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ cipher.init(
+ Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(16 * 8, iv));
+ byte[] output = cipher.update(input);
+ for (long i = 0; i < 4294967296L + 2; i++) {
+ byte[] output1 = cipher.update(input);
+ assertFalse("GCM Wrapped Around Counter" + i, Arrays.equals(output, output1));
+ }
+ fail("Expected Exception");
+ } catch (Exception expected) {
+ }
+ }
+
+ /**
+ * AES-GCM allows IVs of bit length 1 .. 2^64-1. See NIST SP 800 38d, Section 5.2.1.1
+ * http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
+ *
+ * <p>Disallowing IVs of length 0 is necessary for the following reason: if an empty IV is used
+ * then the tag is an evaluation of a polynomial with the hash subkey as the value. Since the
+ * polynomial can be derived from the ciphertext it is known to an attacker. Therefore, any
+ * message encrypted with an empty IV leaks the hash subkey. In particular, encrypting an empty
+ * plaintext with an empty IV results in a ciphertext having a tag that is equal to the hash
+ * subkey used in AES-GCM. I.e. both are the same as encrypting an all zero block.
+ *
+ * <p>OpenJDK fails this test.
+ */
+ @Test
+ public void testEncryptEmptyPlaintextWithEmptyIv() throws Exception {
+ byte[] emptyIv = new byte[0];
+ byte[] input = new byte[0];
+ byte[] key = TestUtil.hexToBytes("56aae7bd5cbefc71d31c4338e6ddd6c5");
+ SecretKey secretKey = null;
+ try {
+ String alias = "TestKey" + 1;
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+ secretKey = setKeystoreEntry(alias, keySpec);
+ } catch (Exception e) {
+ fail("Failed to set secret key entry in KeyStore.");
+ }
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ Cipher block = Cipher.getInstance("AES/ECB/NoPadding", EXPECTED_PROVIDER_NAME);
+ block.init(Cipher.ENCRYPT_MODE, secretKey);
+ byte[] hashkey = block.doFinal(new byte[16]);
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(16 * 8, emptyIv));
+ byte[] ct = cipher.doFinal(input);
+ // If the encryption above is not rejected then the hash key and the ciphertext are the same.
+ // Both are d1bdd948ddc5a7f7a9250cf78229b84d.
+ fail("Encrypting with an empty IV leaks the hash subkey.");
+ } catch (GeneralSecurityException expected) {
+ // expected behavior
+ }
+ }
+
+ @Test
+ public void testDecryptWithEmptyIv() throws Exception {
+ byte[] emptyIv = new byte[0];
+ byte[] key = TestUtil.hexToBytes("56aae7bd5cbefc71d31c4338e6ddd6c5");
+ SecretKey secretKey = null;
+ try {
+ String alias = "TestKey" + 1;
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+ secretKey = setKeystoreEntry(alias, keySpec);
+ } catch (Exception e) {
+ fail("Failed to set secret key entry in KeyStore.");
+ }
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", EXPECTED_PROVIDER_NAME);
+ try {
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(16 * 8, emptyIv));
+ String ciphertext = "2b65876c00d77facf8f3d0e5be792b129bab10b25bcb739b92d6e2eab241245ff449";
+ String tag = "c2b2d7086e7fa84ca795a881b540";
+ byte[] pt1 = cipher.update(TestUtil.hexToBytes(ciphertext));
+ byte[] pt2 = cipher.doFinal(TestUtil.hexToBytes(tag));
+ // We shouldn't get here. If a provider releases unverified plaintext additionally to
+ // accepting empty IVs then chosen ciphertext attacks might be possible.
+ fail("AES-GCM must not accept an IV of size 0.");
+ } catch (GeneralSecurityException expected) {
+ //Expected
+ }
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java
new file mode 100644
index 0000000..3698e4e
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherInputStreamTest.java
@@ -0,0 +1,286 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * CipherInputStream tests
+ *
+ * <p>CipherInputStream is a class that is basically unsuitable for authenticated encryption
+ * and hence should be avoided whenever possible. The class is unsuitable, because the interface
+ * does not provide a method to tell the caller when decryption failed. I.e. the specification
+ * now explicitly claims that it catches exceptions thrown by the Cipher class such as
+ * BadPaddingException and that it does not rethrow them.
+ * http://www.oracle.com/technetwork/java/javase/8u171-relnotes-4308888.html
+ *
+ * <p>The Jdk implementation still has the property that no unauthenticated plaintext is released.
+ * In the case of an authentication failure the implementation simply returns an empty plaintext.
+ * This allows a trivial attack where the attacker substitutes any message with an empty message.
+ *
+ * <p>The tests in this class have been adapted to this unfortunate situation. testEmptyPlaintext
+ * checks whether corrupting the tag of an empty message is detected. This test currently fails.
+ * All other tests run under the assumption that returning an empty plaintext is acceptable
+ * behaviour, so that the tests are able to catch additional problems.
+ */
+@RunWith(JUnit4.class)
+public class CipherInputStreamTest {
+ static final SecureRandom rand = new SecureRandom();
+
+ static byte[] randomBytes(int size) {
+ byte[] bytes = new byte[size];
+ rand.nextBytes(bytes);
+ return bytes;
+ }
+
+ static SecretKeySpec randomKey(String algorithm, int keySizeInBytes) {
+ return new SecretKeySpec(randomBytes(keySizeInBytes), "AES");
+ }
+
+ static AlgorithmParameterSpec randomParameters(
+ String algorithm, int ivSizeInBytes, int tagSizeInBytes) {
+ if ("AES/GCM/NoPadding".equals(algorithm) || "AES/EAX/NoPadding".equals(algorithm)) {
+ return new GCMParameterSpec(8 * tagSizeInBytes, randomBytes(ivSizeInBytes));
+ }
+ return null;
+ }
+
+ /** Test vectors */
+ public static class TestVector {
+ public String algorithm;
+ public SecretKeySpec key;
+ public AlgorithmParameterSpec params;
+ public byte[] pt;
+ public byte[] aad;
+ public byte[] ct;
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public TestVector(
+ String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize)
+ throws Exception {
+ this.algorithm = algorithm;
+ this.key = randomKey(algorithm, keySize);
+ this.params = randomParameters(algorithm, ivSize, tagSize);
+ this.pt = randomBytes(ptSize);
+ this.aad = randomBytes(aadSize);
+ Cipher cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, this.key, this.params);
+ cipher.updateAAD(aad);
+ this.ct = cipher.doFinal(pt);
+ }
+ }
+
+ Iterable<TestVector> getTestVectors(
+ String algorithm,
+ int[] keySizes,
+ int[] ivSizes,
+ int[] tagSizes,
+ int[] ptSizes,
+ int[] aadSizes)
+ throws Exception {
+ ArrayList<TestVector> result = new ArrayList<TestVector>();
+ for (int keySize : keySizes) {
+ for (int ivSize : ivSizes) {
+ for (int tagSize : tagSizes) {
+ for (int ptSize : ptSizes) {
+ for (int aadSize : aadSizes) {
+ result.add(new TestVector(algorithm, keySize, ivSize, tagSize, ptSize, aadSize));
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testEncrypt(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ InputStream is = new ByteArrayInputStream(t.pt);
+ CipherInputStream cis = new CipherInputStream(is, cipher);
+ byte[] result = new byte[t.ct.length];
+ int totalLength = 0;
+ int length = 0;
+ do {
+ length = cis.read(result, totalLength, result.length - totalLength);
+ if (length > 0) {
+ totalLength += length;
+ }
+ } while (length >= 0 && totalLength != result.length);
+ assertEquals(-1, cis.read());
+ assertEquals(TestUtil.bytesToHex(t.ct), TestUtil.bytesToHex(result));
+ cis.close();
+ }
+ }
+
+ /** JDK-8016249: CipherInputStream in decrypt mode fails on close with AEAD ciphers */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testDecrypt(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ InputStream is = new ByteArrayInputStream(t.ct);
+ CipherInputStream cis = new CipherInputStream(is, cipher);
+ byte[] result = new byte[t.pt.length];
+ int totalLength = 0;
+ int length = 0;
+ do {
+ length = cis.read(result, totalLength, result.length - totalLength);
+ if (length > 0) {
+ totalLength += length;
+ }
+ } while (length >= 0 && totalLength != result.length);
+ assertEquals(-1, cis.read());
+ cis.close();
+ assertEquals(TestUtil.bytesToHex(t.pt), TestUtil.bytesToHex(result));
+ }
+ }
+
+ /**
+ * JDK-8016171 : CipherInputStream masks ciphertext tampering with AEAD ciphers in decrypt mode
+ * Further description of the bug is here:
+ * https://blog.heckel.xyz/2014/03/01/cipherinputstream-for-aead-modes-is-broken-in-jdk7-gcm/
+ * BouncyCastle claims that this bug is fixed in version 1.51. However, the test below still fails
+ * with BouncyCastle v 1.52. A possible explanation is that BouncyCastle has its own
+ * implemenatation of CipherInputStream (org.bouncycastle.crypto.io.CipherInputStream).
+ *
+ * @param tests an iterable with valid test vectors, that will be corrupted for the test
+ * @param acceptEmptyPlaintext determines whether an empty plaintext instead of an exception
+ * is acceptable.
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testCorruptDecrypt(Iterable<TestVector> tests, boolean acceptEmptyPlaintext)
+ throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ byte[] ct = Arrays.copyOf(t.ct, t.ct.length);
+ ct[ct.length - 1] ^= (byte) 1;
+ InputStream is = new ByteArrayInputStream(ct);
+ CipherInputStream cis = new CipherInputStream(is, cipher);
+ try {
+ byte[] result = new byte[t.pt.length];
+ int totalLength = 0;
+ int length = 0;
+ do {
+ length = cis.read(result, totalLength, result.length - totalLength);
+ if (length > 0) {
+ totalLength += length;
+ }
+ } while (length >= 0 && totalLength != result.length);
+ cis.close();
+ if (result.length > 0) {
+ fail(
+ "this should fail; decrypted:"
+ + TestUtil.bytesToHex(result)
+ + " pt: "
+ + TestUtil.bytesToHex(t.pt));
+ } else if (result.length == 0 && !acceptEmptyPlaintext) {
+ fail("Corrupted ciphertext returns empty plaintext");
+ }
+ } catch (IOException ex) {
+ // expected
+ }
+ }
+ }
+
+ @Test
+ public void testAesGcm() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {0, 8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testEncrypt(v);
+ testDecrypt(v);
+ }
+
+ @Test
+ public void testCorruptAesGcm() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ boolean acceptEmptyPlaintext = true;
+ testCorruptDecrypt(v, acceptEmptyPlaintext);
+ }
+
+ /**
+ * Tests the behaviour for corrupt plaintext more strictly than in the tests above.
+ * This test does not accept that an implementation returns an empty plaintext when the
+ * ciphertext has been corrupted.
+ */
+ @Test
+ public void testEmptyPlaintext() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {0};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ boolean acceptEmptyPlaintext = false;
+ testCorruptDecrypt(v, acceptEmptyPlaintext);
+ }
+
+ /** Tests CipherOutputStream with AES-EAX if this algorithm is supported by the provider. */
+ @Test
+ public void testAesEax() throws Exception {
+ final String algorithm = "AES/EAX/NoPadding";
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12, 16};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {0, 8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ try {
+ Cipher.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Skipping testAesEax");
+ return;
+ }
+ Iterable<TestVector> v =
+ getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testEncrypt(v);
+ testDecrypt(v);
+ boolean acceptEmptyPlaintext = true;
+ testCorruptDecrypt(v, acceptEmptyPlaintext);
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java
new file mode 100644
index 0000000..d016941
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/CipherOutputStreamTest.java
@@ -0,0 +1,251 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.crypto.Cipher;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * CipherOutputStream tests
+ *
+ * <p>CipherOutputStream is a class that is basically unsuitable for authenticated encryption
+ * and hence should be avoided whenever possible. The class is unsuitable, because the interface
+ * does not provide a method to tell the caller when decryption failed. I.e. the specification
+ * now explicitly claims that it catches exceptions thrown by the Cipher class such as
+ * BadPaddingException and that it does not rethrow them.
+ * http://www.oracle.com/technetwork/java/javase/8u171-relnotes-4308888.html
+ *
+ * <p>The Jdk implementation has the property that no unauthenticated plaintext is released.
+ * In the case of an authentication failure the implementation simply returns an empty plaintext.
+ * This allows a trivial attack where the attacker substitutes any message with an empty message.
+ *
+ * <p>The tests in this class have been adapted to this unfortunate situation. testEmptyPlaintext
+ * checks whether corrupting the tag of an empty message is detected. This test currently fails.
+ * All other tests run under the assumption that returning an empty plaintext is acceptable
+ * behaviour, so that the tests are able to catch additional problems.
+ */
+
+@RunWith(JUnit4.class)
+public class CipherOutputStreamTest {
+ static final SecureRandom rand = new SecureRandom();
+
+ static byte[] randomBytes(int size) {
+ byte[] bytes = new byte[size];
+ rand.nextBytes(bytes);
+ return bytes;
+ }
+
+ static SecretKeySpec randomKey(String algorithm, int keySizeInBytes) {
+ return new SecretKeySpec(randomBytes(keySizeInBytes), "AES");
+ }
+
+ static AlgorithmParameterSpec randomParameters(
+ String algorithm, int ivSizeInBytes, int tagSizeInBytes) {
+ if ("AES/GCM/NoPadding".equals(algorithm) || "AES/EAX/NoPadding".equals(algorithm)) {
+ return new GCMParameterSpec(8 * tagSizeInBytes, randomBytes(ivSizeInBytes));
+ }
+ return null;
+ }
+
+ /** Test vectors */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public static class TestVector {
+ public String algorithm;
+ public SecretKeySpec key;
+ public AlgorithmParameterSpec params;
+ public byte[] pt;
+ public byte[] aad;
+ public byte[] ct;
+
+ public TestVector(
+ String algorithm, int keySize, int ivSize, int tagSize, int ptSize, int aadSize)
+ throws Exception {
+ this.algorithm = algorithm;
+ this.key = randomKey(algorithm, keySize);
+ this.params = randomParameters(algorithm, ivSize, tagSize);
+ this.pt = randomBytes(ptSize);
+ this.aad = randomBytes(aadSize);
+ Cipher cipher = Cipher.getInstance(algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, this.key, this.params);
+ cipher.updateAAD(aad);
+ this.ct = cipher.doFinal(pt);
+ }
+ }
+
+ Iterable<TestVector> getTestVectors(
+ String algorithm,
+ int[] keySizes,
+ int[] ivSizes,
+ int[] tagSizes,
+ int[] ptSizes,
+ int[] aadSizes)
+ throws Exception {
+ ArrayList<TestVector> result = new ArrayList<TestVector>();
+ for (int keySize : keySizes) {
+ for (int ivSize : ivSizes) {
+ for (int tagSize : tagSizes) {
+ for (int ptSize : ptSizes) {
+ for (int aadSize : aadSizes) {
+ result.add(new TestVector(algorithm, keySize, ivSize, tagSize, ptSize, aadSize));
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testEncrypt(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.ENCRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ CipherOutputStream cos = new CipherOutputStream(os, cipher);
+ cos.write(t.pt);
+ cos.close();
+ assertEquals(TestUtil.bytesToHex(t.ct), TestUtil.bytesToHex(os.toByteArray()));
+ }
+ }
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testDecrypt(Iterable<TestVector> tests) throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ CipherOutputStream cos = new CipherOutputStream(os, cipher);
+ cos.write(t.ct);
+ cos.close();
+ assertEquals(TestUtil.bytesToHex(t.pt), TestUtil.bytesToHex(os.toByteArray()));
+ }
+ }
+
+ /**
+ * Tests decryption of corrupted ciphertext. The test may accept empty plaintext as valid
+ * result because of the problem with CipherOutputStream described in the header of this file.
+ * @param tests an iterable with valid test vectors, that will be corrupted for the test
+ * @param acceptEmptyPlaintext determines whether an empty plaintext instead of an exception
+ * is acceptable.
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testCorruptDecrypt(Iterable<TestVector> tests, boolean acceptEmptyPlaintext)
+ throws Exception {
+ for (TestVector t : tests) {
+ Cipher cipher = Cipher.getInstance(t.algorithm);
+ cipher.init(Cipher.DECRYPT_MODE, t.key, t.params);
+ cipher.updateAAD(t.aad);
+ byte[] ct = Arrays.copyOf(t.ct, t.ct.length);
+ ct[ct.length - 1] ^= (byte) 1;
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ CipherOutputStream cos = new CipherOutputStream(os, cipher);
+ cos.write(ct);
+ try {
+ // cos.close() should call cipher.doFinal().
+ cos.close();
+ byte[] decrypted = os.toByteArray();
+ // Unfortunately Oracle thinks that returning an empty array is valid behaviour.
+ // We accept empty results here, but flag them in the next test, so that we can distinguish
+ // between beheviour considered acceptable by Oracle and more serious flaws.
+ if (decrypted.length > 0) {
+ fail(
+ "this should fail; decrypted:"
+ + TestUtil.bytesToHex(decrypted)
+ + " pt: "
+ + TestUtil.bytesToHex(t.pt));
+ } else if (decrypted.length == 0 && !acceptEmptyPlaintext) {
+ fail("Corrupted ciphertext returns empty plaintext");
+ }
+ } catch (IOException ex) {
+ // expected
+ }
+ }
+ }
+
+ @Test
+ public void testAesGcm() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testEncrypt(v);
+ testDecrypt(v);
+ boolean acceptEmptyPlaintext = true;
+ testCorruptDecrypt(v, acceptEmptyPlaintext);
+ }
+
+ /**
+ * Tests the behaviour for corrupt plaintext more strictly than in the tests above.
+ * This test does not accept that an implementation returns an empty plaintext when the
+ * ciphertext has been corrupted.
+ */
+ @Test
+ public void testEmptyPlaintext() throws Exception {
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {0};
+ final int[] aadSizes = {0, 8, 24};
+ Iterable<TestVector> v =
+ getTestVectors("AES/GCM/NoPadding", keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testEncrypt(v);
+ testDecrypt(v);
+ boolean acceptEmptyPlaintext = false;
+ testCorruptDecrypt(v, acceptEmptyPlaintext);
+ }
+
+ /** Tests CipherOutputStream with AES-EAX if AES-EAS is supported by the provider. */
+ @SuppressWarnings("InsecureCryptoUsage")
+ @Test
+ public void testAesEax() throws Exception {
+ final String algorithm = "AES/EAX/NoPadding";
+ final int[] keySizes = {16, 32};
+ final int[] ivSizes = {12, 16};
+ final int[] tagSizes = {12, 16};
+ final int[] ptSizes = {8, 16, 65, 8100};
+ final int[] aadSizes = {0, 8, 24};
+ try {
+ Cipher.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Skipping testAesEax");
+ return;
+ }
+ Iterable<TestVector> v =
+ getTestVectors(algorithm, keySizes, ivSizes, tagSizes, ptSizes, aadSizes);
+ testEncrypt(v);
+ testDecrypt(v);
+ boolean acceptEmptyPlaintext = true;
+ testCorruptDecrypt(v, acceptEmptyPlaintext);
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java
new file mode 100644
index 0000000..517d23b
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdhTest.java
@@ -0,0 +1,1072 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
+import com.google.security.wycheproof.WycheproofRunner.ProviderType;
+import com.google.security.wycheproof.WycheproofRunner.SlowTest;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import javax.crypto.KeyAgreement;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Testing ECDH.
+ *
+ * <p><b>Defense in depth</b>: The tests for ECDH assume that a attacker has control over all
+ * aspects of the public key in an exchange. That means that the attacker can potentially send weak
+ * or invalid public keys. For example, invalid public keys can contain points not on the curve,
+ * curves that have been deliberately chosen so that DLs are easy to compute as well as orders or
+ * cofactors that are wrong. It is expected that implementations validate the inputs of a key
+ * agreement and that in no case information about the private key is leaked.
+ *
+ * <p><b>References:</b> Ingrid Biehl, Bernd Meyer, Volker Müller, "Differential Fault Attacks on
+ * Elliptic Curve Cryptosystems", Crypto '00, pp. 131-164
+ *
+ * <p>Adrian Antipa, Daniel Brown, Alfred Menezes, Rene Struik, and Scott Vanstone, "Validation of
+ * Elliptic Curve Public Keys", PKC 2003, https://www.iacr.org/archive/pkc2003/25670211/25670211.pdf
+ *
+ * <p><b>Bugs:</b> CVE-2015-7940: BouncyCastle before 1.51 does not validate a point is on the
+ * curve. BouncyCastle v.1.52 checks that the public key point is on the public key curve but does
+ * not check whether public key and private key use the same curve. BouncyCastle v.1.53 is still
+ * vulnerable to attacks with modified public keys. An attacker can change the order of the curve
+ * used by the public key. ECDHC would then reduce the private key modulo this order, which can be
+ * used to find the private key.
+ *
+ * <p>CVE-2015-6924: Utimaco HSMs vulnerable to invalid curve attacks, which made the private key
+ * extraction possible.
+ *
+ * <p>CVE-2015-7940: Issue with elliptic curve addition in mixed Jacobian-affine coordinates
+ *
+ * @author bleichen@google.com (Daniel Bleichenbacher)
+ */
+// TODO(bleichen): Stuff we haven't implemented:
+// - timing attacks
+// Stuff we are delaying because there are more important bugs:
+// - testWrongOrder using BouncyCastle with ECDHWithSHA1Kdf throws
+// java.lang.UnsupportedOperationException: KDF can only be used when algorithm is known
+// Not sure if that is expected or another bug.
+// CVEs for ECDH we haven't used anywhere.
+// - CVE-2014-3470: OpenSSL anonymous ECDH denial of service: triggered by NULL value in
+// certificate.
+// - CVE-2014-3572: OpenSSL downgrades ECDHE to ECDH
+// - CVE-2011-3210: OpenSSL was not thread safe
+@RunWith(JUnit4.class)
+public class EcdhTest {
+
+ static final String[] ECDH_VARIANTS = {
+ // Raw ECDH. The shared secret is the x-coordinate of the ECDH computation.
+ // The tests below assume that this variant is implemenented.
+ "ECDH",
+ // ECDHC is a variant described in P1363 7.2.2 ECSVDP-DHC.
+ // BouncyCastle implements this variant.
+ "ECDHC",
+ // A variant with an explicit key derivation function.
+ // This is implemented by BouncyCastle.
+ "ECDHWITHSHA1KDF",
+ };
+
+ /** Test vectors */
+ public static class EcPublicKeyTestVector {
+ final String comment;
+ final String encoded; // hexadecimal representation of the X509 encoding
+ final BigInteger p; // characteristic of the field
+ final BigInteger n; // order of the subgroup
+ final BigInteger a; // parameter a of the Weierstrass representation
+ final BigInteger b; // parameter b of the Weierstrass represnetation
+ final BigInteger gx; // x-coordinate of the generator
+ final BigInteger gy; // y-coordainat of the generator
+ final Integer h; // cofactor: may be null
+ final BigInteger pubx; // x-coordinate of the public point
+ final BigInteger puby; // y-coordinate of the public point
+
+ public EcPublicKeyTestVector(
+ String comment,
+ String encoded,
+ BigInteger p,
+ BigInteger n,
+ BigInteger a,
+ BigInteger b,
+ BigInteger gx,
+ BigInteger gy,
+ Integer h,
+ BigInteger pubx,
+ BigInteger puby) {
+ this.comment = comment;
+ this.encoded = encoded;
+ this.p = p;
+ this.n = n;
+ this.a = a;
+ this.b = b;
+ this.gx = gx;
+ this.gy = gy;
+ this.h = h;
+ this.pubx = pubx;
+ this.puby = puby;
+ }
+
+ /**
+ * Returns this key as ECPublicKeySpec or null if the key cannot be represented as
+ * ECPublicKeySpec. The later happens for example if the order of cofactor are not positive.
+ */
+ public ECPublicKeySpec getSpec() {
+ try {
+ ECFieldFp fp = new ECFieldFp(p);
+ EllipticCurve curve = new EllipticCurve(fp, a, b);
+ ECPoint g = new ECPoint(gx, gy);
+ // ECParameterSpec requires that the cofactor h is specified.
+ if (h == null) {
+ return null;
+ }
+ ECParameterSpec params = new ECParameterSpec(curve, g, n, h);
+ ECPoint pubPoint = new ECPoint(pubx, puby);
+ ECPublicKeySpec pub = new ECPublicKeySpec(pubPoint, params);
+ return pub;
+ } catch (Exception ex) {
+ System.out.println(comment + " throws " + ex.toString());
+ return null;
+ }
+ }
+
+ public X509EncodedKeySpec getX509EncodedKeySpec() {
+ return new X509EncodedKeySpec(TestUtil.hexToBytes(encoded));
+ }
+ }
+
+public static final EcPublicKeyTestVector EC_VALID_PUBLIC_KEY =
+ new EcPublicKeyTestVector(
+ "unmodified",
+ "3059301306072a8648ce3d020106082a8648ce3d03010703420004cdeb39edd0"
+ + "3e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b84"
+ + "29598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16));
+
+ public static final EcPublicKeyTestVector[] EC_MODIFIED_PUBLIC_KEYS = {
+ // Modified keys
+ new EcPublicKeyTestVector(
+ "public point not on curve",
+ "3059301306072a8648ce3d020106082a8648ce3d03010703420004cdeb39edd0"
+ + "3e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b84"
+ + "29598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebaca",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebaca", 16)),
+ new EcPublicKeyTestVector(
+ "public point = (0,0)",
+ "3059301306072a8648ce3d020106082a8648ce3d030107034200040000000000"
+ + "0000000000000000000000000000000000000000000000000000000000000000"
+ + "000000000000000000000000000000000000000000000000000000",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 1,
+ new BigInteger("0"),
+ new BigInteger("0")),
+ new EcPublicKeyTestVector(
+ "order = 1",
+ "308201133081cc06072a8648ce3d02013081c0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f502010102010103420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("01", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "order = 26959946660873538060741835960514744168612397095220107664918121663170",
+ "3082012f3081e806072a8648ce3d02013081dc020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f5021d00ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac202010103420004cdeb39edd03e2b1a11a5e134ec"
+ + "99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b49bbb85c"
+ + "3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "generator = (0,0)",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b04410400000000000000000000000000000000000000"
+ + "0000000000000000000000000000000000000000000000000000000000000000"
+ + "00000000000000000000000000022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc63255102010103420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("0"),
+ new BigInteger("0"),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "generator not on curve",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f7022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc63255102010103420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f7", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "cofactor = 2",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f5022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc63255102010203420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 2,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "cofactor = None",
+ "308201303081e906072a8648ce3d02013081dd020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f5022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc63255103420004cdeb39edd03e2b1a11a5e134"
+ + "ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b49bbb8"
+ + "5c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ null,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "modified prime",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100fd091059a6893635f900e9449d63f572b2aebc4cff7b4e5e33f1b200"
+ + "e8bbc1453044042002f6efa55976c9cb06ff16bb629c0a8d4d5143b40084b1a1"
+ + "cc0e4dff17443eb704205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441040000000000000000000006597fa94b1fd90000"
+ + "000000000000000000000000021b8c7dd77f9a95627922eceefea73f028f1ec9"
+ + "5ba9b8fa95a3ad24bdf9fff414022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc63255102010103420004000000000000000000"
+ + "0006597fa94b1fd90000000000000000000000000000021b8c7dd77f9a956279"
+ + "22eceefea73f028f1ec95ba9b8fa95a3ad24bdf9fff414",
+ new BigInteger("fd091059a6893635f900e9449d63f572b2aebc4cff7b4e5e33f1b200e8bbc145", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("06597fa94b1fd9000000000000000000000000000002", 16),
+ new BigInteger("1b8c7dd77f9a95627922eceefea73f028f1ec95ba9b8fa95a3ad24bdf9fff414", 16),
+ 1,
+ new BigInteger("06597fa94b1fd9000000000000000000000000000002", 16),
+ new BigInteger("1b8c7dd77f9a95627922eceefea73f028f1ec95ba9b8fa95a3ad24bdf9fff414", 16)),
+ new EcPublicKeyTestVector(
+ "using secp224r1",
+ "304e301006072a8648ce3d020106052b81040021033a0004074f56dc2ea648ef"
+ + "89c3b72e23bbd2da36f60243e4d2067b70604af1c2165cec2f86603d60c8a611"
+ + "d5b84ba3d91dfe1a480825bcc4af3bcf",
+ new BigInteger("ffffffffffffffffffffffffffffffff000000000000000000000001", 16),
+ new BigInteger("ffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d", 16),
+ new BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffffffffffe", 16),
+ new BigInteger("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4", 16),
+ new BigInteger("b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21", 16),
+ new BigInteger("bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34", 16),
+ 1,
+ new BigInteger("074f56dc2ea648ef89c3b72e23bbd2da36f60243e4d2067b70604af1", 16),
+ new BigInteger("c2165cec2f86603d60c8a611d5b84ba3d91dfe1a480825bcc4af3bcf", 16)),
+ new EcPublicKeyTestVector(
+ "a = 0",
+ "308201143081cd06072a8648ce3d02013081c1020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30250401000420f104880c3980129c7efa19b6b0cb04e547b8d0fc0b"
+ + "95f4946496dd4ac4a7c440044104cdeb39edd03e2b1a11a5e134ec99d5f25f21"
+ + "673d403f3ecb47bd1fa676638958ea58493b8429598c0b49bbb85c3303ddb155"
+ + "3c3b761c2caacca71606ba9ebac8022100ffffffff00000000ffffffffffffff"
+ + "ffbce6faada7179e84f3b9cac2fc63255102010103420004cdeb39edd03e2b1a"
+ + "11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c"
+ + "0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("0"),
+ new BigInteger("f104880c3980129c7efa19b6b0cb04e547b8d0fc0b95f4946496dd4ac4a7c440", 16),
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "new curve with generator of order 3 that is also on secp256r1",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff3044042046dc879a5c2995d0e6f682468ea95791b7bbd0225cfdb251"
+ + "3fb10a737afece170420bea6c109251bfe4acf2eeda7c24c4ab70a1473335dec"
+ + "28b244d4d823d15935e2044104701c05255026aa4630b78fc6b769e388059ab1"
+ + "443cbdd1f8348bedc3be589dc34cfdab998ad27738ae382aa013986ade0f4859"
+ + "2a9a1ae37ca61d25ec5356f1bd022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc63255102010103420004701c05255026aa4630"
+ + "b78fc6b769e388059ab1443cbdd1f8348bedc3be589dc3b3025465752d88c851"
+ + "c7d55fec679521f0b7a6d665e51c8359e2da13aca90e42",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("46dc879a5c2995d0e6f682468ea95791b7bbd0225cfdb2513fb10a737afece17", 16),
+ new BigInteger("bea6c109251bfe4acf2eeda7c24c4ab70a1473335dec28b244d4d823d15935e2", 16),
+ new BigInteger("701c05255026aa4630b78fc6b769e388059ab1443cbdd1f8348bedc3be589dc3", 16),
+ new BigInteger("4cfdab998ad27738ae382aa013986ade0f48592a9a1ae37ca61d25ec5356f1bd", 16),
+ 1,
+ new BigInteger("701c05255026aa4630b78fc6b769e388059ab1443cbdd1f8348bedc3be589dc3", 16),
+ new BigInteger("b3025465752d88c851c7d55fec679521f0b7a6d665e51c8359e2da13aca90e42", 16)),
+ // Invalid keys
+ new EcPublicKeyTestVector(
+ "order = -1157920892103562487626974469494075735299969552241357603"
+ + "42422259061068512044369",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f50221ff00000000ffffffff0000000000000000"
+ + "4319055258e8617b0c46353d039cdaaf02010103420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger(
+ "-115792089210356248762697446949407573529996955224135760342422259061068512044369"),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "order = 0",
+ "308201133081cc06072a8648ce3d02013081c0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f502010002010103420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("0"),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "cofactor = -1",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f5022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc6325510201ff03420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ -1,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ new EcPublicKeyTestVector(
+ "cofactor = 0",
+ "308201333081ec06072a8648ce3d02013081e0020101302c06072a8648ce3d01"
+ + "01022100ffffffff00000001000000000000000000000000ffffffffffffffff"
+ + "ffffffff30440420ffffffff00000001000000000000000000000000ffffffff"
+ + "fffffffffffffffc04205ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53"
+ + "b0f63bce3c3e27d2604b0441046b17d1f2e12c4247f8bce6e563a440f277037d"
+ + "812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33"
+ + "576b315ececbb6406837bf51f5022100ffffffff00000000ffffffffffffffff"
+ + "bce6faada7179e84f3b9cac2fc63255102010003420004cdeb39edd03e2b1a11"
+ + "a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958ea58493b8429598c0b"
+ + "49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8",
+ new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16),
+ new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16),
+ new BigInteger("ffffffff00000001000000000000000000000000fffffffffffffffffffffffc", 16),
+ new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16),
+ new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16),
+ new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16),
+ 0,
+ new BigInteger("cdeb39edd03e2b1a11a5e134ec99d5f25f21673d403f3ecb47bd1fa676638958", 16),
+ new BigInteger("ea58493b8429598c0b49bbb85c3303ddb1553c3b761c2caacca71606ba9ebac8", 16)),
+ };
+
+ /** Checks that key agreement using ECDH works. */
+ @Test
+ public void testBasic() throws Exception {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
+ keyGen.initialize(ecSpec);
+ KeyPair keyPairA = keyGen.generateKeyPair();
+ KeyPair keyPairB = keyGen.generateKeyPair();
+
+ KeyAgreement kaA = KeyAgreement.getInstance("ECDH");
+ KeyAgreement kaB = KeyAgreement.getInstance("ECDH");
+ kaA.init(keyPairA.getPrivate());
+ kaB.init(keyPairB.getPrivate());
+ kaA.doPhase(keyPairB.getPublic(), true);
+ kaB.doPhase(keyPairA.getPublic(), true);
+ byte[] kAB = kaA.generateSecret();
+ byte[] kBA = kaB.generateSecret();
+ assertEquals(TestUtil.bytesToHex(kAB), TestUtil.bytesToHex(kBA));
+ }
+
+ @NoPresubmitTest(
+ providers = {ProviderType.BOUNCY_CASTLE},
+ bugs = {"BouncyCastle uses long encoding. Is this a bug?"}
+ )
+ @Test
+ public void testEncode() throws Exception {
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ ECPublicKey valid = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec());
+ assertEquals(TestUtil.bytesToHex(valid.getEncoded()), EC_VALID_PUBLIC_KEY.encoded);
+ }
+
+ @Test
+ public void testDecode() throws Exception {
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ ECPublicKey key1 = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec());
+ ECPublicKey key2 = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getX509EncodedKeySpec());
+ ECParameterSpec params1 = key1.getParams();
+ ECParameterSpec params2 = key2.getParams();
+ assertEquals(params1.getCofactor(), params2.getCofactor());
+ assertEquals(params1.getCurve(), params2.getCurve());
+ assertEquals(params1.getGenerator(), params2.getGenerator());
+ assertEquals(params1.getOrder(), params2.getOrder());
+ assertEquals(key1.getW(), key2.getW());
+ }
+
+ /**
+ * This test modifies the order of group in the public key. A severe bug would be an
+ * implementation that leaks information whether the private key is larger than the order given in
+ * the public key. Also a severe bug would be to reduce the private key modulo the order given in
+ * the public key parameters.
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testModifiedPublic(String algorithm) throws Exception {
+ KeyAgreement ka;
+ try {
+ ka = KeyAgreement.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("testWrongOrder: " + algorithm + " not supported");
+ return;
+ }
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ keyGen.initialize(EcUtil.getNistP256Params());
+ ECPrivateKey priv = (ECPrivateKey) keyGen.generateKeyPair().getPrivate();
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ ECPublicKey validKey = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec());
+ ka.init(priv);
+ ka.doPhase(validKey, true);
+ String expected = TestUtil.bytesToHex(ka.generateSecret());
+ for (EcPublicKeyTestVector test : EC_MODIFIED_PUBLIC_KEYS) {
+ try {
+ X509EncodedKeySpec spec = test.getX509EncodedKeySpec();
+ ECPublicKey modifiedKey = (ECPublicKey) kf.generatePublic(spec);
+ ka.init(priv);
+ ka.doPhase(modifiedKey, true);
+ String shared = TestUtil.bytesToHex(ka.generateSecret());
+ // The implementation did not notice that the public key was modified.
+ // This is not nice, but at the moment we only fail the test if the
+ // modification was essential for computing the shared secret.
+ //
+ // BouncyCastle v.1.53 fails this test, for ECDHC with modified order.
+ // This implementation reduces the product s*h modulo the order given
+ // in the public key. An attacker who can modify the order of the public key
+ // and who can learn whether such a modification changes the shared secret is
+ // able to learn the private key with a simple binary search.
+ assertEquals("algorithm:" + algorithm + " test:" + test.comment, expected, shared);
+ } catch (GeneralSecurityException ex) {
+ // OK, since the public keys have been modified.
+ System.out.println("testModifiedPublic:" + test.comment + " throws " + ex.toString());
+ }
+ }
+ }
+
+ /**
+ * This is a similar test as testModifiedPublic. However, this test uses test vectors
+ * ECPublicKeySpec
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testModifiedPublicSpec(String algorithm) throws Exception {
+ KeyAgreement ka;
+ try {
+ ka = KeyAgreement.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("testWrongOrder: " + algorithm + " not supported");
+ return;
+ }
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ keyGen.initialize(EcUtil.getNistP256Params());
+ ECPrivateKey priv = (ECPrivateKey) keyGen.generateKeyPair().getPrivate();
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ ECPublicKey validKey = (ECPublicKey) kf.generatePublic(EC_VALID_PUBLIC_KEY.getSpec());
+ ka.init(priv);
+ ka.doPhase(validKey, true);
+ String expected = TestUtil.bytesToHex(ka.generateSecret());
+ for (EcPublicKeyTestVector test : EC_MODIFIED_PUBLIC_KEYS) {
+ ECPublicKeySpec spec = test.getSpec();
+ if (spec == null) {
+ // The constructor of EcPublicKeySpec performs some very minor validity checks.
+ // spec == null if one of these validity checks fails. Of course such a failure is OK.
+ continue;
+ }
+ try {
+ ECPublicKey modifiedKey = (ECPublicKey) kf.generatePublic(spec);
+ ka.init(priv);
+ ka.doPhase(modifiedKey, true);
+ String shared = TestUtil.bytesToHex(ka.generateSecret());
+ // The implementation did not notice that the public key was modified.
+ // This is not nice, but at the moment we only fail the test if the
+ // modification was essential for computing the shared secret.
+ //
+ // BouncyCastle v.1.53 fails this test, for ECDHC with modified order.
+ // This implementation reduces the product s*h modulo the order given
+ // in the public key. An attacker who can modify the order of the public key
+ // and who can learn whether such a modification changes the shared secret is
+ // able to learn the private key with a simple binary search.
+ assertEquals("algorithm:" + algorithm + " test:" + test.comment, expected, shared);
+ } catch (GeneralSecurityException ex) {
+ // OK, since the public keys have been modified.
+ System.out.println("testModifiedPublic:" + test.comment + " throws " + ex.toString());
+ }
+ }
+ }
+
+ @Test
+ public void testModifiedPublic() throws Exception {
+ testModifiedPublic("ECDH");
+ testModifiedPublic("ECDHC");
+ }
+
+ @Test
+ public void testModifiedPublicSpec() throws Exception {
+ testModifiedPublicSpec("ECDH");
+ testModifiedPublicSpec("ECDHC");
+ }
+
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testDistinctCurves(String algorithm, ECPrivateKey priv, ECPublicKey pub)
+ throws Exception {
+ KeyAgreement kaA;
+ try {
+ kaA = KeyAgreement.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm not supported: " + algorithm);
+ return;
+ }
+ byte[] shared;
+ try {
+ kaA.init(priv);
+ kaA.doPhase(pub, true);
+ shared = kaA.generateSecret();
+ } catch (InvalidKeyException ex) {
+ // This is expected.
+ return;
+ }
+ // Printing some information to determine what might have gone wrong:
+ // E.g., if the generated secret is the same as the x-coordinate of the public key
+ // then it is likely that the ECDH computation was using a fake group with small order.
+ // Such a situation is probably exploitable.
+ // This probably is exploitable. If the curve of the private key was used for the ECDH
+ // then the generated secret and the x-coordinate of the public key are likely
+ // distinct.
+ EllipticCurve pubCurve = pub.getParams().getCurve();
+ EllipticCurve privCurve = priv.getParams().getCurve();
+ ECPoint pubW = pub.getW();
+ System.out.println("testDistinctCurves: algorithm=" + algorithm);
+ System.out.println(
+ "Private key: a="
+ + privCurve.getA()
+ + " b="
+ + privCurve.getB()
+ + " p"
+ + EcUtil.getModulus(privCurve));
+ System.out.println(" s =" + priv.getS());
+ System.out.println(
+ "Public key: a="
+ + pubCurve.getA()
+ + " b="
+ + pubCurve.getB()
+ + " p"
+ + EcUtil.getModulus(pubCurve));
+ System.out.println(" w = (" + pubW.getAffineX() + ", " + pubW.getAffineY() + ")");
+ System.out.println(
+ " = ("
+ + pubW.getAffineX().toString(16)
+ + ", "
+ + pubW.getAffineY().toString(16)
+ + ")");
+ System.out.println("generated shared secret:" + TestUtil.bytesToHex(shared));
+ fail("Generated secret with distinct Curves using " + algorithm);
+ }
+
+ /**
+ * This test modifies the order of group in the public key. A severe bug would be an
+ * implementation that leaks information whether the private key is larger than the order given in
+ * the public key. Also a severe bug would be to reduce the private key modulo the order given in
+ * the public key parameters.
+ */
+ // TODO(bleichen): This can be merged with testModifiedPublic once this is fixed.
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testWrongOrder(String algorithm, ECParameterSpec spec) throws Exception {
+ KeyAgreement ka;
+ try {
+ ka = KeyAgreement.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("testWrongOrder: " + algorithm + " not supported");
+ return;
+ }
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ ECPrivateKey priv;
+ ECPublicKey pub;
+ try {
+ keyGen.initialize(spec);
+ priv = (ECPrivateKey) keyGen.generateKeyPair().getPrivate();
+ pub = (ECPublicKey) keyGen.generateKeyPair().getPublic();
+ } catch (GeneralSecurityException ex) {
+ // This is OK, since not all provider support Brainpool curves
+ System.out.println("testWrongOrder: could not generate keys for curve");
+ return;
+ }
+ // Get the shared secret for the unmodified keys.
+ ka.init(priv);
+ ka.doPhase(pub, true);
+ byte[] shared = ka.generateSecret();
+ // Generate a modified public key.
+ ECParameterSpec modifiedParams =
+ new ECParameterSpec(
+ spec.getCurve(), spec.getGenerator(), spec.getOrder().shiftRight(16), 1);
+ ECPublicKeySpec modifiedPubSpec = new ECPublicKeySpec(pub.getW(), modifiedParams);
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ ECPublicKey modifiedPub;
+ try {
+ modifiedPub = (ECPublicKey) kf.generatePublic(modifiedPubSpec);
+ } catch (GeneralSecurityException ex) {
+ // The provider does not support non-standard curves or did a validity check.
+ // Both would be correct.
+ System.out.println("testWrongOrder: can't modify order.");
+ return;
+ }
+ byte[] shared2;
+ try {
+ ka.init(priv);
+ ka.doPhase(modifiedPub, true);
+ shared2 = ka.generateSecret();
+ } catch (GeneralSecurityException ex) {
+ // This is the expected behavior
+ System.out.println("testWrongOrder:" + ex.toString());
+ return;
+ }
+ // TODO(bleichen): Getting here is already a bug and we might flag this later.
+ // At the moment we are only interested in really bad behavior of a library, that potentially
+ // leaks the secret key. This is the case when the shared secrets are different, since this
+ // suggests that the implementation reduces the multiplier modulo the given order of the curve
+ // or some other behaviour that is dependent on the private key.
+ // An attacker who can check whether a DH computation was done correctly or incorrectly because
+ // of modular reduction, can determine the private key, either by a binary search or by trying
+ // to guess the private key modulo some small "order".
+ // BouncyCastle v.1.53 fails this test, and leaks the private key.
+ System.out.println(
+ "Generated shared secret with a modified order:"
+ + algorithm
+ + "\n"
+ + "expected:"
+ + TestUtil.bytesToHex(shared)
+ + " computed:"
+ + TestUtil.bytesToHex(shared2));
+ assertEquals(
+ "Algorithm:" + algorithm, TestUtil.bytesToHex(shared), TestUtil.bytesToHex(shared2));
+ }
+
+ @Test
+ public void testWrongOrderEcdh() throws Exception {
+ testWrongOrder("ECDH", EcUtil.getNistP256Params());
+ testWrongOrder("ECDH", EcUtil.getBrainpoolP256r1Params());
+ }
+
+ @Test
+ public void testWrongOrderEcdhc() throws Exception {
+ testWrongOrder("ECDHC", EcUtil.getNistP256Params());
+ testWrongOrder("ECDHC", EcUtil.getBrainpoolP256r1Params());
+ }
+
+ /**
+ * Tests for the problem detected by CVE-2017-10176.
+ *
+ * <p>Some libraries do not compute P + (-P) correctly and return 2 * P or throw exceptions. When
+ * the library uses addition-subtraction chains for the point multiplication then such cases can
+ * occur for example when the private key is close to the order of the curve.
+ */
+ private void testLargePrivateKey(ECParameterSpec spec) throws Exception {
+ BigInteger order = spec.getOrder();
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ ECPublicKey pub;
+ try {
+ keyGen.initialize(spec);
+ pub = (ECPublicKey) keyGen.generateKeyPair().getPublic();
+ } catch (GeneralSecurityException ex) {
+ // curve is not supported
+ return;
+ }
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ KeyAgreement ka = KeyAgreement.getInstance("ECDH");
+ for (int i = 1; i <= 64; i++) {
+ BigInteger p1 = BigInteger.valueOf(i);
+ ECPrivateKeySpec spec1 = new ECPrivateKeySpec(p1, spec);
+ ECPrivateKeySpec spec2 = new ECPrivateKeySpec(order.subtract(p1), spec);
+ ka.init(kf.generatePrivate(spec1));
+ ka.doPhase(pub, true);
+ byte[] shared1 = ka.generateSecret();
+ ka.init(kf.generatePrivate(spec2));
+ ka.doPhase(pub, true);
+ byte[] shared2 = ka.generateSecret();
+ // The private keys p1 and p2 are equivalent, since only the x-coordinate of the
+ // shared point is used to generate the shared secret.
+ assertEquals(TestUtil.bytesToHex(shared1), TestUtil.bytesToHex(shared2));
+ }
+ }
+
+ @Test
+ public void testLargePrivateKey() throws Exception {
+ testLargePrivateKey(EcUtil.getNistP224Params());
+ testLargePrivateKey(EcUtil.getNistP256Params());
+ testLargePrivateKey(EcUtil.getNistP384Params());
+ // This test failed before CVE-2017-10176 was fixed.
+ testLargePrivateKey(EcUtil.getNistP521Params());
+ testLargePrivateKey(EcUtil.getBrainpoolP256r1Params());
+ }
+
+ /**
+ * This test tries to determine whether point multipliplication using two distinct
+ * points leads to distinguishable timings.
+ *
+ * The main goal here is to determine if the attack by Toru Akishita and Tsuyoshi Takagi
+ * in https://www-old.cdc.informatik.tu-darmstadt.de/reports/TR/TI-03-01.zvp.pdf
+ * might be applicable. I.e. one of the points contains a zero value when multiplied
+ * by mul, the other one does not.
+ *
+ * In its current form the test here is quite weak for a number of reasons:
+ * (1) The timing is often noisy, because the test is run as a unit test.
+ * (2) The test is executed with only a small number of input points.
+ * (3) The number of samples is rather low. Running this test with a larger sample
+ * size would detect more timing differences. Unfortunately
+ * (4) The test does not determine if a variable run time is exploitable. For example
+ * if the tested provider uses windowed exponentiation and the special point is
+ * in the precomputation table then timing differences are easy to spot, but more
+ * difficult to exploit and hence additional experiments would be necessary.
+ *
+ * @param spec the specification of the curve
+ * @param p0 This is a special point. I.e. multiplying this point by mul
+ * may lead to a zero value that may be observable.
+ * @param p1 a random point on the curve
+ * @param mul an integer, such that multiplying p0 with this value may lead to a timing
+ * difference
+ * @param privKeySize the size of the private key in bits
+ * @param comment describes the test case
+ */
+ private void testTiming(ECParameterSpec spec, ECPoint p0, ECPoint p1,
+ BigInteger mul, int privKeySize, String comment) throws Exception {
+ ThreadMXBean bean = ManagementFactory.getThreadMXBean();
+ if (!bean.isCurrentThreadCpuTimeSupported()) {
+ System.out.println("getCurrentThreadCpuTime is not supported. Skipping");
+ return;
+ }
+ SecureRandom random = new SecureRandom();
+ int fixedSize = mul.bitLength();
+ int missingBits = privKeySize - 2 * fixedSize;
+ assertTrue(missingBits > 0);
+ // possible values for tests, minCount:
+ // 1024, 410
+ // 2048, 880
+ // 4096, 1845
+ // 10000, 4682
+ // I.e. these are values, such that doing 'tests' coin flips results in <= minCount heads or
+ // tails with a probability smaller than 2^-32.
+ //
+ // def min_count(n, b=33):
+ // res, sum, k = 1,1,0
+ // bnd = 2**(n-b)
+ // while sum < bnd:
+ // res *= n - k
+ // res //= 1 + k
+ // k += 1
+ // sum += res
+ // return k - 1
+ final int tests = 2048;
+ final int minCount = 880;
+ // the number of measurements done with each point
+ final int repetitions = 8;
+ // the number of warmup experiments that are ignored
+ final int warmup = 8;
+ final int sampleSize = warmup + tests;
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ PublicKey[] publicKeys = new PublicKey[2];
+ try {
+ publicKeys[0] = kf.generatePublic(new ECPublicKeySpec(p0, spec));
+ publicKeys[1] = kf.generatePublic(new ECPublicKeySpec(p1, spec));
+ } catch (InvalidKeySpecException ex) {
+ // unsupported curve
+ return;
+ }
+ PrivateKey[] privKeys = new PrivateKey[sampleSize];
+ for (int i = 0; i < sampleSize; i++) {
+ BigInteger m = new BigInteger(missingBits, random);
+ m = mul.shiftLeft(missingBits).add(m);
+ m = m.shiftLeft(fixedSize).add(mul);
+ ECPrivateKeySpec privSpec = new ECPrivateKeySpec(m, spec);
+ privKeys[i] = kf.generatePrivate(privSpec);
+ }
+ KeyAgreement ka = KeyAgreement.getInstance("ECDH");
+ long[][] timings = new long[2][sampleSize];
+ for (int i = 0; i < sampleSize; i++) {
+ for (int j = 0; j < 2 * repetitions; j++) {
+ // idx determines which key to use.
+ int idx = (j ^ i) & 1;
+ ka.init(privKeys[i]);
+ long start = bean.getCurrentThreadCpuTime();
+ ka.doPhase(publicKeys[idx], true);
+ byte[] unused = ka.generateSecret();
+ long time = bean.getCurrentThreadCpuTime() - start;
+ timings[idx][i] += time;
+ }
+ }
+ for (int i = 0; i < sampleSize; i++) {
+ for (int j = 0; j < 2; j++) {
+ timings[j][i] /= repetitions;
+ }
+ }
+
+ // Performs some statistics.
+ boolean noisy = false; // Set to true, if the timings have a large variance.
+ System.out.println("ECDH timing test:" + comment);
+ double[] avg = new double[2];
+ double[] var = new double[2];
+ for (int i = 0; i < 2; i++) {
+ double sum = 0.0;
+ double sumSqr = 0.0;
+ for (int j = warmup; j < sampleSize; j++) {
+ double val = (double) timings[i][j];
+ sum += val;
+ sumSqr += val * val;
+ }
+ avg[i] = sum / tests;
+ var[i] = (sumSqr - avg[i] * sum) / (tests - 1);
+ double stdDev = Math.sqrt(var[i]);
+ double cv = stdDev / avg[i];
+ System.out.println("Timing for point " + i + " avg: " + avg[i] + " std dev: " + stdDev
+ + " cv:" + cv);
+ // The ratio 0.05 below is a somewhat arbitrary value that tries to determine if the noise
+ // is too big to detect even larger timing differences.
+ if (cv > 0.05) {
+ noisy = true;
+ }
+ }
+ // Paired Z-test:
+ // The outcome of this value can be significantly influenced by extreme outliers, such
+ // as slow timings because of things like a garbage collection.
+ double sigmas = Math.abs(avg[0] - avg[1]) / Math.sqrt((var[0] + var[1]) / tests);
+ System.out.println("Sigmas: " + sigmas);
+
+ // Pairwise comparison:
+ // this comparison has the property that it compares timings done with the same
+ // private key, hence timing differences from using different addition chain sizes
+ // are ignored. Extreme outliers should not influence the result a lot, as long as the
+ // number of outliers is small.
+ int point0Faster = 0;
+ int equal = 0;
+ for (int i = 0; i < sampleSize; i++) {
+ if (timings[0][i] < timings[1][i]) {
+ point0Faster += 1;
+ } else if (timings[0][i] < timings[1][i]) {
+ equal += 1;
+ }
+ }
+ point0Faster += equal / 2;
+ System.out.println("Point 0 multiplication is faster: " + point0Faster);
+ if (point0Faster < minCount || point0Faster > sampleSize - minCount) {
+ fail("Timing differences in ECDH computation detected");
+ } else if (noisy) {
+ System.out.println("Timing was too noisy to expect results.");
+ }
+ }
+
+ @SlowTest(providers =
+ {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE, ProviderType.OPENJDK})
+ @Test
+ public void testTimingSecp256r1() throws Exception {
+ // edge case for projective coordinates
+ BigInteger x1 =
+ new BigInteger("81bfb55b010b1bdf08b8d9d8590087aa278e28febff3b05632eeff09011c5579", 16);
+ BigInteger y1 =
+ new BigInteger("732d0e65267ea28b7af8cfcb148936c2af8664cbb4f04e188148a1457400c2a7", 16);
+ ECPoint p1 = new ECPoint(x1, y1);
+ // random point
+ BigInteger x2 =
+ new BigInteger("8608e36a91f1fba12e4074972af446176b5608c9c58dc318bd0742754c3dcee7", 16);
+ BigInteger y2 =
+ new BigInteger("bc2c9ecd44af916ca58d9e3ef1257f698d350ef486eb86137fe69a7375bcc191", 16);
+ ECPoint p2 = new ECPoint(x2, y2);
+ testTiming(EcUtil.getNistP256Params(), p1, p2, new BigInteger("2"), 256, "secp256r1");
+ }
+
+ @SlowTest(providers =
+ {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE, ProviderType.OPENJDK})
+ @Test
+ public void testTimingSecp384r1() throws Exception {
+ // edge case for projective coordinates
+ BigInteger x1 =
+ new BigInteger("7a6fadfee03eb09554f2a04fe08300aca88bb3a46e8f6347bace672cfe427698"
+ + "8541cef8dc10536a84580215f5f90a3b", 16);
+ BigInteger y1 =
+ new BigInteger("6d243d5d9de1cdddd04cbeabdc7a0f6c244391f7cb2d5738fe13c334add4b458"
+ + "5fef61ffd446db33b39402278713ae78", 16);
+ ECPoint p1 = new ECPoint(x1, y1);
+ // random point
+ BigInteger x2 =
+ new BigInteger("71f3c57d6a879889e582af2c7c5444b0eb6ba95d88365b21ca9549475273ecdd"
+ + "3930aa0bebbd1cf084e4049667278602", 16);
+ BigInteger y2 =
+ new BigInteger("9dcbc4d843af8944eb4ba018d369b351a9ea0f7b9e3561df2ee218d54e198f7c"
+ + "837a3abaa41dffd2d2cb771a7599ed9e", 16);
+ ECPoint p2 = new ECPoint(x2, y2);
+ testTiming(EcUtil.getNistP384Params(), p1, p2, new BigInteger("2"), 384, "secp384r1");
+ }
+
+ @SlowTest(providers =
+ {ProviderType.BOUNCY_CASTLE, ProviderType.SPONGY_CASTLE, ProviderType.OPENJDK})
+ @Test
+ public void testTimingBrainpoolP256r1() throws Exception {
+ // edge case for Jacobian and projective coordinates
+ BigInteger x1 =
+ new BigInteger("79838c22d2b8dc9af2e6cf56f8826dc3dfe10fcb17b6aaaf551ee52bef12f826", 16);
+ BigInteger y1 =
+ new BigInteger("1e2ed3d453088c8552c6feecf898667bc1e15905002edec6b269feb7bea09d5b", 16);
+ ECPoint p1 = new ECPoint(x1, y1);
+
+ // random point
+ BigInteger x2 =
+ new BigInteger("2720b2e821b2ac8209b573bca755a68821e1e09deb580666702570dd527dd4c1", 16);
+ BigInteger y2 =
+ new BigInteger("25cdd610243c7e693fad7bd69b43ae3e63e94317c4c6b717d9c8bc3be8c996fb", 16);
+ ECPoint p2 = new ECPoint(x2, y2);
+ testTiming(EcUtil.getBrainpoolP256r1Params(), p1, p2, new BigInteger("2"), 255,
+ "brainpoolP256r1");
+ }
+}
+
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java
new file mode 100644
index 0000000..a6ce23a
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/EcdsaTest.java
@@ -0,0 +1,417 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.security.wycheproof.WycheproofRunner.ProviderType;
+import com.google.security.wycheproof.WycheproofRunner.SlowTest;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadMXBean;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests ECDSA signatures.
+ *
+ * <p>Tests for signature verification with test vectors are in JsonSignatureTest.java toghether
+ * with other signature schemes.
+ *
+ * @author bleichen@google.com (Daniel Bleichenbacher)
+ */
+@RunWith(JUnit4.class)
+public class EcdsaTest {
+
+ /**
+ * Determines the Hash name from the ECDSA algorithm. There is a small inconsistency in the naming
+ * of algorithms. The Oracle standard use no hyphen in SHA256WithECDSA but uses a hyphen in the
+ * message digest, i.e., SHA-256.
+ */
+ private String getHashAlgorithm(String ecdsaAlgorithm) {
+ ecdsaAlgorithm = ecdsaAlgorithm.toUpperCase();
+ int idx = ecdsaAlgorithm.indexOf("WITH");
+ if (idx > 0) {
+ if (ecdsaAlgorithm.startsWith("SHA")) {
+ return "SHA-" + ecdsaAlgorithm.substring(3, idx);
+ } else {
+ return ecdsaAlgorithm.substring(0, idx);
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Extract the integer r from an ECDSA signature. This method implicitely assumes that the ECDSA
+ * signature is DER encoded. and that the order of the curve is smaller than 2^1024.
+ */
+ BigInteger extractR(byte[] signature) throws Exception {
+ int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
+ int lengthR = signature[startR + 1];
+ return new BigInteger(Arrays.copyOfRange(signature, startR + 2, startR + 2 + lengthR));
+ }
+
+ BigInteger extractS(byte[] signature) throws Exception {
+ int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
+ int lengthR = signature[startR + 1];
+ int startS = startR + 2 + lengthR;
+ int lengthS = signature[startS + 1];
+ return new BigInteger(Arrays.copyOfRange(signature, startS + 2, startS + 2 + lengthS));
+ }
+
+ /** Extract the k that was used to sign the signature. */
+ BigInteger extractK(byte[] signature, BigInteger h, ECPrivateKey priv) throws Exception {
+ BigInteger x = priv.getS();
+ BigInteger n = priv.getParams().getOrder();
+ BigInteger r = extractR(signature);
+ BigInteger s = extractS(signature);
+ BigInteger k = x.multiply(r).add(h).multiply(s.modInverse(n)).mod(n);
+ return k;
+ }
+
+ /**
+ * Computes the bias of samples as
+ *
+ * abs(sum(e^(2 pi i s m / modulus) for s in samples) / sqrt(samples.length).
+ *
+ * If the samples are taken from a uniform distribution in the range 0 .. modulus - 1
+ * and the number of samples is significantly larger than L^2
+ * then the probability that the result is larger than L is approximately e^(-L^2).
+ * The approximation can be derived from the assumption that samples taken from
+ * a uniform distribution give a result that approximates a standard complex normal
+ * distribution Z. I.e. Z has a density f_Z(z) = exp(-abs(z)^2) / pi.
+ * https://en.wikipedia.org/wiki/Complex_normal_distribution
+ */
+ double bias(BigInteger[] samples, BigInteger modulus, BigInteger m) {
+ double sumReal = 0.0;
+ double sumImag = 0.0;
+ for (BigInteger s : samples) {
+ BigInteger r = s.multiply(m).mod(modulus);
+ // multiplier = 2 * pi / 2^52
+ double multiplier = 1.3951473992034527e-15;
+ // computes the quotent 2 * pi * r / modulus
+ double quot = r.shiftLeft(52).divide(modulus).doubleValue() * multiplier;
+ sumReal += Math.cos(quot);
+ sumImag += Math.sin(quot);
+ }
+ return Math.sqrt((sumReal * sumReal + sumImag * sumImag) / samples.length);
+ }
+
+ /**
+ * This test checks the basic functionality of ECDSA. It simply tries to generate a key, sign and
+ * verify a message for a given, algorithm and curve.
+ *
+ * @param algorithm the algorithm to test (e.g. "SHA256WithECDSA")
+ * @param curve the curve to test (e.g. "secp256r1")
+ * @return whether the algorithm and curve are supported.
+ * @throws Exception if an unexpected error occurred.
+ */
+ boolean testParameters(String algorithm, String curve) throws Exception {
+ String message = "123400";
+
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec(curve);
+ KeyPair keyPair;
+ try {
+ keyGen.initialize(ecSpec);
+ keyPair = keyGen.generateKeyPair();
+ } catch (InvalidAlgorithmParameterException ex) {
+ // The curve is not supported.
+ // The documentation does not specify whether the method initialize
+ // has to reject unsupported curves or if only generateKeyPair checks
+ // whether the curve is supported.
+ return false;
+ }
+ ECPublicKey pub = (ECPublicKey) keyPair.getPublic();
+ ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate();
+
+ // Print the parameters.
+ System.out.println("Parameters for curve:" + curve);
+ EcUtil.printParameters(pub.getParams());
+
+ Signature signer;
+ Signature verifier;
+ try {
+ signer = Signature.getInstance(algorithm);
+ verifier = Signature.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ // The algorithm is not supported.
+ return false;
+ }
+ // Both algorithm and curve are supported.
+ // Hence, we expect that signing and verifying properly works.
+ byte[] messageBytes = message.getBytes("UTF-8");
+ signer.initSign(priv);
+ signer.update(messageBytes);
+ byte[] signature = signer.sign();
+ verifier.initVerify(pub);
+ verifier.update(messageBytes);
+ assertTrue(verifier.verify(signature));
+ return true;
+ }
+
+ /**
+ * This test checks the basic functionality of ECDSA. This mainly checks that the provider follows
+ * the JCA interface.
+ */
+ @Test
+ public void testBasic() throws Exception {
+ String algorithm = "SHA256WithECDSA";
+ String curve = "secp256r1";
+ assertTrue(testParameters(algorithm, curve));
+ }
+
+ /** Checks whether the one time key k in ECDSA is biased. */
+ public void testBias(String algorithm, String curve, ECParameterSpec ecParams) throws Exception {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ try {
+ keyGen.initialize(ecParams);
+ } catch (InvalidAlgorithmParameterException ex) {
+ System.out.println("This provider does not support curve:" + curve);
+ return;
+ }
+ KeyPair keyPair = keyGen.generateKeyPair();
+ ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate();
+ // If we throw a fair coin tests times then the probability that
+ // either heads or tails appears less than mincount is less than 2^{-32}.
+ // Therefore the test below is not expected to fail unless the generation
+ // of the one time keys is indeed biased.
+ final int tests = 1024;
+ final int mincount = 410;
+
+ String hashAlgorithm = getHashAlgorithm(algorithm);
+ String message = "Hello";
+ byte[] messageBytes = message.getBytes("UTF-8");
+ byte[] digest = MessageDigest.getInstance(hashAlgorithm).digest(messageBytes);
+
+ // TODO(bleichen): Truncate the digest if the digest size is larger than the
+ // curve size.
+ BigInteger h = new BigInteger(1, digest);
+ BigInteger q = priv.getParams().getOrder();
+ BigInteger qHalf = q.shiftRight(1);
+
+ Signature signer = Signature.getInstance(algorithm);
+ signer.initSign(priv);
+ BigInteger[] kList = new BigInteger[tests];
+ for (int i = 0; i < tests; i++) {
+ signer.update(messageBytes);
+ byte[] signature = signer.sign();
+ kList[i] = extractK(signature, h, priv);
+ }
+
+ // Checks whether the most significant bits and the least significant bits
+ // of the value k are unbiased.
+ int countMsb = 0; // count the number of k's with lsb set
+ int countLsb = 0; // count the number of k's with msb set
+ for (BigInteger k : kList) {
+ if (k.testBit(0)) {
+ countLsb++;
+ }
+ if (k.compareTo(qHalf) > 0) {
+ countMsb++;
+ }
+ }
+ if (countLsb < mincount || countLsb > tests - mincount) {
+ fail("Bias detected in the least significant bit of k:" + countLsb);
+ }
+ if (countMsb < mincount || countMsb > tests - mincount) {
+ fail("Bias detected in the most significant bit of k:" + countMsb);
+ }
+
+ // One situation where the bits above are not biased even if k itself is
+ // badly distributed is the case where the signer replaces s by
+ // min(s, q - s). Such a replacement is sometimes done to avoid signature
+ // malleability of ECDSA.
+ // Breitner and Heninger describe such cases in the paper
+ // "Biased Nonce Sense: Lattice Attacks against Weak ECDSA Signatures in Cryptocurrencies",
+ // https://eprint.iacr.org/2019/023.pdf
+ // The following tests should catch the bugs described in this paper.
+ // The threshold below has been chosen to give false positives with probability < 2^{-32}.
+ double threshold = 5;
+
+ // This test detects for example the case when either k or q-k is small.
+ double bias1 = bias(kList, q, BigInteger.ONE);
+ if (bias1 > threshold) {
+ fail("Bias for k detected. bias1 = " + bias1);
+ }
+ // Same as above but shifing by one bit.
+ double bias2 = bias(kList, q, BigInteger.valueOf(2));
+ if (bias2 > threshold) {
+ fail("Bias for k detected. bias2 = " + bias2);
+ }
+ double bias3 = bias(kList, q, qHalf);
+ if (bias3 > threshold) {
+ fail("Bias for k detected. bias3 = " + bias3);
+ }
+ // Checks whether most significant bytes, words, dwords or qwords are strongly correlated.
+ for (int bits : new int[] {8, 16, 32, 64}) {
+ BigInteger multiplier = BigInteger.ONE.shiftLeft(bits).subtract(BigInteger.ONE);
+ double bias4 = bias(kList, q, multiplier);
+ if (bias4 > threshold) {
+ fail("Bias for k detected. bits = " + bits + " bias4 = " + bias4);
+ }
+ }
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.CONSCRYPT,
+ ProviderType.OPENJDK,
+ ProviderType.SPONGY_CASTLE
+ }
+ )
+ @Test
+ public void testBiasAll() throws Exception {
+ testBias("SHA256WithECDSA", "secp256r1", EcUtil.getNistP256Params());
+ testBias("SHA224WithECDSA", "secp224r1", EcUtil.getNistP224Params());
+ testBias("SHA384WithECDSA", "secp384r1", EcUtil.getNistP384Params());
+ testBias("SHA512WithECDSA", "secp521r1", EcUtil.getNistP521Params());
+ testBias("SHA256WithECDSA", "brainpoolP256r1", EcUtil.getBrainpoolP256r1Params());
+ }
+
+ /**
+ * Tests for a potential timing attack. This test checks if there is a correlation between the
+ * timing of signature generation and the size of the one-time key k. This is for example the case
+ * if a double and add method is used for the point multiplication. The test fails if such a
+ * correlation can be shown with high confidence. Further analysis will be necessary to determine
+ * how easy it is to exploit the bias in a timing attack.
+ */
+ // TODO(bleichen): Determine if there are exploitable providers.
+ //
+ // SunEC currently fails this test. Since ECDSA typically is used with EC groups whose order
+ // is 224 bits or larger, it is unclear whether the same attacks that apply to DSA are practical.
+ //
+ // The ECDSA implementation in BouncyCastle leaks information about k through timing too.
+ // The test has not been optimized to detect this bias. It would require about 5'000'000 samples,
+ // which is too much for a simple unit test.
+ //
+ // BouncyCastle uses FixedPointCombMultiplier for ECDSA. This is a method using
+ // precomputation. The implementation is not constant time, since the precomputation table
+ // contains the point at infinity and adding this point is faster than ordinary point additions.
+ // The timing leak only has a small correlation to the size of k and at the moment it is is very
+ // unclear if the can be exploited. (Randomizing the precomputation table by adding the same
+ // random point to each element in the table and precomputing the necessary offset to undo the
+ // precomputation seems much easier than analyzing this.)
+ public void testTiming(String algorithm, String curve, ECParameterSpec ecParams)
+ throws Exception {
+ ThreadMXBean bean = ManagementFactory.getThreadMXBean();
+ if (!bean.isCurrentThreadCpuTimeSupported()) {
+ System.out.println("getCurrentThreadCpuTime is not supported. Skipping");
+ return;
+ }
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
+ try {
+ keyGen.initialize(ecParams);
+ } catch (InvalidAlgorithmParameterException ex) {
+ System.out.println("This provider does not support curve:" + curve);
+ return;
+ }
+ KeyPair keyPair = keyGen.generateKeyPair();
+ ECPrivateKey priv = (ECPrivateKey) keyPair.getPrivate();
+
+ String message = "Hello";
+ String hashAlgorithm = getHashAlgorithm(algorithm);
+ byte[] messageBytes = message.getBytes("UTF-8");
+ byte[] digest = MessageDigest.getInstance(hashAlgorithm).digest(messageBytes);
+ BigInteger h = new BigInteger(1, digest);
+ Signature signer = Signature.getInstance(algorithm);
+ signer.initSign(priv);
+ // The number of samples used for the test. This number is a bit low.
+ // I.e. it just barely detects that SunEC leaks information about the size of k.
+ int samples = 50000;
+ long[] timing = new long[samples];
+ BigInteger[] k = new BigInteger[samples];
+ for (int i = 0; i < samples; i++) {
+ long start = bean.getCurrentThreadCpuTime();
+ signer.update(messageBytes);
+ byte[] signature = signer.sign();
+ timing[i] = bean.getCurrentThreadCpuTime() - start;
+ k[i] = extractK(signature, h, priv);
+ }
+ long[] sorted = Arrays.copyOf(timing, timing.length);
+ Arrays.sort(sorted);
+ double n = priv.getParams().getOrder().doubleValue();
+ double expectedAverage = n / 2;
+ double maxSigma = 0;
+ System.out.println("testTiming algorithm:" + algorithm);
+ for (int idx = samples - 1; idx > 10; idx /= 2) {
+ long cutoff = sorted[idx];
+ int count = 0;
+ BigInteger total = BigInteger.ZERO;
+ for (int i = 0; i < samples; i++) {
+ if (timing[i] <= cutoff) {
+ total = total.add(k[i]);
+ count += 1;
+ }
+ }
+ double expectedStdDev = n / Math.sqrt(12 * count);
+ double average = total.doubleValue() / count;
+ // Number of standard deviations that the average is away from
+ // the expected value:
+ double sigmas = Math.abs(expectedAverage - average) / expectedStdDev;
+ if (sigmas > maxSigma) {
+ maxSigma = sigmas;
+ }
+ System.out.println(
+ "count:"
+ + count
+ + " cutoff:"
+ + cutoff
+ + " relative average:"
+ + (average / expectedAverage)
+ + " sigmas:"
+ + sigmas);
+ }
+ // Checks if the signatures with a small timing have a biased k.
+ // We use 7 standard deviations, so that the probability of a false positive is smaller
+ // than 10^{-10}.
+ if (maxSigma >= 7) {
+ fail("Signatures with short timing have a biased k");
+ }
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.CONSCRYPT,
+ ProviderType.OPENJDK,
+ ProviderType.SPONGY_CASTLE
+ }
+ )
+ @Test
+ public void testTimingAll() throws Exception {
+ testTiming("SHA256WithECDSA", "secp256r1", EcUtil.getNistP256Params());
+ // TODO(bleichen): crypto libraries sometimes use optimized code for curves that are frequently
+ // used. Hence it would make sense to test distinct curves. But at the moment testing many
+ // curves is not practical since one test alone is already quite time consuming.
+ // testTiming("SHA224WithECDSA", "secp224r1", EcUtil.getNistP224Params());
+ // testTiming("SHA384WithECDSA", "secp384r1", EcUtil.getNistP384Params());
+ // testTiming("SHA512WithECDSA", "secp521r1", EcUtil.getNistP521Params());
+ // testTiming("SHA256WithECDSA", "brainpoolP256r1", EcUtil.getBrainpoolP256r1Params());
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java
new file mode 100644
index 0000000..c34f3f7
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonAeadTest.java
@@ -0,0 +1,303 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+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.junit.Test;
+import org.junit.Ignore;
+import android.security.keystore.KeyProtection;
+import android.security.keystore.KeyProperties;
+import java.security.KeyStore;
+
+/** This test uses test vectors in JSON format to test AEAD schemes. */
+public class JsonAeadTest {
+
+ private static final String EXPECTED_PROVIDER_NAME = TestUtil.EXPECTED_PROVIDER_NAME;
+ private static final String EXPECTED_CRYPTO_PROVIDER_NAME = TestUtil.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
+
+ /** Joins two bytearrays. */
+ protected static byte[] join(byte[] head, byte[] tail) {
+ byte[] res = new byte[head.length + tail.length];
+ System.arraycopy(head, 0, res, 0, head.length);
+ System.arraycopy(tail, 0, res, head.length, tail.length);
+ return res;
+ }
+
+ /** Convenience method to get a byte array from an JsonObject */
+ protected static byte[] getBytes(JsonObject obj, String name) throws Exception {
+ return JsonUtil.asByteArray(obj.get(name));
+ }
+
+ protected static boolean arrayEquals(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ byte res = 0;
+ for (int i = 0; i < a.length; i++) {
+ res |= (byte) (a[i] ^ b[i]);
+ }
+ return res == 0;
+ }
+
+ /**
+ * Returns an initialized instance of Cipher. Typically it is somewhat
+ * time consuming to generate a new instance of Cipher for each encryption.
+ * However, some implementations of ciphers (e.g. AES-GCM in jdk) check that
+ * the same key and nonce are not reused twice in a row to catch simple
+ * programming errors. This precaution can interfere with the tests, since
+ * the test vectors do sometimes repeat nonces. To avoid such problems cipher
+ * instances are not reused.
+ * @param algorithm the cipher algorithm including encryption mode and padding.
+ * @param opmode one of Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE
+ * @param key the key bytes
+ * @param iv the bytes of the initialization vector
+ * @param tagSize the expected size of the tag
+ * @return an initialized instance of Cipher
+ * @throws Exception if the initialization failed.
+ */
+ protected static Cipher getInitializedCipher(
+ String algorithm, int opmode, byte[] key, byte[] iv, int tagSize)
+ throws Exception {
+ Cipher cipher = Cipher.getInstance(algorithm, EXPECTED_CRYPTO_PROVIDER_NAME);
+ if (algorithm.equalsIgnoreCase("AES/GCM/NoPadding")) {
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+
+ KeyStore keyStore = KeyStore.getInstance(EXPECTED_PROVIDER_NAME);
+ keyStore.load(null);
+ keyStore.setEntry(
+ "key1",
+ new KeyStore.SecretKeyEntry(keySpec),
+ new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+ .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+ .setRandomizedEncryptionRequired(false)
+ .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+ .build());
+ // Key imported, obtain a reference to it.
+ SecretKey keyStoreKey = (SecretKey) keyStore.getKey("key1", null);
+
+ AlgorithmParameters params = AlgorithmParameters.getInstance("GCM");
+ params.init(new GCMParameterSpec(tagSize, iv));
+ cipher.init(opmode, keyStoreKey, params);
+ } else if (algorithm.equalsIgnoreCase("AES/EAX/NoPadding")
+ || algorithm.equalsIgnoreCase("AES/CCM/NoPadding")) {
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+ // TODO(bleichen): This works for BouncyCastle but looks non-standard.
+ // org.bouncycastle.crypto.params.AEADParameters works too, but would add a dependency that
+ // we want to avoid.
+ GCMParameterSpec parameters = new GCMParameterSpec(tagSize, iv);
+ cipher.init(opmode, keySpec, parameters);
+ } else if (algorithm.toUpperCase().startsWith("CHACHA")) {
+ SecretKeySpec keySpec = new SecretKeySpec(key, "ChaCha20");
+ IvParameterSpec parameters = new IvParameterSpec(iv);
+ cipher.init(opmode, keySpec, parameters);
+ } else {
+ fail("Algorithm not supported:" + algorithm);
+ }
+ return cipher;
+ }
+
+ /** Example format for test vectors
+ * {
+ * "algorithm" : "AES-EAX",
+ * "generatorVersion" : "0.0a14",
+ * "numberOfTests" : 143,
+ * "testGroups" : [
+ * {
+ * "ivSize" : 128,
+ * "keySize" : 128,
+ * "tagSize" : 128,
+ * "type" : "AES-EAX",
+ * "tests" : [
+ * {
+ * "aad" : "6bfb914fd07eae6b",
+ * "comment" : "eprint.iacr.org/2003/069",
+ * "ct" : "",
+ * "iv" : "62ec67f9c3a4a407fcb2a8c49031a8b3",
+ * "key" : "233952dee4d5ed5f9b9c6d6ff80ff478",
+ * "msg" : "",
+ * "result" : "valid",
+ * "tag" : "e037830e8389f27b025a2d6527e79d01",
+ * "tcId" : 1
+ * },
+ * ...
+ **/
+ // This is a false positive, since errorprone cannot track values passed into a method.
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testAead(String filename, String algorithm) throws Exception {
+ // Version number have the format major.minor[.subversion].
+ // Versions before 1.0 are experimental and use formats that are expected to change.
+ // Versions after 1.0 change the major number if the format changes and change
+ // the minor number if only the test vectors (but not the format) changes.
+ // Subversions are release candidate for the next version.
+ //
+ // Relevant version changes:
+ // <ul>
+ // <li> Version 0.5 adds test vectors for CCM.
+ // <li> Version 0.6 adds test vectors for Chacha20-Poly1305.
+ // Chacha20-Poly1305 is a new cipher added in jdk11.
+ // </ul>
+ final String expectedVersion = "0.6";
+
+ // Checking preconditions.
+ try {
+ Cipher.getInstance(algorithm, EXPECTED_CRYPTO_PROVIDER_NAME);
+ } catch (NoSuchAlgorithmException ex) {
+ throw ex;
+ }
+
+ JsonObject test = JsonUtil.getTestVectors(this.getClass(), filename);
+ String generatorVersion = test.get("generatorVersion").getAsString();
+ int numTests = test.get("numberOfTests").getAsInt();
+ int cntTests = 0;
+ int errors = 0;
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ int tagSize = group.get("tagSize").getAsInt();
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ cntTests++;
+ JsonObject testcase = t.getAsJsonObject();
+ int tcid = testcase.get("tcId").getAsInt();
+ String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString();
+ byte[] key = getBytes(testcase, "key");
+ byte[] iv = getBytes(testcase, "iv");
+ byte[] msg = getBytes(testcase, "msg");
+ byte[] aad = getBytes(testcase, "aad");
+ byte[] ciphertext = join(getBytes(testcase, "ct"), getBytes(testcase, "tag"));
+ // Result is one of "valid", "invalid", "acceptable".
+ // "valid" are test vectors with matching plaintext, ciphertext and tag.
+ // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag.
+ // "acceptable" are test vectors with weak parameters or legacy formats.
+ String result = testcase.get("result").getAsString();
+
+ // Test encryption
+ Cipher cipher;
+ try {
+ cipher = getInitializedCipher(algorithm, Cipher.ENCRYPT_MODE, key, iv, tagSize);
+ } catch (GeneralSecurityException ex) {
+ // Some libraries restrict key size, iv size and tag size.
+ // Because of the initialization of the cipher might fail.
+ continue;
+ }
+ try {
+ cipher.updateAAD(aad);
+ byte[] encrypted = cipher.doFinal(msg);
+ boolean eq = arrayEquals(ciphertext, encrypted);
+ if (result.equals("invalid")) {
+ if (eq) {
+ // Some test vectors use invalid parameters that should be rejected.
+ // E.g. an implementation must never encrypt using AES-GCM with an IV of length 0,
+ // since this leaks the authentication key.
+ errors++;
+ }
+ } else {
+ if (!eq) {
+ errors++;
+ }
+ }
+ } catch (GeneralSecurityException ex) {
+ if (result.equals("valid")) {
+ errors++;
+ }
+ }
+
+ // Test decryption
+ Cipher decCipher;
+ try {
+ decCipher = getInitializedCipher(algorithm, Cipher.DECRYPT_MODE, key, iv, tagSize);
+ } catch (GeneralSecurityException ex) {
+ errors++;
+ continue;
+ }
+ try {
+ decCipher.updateAAD(aad);
+ byte[] decrypted = decCipher.doFinal(ciphertext);
+ boolean eq = arrayEquals(decrypted, msg);
+ if (result.equals("invalid")) {
+ errors++;
+ } else {
+ if (!eq) {
+ errors++;
+ }
+ }
+ } catch (GeneralSecurityException ex) {
+ if (result.equals("valid")) {
+ errors++;
+ }
+ }
+ }
+ }
+ assertEquals(0, errors);
+ assertEquals(numTests, cntTests);
+ }
+
+ @Test
+ public void testAesGcm() throws Exception {
+ testAead("aes_gcm_test.json", "AES/GCM/NoPadding");
+ }
+
+ @Test
+ @Ignore // Ignored due to AES/EAX algorithm not supported in AndroidKeyStore.
+ public void testAesEax() throws Exception {
+ testAead("aes_eax_test.json", "AES/EAX/NoPadding");
+ }
+
+ @Test
+ @Ignore // Ignored due to AES/CCM algorithm not supported in AndroidKeyStore.
+ public void testAesCcm() throws Exception {
+ testAead("aes_ccm_test.json", "AES/CCM/NoPadding");
+ }
+
+ /**
+ * Tests ChaCha20-Poly1305 defined in RFC 7539.
+ *
+ * <p>The algorithm name for ChaCha20-Poly1305 is not well defined:
+ * jdk11 uses "ChaCha20-Poly1305".
+ * ConsCrypt uses "ChaCha20/Poly1305/NoPadding".
+ * These two implementations implement RFC 7539.
+ *
+ * <p>BouncyCastle has a cipher "ChaCha7539". This implementation
+ * only implements ChaCha20 with a 12 byte IV. An implementation
+ * of RFC 7539 is the class JceChaCha20Poly1305. It is unclear
+ * whether this class can be accessed through the JCA interface.
+ */
+ @Test
+ @Ignore // Ignored due to ChaCha20 algorithm not supported in AndroidKeyStore.
+ public void testChaCha20Poly1305() throws Exception {
+ // A list of potential algorithm names for ChaCha20-Poly1305.
+ String[] algorithms =
+ new String[]{"ChaCha20-Poly1305",
+ "ChaCha20/Poly1305/NoPadding"};
+ for (String name : algorithms) {
+ try {
+ Cipher.getInstance(name, EXPECTED_CRYPTO_PROVIDER_NAME);
+ } catch (NoSuchAlgorithmException ex) {
+ continue;
+ }
+ testAead("chacha20_poly1305_test.json", name);
+ return;
+ }
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java
new file mode 100644
index 0000000..a072989
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonCipherTest.java
@@ -0,0 +1,242 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This test uses test vectors in JSON format to test symmetric ciphers.
+ *
+ * <p>Ciphers tested in this class are unauthenticated ciphers (i.e. don't have additional data) and
+ * are randomized using an initialization vector as long as the JSON test vectors are represented
+ * with the type "IndCpaTest".
+ */
+@RunWith(JUnit4.class)
+public class JsonCipherTest {
+
+ /** Convenience method to get a byte array from a JsonObject. */
+ protected static byte[] getBytes(JsonObject object, String name) throws Exception {
+ return JsonUtil.asByteArray(object.get(name));
+ }
+
+ protected static boolean arrayEquals(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ byte res = 0;
+ for (int i = 0; i < a.length; i++) {
+ res |= (byte) (a[i] ^ b[i]);
+ }
+ return res == 0;
+ }
+
+ /**
+ * Initialize a Cipher instance.
+ *
+ * @param cipher an instance of a symmetric cipher that will be initialized.
+ * @param algorithm the name of the algorithm used (e.g. 'AES')
+ * @param opmode either Cipher.ENCRYPT_MODE or Cipher.DECRYPT_MODE
+ * @param key raw key bytes
+ * @param iv the initialisation vector
+ */
+ protected static void initCipher(
+ Cipher cipher, String algorithm, int opmode, byte[] key, byte[] iv) throws Exception {
+ SecretKeySpec keySpec = null;
+ if (algorithm.startsWith("AES/")) {
+ keySpec = new SecretKeySpec(key, "AES");
+ } else {
+ fail("Unsupported algorithm:" + algorithm);
+ }
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+ cipher.init(opmode, keySpec, ivSpec);
+ }
+
+
+ /** Example format for test vectors
+ * {
+ * "algorithm" : "AES-CBC-PKCS5",
+ * "generatorVersion" : "0.2.1",
+ * "numberOfTests" : 183,
+ * "header" : [
+ * ],
+ * "testGroups" : [
+ * {
+ * "ivSize" : 128,
+ * "keySize" : 128,
+ * "type" : "IndCpaTest",
+ * "tests" : [
+ * {
+ * "tcId" : 1,
+ * "comment" : "empty message",
+ * "key" : "e34f15c7bd819930fe9d66e0c166e61c",
+ * "iv" : "da9520f7d3520277035173299388bee2",
+ * "msg" : "",
+ * "ct" : "b10ab60153276941361000414aed0a9d",
+ * "result" : "valid"
+ * },
+ * ...
+ **/
+ // This is a false positive, since errorprone cannot track values passed into a method.
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testCipher(String filename, String algorithm) throws Exception {
+ // Testing with old test vectors may a reason for a test failure.
+ // Version number have the format major.minor[status].
+ // Versions before 1.0 are experimental and use formats that are expected to change.
+ // Versions after 1.0 change the major number if the format changes and change
+ // the minor number if only the test vectors (but not the format) changes.
+ // Versions meant for distribution have no status.
+ final String expectedVersion = "0.4";
+ JsonObject test = JsonUtil.getTestVectors(filename);
+ Set<String> exceptions = new TreeSet<String>();
+ String generatorVersion = test.get("generatorVersion").getAsString();
+ if (!generatorVersion.equals(expectedVersion)) {
+ System.out.println(
+ algorithm
+ + ": expecting test vectors with version "
+ + expectedVersion
+ + " found vectors with version "
+ + generatorVersion);
+ }
+ int numTests = test.get("numberOfTests").getAsInt();
+ int cntTests = 0;
+ int errors = 0;
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm is not supported. Skipping test for " + algorithm);
+ return;
+ }
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ cntTests++;
+ JsonObject testcase = t.getAsJsonObject();
+ int tcid = testcase.get("tcId").getAsInt();
+ String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString();
+ byte[] key = getBytes(testcase, "key");
+ byte[] iv = getBytes(testcase, "iv");
+ byte[] msg = getBytes(testcase, "msg");
+ byte[] ciphertext = getBytes(testcase, "ct");
+ // Result is one of "valid", "invalid", "acceptable".
+ // "valid" are test vectors with matching plaintext, ciphertext and tag.
+ // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag.
+ // "acceptable" are test vectors with weak parameters or legacy formats.
+ String result = testcase.get("result").getAsString();
+
+ // Test encryption
+ try {
+ initCipher(cipher, algorithm, Cipher.ENCRYPT_MODE, key, iv);
+ } catch (GeneralSecurityException ex) {
+ // Some libraries restrict key size, iv size and tag size.
+ // Because of the initialization of the cipher might fail.
+ System.out.println(ex.toString());
+ continue;
+ }
+ try {
+ byte[] encrypted = cipher.doFinal(msg);
+ boolean eq = arrayEquals(ciphertext, encrypted);
+ if (result.equals("invalid")) {
+ if (eq) {
+ // Some test vectors use invalid parameters that should be rejected.
+ System.out.println("Encrypted " + tc);
+ errors++;
+ }
+ } else {
+ if (!eq) {
+ System.out.println(
+ "Incorrect ciphertext for "
+ + tc
+ + " ciphertext:"
+ + TestUtil.bytesToHex(encrypted));
+ errors++;
+ }
+ }
+ } catch (GeneralSecurityException ex) {
+ if (result.equals("valid")) {
+ System.out.println("Failed to encrypt " + tc);
+ errors++;
+ }
+ }
+
+ // Test decryption
+ // The algorithms tested in this class are typically malleable. Hence, it is in possible
+ // that modifying ciphertext randomly results in some other valid ciphertext.
+ // However, all the test vectors in Wycheproof are constructed such that they have
+ // invalid padding. If this changes then the test below is too strict.
+ try {
+ initCipher(cipher, algorithm, Cipher.DECRYPT_MODE, key, iv);
+ } catch (GeneralSecurityException ex) {
+ System.out.println("Parameters accepted for encryption but not decryption " + tc);
+ errors++;
+ continue;
+ }
+ try {
+ byte[] decrypted = cipher.doFinal(ciphertext);
+ boolean eq = arrayEquals(decrypted, msg);
+ if (result.equals("invalid")) {
+ System.out.println("Decrypted invalid ciphertext " + tc + " eq:" + eq);
+ errors++;
+ } else {
+ if (!eq) {
+ System.out.println(
+ "Incorrect decryption " + tc + " decrypted:" + TestUtil.bytesToHex(decrypted));
+ }
+ }
+ } catch (GeneralSecurityException ex) {
+ exceptions.add(ex.getMessage());
+ if (result.equals("valid")) {
+ System.out.println("Failed to decrypt " + tc);
+ errors++;
+ }
+ }
+ }
+ }
+ assertEquals(0, errors);
+ assertEquals(numTests, cntTests);
+ // Generally it is preferable if trying to decrypt ciphertexts with incorrect paddings
+ // does not leak information about invalid paddings through exceptions.
+ // Such information could simplify padding attacks. Ideally, providers should not include
+ // any distinguishing features in the exception. Hence, we expect just one exception here.
+ //
+ // Seeing distinguishable exception, doesn't necessarily mean that protocols using
+ // AES/CBC/PKCS5Padding with the tested provider are vulnerable to attacks. Rather it means
+ // that the provider might simplify attacks if the protocol is using AES/CBC/PKCS5Padding
+ // incorrectly.
+ System.out.println("Number of distinct exceptions:" + exceptions.size());
+ for (String ex : exceptions) {
+ System.out.println(ex);
+ }
+ assertEquals(1, exceptions.size());
+ }
+
+ @Test
+ public void testAesCbcPkcs5() throws Exception {
+ testCipher("aes_cbc_pkcs5_test.json", "AES/CBC/PKCS5Padding");
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java
new file mode 100644
index 0000000..5a8b877
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonEcdhTest.java
@@ -0,0 +1,209 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPrivateKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import javax.crypto.KeyAgreement;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** This test uses test vectors in JSON format to check implementations of ECDH. */
+@RunWith(JUnit4.class)
+public class JsonEcdhTest {
+
+ /** Convenience mehtod to get a String from a JsonObject */
+ protected static String getString(JsonObject object, String name) throws Exception {
+ return object.get(name).getAsString();
+ }
+
+ /** Convenience method to get a BigInteger from a JsonObject */
+ protected static BigInteger getBigInteger(JsonObject object, String name) throws Exception {
+ return JsonUtil.asBigInteger(object.get(name));
+ }
+
+ /** Convenience method to get a byte array from a JsonObject */
+ protected static byte[] getBytes(JsonObject object, String name) throws Exception {
+ return JsonUtil.asByteArray(object.get(name));
+ }
+
+ /**
+ * Example for test vector
+ * {
+ * "algorithm" : "ECDH",
+ * "header" : [],
+ * "notes" : {
+ * "AddSubChain" : "The private key has a special value....",
+ * }
+ * "generatorVersion" : "0.7",
+ * "numberOfTests" : 308,
+ * "testGroups" : [
+ * {
+ * "type" : "EcdhTest",
+ * "tests" : [
+ * {
+ * "comment" : "normal case",
+ * "curve" : "secp224r1",
+ * "private" : "565577a49415ca761a0322ad54e4ad0ae7625174baf372c2816f5328",
+ * "public" : "30...",
+ * "result" : "valid",
+ * "shared" : "b8ecdb552d39228ee332bafe4886dbff272f7109edf933bc7542bd4f",
+ * "tcId" : 1
+ * },
+ * ...
+ **/
+ public void testEcdhComp(String filename) throws Exception {
+ JsonObject test = JsonUtil.getTestVectors(filename);
+
+ // This test expects test vectors as defined in wycheproof/schemas/ecdh_test_schema.json.
+ // In particular, this means that the public keys use X509 encoding.
+ // Test vectors with different encodings of the keys have a different schema.
+ final String expectedSchema = "ecdh_test_schema.json";
+ String schema = test.get("schema").getAsString();
+ assertEquals("Unexpected schema in file:" + filename, expectedSchema, schema);
+
+ int numTests = test.get("numberOfTests").getAsInt();
+ int passedTests = 0;
+ int rejectedTests = 0; // invalid test vectors leading to exceptions
+ int skippedTests = 0; // valid test vectors leading to exceptions
+ int errors = 0;
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ String curve = getString(group, "curve");
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ JsonObject testcase = t.getAsJsonObject();
+ int tcid = testcase.get("tcId").getAsInt();
+ String comment = getString(testcase, "comment");
+ BigInteger priv = getBigInteger(testcase, "private");
+ byte[] publicEncoded = getBytes(testcase, "public");
+ String result = getString(testcase, "result");
+ String expectedHex = getString(testcase, "shared");
+ KeyFactory kf = KeyFactory.getInstance("EC");
+ try {
+ ECPrivateKeySpec spec = new ECPrivateKeySpec(priv, EcUtil.getCurveSpecRef(curve));
+ PrivateKey privKey = kf.generatePrivate(spec);
+ X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(publicEncoded);
+ PublicKey pubKey = kf.generatePublic(x509keySpec);
+ KeyAgreement ka = KeyAgreement.getInstance("ECDH");
+ ka.init(privKey);
+ ka.doPhase(pubKey, true);
+ String sharedHex = TestUtil.bytesToHex(ka.generateSecret());
+ if (result.equals("invalid")) {
+ System.out.println(
+ "Computed ECDH with invalid parameters"
+ + " tcId:"
+ + tcid
+ + " comment:"
+ + comment
+ + " shared:"
+ + sharedHex);
+ errors++;
+ } else if (!expectedHex.equals(sharedHex)) {
+ System.out.println(
+ "Incorrect ECDH computation"
+ + " tcId:"
+ + tcid
+ + " comment:"
+ + comment
+ + "\nshared:"
+ + sharedHex
+ + "\nexpected:"
+ + expectedHex);
+ errors++;
+ } else {
+ passedTests++;
+ }
+ } catch (InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException ex) {
+ // These are the exception that we expect to see when a curve is not implemented
+ // or when a key is not valid.
+ if (result.equals("valid")) {
+ skippedTests++;
+ } else {
+ rejectedTests++;
+ }
+ } catch (Exception ex) {
+ // Other exceptions typically indicate that something is wrong with the implementation.
+ System.out.println(
+ "Test vector with tcId:" + tcid + " comment:" + comment + " throws:" + ex.toString());
+ errors++;
+ }
+ }
+ }
+ assertEquals(0, errors);
+ assertEquals(numTests, passedTests + rejectedTests + skippedTests);
+ }
+
+ @Test
+ public void testSecp224r1() throws Exception {
+ testEcdhComp("ecdh_secp224r1_test.json");
+ }
+
+ @Test
+ public void testSecp256r1() throws Exception {
+ testEcdhComp("ecdh_secp256r1_test.json");
+ }
+
+ @Test
+ public void testSecp384r1() throws Exception {
+ testEcdhComp("ecdh_secp384r1_test.json");
+ }
+
+ @Test
+ public void testSecp521r1() throws Exception {
+ testEcdhComp("ecdh_secp521r1_test.json");
+ }
+
+ @Test
+ public void testSecp256k1() throws Exception {
+ testEcdhComp("ecdh_secp256k1_test.json");
+ }
+
+ @Test
+ public void testBrainpoolP224r1() throws Exception {
+ testEcdhComp("ecdh_brainpoolP224r1_test.json");
+ }
+
+ @Test
+ public void testBrainpoolP256r1() throws Exception {
+ testEcdhComp("ecdh_brainpoolP256r1_test.json");
+ }
+
+ @Test
+ public void testBrainpoolP320r1() throws Exception {
+ testEcdhComp("ecdh_brainpoolP320r1_test.json");
+ }
+
+ @Test
+ public void testBrainpoolP384r1() throws Exception {
+ testEcdhComp("ecdh_brainpoolP384r1_test.json");
+ }
+
+ @Test
+ public void testBrainpoolP512r1() throws Exception {
+ testEcdhComp("ecdh_brainpoolP512r1_test.json");
+ }
+
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java
new file mode 100644
index 0000000..eeb48ec
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonMacTest.java
@@ -0,0 +1,332 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Locale;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** This test uses test vectors in JSON format to test MAC primitives. */
+@RunWith(JUnit4.class)
+public class JsonMacTest {
+
+ /** Convenience method to get a byte array from an JsonObject */
+ protected static byte[] getBytes(JsonObject obj, String name) throws Exception {
+ return JsonUtil.asByteArray(obj.get(name));
+ }
+
+ protected static boolean arrayEquals(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ byte res = 0;
+ for (int i = 0; i < a.length; i++) {
+ res |= (byte) (a[i] ^ b[i]);
+ }
+ return res == 0;
+ }
+
+ /**
+ * Computes a MAC.
+ *
+ * @param algorithm the algorithm.
+ * @param key the key bytes
+ * @param msg the message to MAC.
+ * @param tagSize the expected size of the tag in bits.
+ * @return the tag
+ * @throws GeneralSecurityException if the algorithm or the parameter sizes are not supported or
+ * if the initialization failed. For example one case are GMACs with a tag size othe than 128
+ * bits, since the JCE interface does not seem to support such a specification.
+ */
+ protected static byte[] computeMac(String algorithm, byte[] key, byte[] msg, int tagSize)
+ throws GeneralSecurityException {
+ Mac mac = Mac.getInstance(algorithm);
+ algorithm = algorithm.toUpperCase(Locale.ENGLISH);
+ if (algorithm.startsWith("HMAC")) {
+ SecretKeySpec keySpec = new SecretKeySpec(key, algorithm);
+ // TODO(bleichen): Is there a provider independent truncation?
+ // The class javax.xml.crypto.dsig.spec.HMACParameterSpec would allow to
+ // truncate HMAC tags as follows:
+ // <pre>
+ // HMACParameterSpec params = new HMACParameterSpec(tagSize);
+ // mac.init(keySpec, params);
+ // mac.update(msg);
+ // return mac.doFinal();
+ // </pre>
+ // But this class is often not supported. Hence the computation here, just computes a
+ // full length tag and truncates it. The drawback of having to truncate tags is that
+ // the caller has to compare truncated tags during verification.
+ mac.init(keySpec);
+ mac.update(msg);
+ byte[] tag = mac.doFinal();
+ return Arrays.copyOf(tag, tagSize / 8);
+ } else {
+ throw new NoSuchAlgorithmException(algorithm);
+ }
+ }
+
+ /**
+ * Tests a randomized MAC (i.e. a message authetication that takes an additional IV as parameter)
+ * against test vectors.
+ *
+ * @param filename the JSON file with the test vectors.
+ */
+ public void testMac(String filename) throws Exception {
+ // Checking preconditions.
+ JsonObject test = JsonUtil.getTestVectors(filename);
+ String algorithm = test.get("algorithm").getAsString();
+ try {
+ Mac.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm is not supported. Skipping test for " + algorithm);
+ return;
+ }
+
+ int numTests = test.get("numberOfTests").getAsInt();
+ int cntTests = 0;
+ int passedTests = 0;
+ int errors = 0;
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ int tagSize = group.get("tagSize").getAsInt();
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ cntTests++;
+ JsonObject testcase = t.getAsJsonObject();
+ int tcid = testcase.get("tcId").getAsInt();
+ String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString();
+ byte[] key = getBytes(testcase, "key");
+ byte[] msg = getBytes(testcase, "msg");
+ byte[] expectedTag = getBytes(testcase, "tag");
+ // Result is one of "valid", "invalid", "acceptable".
+ // "valid" are test vectors with matching plaintext, ciphertext and tag.
+ // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag.
+ // "acceptable" are test vectors with weak parameters or legacy formats.
+ String result = testcase.get("result").getAsString();
+
+ byte[] computedTag = null;
+ try {
+ computedTag = computeMac(algorithm, key, msg, tagSize);
+ } catch (GeneralSecurityException ex) {
+ // Some libraries restrict key size or tag size. Hence valid MACs might be
+ // rejected.
+ continue;
+ } catch (IllegalArgumentException ex) {
+ // Thrown by javax.crypto.spec.SecretKeySpec (e.g. when the key is empty).
+ continue;
+ }
+
+ boolean eq = arrayEquals(expectedTag, computedTag);
+ if (result.equals("invalid")) {
+ if (eq) {
+ // Some test vectors use invalid parameters that should be rejected.
+ // E.g. an implementation must not allow AES-GMAC with an IV of length 0,
+ // since this leaks the authentication key.
+ System.out.println("Computed mac for test case " + tc);
+ errors++;
+ }
+ } else {
+ if (eq) {
+ passedTests++;
+ } else {
+ System.out.println(
+ "Incorrect tag for "
+ + tc
+ + " expected:"
+ + TestUtil.bytesToHex(expectedTag)
+ + " computed:"
+ + TestUtil.bytesToHex(computedTag));
+ errors++;
+ }
+ }
+ }
+ }
+ System.out.println("passed Tests for " + algorithm + ":" + passedTests);
+ assertEquals(0, errors);
+ assertEquals(numTests, cntTests);
+ }
+
+ /**
+ * Returns an initialized instance of a randomized MAC.
+ *
+ * @param algorithm the algorithm.
+ * @param key the key bytes
+ * @param iv the bytes of the initialization vector
+ * @param tagSize the expected size of the tag in bits.
+ * @return an initialized instance of a MAC.
+ * @throws GeneralSecurityException if the algorithm or the parameter sizes are not supported or
+ * if the initialization failed. For example one case are GMACs with a tag size othe than 128
+ * bits, since the JCE interface does not seem to support such a specification.
+ */
+ protected static Mac getInitializedMacWithIv(String algorithm, byte[] key, byte[] iv, int tagSize)
+ throws GeneralSecurityException {
+ Mac mac = Mac.getInstance(algorithm);
+ algorithm = algorithm.toUpperCase(Locale.ENGLISH);
+ if (algorithm.equals("AES-GMAC")) {
+ SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
+ if (tagSize != 128) {
+ throw new InvalidAlgorithmParameterException("only 128-bit tag is supported");
+ }
+ IvParameterSpec params = new IvParameterSpec(iv);
+ // TODO(bleichen): I'm unaware of a method that allows to specify the tag size in JCE.
+ // E.g. the following parameter specification does not work (at least not in BC):
+ // GCMParameterSpec params = new GCMParameterSpec(tagSize, iv);
+ mac.init(keySpec, params);
+ return mac;
+ } else {
+ throw new NoSuchAlgorithmException(algorithm);
+ }
+ }
+
+ /**
+ * Tests a randomized MAC (i.e. a message authetication that takes an additional IV as
+ * parameter) against test vectors.
+ *
+ * @param filename the JSON file with the test vectors.
+ * @param algorithm the JCE name of the algorithm to test.
+ */
+ public void testMacWithIv(String filename, String algorithm) throws Exception {
+ // Checking preconditions.
+ try {
+ Mac.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm is not supported. Skipping test for " + algorithm);
+ return;
+ }
+
+ JsonObject test = JsonUtil.getTestVectors(filename);
+ int numTests = test.get("numberOfTests").getAsInt();
+ int cntTests = 0;
+ int passedTests = 0;
+ int errors = 0;
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ int tagSize = group.get("tagSize").getAsInt();
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ cntTests++;
+ JsonObject testcase = t.getAsJsonObject();
+ int tcid = testcase.get("tcId").getAsInt();
+ String tc = "tcId: " + tcid + " " + testcase.get("comment").getAsString();
+ byte[] key = getBytes(testcase, "key");
+ byte[] iv = getBytes(testcase, "iv");
+ byte[] msg = getBytes(testcase, "msg");
+ byte[] expectedTag = getBytes(testcase, "tag");
+ // Result is one of "valid", "invalid", "acceptable".
+ // "valid" are test vectors with matching plaintext, ciphertext and tag.
+ // "invalid" are test vectors with invalid parameters or invalid ciphertext and tag.
+ // "acceptable" are test vectors with weak parameters or legacy formats.
+ String result = testcase.get("result").getAsString();
+
+ Mac mac;
+ try {
+ mac = getInitializedMacWithIv(algorithm, key, iv, tagSize);
+ } catch (GeneralSecurityException ex) {
+ // Some libraries restrict key size, iv size and tag size.
+ // Because of the initialization of the Mac might fail.
+ continue;
+ } catch (IllegalArgumentException ex) {
+ // Thrown by javax.crypto.spec.SecretKeySpec (e.g. when the key is empty).
+ continue;
+ }
+
+ byte[] computedTag = mac.doFinal(msg);
+ boolean eq = arrayEquals(expectedTag, computedTag);
+ if (result.equals("invalid")) {
+ if (eq) {
+ // Some test vectors use invalid parameters that should be rejected.
+ // E.g. an implementation must not allow AES-GMAC with an IV of length 0,
+ // since this leaks the authentication key.
+ System.out.println("Computed mac for test case " + tc);
+ errors++;
+ }
+ } else {
+ if (eq) {
+ passedTests++;
+ } else {
+ System.out.println(
+ "Incorrect tag for "
+ + tc
+ + " expected:"
+ + TestUtil.bytesToHex(expectedTag)
+ + " computed:"
+ + TestUtil.bytesToHex(computedTag));
+ errors++;
+ }
+ }
+ }
+ }
+ System.out.println("passed Tests for " + algorithm + ":" + passedTests);
+ assertEquals(0, errors);
+ assertEquals(numTests, cntTests);
+ }
+
+ @Test
+ public void testHmacSha1() throws Exception {
+ testMac("hmac_sha1_test.json");
+ }
+
+ @Test
+ public void testHmacSha224() throws Exception {
+ testMac("hmac_sha224_test.json");
+ }
+
+ @Test
+ public void testHmacSha256() throws Exception {
+ testMac("hmac_sha256_test.json");
+ }
+
+ @Test
+ public void testHmacSha384() throws Exception {
+ testMac("hmac_sha384_test.json");
+ }
+
+ @Test
+ public void testHmacSha512() throws Exception {
+ testMac("hmac_sha512_test.json");
+ }
+
+ @Test
+ public void testHmacSha3_224() throws Exception {
+ testMac("hmac_sha3_224_test.json");
+ }
+
+ @Test
+ public void testHmacSha3_256() throws Exception {
+ testMac("hmac_sha3_256_test.json");
+ }
+
+ @Test
+ public void testHmacSha3_384() throws Exception {
+ testMac("hmac_sha3_384_test.json");
+ }
+
+ @Test
+ public void testHmacSha3_512() throws Exception {
+ testMac("hmac_sha3_512_test.json");
+ }
+
+ @Test
+ public void testAesGmac() throws Exception {
+ testMacWithIv("gmac_test.json", "AES-GMAC");
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java
new file mode 100644
index 0000000..21bf0d5
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/JsonSignatureTest.java
@@ -0,0 +1,966 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.security.wycheproof.WycheproofRunner.ExcludedTest;
+import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
+import com.google.security.wycheproof.WycheproofRunner.ProviderType;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This test uses test vectors in JSON format to check digital signature schemes. There are still a
+ * lot of open questions, e.g. the format for the test vectors is not yet finalized. Therefore, we
+ * are not integrating the tests here into other tests
+ */
+@RunWith(JUnit4.class)
+public class JsonSignatureTest {
+
+ /**
+ * Defines the format of the signatures. RAW is used when the signature scheme already
+ * defines an encoding (e.g. this is used for RSA signatures).
+ */
+ public enum Format { RAW, ASN, P1363 };
+
+ /** Convenience method to get a String from a JsonObject */
+ protected static String getString(JsonObject object, String name) {
+ return object.get(name).getAsString();
+ }
+
+ /** Convenience method to get a byte array from a JsonObject */
+ protected static byte[] getBytes(JsonObject object, String name) throws Exception {
+ return JsonUtil.asByteArray(object.get(name));
+ }
+
+ /**
+ * Convert hash names, so that they can be used in an algorithm name for a signature. The
+ * algorithm names used in JCA are a bit inconsequential. E.g. a dash is necessary for message
+ * digests (e.g. "SHA-256") but are not used in the corresponding names for digital signatures
+ * (e.g. "SHA256WITHECDSA"). Providers sometimes use distinct algorithm names for the same
+ * cryptographic primitive. On the other hand, the dash remains for SHA-3. Hence, the correct
+ * name for ECDSA with SHA3-256 is "SHA3-256WithECDSA".
+ *
+ * <p>See https://docs.oracle.com/en/java/javase/11/docs/specs/security/standard-names.html
+ *
+ * @param md the name of a message digest
+ * @return the name of the message digest when used in a signature algorithm.
+ */
+ protected static String convertMdName(String md) {
+ if (md.equalsIgnoreCase("SHA-1")) {
+ return "SHA1";
+ } else if (md.equalsIgnoreCase("SHA-224")) {
+ return "SHA224";
+ } else if (md.equalsIgnoreCase("SHA-256")) {
+ return "SHA256";
+ } else if (md.equalsIgnoreCase("SHA-384")) {
+ return "SHA384";
+ } else if (md.equalsIgnoreCase("SHA-512")) {
+ return "SHA512";
+ } else if (md.equalsIgnoreCase("SHA-512/224")) {
+ return "SHA512/224";
+ } else if (md.equalsIgnoreCase("SHA-512/256")) {
+ return "SHA512/256";
+ }
+ return md;
+ }
+
+ /**
+ * Returns an instance of java.security.Signature for an algorithm name, a digest name and a
+ * signature format.
+ *
+ * @param md the name of the message digest (e.g. "SHA-256")
+ * @param signatureAlgorithm the name of the signature algorithm (e.g. "ECDSA")
+ * @param signatureFormat the format of the signatures.
+ * @return an instance of java.security.Signature if the algorithm is known
+ * @throws NoSuchAlgorithmException if the algorithm is not known
+ */
+ protected static Signature getSignatureInstance(
+ JsonObject group, String signatureAlgorithm, Format signatureFormat)
+ throws NoSuchAlgorithmException {
+ String md = "";
+ if (group.has("sha")) {
+ md = convertMdName(getString(group, "sha"));
+ }
+ if (signatureAlgorithm.equals("ECDSA") || signatureAlgorithm.equals("DSA")) {
+ if (signatureFormat == Format.ASN) {
+ return Signature.getInstance(md + "WITH" + signatureAlgorithm);
+ } else if (signatureFormat == Format.P1363) {
+ // The algorithm names for signature schemes with P1363 format have distinct names
+ // in distinct providers. This is mainly the case since the P1363 format has only
+ // been added in jdk11, while providers such as BouncyCastle added the format earlier
+ // than that. Hence the code below just tries known algorithm names.
+ try {
+ String jdkName = md + "WITH" + signatureAlgorithm + "inP1363Format";
+ return Signature.getInstance(jdkName);
+ } catch (NoSuchAlgorithmException ex) {
+ // jdkName is not known.
+ }
+ try {
+ String bcName = md + "WITHPLAIN-" + signatureAlgorithm;
+ return Signature.getInstance(bcName);
+ } catch (NoSuchAlgorithmException ex) {
+ // bcName is not known.
+ }
+ }
+ } else if (signatureAlgorithm.equals("RSA")) {
+ if (signatureFormat == Format.RAW) {
+ return Signature.getInstance(md + "WITH" + signatureAlgorithm);
+ }
+ } else if (signatureAlgorithm.equals("ED25519") || signatureAlgorithm.equals("ED448")) {
+ if (signatureFormat == Format.RAW) {
+ // http://openjdk.java.net/jeps/339
+ try {
+ return Signature.getInstance(signatureAlgorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ // signatureAlgorithm is not known.
+ }
+ // An alternative name (e.g. used by BouncyCastle) is "EDDSA".
+ try {
+ return Signature.getInstance("EDDSA");
+ } catch (NoSuchAlgorithmException ex) {
+ // "EDDSA" is not known either.
+ }
+ }
+ }
+ throw new NoSuchAlgorithmException(
+ "Algorithm "
+ + signatureAlgorithm
+ + " with format "
+ + signatureFormat
+ + " is not supported");
+ }
+
+ /**
+ * Returns the expected JSON schema for a given test or "" if the schema is undefined.
+ * The purpose of this function is to perform a sanity test with the goal to recognize
+ * incorrect test setups.
+ * @param signatureAlgorithm the signataure algorithm (e.g. "ECDSA")
+ * @param signatureFormat the format of the signatures
+ * @param verify true if verification is tested, false if signature generations is tested.
+ */
+ protected static String expectedSchema(String signatureAlgorithm, Format signatureFormat,
+ boolean verify) {
+ if (verify) {
+ if (signatureAlgorithm.equals("ECDSA")) {
+ switch (signatureFormat) {
+ case ASN: return "ecdsa_verify_schema.json";
+ case P1363: return "ecdsa_p1363_verify_schema.json";
+ default: break;
+ }
+ } else if (signatureAlgorithm.equals("DSA")) {
+ switch (signatureFormat) {
+ case ASN: return "dsa_verify_schema.json";
+ case P1363: return "dsa_p1363_verify_schema.json";
+ default: break;
+ }
+ } else if (signatureAlgorithm.equals("RSA")) {
+ // Only RSA-PKCS1 is implemented in this unit test.
+ // RSA-PSS signatures have their own unit test, because the algorithm parameters
+ // require a setup that is a little different.
+ switch (signatureFormat) {
+ case RAW: return "rsassa_pkcs1_verify_schema.json";
+ default: break;
+ }
+ } else if (signatureAlgorithm.equals("ED25519") || signatureAlgorithm.equals("ED448")) {
+ switch (signatureFormat) {
+ case RAW:
+ return "eddsa_verify_schema.json";
+ default:
+ break;
+ }
+ }
+ } else {
+ // signature generation
+ if (signatureAlgorithm.equals("RSA")) {
+ return "rsassa_pkcs1_generate_schema.json";
+ } else if (signatureAlgorithm.equals("ED25519") || signatureAlgorithm.equals("ED448")) {
+ // TODO(bleichen):
+ switch (signatureFormat) {
+ case RAW:
+ return "eddsa_verify_schema.json";
+ default:
+ break;
+ }
+ }
+ }
+ // If the schema is not defined then the tests below still run. The only drawback is that
+ // incorrect test setups are not recognized and will probably lead to failures later.
+ return "";
+ }
+ /**
+ * Get a PublicKey from a JsonObject.
+ *
+ * <p>object contains the key in multiple formats: "key" : elements of the public key "keyDer":
+ * the key in ASN encoding encoded hexadecimal "keyPem": the key in Pem format encoded hexadecimal
+ * The test can use the format that is most convenient.
+ */
+ // This is a false positive, since errorprone cannot track values passed into a method.
+ @SuppressWarnings("InsecureCryptoUsage")
+ protected static PublicKey getPublicKey(JsonObject group, String algorithm) throws Exception {
+ KeyFactory kf;
+ if (algorithm.equals("ECDSA")) {
+ kf = KeyFactory.getInstance("EC");
+ } else if (algorithm.equals("ED25519") || algorithm.equals("ED448")) {
+ // http://openjdk.java.net/jeps/339
+ kf = KeyFactory.getInstance("EdDSA");
+ } else {
+ kf = KeyFactory.getInstance(algorithm);
+ }
+ byte[] encoded = TestUtil.hexToBytes(getString(group, "keyDer"));
+ X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(encoded);
+ return kf.generatePublic(x509keySpec);
+ }
+
+ /**
+ * Get a PrivateKey from a JsonObject.
+ */
+ // This is a false positive, since errorprone cannot track values passed into a method.
+ @SuppressWarnings("InsecureCryptoUsage")
+ protected static PrivateKey getPrivateKey(JsonObject object, String algorithm) throws Exception {
+ if (algorithm.equals("RSA")) {
+ KeyFactory kf = KeyFactory.getInstance(algorithm);
+ byte[] encoded = TestUtil.hexToBytes(getString(object, "privateKeyPkcs8"));
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+ return kf.generatePrivate(keySpec);
+ } else {
+ throw new NoSuchAlgorithmException("Algorithm " + algorithm + " is not supported");
+ }
+ }
+
+ /**
+ * Tests the signature verification with test vectors in a given JSON file.
+ *
+ * <p> Example format for test vectors
+ * {
+ * "algorithm": "ECDSA",
+ * "generatorVersion": "0.0a13",
+ * "numberOfTests": 217,
+ * "testGroups": [
+ * {
+ * "key": {
+ * "curve": "secp256r1",
+ * "type": "ECPublicKey",
+ * "wx": "0c9c4bc2617c81eb2dcbfda2db2a370a955be86a0d2e95fcb86a99f90cf046573",
+ * "wy": "0c400363b1b6bcc3595a7d6d3575ccebcbb03f90ba8e58da2bc4824272f4fecff"
+ * },
+ * "keyDer": <X509encoded key>
+ * "keyPem": "-----BEGIN PUBLIC KEY-----\ ... \n-----END PUBLIC KEY-----",
+ * "sha": "SHA-256",
+ * "tests": [
+ * {
+ * "comment": "random signature",
+ * "msg": "48656c6c6f",
+ * "result": "valid",
+ * "sig": "...",
+ * "tcId": 1
+ * },
+ * ...
+ * }
+ *
+ * @param filename the filename of the test vectors
+ * @param signatureAlgorithm the algorithm name of the test vectors
+ * @param signatureFormat the format of the signatures. This should be Format.P1363 for
+ * P1363 encoded signatures Format.ASN for ASN.1 encoded signature and Format.RAW
+ otherwise.
+ * @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
+ * This is for example used for files with test vectors that use elliptic curves that are not
+ * commonly supported.
+ **/
+ public void testVerification(
+ String filename, String signatureAlgorithm, Format signatureFormat, boolean allowSkippingKeys)
+ throws Exception {
+ JsonObject test = JsonUtil.getTestVectors(filename);
+ // Checks whether the test vectors in the file use the expected algorithm and the expected
+ // format for the signatures.
+ String schema = expectedSchema(signatureAlgorithm, signatureFormat, true);
+ String actualSchema = getString(test, "schema");
+ if (!schema.isEmpty() && !schema.equals(actualSchema)) {
+ System.out.println(
+ signatureAlgorithm
+ + ": expecting test vectors with schema "
+ + schema
+ + " found vectors with schema "
+ + actualSchema);
+ }
+ int numTests = test.get("numberOfTests").getAsInt();
+ int cntTests = 0;
+ int verifiedSignatures = 0;
+ int errors = 0;
+ int skippedKeys = 0;
+ int skippedAlgorithms = 0;
+ int supportedKeys = 0;
+ Set<String> skippedGroups = new HashSet<String>();
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ PublicKey key;
+ try {
+ key = getPublicKey(group, signatureAlgorithm);
+ } catch (GeneralSecurityException ex) {
+ if (!allowSkippingKeys) {
+ throw ex;
+ }
+ if (group.has("key")) {
+ JsonObject keyStruct = group.getAsJsonObject("key");
+ if (keyStruct.has("curve")) {
+ skippedGroups.add("curve = " + getString(keyStruct, "curve"));
+ }
+ }
+ skippedKeys++;
+ continue;
+ }
+ Signature verifier;
+ try {
+ verifier = getSignatureInstance(group, signatureAlgorithm, signatureFormat);
+ } catch (NoSuchAlgorithmException ex) {
+ if (!allowSkippingKeys) {
+ throw ex;
+ }
+ skippedAlgorithms++;
+ continue;
+ }
+ supportedKeys++;
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ cntTests++;
+ JsonObject testcase = t.getAsJsonObject();
+ byte[] message = getBytes(testcase, "msg");
+ byte[] signature = getBytes(testcase, "sig");
+ int tcid = testcase.get("tcId").getAsInt();
+ String sig = TestUtil.bytesToHex(signature);
+ String result = getString(testcase, "result");
+ verifier.initVerify(key);
+ verifier.update(message);
+ boolean verified = false;
+ Exception failure = null;
+ try {
+ verified = verifier.verify(signature);
+ } catch (SignatureException ex) {
+ // verify can throw SignatureExceptions if the signature is malformed.
+ // We don't flag these cases and simply consider the signature as invalid.
+ verified = false;
+ failure = ex;
+ } catch (java.lang.ArithmeticException ex) {
+ // b/33446454 The Sun provider may throw an ArithmeticException instead of
+ // the expected SignatureException for DSA signatures.
+ // We should eventually remove this.
+ verified = false;
+ failure = ex;
+ } catch (Exception ex) {
+ // Other exceptions (i.e. unchecked exceptions) are considered as error
+ // since a third party should never be able to cause such exceptions.
+ System.out.println(
+ signatureAlgorithm
+ + " signature throws "
+ + ex.toString()
+ + " "
+ + filename
+ + " tcId:"
+ + tcid
+ + " sig:"
+ + sig);
+ verified = false;
+ failure = ex;
+ errors++;
+ }
+ if (!verified && result.equals("valid")) {
+ String reason = "";
+ if (failure != null) {
+ reason = " reason:" + failure;
+ }
+ System.out.println(
+ "Valid "
+ + signatureAlgorithm
+ + " signature not verified."
+ + " "
+ + filename
+ + " tcId:"
+ + tcid
+ + " sig:"
+ + sig
+ + reason);
+ errors++;
+ } else if (verified) {
+ if (result.equals("invalid")) {
+ System.out.println(
+ "Invalid"
+ + signatureAlgorithm
+ + " signature verified."
+ + " "
+ + filename
+ + " tcId:"
+ + tcid
+ + " sig:"
+ + sig);
+ errors++;
+ } else {
+ verifiedSignatures++;
+ }
+ }
+ }
+ }
+ // Prints some information if tests were skipped. This avoids giving
+ // the impression that algorithms are supported.
+ if (skippedKeys > 0 || skippedAlgorithms > 0 || verifiedSignatures == 0) {
+ System.out.println(
+ "File:"
+ + filename
+ + " number of skipped keys:"
+ + skippedKeys
+ + " number of skipped algorithms:"
+ + skippedAlgorithms
+ + " number of supported keys:"
+ + supportedKeys
+ + " verified signatures:"
+ + verifiedSignatures);
+ for (String s : skippedGroups) {
+ System.out.println("Skipped groups where " + s);
+ }
+ }
+ assertEquals(0, errors);
+ if (skippedKeys == 0 && skippedAlgorithms == 0) {
+ assertEquals(numTests, cntTests);
+ }
+ }
+
+ /**
+ * Tests signature generation of deterministic signature schemes such as RSA-PKCS#1 v1.5.
+ *
+ * <p>The test expects that signatures are fully complying with the standards.
+ * E.g. it is acceptable when RSA-PKCS#1 verification considers ASN encodings of the
+ * digest name with a missing NULL value for legacy reasons. However, it is considered not
+ * acceptable when the signature generation does not include the NULL value.
+ *
+ * @param filename the filename of the test vectors
+ * @param signatureAlgorithm the algorithm name of the test vectors (e.g. "RSA")
+ * @param signatureFormat the format of the signatures.
+ * @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
+ */
+ public void testSigning(
+ String filename, String signatureAlgorithm, Format signatureFormat,
+ boolean allowSkippingKeys) throws Exception {
+ JsonObject test = JsonUtil.getTestVectors(filename);
+ // Checks whether the test vectors in the file use the expected algorithm and the expected
+ // format for the signatures.
+ String schema = expectedSchema(signatureAlgorithm, signatureFormat, false);
+ String actualSchema = getString(test, "schema");
+ if (!schema.isEmpty() && !schema.equals(actualSchema)) {
+ System.out.println(
+ signatureAlgorithm
+ + ": expecting test vectors with schema "
+ + schema
+ + " found vectors with schema "
+ + actualSchema);
+ }
+ int cntTests = 0;
+ int errors = 0;
+ int skippedKeys = 0;
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ PrivateKey key;
+ try {
+ key = getPrivateKey(group, signatureAlgorithm);
+ } catch (GeneralSecurityException ex) {
+ skippedKeys++;
+ continue;
+ }
+ Signature signer;
+ try {
+ signer = getSignatureInstance(group, signatureAlgorithm, signatureFormat);
+ } catch (NoSuchAlgorithmException ex) {
+ skippedKeys++;
+ continue;
+ }
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ JsonObject testcase = t.getAsJsonObject();
+ String result = getString(testcase, "result");
+ byte[] message = getBytes(testcase, "msg");
+ byte[] signature = getBytes(testcase, "sig");
+ int tcid = testcase.get("tcId").getAsInt();
+ String expectedSig = TestUtil.bytesToHex(signature);
+ try {
+ signer.initSign(key);
+ signer.update(message);
+ String sig = TestUtil.bytesToHex(signer.sign());
+ if (!sig.equals(expectedSig)) {
+ System.out.println(
+ "Incorrect signature generated "
+ + filename
+ + " tcId:"
+ + tcid
+ + " expected:"
+ + expectedSig
+ + " sig:"
+ + sig);
+ errors++;
+ } else {
+ cntTests++;
+ }
+ } catch (InvalidKeyException | SignatureException ex) {
+ if (result.equals("valid")) {
+ System.out.println(
+ "Failed to sign "
+ + filename
+ + " tcId:"
+ + tcid
+ + " with exception:"
+ + ex);
+
+ errors++;
+ }
+ }
+ }
+ }
+ assertEquals(0, errors);
+ if (skippedKeys > 0) {
+ System.out.println("File:" + filename);
+ System.out.println("Number of signatures verified:" + cntTests);
+ System.out.println("Number of skipped keys:" + skippedKeys);
+ assertTrue(allowSkippingKeys);
+ }
+ }
+
+ @Test
+ public void testEcdsa() throws Exception {
+ testVerification("ecdsa_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp224r1Sha224() throws Exception {
+ testVerification("ecdsa_secp224r1_sha224_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ @Test
+ public void testSecp224r1Sha256() throws Exception {
+ testVerification("ecdsa_secp224r1_sha256_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ @Test
+ public void testSecp224r1Sha512() throws Exception {
+ testVerification("ecdsa_secp224r1_sha512_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ @Test
+ public void testSecp256r1Sha256() throws Exception {
+ testVerification("ecdsa_secp256r1_sha256_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ @Test
+ public void testSecp256r1Sha512() throws Exception {
+ testVerification("ecdsa_secp256r1_sha512_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ @Test
+ public void testSecp384r1Sha384() throws Exception {
+ testVerification("ecdsa_secp384r1_sha384_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ @Test
+ public void testSecp384r1Sha512() throws Exception {
+ testVerification("ecdsa_secp384r1_sha512_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ @Test
+ public void testSecp521r1Sha512() throws Exception {
+ testVerification("ecdsa_secp521r1_sha512_test.json", "ECDSA", Format.ASN, false);
+ }
+
+ // Testing curves that may not be supported by a provider.
+ @Test
+ public void testSecp256k1Sha256() throws Exception {
+ testVerification("ecdsa_secp256k1_sha256_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp256k1Sha512() throws Exception {
+ testVerification("ecdsa_secp256k1_sha512_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @NoPresubmitTest(
+ providers = {ProviderType.OPENJDK},
+ bugs = {"b/117643131"}
+ )
+ @Test
+ public void testBrainpoolP224r1Sha224() throws Exception {
+ testVerification("ecdsa_brainpoolP224r1_sha224_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testBrainpoolP256r1Sha256() throws Exception {
+ testVerification("ecdsa_brainpoolP256r1_sha256_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testBrainpoolP320r1Sha384() throws Exception {
+ testVerification("ecdsa_brainpoolP320r1_sha384_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testBrainpoolP384r1Sha384() throws Exception {
+ testVerification("ecdsa_brainpoolP384r1_sha384_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testBrainpoolP512r1Sha512() throws Exception {
+ testVerification("ecdsa_brainpoolP512r1_sha512_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ // SHA-3 signatures
+ @Test
+ public void testSecp224r1Sha3_224 () throws Exception {
+ testVerification("ecdsa_secp224r1_sha3_224_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp224r1Sha3_256 () throws Exception {
+ testVerification("ecdsa_secp224r1_sha3_256_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp224r1Sha3_512 () throws Exception {
+ testVerification("ecdsa_secp224r1_sha3_512_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp256r1Sha3_256 () throws Exception {
+ testVerification("ecdsa_secp256r1_sha3_256_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp256r1Sha3_512 () throws Exception {
+ testVerification("ecdsa_secp256r1_sha3_512_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp256k1Sha3_256 () throws Exception {
+ testVerification("ecdsa_secp256k1_sha3_256_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp256k1Sha3_512 () throws Exception {
+ testVerification("ecdsa_secp256k1_sha3_512_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp384r1Sha3_384 () throws Exception {
+ testVerification("ecdsa_secp384r1_sha3_384_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp384r1Sha3_512 () throws Exception {
+ testVerification("ecdsa_secp384r1_sha3_512_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ @Test
+ public void testSecp521r1Sha3_512 () throws Exception {
+ testVerification("ecdsa_secp521r1_sha3_512_test.json", "ECDSA", Format.ASN, true);
+ }
+
+ // jdk11 adds P1363 encoded signatures.
+ @Test
+ public void testSecp224r1Sha224inP1363Format() throws Exception {
+ testVerification("ecdsa_secp224r1_sha224_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp224r1Sha256inP1363Format() throws Exception {
+ testVerification("ecdsa_secp224r1_sha256_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp224r1Sha512inP1363Format() throws Exception {
+ testVerification("ecdsa_secp224r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp256r1Sha256inP1363Format() throws Exception {
+ testVerification("ecdsa_secp256r1_sha256_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp256r1Sha512inP1363Format() throws Exception {
+ testVerification("ecdsa_secp256r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp384r1Sha384inP1363Format() throws Exception {
+ testVerification("ecdsa_secp384r1_sha384_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp384r1Sha512inP1363Format() throws Exception {
+ testVerification("ecdsa_secp384r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp521r1Sha512inP1363Format() throws Exception {
+ testVerification("ecdsa_secp521r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp256k1Sha256inP1363Format() throws Exception {
+ testVerification("ecdsa_secp256k1_sha256_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testSecp256k1Sha512inP1363Format() throws Exception {
+ testVerification("ecdsa_secp256k1_sha512_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @NoPresubmitTest(
+ providers = {ProviderType.OPENJDK},
+ bugs = {"b/117643131"}
+ )
+ @Test
+ public void testBrainpoolP224r1Sha224inP1363Format() throws Exception {
+ testVerification("ecdsa_brainpoolP224r1_sha224_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testBrainpoolP256r1Sha256inP1363Format() throws Exception {
+ testVerification("ecdsa_brainpoolP256r1_sha256_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testBrainpoolP320r1Sha384inP1363Format() throws Exception {
+ testVerification("ecdsa_brainpoolP320r1_sha384_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testBrainpoolP384r1Sha384inP1363Format() throws Exception {
+ testVerification("ecdsa_brainpoolP384r1_sha384_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ @Test
+ public void testBrainpoolP512r1Sha512inP1363Format() throws Exception {
+ testVerification("ecdsa_brainpoolP512r1_sha512_p1363_test.json", "ECDSA", Format.P1363, true);
+ }
+
+ // Testing RSA PKCS#1 v1.5 signatures.
+ @Test
+ public void testRsaSigning() throws Exception {
+ testSigning("rsa_sig_gen_misc_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures() throws Exception {
+ testVerification("rsa_signature_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignature2048sha224() throws Exception {
+ testVerification("rsa_signature_2048_sha224_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures2048sha256() throws Exception {
+ testVerification("rsa_signature_2048_sha256_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures2048sha384() throws Exception {
+ testVerification("rsa_signature_2048_sha384_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures2048sha512() throws Exception {
+ testVerification("rsa_signature_2048_sha512_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures3072sha256() throws Exception {
+ testVerification("rsa_signature_3072_sha256_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures3072sha384() throws Exception {
+ testVerification("rsa_signature_3072_sha384_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures3072sha512() throws Exception {
+ testVerification("rsa_signature_3072_sha512_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures4096sha384() throws Exception {
+ testVerification("rsa_signature_4096_sha384_test.json", "RSA", Format.RAW, false);
+ }
+
+ @Test
+ public void testRsaSignatures4096sha512() throws Exception {
+ testVerification("rsa_signature_4096_sha512_test.json", "RSA", Format.RAW, false);
+ }
+
+ // RSA signatures with truncated hashes. Tests may be skipped if the provider
+ // does not support the hash.
+ @Test
+ public void testRsaSignatures2048sha512_224() throws Exception {
+ testVerification("rsa_signature_2048_sha512_224_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures2048sha512_256() throws Exception {
+ testVerification("rsa_signature_2048_sha512_256_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures3072sha512_256() throws Exception {
+ testVerification("rsa_signature_3072_sha512_256_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures4096sha512_256() throws Exception {
+ testVerification("rsa_signature_4096_sha512_256_test.json", "RSA", Format.RAW, true);
+ }
+
+ // RSA signatures with SHA-3. Not every provider supports SHA-3. Hence the tests
+ // may be skipped.
+ @Test
+ public void testRsaSignature2048sha3_224() throws Exception {
+ testVerification("rsa_signature_2048_sha3_224_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures2048sha3_256() throws Exception {
+ testVerification("rsa_signature_2048_sha3_256_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures2048sha3_512() throws Exception {
+ testVerification("rsa_signature_2048_sha3_512_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures3072sha3_256() throws Exception {
+ testVerification("rsa_signature_3072_sha3_256_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures3072sha3_384() throws Exception {
+ testVerification("rsa_signature_3072_sha3_384_test.json", "RSA", Format.RAW, true);
+ }
+
+ @Test
+ public void testRsaSignatures3072sha3_512() throws Exception {
+ testVerification("rsa_signature_3072_sha3_512_test.json", "RSA", Format.RAW, true);
+ }
+
+ // EdDSA
+ @NoPresubmitTest(
+ providers = {ProviderType.BOUNCY_CASTLE},
+ bugs = {"https://github.com/bcgit/bc-java/issues/508"})
+ @Test
+ public void testEd25519Verify() throws Exception {
+ testVerification("eddsa_test.json", "ED25519", Format.RAW, true);
+ }
+
+ @NoPresubmitTest(
+ providers = {ProviderType.BOUNCY_CASTLE},
+ bugs = {"https://github.com/bcgit/bc-java/issues/508"})
+ @Test
+ public void testEd448Verify() throws Exception {
+ testVerification("ed448_test.json", "ED448", Format.RAW, true);
+ }
+
+ // DSA
+ // Two signature encodings for DSA are tested below: ASN encoded signatures
+ // and P1363 encoded signatures.
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa2048Sha224() throws Exception {
+ testVerification("dsa_2048_224_sha224_test.json", "DSA", Format.ASN, true);
+ }
+
+ // NIST allows 2048-bit DSA keys with either a 224-bit q or a 256-bit q.
+ // In both cases the security level is 112-bit.
+ // Jdk generates DSA keys with a 224-bit q (unless specified).
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa2048JdkSha256() throws Exception {
+ testVerification("dsa_2048_224_sha256_test.json", "DSA", Format.ASN, true);
+ }
+
+ // OpenSSL generates DSA keys with a 256-bit q (unless specified).
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa2048Sha256() throws Exception {
+ testVerification("dsa_2048_256_sha256_test.json", "DSA", Format.ASN, true);
+ }
+
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa3072Sha256() throws Exception {
+ testVerification("dsa_3072_256_sha256_test.json", "DSA", Format.ASN, true);
+ }
+
+ // DSA tests using P1363 formated signatures.
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa2048Sha224inP1363Format() throws Exception {
+ testVerification("dsa_2048_224_sha224_p1363_test.json", "DSA", Format.P1363, true);
+ }
+
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa2048JdkSha256inP1363Format() throws Exception {
+ testVerification("dsa_2048_224_sha256_p1363_test.json", "DSA", Format.P1363, true);
+ }
+
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa2048Sha256inP1363Format() throws Exception {
+ testVerification("dsa_2048_256_sha256_p1363_test.json", "DSA", Format.P1363, true);
+ }
+
+ @ExcludedTest(
+ providers = {ProviderType.CONSCRYPT},
+ comment = "Conscrypt does not support DSA.")
+ @Test
+ public void testDsa3072Sha256inP1363Format() throws Exception {
+ testVerification("dsa_3072_256_sha256_p1363_test.json", "DSA", Format.P1363, true);
+ }
+
+}
+
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java
new file mode 100644
index 0000000..34b6115
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/MacTest.java
@@ -0,0 +1,398 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.security.wycheproof.WycheproofRunner.ProviderType;
+import com.google.security.wycheproof.WycheproofRunner.SlowTest;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for MACs.
+ *
+ * <p>TODO(bleichen): The tests are quite incomplete. Some of the missing stuff: More test vectors
+ * with known results are necessary. So far only simple test vectors for long messages are
+ * available.
+ */
+@RunWith(JUnit4.class)
+public class MacTest {
+
+ /**
+ * Computes the maximum of an array with at least one element.
+ *
+ * @param values the values from which the max is computed.
+ * @return the maximum
+ * @throws IllegalArgumentException if values is empty of null.
+ */
+ private static int max(int[] values) {
+ if (values == null || values.length == 0) {
+ throw new IllegalArgumentException("Expecting an array with at least one element");
+ }
+ int result = Integer.MIN_VALUE;
+ for (int value : values) {
+ result = Math.max(result, value);
+ }
+ return result;
+ }
+
+ protected static boolean arrayEquals(byte[] a, byte[] b) {
+ if (a.length != b.length) {
+ return false;
+ }
+ byte res = 0;
+ for (int i = 0; i < a.length; i++) {
+ res |= (byte) (a[i] ^ b[i]);
+ }
+ return res == 0;
+ }
+
+ /**
+ * Tests computing a MAC by computing it multiple times. The test passes all the results are the
+ * same in all cases.
+ *
+ * @param algorithm the name of the MAC (e.g. "HMACSHA1")
+ * @param key the key of the MAC
+ * @param data input data for the MAC. The size of the data must be at least as long as the sum of
+ * all chunkSizes.
+ * @param chunkSizes the sizes of the chunks used in the calls of update
+ */
+ private void testUpdateWithChunks(String algorithm, Key key, byte[] data, int... chunkSizes)
+ throws Exception {
+ Mac mac = Mac.getInstance(algorithm);
+
+ // First evaluation: compute MAC in one piece.
+ int totalLength = 0;
+ for (int chunkSize : chunkSizes) {
+ totalLength += chunkSize;
+ }
+ mac.init(key);
+ mac.update(data, 0, totalLength);
+ byte[] mac1 = mac.doFinal();
+
+ // Second evaluation: using multiple chunks
+ mac.init(key);
+ int start = 0;
+ for (int chunkSize : chunkSizes) {
+ mac.update(data, start, chunkSize);
+ start += chunkSize;
+ }
+ byte[] mac2 = mac.doFinal();
+ if (!arrayEquals(mac1, mac2)) {
+ fail(
+ "Different MACs for same input:"
+ + " computed as one piece:"
+ + TestUtil.bytesToHex(mac1)
+ + " computed with multiple array segments:"
+ + TestUtil.bytesToHex(mac2));
+ }
+ // Third evaluation: using ByteBuffers
+ mac.init(key);
+ start = 0;
+ for (int chunkSize : chunkSizes) {
+ ByteBuffer chunk = ByteBuffer.wrap(data, start, chunkSize);
+ mac.update(chunk);
+ start += chunkSize;
+ }
+ byte[] mac3 = mac.doFinal();
+ if (!arrayEquals(mac1, mac3)) {
+ fail(
+ "Different MACs for same input:"
+ + " computed as one piece:"
+ + TestUtil.bytesToHex(mac1)
+ + " computed with wrapped chunks:"
+ + TestUtil.bytesToHex(mac3));
+ }
+ // Forth evaluation: using ByteBuffer slices.
+ // The effect of using slice() is that the resulting ByteBuffer has
+ // position 0, but possibly an non-zero value for arrayOffset().
+ mac.init(key);
+ start = 0;
+ for (int chunkSize : chunkSizes) {
+ ByteBuffer chunk = ByteBuffer.wrap(data, start, chunkSize).slice();
+ mac.update(chunk);
+ start += chunkSize;
+ }
+ byte[] mac4 = mac.doFinal();
+ if (!arrayEquals(mac1, mac4)) {
+ fail(
+ "Different MACs for same input:"
+ + " computed as one piece:"
+ + TestUtil.bytesToHex(mac1)
+ + " computed with ByteBuffer slices:"
+ + TestUtil.bytesToHex(mac4));
+ }
+ }
+
+ /**
+ * The paper "Finding Bugs in Cryptographic Hash Function Implementations" by Mouha, Raunak, Kuhn,
+ * and Kacker, https://eprint.iacr.org/2017/891.pdf contains an analysis of implementations
+ * submitted to the SHA-3 competition. Many of the implementations contain bugs. The authors
+ * propose some tests for cryptographic libraries. The test here implements a check for
+ * incremental updates with the values proposed in Table 3.
+ */
+ private void testUpdate(String algorithm, Key key) throws Exception {
+ int[] chunkSize1 = {0, 8, 16, 24, 32, 40, 48, 56, 64};
+ int[] chunkSize2 = {0, 8, 16, 24, 32, 40, 48, 56, 64};
+ int[] chunkSize3 = {0, 8, 16, 32, 64, 128, 256, 512, 1024, 2048};
+ int[] chunkSize4 = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 127, 128, 129, 255, 256,
+ 257, 511, 512, 513
+ };
+ int maxSize = max(chunkSize1) + max(chunkSize2) + max(chunkSize3) + max(chunkSize4);
+ byte[] data = new byte[maxSize];
+ SecureRandom rand = new SecureRandom();
+ rand.nextBytes(data);
+ for (int size1 : chunkSize1) {
+ for (int size2 : chunkSize2) {
+ for (int size3 : chunkSize3) {
+ for (int size4 : chunkSize4) {
+ testUpdateWithChunks(algorithm, key, data, size1, size2, size3, size4);
+ }
+ }
+ }
+ }
+ }
+
+ public void testMac(String algorithm, int keySize) throws Exception {
+ try {
+ Mac.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm " + algorithm + " is not supported. Skipping test.");
+ return;
+ }
+ byte[] key = new byte[keySize];
+ SecureRandom rand = new SecureRandom();
+ rand.nextBytes(key);
+ testUpdate(algorithm, new SecretKeySpec(key, algorithm));
+ }
+
+ @Test
+ public void testHmacSha1() throws Exception {
+ testMac("HMACSHA1", 20);
+ }
+
+ @Test
+ public void testHmacSha224() throws Exception {
+ testMac("HMACSHA224", 28);
+ }
+
+ @Test
+ public void testHmacSha256() throws Exception {
+ testMac("HMACSHA256", 32);
+ }
+
+ @Test
+ public void testHmacSha384() throws Exception {
+ testMac("HMACSHA384", 48);
+ }
+
+ @Test
+ public void testHmacSha512() throws Exception {
+ testMac("HMACSHA512", 64);
+ }
+
+ @Test
+ public void testHmacSha3_224() throws Exception {
+ testMac("HMACSHA3-224", 28);
+ }
+
+ @Test
+ public void testHmacSha3_256() throws Exception {
+ testMac("HMACSHA3-256", 32);
+ }
+
+ @Test
+ public void testHmacSha3_384() throws Exception {
+ testMac("HMACSHA3-384", 48);
+ }
+
+ @Test
+ public void testHmacSha3_512() throws Exception {
+ testMac("HMACSHA3-512", 64);
+ }
+
+ /**
+ * Computes the mac of a message repeated multiple times.
+ *
+ * @param algorithm the message digest (e.g. "HMACSHA1")
+ * @param message the bytes to mac
+ * @param repetitions the number of repetitions of the message
+ * @return the digest
+ * @throws GeneralSecurityException if the computation of the mac fails (e.g. because the
+ * algorithm is unknown).
+ */
+ public byte[] macRepeatedMessage(String algorithm, Key key, byte[] message, long repetitions)
+ throws Exception {
+ Mac mac = Mac.getInstance(algorithm);
+ mac.init(key);
+ // If the message is short then it is more efficient to collect multiple copies
+ // of the message in one chunk and call update with the larger chunk.
+ final int maxChunkSize = 1 << 16;
+ if (message.length != 0 && 2 * message.length < maxChunkSize) {
+ int repetitionsPerChunk = maxChunkSize / message.length;
+ byte[] chunk = new byte[message.length * repetitionsPerChunk];
+ for (int i = 0; i < repetitionsPerChunk; i++) {
+ System.arraycopy(message, 0, chunk, i * message.length, message.length);
+ }
+ while (repetitions >= repetitionsPerChunk) {
+ mac.update(chunk);
+ repetitions -= repetitionsPerChunk;
+ }
+ }
+
+ for (int i = 0; i < repetitions; i++) {
+ mac.update(message);
+ }
+ return mac.doFinal();
+ }
+
+ /**
+ * A test for hashing long messages.
+ *
+ * <p>Java does not allow strings or arrays of size 2^31 or longer. However, it is still possible
+ * to compute a MAC of a long message by repeatedly calling Mac.update(). To compute correct MACs
+ * the total message length must be known. This length can be bigger than 2^32 bytes.
+ *
+ * <p>Reference: http://www-01.ibm.com/support/docview.wss?uid=swg1PK62549 IBMJCE SHA-1
+ * IMPLEMENTATION RETURNS INCORRECT HASH FOR LARGE SETS OF DATA
+ */
+ private void testLongMac(
+ String algorithm, String keyhex, String message, long repetitions, String expected)
+ throws Exception {
+
+ Key key = new SecretKeySpec(TestUtil.hexToBytes(keyhex), algorithm);
+ byte[] bytes = message.getBytes(UTF_8);
+ byte[] mac = null;
+ try {
+ mac = macRepeatedMessage(algorithm, key, bytes, repetitions);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Algorithm " + algorithm + " is not supported. Skipping test.");
+ return;
+ }
+ String hexmac = TestUtil.bytesToHex(mac);
+ assertEquals(expected, hexmac);
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha1() throws Exception {
+ testLongMac(
+ "HMACSHA1",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 2147483647L,
+ "703925f6dceb9c602969ad39bba9b1eb49472071");
+ testLongMac(
+ "HMACSHA1",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 5000000000L,
+ "d7f4c387f2237ea119fcc27cd7520fc5132b6230");
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha256() throws Exception {
+ testLongMac(
+ "HMACSHA256",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 2147483647L,
+ "84f213c9bb5b329d547bc31dabed41939754b1af7482365ec74380c45f6ea0a7");
+ testLongMac(
+ "HMACSHA256",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "a",
+ 5000000000L,
+ "59a75754df7093fa4339aa618b64b104f153a5b42cc85394fdb8735b13ea684a");
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha384() throws Exception {
+ testLongMac(
+ "HMACSHA384",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f",
+ "a",
+ 2147483647L,
+ "aea987905f64791691b3fdea06f8e4125f396ebb73f37894e961b1a7522a55da"
+ + "ecd856a70c92c6646e6f8c3fcb935528");
+ testLongMac(
+ "HMACSHA384",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f",
+ "a",
+ 5000000000L,
+ "88485c9c5714d43a99dacbc861988c7ea39c02d82104bf93e55ec1b8a24fe15a"
+ + "a477e6a84d159d8b7a3daaa89c4f2372");
+ }
+
+ @SlowTest(
+ providers = {
+ ProviderType.OPENJDK,
+ ProviderType.BOUNCY_CASTLE,
+ ProviderType.SPONGY_CASTLE,
+ ProviderType.CONSCRYPT
+ })
+ @Test
+ public void testLongMacSha512() throws Exception {
+ testLongMac(
+ "HMACSHA512",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
+ "a",
+ 2147483647L,
+ "fc68fbc294951c691e5bc085c3af026099f39a57230b242aaf1fc5ca691e05da"
+ + "d1a5de7d4f30e1c958c6a2cee6159218dab683187e6d56bab824a3adefde9102");
+ testLongMac(
+ "HMACSHA512",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
+ "a",
+ 5000000000L,
+ "31b1d721b958203bff7d7ddf50d48b17fc760a80a99a7f23ec966ce3bbefff29"
+ + "0d176eebbb6a440960024be0726c94960bbf75816548a7fd4552c7baba4585ee");
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java
new file mode 100644
index 0000000..5f82420
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaEncryptionTest.java
@@ -0,0 +1,225 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * RSA encryption tests
+ *
+ * @author bleichen@google.com (Daniel Bleichenbacher)
+ */
+@RunWith(JUnit4.class)
+public class RsaEncryptionTest {
+
+ /**
+ * Providers that implement RSA with PKCS1Padding but not OAEP are outdated and should be avoided
+ * even if RSA is currently not used in a project. Such providers promote using an insecure
+ * cipher. There is a great danger that PKCS1Padding is used as a temporary workaround, but later
+ * stays in the project for much longer than necessary.
+ */
+ @Test
+ public void testOutdatedProvider() throws Exception {
+ try {
+ Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ try {
+ Cipher.getInstance("RSA/ECB/OAEPWITHSHA-1ANDMGF1PADDING");
+ } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) {
+ fail("Provider " + c.getProvider().getName() + " is outdated and should not be used.");
+ }
+ } catch (NoSuchPaddingException | NoSuchAlgorithmException ex) {
+ System.out.println("RSA/ECB/PKCS1Padding is not implemented");
+ }
+ }
+
+ /**
+ * Get a PublicKey from a JsonObject.
+ *
+ * <p>object contains the key in multiple formats: "key" : elements of the public key "keyDer":
+ * the key in ASN encoding encoded hexadecimal "keyPem": the key in Pem format encoded hexadecimal
+ * The test can use the format that is most convenient.
+ */
+ // This is a false positive, since errorprone cannot track values passed into a method.
+ @SuppressWarnings("InsecureCryptoUsage")
+ protected static PrivateKey getPrivateKey(JsonObject object) throws Exception {
+ KeyFactory kf;
+ kf = KeyFactory.getInstance("RSA");
+ byte[] encoded = TestUtil.hexToBytes(object.get("privateKeyPkcs8").getAsString());
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+ return kf.generatePrivate(keySpec);
+ }
+
+ /** Convenience method to get a byte array from a JsonObject */
+ protected static byte[] getBytes(JsonObject object, String name) throws Exception {
+ return JsonUtil.asByteArray(object.get(name));
+ }
+
+ /**
+ * Tries decrypting RSA-PKCS #1 v 1.5 encrypted ciphertext.
+ * RSA-PKCS #1 v 1.5 is susceptible to chosen ciphertext attacks. The seriousness of the
+ * attack depends on how much information is leaked when decrypting an invalid ciphertext.
+ * The test vectors with invalid padding contain a flag "InvalidPkcs1Padding".
+ * The test below expects that all test vectors with this flag throw an indistinguishable
+ * exception.
+ *
+ * <p><b>References:</b>
+ *
+ * <ul>
+ * <li>Bleichenbacher, "Chosen ciphertext attacks against protocols based on the RSA encryption
+ * standard PKCS# 1" Crypto 98
+ * <li>Manger, "A chosen ciphertext attack on RSA optimal asymmetric encryption padding (OAEP)
+ * as standardized in PKCS# 1 v2.0", Crypto 2001 This paper shows that OAEP is susceptible
+ * to a chosen ciphertext attack if error messages distinguish between different failure
+ * condidtions.
+ * <li>Bardou, Focardi, Kawamoto, Simionato, Steel, Tsay "Efficient Padding Oracle Attacks on
+ * Cryptographic Hardware", Crypto 2012 The paper shows that small differences on what
+ * information an attacker receives can make a big difference on the number of chosen
+ * message necessary for an attack.
+ * <li>Smart, "Errors matter: Breaking RSA-based PIN encryption with thirty ciphertext validity
+ * queries" RSA conference, 2010 This paper shows that padding oracle attacks can be
+ * successful with even a small number of queries.
+ * </ul>
+ *
+ * <p><b>Some recent bugs:</b> CVE-2012-5081: Java JSSE provider leaked information through
+ * exceptions and timing. Both the PKCS #1 padding and the OAEP padding were broken:
+ * http://www-brs.ub.ruhr-uni-bochum.de/netahtml/HSS/Diss/MeyerChristopher/diss.pdf
+ *
+ * <p><b>What this test does not (yet) cover:</b>
+ *
+ * <ul>
+ * <li>A previous version of one of the provider leaked the block type. (when was this fixed?)
+ * <li>Some attacks require a large number of ciphertexts to be detected if random ciphertexts
+ * are used. Such problems require specifically crafted ciphertexts to run in a unit test.
+ * E.g. "Attacking RSA-based Sessions in SSL/TLS" by V. Klima, O. Pokorny, and T. Rosa:
+ * https://eprint.iacr.org/2003/052/
+ * <li>Timing leakages because of differences in parsing the padding (e.g. CVE-2015-7827) Such
+ * differences are too small to be reliably detectable in unit tests.
+ * </ul>
+ */
+ @SuppressWarnings("InsecureCryptoUsage")
+ public void testDecryption(String filename) throws Exception {
+ final String expectedSchema = "rsaes_pkcs1_decrypt_schema.json";
+ JsonObject test = JsonUtil.getTestVectors(filename);
+ String schema = test.get("schema").getAsString();
+ if (!schema.equals(expectedSchema)) {
+ System.out.println(
+ "Expecting test vectors with schema "
+ + expectedSchema
+ + " found vectors with schema "
+ + schema);
+ }
+ // Padding oracle attacks become simpler when the decryption leaks detailed information about
+ // invalid paddings. Hence implementations are expected to not include such information in the
+ // exception thrown in the case of an invalid padding.
+ // Test vectors with an invalid padding have a flag "InvalidPkcs1Padding".
+ // Invalid test vectors without this flag are cases where the error are detected before
+ // the ciphertext is decrypted, e.g. if the size of the ciphertext is incorrect.
+ final String invalidPkcs1Padding = "InvalidPkcs1Padding";
+ Set<String> exceptions = new TreeSet<String>();
+
+ int errors = 0;
+ Cipher decrypter = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ PrivateKey key = getPrivateKey(group);
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ JsonObject testcase = t.getAsJsonObject();
+ int tcid = testcase.get("tcId").getAsInt();
+ String messageHex = TestUtil.bytesToHex(getBytes(testcase, "msg"));
+ byte[] ciphertext = getBytes(testcase, "ct");
+ String ciphertextHex = TestUtil.bytesToHex(ciphertext);
+ String result = testcase.get("result").getAsString();
+ decrypter.init(Cipher.DECRYPT_MODE, key);
+ byte[] decrypted = null;
+ String exception = "";
+ try {
+ decrypted = decrypter.doFinal(ciphertext);
+ } catch (Exception ex) {
+ // TODO(bleichen): The exception thrown should always be
+ // a GeneralSecurityException.
+ // However, BouncyCastle throws some non-conforming exceptions.
+ // For the moment we do not count this as a problem to avoid that
+ // more serious bugs remain hidden. In particular, the test expects
+ // that all ciphertexts with an invalid padding throw the same
+ // indistinguishable exception.
+ decrypted = null;
+ exception = ex.toString();
+ for (JsonElement flag : testcase.getAsJsonArray("flags")) {
+ if (flag.getAsString().equals(invalidPkcs1Padding)) {
+ exceptions.add(exception);
+ break;
+ }
+ }
+ }
+ if (decrypted == null && result.equals("valid")) {
+ System.out.printf(
+ "Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s cause:%s\n",
+ filename, tcid, ciphertextHex, exception);
+ errors++;
+ } else if (decrypted != null) {
+ String decryptedHex = TestUtil.bytesToHex(decrypted);
+ if (result.equals("invalid")) {
+ System.out.printf(
+ "Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n",
+ filename, tcid, messageHex, decryptedHex);
+ errors++;
+ } else if (!decryptedHex.equals(messageHex)) {
+ System.out.printf(
+ "Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n",
+ filename, tcid, messageHex, decryptedHex);
+ errors++;
+ }
+ }
+ }
+ }
+ if (exceptions.size() != 1) {
+ System.out.println("Exceptions for RSA/ECB/PKCS1Padding");
+ for (String s : exceptions) {
+ System.out.println(s);
+ }
+ fail("Exceptions leak information about the padding");
+ }
+ assertEquals(0, errors);
+ }
+
+ @Test
+ public void testDecryption2048() throws Exception {
+ testDecryption("rsa_pkcs1_2048_test.json");
+ }
+
+ @Test
+ public void testDecryption3072() throws Exception {
+ testDecryption("rsa_pkcs1_3072_test.json");
+ }
+
+ @Test
+ public void testDecryption4096() throws Exception {
+ testDecryption("rsa_pkcs1_4096_test.json");
+ }
+}
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java
new file mode 100644
index 0000000..201dfbc
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaOaepTest.java
@@ -0,0 +1,390 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import javax.crypto.Cipher;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Checks implementations of RSA-OAEP.
+ */
+@RunWith(JUnit4.class)
+public class RsaOaepTest {
+
+ /**
+ * A list of algorithm names for RSA-OAEP.
+ *
+ * The standard algorithm names for RSA-OAEP are defined in
+ * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
+ */
+ static String[] OaepAlgorithmNames = {
+ "RSA/None/OAEPPadding",
+ "RSA/None/OAEPwithSHA-1andMGF1Padding",
+ "RSA/None/OAEPwithSHA-224andMGF1Padding",
+ "RSA/None/OAEPwithSHA-256andMGF1Padding",
+ "RSA/None/OAEPwithSHA-384andMGF1Padding",
+ "RSA/None/OAEPwithSHA-512andMGF1Padding",
+ };
+
+ protected static void printParameters(AlgorithmParameterSpec params) {
+ if (params instanceof OAEPParameterSpec) {
+ OAEPParameterSpec oaepParams = (OAEPParameterSpec) params;
+ System.out.println("OAEPParameterSpec");
+ System.out.println("digestAlgorithm:" + oaepParams.getDigestAlgorithm());
+ System.out.println("mgfAlgorithm:" + oaepParams.getMGFAlgorithm());
+ printParameters(oaepParams.getMGFParameters());
+ } else if (params instanceof MGF1ParameterSpec) {
+ MGF1ParameterSpec mgf1Params = (MGF1ParameterSpec) params;
+ System.out.println("MGF1ParameterSpec");
+ System.out.println("digestAlgorithm:" + mgf1Params.getDigestAlgorithm());
+ } else {
+ System.out.println(params.toString());
+ }
+ }
+
+ /**
+ * This is not a real test. The JCE algorithm names only specify one hash algorithm. But OAEP
+ * uses two hases. One hash algorithm is used to hash the labels. The other hash algorithm is
+ * used for the mask generation function.
+ *
+ * <p>Different provider use different default values for the hash function that is not specified
+ * in the algorithm name. Jdk uses mgfsha1 as default. BouncyCastle and Conscrypt use the same
+ * hash for labels and mgf. Every provider allows to specify all the parameters using
+ * an OAEPParameterSpec instance.
+ *
+ * <p>This test simply tries a number of algorithm names for RSA-OAEP and prints the OAEP
+ * parameters for the case where no OAEPParameterSpec is used.
+ */
+ // TODO(bleichen): jdk11 will also add parameters to the RSA keys. This will need more tests.
+ @Test
+ public void testDefaults() throws Exception {
+ String pubKey =
+ "30820122300d06092a864886f70d01010105000382010f003082010a02820101"
+ + "00bdf90898577911c71c4d9520c5f75108548e8dfd389afdbf9c997769b8594e"
+ + "7dc51c6a1b88d1670ec4bb03fa550ba6a13d02c430bfe88ae4e2075163017f4d"
+ + "8926ce2e46e068e88962f38112fc2dbd033e84e648d4a816c0f5bd89cadba0b4"
+ + "d6cac01832103061cbb704ebacd895def6cff9d988c5395f2169a6807207333d"
+ + "569150d7f569f7ebf4718ddbfa2cdbde4d82a9d5d8caeb467f71bfc0099b0625"
+ + "a59d2bad12e3ff48f2fd50867b89f5f876ce6c126ced25f28b1996ee21142235"
+ + "fb3aef9fe58d9e4ef6e4922711a3bbcd8adcfe868481fd1aa9c13e5c658f5172"
+ + "617204314665092b4d8dca1b05dc7f4ecd7578b61edeb949275be8751a5a1fab"
+ + "c30203010001";
+ KeyFactory kf;
+ kf = KeyFactory.getInstance("RSA");
+ X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(TestUtil.hexToBytes(pubKey));
+ PublicKey key = kf.generatePublic(x509keySpec);
+ for (String oaepName : OaepAlgorithmNames) {
+ try {
+ Cipher c = Cipher.getInstance(oaepName);
+ c.init(Cipher.ENCRYPT_MODE, key);
+ System.out.println("Algorithm " + oaepName + " uses the following defaults");
+ AlgorithmParameters params = c.getParameters();
+ printParameters(params.getParameterSpec(OAEPParameterSpec.class));
+ } catch (NoSuchAlgorithmException ex) {
+ continue;
+ }
+ }
+ }
+
+ /** Convenience mehtod to get a String from a JsonObject */
+ protected static String getString(JsonObject object, String name) throws Exception {
+ return object.get(name).getAsString();
+ }
+
+ /** Convenience method to get a byte array from a JsonObject */
+ protected static byte[] getBytes(JsonObject object, String name) throws Exception {
+ return JsonUtil.asByteArray(object.get(name));
+ }
+
+ /**
+ * Get a PublicKey from a JsonObject.
+ *
+ * <p>object contains the key in multiple formats: "key" : elements of the public key "keyDer":
+ * the key in ASN encoding encoded hexadecimal "keyPem": the key in Pem format encoded hexadecimal
+ * The test can use the format that is most convenient.
+ */
+ // This is a false positive, since errorprone cannot track values passed into a method.
+ @SuppressWarnings("InsecureCryptoUsage")
+ protected static PrivateKey getPrivateKey(JsonObject object) throws Exception {
+ KeyFactory kf;
+ kf = KeyFactory.getInstance("RSA");
+ byte[] encoded = TestUtil.hexToBytes(getString(object, "privateKeyPkcs8"));
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+ return kf.generatePrivate(keySpec);
+ }
+
+ protected static String getOaepAlgorithmName(JsonObject group) throws Exception {
+ String mgf = getString(group, "mgf");
+ String mgfSha = getString(group, "mgfSha");
+ return "RSA/ECB/OAEPwith" + mgfSha + "and" + mgf + "Padding";
+ }
+
+ protected static OAEPParameterSpec getOaepParameters(JsonObject group,
+ JsonObject test) throws Exception {
+ String sha = getString(group, "sha");
+ String mgf = getString(group, "mgf");
+ String mgfSha = getString(group, "mgfSha");
+ PSource p = PSource.PSpecified.DEFAULT;
+ if (test.has("label")) {
+ p = new PSource.PSpecified(getBytes(test, "label"));
+ }
+ return new OAEPParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), p);
+ }
+
+ /**
+ * Tests the signature verification with test vectors in a given JSON file.
+ *
+ * <p> Example format for test vectors
+ * { "algorithm" : "RSA-OAEP",
+ * "schema" : "rsaes_oaep_decrypt_schema.json",
+ * "generatorVersion" : "0.7",
+ * ...
+ * "testGroups" : [
+ * {
+ * "d" : "...",
+ * "e" : "10001",
+ * "n" : "...",
+ * "keysize" : 2048,
+ * "sha" : "SHA-256",
+ * "mgf" : "MGF1",
+ * "mgfSha" : "SHA-256",
+ * "privateKeyPem" : "-----BEGIN RSA PRIVATE KEY-----\n...",
+ * "privateKeyPkcs8" : "...",
+ * "type" : "RSAES",
+ * "tests" : [
+ * {
+ * "tcId" : 1,
+ * "comment" : "",
+ * "msg" : "30313233343030",
+ * "ct" : "...",
+ * "label" : "",
+ * "result" : "valid",
+ * "flags" : [],
+ * },
+ * ...
+ *
+ * @param filename the filename of the test vectors
+ * @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
+ * Most of the tests below are using allowSkippingKeys == false. The reason for doing this
+ * is that providers have distinctive defaults. E.g., no OAEPParameterSpec is given then
+ * BouncyCastle and Conscrypt use the same hash function for hashing the label and for the
+ * mask generation function, while jdk uses MGF1SHA1. This is unfortunate and probably
+ * difficult to fix. Hence, the tests below simply require that providers support each
+ * others default parameters under the assumption that the OAEPParameterSpec is fully
+ * specified.
+ **/
+ public void testOaep(String filename, boolean allowSkippingKeys)
+ throws Exception {
+ JsonObject test = JsonUtil.getTestVectors(filename);
+
+ // Compares the expected and actual JSON schema of the test vector file.
+ // Mismatched JSON schemas will likely lead to a test failure.
+ String generatorVersion = getString(test, "generatorVersion");
+ String expectedSchema = "rsaes_oaep_decrypt_schema.json";
+ String actualSchema = getString(test, "schema");
+ if (!expectedSchema.equals(actualSchema)) {
+ System.out.println(
+ "Expecting test vectors with schema "
+ + expectedSchema
+ + " found vectors with schema "
+ + actualSchema
+ + " generatorVersion:"
+ + generatorVersion);
+ }
+
+ int numTests = test.get("numberOfTests").getAsInt();
+ int cntTests = 0;
+ int errors = 0;
+ int skippedKeys = 0;
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ PrivateKey key;
+ try {
+ key = getPrivateKey(group);
+ } catch (GeneralSecurityException ex) {
+ skippedKeys++;
+ if (!allowSkippingKeys) {
+ System.out.printf("Key generation throws:%s\n", ex.toString());
+ }
+ continue;
+ }
+ String algorithm = getOaepAlgorithmName(group);
+ Cipher decrypter = Cipher.getInstance(algorithm);
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ cntTests++;
+ JsonObject testcase = t.getAsJsonObject();
+ int tcid = testcase.get("tcId").getAsInt();
+ String messageHex = TestUtil.bytesToHex(getBytes(testcase, "msg"));
+ OAEPParameterSpec params = getOaepParameters(group, testcase);
+ byte[] ciphertext = getBytes(testcase, "ct");
+ String ciphertextHex = TestUtil.bytesToHex(ciphertext);
+ String result = getString(testcase, "result");
+ decrypter.init(Cipher.DECRYPT_MODE, key, params);
+ byte[] decrypted = null;
+ try {
+ decrypted = decrypter.doFinal(ciphertext);
+ } catch (GeneralSecurityException ex) {
+ decrypted = null;
+ } catch (Exception ex) {
+ // Other exceptions (i.e. unchecked exceptions) are considered as error
+ // since a third party should never be able to cause such exceptions.
+ System.out.printf("Decryption throws %s. filename:%s tcId:%d ct:%s\n",
+ ex.toString(), filename, tcid, ciphertextHex);
+ decrypted = null;
+ // TODO(bleichen): BouncyCastle throws some non-conforming exceptions.
+ // For the moment we do not count this as a problem to avoid that
+ // more serious bugs remain hidden.
+ // errors++;
+ }
+ if (decrypted == null && result.equals("valid")) {
+ System.out.printf(
+ "Valid ciphertext not decrypted. filename:%s tcId:%d ct:%s\n",
+ filename, tcid, ciphertextHex);
+ errors++;
+ } else if (decrypted != null) {
+ String decryptedHex = TestUtil.bytesToHex(decrypted);
+ if (result.equals("invalid")) {
+ System.out.printf(
+ "Invalid ciphertext decrypted. filename:%s tcId:%d expected:%s decrypted:%s\n",
+ filename, tcid, messageHex, decryptedHex);
+ errors++;
+ } else if (!decryptedHex.equals(messageHex)) {
+ System.out.printf(
+ "Incorrect decryption. filename:%s tcId:%d expected:%s decrypted:%s\n",
+ filename, tcid, messageHex, decryptedHex);
+ errors++;
+ }
+ }
+ }
+ }
+ assertEquals(0, errors);
+ if (skippedKeys > 0) {
+ System.out.println("RSAES-OAEP: file:" + filename + " skipped key:" + skippedKeys);
+ assertTrue(allowSkippingKeys);
+ } else {
+ assertEquals(numTests, cntTests);
+ }
+ }
+
+ @Test
+ public void testRsaOaep2048Sha1Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_2048_sha1_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha224Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_2048_sha224_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha224Mgf1Sha224() throws Exception {
+ testOaep("rsa_oaep_2048_sha224_mgf1sha224_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha256Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_2048_sha256_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha256Mgf1Sha256() throws Exception {
+ testOaep("rsa_oaep_2048_sha256_mgf1sha256_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha384Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_2048_sha384_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha384Mgf1Sha384() throws Exception {
+ testOaep("rsa_oaep_2048_sha384_mgf1sha384_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha512Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_2048_sha512_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep2048Sha512Mgf1Sha512() throws Exception {
+ testOaep("rsa_oaep_2048_sha512_mgf1sha512_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep3072Sha256Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_3072_sha256_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep3072Sha256Mgf1Sha256() throws Exception {
+ testOaep("rsa_oaep_3072_sha256_mgf1sha256_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep3072Sha512Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_3072_sha512_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep3072Sha512Mgf1Sha512() throws Exception {
+ testOaep("rsa_oaep_3072_sha512_mgf1sha512_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep4096Sha256Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_4096_sha256_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep4096Sha256Mgf1Sha256() throws Exception {
+ testOaep("rsa_oaep_4096_sha256_mgf1sha256_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep4096Sha512Mgf1Sha1() throws Exception {
+ testOaep("rsa_oaep_4096_sha512_mgf1sha1_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaep4096Sha512Mgf1Sha512() throws Exception {
+ testOaep("rsa_oaep_4096_sha512_mgf1sha512_test.json", false);
+ }
+
+ @Test
+ public void testRsaOaepMisc() throws Exception {
+ testOaep("rsa_oaep_misc_test.json", false);
+ }
+
+}
+
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java
new file mode 100644
index 0000000..8868c23
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaPssTest.java
@@ -0,0 +1,568 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.security.wycheproof.WycheproofRunner.NoPresubmitTest;
+import com.google.security.wycheproof.WycheproofRunner.ProviderType;
+import java.lang.reflect.Constructor;
+import java.math.BigInteger;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.MGF1ParameterSpec;
+import java.security.spec.PSSParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for RSA-PSS.
+ */
+@RunWith(JUnit4.class)
+public class RsaPssTest {
+
+ /**
+ * Returns an AlgorithmParameterSpec for generating a RSASSA-PSS key,
+ * which include the PSSParameters.
+ * Requires jdk11.
+ *
+ * @param keySizeInBits the size of the modulus in bits.
+ * @param sha the name of the hash function for hashing the input (e.g. "SHA-256")
+ * @param mgf the name of the mask generating function (typically "MGF1")
+ * @param mgfSha the name of the hash function for the mask generating function
+ * (typically the same as sha).
+ * @param saltLength the length of the salt in bytes (typically the digest size of sha,
+ * i.e. 32 for "SHA-256")
+ * @throws NoSuchMethodException if the AlgorithmParameterSpec is not
+ * supported (i.e. this happens before jdk11).
+ */
+ public RSAKeyGenParameterSpec getPssAlgorithmParameters(
+ int keySizeInBits,
+ String sha,
+ String mgf,
+ String mgfSha,
+ int saltLength) throws Exception {
+ BigInteger publicExponent = new BigInteger("65537");
+ PSSParameterSpec params =
+ new PSSParameterSpec(sha, mgf, new MGF1ParameterSpec(mgfSha), saltLength, 1);
+ // Uses reflection to call
+ // public RSAKeyGenParameterSpec(int keysize, BigInteger publicExponent,
+ // AlgorithmParameterSpec keyParams)
+ // because this method is only supported in jdk11. This throws a NoSuchMethodException
+ // for older jdks.
+ Constructor<RSAKeyGenParameterSpec> c =
+ RSAKeyGenParameterSpec.class.getConstructor(
+ int.class, BigInteger.class, AlgorithmParameterSpec.class);
+ return c.newInstance(keySizeInBits, publicExponent, params);
+ }
+
+ /**
+ * Tries encoding and decoding of RSASSA-PSS keys generated with RSASSA-PSS.
+ *
+ * RSASSA-PSS keys contain the PSSParameters, hence their encodings are
+ * somewhat different than plain RSA keys.
+ */
+ @NoPresubmitTest(
+ providers = {ProviderType.OPENJDK},
+ bugs = {"b/120406853"}
+ )
+ @Test
+ public void testEncodeDecodePublic() throws Exception {
+ int keySizeInBits = 2048;
+ PublicKey pub;
+ try {
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSASSA-PSS");
+ keyGen.initialize(keySizeInBits);
+ KeyPair keypair = keyGen.genKeyPair();
+ pub = keypair.getPublic();
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Key generation for RSASSA-PSS is not supported.");
+ return;
+ }
+ byte[] encoded = pub.getEncoded();
+ assertEquals(
+ "The test assumes that the public key is in X.509 format", "X.509", pub.getFormat());
+ System.out.println("Generated RSA-PSS key");
+ System.out.println(TestUtil.bytesToHex(encoded));
+ KeyFactory kf = KeyFactory.getInstance("RSASSA-PSS");
+ X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
+ kf.generatePublic(spec);
+
+ // Tries to generate another pair or keys. This time the generator is given an
+ // RSAKeyGenParameterSpec containing the key size an the PSS parameters.
+ String sha = "SHA-256";
+ String mgf = "MGF1";
+ int saltLength = 20;
+ try {
+ RSAKeyGenParameterSpec params =
+ getPssAlgorithmParameters(keySizeInBits, sha, mgf, sha, saltLength);
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSASSA-PSS");
+ keyGen.initialize(params);
+ KeyPair keypair = keyGen.genKeyPair();
+ pub = keypair.getPublic();
+ } catch (NoSuchAlgorithmException | NoSuchMethodException ex) {
+ System.out.println("Key generation for RSASSA-PSS is not supported.");
+ return;
+ }
+ byte[] encoded2 = pub.getEncoded();
+ System.out.println("Generated RSA-PSS key with PSS parameters");
+ System.out.println(TestUtil.bytesToHex(encoded2));
+ X509EncodedKeySpec spec2 = new X509EncodedKeySpec(encoded2);
+ kf.generatePublic(spec2);
+ }
+
+ /**
+ * Tests the default parameters used for a given algorithm name.
+ *
+ * @param algorithm the algorithm name for an RSA-PSS instance. (e.g. "SHA256WithRSAandMGF1")
+ * @param expectedHash the hash algorithm expected for the given algorithm
+ * @param expectedMgf the mask generation function expected for the given algorithm (e.g. "MGF1")
+ * @param expectedMgfHash the hash algorithm exptected for the mask generation function
+ * @param expectedSaltLength the expected salt length in bytes for the given algorithm
+ * @param expectedTrailerField the expected value for the tailer field (e.g. 1 for 0xbc).
+ */
+ protected void testDefaultForAlgorithm(
+ String algorithm,
+ String expectedHash,
+ String expectedMgf,
+ String expectedMgfHash,
+ int expectedSaltLength,
+ int expectedTrailerField) throws Exception {
+ // An X509 encoded 2048-bit RSA public key.
+ String pubKey =
+ "30820122300d06092a864886f70d01010105000382010f003082010a02820101"
+ + "00bdf90898577911c71c4d9520c5f75108548e8dfd389afdbf9c997769b8594e"
+ + "7dc51c6a1b88d1670ec4bb03fa550ba6a13d02c430bfe88ae4e2075163017f4d"
+ + "8926ce2e46e068e88962f38112fc2dbd033e84e648d4a816c0f5bd89cadba0b4"
+ + "d6cac01832103061cbb704ebacd895def6cff9d988c5395f2169a6807207333d"
+ + "569150d7f569f7ebf4718ddbfa2cdbde4d82a9d5d8caeb467f71bfc0099b0625"
+ + "a59d2bad12e3ff48f2fd50867b89f5f876ce6c126ced25f28b1996ee21142235"
+ + "fb3aef9fe58d9e4ef6e4922711a3bbcd8adcfe868481fd1aa9c13e5c658f5172"
+ + "617204314665092b4d8dca1b05dc7f4ecd7578b61edeb949275be8751a5a1fab"
+ + "c30203010001";
+ KeyFactory kf;
+ kf = KeyFactory.getInstance("RSA");
+ X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(TestUtil.hexToBytes(pubKey));
+ PublicKey key = kf.generatePublic(x509keySpec);
+ Signature verifier;
+ try {
+ verifier = Signature.getInstance(algorithm);
+ verifier.initVerify(key);
+ } catch (NoSuchAlgorithmException ex) {
+ System.out.println("Unsupported algorithm:" + algorithm);
+ return;
+ }
+ AlgorithmParameters params = verifier.getParameters();
+ if (params == null) {
+ // No defaults are specified. This is a good choice since this avoid
+ // incompatible implementations.
+ return;
+ }
+ PSSParameterSpec pssParams = params.getParameterSpec(PSSParameterSpec.class);
+ assertEquals("digestAlgorithm", expectedHash, pssParams.getDigestAlgorithm());
+ assertEquals("mgfAlgorithm", expectedMgf, pssParams.getMGFAlgorithm());
+ assertEquals("saltLength", expectedSaltLength, pssParams.getSaltLength());
+ assertEquals("trailerField", expectedTrailerField, pssParams.getTrailerField());
+ if (expectedMgf.equals("MGF1")) {
+ MGF1ParameterSpec mgf1Params = (MGF1ParameterSpec) pssParams.getMGFParameters();
+ assertEquals("mgf1 digestAlgorithm", expectedMgfHash, mgf1Params.getDigestAlgorithm());
+ }
+ }
+
+ /**
+ * Tests the default values for PSS parameters.
+ *
+ * <p>RSA-PSS has a number of parameters. RFC 8017 specifies the parameters as follows:
+ *
+ * <pre>
+ * RSASSA-PSS-params :: = SEQUENCE {
+ * hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+ * maskGenerationAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+ * saltLength [2] INTEGER DEFAULT 20,
+ * trailerField [3] TrailerField DEFAULT trailerFieldBC
+ * }
+ * </pre>
+ *
+ * <p>The algorithm name for RSA-PSS used in jdk11 is "RSASSA-PSS". Previously, the algorithm
+ * names for RSA-PSS were defined in the section "Signature Algorithms" of
+ * https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
+ * I.e. the proposed standard names had the format <digest>with<encryption>and<mgf>, e.g.,
+ * SHA256withRSAandMGF1. This name only specifies the hashAlgorithm and the mask generation
+ * algorithm, but not the hash used for the mask generation algorithm, the salt length and
+ * the trailerField. The missing parameters can be explicitly specified with and instance
+ * of PSSParameterSpec. The test below checks that distinct providers use the same default values
+ * when no PSSParameterSpec is given.
+ *
+ * <p>In particular, the test expects that the two hash algorithm (for message hashing and mgf)
+ * are the same. It expects that the saltLength is the same as the size of the message digest.
+ * It expects that the default for the trailerField is 1. These expectations are based on
+ * existing implementations. They differ from the ASN defaults in RFC 8017.
+ *
+ * <p>There is no test for defaults for the algorithm name "RSASSA-PSS".
+ * "RSASSA-PSS" does not specify any parameters. Using the default values from RFC 8017
+ * (i.e. SHA-1 for both hashes) leads to potential weaknesses and hence is of course a bad
+ * choice. Other defaults lead to incompatibilities and hence isn't a reasonable choice either.
+ * jdk11 requires that the parameters are always specified. BouncyCastle however uses the SHA-1
+ * default. The behaviour in jdk11 is preferable, since it requires that an implementor chooses
+ * PSSParameters explicitly, and does not default to weak behaviour.
+ */
+ @Test
+ public void testDefaults() throws Exception {
+ testDefaultForAlgorithm("SHA1withRSAandMGF1", "SHA-1", "MGF1", "SHA-1", 20, 1);
+ testDefaultForAlgorithm("SHA224withRSAandMGF1", "SHA-224", "MGF1", "SHA-224", 28, 1);
+ testDefaultForAlgorithm("SHA256withRSAandMGF1", "SHA-256", "MGF1", "SHA-256", 32, 1);
+ testDefaultForAlgorithm("SHA384withRSAandMGF1", "SHA-384", "MGF1", "SHA-384", 48, 1);
+ testDefaultForAlgorithm("SHA512withRSAandMGF1", "SHA-512", "MGF1", "SHA-512", 64, 1);
+ testDefaultForAlgorithm(
+ "SHA512/224withRSAandMGF1", "SHA-512/224", "MGF1", "SHA-512/224", 28, 1);
+ testDefaultForAlgorithm(
+ "SHA512/256withRSAandMGF1", "SHA-512/256", "MGF1", "SHA-512/256", 32, 1);
+ testDefaultForAlgorithm("SHA3-224withRSAandMGF1", "SHA3-224", "MGF1", "SHA3-224", 28, 1);
+ testDefaultForAlgorithm("SHA3-256withRSAandMGF1", "SHA3-256", "MGF1", "SHA3-256", 32, 1);
+ testDefaultForAlgorithm("SHA3-384withRSAandMGF1", "SHA3-384", "MGF1", "SHA3-384", 48, 1);
+ testDefaultForAlgorithm("SHA3-512withRSAandMGF1", "SHA3-512", "MGF1", "SHA3-512", 64, 1);
+ }
+
+ /** Convenience mehtod to get a String from a JsonObject */
+ protected static String getString(JsonObject object, String name) throws Exception {
+ return object.get(name).getAsString();
+ }
+
+ /** Convenience method to get a byte array from a JsonObject */
+ protected static byte[] getBytes(JsonObject object, String name) throws Exception {
+ return JsonUtil.asByteArray(object.get(name));
+ }
+
+ /**
+ * Returns the algorithm name for the RSA-PSS signature scheme.
+ * Oracle previously specified that algorithm names for RSA-PSS are strings like
+ * "SHA256WITHRSAandMGF1".
+ * See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
+ * These algorithm names fail to specify the hash function for the MGF. A cleaner solution
+ * in jdk11 is to use the algorithm name "RSASSA-PSS" and specify the parameters separately.
+ * This function simply attempts to return an algorithm name that works.
+ *
+ * @param group A json dictionary containing a field "sha" with message digest (e.g. "SHA-256")
+ * and the a field "mgf" for the mask generation function (e.g. "MGF1").
+ * @return the algorithm name
+ */
+ protected static String getAlgorithmName(JsonObject group) throws Exception {
+ try {
+ Signature.getInstance("RSASSA-PSS");
+ return "RSASSA-PSS";
+ } catch (NoSuchAlgorithmException ex) {
+ // RSASSA-PSS is not known. Try the other option.
+ }
+ String md = getString(group, "sha");
+ String mgf = getString(group, "mgf");
+ if (md.equals("SHA-1")) {
+ md = "SHA1";
+ } else if (md.equals("SHA-224")) {
+ md = "SHA224";
+ } else if (md.equals("SHA-256")) {
+ md = "SHA256";
+ } else if (md.equals("SHA-384")) {
+ md = "SHA384";
+ } else if (md.equals("SHA-512")) {
+ md = "SHA512";
+ } else if (md.equals("SHA-512/224")) {
+ md = "SHA512/224";
+ } else if (md.equals("SHA-512/256")) {
+ md = "SHA512/256";
+ }
+ return md + "WITHRSAand" + mgf;
+ }
+
+ /**
+ * Get a PublicKey from a JsonObject.
+ *
+ * <p>object contains the key in multiple formats: "key" : elements of the public key "keyDer":
+ * the key in ASN encoding encoded hexadecimal "keyPem": the key in Pem format encoded hexadecimal
+ * The test can use the format that is most convenient.
+ */
+ protected static PublicKey getPublicKey(JsonObject object, boolean pssParamsIncluded)
+ throws Exception {
+ KeyFactory kf;
+ if (pssParamsIncluded) {
+ kf = KeyFactory.getInstance("RSASSA-PSS");
+ } else {
+ kf = KeyFactory.getInstance("RSA");
+ }
+ byte[] encoded = TestUtil.hexToBytes(getString(object, "keyDer"));
+ X509EncodedKeySpec x509keySpec = new X509EncodedKeySpec(encoded);
+ return kf.generatePublic(x509keySpec);
+ }
+
+ protected static PSSParameterSpec getPSSParams(JsonObject group) throws Exception {
+ String mgf = getString(group, "mgf");
+ String mgfSha = getString(group, "mgfSha");
+ int saltLen = group.get("sLen").getAsInt();
+ return new PSSParameterSpec(mgfSha, mgf, new MGF1ParameterSpec(mgfSha), saltLen, 1);
+ }
+
+ /**
+ * Tests the signature verification with test vectors in a given JSON file.
+ *
+ * <p> Example format for test vectors
+ * {
+ * "algorithm" : "RSASSA-PSS",
+ * "generatorVersion" : "0.4.12",
+ * "numberOfTests" : 37,
+ * "header" : [],
+ * "testGroups" : [
+ * {
+ * "e" : "10001",
+ * "keyAsn" : "3082010a02820101...",
+ * "keyDer" : "30820122300d0609...",
+ * "keyPem" : "-----BEGIN PUBLIC KEY-----\n...",
+ * "keysize" : 2048,
+ * "mgf" : "MGF1",
+ * "mgfSha" : "SHA-256",
+ * "n" : "0a2b451a07d0aa5f...",
+ * "saltLen" : 20,
+ * "sha" : "SHA-256",
+ * "type" : "RSASigVer",
+ * "tests" : [
+ * {
+ * "tcId" : 1,
+ * "comment" : "",
+ * "msg" : "313133343030",
+ * "sig" : "577dfef111ae9a39..."
+ * "result" : "valid",
+ * "flags" : []
+ * },
+ * ...
+ *
+ * @param filename the filename of the test vectors
+ * @param allowSkippingKeys if true then keys that cannot be constructed will not fail the test.
+ * This is for example used for files with test vectors that use elliptic curves that are not
+ * commonly supported.
+ * @param paramsIncluded if true then the enoding of the public key contains the PSS parameters.
+ * The algorithm parameters of PSS are defined in appendix A.2 of RFC 8017. One option is not
+ * to include the parameters and use { OID rsaEncryption PARAMETERS NULL } for the algorithm
+ * identifier. Another option is to include the parameters by using
+ * { OID id-RSASSA-PSS PARAMETERS RSASSA-PSS-params } as algorithm identifier.
+ * The second option requires that an RSAKey contains an AlgorithmParameterSpec. The
+ * AlgorithmParameterSpec is a recent addition made in jdk11. Hence many providers are
+ * currently not supporting this.
+ **/
+ public void testRsaPss(String filename, boolean allowSkippingKeys, boolean paramsIncluded)
+ throws Exception {
+ // Testing with old test vectors may be a reason for a test failure.
+ // Generally mismatched version numbers are of little or no concern, since
+ // the test vector version change much more frequently than the format.
+ //
+ // Version numbers have the format major.minor[status].
+ // Versions before 1.0 are experimental and use formats that are expected to change.
+ // Versions after 1.0 change the major number if the format changes and change
+ // the minor number if only the test vectors (but not the format) changes.
+ // Versions meant for distribution have no status.
+ final String expectedVersion = "0.6";
+ JsonObject test = JsonUtil.getTestVectors(filename);
+ String generatorVersion = getString(test, "generatorVersion");
+ if (!generatorVersion.equals(expectedVersion)) {
+ System.out.println(
+ "Expecting test vectors with version "
+ + expectedVersion
+ + " found vectors with version "
+ + generatorVersion);
+ }
+ int numTests = test.get("numberOfTests").getAsInt();
+ int cntTests = 0;
+ int errors = 0;
+ int skippedKeys = 0;
+ int verifiedTests = 0;
+ Set<String> skippedAlgorithms = new HashSet<String>();
+ for (JsonElement g : test.getAsJsonArray("testGroups")) {
+ JsonObject group = g.getAsJsonObject();
+ String algorithm = getAlgorithmName(group);
+ PublicKey key = null;
+ Signature verifier = null;
+ try {
+ key = getPublicKey(group, paramsIncluded);
+ verifier = Signature.getInstance(algorithm);
+ if (!paramsIncluded) {
+ PSSParameterSpec pssParams = getPSSParams(group);
+ verifier.setParameter(pssParams);
+ }
+ } catch (GeneralSecurityException ex) {
+ if (allowSkippingKeys) {
+ skippedKeys++;
+ skippedAlgorithms.add(algorithm);
+ } else {
+ System.out.println("Failed to generate verifier for " + algorithm + ex);
+ errors++;
+ }
+ continue;
+ }
+ for (JsonElement t : group.getAsJsonArray("tests")) {
+ cntTests++;
+ JsonObject testcase = t.getAsJsonObject();
+ byte[] message = getBytes(testcase, "msg");
+ byte[] signature = getBytes(testcase, "sig");
+ int tcid = testcase.get("tcId").getAsInt();
+ String sig = TestUtil.bytesToHex(signature);
+ String result = getString(testcase, "result");
+ verifier.initVerify(key);
+ verifier.update(message);
+ boolean verified = false;
+ Exception reason = null;
+ try {
+ verified = verifier.verify(signature);
+ } catch (SignatureException ex) {
+ // verify can throw SignatureExceptions if the signature is malformed.
+ // We don't flag these cases and simply consider the signature as invalid.
+ verified = false;
+ reason = ex;
+ } catch (Exception ex) {
+ // Other exceptions (i.e. unchecked exceptions) are considered as error
+ // since a third party should never be able to cause such exceptions.
+ System.out.println(
+ "Signature verification throws "
+ + ex.toString()
+ + " "
+ + filename
+ + " tcId:"
+ + tcid
+ + " sig:"
+ + sig);
+ verified = false;
+ errors++;
+ }
+ if (!verified && result.equals("valid")) {
+ String comment = "";
+ if (reason != null) {
+ comment = " exception:" + reason;
+ }
+ System.out.println(
+ "Valid signature not verified. "
+ + filename
+ + " tcId:"
+ + tcid
+ + " sig:"
+ + sig
+ + comment);
+ errors++;
+ } else if (verified && result.equals("invalid")) {
+ System.out.println(
+ "Invalid signature verified. "
+ + filename
+ + " tcId:"
+ + tcid
+ + " sig:"
+ + sig);
+ errors++;
+ } else if (verified) {
+ verifiedTests++;
+ }
+ }
+ }
+
+ // Prints some information if tests were skipped. This avoids giving
+ // the impression that algorithms are supported.
+ if (skippedKeys > 0 || verifiedTests == 0) {
+ System.out.println(
+ "File:"
+ + filename
+ + " number of skipped keys:"
+ + skippedKeys
+ + " verified signatures:"
+ + verifiedTests);
+ for (String s : skippedAlgorithms) {
+ System.out.println("Skipped algorithms " + s);
+ }
+ }
+
+ assertEquals(0, errors);
+ if (skippedKeys == 0) {
+ assertEquals(numTests, cntTests);
+ } else {
+ assertTrue(allowSkippingKeys);
+ }
+ }
+
+ @Test
+ public void testRsaPss2048Sha256() throws Exception {
+ testRsaPss("rsa_pss_2048_sha256_mgf1_32_test.json", true, false);
+ }
+
+ @NoPresubmitTest(
+ providers = {ProviderType.BOUNCY_CASTLE},
+ bugs = {"b/111634359"}
+ )
+ @Test
+ public void testRsaPss3072Sha256() throws Exception {
+ testRsaPss("rsa_pss_3072_sha256_mgf1_32_test.json", true, false);
+ }
+
+ @Test
+ public void testRsaPss4096Sha256() throws Exception {
+ testRsaPss("rsa_pss_4096_sha256_mgf1_32_test.json", true, false);
+ }
+
+ @Test
+ public void testRsaPss4096Sha512() throws Exception {
+ testRsaPss("rsa_pss_4096_sha512_mgf1_32_test.json", true, false);
+ }
+
+ @Test
+ public void testRsaPss2048Sha256NoSalt() throws Exception {
+ testRsaPss("rsa_pss_2048_sha256_mgf1_0_test.json", true, false);
+ }
+
+ @Test
+ public void testRsaPss2048Sha512_224() throws Exception {
+ testRsaPss("rsa_pss_2048_sha512_256_mgf1_28_test.json", true, false);
+ }
+
+ @Test
+ public void testRsaPss2048Sha512_256() throws Exception {
+ testRsaPss("rsa_pss_2048_sha512_256_mgf1_32_test.json", true, false);
+ }
+
+ // BouncyCastle and Conscrypt do not support RSA-PSS Parameters in the
+ // encoding of the key. jdk11 should support this, but as long as
+ // testEncodeDecodePublic fails it makes no sense to try this test.
+ /*
+ @ExcludedTest(
+ providers = {ProviderType.BOUNCY_CASTLE, ProviderType.CONSCRYPT},
+ comment = "RSA-PSS parameters in RSAKeys is added in jdk11"
+ )
+ @NoPresubmitTest(
+ providers = {ProviderType.OPENJDK},
+ bugs={"jdk can't read the keys"}
+ )
+ @Test
+ public void testRsaPss2048Sha256WithParams() throws Exception {
+ testRsaPss("rsa_pss_2048_sha256_mgf1_32_params_test.json", false, true);
+ }
+ */
+}
+
diff --git a/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java
new file mode 100644
index 0000000..32ea493
--- /dev/null
+++ b/keystore-cts/java/com/google/security/wycheproof/testcases/RsaSignatureTest.java
@@ -0,0 +1,1323 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.security.wycheproof;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.RSAPrivateCrtKeySpec;
+import java.security.spec.RSAPublicKeySpec;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests PKCS #1 v 1.5 signatures */
+// TODO(bleichen):
+// - document stuff
+// - Join other RSA tests
+@RunWith(JUnit4.class)
+public class RsaSignatureTest {
+ static final RSAPublicKeySpec RSA_KEY1 =
+ new RSAPublicKeySpec(
+ new BigInteger(
+ "ab9014dc47d44b6d260fc1fef9ab022042fd9566e9d7b60c54100cb6e1d4edc9"
+ + "8590467d0502c17fce69d00ac5efb40b2cb167d8a44ab93d73c4d0f109fb5a26"
+ + "c2f8823236ff517cf84412e173679cfae42e043b6fec81f9d984b562517e6feb"
+ + "e1f72295dbc3fdfc19d3240aa75515563f31dad83563f3a315acf9a0b351a23f",
+ 16),
+ new BigInteger("65537"));
+ static final String ALGORITHM_KEY1 = "SHA256WithRSA";
+
+ /**
+ * Test signatures for RSA_KEY1 and MESSAGE = "Test". The first signature is valid. All other
+ * signatures are invalid. The signatures were generated by modifying the PKCS #1 padding in
+ * various ways. I.e. while the generation of the false signature did require the private RSA key,
+ * failing the test often is a sign that signatures can be forged. Such forgeries are much too
+ * frequent. The following list is just an incomplete selection of past vulnerabilities:
+ *
+ * <ul>
+ * <li>CVE-2006-4339: OpenSSL before 0.9.7 was vulnerable to signature forgeries. After the
+ * hasty patch OpenSSL still accepted at least 2^800 false 2048-bit signatures for each
+ * valid one. Even though unclear whether this was exploitable it was only fixed around
+ * 2014.
+ * <li>CVE-2006-4340: Mozilla NSS before 3.11.3.
+ * <li>CVE-2006-4790: GnuTLS before version 1.4.4.
+ * <li>CVE-2012-2388: StrongSwan
+ * <li>CVE-2016-1494: Python-RSA before version 3.3 failed to correclty verify RSA signatures.
+ * <li>BouncyCastle was vulnerable at least until version 1.47. The bug was silently fixed
+ * around 2012.
+ * <li>Berserk: http://www.intelsecurity.com/advanced-threat-research/berserk.html
+ * <li>Truncated comparison of hashes e.g.: http://wiibrew.org/wiki/Signing_bug
+ * <li>CVE-2016-5547: OpenJDK8 RSA signature's throws an OutOfMemoryError for some invalid
+ * signatures
+ * </ul>
+ */
+ static final String[] SIGNATURES_KEY1 = {
+ // Message:Test
+ // Digest:sha256
+ // Key size:1024
+
+ // Correct signature
+ // padding:3031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "68ea71ee1911687eb54b3d19cedcfd44719d0b24accccc59bdafd84e4eba48ef"
+ + "0be7f115e7073f9f273286a7dcee3b94cdbe208e30ae496987479d3aa12ab0e1"
+ + "2685ab592d7693a494e6ad27d526ed3ab5912c7f81e09983931794c2165c22fd"
+ + "859e0f9af1a93a4dfe144098c562731e6059d236b52cb865996c87a9baf7f103",
+
+ // long form encoding of length
+ // padding:308131300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "52f46d508e31f030b17c537888585f919037562e15f1924543601a41f9b701ee"
+ + "416ad73d6576b4eaaa64e685289dc478751dfe2d7e588252bfe2d43f4b3a31c6"
+ + "c6c39a9df884a2fc2e45f09c2150a830974b1c9d26090830b37bf06f1d57be1d"
+ + "a34ebb016e9db7ce2c34e94872c89567ff6f2ab35a1a9fb6632e100c7d7af834",
+ // padding:303230810d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "3f34017b3172aaeec72d208308e9b83150699f86634b948847eab56f0169fef5"
+ + "1b5636a96866f4f0f4c649400489e047803a91f2b2f32ab715065e20770c4e27"
+ + "88946b85aca5c90efdd6a9458dd9b6f797f96a3de88d2e4896afe147d8c03899"
+ + "43828100061903a30eaff1dadd98d3e49dba56cdcfa5f215d9c615f974f4a0bc",
+ // padding:3032300e06810960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "1478337676aa47ca72ea7557facff06f6c777f56063f4487d345e43dc56a6bc5"
+ + "f8a891085d53a32c9d1c3cf7f469e7f56847b0b1b9b5b784526078271f21d055"
+ + "0afc40f81e2b8e8dec851d87511cace965edceb83cb96c8d6616e1ee75bb22c5"
+ + "4412fc942a6f71c9fc609a31a69d34b774a97c1ba4f85cca28d9993db8543f75",
+ // padding:3032300e06096086480165030402010581000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "77ba423e600bdd761ed10e7c00698a87fe1322f5f42b2902a0be7a24b1cf44f6"
+ + "13fa55edeb2ded0475f8e1a13e5368f9a2bfc4f2f926ef289a2207bf3689fc1c"
+ + "8ec3e5463064a7f51bbc993966cc4016319b7c95f282372f1ff848d7fca753a8"
+ + "1d905b3341b0fbf60ba186e750f3171cfc84288eff8742bda432bd6c8dc04f9f",
+ // padding:3032300d06096086480165030402010500048120532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "9460ee79bb990bc3fe28cfca92363e6ff6900e3b61b3a402f06024a72b7a65d6"
+ + "2094b4419e93900995eb121327f72b26b139bab3e5e2bd0c82e0cf6357f3b16f"
+ + "1c1dd4407a9a820f20e3baaa2259614d9ee3e015e1c1778befa13aff1e545ea1"
+ + "758cba4713631d63180a91b52df394294441642964a024f45b2251c90e002ec0",
+
+ // length contains leading 0
+ // padding:30820031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "96ac043d3cada45aed0dbdc4662dcf7855553a5effa1077048b51c7e9bfff7c2"
+ + "bb3486ea42894d4b4afb26a3b3bd32cb68d5c4d8ca2622f50d8c56fdc25baf83"
+ + "b9909ecb096419ddc13578dcc8121007f7204ee82c517ae03de70fa23ef23906"
+ + "02029a0cbc8a96c5b781d857dbf12802aa561f5f41ea35aa0babb91b9f891762",
+ // padding:30333082000d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "2a70643572a7cda975d9e2c0827837e60eaa78c297b1ff75b84f654a91fe3329"
+ + "4ccbeda52676ece50fcc03018151e66c24940bd0574ab85a6599231d587f4a6e"
+ + "0ae841cb6696e7dcfd182cb75001304e36887bc4fe3b373828f8b0e62ac2300a"
+ + "626c9e6a2cd05bb7910e74da2978dae1948f855b3b455cd30367160e21581cab",
+ // padding:3033300f0682000960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "27778e39b45dee1e7003f1d315d3466fc111791187ddc056784c158df92097e1"
+ + "23021e11918b6df8d905304db732e83d904bc914271b03def4ee129c3fc8adcc"
+ + "4f81b690e09e70e46c8b920093f304e64ecb7358740e976d28538a9eecf09ec1"
+ + "e1cd47df9107968207b21538cabe076bcc07c3862c46a793fcf638c70a972885",
+ // padding:3033300f0609608648016503040201058200000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "3a879e9f883b158908014f3617cae3315d47afdadd30840494f68d91c04dfe81"
+ + "bd16a40c7d21238cd1816928d989a232a3492325ab0f95d4426e3fb7d58c9908"
+ + "191dc557d8779dabb282287b7860c30e0796283428e0276447235809882ee990"
+ + "deb0f4312c01e7ddf0690406eeacb660acc6957bb670904cfd8d04df5e3ebda2",
+ // padding:3033300d0609608648016503040201050004820020532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "2b82155f363a3b283ae455f59e41c29dec2fbd8c7438b0e347aec5b38c7c895c"
+ + "b7d326870e4fbdb935fcbb561f223bd926dbe8b95ef5eaab27920dbe30c641e9"
+ + "9f526a9bc356af54198b459b59383135a82cd5b6edab7da0b1a51d939b2f9951"
+ + "e1432d637c4f04a3546ed9c890143ae364602b94eabdaa2a45e4bdf0b5bdfa71",
+
+ // wrong length
+ // padding:3032300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "1dda56dc953aeee7fd76ae7166d92ab9e3d1d9759e76f8f1d7634a73cbf69e39"
+ + "d8249153d7c2d83c9664db13552f0c78df34b8a67e7b6c10bcc61b5ead7ba62c"
+ + "e0ec7ba8ac78d146f7e4cadee6f6250e0bc3100660e7afbe3afa17fa288d9754"
+ + "9b4c8cacc00ac5c942673485739f89c9e5e63ad2be97a8f2313f5c5b095e7542",
+ // padding:3030300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "692c143b82196a391a3546607336e6f3bc047412645cf0def0d62d1b42234c14"
+ + "da138bb7f451b45073bbda2aba23412e83bc40d4e7de3e0684f2cad7d059f2d6"
+ + "831aa3d2ece4964ca75cd41dce23c5ba495c15345b36947b4b5a051fe1b84e14"
+ + "8b5ae21f112d2245b1acbaeef9dc4a0c408829b9d2b1b5ab1d3a40af0a27b99e",
+ // padding:3031300e060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "66c31a45b2287425a20f65c3eba9cc58c370882f5fc62921935491fbd516df9b"
+ + "af9b28304a21d9008b61a92779ecfb3b0c03f6d74354f5159956e3fc1d35bd73"
+ + "76289378f05d7a71e05ab32794f2566a54635e8dc64740acbe10a293ceddbebe"
+ + "8499b520f406023a134eb9927ebb788b92488f036d109ec0a40ac52372e847b3",
+ // padding:3031300c060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "7b85536bdcda4ad3fc40129f2ff9dc85d9ec049913784064e735868664044627"
+ + "8a2006d93fb33429407597e5d8c783e3f7aee8a7791d69139f3c802a6547f01b"
+ + "f987415eec2447b0e8c4f3aee7ae2085d141fa34ca6634bc109dede93285d5c4"
+ + "0cfcd98bd47ceb9cc1890dfff53b7ebb8038533580c7a67fe14c0c422e20cd64",
+ // padding:3031300d060a60864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "5d77fba3cbb1905d83aa532fcc3227a95d7931bf0c2ab51f8118824de9dc029b"
+ + "d2470adf48b41c694ec7359d00a1336990c30ee368dd40bd681ba74794415d39"
+ + "97e7a756659397bf6abd44ca91c12a8580a3f5d1cdbc7f3be0c23c72334ce9b1"
+ + "419e6540dab73f5ff8ab57d0bbbe92b688bd3495f9344822b622042c2491bc41",
+ // padding:3031300d060860864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "7b5476fb78f389d1131764e7a13322f86008924c8c098f6d74f2df4dcc5a504c"
+ + "d786b3eaae33295cd1e87a2bbd1a06cb385674d465110a9a990d52de9a67f1c1"
+ + "3ecaaa86383d489423c084fae9ecd2e9b109f4f04b8c013e3409128f3a079c06"
+ + "8c1ad27bc2a20e76ad149325b7b0f0bd804a4e33949a98aac49076260702b0b0",
+ // padding:3031300d060960864801650304020105010420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "6034e1253e4860a29096e392076794cfcea166a30b340cc09f77baa5952c06d1"
+ + "48bd89b750c3112930ef210a50a7d3f6569da89912b5e50e824116e73a155369"
+ + "58f75779506d07e67ec9c0cd8de4b51dfbb0fe56926feed18ffbd83b0cdd50d5"
+ + "6326c54adf97e629378ae5f0f02fcda3da1aa98cb1d1990946edec711a85a0d8",
+ // padding:3031300d060960864801650304020105000421532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "a44cd265e1ecea83fc74e9eef746ef173277cc96f69a1798590ddee7ce5b5c34"
+ + "a82ad58a5c042db19005e04eec4159900ea764c0d008c52b94577d1c438661fb"
+ + "767902d9d1bbd6a90bdc4df685ec5951eac81d8b4dd36bceef7b6f919e85b6c9"
+ + "94c7cf22a804f15cebe63b77f47b3bc2c2aaa68c6362c27a574b849efafe72e9",
+ // padding:3031300d06096086480165030402010500041f532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "a160aa43f4873cada34bea5ccd2be9dce07940ee1c08eaad524a5019993bc753"
+ + "ce92cccada706b483f106ff20b327b35e7c83955ad3bbff3f26ced3489877d1b"
+ + "5bf285d61afcb30219c02a440da61030e301aadb901a525345d1a651a21c31a6"
+ + "2ac9fb71738c3e215a8941ca9a3c4910679c5e774530c28788f6eddd7a31c024",
+
+ // uint32 overflow in length
+ // padding:30850100000031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20
+ // a6ec113d682299550d7a6e0f345e25
+ "1369c78f816a9baf027e255de0c258125be90f35b8daafee87f2ffef2d465e06"
+ + "94af4401cc5cdc7ca78b08d5688ceefbddc02abc5495d47c6829d696f8370ea4"
+ + "27e7e0225eaf22cda720bbb5881edd16b19bbf2ca86654c65b4ad481c13fb38a"
+ + "f00d77922f46b311f936c51f4610f6bdb514b366aa05f029c1e63e3cfcf9763d",
+ // padding:30363085010000000d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20
+ // a6ec113d682299550d7a6e0f345e25
+ "41d4c1ea43cb207af8bfc1552e31da7ca5744b68c4e00c3bf55f4edd4c81e91c"
+ + "01f44fa05290dbaa1fdcdcc775f6032a049b4965345c16aac6994b06cda9e038"
+ + "7dbff96cdb115e014f69bb057faca2f618c70a31edd0beaef7acdcc0fb7c83b2"
+ + "f07a8b9de48aa04b7c973920af5b8dc20aac343251ddf4c2277985c3db1dac2f",
+ // padding:303630120685010000000960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20
+ // a6ec113d682299550d7a6e0f345e25
+ "76bae6c330b9ab33aa9f2abe8559c51fb95f953a75e48053ab99078069214b50"
+ + "9dd1b5080ac6819e32912619372d71a9ff1a67449dd699e5bc6ec0e18d1893df"
+ + "b5bd571d933926d05b0d9fd7036ba4556e209369d1c57ec49cd9075e583c257c"
+ + "6fd4899c2a8bbb157547812cc692f264bf54712c71ee090b974d99b4d1629696",
+ // padding:303630120609608648016503040201058501000000000420532eaabd9574880dbf76b9b8cc00832c20
+ // a6ec113d682299550d7a6e0f345e25
+ "3480a5c22f092f259b5bc4fdb9a33c044c24a645b57d61920effde1dc0bbfe53"
+ + "738023f16025841f9323b40f72c11091941bbdfaf7c2fbf77ad6626dbd6a3b7a"
+ + "bb3ee916d96a922b11c86ce80ee67dec619bb98e9246d35a33b11b3a4e2a3a13"
+ + "0e8b57ed4bcdd4b4e73aec3f9e3d50d3db5e29cffeb186846c72d09468d018ed",
+ // padding:3036300d0609608648016503040201050004850100000020532eaabd9574880dbf76b9b8cc00832c20
+ // a6ec113d682299550d7a6e0f345e25
+ "5b3d3a198d4b36c6d9641db181fff59407a25bf1571f85e47bad1eaf13807987"
+ + "2b93b9eb51aae09b48d6f4ef56badd96a6584277d8f3c6e4a4e11275f72021b5"
+ + "0a1665ddaaa56a2a7caa7da6b4d502c5214e17042811154d411dd2197c250264"
+ + "bb69ba43adf668d4f7b81d932afa55e378214bb19ddeb431f702a91dd11e23bb",
+
+ // uint64 overflow in length
+ // padding:3089010000000000000031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc
+ // 00832c20a6ec113d682299550d7a6e0f345e25
+ "1cc5577d04e34550e7f3d136064547efa30b9413e2c423b5a320eaaaf11cbebb"
+ + "91e13bbe3874e4650e057a8e38c8a366c473f35e0de82b22f846721a09e3f279"
+ + "ebdf54c8df395a9041333f09cb7bed5291bc1842857c4ce6ad5a1c2c476c1efd"
+ + "dd5fe42824c25e0581aa7bb8f621d3b53566637c6266bb1bd0a5b7fb79c72616",
+ // padding:303a308901000000000000000d060960864801650304020105000420532eaabd9574880dbf76b9b8cc
+ // 00832c20a6ec113d682299550d7a6e0f345e25
+ "6e56d1746105344b34fb8299d173f4a5032cbce3556ca9d1eee35f8b31818efc"
+ + "121a1a9599c24fef8531243016dd6288d67b4bf9fdbf2c90fba5b1661be03531"
+ + "b5e15385ea465d1376010f0af761e8fb1afff7823dcef8dc100d97c192e9a7d0"
+ + "3c82321d83fd8ecf67207c65cf182e1104ec5669536070cf1e3fe73c5e27edeb",
+ // padding:303a3016068901000000000000000960864801650304020105000420532eaabd9574880dbf76b9b8cc
+ // 00832c20a6ec113d682299550d7a6e0f345e25
+ "37a413f9202591b8860cd9d68515ab522ae800e9a71793b479f1fb74ab8c9b07"
+ + "e72fe82dabe1189d028b813610e5e57c055af2d32837551fdb0cd93d7669a3c0"
+ + "2a14c460f4c92136a4d11cfb7dcc76401bb5b699fbc64d302736d68c3591ecd5"
+ + "9220107cd63f55c83edd38c4568e6f7749c0d9baebfb7c8ae1bf2179101745a9",
+ // padding:303a3016060960864801650304020105890100000000000000000420532eaabd9574880dbf76b9b8cc
+ // 00832c20a6ec113d682299550d7a6e0f345e25
+ "9fa8aac224bb50697103d457e7fc870853b23670ee5b8c7395d68ed82b30db18"
+ + "ae34a569abdcdf19238ffca8f5e435327dbe605bdc1a6dd3eaa3c2beb33f0064"
+ + "2984a2034bf3b3e8de3ec7009e35069d5b27253c4aadcb4f163148e157252e3b"
+ + "9334abb6cf0299161c12908529f52de9416ec6218af7a6963fcc987c5024ea71",
+ // padding:303a300d060960864801650304020105000489010000000000000020532eaabd9574880dbf76b9b8cc
+ // 00832c20a6ec113d682299550d7a6e0f345e25
+ "0f50bc6b1b94aeb6805dee51c92860693de47c4925ab90b57a46e0485a9afeed"
+ + "45083eade73bee684cd07048e632d1dd24aa2efc42c1f85e4fd7b7058dbeafb5"
+ + "3a3d5b1cb1e7dded3352c3c92ded891839263a501afaa78fedfd04546c43d16f"
+ + "7a52b800abc9ab1ef827ae0eb19d9b52def2435f1477a48dff61800b4db830e4",
+
+ // length = 2**31 - 1
+ // padding:30847fffffff300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "9dcc651cc0a1b4d406112c0d1ebd7a9fb5a2c9d9f9cffbeab2d2821e5ed01efa"
+ + "9d191665794649bd1f588b729e8fba1eaa37a5a736a5863973c338a92b2665d6"
+ + "ead13b72a19d2da778febb94b150e8d750340a3b856fca8b3b6e3cbfecb9c397"
+ + "c23f46912ba546ab0f64ed88404ce317f8fb2278b68950e9712d6b11f5cdfcaa",
+ // padding:303530847fffffff060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "0397d14205c2f52423ef69c874294dc2b37d5be5d5647f7e83f1dd6783cb41cc"
+ + "e52e6de1dc8c9e93ca1ef887d4c0ea79cd8b26391d638bbd8080bce830bf1bd7"
+ + "fb1de31346f28d609874fafd4a34fb7bee900441f55589ec3c5e190106d8816c"
+ + "adfcfb445834739cafaaa3903ed93cedc41a76aa0ce18fb49a3a73b7b5928735",
+ // padding:3035301106847fffffff60864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "2c3ffd881c1c0ce2e4c98282d6011179a89b1e84b17072bcbbb64164e5e05410"
+ + "d0414a1fdbbc04564f3d80f3891f28c3f02e92bf97b4339b5bd4699614e236d4"
+ + "223cef0688c44b297eb9c0e22246b4cb28983b102a446dc76671206c3b77af68"
+ + "97f2f445512abda37bc9c37257dd4f1c6f0e6ec40929eb6b0058682b9d2f6c66",
+ // padding:30353011060960864801650304020105847fffffff0420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "668bd06eafe953fca6a17b0da0f9006ceadb09ad904786b7530148df7eedc146"
+ + "d20a5472c39677d65e59934c00227fb662b3474596e6072f56d2c00c3d31e66f"
+ + "0da85f4670e75c3f2c910c0fec8c98bc31fb2eceff80350b78aec0d316e9bbb3"
+ + "31544d8a3d0b1649291396c717e350bebba3d3c3a0b1d55f010879b8c7b7d4f9",
+ // padding:3035300d0609608648016503040201050004847fffffff532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "87482257ae1d18d0357428b756ae35a48549536a3439ca3c148eee64f4c096d8"
+ + "96219097d55c14a25eb1490779f6b1471aed238cc0d6aaf265c12ac086d04de9"
+ + "b79a37518056dfacc12cb4916c17505fc7e2e6c1e0db720a286ea65bde4d3da1"
+ + "d2dcb8d0276e8ce73f3f923209149955285c602572cfd24c82e8d96d45f569e6",
+
+ // length = 2**32 - 1
+ // padding:3084ffffffff300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "03aadd447f36952dfe73ae89e5c656b7d37ec92535e547cca62a7747f3831f2f"
+ + "613c7dc094f3d5c4c6b9e02b21ed4626930ef3948b42ed41f4cf468d2474acad"
+ + "f1c75599c5619e4872e6d3dfd93abe92234165135ed265e0c0f64fddf23e50c1"
+ + "f9fdcede8778a8ca008ab00f8afa887da3f4699df9f1140953232f36d035b03f",
+ // padding:30353084ffffffff060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "31afd9a0d827755352b16de04de42e98a8c72f08919ed475530a00c762b8a03b"
+ + "de22634dd856a7eede4b4947d780cb3efe55775e16d7f46f209dbcb5569b2d94"
+ + "69cc271aa850f74960f7c741928055925349821e32e1e0fe5a040010a39a4b6a"
+ + "343f7f35c204106b3617e528a99dcaea8a93766adcfe7be31cdb98f7f7f14669",
+ // padding:303530110684ffffffff60864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "16ac0aa2d727ef5fbf0305259ee6fa40827c92419f819673fd64cc2dc2dbfe7c"
+ + "e1cfcf06e26d45f59cb3d9afd30d7a6265863fe856e0a0b1b9508b1e7a2dfb0f"
+ + "87f5ebfc444bbdae504abde7daa33bffb991551940df682c8e2c45edef0563b3"
+ + "4d4f11e1955e83c2145ee321165517d1532abd64dc613a280fc30670bba1f898",
+ // padding:3035301106096086480165030402010584ffffffff0420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "0fe0c75dae62462e66e7277b03c9113727419f7d4db7b2a567c0c189fb6328e1"
+ + "f73d5d44e2196b436f4c2f0f12950d419774c8a51c55f9b2217f904c4f03d5f5"
+ + "754174719dfb85f62795ef75e6d54e703bf231fd8472250f529f85294f29f6c5"
+ + "653ef585079c3b3d8f931da80a46c8afeef37696fb0e7986d413bb1996b8ad57",
+ // padding:3035300d060960864801650304020105000484ffffffff532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "9ef993e6ccf015b0b0de75b51213a1c3efcaf66bf83655287484ef28d9848062"
+ + "26a7af1704fa6a7fc02984b44449f83ae24761021e49ba6117505c1e609406b0"
+ + "02215de27d696643c3354fb48e6c64e7300944edaeb96e4872275f75532f5aab"
+ + "94358d4954522fc7903439e99223d8124e79a3f519050b6b576b77d5abe7c3e3",
+
+ // length = 2**64 - 1
+ // padding:3088ffffffffffffffff300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "97c602416f2131d34f2a57acecf26365a30c12f77e5beac095533848ce227302"
+ + "092c6f44b47f011d6eb0a91f8024d1935d8bb274c42b57875115a94281fd3cb1"
+ + "98f9334758d3200c1c721f6babef332c02a89968a7089f7783993bdd54f809f8"
+ + "372437798d2364040c1faabfb00faabf28cd6ae4ffea29ae2c08a6a7e6074700",
+ // padding:30393088ffffffffffffffff060960864801650304020105000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "2a970dc291a1dc935cca6985dda703bcc1ece2e40817ce8fa79b6e8fe84e1136"
+ + "86e6e65570d46bf22147bcbc389cb5f86f92dc185f556d15e7614cef119fcd73"
+ + "05a31fd2f8710812f35f9f0bd8a1a6e5be3163de644370c67181b7575635dfb9"
+ + "f717f78631d62db714b2a19cea7079ff13c8926ae0c601e4befb6541b02a7e20",
+ // padding:303930150688ffffffffffffffff60864801650304020105000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "6e16d110235cd11e32b114ca9dac0cd6a1b041a6d2c61941d49bb458241281f6"
+ + "2a4e2b1bf3cebc3e67e8c062ec67a51a599a553b09732e23e1d09fb2b20be7fd"
+ + "311a7122414d535651718a1421d4239276c227b96506729a09e3ff2779dd1c79"
+ + "de4d402623039b826e2bb4d26d1b56775fce14ed0203a9ebd8f042d981705a77",
+ // padding:3039301506096086480165030402010588ffffffffffffffff0420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "0716d252488e08f10a25cec94714e6105bd4e13ff019431190864cb0f4378d31"
+ + "5f4bd0fdf186e1f2d45a6e97eb04fb2013273e178ce4f82a0b67bf9d021b1d8a"
+ + "b73d753adf2073ee1ad6190b2163139db63778a3670b7cce23f45efb601bd596"
+ + "44a431cbe534ecdf4c4c58ed02ed03863ee32d296b5736c010305fec655b1a44",
+ // padding:3039300d060960864801650304020105000488ffffffffffffffff532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "03e52a2ed638bfb9184a0ee3698502af3a19bb959a984957de5101e6f7a62ccc"
+ + "c2ec2a6293fa9d76fabf3ce7e4bf35c65a5f864bc003686a1e05b57c5af6ad58"
+ + "8e05a5225479422d7b78c5bedddaec7f4b8c1e9ab7478c1ee253847324e02543"
+ + "4b76a01b82a40123ab31ec9862c6016885dc6cbfe97801503369fd3688bdaaf8",
+
+ // removing sequence
+ // padding:
+ "5df1c4a701c6fc1f2daf6f4538f29c3452667424c05edcbdaba4a1678c8b5bc0"
+ + "e89656a0e48aef46642e0bb597813688904e9d74cbd377a3d9d2c965bd3ed06f"
+ + "136f10367ea3eecf89a97508389448a31ae0e79ed3725d0c4e99a516daa41164"
+ + "79bc53da5d7c2f26c7ec6310d4cb4174bb781405630a9b1c147b0e1da3a7faf9",
+ // padding:30220420532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25
+ "3e43837b92ebe4df08586fced3dce46aeb2fdb6ec2bd0c58e823f6e6363b9b67"
+ + "6786929d13ede60a8d8d0daaf71f0de8880ed0fdac8706eb2f324394145818b6"
+ + "41d1049cc7552bc6273d86e901099c78297381faec5c518fb6de429700f3bbfe"
+ + "f76cdecbb60088b9f2a77d75b8ff86f06cf23850e3183a267c0ea34f4f839015",
+
+ // appending 0's to sequence
+ // padding:3033300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e250000
+ "26d20fecdcf0b7d6a0472754aecbe115c39d580ce9d78b67d1a6395aa6ce6689"
+ + "bf6d0d96545341fbf04956a48c47f7d30bda017acb1d8e24ce596aacd3e05b1a"
+ + "fa571d19f5316142557f765e4c5d080bc5336b79e2c02d8833d076ac9d7794ff"
+ + "be85c66d0db97e1f5bd2ecb46afb15c19a8fe083fa593420e996a483c2a3a766",
+ // padding:3033300f0609608648016503040201050000000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "1163082ba8d48352df7eab96a0067539faff24374a630aa4393461a0aac71660"
+ + "6625d706699dfc22cf3aff89fcc278f83a0adac87aa0bf192dd86a97031515de"
+ + "1933a23849478ebed20e4203abfb47345bc18f38da5d45e829997b10107c5369"
+ + "99b2ce10b2781e1db03e10cc2bdbc2e0ff4c3db5d271ce83c1e7e267e7c1e107",
+
+ // prepending 0's to sequence
+ // padding:30330000300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "0ded592bef1fa809841e0d7365e66af12f4239be0928656e7c49a043b9f2b18b"
+ + "9bd2dfe93a810c6e6c8ae6cb8a5c9d6e9d39a96a10b3bbdb92a7b8f575c2db48"
+ + "41c1b628160f956f54e0c58d3b6fd4d640b0a06d39476daba7be04b63a75f38b"
+ + "bf7517d9751d2b12d2dc00e44de7263275dce6b0c0af65d3c04878d6fc1be2ac",
+ // padding:3033300f0000060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "95a42e5d9bd9ad5a8579444e8167bdecec16116a7900117b298c82d5560f1d16"
+ + "e9fbe963764727fef9111f2465e66177b576bdb8c70a58e3df6ff69edd2d6827"
+ + "c97d626b09c24cc49f223cd5d2db2916c54fd8f2ac7301723449b1823f2ff48c"
+ + "56849f7d608312d4bb7a97f90ba218f99cb773fba0a34909618f5d25854d7687",
+
+ // appending unused 0's
+ // padding:3031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e250000
+ "2344c598a8905b350f20de5cf0cee60253729a54be45b0b19acc109ac15862ef"
+ + "ab2e7c96e92bc990ed6959a40d725c24c25c8d223a46f490905c1448d8dbf7c9"
+ + "c427bc2e896bdce6d2c1daabdc93ce177f9525ac69d899bded12443338834a16"
+ + "d885456057461740c5140cb9a89a017851f9e99e38c1727fe5ccad9a7a8709d6",
+ // padding:3033300d0609608648016503040201050000000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "a08cbe4009080f73cef03116ea949d1dbacce7025f7f61040fb4e052754d5b2d"
+ + "74c2dd06c0dfe1d09b97aa5739c809bec6d8cb27e852e9fef353bfa32964b994"
+ + "95a6dc63d6ce77460ac280c74c0cabdef794f74930f7f8827af1c6690d22ec2d"
+ + "f3af497837bbe900a890e3feeaca2c0d16b0017155390ff0396a35ecb62b5992",
+ // padding:3033300f0609608648016503040201000005000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "500df36bd7d0b56642e2d5dab6e4ec0b148e7b8673cfab40e45c5dad5efc469b"
+ + "3321ce027a3a7ff5689366a18a32267d161a1266491b055f11557c35bd0d4f43"
+ + "df11b8a26f7b13c54be423b87b30b1dca956151c3ec3df03b30918a413179b0e"
+ + "064bf434736b323408e3f1330743c8bdbbb9d466dc1e21710c12e2e3b638b172",
+
+ // appending null value
+ // padding:3033300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e250500
+ "11a382fe570e0cfeb515955b70ec89a9353cda0c5a5d3cfa3e16e41340eccaa1"
+ + "8ba21ad87c4a54a7131c4a7cf9afed68b1c1645568bab9b0fe7dfe0437abbe1f"
+ + "b6cf06bb690f46aa2eca034093ded661c38954341f3f35abe484015150307eca"
+ + "fd06d4309836771dfe29bfe56350d68725e0cd02b1479c6f99eeba2d59f40626",
+ // padding:3033300f0609608648016503040201050005000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "110f3f188df46da58cdd46b5d460ba3d2f8d00d907289634d52a3ce693eb232c"
+ + "d6db738c48c8aa22d923d4f81d55925b3d4ff29ad9869f97a244d37b860cbd46"
+ + "46c6318c041729a7aaf473b61a93cccd62fe223d1be00364f03d722f43c7beff"
+ + "98c3fde573e7e6a0ce7d4a2a4bcf279765e29769bd4f884ce41fb808ac3d541a",
+ // padding:3033300f060b608648016503040201050005000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "470416ee76f0bbdbd2812b533813e4463b799f4036e6955f3e174f6287e3c73d"
+ + "57c32875607e2eaf06d612cc85170ba5df31286edb645ae9ceb9e62064050f3e"
+ + "7f6b36fe8fdae7a3bd89b6acc523c923b9d3f3e5f57d80c9100b39dde75caf46"
+ + "adcae56668149ce0b80762bc459ac598241dd79c6b4fe0220ad53e3c591243fe",
+ // padding:3033300f0609608648016503040201050205000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "100714ee0d38c541c2632e96885a7ce0afcb22f0cbd84c556f19d1b44bce75a8"
+ + "fdf141e975dda1812b4465050d4615a51c3b9816606c7ac88d6b684df938e7a8"
+ + "852835dcf5bf0ee45f2e413290691832095af77eef0e7a86f72167dbb03758e6"
+ + "8561f7f06afc6e902ba19fad57e00cb43c0fb2a5ead689a146c79c9e6188bd85",
+ // padding:3033300d060960864801650304020105000422532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e250500
+ "44eaf5ded57ac5c25c17eb31c2e071400b46b9022641347b2edb0b14efbd4eac"
+ + "5f71e4bfbe791e164c003667387e57ae22c6b00e69971d7245e381f6459e5f88"
+ + "d9dc0fdb385b777fe99e5e4d79aec057e41a1e457fe2b91a5f4a8878d2eaa1c3"
+ + "ad8393d281eca07ebd287364a19045029fa7ed0e62a21e5e42a88a52ea4abc8b",
+
+ // including garbage
+ // padding:303549803031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "236a815c2441d111d254172149ab2429cc4e6caf3335579bf438f22723de0a4a"
+ + "5e532ed71f24c0fc6032c60aebb2b7e76cd0d14f262d1d9bba80a53dbdb12c9b"
+ + "89902fc5f5511125d21b7df32e9b303c4b393fd6add6ac7536901ea8ae5785dc"
+ + "fe90e85ad0c16146b1f15036c31d7758a364fb54cc1d183b8566bda592ba446c",
+ // padding:303525003031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "762d30b302cd76b021e237f28017e48488ff3bb30ff9e92db5b1e76eec2ee91c"
+ + "9af03e1c5038afc22591b1cd8cfae648a33ab77901f9f3736e50eea83f7c7a45"
+ + "46dc55c0265fb17dfdd30250fa3881e34e51b4f2e54554ad098eee952ec888e9"
+ + "11a0ea5df42c0560bcb4bdd718c88d834b534917e555c38fd1ec3593b2f25b39",
+ // padding:30333031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e250004deadbeef
+ "8cbf9d425abef67ff0a7fb648e70b82b1556ac80e46dcff37145b9041bee2bbb"
+ + "fa56817e04994c9cf1123c6df2aeeb1637595eb1e20adef51d657943fd67826a"
+ + "c5d5dfba106ae9cd243f12746917a446ce955034b46ceb0f4d542b7bcd06ad3e"
+ + "6e10899d5338e6d8caf3d4de3cbf45d45a58d946a64d0bc13e97a4ab4e6b6016",
+ // padding:303530114980300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "6477bd3337d601fe92e19e7f6b216f73eaca68aa408c5a570876ad8db6113505"
+ + "43d1dd458b511e3095e57996ca589c00f2beb6b6fe4564f4373571d904958acd"
+ + "1bcd33f57959a231bb126bb2b37bf1403d52836752198b6954567f07b31ed110"
+ + "5dcc50004e4cd7e897516c536c205b339ff0d35463ca6871ea5dce7a8daed8d7",
+ // padding:303530112500300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "975d07b7295268a8662aedbd2b65b5eb10bb496077f41b90d12d34ebc7e492f0"
+ + "c7f3a41d4164a279f06ea616f91968628be4ceecd4a554477bc76cc6b2e6bda4"
+ + "042dc253327c4b8fc40e9242cbc8b835114a7379a3081bae4b2803a99deb4a54"
+ + "0f8c149ca5db3a61c7bc9f61cd7e55521660a06603849896c791a18d1c7360e1",
+ // padding:3039300f300d060960864801650304020105000004deadbeef0420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "37352cd11eb5ff7380bfb7c0d3e8d9979ae7cb489a71c31a077d59496547b0c9"
+ + "5a760387ed50eefde0b762222f05a6033740f6e010693edf3ef8ab5f9c57f4eb"
+ + "1f6ccd83287dcc2e90857defe5ba4109bf79ad84ab069c85a25758d22536c688"
+ + "2919245fa2d7e7921b3635d984deeb6555cabdfc46a42c75875d55924c8bac62",
+ // padding:30353011260d4980060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "0c783603c7af53df27538b983fb7368e9f62d4552f008f2920a21cff3186d2ac"
+ + "7de451fe8c28b71d38c657c41c79174c84004deffc69e69cebf2aba2a43ccbf0"
+ + "52f6fbdc3c9d3683275913c4583dced686291bd1c0217a015d9ce732eb410c8b"
+ + "27f2fa7c9ff516a81577490f5bffc8121c7ac674caa464956942786c5dca6b4d",
+ // padding:30353011260d2500060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "1fb1960934683292a4c92cf3d582cd5fe68888a5b0f6c2e64538289da7f96a9e"
+ + "fcc36bdbf1fdc0cc0b3b36c6af608309de58c6151112f3a78599ade4a718b359"
+ + "547a4cac9a020e5e7e7117d1bfeb3ec21bfe9732825e624b27ddf8a946eb858b"
+ + "30461706f769a54b0478e0753388951d98129383590186b80836608f7e06c72f",
+ // padding:30393015260b06096086480165030402010004deadbeef05000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "7e4f953b288c20fd5bec56a00745db9be03590efcb637e2ce2119a0a1846e9f3"
+ + "8c0ebc5f2498ebde6217d81c9939b6d6a6f35ba54ee50d6313d3f2579751e7ae"
+ + "8d31ef4b0e99ca2e96c80459a7e5ff51f6f31e9c965be19097de13017c90037a"
+ + "a482d197c986f50bf2d5e1acb3f3024605e46d963410a4a623c898d0d773a78e",
+ // padding:3035301106096086480165030402012504498005000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "03e121f5766771a65a400b354f78ca27c9a04a25d4ab80fcd6ee91bbca3baf27"
+ + "752abe5cfb7959779644c064bdbaede14847fc035a3a19523d83cae3c31a64ef"
+ + "7538805e398e196ed8ee9ef6b3f58f10e7e16c95495f82ba430e5d997d165564"
+ + "44bb1447ebb17829ab879e61ac297ebfd4b94aa99b68b0b498d8e434d4fb3c6b",
+ // padding:3035301106096086480165030402012504250005000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "90a5d10e2e19f7e016d5126a3d3eb91432611ebfd411b07a4be15aa48c39df33"
+ + "f3a2855f1e150ad34c7f83973bd73eca6575dcbac4086aa0a38db3d6e6ee2e9f"
+ + "419768493fb4829f1f6d67f80359f82d95483d6057de17fd388ae46687c429de"
+ + "a4d9f7a286c95fb1b9df0f1ba40a4263307789952b1bd07cdcb3b5cef10d9d2e",
+ // padding:303930150609608648016503040201250205000004deadbeef0420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "a88d38e8c765b7e439f42294e71c1689a318ed5414efdf474196989829d4989f"
+ + "ce8910798f4d7873fb43d3a501fa15c8019813104e4699597246db66f96c838e"
+ + "45aa3596a1d26cbe9f6ee91c077422953b402f7e11f8768a2f132295bff79a0d"
+ + "10ab843cbcf2c921113992336638f4052446f52815328ba4946510a6b701d448",
+ // padding:3035300d06096086480165030402010500242449800420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "34508ce63c502b3f8185f7cc61c724185aab2c1bac68cf2a7e7f4234edcc0e38"
+ + "cd15f73e73d02431c62c28a2241f629382ac5e9329ab71dd7e9152b10bf86b55"
+ + "0c855aade6a5941ffacafb4bfd57066bd6e39bd0d8ecf57ad9a6f3ba48831800"
+ + "bd8e6e9773a0ba3770cfb9ae329bb4451f450ee35796b5578104b7ff5ae2dc31",
+ // padding:3035300d06096086480165030402010500242425000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "7f642b5702c331dd76b7ff66578a2c0547d91c556b7b9751443d911729fb5ce8"
+ + "426515ba068e2839cfdc956eb813c25d65a2d5213b59302c0ed5e6fb95c49002"
+ + "edb1605f8f622912fdc309d92e6e3f188ba19e991fab0a7018ae4f6e70927d91"
+ + "cffec51b2dcc8113908faa1173ec9ed72350aa93a8cadef8bfa7305bae22bdf9",
+ // padding:3039300d0609608648016503040201050024220420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e250004deadbeef
+ "0abb75f2fac084da0b99bc823c021c4872e23302a6a25e400b6f25d60f7c9038"
+ + "99a69dc548676106b44f37c1e6d2604eb995a16880a2a8e2cc9e0ccb2b984ae4"
+ + "82036f69a6ad31a2b5836e73e0d30c3e10f8b93c7587d7c0f2371183edc3b8cd"
+ + "0fd7bc325b1cf75e1079f8d6df53fe495722cc1ce707cca49bc6f4ed2ca6c4f9",
+
+ // including undefined tags
+ // padding:3039aa00bb00cd003031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "9f2234b108a45abaed850e19d2f9576f59bb83dbc6165da61c4798638f9c9858"
+ + "7c7eb92a8c901dc4430e4a47dc05681ae811ffcad6f7a604c43551cd0f5d1235"
+ + "49435d622f7efec578301efd49dc6b139abbc3c7d6a26858f6d18f09b863a145"
+ + "d6483c9efc6c322fec1341b6362dc1d752c714efcdfb09097a0ce6df7dbe88a9",
+ // padding:3037aa02aabb3031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c
+ // 20a6ec113d682299550d7a6e0f345e25
+ "24ba137a293599ab7e50a0a4f8c7a5cd02dda6a4568c93f84d00ff4729656456"
+ + "3c9051b334db2fd2c081b23d322d4870a61b2435d651d7efb4e1b0920e759f7f"
+ + "d81a937bbc85ff43dbe2b702dec3acf4db68d5fd7b8a2f6d32cc49a7300dd659"
+ + "623b391927a2442d69c6c3c29e59eb80b1d0a95bec6d18a6223cf4357eb7cc96",
+ // padding:30393015aa00bb00cd00300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "04023dd35fa479f8156794d02935f8669c023c774b95c5a0e02837e32ccaf7a4"
+ + "ba5195835a15de6a21796eb96bdaed868f9e8b7f0a5a21c1a3058f53aadb62d6"
+ + "ee74cd70b2c38f17e42a1f7ffd88955731b4e15368211ad53f617aacbb54a7e7"
+ + "078740ba6daaca81c1b321b748ea1d13f7aece490226636ecac41bdc275175d6",
+ // padding:30373013aa02aabb300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c
+ // 20a6ec113d682299550d7a6e0f345e25
+ "253bed76e4b8465ebfffd1b7214ce586294d3bea290517ca2bfc417ba9d8e72d"
+ + "286570c348dc6084fd379c2bf4dae424189964639533e17c409ae18e445210ed"
+ + "4dc98de4ad7336554740d1532d5010a1bd7ebbc33ba48a3365d50669e4f4522d"
+ + "0e5ff7a3bdb1c42c42dee647a8a3ce16633eb33bbc0a869e12cf99f9481dcf85",
+ // padding:303930152611aa00bb00cd00060960864801650304020105000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "0775598491297eb9004eed66234ded82e047ea2f06837425e6bd27f33b137366"
+ + "7f3ff4961d60f85edede88ec2bba2680151da3763f0df9785b31771da7e64386"
+ + "2ff9ba944ab54bb1356ee113e420002a873f1eb381660f3eb84b1d6b25ccb8b8"
+ + "2ad12ad0a449c4de205144873329e80ae8a84d1d3c1660b3303cbef28b48a553",
+ // padding:30373013260faa02aabb060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c
+ // 20a6ec113d682299550d7a6e0f345e25
+ "a73df043d06ae53a37773016a4e21d3f1093c50e079b189c4bd7db3e2e9875b1"
+ + "4e5374cb8e7394a9f1b45c7e4e9dd516198bf5055b30ea4d205f39fddaab3da0"
+ + "cec63524bdae2ae166a3874c59057d93855d6e6314fc5da8111ff58666a73c00"
+ + "a105311859f27d2fb92f507531b9d681e219861e4f0b2b979c185af2690eb4f7",
+ // padding:3039301506096086480165030402012508aa00bb00cd0005000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "17e5a889b8139593e192f7af684c918f2751f157214863f88984ff3d8c9e381d"
+ + "1bee5ee788fc82869f4c3d8483e3c17c873a850a7a5c85e4518cbd8531b331a3"
+ + "08a0368a868bb7995ce0f8a7ac5ba53b88c31c958dfabb36ed461472505b5984"
+ + "18185b864f381342c29dc80e55ca7c2095e7788e7e8d385d61de605f74e431b9",
+ // padding:3037301306096086480165030402012506aa02aabb05000420532eaabd9574880dbf76b9b8cc00832c
+ // 20a6ec113d682299550d7a6e0f345e25
+ "a659f7c44e4589e9f6658b0b57e82e65d5ee9fbe2376894f558a7ca4b6e3c503"
+ + "2f953d1dccfb9b76bbc53dd5d1a52cfc092c6ca279b37c0a43c99ec0553d7ef4"
+ + "d9bf9361a1c4a3fb7496aa58c0af518312e18819fffdafd1a230a38440a6fbb0"
+ + "e69babaa977b8b5fe08ed7c6d59c0391ccd80b42a0c0102264b0ed6af8524e9e",
+ // padding:3039300d060960864801650304020105002428aa00bb00cd000420532eaabd9574880dbf76b9b8cc00
+ // 832c20a6ec113d682299550d7a6e0f345e25
+ "83fd4599a47bc0852ee1a12b2d97fceae6d8442fd089df1d21ecc252a4109824"
+ + "10bbd2cc6bbca219502c2934ac593a09beefdeb54b0692b3e5724b79b0f5c535"
+ + "41b62b0c4bf80a658af71d5964fc6a1fd7823370d00e24dcead4bdc86bcd883f"
+ + "e3f48dc7f8468ce99b7580306007021b68b48ace274e3c09a1b5e21fc7542ef0",
+ // padding:3037300d060960864801650304020105002426aa02aabb0420532eaabd9574880dbf76b9b8cc00832c
+ // 20a6ec113d682299550d7a6e0f345e25
+ "4bab6fc6948143f8ec7c8ad86a0c5cda5bd8151c24ca7916857778729c882581"
+ + "603363fde0ae2a28b6f8f2c8ce8d5f6b6e731bf8ef735bd31318069544295b54"
+ + "b04ff2abd1e11900373931164586d7c830bae704f7314eebf1d32b3a171274ed"
+ + "456e335d2a0b998ac441053ef096a037bfa6e5cdf3835c45ede383f0ee8feeec",
+
+ // changing tag value
+ // padding:2e31300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "5770bbbb883f93f7c29bdab32e496f2e9063110fe648705fd0b1dc927052fc9a"
+ + "ce9b36d898d19cd4f862b777b7c790d767b8313f735ff567c34cfb31f2964454"
+ + "0645beea182cabdf789ff9ac3f68cc20444af0b9d4ec0bc8992945063fdb733c"
+ + "ccef7590a10bdf491bc21c38f25ff65a581b40343e30529c3dbb71f62189ba3f",
+ // padding:3231300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "359dfbf40d3c2383f58bef1d518abe9852ca80d797393e4e1a9380ef08aa851d"
+ + "585213e8897c6f701ab680b0f63ccf5ea4216331918ca9a984fb6ba549f4bd06"
+ + "6ec1fc4f1ed053fa5658b01df674a21322ba7e21fba6cbb3a8eb5565fb7bc269"
+ + "f99c65981efa650dde613ccd6d3927cdae45922d94dcf7ca5188bf5acf84035f",
+ // padding:ff31300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "24a61067121e9b4363b816b7c5750584c23f3c3200ca929fdedbe95d7504c56e"
+ + "a7dffd762074e44e96e22147943f2b704003967270b2be1bd1baadc3861c4cae"
+ + "91bd41530c67220349db4481d324d9927d52fe85618ddab2598996c5813f3299"
+ + "e1afb020b24003fa94f94a0c6c02b3183295e0de79eda021dccc5539cd7874ce",
+ // padding:30312e0d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "11b8d1dc2fa2afbc32f048d7454ba032b432a2ecd438506aa72c697a5c118e9e"
+ + "231a0c6b6340b5564402b7e837c59dd36f726fd626621b8f543964198484087e"
+ + "ded70e7bb1dd63df2cea33198b9d02dd28e3b8bd006ba991a8b3bf06ac928bef"
+ + "45cba2362f2e11a5fbfb0310e84e8b7ba1e17c315adc1f34519134c36689619d",
+ // padding:3031320d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "8748f029d5294dc917cf4fb347e0046f903c088fd976ca97b1322738549df7c5"
+ + "6cd67349d66596338fe418b29de9e8af8872fcdbb55e1a6f74e9965fe7a365b8"
+ + "46b667d0ae50df23083be73cceb59db545a3e1a560f6ce0e9eaee57b5f95b848"
+ + "7a3987c00f364d0f148ead6d7e6a37b05456b913b7a79c0547b80da2a2893881",
+ // padding:3031ff0d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "2bfc087003f3b98f0c8c5273de34f5e4d5047e909cd80e222072f6a7926ced5a"
+ + "e169131342640f2be11bde2f7565c3c63d0335614dd278915514de8421f4521f"
+ + "0138109a5c9778f86647b8a42815b6b861f173f5a6df893873f99c5e62bc3c08"
+ + "6150e3b7d7abb943ecbe5806068abc433e9052d9bdfa19a58d19da463dbf3b23",
+ // padding:3031300d040960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "0fe03eea6c50ab664bebc7d64346762aa29b08b61f2877973cd543c9533c9d04"
+ + "51db8d836eb46e8d64283306efd7ef6387cdc3c794f7474f2e7d51b9df078095"
+ + "adc85fb810cae52434c9cee5048fbff72610778397fd83204f44bb87f7637373"
+ + "d111dd16e18287bd9ffe816683bc3663f586082fe0811ff6a06c0264b67f7716",
+ // padding:3031300d080960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "748b9e64195314003ca31f726bb3d3294abd8eb376365acc5b1cd36934bbe1a9"
+ + "bae99ceb7c1a40c910bca6007ced7961ecc9ac74c7a6424cc87b6b9610320ab9"
+ + "c5b527d986c6e8ed21e677bbe2ee7752e2dbcfceecc2dd6da3f6c6b9c81435e9"
+ + "e060dcd67ba834729761dfc9570b79bb1b8ead7bc1325c2233e445eeed12dcad",
+ // padding:3031300dff0960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "7379bc96dd40d37a7f8e58f87c10fb94f250a964a55b2abead479b368e60e442"
+ + "e6eb864952308eb45eef1d318b6a5ffce634fcb886dbfa062060b9809cf89a09"
+ + "a26fd334ca22a1917fd219900ec0c68164c308cb9cbca3fb2b89ed8637c5540f"
+ + "7a5886ab1e52c503e20edd6316e41c746e53917e107ef5308590800ad378ac97",
+ // padding:3031300d060960864801650304020103000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "4a2478918565be6b46fe61e5f66cd1befb7a3026b5a1502e9a42636b0b924a02"
+ + "e85d7ffdfd8671b1d6d3e604e3ac6a5302db4e0ae0975d0661efa018d6ba0c63"
+ + "2a6381368dcb75926542c74823a8c6d8732619764d5a61062fb3b17ae243bd69"
+ + "1c97c8f9821af9526abcb522ec8e9dca32de1989e576e336af9dddc3e766541b",
+ // padding:3031300d060960864801650304020107000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "5ec392c91cc165ae59013337e7f7d5f2f9b3a6d45b6f6beee6dbf93e7b960790"
+ + "0f4672555a57de6e9e1aee1fc9b7adfc0dc00e122e84b0233c0d615dd0d79764"
+ + "fdc9d1b0e541f2de0083ab479f313a07f55f51390d1c2274858b219b1ec0601b"
+ + "82a2f7648ae95ec17099067a173e3e83959b6c06f149af0e4610761aab5be1a5",
+ // padding:3031300d0609608648016503040201ff000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "6173aef07a7057c3e97f6b7d4d7266918876f9fad86000b4c8ec7f83ee491563"
+ + "115b0cb5d580df8c97feb0d95866eabb79147926f5395c5189554749f4a2c75c"
+ + "0d96325971635be029062e1f27536c5041bb42f42e1fa10e21bb8e9a2e2502f2"
+ + "a7299dfe3bd8720ecb8a57238056ab0eb546de8dc0e56b317c73ab1e19772596",
+ // padding:3031300d060960864801650304020105000220532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "3b80c72f3b7ada8b38b30527bca41180b4a89b066f44a17b9df5963dca46517d"
+ + "9160326afee7a34b650b9e7746e764958ce6a0a6268481a8df40e0a95a81ab0f"
+ + "0bd20c050becfc0c4b03ebda19749a4a1dd3ce925fafd9a4006a835eedf221a6"
+ + "ceab6aac6bc74f743fe171ef8c01935f8901e1ec9ff6e33ae8311851fa14a65e",
+ // padding:3031300d060960864801650304020105000620532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "4b0eaf3ae1c7a3322dcfabee0569aaafba51e0f34fa6afc325bacc853ccd2daa"
+ + "3dca56c918325bf553af02ddd19fb597c368dd18892d52d9e935dc51d38347eb"
+ + "ae2a7f90c78504355f6899ab4452d5f51d2025381d81042a08582dc50bc10782"
+ + "46ee69652043bb747969a7450659e333193990f34a8ce3f036221193e700489c",
+ // padding:3031300d06096086480165030402010500ff20532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "1fe7b390bcbd5bc1904e676111653e14e581e7817b45294bb790e4e62f3010aa"
+ + "aa77e246b29729f2b7da65a2f437b8d9c4fe3b26baad367a19fd7b1758d04c2f"
+ + "788c45e5309a833522b46d7255dd5ef70ed006ef966aa7c648bd0b893b8e1566"
+ + "961c16e9554fb729ec81819f1b3da890d413a153f487c030c7581da9531bf134",
+
+ // dropping value of sequence
+ // padding:3000
+ "317379f37cb7f21fd03259a27db3575d491a248df82e67b39d4956a1c619094f"
+ + "cde001544f0fa70c64dc0d0440fb21d2860a20a911cbb397792bf3eafa5cc050"
+ + "e78b1e7bb29d041cfa0287bdf54a90a7a8bff5c870e898fe34bb522477daf8e0"
+ + "03bc22891b789ff215869cceb92610c4b03210d19506058d941e6fce7a3cd786",
+ // padding:302430000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25
+ "2de802ddacb7e47e27875943d5098419bca3b170bf74f1c4b4a8ac420d4469d9"
+ + "aea97592fbeaaa1dcb5fd20bb97afc5f7abae17a9bb85c5490db97010c5217c8"
+ + "8f9f52b5e209cf5fba5f0594f4e4450114dd0348ece336870a1333f7660caf95"
+ + "9056ba13b77d35239eea164ddbc8808f8e7e1beb070f551b6e95f90d5bdbd925",
+
+ // using composition
+ // padding:303530013030300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "09342a8fb8402b5e50fbf8c5d1cae415ce02c0a803adfed88188982129e84809"
+ + "18dc21616bb5f8381e8dfe13f63234090c32e542a005df70df5e8e00dd2a478d"
+ + "10fff1b61efbdcf0e410236f7c031c9a5f7cd0db9098f8a32a6a49f408e72c4a"
+ + "29b7d27e8041ba605bf089bbdb9777e19b31ecca0d49b90d54701721af79cf3a",
+ // padding:30353011300106300c0960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "3ef90c414a64601c538c286f2c35f32445039799b8c266eed605027578edda79"
+ + "6a409d905a751bf5c1cdea97840437fa82733d8f27efbbc05da732887078a8f5"
+ + "47bbfb54607a54f893df7dde0c35c45f9c2402bed0405c72e98175e5b9d6f902"
+ + "24e07d12e8c1bbad2fc8b1a14c42dd5fb7e554db5edae89d335705c672cd7b55",
+ // padding:30353011260d0601600608864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "3ad3e4ec3636b5eb8aac2161c04d228491ca0d9da2abd69d8904054373940b39"
+ + "b5c025c011c9b9508a25ec25b24a0837cdd6a27cb5c8ba3683d90ba5912ede9a"
+ + "21f2f7e851dc49dfebea8807576be703a6a87ca44c370db76812b9929a54fb8e"
+ + "2259453ccaf47da1b8ddc5b7322c20197604b9e028ec00bd7eb48012274d5b81",
+ // padding:3035300d060960864801650304020105002424040153041f2eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "778d93be708d56defbb6dedcfec2a917a3772b2810e26143db1f9d0f26c4fbb8"
+ + "de8db5818aa32ebb2cdcd7960e593ace2c3c3eb682c930cbffcfa6b34438ee2a"
+ + "786a9707d5d10902f7f4d8fc677106275fcb6cb08f56f341e0f52af590e0bdfa"
+ + "2f2bf95693265e87f5046bcf3e6de34810e8eaa479f3afa2b0a98b175007c209",
+
+ // truncate sequence
+ // padding:3030300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e
+ "a3e0e0cb9f7cdf8a2b95139f7c475f274bb63252385f62e66f82158f429e74d8"
+ + "3df9ab1040717d34b6a5e009b6ac95960826ee83bb298ecf900425ff03a8f156"
+ + "053b57eac6086d61dd3a8085b84c83bebbe3270164e3147ddee8966a02679640"
+ + "1fa48da70f5d949386eccad26b0016543f3f90c8ac2874100dce13f03845509c",
+ // padding:30300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d6822
+ // 99550d7a6e0f345e25
+ "84345c9d3de7b5da2156d3669a731c4baf6726c4c231bc8bcaef950d7ac37ca8"
+ + "d86e9c9558404f313de3fdf09024d25491b0a933cc3958033210b1c4f90070dd"
+ + "d083005873762566ff2cd7f6915b4cb430f5e7e1bca8c2ec32b4ddee48aba667"
+ + "f9d614a27c3bb40c6cb7f0cd77d3d17257f197974d1871cc09c9583cc6af8e15",
+ // padding:3030300c0609608648016503040201050420532eaabd9574880dbf76b9b8cc00832c20a6ec113d6822
+ // 99550d7a6e0f345e25
+ "227aaebca262d2189c479ab46d8715a34100bc1975c2d3991a4ade27376f0687"
+ + "56cc9d89e903713bc28394d202d81b32126d7eb09154261841227cba6ea0a60d"
+ + "0ed9302f816fb4dd241dcd2d746d5c1b068c42c0b2bd567ef799cbfd0a83e8a3"
+ + "0c4fa2f7296dceca38c36ab597ba992f658ef7955d32d38847870afbca35d836",
+ // padding:3030300c0960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d6822
+ // 99550d7a6e0f345e25
+ "a4316d438c7091b3bd5ec09aeea9095cb5046d8f08642b087c34985c34377bda"
+ + "fe74285d00862fba20572ce7a06dfe62b4fc08704d1cfb161cd88478e7e1c545"
+ + "1e0bdcce0fdd83c0e37fba5168ae03fcf4ccf60fa12c9b0acb39fe99b06933b9"
+ + "e0774f41151e0564ef805144c0cb76101672c287912197155d91bf036e84d1ce",
+
+ // prepend empty sequence
+ // padding:30333000300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "361f80a57ddb48796e50b3e6467cb00a9e1e193330ecd2cd6a31f649b49eac27"
+ + "e295450efe03e09e59f1829cc661d36b0fe904602c644aad7ec8cb2ca3099078"
+ + "b6d4f7b9233dc159fd1a6189451fedbd176e436f6605f2b889fc7197ebb520ac"
+ + "cd7f90e543da44453c7ba1948e83e31f5907d1989d982acbb348ca2216fe050d",
+ // padding:3033300f3000060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "674c01596bf71fccd36aa81b000be007f6cff713e5f6ffe58b25e790f9a1f654"
+ + "2ba3f68e1eeaf1bb1ac6c3d55aeaf08140f6cc3d0474f6bd87ee442568346553"
+ + "ceb34efb5301a4d3a5b3f28a5fb038ccfe8444524d18adfa042aa1685fc3a5f9"
+ + "005da5688853b8660ba74f0e32c5be38c743b0048ca9b9fc19a35a5ff4e2c48f",
+
+ // append empty sequence
+ // padding:3033300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e253000
+ "07ba2b5d519b1f60dc455d6ad90b4135cb45c5da5a2a2c9b8cb954165394a0f4"
+ + "0145ebf2b1a3ff1d47f5031d542d25041fe9b6d78aab623c40eedcd846761816"
+ + "8ad02af8a696573c5c63cae0b2c26583b0240848d663fdd0195322bc2c8dbf9b"
+ + "5db2ff9cc3e75e70480e51da0d6dd402fa87772ddef5256467205cf41a42d18a",
+ // padding:3033300f0609608648016503040201050030000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "9fd302307455d4e946c1ccee65b0941c3550c823279cc52c4f29ecff72a12ac4"
+ + "0ef6b7e37b7dd774b7735bbae89b0792908bafc47f0b0a11637042fc8541b346"
+ + "151bdadc3990e64b6d1807dd0e7f9266ceb3f686a9813341f835562d3c8c8486"
+ + "8a1f98db97d3e695ce4a25fce80b828d010d6323120362ac48700abff8a7116e",
+
+ // sequence of sequence
+ // padding:30333031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "247aa1cb69ccb72795c93809d7c3a5e52de98ec5285196058a6ab18ec2f5d9fe"
+ + "f5545ab5df923f63bd58f5f247b3d824bf161bcb56d325d4e2fc7eb3765dd81b"
+ + "5580422abf2a3bca8d8af94cf6a9a3133b1494f66d5cbe938d30b9308b5ce2cc"
+ + "6d3df37d3299b6a7616d40afcc7935d80225e1a89a7a63ebff13a66e21280a6a",
+ // padding:3033300f300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "6d6248f823020a9604bbafe5acc103d9bd020624585c95805533de22afa3b6b1"
+ + "b511f8805296ee4d3e96d707c91e55df8959464ddb6d6a3d62b1cb248754302b"
+ + "2833406300f4975d913f1b90f95e3673e2c57d6181d73a360e8c818b8a9dd1e7"
+ + "a4fdcd68683f11dd47c2d395f20b0ce9c59eede6ae6aa58a707c4ea8d1a73a9a",
+
+ // truncated sequence
+ // padding:300f300d06096086480165030402010500
+ "941d41c39aa8bf3879d16cb78c5486589e7b97e56a0249c4f613060d26b78659"
+ + "8fd2d34bc4e99cc8888137975937307d6a328059a09f3b994bf955c7de4a2841"
+ + "a0d10bbbebb2db3b332656f258c66c8d50cf9155ba94e1cb21a78e6147af7695"
+ + "8ddd997665b6d8f67ea8f5e1fdbebd7df635f20494489c895d33ae4c7f248bad",
+
+ // repeat element in sequence
+ // padding:3053300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e250420532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25
+ "a32afeafa2c3b58bef55776ef6daaac6647485dde100d968e0449d1a2d5a1218"
+ + "07ca2fdd70e2e9cf524cae4f263e11837000df85f0886b718ff45cd316c8d031"
+ + "b746dabfb956dd6118a37e0dabcda1ce9c728afd9a5f2448f5b15d2798221888"
+ + "8d457752485119f53219315bf63141c9c0802327226a096403ece022cb27c0df",
+
+ // removing oid
+ // padding:3026300205000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25
+ "6098a732419cd71887548ccf4fbf3edeaf9fe7b220bd747ae1b995b746de1f4d"
+ + "7b48c73ddb71903f50ccf7c93be9c8219de5a75ecc302ab50356069dfaf642f3"
+ + "2ec580a283519fbcf04784860b0660174dfb7e1e527bb320960bde8f6c605bc3"
+ + "c1055b878d2adbb44e1b6c41add15cb603345c4fe2d1c0158fa03f21b4c015e0",
+
+ // appending 0's to oid
+ // padding:3033300f060b608648016503040201000005000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "2f08cdca4d621007acd15b1f4e3c39882a8aef706878e8f101e7fb250798a352"
+ + "8dcbf4d3327ceb0754a2ca0850794094dde8a875cb947d624d386ddb9593259c"
+ + "53ef2311260ac3c9cd1277050ec98d105188f590f198ba908ddcf3f9ed18f5a9"
+ + "6cc6b353fadde007658f87ff4c201db7621d69c8278305f3e9f2041a2dddfad0",
+
+ // prepending 0's to oid
+ // padding:3033300f060b000060864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "10078ea73abb9bbb879c9d8139b1758170fb73b34f39cdc83e6a725439e315a5"
+ + "cba4421fe15e8c80d8fda0a9aba9a12c23aab41f7328d4191e6c7c3a53a505ab"
+ + "518dce078439347945671ab06a2cd5375457b3bf181c40a1a4be1ea8305c9a40"
+ + "1488532c7cdc1150fb9c46a2e846ce4a2fd9ee863d0b0b8af7f10360acc47f10",
+
+ // dropping value of oid
+ // padding:30283004060005000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e
+ // 25
+ "69a74665f61787b54b522937c534e95e91917f5dd4fa9e3472add6e21dc033a2"
+ + "75408f35c71ff6cc029e25986fe6dced8ed053a9040aac32fc444e9252d2bd40"
+ + "81fe3e51ace15a0f694c0b8953dd6afa7f8cac67f4d8e17513b415c14b439a63"
+ + "4274893885907e2ea428a6e242154a58a031fedae31c73df7cd4e2f5591496cb",
+
+ // modify first byte of oid
+ // padding:3031300d060961864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "8f3b544724810d462cc9b19f356e61efe7c192dd63511a9f1f63286ca81f8947"
+ + "7c2b464f8e51a97ee138dcf8c6709d79a78591081384af7cb5e182c9867b8260"
+ + "13e6191efddddcc39909d3ffbb18944503b69d774c959831a8092f4790a49335"
+ + "21100c3e9741c3b58e1d24b75425ee28fde4e40c249b4dccd726cb06cb9ad2e3",
+
+ // modify last byte of oid
+ // padding:3031300d060960864801650304020005000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "3933d6937e977caac37a07a5c4ae503565af57e6c4e830004147f8bbf6784f79"
+ + "666d89cb4cac60e3f0aff2d5ed6a182921e490c958bfa49c86fcf0270914c102"
+ + "275b0878f01795c7a2f44a8a6f5306aa67a81f9294089876801503989e749d15"
+ + "2c3e34906291f1f54bb6232fdd3d51e807f70927bf38ef70bd2ba45f0323acf2",
+
+ // truncate oid
+ // padding:3030300c0608608648016503040205000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d6822
+ // 99550d7a6e0f345e25
+ "7b2f6581fb0b4f913ed38c0ea20dff2bd60723f2bc3f1022ceb946e48adb75b1"
+ + "e0be031dd8b706d82967f93c6b6ba496d8c4b49aea9970e139b18fefdce30a4e"
+ + "c04f77625eaca4c7d1265cebbbcf53b63a113cf06bc50e4a416a771cd28785a0"
+ + "075631a3ef60c9212e224aaa063e7d8109c27e248e6422b26acd02ec012b7bf3",
+ // padding:3030300c0608864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d6822
+ // 99550d7a6e0f345e25
+ "4dc9e86e076a395b530868d9fec9f858bd6e8c10cc1e32cae7653abb3f23991c"
+ + "677e970ee468c7f35022f3241f5d35673a8cf4ce9134b1e63a994dc7abc8cf4b"
+ + "9dbbb126b314312539931a0163c911f0234f5c3f683c9376f2ecaa3294d71a12"
+ + "74f6c63b84ea8faf826eacb05e4fa5459b787ff384b2cfe0f1f4c755f32b5c50",
+
+ // wrong oid
+ // padding:302d300906052b0e03021a05000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d
+ // 7a6e0f345e25
+ "8bbc9167821885a728260bf9831120ecc42c14b2b07854169c86421146367d1b"
+ + "ec66d8c3daadd115f16a29754e7fa8fb70a63966f7838484615d4364311b6c3f"
+ + "6e73ecd8ced0adb52db2c374297119f5fe571bd5396529d13b7225e87db5b5b0"
+ + "df38e4c56f2349071b09ff5c1ded919b398d4aff38c6ae29af6f6ff99d3e8836",
+
+ // longer oid
+ // padding:3032300e060a6086480165030402010105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "2054d402bf6a148b52972b830c8c8a16a6aeddbcd5c2ae3fd83de67c666e712f"
+ + "a98650308658837a67ab87b2c444bedc7cf995c19af433da9343f260049b1bcb"
+ + "436ebe27d8a502728dfb0daac5d2710e2c39fa000b909aede07ad7a0d27629e0"
+ + "ac27ed9fcd41a39e09f7acdec4c2df77f38c535f46e3b96f2772a81e65e74bb8",
+
+ // oid with modified node
+ // padding:3031300d060960864801650304021105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "3a94d241563a2ad97574ec82baefccd9dd114e21fa9169d0f54c4d0f57826224"
+ + "804ddc9b29c1905c59f39bd6aa3366705a85f5e6e18c0eb0f67986b5265e7371"
+ + "865b618e90e5c5313f0b6fce2343aa12d4ed44d6770fa08d4f1342608a4fb627"
+ + "a273f3a1f1340d1f5c55957ce51048e3690a845851009cbfe38d3c96e96d4172",
+ // padding:30353011060d6086480165030402888080800105000420532eaabd9574880dbf76b9b8cc00832c20a6
+ // ec113d682299550d7a6e0f345e25
+ "079cb62831dbeb40a638402865cc92cb49913dae214babc3f4f8d69d64cf1436"
+ + "2c23c8dd6ebcee9c44633dd54a62bb2f0042c20033728fc2f8ff482cf0be3ee1"
+ + "03bacf757b50319495d9a838844ea1064f4bd1f1ebdc1b71a318c3c8f7d76ebd"
+ + "79ef2f3991d4d87e110d60e5fc655adfa4a8e792e46c1c7aa96156b884e2f7a9",
+
+ // large integer in oid
+ // padding:303a3016061260864801650304028280808080808080800105000420532eaabd9574880dbf76b9b8cc
+ // 00832c20a6ec113d682299550d7a6e0f345e25
+ "2c9083459ba6504dc10e0e63edf8ede8bdb4a9728673306908ad4e8f25656d48"
+ + "65f0748b9fd2cf7b51db0a2c659e0ce021fef3d2d3d0cf7c45343729c2001a19"
+ + "d37e29398a9a7e92d7f62693252261f1f7406b54af5447db6e846f981722059b"
+ + "7bb09ba95268c321c156ff659e0ce8e709d2819d5ce15f5dcfa54c55114a611a",
+
+ // oid with invalid node
+ // padding:3032300e060a608648016503040201e005000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "9a76669c75f0f11399699f76e7bfbefc0d29feb5a8d86de1f751eedbb5c9e7b8"
+ + "1ecbc224534db67cfe1b611951a6ff499d86e11cac4a1725e2ff707085a81a76"
+ + "c73d5b53d1b0b2c4fab2d2eebe57eca83242a261cfca768abcd8e1f42e3841d6"
+ + "98bef3d4f16ac2dfab0fd42ef0abb0463474367dff7ec99d665a9838f2cfc24c",
+ // padding:3032300e060a6080864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d
+ // 682299550d7a6e0f345e25
+ "6674ec2352f0d3e90f4b72086f39815db11b056babc57644c8a703014f439baa"
+ + "46e8ed961714d5c7b5f0ec97ba3fe5ab867c16b7e1de089868dcb195fc20cc42"
+ + "fa1b3d3060f50cca77281bb6be18d65a1ee8e5a381e21e7f02e819752b71327a"
+ + "28719c7284f6425bc9241abb08d000faf58d48848d7f4b8d68b28266e663f36b",
+
+ // appending 0's to null
+ // padding:3033300f0609608648016503040201050200000420532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "8d18a5e0a81522b56eb9e4f43bee15475cdfc7881006150cc230e76028283375"
+ + "a13425fe5a106f2626346a65817010a5510b157b234a16fcb9426909a524a288"
+ + "161537be91ab13033ed296f5f8c1e5c3bdb963f12d7b5eded46106f7c2dc1ae9"
+ + "c451415303cb7e6a3f59809b922183b9638197909d5730e5b1e89705fbbe8464",
+
+ // appending 0's to digest
+ // padding:3033300d060960864801650304020105000422532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e250000
+ "51640e26e8764936a7d9d709b3e0f52a5f1843453f2c6107a8e7fd6dad8b1c02"
+ + "ecc71659cd4134be952c03ee83c190bea4ea7260e5472c3cdf87b6ad45b5c974"
+ + "957ee9b4bf6f30152c2d939f722cff32e5482db96f3e283532b96716d3624daf"
+ + "16767e0ecdad16c97e56e4e076d64b92af329d2d6a2f8d14b59d1b84853659ab",
+
+ // prepending 0's to digest
+ // padding:3033300d0609608648016503040201050004220000532eaabd9574880dbf76b9b8cc00832c20a6ec11
+ // 3d682299550d7a6e0f345e25
+ "9080bd4ac03b7ecedd45f8165360d4848bdfe1c9212ee1a4debc1aa92886cd79"
+ + "47a2df5435789bbb0b3e8f78815aac80e2cff14e1939e9ec32f42e7c29ed4029"
+ + "c88cafb64e8523dc85217c40d1bba900468a69c5bd4d12ac67401698fbffaa51"
+ + "59907ad459d3843e12487b3b2315c585881bc42e45543f7cf25110ab7e0a19f4",
+
+ // dropping value of digest
+ // padding:3011300d060960864801650304020105000400
+ "5f66f645307346216d3ba9c3d8b29e96270cb3b2e686a676fe975c10b8c26fda"
+ + "8d8eb172628bb3dcd726160c13ab8c5afb1d6ae943ea4c18d00465d97c0d2bcc"
+ + "27a63c18457ff8d6e3f5ba373b4be7b6f4c610f83578613f4fe41a40d86230af"
+ + "ce0bb8d4496425a5bf0a80c6b1b1e2a981cd44c31a9aa603748c3d2fd2b85478",
+
+ // modify first byte of digest
+ // padding:3031300d060960864801650304020105000420522eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "8ccff4ab4fd8534b6b50593f775bf6684391225adc37745e7ff25a4c4baff78a"
+ + "252aa1177ea3f3f09d2791da50ba19cef40ab8915379f128bba3271069cc2c02"
+ + "725e09f0b2cdfa0d313eba3f5a7e231588fd617b7d90b285e88a944d7d0a7fe9"
+ + "cc558dfe8103391ab2e6fbf762d829a55ed4486b5d888957078ffcf49e8ec352",
+
+ // modify last byte of digest
+ // padding:3031300d060960864801650304020105000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e24
+ "694b90d259b8dbe290f5851ede2ebf3bb718c1674ab1d3b7b6418e8ef7ea0760"
+ + "bf3ce69d98a7a3baae5aee488cddfb877972fa88ad05996879d0ce15aca53591"
+ + "423bf1b1b3ff02f823cdbb26bb80e3f7b83c3b7ac01ad7806335f871cd7b7e9e"
+ + "64708c200a9cd092589131aeb7db15655174000cf7db782bd54325ea956a1a15",
+
+ // truncate digest
+ // padding:3030300d06096086480165030402010500041f532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e
+ "888cd9bdece5ceeef0fea92727ef1a1d996960f3f551bf108682f81035903236"
+ + "69ba1ab48becd14a49b87a900434d0ca7670d094b08b2f851834757bef580d2d"
+ + "3278d85b88036ea90d4c2a673dfafeb0c3701332c2b77493110d9b28dade7e98"
+ + "5ec27240c90498372fc00ac8e0e5547e4d59cdd19022b8d961f3b63630b5448d",
+ // padding:3030300d06096086480165030402010500041f2eaabd9574880dbf76b9b8cc00832c20a6ec113d6822
+ // 99550d7a6e0f345e25
+ "762c745262627d0df634d6cce41fb8af3cb855fc2d974b8093b035e9d11e510b"
+ + "9b7e7d61581b8f262fc1c4b8a6da3f6d609512e32f16416c7449c623c1773417"
+ + "032ddf2a559d7eb3af129fd02f83b5e35f5b5c065b1e0bc6481f38b6361f0b01"
+ + "8b5e7166e8e67dddcf1550222f125efde241a27b0e7f670d15346dde082a8c4e",
+
+ // wrong hash in padding
+ // padding:3030300c06082a864886f70d020505000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d6822
+ // 99550d7a6e0f345e25
+ "27830ed405bc9d34009ec6258b766100273b4dcf2a9b3cf6ae31029837c6e24c"
+ + "f6e819734c1fd10c2c23db34d227d98d3498850f083ecd78b648baccfd4647a5"
+ + "72607dedbc2b8ab7a595c0594ece904380e7f395ba4840a81367e99275cde106"
+ + "4fc6f7fbd564c5f26ddd0103991ae8262eaf16623685b43f77ea7a05d080166a",
+ // padding:3031300d060960864801650304020205000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "4cdfa8cd615bcdefa253d75212e4ed0a1fd60841656c6a749690cb0c6c3cd723"
+ + "b518560c3b11a734010acf6e38f0526338351d9b58351826b360c851d3c86429"
+ + "f38eb689e8555aa2a23157e197faebdd29bc49f84c10dacca655cd5fa50fdec8"
+ + "6a72f0ff1c7f8feeec31fee188fbfa72776a7b5cdae1c1506830bd3a00181b13",
+ // padding:3031300d060960864801650304020305000420532eaabd9574880dbf76b9b8cc00832c20a6ec113d68
+ // 2299550d7a6e0f345e25
+ "024746d8dd71ecfe33cf0ad7ab8ddab9dfeb5740ec47b8ddd668f07b8f7610f7"
+ + "26692404ac14c3a1947ff4246fe0a9e216131489125e71df68d60930fac06a20"
+ + "e948a3e4948aff5e3f9772155f8bd6772b1cefd8180ae719afc061e2f0d68a69"
+ + "769930b8d90ca4ecd6c7b20d04f0cc939502e698ad1c500403763c0205f6870d",
+
+ // wrong hash in signature
+ // padding:3020300c06082a864886f70d0205050004100cbc6611f5540bd0809a388dc95a615b
+ "3a152ced8b5e0efa33cd57d4afe67f31ed3b9fb22e7b0ff32795cd9510374fa0"
+ + "9fc63a3366465f83ba4d44e36418a5c1d171b6ca05d8c74a242983d5e5912cd0"
+ + "5bdbd75fcfd5b4eda7cadab21e6dcefca8e2ab7303871ef360beff45564a01bd"
+ + "c887d9e849e407c6aa5b12055647f6c9df49758d1272f7cb476f51088e21f246",
+ // padding:3021300906052b0e03021a05000414640ab2bae07bedc4c163f679a746f7ab7fb5d1fa
+ "3765b8800e6ccf29544d834034e39f8fe7a2e6dfd7e6b4a8f81df091bbfd7aa1"
+ + "7edfa6005024fe04d35c340a2215fd3f1cf4b4dfdd3c8ad09e6df2c2256c7541"
+ + "e19c2e80051d1ef5df5c384bfb6be88c4415eb2740db2d9fb3214890a8a0f191"
+ + "46dfb7897bacc02700a89139dc8fb21b2a7bbfbd43604d7f384cc00aecefb4ef",
+ // padding:3041300d0609608648016503040202050004307b8f4654076b80eb963911f19cfad1aaf4285ed48e82
+ // 6f6cde1b01a79aa73fadb5446e667fc4f90417782c91270540f3
+ "5c5b097c21ac2eb156de39d1eaebe3b96082f54b0171469a94edf7d2027ebfde"
+ + "bc0837f766cfefec577e7b797c7a082df2ecc826e55d39927b01c2da26f8f681"
+ + "4ec993e3b93ee87a3418322b65ac652b3bba6d34373a13fd40b66be489938fad"
+ + "f67bbda762f6ee09a1ddc41382051d4a9a946e0df832bc65b7d5dd58cc5a402b",
+ // padding:3051300d060960864801650304020305000440c6ee9e33cf5c6715a1d148fd73f7318884b41adcb916
+ // 021e2bc0e800a5c5dd97f5142178f6ae88c8fdd98e1afb0ce4c8d2c54b5f37b30b7da1997bb33b0b8a31
+ "0ede4ac9ffcb6d3d42c75cf73303a28ba6089941f68dcf392a75b071f6c149a1"
+ + "09cab95b80a679ca3b29ae44e51c18a2db4c72211ae6b959c7f22e854c45f20f"
+ + "5560446f33be4819f08d981d2fb176d48039ac4acd28127d593f9e219ad40e2a"
+ + "5ee911b334b3b8bb290f2327524e3faae2c028745e03d58882bfe503c4ff04b2",
+
+ // using PKCS#1 encryption padding
+ // padding:0002ff...00<asn wrapped hash>
+ "6c0b3edf5f6e5d3f07057d0b752e89cfdd1c289ad18a0ba94670cd36547734e2"
+ + "c7bb32dd49709f0f7149944c450c23b7f2d360e3602cad5ddff7fd9d711eef6d"
+ + "d4c32e66c4433f041fffefe112024a655bc5bacbd0914bbb2b2a41a91b1293fe"
+ + "9478ddca926a13e6131cc5e9b70625eac1e533ce8171a2dc7b2c4a490e966445",
+ // padding:0002ff...00<hash>
+ "1acce04e348a5c8377c54d8ddd8ec2d8c5cb9b195863c32eb716745f3462b5f2"
+ + "49b612aefb31ba484949d0a0cb5cb8e1f06c1cec58fe5ffff6ba796218c46c3e"
+ + "527c7ab0c4276ccbafd133812faec33721a08542e7e3a34449bebbb28bd0f289"
+ + "94c6801ba5c971991004e31de8f728f6bc37a4ec7b049c1f2dc64d4be9415462",
+
+ // invalid PKCS#1 signature padding
+ // padding:0001ff...ee00
+ "61a4066d0b64964100ecf583325cad10b53912aba1bf3606720d2bdd8e21120b"
+ + "b0b5e4323987d96039819ccce0e5e90854bc0e5c239ab198f75b00355a04e4eb"
+ + "1f855f76697cd65732820575306eb9323954bc5913568a7278fcdeff8e8acad4"
+ + "481e3559f8c44a0be3bc02bae437c3146e4516632b3fe788c3a0e44171155728",
+
+ // PKCS#1 padding too short
+ // padding:000001ff...
+ "979a313677883b0980997f1cb525f43401739945860149dcad80f602df8abed4"
+ + "fd85bcd6e174d9183a5a44008fd77b5a5abcffbcfd4f47ccd2dabef963d9b228"
+ + "310d99000ed0cebbf61438cbe586985bcffb3923a8467a97ae791d0b04925c08"
+ + "94b5a41583d6de72d4369f481f66abce41a577fb128fc0b0aeec746ec089d834",
+
+ // invalid length
+ // padding:2 bytes too long
+ "ab9014dc47d44b6d260fc1fef9ab022042fd9566e9d7b60c54100cb6e1d4edc9"
+ + "8590467d0502c17fce69d00ac5efb40b2cb167d8a44ab93d73c4d0f109fb5a26"
+ + "c2f8823236ff517cf84412e173679cfae42e043b6fec81f9d984b562517e6feb"
+ + "e1f72295dbc3fdfc19d3240aa75515563f31dad83563f3a315acf9a0b351a23f"
+ + "0000",
+ };
+
+ @Test
+ public void testBasic() throws Exception {
+ String algorithm = "SHA256WithRSA";
+ String hashAlgorithm = "SHA-256";
+ String message = "Hello";
+ int keysize = 2048;
+
+ KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+ keyGen.initialize(keysize);
+ KeyPair keyPair = keyGen.generateKeyPair();
+ RSAPublicKey pub = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey priv = (RSAPrivateKey) keyPair.getPrivate();
+
+ byte[] messageBytes = message.getBytes("UTF-8");
+ Signature signer = Signature.getInstance(algorithm);
+ Signature verifier = Signature.getInstance(algorithm);
+ signer.initSign(priv);
+ signer.update(messageBytes);
+ byte[] signature = signer.sign();
+ verifier.initVerify(pub);
+ verifier.update(messageBytes);
+ assertTrue(verifier.verify(signature));
+
+ // Extract some parameters.
+ byte[] rawHash = MessageDigest.getInstance(hashAlgorithm).digest(messageBytes);
+
+ // Print keys and signature, so that it can be used to generate new test vectors.
+ System.out.println("Message:" + message);
+ System.out.println("Hash:" + TestUtil.bytesToHex(rawHash));
+ System.out.println("Public key:");
+ System.out.println("Modulus:" + pub.getModulus().toString());
+ System.out.println("E:" + pub.getPublicExponent().toString());
+ System.out.println("encoded:" + TestUtil.bytesToHex(pub.getEncoded()));
+ System.out.println("Private key:");
+ System.out.println("D:" + priv.getPrivateExponent().toString());
+ System.out.println("encoded:" + TestUtil.bytesToHex(priv.getEncoded()));
+ System.out.println("Signature:" + TestUtil.bytesToHex(signature));
+ }
+
+ /**
+ * Tests an RSA signature implementation with a number of vectors. The test assumes that the first
+ * test vector is valid, but everything else is invalid. Many of the test vectors are derived by
+ * signing modified ASN encodings. Hence accepting an invalid signature does not mean by itself
+ * that the implementation can be broken, but often points to a bigger problem. The test expects
+ * that verifying an invalid signature either leads to a return value False or will result in a
+ * SignatureException. Verifying an RSA signature should not result in an RuntimeException, so
+ * that reasonably implementated applications can be expected to catch and treat invalid
+ * signatures appropriately. While RuntimeExceptions may not be exploitable, they often indicate
+ * an oversight in the implementation of the provider.
+ * https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
+ */
+ private void testVectors(RSAPublicKeySpec key, String algorithm, String[] testvectors)
+ throws Exception {
+ byte[] message = "Test".getBytes("UTF-8");
+ Signature verifier = Signature.getInstance(algorithm);
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ PublicKey pub = kf.generatePublic(key);
+ int errors = 0;
+ boolean first = true;
+ for (String signature : testvectors) {
+ byte[] signatureBytes = TestUtil.hexToBytes(signature);
+ verifier.initVerify(pub);
+ verifier.update(message);
+ boolean verified = false;
+ try {
+ verified = verifier.verify(signatureBytes);
+ } catch (SignatureException ex) {
+ // verify can throw SignatureExceptions if the signature is malformed.
+ }
+ if (first && !verified) {
+ System.out.println("Valid signature not verified:" + signature);
+ errors++;
+ } else if (!first && verified) {
+ System.out.println("Incorrect signature verified:" + signature);
+ errors++;
+ }
+ first = false;
+ }
+ assertEquals(0, errors);
+ }
+
+ /** SunJCE threw an OutOfMemoryError with one of the signatures. */
+ @Test
+ public void testVectorsAll() throws Exception {
+ testVectors(RSA_KEY1, ALGORITHM_KEY1, SIGNATURES_KEY1);
+ }
+
+ /**
+ * Signatures with legacy encoding. Such signatures are sometimes accepted to be compatible with
+ * previously buggy implementations.
+ */
+ static final String[] LEGACY_SIGNATURES_KEY1 = {
+ // A signature where the NULL parameter is missing in the ASN encoding.
+ // padding = 302f300b06096086480165030402010420532eaabd9574880dbf
+ // 76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25
+ "253e1d19bbe91064f2364c1e7db3ba8eb6dc5b19202e440eab6fbdf28c8c6ec0"
+ + "5b812983713c338c72b6e99b8edf506a89ff9fc8e5c2c52362097a56dc228060"
+ + "eca01e1ff318c6c81617691438703411c1f953b21cd74331f87c9b8b189fdffd"
+ + "fe8550bd2bd1d47be915f8604a0f472199dd705e19b1b815f99b68d60bc257c7",
+ };
+
+ /**
+ * Tests legacy signatures. In this context we use the term legacy signatures for signatures that
+ * are not conforming to the PKCS #1 standard, but are sometimes generated by buggy signers. So
+ * far this test considers both accepting and rejecting such signatures as valid behavior.
+ *
+ * <p>Currently we check for just one type of legacy signatures: i.e., a missing NULL parameter in
+ * the ASN encoding of the hash. BouncyCastle and the SunJCE accept this signature, Conscrypt does
+ * not.
+ *
+ * <p>Some references that support accepting this signature:
+ * https://codereview.chromium.org/1690123002/
+ * https://groups.google.com/a/chromium.org/forum/#!topic/chromium-reviews/Jo5S7HtEABI claims that
+ * 7% of the responses in the Online Certificate Status Protocol (OCSP) miss the NULL parameter
+ */
+ @Test
+ public void testLegacySignatures() throws Exception {
+ RSAPublicKeySpec key = RSA_KEY1;
+ String algorithm = ALGORITHM_KEY1;
+ byte[] message = "Test".getBytes("UTF-8");
+ Signature verifier = Signature.getInstance(algorithm);
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ PublicKey pub = kf.generatePublic(key);
+ for (String signature : LEGACY_SIGNATURES_KEY1) {
+ byte[] signatureBytes = TestUtil.hexToBytes(signature);
+ verifier.initVerify(pub);
+ verifier.update(message);
+ boolean verified = false;
+ try {
+ verified = verifier.verify(signatureBytes);
+ } catch (SignatureException ex) {
+ verified = false;
+ }
+ if (verified) {
+ System.out.println("Verfied legacy signature:" + signature);
+ } else {
+ System.out.println("Rejected legacy signature:" + signature);
+ }
+ }
+ }
+
+ /**
+ * Faults during the generation of a signature can leak the information about the private key.
+ * A. K. Lenstra showed in "Memo on RSA signature generation in the presence of faults",
+ * (https://infoscience.epfl.ch/record/164524/files/nscan20.PDF) that PKCS #1 signatures are
+ * especially susceptible to faults when the Chinese Remainder Theorem is used to compute the
+ * signature: one single faulty signature is sufficient to leak the private key.
+ *
+ * One countermeasure that is often used in libraries is to blind the RSA computation and
+ * verify the signature before returning it. Nowadays, libraries are expected to have at least
+ * some countermeasures against faulty computations. In some cases (e.g. OpenSSL) the library
+ * tries to fix a faulty computation by generating a correct signature without using Chinese
+ * remaindering.
+ *
+ * The test here does not induce a fault. Instead it tries to sign with a faulty private key.
+ * The expected outcome of the test is that underlying provider either detects that the fault
+ * or generates a valid signature by ignoring the faulty CRT parameter.
+ *
+ * Since the test only simulates a fault, but does not actually induce a fault it is somewhat
+ * incomplete. It does not detect all vulnerable implementations. The test should nonetheless
+ * detect implementations that include no verification at all.
+ */
+ @Test
+ public void testFaultySigner() throws Exception {
+ BigInteger e = new BigInteger("65537");
+ BigInteger d = new BigInteger(
+ "1491581187972832788084570222215155297353839087630599492610691218"
+ + "6098027383804966741416365668088258821394558334495197493887270311"
+ + "7558637148793177374456685063919969705672268324029058661801838398"
+ + "1099187046803818325657704350675941092582695993374867459573741707"
+ + "2513551423973482044545986645893321692393572214394692273248819124"
+ + "5866638922766330300631727125395012955305761836925591665625409882"
+ + "5987442083465656021724458811783361811914866856391248003733867121"
+ + "5531501554906114868306919889638573670925006068497222709802245970"
+ + "0014474779292382225845722344584808716054088377124806520166137504"
+ + "58797849822813881641713404303944154638273");
+ BigInteger q = new BigInteger(
+ "1327930250247153291239240833779228146841620599139480980326615632"
+ + "6868823273498280322301518048955331731683358443542450740927959439"
+ + "3056349447047388914345605165927201322192706870545643991584573901"
+ + "9099563807204264522234257863225478717589651408831271029849307682"
+ + "13198832542217762257092135384802889866043941823057701");
+ BigInteger p = new BigInteger(
+ "1546732137638443281784728718025150988901748595222448633054370906"
+ + "7724307988669542799529278238746541544956234718616481585427107180"
+ + "6134464028933334724614223213582911567222033332353858049787180486"
+ + "8311341830570208335451999930773903649599388066890163502238099141"
+ + "76306676019969635213034585825883528127235874684082417");
+
+ BigInteger n = p.multiply(q);
+ BigInteger dp = d.mod(p.subtract(BigInteger.ONE));
+ BigInteger dq = d.mod(q.subtract(BigInteger.ONE));
+ BigInteger crt = q.modInverse(p);
+ RSAPrivateCrtKeySpec validKey = new RSAPrivateCrtKeySpec(n, e, d, p, q, dp, dq, crt);
+ RSAPrivateCrtKeySpec invalidKey =
+ new RSAPrivateCrtKeySpec(n, e, d, p, q, dp.add(BigInteger.valueOf(2)), dq, crt);
+ byte[] message = "Test".getBytes("UTF-8");
+ KeyFactory kf = KeyFactory.getInstance("RSA");
+ PrivateKey validPrivKey = kf.generatePrivate(validKey);
+ Signature signer = Signature.getInstance("SHA256WithRSA");
+ signer.initSign(validPrivKey);
+ signer.update(message);
+ byte[] signature = signer.sign();
+ PrivateKey invalidPrivKey = null;
+ try {
+ invalidPrivKey = kf.generatePrivate(invalidKey);
+ } catch (InvalidKeySpecException ex) {
+ // The provider checks the private key and notices a mismatch.
+ // This is a good sign, though of course in this case it means that we can't
+ // check for faults.
+ System.out.println("Provider catches invalid RSA key:" + ex);
+ return;
+ }
+ byte[] invalidSignature = null;
+ try {
+ signer.initSign(invalidPrivKey);
+ signer.update(message);
+ invalidSignature = signer.sign();
+ } catch (Exception ex) {
+ // We do not necessarily expect a checked exception here, since generating
+ // an invalid signature typically indicates a programming error.
+ // Though RuntimeExceptions are fine here.
+ System.out.println("Generating PKCS#1 signature with faulty key throws:" + ex);
+ return;
+ }
+ String signatureHex = TestUtil.bytesToHex(signature);
+ String invalidSignatureHex = TestUtil.bytesToHex(invalidSignature);
+ if (signatureHex.equals(invalidSignatureHex)) {
+ // The provider generated a correct signature. This can for example happen if the provider
+ // does not use the CRT parameters.
+ System.out.println("Signature generation did not use faulty parameter");
+ return;
+ }
+ fail("Generated faulty PKCS #1 signature with faulty parameters"
+ + " valid signature:"
+ + signatureHex
+ + " invalid signature:"
+ + invalidSignatureHex);
+ }
+}