From 5b26e5f1a8d7df19beb8abf7401419435ae5b073 Mon Sep 17 00:00:00 2001 From: Benny Baumann Date: Thu, 19 Oct 2017 01:31:23 +0200 Subject: [PATCH] add: public key check testing for ROCA (Return of Coppersmith Attack) vulnerability Check based on code from https://github.com/crocs-muni/roca/blob/master/java/BrokenKey.java Change-Id: I84138914ad944fcc089f50cc8d84dbcd38723ff8 --- debian/copyright | 23 ++++ src/club/wpia/gigi/crypto/key/KeyCheck.java | 3 +- .../wpia/gigi/crypto/key/KeyCheckROCA.java | 123 ++++++++++++++++++ .../gigi/crypto/key/KeyCheckROCATest.java | 122 +++++++++++++++++ 4 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 src/club/wpia/gigi/crypto/key/KeyCheckROCA.java create mode 100644 tests/club/wpia/gigi/crypto/key/KeyCheckROCATest.java diff --git a/debian/copyright b/debian/copyright index 7ccbafbb..3eb3b146 100644 --- a/debian/copyright +++ b/debian/copyright @@ -22,6 +22,10 @@ Files: src/club/wpia/gigi/database Copyright: 2014-2017 WPIA Software Team 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: BSD Copyright (c) 2014, WPIA All rights reserved. @@ -61,3 +65,22 @@ License: AGPL-3.0 . You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + +License: MIT + 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. diff --git a/src/club/wpia/gigi/crypto/key/KeyCheck.java b/src/club/wpia/gigi/crypto/key/KeyCheck.java index d95ad514..4715bc42 100644 --- a/src/club/wpia/gigi/crypto/key/KeyCheck.java +++ b/src/club/wpia/gigi/crypto/key/KeyCheck.java @@ -24,9 +24,10 @@ public abstract class KeyCheck { public static void checkKey(PublicKey key) throws GigiApiException { - if (checks.isEmpty() || checks.size() < 1) { + if (checks.isEmpty() || checks.size() < 2) { // Mandatory checks are registered here register(new KeyCheckSmallFactors()); + register(new KeyCheckROCA()); } if (key == null) { diff --git a/src/club/wpia/gigi/crypto/key/KeyCheckROCA.java b/src/club/wpia/gigi/crypto/key/KeyCheckROCA.java new file mode 100644 index 00000000..e808cdda --- /dev/null +++ b/src/club/wpia/gigi/crypto/key/KeyCheckROCA.java @@ -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 Original ROCA + * vulnerability website + * @see Report by + * ArsTechnica on ROCA + * @see Gist + * by Hanno Böck detailling impacted keys + * @see Java + * port by Martin Paljak + */ +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/tests/club/wpia/gigi/crypto/key/KeyCheckROCATest.java b/tests/club/wpia/gigi/crypto/key/KeyCheckROCATest.java new file mode 100644 index 00000000..3812d030 --- /dev/null +++ b/tests/club/wpia/gigi/crypto/key/KeyCheckROCATest.java @@ -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 + } + + } + +} -- 2.39.2