From 1f9ad737be12e85277c4623917e9ff99a2b5e58b Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Tue, 20 Feb 2024 17:20:24 -0800 Subject: [PATCH 1/3] update cert reading to use bouncycastle --- cadc-util/build.gradle | 6 +- .../java/ca/nrc/cadc/auth/SSLUtilTest.java | 116 +++++---- .../java/ca/nrc/cadc/auth/CertCmdArgUtil.java | 43 +--- .../main/java/ca/nrc/cadc/auth/SSLUtil.java | 230 ++++++++---------- 4 files changed, 175 insertions(+), 220 deletions(-) diff --git a/cadc-util/build.gradle b/cadc-util/build.gradle index 04e27d08..f085877f 100644 --- a/cadc-util/build.gradle +++ b/cadc-util/build.gradle @@ -16,7 +16,7 @@ sourceCompatibility = 1.8 group = 'org.opencadc' -version = '1.10.6' +version = '1.11.0' description = 'OpenCADC core utility library' def git_url = 'https://github.com/opencadc/core' @@ -33,7 +33,9 @@ dependencies { compile 'org.apache.logging.log4j:log4j-core:2.17.2' compile 'org.apache.logging.log4j:log4j:2.17.2' - compile 'org.bouncycastle:bcprov-jdk15on:1.46' + //compile 'org.bouncycastle:bcprov-jdk15on:1.46' + compile 'org.bouncycastle:bcprov-jdk18on:[1.70,2.0)' + compile 'org.bouncycastle:bcpkix-jdk18on:[1.70,2.0)' compile 'javax.servlet:javax.servlet-api:3.1.0' compile 'org.json:json:20231013' compile 'xerces:xercesImpl:[2.12.2,)' diff --git a/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java b/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java index d9ba3d35..746a0609 100644 --- a/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java +++ b/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2016. (c) 2016. + * (c) 2024. (c) 2024. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -81,6 +81,7 @@ import java.security.cert.CertificateNotYetValidException; import java.util.Calendar; import java.util.GregorianCalendar; +import java.util.Set; import javax.net.SocketFactory; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; @@ -171,7 +172,7 @@ public static void setUpBeforeClass() throws Exception Log4jInit.setLevel("ca.nrc.cadc.auth", Level.INFO); SSL_PEM = FileUtil.getFileFromResource(TEST_PEM_FN, SSLUtilTest.class); } - + @Test public void testReadPem() throws Exception { @@ -225,6 +226,72 @@ public void testGetSocketFactoryFromFile() throws Exception Assert.fail("unexpected exception: " + t); } } + + @Test + public void testReadCert() { + try { + File f = new File(System.getProperty("user.home") + "/.ssl/" + System.getProperty("user.name") + ".pem"); + log.info("in: " + f.getAbsolutePath()); + + Subject s = SSLUtil.createSubject(f); + log.info("created: " + s); + Assert.assertFalse(s.getPrincipals().isEmpty()); + + Set cs = s.getPublicCredentials(X509CertificateChain.class); + Assert.assertFalse("chain", cs.isEmpty()); + X509CertificateChain chain = cs.iterator().next(); + Assert.assertNotNull(chain.getChain()); + Assert.assertEquals(1, chain.getChain().length); + Assert.assertNotNull(chain.getPrivateKey()); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testReadUserProxyCert() { + try { + File f = new File(System.getProperty("user.home") + "/.ssl/cadcproxy.pem"); + log.info("in: " + f.getAbsolutePath()); + + Subject s = SSLUtil.createSubject(f); + log.info("created: " + s); + Assert.assertFalse(s.getPrincipals().isEmpty()); + + Set cs = s.getPublicCredentials(X509CertificateChain.class); + Assert.assertFalse("chain", cs.isEmpty()); + X509CertificateChain chain = cs.iterator().next(); + Assert.assertNotNull(chain.getChain()); + Assert.assertEquals(2, chain.getChain().length); + Assert.assertNotNull(chain.getPrivateKey()); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } + + @Test + public void testReadProxyCert() { + try { + File f = SSL_PEM; + log.info("in: " + f.getAbsolutePath()); + + Subject s = SSLUtil.createSubject(f); + log.info("created: " + s); + Assert.assertFalse(s.getPrincipals().isEmpty()); + + Set cs = s.getPublicCredentials(X509CertificateChain.class); + Assert.assertFalse("chain", cs.isEmpty()); + X509CertificateChain chain = cs.iterator().next(); + Assert.assertNotNull(chain.getChain()); + Assert.assertEquals(2, chain.getChain().length); + Assert.assertNotNull(chain.getPrivateKey()); + } catch (Exception unexpected) { + log.error("unexpected exception", unexpected); + Assert.fail("unexpected exception: " + unexpected); + } + } @Test public void testInitSSL() throws Exception @@ -287,51 +354,6 @@ public void testCadcHTTPS() throws Exception } } - @Test - public void testPrivateKeyParser() throws Exception - { - // tests the parser with different size keys - // 512 bit - byte[] privateKey = SSLUtil.getPrivateKey(KEY_512.getBytes()); - try - { - log.debug("test parsing of RSA 512 bit key: "); - SSLUtil.parseKeySpec(privateKey); - } - catch (Throwable t) - { - t.printStackTrace(); - Assert.fail("unexpected exception: " + t); - } - - // 1024 bit - privateKey = SSLUtil.getPrivateKey(KEY_1024.getBytes()); - try - { - log.debug("test parsing of RSA 1024 bit key: "); - SSLUtil.parseKeySpec(privateKey); - } - catch (Throwable t) - { - t.printStackTrace(); - Assert.fail("unexpected exception: " + t); - } - - // 2048 bit - privateKey = SSLUtil.getPrivateKey(KEY_2048.getBytes()); - try - { - log.debug("test parsing of RSA 2048 bit key: "); - SSLUtil.parseKeySpec(privateKey); - } - catch (Throwable t) - { - t.printStackTrace(); - Assert.fail("unexpected exception: " + t); - } - - } - @Test public void testValidSubject() throws Exception { diff --git a/cadc-util/src/main/java/ca/nrc/cadc/auth/CertCmdArgUtil.java b/cadc-util/src/main/java/ca/nrc/cadc/auth/CertCmdArgUtil.java index be7c0f33..b8979fec 100644 --- a/cadc-util/src/main/java/ca/nrc/cadc/auth/CertCmdArgUtil.java +++ b/cadc-util/src/main/java/ca/nrc/cadc/auth/CertCmdArgUtil.java @@ -132,16 +132,6 @@ private static Subject initSubjectByPem(String fnPem, boolean nullOnNotFound) { return SSLUtil.createSubject(certKeyFile); } - private static Subject initSubjectByCertKey(String fnCert, String fnKey, boolean nullOnNotFound) { - File certFile = loadFile(fnCert, nullOnNotFound); - File keyFile = loadFile(fnKey, nullOnNotFound); - if (nullOnNotFound && certFile == null && keyFile == null) { - return null; - } - - return SSLUtil.createSubject(certFile, keyFile); - } - /** * Init a subject from the command line and throw an exception if not * successful. @@ -181,37 +171,8 @@ public static Subject initSubject(ArgumentMap argMap, boolean returnNullOnNotFou Subject subject = null; if (argMap.isSet(ARG_CERT)) { - if (argMap.isSet(ARG_KEY)) { - // load from cert/key - strCert = argMap.getValue(ARG_CERT); - strKey = argMap.getValue(ARG_KEY); - subject = initSubjectByCertKey(strCert, strKey, false); - } else { - // load from cert pem - strCertKey = argMap.getValue(ARG_CERT); - subject = initSubjectByPem(strCertKey, false); - } - } else { - // load from default - strCertKey = userHome + DFT_CERTKEY_FILE; - strCert = userHome + DFT_CERT_FILE; - strKey = userHome + DFT_KEY_FILE; - try { - subject = initSubjectByPem(strCertKey, returnNullOnNotFound); - } catch (RuntimeException ex1) { - - // Default PEM file not exists or is not readable - if (subject == null) { - try { - subject = initSubjectByCertKey(strCert, strKey, returnNullOnNotFound); - } catch (RuntimeException ex2) { - if (!returnNullOnNotFound) { - throw new RuntimeException("Could not find valid certificate files at " + strCertKey - + " or " + strCert + "," + strKey, ex2); - } - } - } - } + strCertKey = argMap.getValue(ARG_CERT); + subject = initSubjectByPem(strCertKey, false); } return subject; } diff --git a/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java b/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java index ed9d8f52..0ee44cea 100644 --- a/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java +++ b/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java @@ -3,7 +3,7 @@ ******************* CANADIAN ASTRONOMY DATA CENTRE ******************* ************** CENTRE CANADIEN DE DONNÉES ASTRONOMIQUES ************** * - * (c) 2016. (c) 2016. + * (c) 2024. (c) 2024. * Government of Canada Gouvernement du Canada * National Research Council Conseil national de recherches * Ottawa, Canada, K1A 0R6 Ottawa, Canada, K1A 0R6 @@ -71,17 +71,16 @@ import ca.nrc.cadc.util.Base64; import ca.nrc.cadc.util.FileUtil; - import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyManagementException; import java.security.KeyStore; @@ -89,6 +88,7 @@ import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; +import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -98,14 +98,12 @@ import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.RSAPrivateCrtKeySpec; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Set; - import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -116,8 +114,14 @@ import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; import javax.security.auth.Subject; - import org.apache.log4j.Logger; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.util.io.pem.PemObject; /** * Utility class to setup SSL before trying to use HTTPS. @@ -126,6 +130,10 @@ */ public class SSLUtil { private static Logger log = Logger.getLogger(SSLUtil.class); + + static { + Security.addProvider(new BouncyCastleProvider()); + } // SSL, SSLv2mm SSLv3, TLS, TLSv1, TLSv1.1 private static final String SSL_PROTOCOL = "TLS"; @@ -140,51 +148,9 @@ public class SSLUtil { private static final char[] THE_PASSWORD = CERT_ALIAS.toCharArray(); - /** - * Initialise the default SSL socket factory so that all HTTPS connections use - * the provided key store to authenticate (when the server requires client - * authentication). - * - * @see HttpsURLConnection#setDefaultSSLSocketFactory(javax.net.ssl.SSLSocketFactory) - * @param certFile proxy certificate - * @param keyFile private key file in DER format - */ - public static void initSSL(File certFile, File keyFile) { - SSLSocketFactory sf = getSocketFactory(certFile, keyFile); - HttpsURLConnection.setDefaultSSLSocketFactory(sf); - } - public static void initSSL(File pemFile) { - try { - X509CertificateChain chain = readPemCertificateAndKey(pemFile); - SSLSocketFactory sf = getSocketFactory(chain); - HttpsURLConnection.setDefaultSSLSocketFactory(sf); - } catch (InvalidKeySpecException ex) { - throw new RuntimeException("failed to read RSA private key from " + pemFile, ex); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException("BUG: failed to create empty KeyStore", ex); - } catch (FileNotFoundException ex) { - throw new RuntimeException("failed to find certificate and/or key file " + pemFile, ex); - } catch (IOException ex) { - throw new RuntimeException("failed to read certificate file " + pemFile, ex); - } catch (CertificateException ex) { - throw new RuntimeException("failed to load certificate from file " + pemFile, ex); - } - } - - /** - * Initialise the default SSL socket factory so that all HTTPS connections use - * the provided key store to authenticate (when the server requies client - * authentication). - * - * @param certFile proxy certificate - * @param keyFile private key file in DER format - * @return configured SSL socket factory - */ - public static SSLSocketFactory getSocketFactory(File certFile, File keyFile) { - KeyStore ks = getKeyStore(certFile, keyFile); - KeyStore ts = null; - return getSocketFactory(ks, ts); + SSLSocketFactory sf = getSocketFactory(pemFile); + HttpsURLConnection.setDefaultSSLSocketFactory(sf); } /** @@ -210,7 +176,7 @@ public static SSLSocketFactory getSocketFactory(File pemFile) { } return getSocketFactory(chain); } - + /** * Create an SSLSocketfactory from the credentials in the specified Subject. * This method extracts a X509CertificateChain from the public credentials and @@ -249,7 +215,8 @@ public static SSLSocketFactory getSocketFactory(X509CertificateChain chain) { } // may in future try to support other KeyStore formats - static SSLSocketFactory getSocketFactory(KeyStore keyStore, KeyStore trustStore) { + @Deprecated + private static SSLSocketFactory getSocketFactory(KeyStore keyStore, KeyStore trustStore) { KeyManagerFactory kmf = getKeyManagerFactory(keyStore); TrustManagerFactory tmf = getTrustManagerFactory(trustStore); SSLContext ctx = getContext(kmf, tmf, keyStore); @@ -257,24 +224,6 @@ static SSLSocketFactory getSocketFactory(KeyStore keyStore, KeyStore trustStore) return sf; } - public static Subject createSubject(File certFile, File keyFile) { - try { - PrivateKey pk = readPrivateKey(keyFile); - X509Certificate[] chain = readCertificateChain(certFile); - return AuthenticationUtil.getSubject(chain, pk); - } catch (InvalidKeySpecException ex) { - throw new RuntimeException("failed to read RSA private key from " + keyFile, ex); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException("BUG: failed to create empty KeyStore", ex); - } catch (FileNotFoundException ex) { - throw new RuntimeException("failed to find certificate and/or key file " + certFile + "," + keyFile, ex); - } catch (IOException ex) { - throw new RuntimeException("failed to read certificate file " + certFile, ex); - } catch (CertificateException ex) { - throw new RuntimeException("failed to load certificate from file " + certFile, ex); - } - } - public static Subject createSubject(File certKeyFile) { try { X509CertificateChain certKey = readPemCertificateAndKey(certKeyFile); @@ -290,15 +239,17 @@ public static Subject createSubject(File certKeyFile) { } } - static byte[] getPrivateKey(byte[] certBuf) throws IOException { + @Deprecated + private static byte[] getPrivateKey(byte[] certBuf) throws IOException { BufferedReader rdr = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(certBuf))); String line = rdr.readLine(); StringBuilder base64 = new StringBuilder(); while (line != null) { - if (line.startsWith("-----BEGIN RSA PRIVATE KEY-")) { + if (line.matches("-----BEGIN.*PRIVATE KEY-----")) { // log.debug(line); line = rdr.readLine(); - while (line != null && !line.startsWith("-----END RSA PRIVATE KEY-")) { + while (line != null && !line.startsWith("-----END ")) { + // log.debug(line + " (" + line.length() + ")"); base64.append(line.trim()); line = rdr.readLine(); @@ -387,7 +338,6 @@ public static byte[] getCertificates(byte[] certBuf) throws IOException { return result; } - @SuppressWarnings("unchecked") public static X509Certificate[] readCertificateChain(File certFile) throws CertificateException, IOException { try { X509Certificate[] chain = readCertificateChain(FileUtil.readFile(certFile)); @@ -399,12 +349,6 @@ public static X509Certificate[] readCertificateChain(File certFile) throws Certi } - /** - * @param certBuf - * @return certificate chain - * @throws CertificateException - * @throws IOException - */ public static X509Certificate[] readCertificateChain(byte[] certBuf) throws CertificateException, IOException { BufferedInputStream istream = new BufferedInputStream(new ByteArrayInputStream(certBuf)); CertificateFactory cf = CertificateFactory.getInstance("X.509"); @@ -435,13 +379,14 @@ public static X509Certificate[] readCertificateChain(byte[] certBuf) throws Cert return chain; } - public static PrivateKey readPrivateKey(File keyFile) + @Deprecated + private static PrivateKey readPrivateKey(File keyFile) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { byte[] priv = FileUtil.readFile(keyFile); return readPrivateKey(priv); } - public static PrivateKey readPrivateKey(byte[] bytesPrivateKey) + private static PrivateKey readPrivateKey(byte[] bytesPrivateKey) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { KeyFactory kf = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytesPrivateKey); @@ -472,25 +417,8 @@ private static KeyStore getKeyStore(Certificate[] chain, PrivateKey pk) { } } - private static KeyStore getKeyStore(File certFile, File keyFile) { - try { - PrivateKey pk = readPrivateKey(keyFile); - Certificate[] chain = readCertificateChain(certFile); - return getKeyStore(chain, pk); - } catch (InvalidKeySpecException ex) { - throw new RuntimeException("failed to read RSA private key from " + keyFile, ex); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException("BUG: failed to create empty KeyStore", ex); - } catch (FileNotFoundException ex) { - throw new RuntimeException("failed to find certificate and/or key file " + certFile + "," + keyFile, ex); - } catch (IOException ex) { - throw new RuntimeException("failed to read certificate file " + certFile, ex); - } catch (CertificateException ex) { - throw new RuntimeException("failed to load certificate from file " + certFile, ex); - } - } - // currently broken trying to parse the openssl-generated pkcs12 file + @Deprecated private static KeyStore readPKCS12(File f) { InputStream istream = null; try { @@ -600,8 +528,8 @@ private static void printKeyStoreInfo(KeyStore keystore) throws KeyStoreExceptio */ public static X509CertificateChain readPemCertificateAndKey(File pemFile) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, CertificateException { - byte[] data = FileUtil.readFile(pemFile); - return readPemCertificateAndKey(data); + PEMParser parser = new PEMParser(new FileReader(pemFile)); + return readPEM(parser); } /** @@ -618,35 +546,73 @@ public static X509CertificateChain readPemCertificateAndKey(File pemFile) */ public static X509CertificateChain readPemCertificateAndKey(byte[] data) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, CertificateException { - // Currently only RSA keys are supported. If the need to support - // other encoding algorithms arises in the future, then the - // PEMReader in the bouncycastle package should be a good - // candidate for the job. To use this class without creating a - // dependency on the bc package, the implementation of this method - // can be change to look for the bc PEMReader in the classpath and - // use it if present, otherwise default to the RSA implementation - // below. Clients that want to use other encoding schemas will - // have to pass the PEMReader class into the class path themselves. - - byte[] key = getPrivateKey(data); - - KeyFactory kf = KeyFactory.getInstance("RSA"); - RSAPrivateCrtKeySpec spec = parseKeySpec(key); - PrivateKey pk = kf.generatePrivate(spec); - - byte[] certificates = getCertificates(data); - X509Certificate[] chain = readCertificateChain(certificates); + InputStreamReader r = new InputStreamReader(new ByteArrayInputStream(data)); + PEMParser parser = new PEMParser(r); + return readPEM(parser); + } + + private static X509CertificateChain readPEM(PEMParser parser) + throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, CertificateException { + + PrivateKey privateKey = null; + List certs = new ArrayList<>(); + int byteSize = 0; - return new X509CertificateChain(chain, pk); + Object obj = parser.readObject(); + while (obj != null) { + log.debug("found: " + obj.getClass().getName()); + // if private key first: PEMKeyPair followed by PemObject(s) with type=certificate + // if cert/key/cert...: X509CertificateHolder followed by PemObject(s) + if (obj instanceof PEMKeyPair) { + PEMKeyPair pkp = (PEMKeyPair) obj; + PrivateKeyInfo pki = pkp.getPrivateKeyInfo(); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + privateKey = converter.getPrivateKey(pki); + log.debug(" private key: " + privateKey.getEncoded().length); + } else if (obj instanceof PrivateKeyInfo) { + PrivateKeyInfo pki = (PrivateKeyInfo) obj; + JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); + privateKey = converter.getPrivateKey(pki); + log.debug(" private key: " + privateKey.getEncoded().length); + } else if (obj instanceof X509CertificateHolder) { + X509CertificateHolder xch = (X509CertificateHolder) obj; + byte[] bytes = xch.toASN1Structure().getEncoded("DER"); + certs.add(bytes); + byteSize += bytes.length; + log.debug(" certificate: " + bytes.length); + } else if (obj instanceof PemObject) { + PemObject po = (PemObject) obj; + if ("certificate".equalsIgnoreCase(po.getType())) { + certs.add(po.getContent()); + byteSize += po.getContent().length; + log.debug(" certificate: " + po.getContent().length); + } else if ("rsa private key".equalsIgnoreCase(po.getType())) { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(po.getContent()); + privateKey = keyFactory.generatePrivate(keySpec); + log.debug(" private key: " + po.getContent().length); + } else { + log.warn("readPEM: unexpected PemObject type: " + po.getType() + " aka " + obj.getClass().getName()); + } + } + + obj = parser.readPemObject(); + } + + // flatten out the certificate bytes into one byte[] + byte[] flat = new byte[byteSize]; + byteSize = 0; + for (byte[] cert : certs) { + System.arraycopy(cert, 0, flat, byteSize, cert.length); + byteSize += cert.length; + } + X509Certificate[] chain = readCertificateChain(flat); + + return new X509CertificateChain(chain, privateKey); } + - /** - * Parses a byte array and constructs the corresponding RSAPrivateCrtKeySpec. - * - * @param code byte array containing the key - * @return RSAPrivateCrtKeySpec - * @throws IOException - */ + /* public static RSAPrivateCrtKeySpec parseKeySpec(byte[] code) throws IOException { DerParser parser = new DerParser(code); @@ -657,8 +623,11 @@ public static RSAPrivateCrtKeySpec parseKeySpec(byte[] code) throws IOException // Parse inside the sequence parser = sequence.getParser(); + log.debug("type integer: " + Asn1Object.INTEGER); - parser.read(); // Skip version + Asn1Object version = parser.read(); + log.debug("version: " + version.getType() + " " + version.getLength()); + BigInteger modulus = parser.read().getInteger(); BigInteger publicExp = parser.read().getInteger(); BigInteger privateExp = parser.read().getInteger(); @@ -674,7 +643,8 @@ public static RSAPrivateCrtKeySpec parseKeySpec(byte[] code) throws IOException return keySpec; } - + */ + /** * Build a PEM string of certificates and private key. * From c52380bda7866cdae8865e232965b39572f2c9a1 Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Thu, 22 Feb 2024 11:11:50 -0800 Subject: [PATCH 2/3] cadc-util-1.11 make a static method public again (used by cadc-cdp-server) --- cadc-util/build.gradle | 1 - cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cadc-util/build.gradle b/cadc-util/build.gradle index d5c9ed21..32d61655 100644 --- a/cadc-util/build.gradle +++ b/cadc-util/build.gradle @@ -39,7 +39,6 @@ dependencies { compile 'org.json:json:20231013' compile 'xerces:xercesImpl:[2.12.2,)' compile 'org.jdom:jdom2:2.0.6.1' - //compile 'org.springframework:spring-jdbc:5.2.22.RELEASE' compile 'org.springframework:spring-jdbc:5.2.24.RELEASE' compile 'org.apache.commons:commons-dbcp2:[2.8.0,2.9.0)' diff --git a/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java b/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java index 0ee44cea..71e53039 100644 --- a/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java +++ b/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java @@ -386,7 +386,8 @@ private static PrivateKey readPrivateKey(File keyFile) return readPrivateKey(priv); } - private static PrivateKey readPrivateKey(byte[] bytesPrivateKey) + // needed by cadc-cdp-server + public static PrivateKey readPrivateKey(byte[] bytesPrivateKey) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { KeyFactory kf = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytesPrivateKey); From 5f86fb1e5af2fdd19dc37ecde5f04ef0fdcb3c23 Mon Sep 17 00:00:00 2001 From: Patrick Dowler Date: Thu, 22 Feb 2024 15:00:36 -0800 Subject: [PATCH 3/3] SSLUtil: cleanup cruft --- .../java/ca/nrc/cadc/auth/SSLUtilTest.java | 123 +----------- .../main/java/ca/nrc/cadc/auth/SSLUtil.java | 190 ++---------------- 2 files changed, 18 insertions(+), 295 deletions(-) diff --git a/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java b/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java index 746a0609..6c971e8f 100644 --- a/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java +++ b/cadc-util/src/intTest/java/ca/nrc/cadc/auth/SSLUtilTest.java @@ -107,62 +107,6 @@ public class SSLUtilTest private static String TEST_PEM_FN = "proxy.pem"; private static File SSL_PEM; - private static final String KEY_512 = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIIBOgIBAAJBAOvm3yk/tr7/8ZaT584T54tOviYIpoWWRfwDgd176c0kTfTj43+C\n" + - "BgxFcequf5mY51mgD7v38krRA3+xXi/igfsCAwEAAQJBAMqVrQXGcpDaScVPZV1j\n" + - "WJAY4lDVUvQb1iQTev4SwPjqUy8H/f0Zt+Bezwf1LaxcHcCFA6QnDxHw6l99/5zw\n" + - "p7kCIQD+4rfjcZyYUKwF0C2deKEgvZUjpiLYVyh/G4qKfT2sPwIhAOzu598CHLLn\n" + - "LSZoBRJtjuhAr1zUrfkoBsNHQwTKi6tFAiBOpKtyXPKhOHrrTEFWzgqBLJ2gozkr\n" + - "ITFYjqnfcycdRwIgbMW1L31hvYRCBxrEEVS4wclIeJ6vC+6jRC1ICEAQZN0CIFe+\n" + - "Az22zN/URBRVBK32tI2axHy/j80Asysh+hxalp1F\n" + - "-----END RSA PRIVATE KEY-----\n"; - private static final String KEY_1024 = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIICXQIBAAKBgQDIcNIXiBAPuOjTNWJUbsI+TR1FSXNGM2bv2naU7pKQ0OWI+DDs\n" + - "K1xlctgTi5WrfsMjPKoM+0zpVT5qjUrHyFatbTu9tYjLblPmi/yzOEOIloqZ8lF1\n" + - "hrkUG98f8IBbgx4BbkXscVKdP9awngEpIYrZt3QXLUwUP+oF2PCGH5f+nwIDAQAB\n" + - "AoGBAI5gVVuRspb4aaldSjNfWWqXrCsDOXasHHpTW9f+fu2O9PyOD3Iyerc1FHcN\n" + - "t4rRyBrHhKMj/kXf3y4gnvW6QJY8MM+lHx7oubS8O5aqVexKa8dawQNHvMfLz9PU\n" + - "OuN7X2+rvLS3+qPUtL2LiklCSsrr137M4OBNdfTcZKqAEaLBAkEA/QWsVzhx526D\n" + - "HCBaJ6cgfo6Ravqjg17DDe/yt5iC+dQzGMozWJdHjOS/066aZNF4Els4iSVWdIT5\n" + - "KqBmgjBFZQJBAMrMub59uqqHFGQzWgtOdQamiyeEwr48+a3xHUYy5p+1h7TWetHR\n" + - "OQTFOwGfpv8h7RGd2TS3fxzK+G5LKIUChbMCQQDF+rpvROtbe017pJTmkg8K9+Mx\n" + - "IgzvriZRsX7pyZwyf6e7rfufRj/mLtcqe2SznnOlaVtDdMPBSIrun7OWCs9BAkB6\n" + - "V2b2dALYPQUgLZp0l7AhgvcPsBeLjF1TgdGXN73JO0nS3lDZos4zAojGQfoMj/rk\n" + - "VcVi+A/G3utgHhcjppHhAkBpxQmU1fAB2wKVNtTh2puPmKt+g1wob1yZASRJEadZ\n" + - "5QR7EgNAJtdlouvJcdnTXHJS9JazpcS3061+u2TfgIvx\n" + - "-----END RSA PRIVATE KEY-----\n"; - private static final String KEY_2048 = - "-----BEGIN RSA PRIVATE KEY-----\n" + - "MIIEpgIBAAKCAQEA4Sa+glBzaztCbgZ90uFm5sfjSWUtZCGTw1UwcIFsQtkBa1ut\n" + - "gn1b2LtGevctPoXMreMq4f/5TNAtJuBOHP+2Xv+mIBBbm+zJwTAfk9I0/6wcNlkS\n" + - "FKhSqkpvwSv6+ecQ6R5zUrQv1aMwI392GyAiY8Jo4J6UVGKe7+YY1yvWraVFZYzB\n" + - "FeCNb9Qlo4X+uyZRjz0JJmJZE8H6USmrAa1DPQoWRBpJPJ/sIM1ejTz6lLyC3A54\n" + - "e0Nh+z8dPfUVzBySOgPzypPbuyEaVEFlmm+PqyrfoSIgNQNeOY0SbyhN374xpSHz\n" + - "PBPUPgy5qwidQB4+XN6YQlumz/i4+UnPdvR8jwIDAQABAoIBAQCmGGn0QptS4O2Z\n" + - "s0pBNq0t1QoUTAKXWrniIMdSR/fwvJvycjhnCkmmckmFTzFebWBYazxoauijxPN6\n" + - "OYEGnZIRNPF9t/OM7LrNvM2exDT65CIP6dePy7joDW+yBtroXpC4GRGkUm7zYKaT\n" + - "mWUsj6EvDO1Hv1TXh8WOXqW2no2JnP7OgCcVCGSR4/o6vBaDlUUBSiA3HIG7LWwW\n" + - "uin3LdZ8Z6CuwwlSdc758LIRlE5cGwU4Q4GH34KsMHJU0xkTaeSoYWFWo9xBTlQ1\n" + - "KuLgrYHeomKItiQTeXmuD8hepqhBkrH7tgfPcDrwmOlgarrJ1YK5Ve+i2nyHWVlP\n" + - "/g8DyrgxAoGBAP+08fzjxht5P7wW1E3QY+79NG5vO9uc+joG5X2U0Yu/98UWa1P5\n" + - "d6cbHQQuML0g7MfgRK52h80x+0xPcNewOmCX8vkYLpOqW++UOxH0Czam6igxeprV\n" + - "5WIYg5RB1QhbhQ5rRc4O+l61Yi9hdQBnJNfFbSqU373JLZpD6T5wdCXnAoGBAOFo\n" + - "1I8PEZaxiNz3qGb5LHEeSHRqQHKBQMoUcjAThA8T/Dx7uo3EkrKptyxJdpNeJbnE\n" + - "/GJsjXrElAHJqST1wmQzC3KLhgmKZliB4Ewk1foIfid36T0w/+RrlW2kwqinkr+H\n" + - "UOCSKnsQE8+xeGZ68WxmrBDmGnf5AtS9dfa6cc8ZAoGBAItxhoFNSSyUS3Br1qz0\n" + - "lnqutBgBKthRW5enSSDZtggK0Lg2yKLLqTeErqcn9UY+HUHGiE3Hr7jzp8HulG/a\n" + - "14rzcfnq+QNn5Kja4fehaTgNgCYZDW5AdM2w5phD6kObfQzm7PM48coSChAiimaE\n" + - "2O+d5zFQbE8X1XmJzTlSo9RDAoGBAKA/BXXasZdfCTyF+DuUgwq8C6hvbPe6edPv\n" + - "6ynQhf6uJ5DcKUjl6aCIVQdwBpNHyCwkJYTXRVF09P+8XLpA2Pyg6U96b0TTFmVv\n" + - "l4SqX1CMvxrR/YeaESFTdnznN9fsob/1tAKjBv5L9LmfokfAuWdmKocs/r4x0dhq\n" + - "BLXt4EDpAoGBAKN61XJ+kwO6FkxuyTlbv458Bc9toFyPqSaafJeEp/p3KdLMlphr\n" + - "0GzdeGNkrNfseVbSAjnlO2zmmhVe6Oz3oIR4d/5Hb8QEZi8f7nOZboufITyGTtYG\n" + - "LfVRkN/AuTrxRxWQDbZOo55ACoJA3DH7/BMOXhf9RikjrvESLtCWzsf2\n" + - "-----END RSA PRIVATE KEY-----\n"; - - /** * @throws java.lang.Exception */ @@ -173,21 +117,6 @@ public static void setUpBeforeClass() throws Exception SSL_PEM = FileUtil.getFileFromResource(TEST_PEM_FN, SSLUtilTest.class); } - @Test - public void testReadPem() throws Exception - { - try - { - X509CertificateChain chain = SSLUtil.readPemCertificateAndKey(SSL_PEM); - Assert.assertNotNull("Null chain", chain); - } - catch (Throwable t) - { - t.printStackTrace(); - Assert.fail("unexpected exception: " + t); - } - } - @Test public void testGetSocketFactoryFromNull() throws Exception { @@ -228,7 +157,8 @@ public void testGetSocketFactoryFromFile() throws Exception } @Test - public void testReadCert() { + public void testReadUserCert() { + // test reading a real user cert try { File f = new File(System.getProperty("user.home") + "/.ssl/" + System.getProperty("user.name") + ".pem"); log.info("in: " + f.getAbsolutePath()); @@ -251,6 +181,7 @@ public void testReadCert() { @Test public void testReadUserProxyCert() { + // test reading a proxy cert (usually made from user cert using openssl) try { File f = new File(System.getProperty("user.home") + "/.ssl/cadcproxy.pem"); log.info("in: " + f.getAbsolutePath()); @@ -273,6 +204,7 @@ public void testReadUserProxyCert() { @Test public void testReadProxyCert() { + // test read proxy.pem found in classpath (test resources) try { File f = SSL_PEM; log.info("in: " + f.getAbsolutePath()); @@ -307,53 +239,6 @@ public void testInitSSL() throws Exception } } - public void testHTTPS(URL url) throws Exception - { - HttpURLConnection.setFollowRedirects(false); - - SSLSocketFactory sf = SSLUtil.getSocketFactory(SSL_PEM); - URLConnection con = url.openConnection(); - - log.debug("URLConnection type: " + con.getClass().getName()); - HttpsURLConnection ucon = (HttpsURLConnection) con; - ucon.setSSLSocketFactory(sf); - log.debug("status: " + ucon.getResponseCode()); - log.debug("content-length: " + ucon.getContentLength()); - log.debug("content-type: " + ucon.getContentType()); - } - - @Test - public void testGoogleHTTPS() throws Exception - { - try - { - URL url = new URL("https://www.google.com/"); - log.debug("test URL: " + url); - testHTTPS(url); - } - catch (Throwable t) - { - t.printStackTrace(); - Assert.fail("unexpected exception: " + t); - } - } - - @Test - public void testCadcHTTPS() throws Exception - { - try - { - URL url = new URL("https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/"); - log.debug("test URL: " + url); - testHTTPS(url); - } - catch (Throwable t) - { - t.printStackTrace(); - Assert.fail("unexpected exception: " + t); - } - } - @Test public void testValidSubject() throws Exception { diff --git a/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java b/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java index 71e53039..5d821fa0 100644 --- a/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java +++ b/cadc-util/src/main/java/ca/nrc/cadc/auth/SSLUtil.java @@ -148,19 +148,20 @@ public class SSLUtil { private static final char[] THE_PASSWORD = CERT_ALIAS.toCharArray(); + /** + * Set the default SSL socket factory with client credentials. This is in + * use in some test code but it's a very bad idea because all threads in the + * JVM use those same credentials for SSL (https) calls. + * + * @param pemFile + * @deprecated + */ + @Deprecated public static void initSSL(File pemFile) { SSLSocketFactory sf = getSocketFactory(pemFile); HttpsURLConnection.setDefaultSSLSocketFactory(sf); } - /** - * Initialise the default SSL socket factory so that all HTTPS connections use - * the provided key store to authenticate (when the server requires client - * authentication). - * - * @param pemFile proxy certificate - * @return configured SSL socket factory - */ public static SSLSocketFactory getSocketFactory(File pemFile) { X509CertificateChain chain; try { @@ -211,15 +212,9 @@ public static SSLSocketFactory getSocketFactory(X509CertificateChain chain) { ks = getKeyStore(chain.getChain(), chain.getPrivateKey()); } - return getSocketFactory(ks, ts); - } - - // may in future try to support other KeyStore formats - @Deprecated - private static SSLSocketFactory getSocketFactory(KeyStore keyStore, KeyStore trustStore) { - KeyManagerFactory kmf = getKeyManagerFactory(keyStore); - TrustManagerFactory tmf = getTrustManagerFactory(trustStore); - SSLContext ctx = getContext(kmf, tmf, keyStore); + KeyManagerFactory kmf = getKeyManagerFactory(ks); + TrustManagerFactory tmf = getTrustManagerFactory(ts); + SSLContext ctx = getContext(kmf, tmf, ks); SSLSocketFactory sf = ctx.getSocketFactory(); return sf; } @@ -239,38 +234,6 @@ public static Subject createSubject(File certKeyFile) { } } - @Deprecated - private static byte[] getPrivateKey(byte[] certBuf) throws IOException { - BufferedReader rdr = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(certBuf))); - String line = rdr.readLine(); - StringBuilder base64 = new StringBuilder(); - while (line != null) { - if (line.matches("-----BEGIN.*PRIVATE KEY-----")) { - // log.debug(line); - line = rdr.readLine(); - while (line != null && !line.startsWith("-----END ")) { - - // log.debug(line + " (" + line.length() + ")"); - base64.append(line.trim()); - line = rdr.readLine(); - } - // log.debug(line); - line = null; // break from outer loop - } else { - line = rdr.readLine(); - } - } - rdr.close(); - String encoded = base64.toString(); - // log.debug("RSA PRIVATE KEY: " + encoded); - // log.debug("RSA private key: " + encoded.length() + " chars"); - // now: base64 -> byte[] - byte[] ret = Base64.decode(encoded); - // log.debug("RSA private key: " + ret.length + " bytes"); - - return ret; - } - /** * Extracts all the certificates from the argument, decodes them from base64 to * byte[] and concatenates all the certificates preserving the order. @@ -379,13 +342,6 @@ public static X509Certificate[] readCertificateChain(byte[] certBuf) throws Cert return chain; } - @Deprecated - private static PrivateKey readPrivateKey(File keyFile) - throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { - byte[] priv = FileUtil.readFile(keyFile); - return readPrivateKey(priv); - } - // needed by cadc-cdp-server public static PrivateKey readPrivateKey(byte[] bytesPrivateKey) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException { @@ -418,35 +374,6 @@ private static KeyStore getKeyStore(Certificate[] chain, PrivateKey pk) { } } - // currently broken trying to parse the openssl-generated pkcs12 file - @Deprecated - private static KeyStore readPKCS12(File f) { - InputStream istream = null; - try { - istream = new FileInputStream(f); - KeyStore ks = KeyStore.getInstance("PKCS12"); - // assume a non-password-protected proxy cert - ks.load(istream, THE_PASSWORD); - return ks; - } catch (KeyStoreException ex) { - throw new RuntimeException("failed to find KeyStore for " + KEYSTORE_TYPE, ex); - } catch (FileNotFoundException ex) { - throw new RuntimeException("failed to find key store file " + f, ex); - } catch (IOException ex) { - throw new RuntimeException("failed to read key store file " + f, ex); - } catch (NoSuchAlgorithmException ex) { - throw new RuntimeException("failed to check integtrity of key store file " + f, ex); - } catch (CertificateException ex) { - throw new RuntimeException("failed to load proxy certificate(s) from key store file " + f, ex); - } finally { - try { - istream.close(); - } catch (Throwable ignore) { - // do nothing - } - } - } - private static KeyManagerFactory getKeyManagerFactory(KeyStore keyStore) { String da = KEYMANAGER_ALGORITHM; try { @@ -611,96 +538,7 @@ private static X509CertificateChain readPEM(PEMParser parser) return new X509CertificateChain(chain, privateKey); } - - - /* - public static RSAPrivateCrtKeySpec parseKeySpec(byte[] code) throws IOException { - DerParser parser = new DerParser(code); - - Asn1Object sequence = parser.read(); - if (sequence.getType() != Asn1Object.SEQUENCE) { - throw new IOException("Invalid DER: not a sequence"); //$NON-NLS-1$ - } - - // Parse inside the sequence - parser = sequence.getParser(); - log.debug("type integer: " + Asn1Object.INTEGER); - - Asn1Object version = parser.read(); - log.debug("version: " + version.getType() + " " + version.getLength()); - - BigInteger modulus = parser.read().getInteger(); - BigInteger publicExp = parser.read().getInteger(); - BigInteger privateExp = parser.read().getInteger(); - BigInteger prime1 = parser.read().getInteger(); - BigInteger prime2 = parser.read().getInteger(); - BigInteger exp1 = parser.read().getInteger(); - BigInteger exp2 = parser.read().getInteger(); - BigInteger crtCoef = parser.read().getInteger(); - - RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, - exp2, crtCoef); - - return keySpec; - - } - */ - - /** - * Build a PEM string of certificates and private key. - * - * @param certChainStr - * @param bytesPrivateKey - * @return certificate chain and private key as a PEM encoded string - */ - private static String buildPEM(String certChainStr, byte[] bytesPrivateKey) { - if (certChainStr == null || bytesPrivateKey == null) { - throw new RuntimeException("Cannot build PEM of cert & privateKey. An argument is null."); - } - - // locate the 2nd occurance of CERT_BEGIN string - int posCertEnd = certChainStr.indexOf(X509CertificateChain.CERT_END); - if (posCertEnd == -1) { - throw new RuntimeException("Cannot find END mark of certificate."); - } - - StringBuilder sb = new StringBuilder(); - sb.append(X509CertificateChain.PRIVATE_KEY_BEGIN); - sb.append(X509CertificateChain.NEW_LINE); - sb.append(Base64.encodeLines64(bytesPrivateKey)); - sb.append(X509CertificateChain.PRIVATE_KEY_END); - String privateKeyStr = sb.toString(); - - int posSecondCertStart = certChainStr.indexOf(X509CertificateChain.CERT_BEGIN, posCertEnd); - if (posSecondCertStart == -1) { - // this is an end user certificate, number of certificates==1 - return (certChainStr + X509CertificateChain.NEW_LINE + privateKeyStr); - } else { - // private key goes in between the first and second - // certificate in the chain - String certStrPart1 = certChainStr.substring(0, posSecondCertStart); - String certStrPart2 = certChainStr.substring(posSecondCertStart); - return (certStrPart1 + privateKeyStr + X509CertificateChain.NEW_LINE + certStrPart2); - } - } - - /** - * @param chain - * @return certificate chain and private key as a PEM encoded string - */ - // THIS IS NOT WORKING. - // getEncoded() in privateKey does not use the encoding expected by PEM. - - /* - * public static String writePEMCertificateAndKey(X509CertificateChain chain) { - * if (chain == null) return null; - * - * String certChainStr = chain.certificateString(); byte[] bytesPrivateKey = - * chain.getPrivateKey().getEncoded(); if (certChainStr == null || - * bytesPrivateKey == null) return null; String pemStr = - * SSLUtil.buildPEM(certChainStr, bytesPrivateKey); return pemStr; } - */ - + /** * Checks whether the subject's certificate credentials are valid at a given * date. If date is missing, current time is used as reference. @@ -718,7 +556,7 @@ public static void validateSubject(Subject subject, Date date) throws CertificateException, CertificateExpiredException, CertificateNotYetValidException { if (subject != null) { Set certs = subject.getPublicCredentials(X509CertificateChain.class); - if (certs.size() == 0) { + if (certs.isEmpty()) { // subject without certs throw new CertificateException("No certificates associated with subject"); }