Support PEM encrypted certificates.
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 4 Apr 2021 07:54:00 +0000 (09:54 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 4 Apr 2021 07:54:00 +0000 (09:54 +0200)
org.argeo.cms/src/org/argeo/cms/internal/kernel/InitUtils.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelConstants.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/PkiUtils.java

index 011d3856adc01ab15fec341f8700a75557ab6730..95d17d8a2367982e9bf46497effa39de249ed4ae 100644 (file)
@@ -5,9 +5,11 @@ import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.IOException;
+import java.io.Reader;
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.security.KeyStore;
@@ -110,15 +112,40 @@ class InitUtils {
 
                                // server certificate
                                Path keyStorePath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_KEYSTORE_PATH);
-                               String keyStorePassword = getFrameworkProp(
+                               Path pemKeyPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_KEY_PATH);
+                               Path pemCertPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEFAULT_PEM_CERT_PATH);
+                               String keyStorePasswordStr = getFrameworkProp(
                                                InternalHttpConstants.JETTY_PROPERTY_PREFIX + InternalHttpConstants.SSL_PASSWORD);
-                               if (keyStorePassword == null)
-                                       keyStorePassword = "changeit";
+                               char[] keyStorePassword;
+                               if (keyStorePasswordStr == null)
+                                       keyStorePassword = "changeit".toCharArray();
+                               else
+                                       keyStorePassword = keyStorePasswordStr.toCharArray();
+
+                               // if PEM files both exists, update the PKCS12 file
+                               if (Files.exists(pemCertPath) && Files.exists(pemKeyPath)) {
+                                       // TODO check certificate update time? monitor changes?
+                                       KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
+                                       try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
+                                                       Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
+                                               PkiUtils.loadPem(keyStore, key, keyStorePassword, cert);
+                                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                                               if (log.isDebugEnabled())
+                                                       log.debug("PEM certificate stored in " + keyStorePath);
+                                       } catch (IOException e) {
+                                               log.error("Cannot read PEM files " + pemKeyPath + " and " + pemCertPath, e);
+                                       }
+                               }
+
                                if (!Files.exists(keyStorePath))
                                        createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
                                props.put(InternalHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
                                props.put(InternalHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
-                               props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
+                               props.put(InternalHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
+
+//                             props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
+//                             props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
+//                             props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
 
                                // client certificate authentication
                                String wantClientAuth = getFrameworkProp(
@@ -298,26 +325,29 @@ class InitUtils {
                return repositoryFactory.getRepository(params);
        }
 
-       private static void createSelfSignedKeyStore(Path keyStorePath, String keyStorePassword, String keyStoreType) {
+       private static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
                // for (Provider provider : Security.getProviders())
                // System.out.println(provider.getName());
-               File keyStoreFile = keyStorePath.toFile();
-               char[] ksPwd = keyStorePassword.toCharArray();
-               char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length);
-               if (!keyStoreFile.exists()) {
+//             File keyStoreFile = keyStorePath.toFile();
+               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
+               if (!Files.exists(keyStorePath)) {
                        try {
-                               keyStoreFile.getParentFile().mkdirs();
-                               KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd, keyStoreType);
+                               Files.createDirectories(keyStorePath.getParent());
+                               KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
                                PkiUtils.generateSelfSignedCertificate(keyStore,
                                                new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
                                                1024, keyPwd);
-                               PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore);
+                               PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
                                if (log.isDebugEnabled())
-                                       log.debug("Created self-signed unsecure keystore " + keyStoreFile);
+                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
                        } catch (Exception e) {
-                               if (keyStoreFile.length() == 0)
-                                       keyStoreFile.delete();
-                               log.error("Cannot create keystore " + keyStoreFile, e);
+                               try {
+                                       if (Files.size(keyStorePath) == 0)
+                                               Files.delete(keyStorePath);
+                               } catch (IOException e1) {
+                                       // silent
+                               }
+                               log.error("Cannot create keystore " + keyStorePath, e);
                        }
                } else {
                        throw new IllegalStateException("Keystore " + keyStorePath + " already exists");
index 3efb41591b7e7c749a5e2f029d92a3b8b8dbd00b..7d14ae9e619d2492083501bcac9440f2e16d5551 100644 (file)
@@ -13,6 +13,8 @@ public interface KernelConstants {
        // Files
        String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + NodeConstants.DEPLOY_BASEDN + ".ldif";
        String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + NodeConstants.NODE + ".p12";
+       String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + NodeConstants.NODE + ".key";
+       String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + NodeConstants.NODE + ".crt";
        String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab";
 
        // Security
index 94a9b1f45ca1cba17ae60186e8d8ba77a0631d8d..2105e05d01c91911874d5472683ac9490f0f6827 100644 (file)
@@ -1,27 +1,42 @@
 package org.argeo.cms.internal.kernel;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
 import java.math.BigInteger;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Date;
 
 import javax.security.auth.x500.X500Principal;
 
-import org.argeo.cms.CmsException;
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
 import org.bouncycastle.cert.X509v3CertificateBuilder;
 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
 import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.InputDecryptorProvider;
+import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
+import org.bouncycastle.pkcs.PKCSException;
 
 /**
  * Utilities around private keys and certificate, mostly wrapping BouncyCastle
@@ -56,38 +71,117 @@ class PkiUtils {
 
                        keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
                        return cert;
-               } catch (Exception e) {
-                       throw new CmsException("Cannot generate self-signed certificate", e);
+               } catch (GeneralSecurityException | OperatorCreationException e) {
+                       throw new RuntimeException("Cannot generate self-signed certificate", e);
                }
        }
 
-       public static KeyStore getKeyStore(File keyStoreFile, char[] keyStorePassword, String keyStoreType) {
+       public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
                try {
                        KeyStore store = KeyStore.getInstance(keyStoreType, SECURITY_PROVIDER);
-                       if (keyStoreFile.exists()) {
-                               try (FileInputStream fis = new FileInputStream(keyStoreFile)) {
+                       if (Files.exists(keyStoreFile)) {
+                               try (InputStream fis = Files.newInputStream(keyStoreFile)) {
                                        store.load(fis, keyStorePassword);
                                }
                        } else {
                                store.load(null);
                        }
                        return store;
-               } catch (Exception e) {
-                       throw new CmsException("Cannot load keystore " + keyStoreFile, e);
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
                }
        }
 
-       public static void saveKeyStore(File keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
+       public static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
                try {
-                       try (FileOutputStream fis = new FileOutputStream(keyStoreFile)) {
+                       try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
                                keyStore.store(fis, keyStorePassword);
                        }
-               } catch (Exception e) {
-                       throw new CmsException("Cannot save keystore " + keyStoreFile, e);
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
                }
        }
 
-       public static void main(String[] args) {
+//     public static byte[] pemToPKCS12(final String keyFile, final String cerFile, final String password)
+//                     throws Exception {
+//             // Get the private key
+//             FileReader reader = new FileReader(keyFile);
+//
+//             PEMReader pem = new PemReader(reader, new PasswordFinder() {
+//                     @Override
+//                     public char[] getPassword() {
+//                             return password.toCharArray();
+//                     }
+//             });
+//
+//             PrivateKey key = ((KeyPair) pem.readObject()).getPrivate();
+//
+//             pem.close();
+//             reader.close();
+//
+//             // Get the certificate
+//             reader = new FileReader(cerFile);
+//             pem = new PEMReader(reader);
+//
+//             X509Certificate cert = (X509Certificate) pem.readObject();
+//
+//             pem.close();
+//             reader.close();
+//
+//             // Put them into a PKCS12 keystore and write it to a byte[]
+//             ByteArrayOutputStream bos = new ByteArrayOutputStream();
+//             KeyStore ks = KeyStore.getInstance("PKCS12");
+//             ks.load(null);
+//             ks.setKeyEntry("alias", (Key) key, password.toCharArray(), new java.security.cert.Certificate[] { cert });
+//             ks.store(bos, password.toCharArray());
+//             bos.close();
+//             return bos.toByteArray();
+//     }
+
+       public static void loadPem(KeyStore keyStore, Reader key, char[] keyPassword, Reader cert) {
+               PrivateKey privateKey = loadPemPrivateKey(key, keyPassword);
+               X509Certificate certificate = loadPemCertificate(cert);
+               try {
+                       keyStore.setKeyEntry(certificate.getSubjectX500Principal().getName(), privateKey, keyPassword,
+                                       new java.security.cert.Certificate[] { certificate });
+               } catch (KeyStoreException e) {
+                       throw new RuntimeException("Cannot store PEM certificate", e);
+               }
+       }
+
+       public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
+                       Object object = pemParser.readObject();
+                       PrivateKeyInfo privateKeyInfo;
+                       if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
+                               if (keyPassword == null)
+                                       throw new IllegalArgumentException("A key password is required");
+                               InputDecryptorProvider decProv = new JceOpenSSLPKCS8DecryptorProviderBuilder().build(keyPassword);
+                               privateKeyInfo = ((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv);
+                       } else if (object instanceof PrivateKeyInfo) {
+                               privateKeyInfo = (PrivateKeyInfo) object;
+                       } else {
+                               throw new IllegalArgumentException("Unsupported format for private key");
+                       }
+                       return converter.getPrivateKey(privateKeyInfo);
+               } catch (IOException | OperatorCreationException | PKCSException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       public static X509Certificate loadPemCertificate(Reader reader) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       X509CertificateHolder certHolder = (X509CertificateHolder) pemParser.readObject();
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(SECURITY_PROVIDER)
+                                       .getCertificate(certHolder);
+                       return cert;
+               } catch (IOException | CertificateException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       public static void main(String[] args) throws Exception {
                final String ALGORITHM = "RSA";
                final String provider = "BC";
                SecureRandom secureRandom = new SecureRandom();