]> WPIA git - gigi.git/commitdiff
Merge changes Iabf8ec0b,Ia60382fc,I84138914,If8bc2638,Ia7813913
authorBenny Baumann <BenBE1987@gmx.net>
Thu, 2 Nov 2017 23:10:40 +0000 (00:10 +0100)
committerGerrit Code Review <gigi-system@dogcraft.de>
Thu, 2 Nov 2017 23:10:40 +0000 (00:10 +0100)
* changes:
  add: check for keys using acceptable algorithms
  chg: update debian/copyright file based on upstream spec
  add: public key check testing for ROCA (Return of Coppersmith Attack) vulnerability
  add: public key check searching for small primes (less than 10k)
  add: initial class for performing arbitrary checks to validate public keys

debian/copyright
src/club/wpia/gigi/crypto/key/KeyCheck.java [new file with mode: 0644]
src/club/wpia/gigi/crypto/key/KeyCheckPublicKeyFormat.java [new file with mode: 0644]
src/club/wpia/gigi/crypto/key/KeyCheckROCA.java [new file with mode: 0644]
src/club/wpia/gigi/crypto/key/KeyCheckSmallFactors.java [new file with mode: 0644]
src/club/wpia/gigi/pages/account/certs/CertificateRequest.java
tests/club/wpia/gigi/crypto/key/KeyCheckPublicKeyFormatTest.java [new file with mode: 0644]
tests/club/wpia/gigi/crypto/key/KeyCheckROCATest.java [new file with mode: 0644]
tests/club/wpia/gigi/crypto/key/KeyCheckSmallFactorsTest.java [new file with mode: 0644]
tests/club/wpia/gigi/crypto/key/KeyCheckTest.java [new file with mode: 0644]

index 7ccbafbbefb3650ee0ef245ea9afe280f3268aed..50f2e57b96c47bb2947f9773bf1dec92a3efad19 100644 (file)
@@ -1,4 +1,4 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 Upstream-Name: gigi
 Source: <https://code.wpia.club>
 
@@ -22,9 +22,26 @@ Files: src/club/wpia/gigi/database
 Copyright: 2014-2017 WPIA Software Team <software@wpia.club>
 License: AGPL-3.0 or BSD
 
+Files: src/club/wpia/gigi/crypto/key/KeyCheckROCA.java
+Copyright: 2017, CRoCS, EnigmaBridge Ltd.
+License: AGPL-3.0 or MIT
+
+License: AGPL-3.0
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU Affero General Public License for more details.
+ .
+ You should have received a copy of the GNU Affero General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
 License: BSD
- Copyright (c) 2014, WPIA
- All rights reserved.
+ Copyright (c) <year> <copyright holders>
  .
  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions are met:
@@ -48,16 +65,23 @@ License: BSD
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 
  THE POSSIBILITY OF SUCH DAMAGE.
 
-License: AGPL-3.0
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+License: MIT
+ Copyright (c) <year> <copyright holders>
  .
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- GNU Affero General Public License for more details.
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
  .
- You should have received a copy of the GNU Affero General Public License
- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/src/club/wpia/gigi/crypto/key/KeyCheck.java b/src/club/wpia/gigi/crypto/key/KeyCheck.java
new file mode 100644 (file)
index 0000000..b6efd54
--- /dev/null
@@ -0,0 +1,62 @@
+package club.wpia.gigi.crypto.key;
+
+import java.security.PublicKey;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import club.wpia.gigi.GigiApiException;
+
+public abstract class KeyCheck {
+
+    protected static final Set<KeyCheck> checks = new LinkedHashSet<KeyCheck>();
+
+    public static List<KeyCheck> getChecks() {
+        return Collections.list(Collections.enumeration(checks));
+    }
+
+    public static void register(KeyCheck check) {
+        checks.add(check);
+    }
+
+    public abstract void check(PublicKey key) throws GigiApiException;
+
+    public static void checkKey(PublicKey key) throws GigiApiException {
+
+        if (checks.isEmpty() || checks.size() < 3) {
+            // Mandatory checks are registered here
+            register(new KeyCheckPublicKeyFormat());
+            register(new KeyCheckSmallFactors());
+            register(new KeyCheckROCA());
+        }
+
+        if (key == null) {
+            throw new GigiApiException("Failed key sanity check: No key given!");
+        }
+
+        for (KeyCheck kc : checks) {
+            kc.check(key);
+        }
+
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null) {
+            return false;
+        }
+
+        if (o == this) {
+            return true;
+        }
+
+        return getClass().equals(o.getClass());
+    }
+
+    @Override
+    public int hashCode() {
+        return getClass().hashCode();
+    }
+
+}
diff --git a/src/club/wpia/gigi/crypto/key/KeyCheckPublicKeyFormat.java b/src/club/wpia/gigi/crypto/key/KeyCheckPublicKeyFormat.java
new file mode 100644 (file)
index 0000000..c85b16c
--- /dev/null
@@ -0,0 +1,36 @@
+package club.wpia.gigi.crypto.key;
+
+import java.security.PublicKey;
+import java.security.interfaces.DSAPublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAPublicKey;
+
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.output.template.SprintfCommand;
+
+public class KeyCheckPublicKeyFormat extends KeyCheck {
+
+    static {
+        register(new KeyCheckPublicKeyFormat());
+    }
+
+    @Override
+    public void check(PublicKey key) throws GigiApiException {
+
+        if (key instanceof RSAPublicKey) {
+            return;
+        }
+
+        if (key instanceof DSAPublicKey) {
+            return;
+        }
+
+        if (key instanceof ECPublicKey) {
+            return;
+        }
+
+        throw new GigiApiException(SprintfCommand.createSimple("Public Key Format Check: Unknown or unsupported public key algorithm {0}", key.getAlgorithm()));
+
+    }
+
+}
diff --git a/src/club/wpia/gigi/crypto/key/KeyCheckROCA.java b/src/club/wpia/gigi/crypto/key/KeyCheckROCA.java
new file mode 100644 (file)
index 0000000..e808cdd
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017, CRoCS, EnigmaBridge Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+/*
+ * Credits: ported to Java by Martin Paljak
+ */
+/*
+ * Credits: ported to Gigi KeyCheck interface by Benny Baumann
+ */
+
+package club.wpia.gigi.crypto.key;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+
+import club.wpia.gigi.GigiApiException;
+
+/**
+ * Due to a bug in several chips produced by Infineon several cryptographic
+ * processors in smartcards and other HSM appliances produced RSA keys with a
+ * weakness that made them subject for easy factorization. The vulnerability has
+ * been present in Infineon's library from about 2014 through 2017 making keys
+ * up to 2048 bit RSA practically factorable and strongly weakens larger RSA
+ * keys produced with such an implementation. This check implements a
+ * fingerprinting mechanism detecting such keys using a heuristic found by the
+ * researchers describing the vulnerability. Any such keys SHALL NOT be
+ * certified. This implementation is based on the Java port by Martin Paljak.
+ *
+ * @see <a href="https://crocs.fi.muni.cz/public/papers/rsa_ccs17">Original ROCA
+ *      vulnerability website</a>
+ * @see <a href="https://arstechnica.com/?post_type=post&p=1186901">Report by
+ *      ArsTechnica on ROCA</a>
+ * @see <a
+ *      href="https://gist.github.com/hannob/ad37d9e9e3cbf3b89bc0a8fc80cb9475">Gist
+ *      by Hanno Böck detailling impacted keys</a>
+ * @see <a
+ *      href="https://github.com/crocs-muni/roca/blob/master/java/BrokenKey.java">Java
+ *      port by Martin Paljak</a>
+ */
+public class KeyCheckROCA extends KeyCheck {
+
+    /**
+     * List of Lengths of masks for the listed {@link #markers}.
+     */
+    private static final int[] prims = new int[] {
+            11, 13, 17, 19, 37, 53, 61, 71, 73, 79, 97, 103, 107, 109, 127, 151, 157
+    };
+
+    private static final BigInteger[] primes = new BigInteger[prims.length];
+
+    /**
+     * List of markers used to fingerprint keys vulnerable to ROCA.
+     *
+     * This list is reduced according to:
+     * {@link https://groups.google.com/d/msg/mozilla.dev.security.policy/4RqKdD0FeF4/DcxMqchSAQAJ}
+     */
+    private static final BigInteger[] markers = new BigInteger[] {
+            new BigInteger("1026"), // 11:402
+            new BigInteger("5658"), // 13:161a
+            new BigInteger("107286"), // 17:1a316
+            new BigInteger("199410"), // 19:30af2
+            new BigInteger("67109890"), // 37:4000402
+            new BigInteger("5310023542746834"), // 53:12dd703303aed2
+            new BigInteger("1455791217086302986"), // 61:1434026619900b0a
+            new BigInteger("20052041432995567486"), // 71:1164729716b1d977e
+            new BigInteger("6041388139249378920330"), // 73:147811a48004962078a
+            new BigInteger("207530445072488465666"), // 79:b4010404000640502
+            new BigInteger("79228162521181866724264247298"), // 97:1000000006000001800000002
+            new BigInteger("1760368345969468176824550810518"), // 103:16380e9115bd964257768fe396
+            new BigInteger("50079290986288516948354744811034"), // 107:27816ea9821633397be6a897e1a
+            new BigInteger("473022961816146413042658758988474"), // 109:1752639f4e85b003685cbe7192ba
+            new BigInteger("144390480366845522447407333004847678774"), // 127:6ca09850c2813205a04c81430a190536
+            new BigInteger("1800793591454480341970779146165214289059119882"), // 151:50c018bc00482458dac35b1a2412003d18030a
+            new BigInteger("126304807362733370595828809000324029340048915994"), // 157:161fb414d76af63826461899071bd5baca0b7e1a
+    };
+
+    static {
+        for (int i = 0; i < prims.length; i++) {
+            primes[i] = BigInteger.valueOf(prims[i]);
+        }
+
+        register(new KeyCheckROCA());
+    }
+
+    @Override
+    public void check(PublicKey key) throws GigiApiException {
+
+        if ( !(key instanceof RSAPublicKey)) {
+            return;
+        }
+
+        BigInteger modulus = ((RSAPublicKey) key).getModulus();
+
+        for (int i = 0; i < primes.length; i++) {
+            if (BigInteger.ONE.shiftLeft(modulus.remainder(primes[i]).intValue()).and(markers[i]).equals(BigInteger.ZERO)) {
+                return;
+            }
+        }
+
+        throw new GigiApiException("ROCA vulnerability check for public key: Key likely vulnerable as fingerprint matches.");
+
+    }
+
+}
diff --git a/src/club/wpia/gigi/crypto/key/KeyCheckSmallFactors.java b/src/club/wpia/gigi/crypto/key/KeyCheckSmallFactors.java
new file mode 100644 (file)
index 0000000..a096534
--- /dev/null
@@ -0,0 +1,52 @@
+package club.wpia.gigi.crypto.key;
+
+import java.math.BigInteger;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.ArrayList;
+
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.output.template.SprintfCommand;
+
+public class KeyCheckSmallFactors extends KeyCheck {
+
+    private static final long MAX_CHECKED_SMALL_PRIME_BOUNDARY = 10000;
+
+    private static final BigInteger[] primes;
+
+    static {
+        ArrayList<BigInteger> prims = new ArrayList<>(1024);
+
+        NextPrime:
+        for (long i = 2; i < MAX_CHECKED_SMALL_PRIME_BOUNDARY; i++) {
+            for (BigInteger p : prims) {
+                if (BigInteger.ZERO.equals(BigInteger.valueOf(i).mod(p))) {
+                    continue NextPrime;
+                }
+            }
+
+            prims.add(BigInteger.valueOf(i));
+        }
+
+        primes = prims.toArray(new BigInteger[0]);
+
+        register(new KeyCheckSmallFactors());
+    }
+
+    @Override
+    public void check(PublicKey key) throws GigiApiException {
+        if ( !(key instanceof RSAPublicKey)) {
+            return;
+        }
+
+        BigInteger modulus = ((RSAPublicKey) key).getModulus();
+
+        // Check for small prime factors below 10000
+        for (BigInteger n : primes) {
+            if (BigInteger.ZERO.equals(modulus.mod(n))) {
+                throw new GigiApiException(SprintfCommand.createSimple("Small factors check of public key: Key is divisible by {0}.", n.toString()));
+            }
+        }
+    }
+
+}
index 4158e9ba751bce4f06d9f65f9f02045c8d80e31f..8a1bc5943ea8dad8c66ed7a5e360edd83278f13c 100644 (file)
@@ -16,6 +16,7 @@ import java.util.TreeSet;
 
 import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.crypto.SPKAC;
+import club.wpia.gigi.crypto.key.KeyCheck;
 import club.wpia.gigi.dbObjects.Certificate;
 import club.wpia.gigi.dbObjects.Certificate.CSRType;
 import club.wpia.gigi.dbObjects.Certificate.SANType;
@@ -108,7 +109,7 @@ public class CertificateRequest {
         this(c, csr, (CertificateProfile) null);
     }
 
-    public CertificateRequest(AuthorizationContext ctx, String csr, CertificateProfile cp) throws GeneralSecurityException, IOException, IOException {
+    public CertificateRequest(AuthorizationContext ctx, String csr, CertificateProfile cp) throws GeneralSecurityException, IOException, IOException, GigiApiException {
         this.ctx = ctx;
         if (cp != null) {
             profile = cp;
@@ -190,6 +191,8 @@ public class CertificateRequest {
         }
         this.SANs = SANs;
         pk = parsed.getSubjectPublicKeyInfo();
+        KeyCheck.checkKey(pk);
+
         String sign = getSignatureAlgorithm(data);
         guessDigest(sign);
 
@@ -206,12 +209,13 @@ public class CertificateRequest {
             throw new GigiApiException("Challenge mismatch");
         }
         pk = parsed.getPubkey();
+        KeyCheck.checkKey(pk);
+
         String sign = getSignatureAlgorithm(data);
         guessDigest(sign);
         this.SANs = new HashSet<>();
         this.csr = "SPKAC=" + cleanedSPKAC;
         this.csrType = CSRType.SPKAC;
-
     }
 
     private static String getSignatureAlgorithm(byte[] data) throws IOException {
diff --git a/tests/club/wpia/gigi/crypto/key/KeyCheckPublicKeyFormatTest.java b/tests/club/wpia/gigi/crypto/key/KeyCheckPublicKeyFormatTest.java
new file mode 100644 (file)
index 0000000..e8d3ec6
--- /dev/null
@@ -0,0 +1,117 @@
+package club.wpia.gigi.crypto.key;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+
+import org.junit.Test;
+
+import club.wpia.gigi.GigiApiException;
+
+public class KeyCheckPublicKeyFormatTest {
+
+    @Test
+    public void testFormatRSA() throws GeneralSecurityException, IOException {
+
+        // openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048
+        // -pkeyopt rsa_keygen_pubexp:7331 2>/dev/null |
+        // openssl pkey -pubout -outform pem
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQEArcAPmy3RnXdwyFg3V9k1\n" + //
+                "RaFR/peHa3hLsmh25BInRVArbaMctSBaJBVZwQIgBdqjyITQQZP38i6k+WdsETn9\n" + //
+                "J491UDLKU3E3UG60ZS3BzcJllNdpn4g0IZROxmmUz2JlAXkGtIglmWWDx14qHSNj\n" + //
+                "ON58mc3ihfn/oWkPk2hk/csDxGQq5jSaBUwa9THBg9UQHHBqQbhp2nGfa5a5VRlI\n" + //
+                "0QeIy+8GmKlXYMchReUI25ksLOzaqETD0UXiAPyt+vpvkKCDjWGc3kjabn6OkuTt\n" + //
+                "na7N/52qrEC2ImuanYlzR5gv9jkbFF2PiMIEBD+3B0842rLx0X/lbXhRr1MtuHtN\n" + //
+                "tQICHKM=\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckPublicKeyFormat();
+            c.check(pk);
+        } catch (GigiApiException gae) {
+            throw new Error("Valid key (RSA Public Key) rejected.", gae);
+        }
+
+    }
+
+    @Test
+    public void testFormatDSA() throws GeneralSecurityException, IOException {
+
+        // DSA (using OpenSSH)
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBtzCCASsGByqGSM44BAEwggEeAoGBAJpcf099rROPSjbJ5KWk5RF1ngRqXSo7\n" + //
+                "cmKin9QPxIg0tXmxMGVS2sdtXYtmSJ9fewSAx0vHbojysEGY9ASXEoFpzDye4BbK\n" + //
+                "yog9oHaUUEjxkSTwKcipu5BgM9b/nvigw/bs4dlEM+egdzf36lXXXJgvaTeXSpu9\n" + //
+                "gKrKXTSi0jcvAhUAoH2Nbl6mRgAX4l6U5EXeg0zts3MCgYAW16cPIxLzmvrajRVR\n" + //
+                "aIzAWpN1ApE/kx4CbtWZCdNttHu3c8D6qSnVrWpxY6FzrpeFniwg4vu73Ykh3crH\n" + //
+                "0rVa20lrdRUAYGzbgInS+GLoPDGu1LukF0evJYZwyt6qsaFkQ54h4StSK+oM/mOi\n" + //
+                "haLI45Rvlmade3KRQ/7YkV7DZQOBhQACgYEAjVGvOHImKynxgBl+eHeN2Ddqgj1+\n" + //
+                "AKEOFKuFuedG9tKHtZXx04j982kaDnNc5cZY1KPFPYlS7jVJwcFPuf9Hi1/Aqq+3\n" + //
+                "GnqaPl+tJtSpY2Chu8iIHIi5OXiwQC9ImtIEASZkkO+RIPLpzgb3GTn306NtMxae\n" + //
+                "e+mhIx1IrbzMxSA=\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckPublicKeyFormat();
+            c.check(pk);
+        } catch (GigiApiException gae) {
+            throw new Error("Valid key (DSA Public Key) rejected.", gae);
+        }
+
+    }
+
+    @Test
+    public void testFormatECDSA() throws GeneralSecurityException, IOException {
+
+        // ECDSA (secp256r1 / P-256)
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIQeJlVJLBpevYZjGWPPkD6hSrUEI\n" + //
+                "G86i9e2p2QGCanQzNNM8Dkqv5Oa13qjxhRZNo2w+lVOBkAZyAptNKKT5Kw==\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckPublicKeyFormat();
+            c.check(pk);
+        } catch (GigiApiException gae) {
+            throw new Error("Valid key (ECDSA Public Key on P-256) rejected.", gae);
+        }
+
+    }
+
+    @Test
+    public void testFormatGOST() throws GeneralSecurityException, IOException {
+
+        // GOST R 34.10-2001 (256 bits)
+        // https://lib.void.so/an-example-of-using-openssl-gost-engine-in-cc/
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MGMwHAYGKoUDAgITMBIGByqFAwICIwEGByqFAwICHgEDQwAEQDv/qpUxeRWXnyF8\n" + //
+                "YwSUq7qQsL6MtD42GxLxqzLGx3NmpD4rHRay4xgQp91oTtqJjnybsplij0haRq7i\n" + //
+                "Nf7QEdY=\n" + //
+                "-----END PUBLIC KEY-----";
+
+        final PublicKey pk;
+        try {
+            pk = KeyCheckTest.pkFromString(sfk);
+        } catch (GeneralSecurityException gse) {
+            assumeTrue("Could not load the GOST key due to lack of support", false);
+            return;
+        }
+
+        try {
+            KeyCheck c = new KeyCheckPublicKeyFormat();
+            c.check(pk);
+            fail("Unsupported key (GOST Public Key) accepted.");
+        } catch (GigiApiException gae) {
+            // expected
+        }
+
+    }
+
+}
diff --git a/tests/club/wpia/gigi/crypto/key/KeyCheckROCATest.java b/tests/club/wpia/gigi/crypto/key/KeyCheckROCATest.java
new file mode 100644 (file)
index 0000000..3812d03
--- /dev/null
@@ -0,0 +1,122 @@
+package club.wpia.gigi.crypto.key;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+
+import org.junit.Test;
+
+import club.wpia.gigi.GigiApiException;
+
+// Vulnerable keys for this test taken from
+// @link https://misissued.com/batch/28/
+public class KeyCheckROCATest {
+
+    @Test
+    public void testROCASaneKey() throws GeneralSecurityException, IOException {
+
+        // Normal public key generated with OpenSSL:
+        // openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048
+        // -pkeyopt rsa_keygen_pubexp:7331 2>/dev/null |
+        // openssl pkey -pubout -outform pem
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQEArcAPmy3RnXdwyFg3V9k1\n" + //
+                "RaFR/peHa3hLsmh25BInRVArbaMctSBaJBVZwQIgBdqjyITQQZP38i6k+WdsETn9\n" + //
+                "J491UDLKU3E3UG60ZS3BzcJllNdpn4g0IZROxmmUz2JlAXkGtIglmWWDx14qHSNj\n" + //
+                "ON58mc3ihfn/oWkPk2hk/csDxGQq5jSaBUwa9THBg9UQHHBqQbhp2nGfa5a5VRlI\n" + //
+                "0QeIy+8GmKlXYMchReUI25ksLOzaqETD0UXiAPyt+vpvkKCDjWGc3kjabn6OkuTt\n" + //
+                "na7N/52qrEC2ImuanYlzR5gv9jkbFF2PiMIEBD+3B0842rLx0X/lbXhRr1MtuHtN\n" + //
+                "tQICHKM=\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckROCA();
+            c.check(pk);
+        } catch (GigiApiException gae) {
+            throw new Error("Valid key (not vulnerable to ROCA vulnerability) rejected.", gae);
+        }
+
+    }
+
+    @Test
+    public void testROCAVulnerable1() throws GeneralSecurityException, IOException {
+
+        // D-TRUST Qualified Root CA 1 2014:PN
+        // https://crt.sh/?id=26311918&opt=cablint
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBJDANBgkqhkiG9w0BAQEFAAOCAREAMIIBDAKCAQEAlT2Gi8cR+hX+0iYaYH0e\n" + //
+                "Pmxrqq1tNKlvcesp1wwIeixqeQ2/QJkFMEAVq3hX45Cri7Z/p9ch8+Nd7eva80Ym\n" + //
+                "nn0llfQ2kJDhi1fOTfodR7IN24105y5D6Lf3zre6J2FOxqPH/q0dDJAbTbuaO4kS\n" + //
+                "yI9xUEhvHo8oZ0L3SGq6VyeeOBXDoBg4xp6xp1w6cZ76/3HhuBc26sgoO9AvDRzp\n" + //
+                "M74wvzGBSVaA8+SU1O46plY4os4GlHEdcZM/0NcHeiWwJvycPKkurVL9AxDBq9Iw\n" + //
+                "Dox/+zQzxcS7txvrJeI1ahQwPpzYdJEwFQ6/rCt43KALWt+OoAIvW5TVYllaF62Z\n" + //
+                "XwIFAJLK1sU=\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckROCA();
+            c.check(pk);
+            fail("Invalid key (ROCA vulnerable) accepted.");
+        } catch (GigiApiException gae) {
+            // expected
+        }
+
+    }
+
+    @Test
+    public void testROCAVulnerable2() throws GeneralSecurityException, IOException {
+
+        // D-TRUST Qualified Root CA 2 2014:PN
+        // https://crt.sh/?id=26310640&opt=cablint
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBJDANBgkqhkiG9w0BAQEFAAOCAREAMIIBDAKCAQEAmDbSRazHfc1YoqH6dXWz\n" + //
+                "k2zBJadliqHgpft1Z5HqXF6AzXQ8duHLN3Db+SSDUWP+fDv1Ti69wmH5HqrdSGcl\n" + //
+                "EvoNStTRjFpnzj/7c5AkALWeZlRzcrBjeIFTtSdZvgluA14BnQXmRViC3tgOFMyU\n" + //
+                "I72wqCGuf7Y8cW/DSfSzBWFTO+A9uoj0oMKEaaLd1iVF4mctKf/atrHzy3Ny1/d9\n" + //
+                "WgbLLxiGtrNxVh78j9HCS4rs17AEC3OZnosUE3jCzLCHyQjwI+frkmINj5Qy4L3j\n" + //
+                "GJqxtIBBb9LwaCkkuV3g679/V4BhWKpDt6YIo/YYINRu42GhXSB9x13KhSMGe9vn\n" + //
+                "eQIFAKY6EqM=\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckROCA();
+            c.check(pk);
+            fail("Invalid key (ROCA vulnerable) accepted.");
+        } catch (GigiApiException gae) {
+            // expected
+        }
+
+    }
+
+    @Test
+    public void testROCAVulnerable3() throws GeneralSecurityException, IOException {
+
+        // D-TRUST Qualified Root CA 3 2014:PN
+        // https://crt.sh/?id=26310642&opt=cablint
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBJDANBgkqhkiG9w0BAQEFAAOCAREAMIIBDAKCAQEAlpwnRwC1ogIM/Wywu3ys\n" + //
+                "HhREKeT56eDAMO+68dvz/mWL7dzFhIFHdehRpSpICx06tb7YpK6/XX9/0okTKajt\n" + //
+                "K0paM3mqZWNilpZnCzItFjwYjxKZL8Bgxww0ztqGD/2oHtmviZNO6yeaLYmm2Eqv\n" + //
+                "hXCVPUCcE17BPjybSZaW3ULaTiIQFYcCB5/utyXu3RT8ss2NBNoD9D4S5r3dMMJY\n" + //
+                "qUE/oojbg/4Y955M0S+yEUuv2dfbE+BCkZqgM05yk/wNr9L8F2f7cG2h/qjFUBE5\n" + //
+                "91kZXZ0g3lBhbKx9SUM8/Vq3WMmfDDpV2qk9wXC0sMgVAwTYLN1J3LWow/C+4Ffo\n" + //
+                "xQIFAI0kKjs=\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckROCA();
+            c.check(pk);
+            fail("Invalid key (ROCA vulnerable) accepted.");
+        } catch (GigiApiException gae) {
+            // expected
+        }
+
+    }
+
+}
diff --git a/tests/club/wpia/gigi/crypto/key/KeyCheckSmallFactorsTest.java b/tests/club/wpia/gigi/crypto/key/KeyCheckSmallFactorsTest.java
new file mode 100644 (file)
index 0000000..2d5e4cb
--- /dev/null
@@ -0,0 +1,67 @@
+package club.wpia.gigi.crypto.key;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+
+import org.junit.Test;
+
+import club.wpia.gigi.GigiApiException;
+
+public class KeyCheckSmallFactorsTest {
+
+    @Test
+    public void testSmallPrimesSaneKey() throws GeneralSecurityException, IOException {
+
+        // Normal public key generated with OpenSSL:
+        // openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048
+        // -pkeyopt rsa_keygen_pubexp:7331 2>/dev/null |
+        // openssl pkey -pubout -outform pem
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQEArcAPmy3RnXdwyFg3V9k1\n" + //
+                "RaFR/peHa3hLsmh25BInRVArbaMctSBaJBVZwQIgBdqjyITQQZP38i6k+WdsETn9\n" + //
+                "J491UDLKU3E3UG60ZS3BzcJllNdpn4g0IZROxmmUz2JlAXkGtIglmWWDx14qHSNj\n" + //
+                "ON58mc3ihfn/oWkPk2hk/csDxGQq5jSaBUwa9THBg9UQHHBqQbhp2nGfa5a5VRlI\n" + //
+                "0QeIy+8GmKlXYMchReUI25ksLOzaqETD0UXiAPyt+vpvkKCDjWGc3kjabn6OkuTt\n" + //
+                "na7N/52qrEC2ImuanYlzR5gv9jkbFF2PiMIEBD+3B0842rLx0X/lbXhRr1MtuHtN\n" + //
+                "tQICHKM=\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckSmallFactors();
+            c.check(pk);
+        } catch (GigiApiException gae) {
+            throw new Error("Valid key (regarding small factors) rejected.", gae);
+        }
+
+    }
+
+    @Test
+    public void testSmallPrimes() throws GeneralSecurityException, IOException {
+
+        // The following key is the above one multiplied by 7331.
+        String sfk = "-----BEGIN PUBLIC KEY-----\n" + //
+                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQITb6L+6NEZsFNiuTY42LEg\n" + //
+                "iPqvDa1K+pXftgWEpTPalebLpKX/Ft11V09pQh/bB6QgNzNXxfBVXE2+UhyrsU+c\n" + //
+                "g+Esd55384MjBFI37W1U50Xi9VS1s3ls3ZoL2+GAbs6yeSzLA9bMt8YMtj2QAGxi\n" + //
+                "ZYtKKHLd4qYja0OZCkaED8ys4QB4flRWpbJn+4/Yoj5sXmcy2AP/SoPIRf09T/MQ\n" + //
+                "OerCZ/3p5blhOGZt1I3MqJNcCoK5oKkzkeQ3AkPqOjGo2qSXObJPMYBHKjIs2JHA\n" + //
+                "ioTVHwAOgsEfu69srVcgOzsleAVSeDNFWUv5BayWVlGpHtJi4mGHDdyLL7r2SfMC\n" + //
+                "Rj8CAhyj\n" + //
+                "-----END PUBLIC KEY-----\n";
+
+        PublicKey pk = KeyCheckTest.pkFromString(sfk);
+        try {
+            KeyCheck c = new KeyCheckSmallFactors();
+            c.check(pk);
+            fail("Invalid key (containing small factors) accepted.");
+        } catch (GigiApiException gae) {
+            // expected
+        }
+
+    }
+
+}
diff --git a/tests/club/wpia/gigi/crypto/key/KeyCheckTest.java b/tests/club/wpia/gigi/crypto/key/KeyCheckTest.java
new file mode 100644 (file)
index 0000000..aaca8b8
--- /dev/null
@@ -0,0 +1,39 @@
+package club.wpia.gigi.crypto.key;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.PublicKey;
+
+import org.junit.Test;
+
+import sun.security.util.DerValue;
+import sun.security.x509.X509Key;
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.util.PEM;
+
+public class KeyCheckTest {
+
+    public static PublicKey pkFromString(String pub) throws GeneralSecurityException, IOException {
+        byte[] data = PEM.decode("PUBLIC KEY", pub);
+        DerValue der = new DerValue(data);
+        PublicKey key = X509Key.parse(der);
+
+        return key;
+    }
+
+    @Test
+    public void testNullKey() {
+        try {
+            KeyCheck.checkKey(null);
+            fail("Providing a null key should fail!");
+        } catch (GigiApiException gae) {
+            // Expected failure
+        }
+
+        // Check that at least one key check has been loaded
+        assertFalse(KeyCheck.getChecks().isEmpty());
+    }
+
+}