From bc3ca7a56553d4d2e323618560a8c7f4ceeeec13 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Thu, 10 Jul 2014 12:39:06 +0200 Subject: [PATCH] Implement testing of internal certificate issuing (and login with it) --- src/org/cacert/gigi/Certificate.java | 1 - src/org/cacert/gigi/Launcher.java | 3 +- tests/org/cacert/gigi/TestCertificate.java | 130 ++++++++++++++++++ .../cacert/gigi/testUtils/ManagedTest.java | 15 ++ tests/org/cacert/gigi/testUtils/PemKey.java | 26 ++++ util/org/cacert/gigi/util/SimpleSigner.java | 45 +++--- 6 files changed, 198 insertions(+), 22 deletions(-) create mode 100644 tests/org/cacert/gigi/TestCertificate.java create mode 100644 tests/org/cacert/gigi/testUtils/PemKey.java diff --git a/src/org/cacert/gigi/Certificate.java b/src/org/cacert/gigi/Certificate.java index a9d12882..71fb5b72 100644 --- a/src/org/cacert/gigi/Certificate.java +++ b/src/org/cacert/gigi/Certificate.java @@ -123,7 +123,6 @@ public class Certificate { return CertificateStatus.SIGNING; } crtName = rs.getString(1); - System.out.println(crtName); if (rs.getTime(2) != null && rs.getTime(3) == null) { return CertificateStatus.ISSUED; } diff --git a/src/org/cacert/gigi/Launcher.java b/src/org/cacert/gigi/Launcher.java index 6eb5976a..b3c437ed 100644 --- a/src/org/cacert/gigi/Launcher.java +++ b/src/org/cacert/gigi/Launcher.java @@ -73,7 +73,8 @@ public class Launcher { IOException { final SslContextFactory sslContextFactory = generateSSLContextFactory(conf, "www"); final SslContextFactory secureContextFactory = generateSSLContextFactory(conf, "secure"); - secureContextFactory.setNeedClientAuth(true); + secureContextFactory.setWantClientAuth(true); + secureContextFactory.setNeedClientAuth(false); final SslContextFactory staticContextFactory = generateSSLContextFactory(conf, "static"); final SslContextFactory apiContextFactory = generateSSLContextFactory(conf, "api"); try { diff --git a/tests/org/cacert/gigi/TestCertificate.java b/tests/org/cacert/gigi/TestCertificate.java new file mode 100644 index 00000000..42b1c2f3 --- /dev/null +++ b/tests/org/cacert/gigi/TestCertificate.java @@ -0,0 +1,130 @@ +package org.cacert.gigi; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.Socket; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.sql.SQLException; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509KeyManager; + +import org.cacert.gigi.Certificate.CertificateStatus; +import org.cacert.gigi.testUtils.ManagedTest; +import org.cacert.gigi.testUtils.PemKey; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class TestCertificate extends ManagedTest { + @Test + public void testClientCertLoginStates() throws IOException, GeneralSecurityException, SQLException, + InterruptedException { + String[] key1 = generateCSR("/CN=testmail@example.com"); + Certificate c = new Certificate(1, "/CN=testmail@example.com", "sha256", key1[1]); + final PrivateKey pk = PemKey.parsePEMPrivateKey(key1[0]); + c.issue(); + c.waitFor(60000); + final X509Certificate ce = c.cert(); + KeyManager km = new X509KeyManager() { + + @Override + public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) { + return "client"; + } + + @Override + public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) { + return null; + } + + @Override + public X509Certificate[] getCertificateChain(String arg0) { + return new X509Certificate[] { ce }; + } + + @Override + public String[] getClientAliases(String arg0, Principal[] arg1) { + return new String[] { "client" }; + } + + @Override + public PrivateKey getPrivateKey(String arg0) { + if (arg0.equals("client")) { + return pk; + } + return null; + } + + @Override + public String[] getServerAliases(String arg0, Principal[] arg1) { + return new String[] { "client" }; + } + }; + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(new KeyManager[] { km }, null, null); + + HttpURLConnection connection = (HttpURLConnection) new URL("https://" + + getServerName().replaceFirst("^www.", "secure.") + "/login").openConnection(); + if (connection instanceof HttpsURLConnection) { + ((HttpsURLConnection) connection).setSSLSocketFactory(sc.getSocketFactory()); + } + assertEquals(302, connection.getResponseCode()); + assertEquals("https://" + getServerName().replaceFirst("^www.", "secure.") + "/", + connection.getHeaderField("Location")); + } + + @Test + public void testCertLifeCycle() throws IOException, GeneralSecurityException, SQLException, InterruptedException { + String[] key1 = generateCSR("/CN=testmail@example.com"); + Certificate c = new Certificate(1, "/CN=testmail@example.com", "sha256", key1[1]); + testFails(CertificateStatus.DRAFT, c); + c.issue(); + + testFails(CertificateStatus.SIGNING, c); + c.waitFor(60000); + + testFails(CertificateStatus.ISSUED, c); + c.revoke(); + + testFails(CertificateStatus.BEING_REVOKED, c); + c.waitFor(60000); + + testFails(CertificateStatus.REVOKED, c); + + } + + private void testFails(CertificateStatus status, Certificate c) throws IOException, GeneralSecurityException, + SQLException { + if (status != CertificateStatus.ISSUED) { + try { + c.revoke(); + fail("is in invalid state"); + } catch (IllegalStateException ise) { + + } + } + if (status != CertificateStatus.DRAFT) { + try { + c.issue(); + fail("is in invalid state"); + } catch (IllegalStateException ise) { + + } + } + if (status != CertificateStatus.ISSUED) { + try { + c.cert(); + fail("is in invalid state"); + } catch (IllegalStateException ise) { + + } + } + } +} diff --git a/tests/org/cacert/gigi/testUtils/ManagedTest.java b/tests/org/cacert/gigi/testUtils/ManagedTest.java index 5f441d2b..2159a663 100644 --- a/tests/org/cacert/gigi/testUtils/ManagedTest.java +++ b/tests/org/cacert/gigi/testUtils/ManagedTest.java @@ -284,4 +284,19 @@ public class ManagedTest { } return m.group(1); } + + public static String[] generateCSR(String dn) throws IOException { + Process p = Runtime.getRuntime().exec( + new String[] { "openssl", "req", "-newkey", "rsa:1024", "-nodes", "-subj", dn, "-config", + "keys/selfsign.config" }); + String csr = IOUtils.readURL(new InputStreamReader(p.getInputStream())); + + String[] parts = csr.split("(?<=-----)\n(?=-----)"); + if (parts.length != 2) { + System.err.println(IOUtils.readURL(new InputStreamReader(p.getErrorStream()))); + throw new Error(); + } + return parts; + } + } diff --git a/tests/org/cacert/gigi/testUtils/PemKey.java b/tests/org/cacert/gigi/testUtils/PemKey.java new file mode 100644 index 00000000..9f5a27c0 --- /dev/null +++ b/tests/org/cacert/gigi/testUtils/PemKey.java @@ -0,0 +1,26 @@ +package org.cacert.gigi.testUtils; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; + +public class PemKey { + public static PrivateKey parsePEMPrivateKey(String privKeyPEM) throws NoSuchAlgorithmException, + InvalidKeySpecException { + privKeyPEM = privKeyPEM.replace("-----BEGIN PRIVATE KEY-----", "").replace("\n", ""); + // Remove the first and last lines + privKeyPEM = privKeyPEM.replace("-----END PRIVATE KEY-----", ""); + + // Base64 decode the data + byte[] encoded = Base64.getDecoder().decode(privKeyPEM); + + // PKCS8 decode the encoded RSA private key + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); + KeyFactory kf = KeyFactory.getInstance("RSA"); + PrivateKey privKey = kf.generatePrivate(keySpec); + return privKey; + } +} diff --git a/util/org/cacert/gigi/util/SimpleSigner.java b/util/org/cacert/gigi/util/SimpleSigner.java index 2aaa1659..cb4e4928 100644 --- a/util/org/cacert/gigi/util/SimpleSigner.java +++ b/util/org/cacert/gigi/util/SimpleSigner.java @@ -66,30 +66,35 @@ public class SimpleSigner { runner = new Thread() { @Override public void run() { - try { - gencrl(); - } catch (IOException e2) { - e2.printStackTrace(); - } catch (InterruptedException e2) { - e2.printStackTrace(); - } - while (running) { - try { - signCertificates(); - revokeCertificates(); - Thread.sleep(5000); - } catch (IOException e) { - e.printStackTrace(); - } catch (SQLException e) { - e.printStackTrace(); - } catch (InterruptedException e1) { - } - } + work(); } + }; runner.start(); } + private static void work() { + try { + gencrl(); + } catch (IOException e2) { + e2.printStackTrace(); + } catch (InterruptedException e2) { + e2.printStackTrace(); + } + while (running) { + try { + signCertificates(); + revokeCertificates(); + Thread.sleep(5000); + } catch (IOException e) { + e.printStackTrace(); + } catch (SQLException e) { + e.printStackTrace(); + } catch (InterruptedException e1) { + } + } + } + private static void revokeCertificates() throws SQLException, IOException, InterruptedException { ResultSet rs = revoke.executeQuery(); boolean worked = false; @@ -163,7 +168,7 @@ public class SimpleSigner { X509Certificate crtp = (X509Certificate) cf.generateCertificate(is); BigInteger serial = crtp.getSerialNumber(); updateMail.setString(1, crt.getPath()); - updateMail.setString(2, serial.toString()); + updateMail.setString(2, serial.toString(16)); updateMail.setInt(3, id); updateMail.execute(); System.out.println("sign: " + id); -- 2.39.2