Remove dependency to BouncyCastle in core CMS
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 6 Aug 2022 07:15:48 +0000 (09:15 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 6 Aug 2022 07:15:48 +0000 (09:15 +0200)
org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java [new file with mode: 0644]
org.argeo.cms/bnd.bnd
org.argeo.cms/src/org/argeo/cms/CmsSshd.java
org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/client/SpnegoHttpClient.java
org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/client/jaas.cfg [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsStateImpl.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/KernelConstants.java
org.argeo.cms/src/org/argeo/cms/internal/runtime/PkiUtils.java

diff --git a/org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java b/org.argeo.cms.lib.sshd/src/org/argeo/cms/bc/BcUtils.java
new file mode 100644 (file)
index 0000000..d2fc89f
--- /dev/null
@@ -0,0 +1,168 @@
+package org.argeo.cms.bc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.net.InetAddress;
+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.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.Arrays;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.cms.CmsLog;
+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 the BouncyCastle crypto library. */
+public class BcUtils {
+       private final static CmsLog log = CmsLog.getLog(BcUtils.class);
+
+       private final static String BC_SECURITY_PROVIDER;
+       static {
+               Security.addProvider(new BouncyCastleProvider());
+               BC_SECURITY_PROVIDER = "BC";
+       }
+
+       public static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
+               // for (Provider provider : Security.getProviders())
+               // System.out.println(provider.getName());
+               // File keyStoreFile = keyStorePath.toFile();
+               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
+               if (!Files.exists(keyStorePath)) {
+                       try {
+                               Files.createDirectories(keyStorePath.getParent());
+                               KeyStore keyStore = getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
+                               generateSelfSignedCertificate(keyStore,
+                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
+                                               1024, keyPwd);
+                               saveKeyStore(keyStorePath, keyStorePassword, keyStore);
+                               if (log.isDebugEnabled())
+                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
+                       } catch (Exception 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");
+               }
+       }
+
+       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
+                       int keySize, char[] keyPassword) {
+               try {
+                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", BC_SECURITY_PROVIDER);
+                       kpGen.initialize(keySize, new SecureRandom());
+                       KeyPair pair = kpGen.generateKeyPair();
+                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
+                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
+                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
+                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
+                                       notAfter, x500Principal, pair.getPublic());
+                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
+                                       .setProvider(BC_SECURITY_PROVIDER).build(pair.getPrivate());
+                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER)
+                                       .getCertificate(certGen.build(sigGen));
+                       cert.checkValidity(new Date());
+                       cert.verify(cert.getPublicKey());
+
+                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
+                       return cert;
+               } catch (GeneralSecurityException | OperatorCreationException e) {
+                       throw new RuntimeException("Cannot generate self-signed certificate", e);
+               }
+       }
+
+       public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
+               try (PEMParser pemParser = new PEMParser(reader)) {
+                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BC_SECURITY_PROVIDER);
+                       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(BC_SECURITY_PROVIDER)
+                                       .getCertificate(certHolder);
+                       return cert;
+               } catch (IOException | CertificateException e) {
+                       throw new RuntimeException("Cannot read private key", e);
+               }
+       }
+
+       private static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
+               try {
+                       KeyStore store = KeyStore.getInstance(keyStoreType, BC_SECURITY_PROVIDER);
+                       if (Files.exists(keyStoreFile)) {
+                               try (InputStream fis = Files.newInputStream(keyStoreFile)) {
+                                       store.load(fis, keyStorePassword);
+                               }
+                       } else {
+                               store.load(null);
+                       }
+                       return store;
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot load keystore " + keyStoreFile, e);
+               }
+       }
+
+       private static void saveKeyStore(Path keyStoreFile, char[] keyStorePassword, KeyStore keyStore) {
+               try {
+                       try (OutputStream fis = Files.newOutputStream(keyStoreFile)) {
+                               keyStore.store(fis, keyStorePassword);
+                       }
+               } catch (GeneralSecurityException | IOException e) {
+                       throw new RuntimeException("Cannot save keystore " + keyStoreFile, e);
+               }
+       }
+
+       /** singleton */
+       private BcUtils() {
+       }
+}
index 4a5324ef2e4a4b2ea697a26821b5c2f3f6d7153c..ade2f3aa94f72ac1f1d2eee2616dffb48a30a4ef 100644 (file)
@@ -1,7 +1,6 @@
 Bundle-Activator: org.argeo.cms.internal.osgi.CmsActivator
 
 Import-Package: \
-com.sun.security.jgss,\
 org.osgi.*;version=0.0.0,\
 *
 
index dd8621f0eb532ef6d6bea14494141c4b778e936c..41968be7e8cf44f0533107ae0c0ee3a758f77aea 100644 (file)
@@ -1,10 +1,9 @@
 package org.argeo.cms;
 
 import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.internal.runtime.KernelConstants;
 
 /** Just a marker interface for the time being. */
 public interface CmsSshd {
        final static String NODE_USERNAME_ALIAS = "user.name";
-       final static String DEFAULT_SSH_HOST_KEY_PATH = KernelConstants.DIR_PRIVATE + '/' + CmsConstants.NODE + ".ser";
+       final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser";
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/auth/ConsoleCallbackHandler.java
new file mode 100644 (file)
index 0000000..874381e
--- /dev/null
@@ -0,0 +1,73 @@
+package org.argeo.cms.auth;
+
+import java.io.Console;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Scanner;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.TextOutputCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+/** Callback handler to be used with a command line UI. */
+public class ConsoleCallbackHandler implements CallbackHandler {
+
+       @Override
+       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+               Console console = System.console();
+//             if (console == null)
+//                     throw new IllegalStateException("No console available");
+
+               Scanner scanner = null;
+               PrintWriter writer;
+               if (console == null) {// IDE
+                       scanner = new Scanner(System.in);
+                       writer = new PrintWriter(System.out, true);
+               } else {
+                       writer = console.writer();
+
+               }
+               for (int i = 0; i < callbacks.length; i++) {
+                       if (callbacks[i] instanceof TextOutputCallback) {
+                               TextOutputCallback callback = (TextOutputCallback) callbacks[i];
+                               writer.printf(callback.getMessage());
+                       } else if (callbacks[i] instanceof NameCallback) {
+                               NameCallback callback = (NameCallback) callbacks[i];
+                               writer.printf(callback.getPrompt());
+                               if (callback.getDefaultName() != null)
+                                       writer.printf(" (" + callback.getDefaultName() + ")");
+                               String answer = console != null ? console.readLine() : scanner.next();
+                               if (callback.getDefaultName() != null && answer.trim().equals(""))
+                                       callback.setName(callback.getDefaultName());
+                               else
+                                       callback.setName(answer);
+                       } else if (callbacks[i] instanceof PasswordCallback) {
+                               PasswordCallback callback = (PasswordCallback) callbacks[i];
+                               writer.printf(callback.getPrompt());
+                               char[] answer = console != null ? console.readPassword() : scanner.next().toCharArray();
+                               callback.setPassword(answer);
+                               Arrays.fill(answer, ' ');
+                       }
+//                     else if (callbacks[i] instanceof LocaleChoice) {
+//                             LocaleChoice callback = (LocaleChoice) callbacks[i];
+//                             writer.write("Language");
+//                             writer.write("\n");
+//                             for (int j = 0; j < callback.getLocales().size(); j++) {
+//                                     Locale locale = callback.getLocales().get(j);
+//                                     writer.print(j + " : " + locale.getDisplayName() + "\n");
+//                             }
+//                             writer.write("(" + callback.getDefaultIndex() + ") : ");
+//                             String answer = console.readLine();
+//                             if (answer.trim().equals(""))
+//                                     callback.setSelectedIndex(callback.getDefaultIndex());
+//                             else
+//                                     callback.setSelectedIndex(new Integer(answer.trim()));
+//                     }
+               }
+       }
+
+}
index e5beb69da84371be0acb4656bc9f3a8608c65b55..fcbd7a73da778c5e62a6f8d5561bb658b86c2748 100644 (file)
@@ -1,5 +1,7 @@
 package org.argeo.cms.client;
 
+import java.io.BufferedInputStream;
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.http.HttpClient;
@@ -7,9 +9,15 @@ import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.net.http.HttpResponse.BodyHandler;
 import java.net.http.HttpResponse.BodyHandlers;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
+import java.util.Collection;
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.TrustManager;
@@ -17,10 +25,13 @@ import javax.net.ssl.X509TrustManager;
 import javax.security.auth.Subject;
 import javax.security.auth.login.LoginContext;
 
+import org.argeo.cms.auth.ConsoleCallbackHandler;
 import org.argeo.cms.auth.RemoteAuthUtils;
 import org.argeo.util.http.HttpHeader;
 
 public class SpnegoHttpClient {
+       public final static String CLIENT_LOGIN_CONTEXT = "CLIENT";
+
        public static void main(String[] args) throws MalformedURLException {
 //             String principal = System.getProperty("javax.security.auth.login.name");
                if (args.length == 0) {
@@ -33,10 +44,10 @@ public class SpnegoHttpClient {
                URL u = new URL(url);
                String server = u.getHost();
 
-               URL jaasUrl = SpnegoHttpClient.class.getResource("jaas.cfg");
+               URL jaasUrl = SpnegoHttpClient.class.getResource("jaas-client-ipa.cfg");
                System.setProperty("java.security.auth.login.config", jaasUrl.toExternalForm());
                try {
-                       LoginContext lc = new LoginContext("SINGLE_USER");
+                       LoginContext lc = new LoginContext(CLIENT_LOGIN_CONTEXT, new ConsoleCallbackHandler());
                        lc.login();
 
                        HttpClient httpClient = openHttpClient(lc.getSubject());
@@ -57,31 +68,45 @@ public class SpnegoHttpClient {
 
        private static HttpClient openHttpClient(Subject subject) {
                HttpClient client = HttpClient.newBuilder() //
-//                             .sslContext(insecureContext()) //
+                               .sslContext(ipaSslContext()) //
                                .version(HttpClient.Version.HTTP_1_1) //
                                .build();
 
                return client;
        }
 
-       static SSLContext insecureContext() {
-               TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() {
-                       public void checkClientTrusted(X509Certificate[] xcs, String string) {
+       @SuppressWarnings("unchecked")
+       static SSLContext ipaSslContext() {
+               try {
+                       final Collection<X509Certificate> certificates;
+                       Path caCertificatePath = Paths.get("/etc/ipa/ca.crt");
+                       if (Files.exists(caCertificatePath)) {
+                               CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
+                               try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(caCertificatePath))) {
+                                       certificates = (Collection<X509Certificate>) certificateFactory.generateCertificates(in);
+                               }
+                       } else {
+                               certificates = null;
                        }
+                       TrustManager[] noopTrustManager = new TrustManager[] { new X509TrustManager() {
+                               public void checkClientTrusted(X509Certificate[] xcs, String string) {
+                               }
 
-                       public void checkServerTrusted(X509Certificate[] xcs, String string) {
-                       }
+                               public void checkServerTrusted(X509Certificate[] xcs, String string) {
+                               }
+
+                               public X509Certificate[] getAcceptedIssuers() {
+                                       if (certificates == null)
+                                               return null;
+                                       return certificates.toArray(new X509Certificate[certificates.size()]);
+                               }
+                       } };
 
-                       public X509Certificate[] getAcceptedIssuers() {
-                               return null;
-                       }
-               } };
-               try {
                        SSLContext sc = SSLContext.getInstance("ssl");
                        sc.init(null, noopTrustManager, null);
                        return sc;
-               } catch (KeyManagementException | NoSuchAlgorithmException e) {
-                       throw new IllegalStateException("Cannot create insecure SSL context ", e);
+               } catch (KeyManagementException | NoSuchAlgorithmException | CertificateException | IOException e) {
+                       throw new IllegalStateException("Cannot create SSL context ", e);
                }
        }
 
diff --git a/org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg b/org.argeo.cms/src/org/argeo/cms/client/jaas-client-ipa.cfg
new file mode 100644 (file)
index 0000000..2921a33
--- /dev/null
@@ -0,0 +1,4 @@
+CLIENT {
+    com.sun.security.auth.module.Krb5LoginModule required
+    useTicketCache=true;
+};
diff --git a/org.argeo.cms/src/org/argeo/cms/client/jaas.cfg b/org.argeo.cms/src/org/argeo/cms/client/jaas.cfg
deleted file mode 100644 (file)
index dc540dd..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-SINGLE_USER {
-    com.sun.security.auth.module.Krb5LoginModule required
-     useTicketCache=true
-     debug=true;
-};
-
-com.sun.security.jgss.krb5.initiate {
-    com.sun.security.auth.module.Krb5LoginModule
-     required useTicketCache=true;
-};
\ No newline at end of file
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ConsoleCallbackHandler.java
deleted file mode 100644 (file)
index 0979a21..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.argeo.cms.internal.auth;
-
-import java.io.Console;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-import javax.security.auth.callback.Callback;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.callback.NameCallback;
-import javax.security.auth.callback.PasswordCallback;
-import javax.security.auth.callback.TextOutputCallback;
-import javax.security.auth.callback.UnsupportedCallbackException;
-
-/** Callback handler to be used with a command line UI. */
-public class ConsoleCallbackHandler implements CallbackHandler {
-
-       @Override
-       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-               Console console = System.console();
-               if (console == null)
-                       throw new IllegalStateException("No console available");
-
-               PrintWriter writer = console.writer();
-               for (int i = 0; i < callbacks.length; i++) {
-                       if (callbacks[i] instanceof TextOutputCallback) {
-                               TextOutputCallback callback = (TextOutputCallback) callbacks[i];
-                               writer.write(callback.getMessage());
-                       } else if (callbacks[i] instanceof NameCallback) {
-                               NameCallback callback = (NameCallback) callbacks[i];
-                               writer.write(callback.getPrompt());
-                               if (callback.getDefaultName() != null)
-                                       writer.write(" (" + callback.getDefaultName() + ")");
-                               writer.write(" : ");
-                               String answer = console.readLine();
-                               if (callback.getDefaultName() != null && answer.trim().equals(""))
-                                       callback.setName(callback.getDefaultName());
-                               else
-                                       callback.setName(answer);
-                       } else if (callbacks[i] instanceof PasswordCallback) {
-                               PasswordCallback callback = (PasswordCallback) callbacks[i];
-                               writer.write(callback.getPrompt());
-                               char[] answer = console.readPassword();
-                               callback.setPassword(answer);
-                               Arrays.fill(answer, ' ');
-                       }
-//                     else if (callbacks[i] instanceof LocaleChoice) {
-//                             LocaleChoice callback = (LocaleChoice) callbacks[i];
-//                             writer.write("Language");
-//                             writer.write("\n");
-//                             for (int j = 0; j < callback.getLocales().size(); j++) {
-//                                     Locale locale = callback.getLocales().get(j);
-//                                     writer.print(j + " : " + locale.getDisplayName() + "\n");
-//                             }
-//                             writer.write("(" + callback.getDefaultIndex() + ") : ");
-//                             String answer = console.readLine();
-//                             if (answer.trim().equals(""))
-//                                     callback.setSelectedIndex(callback.getDefaultIndex());
-//                             else
-//                                     callback.setSelectedIndex(new Integer(answer.trim()));
-//                     }
-               }
-       }
-
-}
index c9109c8561fb2500485998c033cbb4ac4d58a419..b76ca5792deeb20ade7a6f4167b7909de2ee3590 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.cms.internal.runtime;
 
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.Reader;
 import java.net.InetAddress;
@@ -66,19 +67,19 @@ public class CmsStateImpl implements CmsState {
                deployPropertyDefaults.put(CmsDeployProperty.LOCALE, Locale.getDefault().toString());
 
                // certificates
-               deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
-               deployPropertyDefaults.put(CmsDeployProperty.SSL_PASSWORD, PkiUtils.DEFAULT_KEYSTORE_PASSWORD);
-               Path keyStorePath = getDataPath(PkiUtils.DEFAULT_KEYSTORE_PATH);
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORETYPE, KernelConstants.PKCS12);
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_PASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD);
+               Path keyStorePath = getDataPath(KernelConstants.DEFAULT_KEYSTORE_PATH);
                if (keyStorePath != null) {
                        deployPropertyDefaults.put(CmsDeployProperty.SSL_KEYSTORE, keyStorePath.toAbsolutePath().toString());
                }
 
-               Path trustStorePath = getDataPath(PkiUtils.DEFAULT_TRUSTSTORE_PATH);
+               Path trustStorePath = getDataPath(KernelConstants.DEFAULT_TRUSTSTORE_PATH);
                if (trustStorePath != null) {
                        deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORE, trustStorePath.toAbsolutePath().toString());
                }
-               deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORETYPE, PkiUtils.PKCS12);
-               deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD, PkiUtils.DEFAULT_KEYSTORE_PASSWORD);
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTORETYPE, KernelConstants.PKCS12);
+               deployPropertyDefaults.put(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD, KernelConstants.DEFAULT_KEYSTORE_PASSWORD);
 
                // SSH
                Path authorizedKeysPath = getDataPath(KernelConstants.NODE_SSHD_AUTHORIZED_KEYS_PATH);
@@ -193,8 +194,8 @@ public class CmsStateImpl implements CmsState {
        private void initCertificates() {
                // server certificate
                Path keyStorePath = Paths.get(getDeployProperty(CmsDeployProperty.SSL_KEYSTORE));
-               Path pemKeyPath = getDataPath(PkiUtils.DEFAULT_PEM_KEY_PATH);
-               Path pemCertPath = getDataPath(PkiUtils.DEFAULT_PEM_CERT_PATH);
+               Path pemKeyPath = getDataPath(KernelConstants.DEFAULT_PEM_KEY_PATH);
+               Path pemCertPath = getDataPath(KernelConstants.DEFAULT_PEM_CERT_PATH);
                char[] keyStorePassword = getDeployProperty(CmsDeployProperty.SSL_PASSWORD).toCharArray();
 
                // Keystore
@@ -204,7 +205,7 @@ public class CmsStateImpl implements CmsState {
                        KeyStore keyStore = PkiUtils.getKeyStore(keyStorePath, keyStorePassword,
                                        getDeployProperty(CmsDeployProperty.SSL_KEYSTORETYPE));
                        try (Reader key = Files.newBufferedReader(pemKeyPath, StandardCharsets.US_ASCII);
-                                       Reader cert = Files.newBufferedReader(pemCertPath, StandardCharsets.US_ASCII);) {
+                                       BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(pemCertPath));) {
                                PkiUtils.loadPrivateCertificatePem(keyStore, CmsConstants.NODE, key, keyStorePassword, cert);
                                Files.createDirectories(keyStorePath.getParent());
                                PkiUtils.saveKeyStore(keyStorePath, keyStorePassword, keyStore);
@@ -220,11 +221,11 @@ public class CmsStateImpl implements CmsState {
                char[] trustStorePassword = getDeployProperty(CmsDeployProperty.SSL_TRUSTSTOREPASSWORD).toCharArray();
 
                // IPA CA
-               Path ipaCaCertPath = Paths.get(PkiUtils.IPA_PEM_CA_CERT_PATH);
+               Path ipaCaCertPath = Paths.get(KernelConstants.IPA_PEM_CA_CERT_PATH);
                if (Files.exists(ipaCaCertPath)) {
                        KeyStore trustStore = PkiUtils.getKeyStore(trustStorePath, trustStorePassword,
                                        getDeployProperty(CmsDeployProperty.SSL_TRUSTSTORETYPE));
-                       try (Reader cert = Files.newBufferedReader(ipaCaCertPath, StandardCharsets.US_ASCII);) {
+                       try (BufferedInputStream cert = new BufferedInputStream(Files.newInputStream(ipaCaCertPath));) {
                                PkiUtils.loadTrustedCertificatePem(trustStore, trustStorePassword, cert);
                                Files.createDirectories(keyStorePath.getParent());
                                PkiUtils.saveKeyStore(trustStorePath, trustStorePassword, trustStore);
@@ -235,16 +236,8 @@ public class CmsStateImpl implements CmsState {
                        }
                }
 
-               if (!Files.exists(keyStorePath))
-                       PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
-//             props.put(JettyHttpConstants.SSL_KEYSTORETYPE, PkiUtils.PKCS12);
-//             props.put(JettyHttpConstants.SSL_KEYSTORE, keyStorePath.toString());
-//             props.put(JettyHttpConstants.SSL_PASSWORD, new String(keyStorePassword));
-
-//             props.put(InternalHttpConstants.SSL_KEYSTORETYPE, "PKCS11");
-//             props.put(InternalHttpConstants.SSL_KEYSTORE, "../../nssdb");
-//             props.put(InternalHttpConstants.SSL_PASSWORD, keyStorePassword);
-
+//             if (!Files.exists(keyStorePath))
+//                     PkiUtils.createSelfSignedKeyStore(keyStorePath, keyStorePassword, PkiUtils.PKCS12);
        }
 
        public void stop() {
index c80e86aea56fad68e155351dc2f4c8ec963ee27a..e6ca1ba605a56057908722f209dbe742cf92a64c 100644 (file)
@@ -1,7 +1,9 @@
 package org.argeo.cms.internal.runtime;
 
+import org.argeo.api.cms.CmsConstants;
+
 /** Internal CMS constants. */
-public interface KernelConstants {
+interface KernelConstants {
        // Directories
        String DIR_PRIVATE = "private";
 
@@ -16,6 +18,20 @@ public interface KernelConstants {
        // KERBEROS
        String DEFAULT_KERBEROS_SERVICE = "HTTP";
 
+       String DEFAULT_KEYSTORE_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".p12";
+
+       String DEFAULT_TRUSTSTORE_PATH = DIR_PRIVATE + "/trusted.p12";
+
+       String DEFAULT_PEM_KEY_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".key";
+
+       String DEFAULT_PEM_CERT_PATH = DIR_PRIVATE + '/' + CmsConstants.NODE + ".crt";
+
+       String IPA_PEM_CA_CERT_PATH = "/etc/ipa/ca.crt";
+
+       String DEFAULT_KEYSTORE_PASSWORD = "changeit";
+
+       String PKCS12 = "PKCS12";
+
        // HTTP client
        // String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility";
 
index f47e544218456968cfeb7982031c9649589a3849..378146ea15715cb9415e70ca0e5c0db0a7e42ad1 100644 (file)
 package org.argeo.cms.internal.runtime;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Reader;
-import java.math.BigInteger;
-import java.net.InetAddress;
 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.KeyFactory;
 import java.security.KeyStore;
 import java.security.KeyStore.TrustedCertificateEntry;
 import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
 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.CertificateFactory;
 import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.Date;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Base64;
+import java.util.Collection;
 import java.util.Objects;
 
-import javax.security.auth.x500.X500Principal;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.api.cms.CmsLog;
-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
  * implementations.
  */
 class PkiUtils {
-       private final static CmsLog log = CmsLog.getLog(PkiUtils.class);
-
-       final static String PKCS12 = "PKCS12";
-       final static String JKS = "JKS";
-
-       static final String DEFAULT_KEYSTORE_PATH = KernelConstants.DIR_PRIVATE + '/' + CmsConstants.NODE + ".p12";
-
-       static final String DEFAULT_TRUSTSTORE_PATH = KernelConstants.DIR_PRIVATE + "/trusted.p12";
-
-       static final String DEFAULT_PEM_KEY_PATH = KernelConstants.DIR_PRIVATE + '/' + CmsConstants.NODE + ".key";
-
-       static final String DEFAULT_PEM_CERT_PATH = KernelConstants.DIR_PRIVATE + '/' + CmsConstants.NODE + ".crt";
-
-       static final String IPA_PEM_CA_CERT_PATH = "/etc/ipa/ca.crt";
-
-       static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
-
-       private final static String SUN_SECURITY_PROVIDER;
-       private final static String SUN_JSSE_SECURITY_PROVIDER;
-       private final static String BC_SECURITY_PROVIDER;
-       static {
-               Security.addProvider(new BouncyCastleProvider());
-               // BouncyCastle does not store trusted certificates properly
-               // TODO report it
-               BC_SECURITY_PROVIDER = "BC";
-               SUN_SECURITY_PROVIDER = "SUN";
-               SUN_JSSE_SECURITY_PROVIDER = "SunJSSE";
-       }
-
-       public static X509Certificate generateSelfSignedCertificate(KeyStore keyStore, X500Principal x500Principal,
-                       int keySize, char[] keyPassword) {
-               try {
-                       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", BC_SECURITY_PROVIDER);
-                       kpGen.initialize(keySize, new SecureRandom());
-                       KeyPair pair = kpGen.generateKeyPair();
-                       Date notBefore = new Date(System.currentTimeMillis() - 10000);
-                       Date notAfter = new Date(System.currentTimeMillis() + 365 * 24L * 3600 * 1000);
-                       BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
-                       X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(x500Principal, serial, notBefore,
-                                       notAfter, x500Principal, pair.getPublic());
-                       ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption")
-                                       .setProvider(BC_SECURITY_PROVIDER).build(pair.getPrivate());
-                       X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC_SECURITY_PROVIDER)
-                                       .getCertificate(certGen.build(sigGen));
-                       cert.checkValidity(new Date());
-                       cert.verify(cert.getPublicKey());
-
-                       keyStore.setKeyEntry(x500Principal.getName(), pair.getPrivate(), keyPassword, new Certificate[] { cert });
-                       return cert;
-               } catch (GeneralSecurityException | OperatorCreationException e) {
-                       throw new RuntimeException("Cannot generate self-signed certificate", e);
-               }
-       }
-
        public static KeyStore getKeyStore(Path keyStoreFile, char[] keyStorePassword, String keyStoreType) {
                try {
-                       KeyStore store = KeyStore.getInstance(keyStoreType, SUN_JSSE_SECURITY_PROVIDER);
+                       KeyStore store = KeyStore.getInstance(keyStoreType);
                        if (Files.exists(keyStoreFile)) {
                                try (InputStream fis = Files.newInputStream(keyStoreFile)) {
                                        store.load(fis, keyStorePassword);
@@ -129,44 +56,8 @@ class PkiUtils {
                }
        }
 
-//     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 loadPrivateCertificatePem(KeyStore keyStore, String alias, Reader key, char[] keyPassword,
-                       Reader cert) {
+                       BufferedInputStream cert) {
                Objects.requireNonNull(keyStore);
                Objects.requireNonNull(key);
                try {
@@ -178,7 +69,7 @@ class PkiUtils {
                }
        }
 
-       public static void loadTrustedCertificatePem(KeyStore keyStore,char[] keyStorePassword, Reader cert) {
+       public static void loadTrustedCertificatePem(KeyStore keyStore, char[] keyStorePassword, BufferedInputStream cert) {
                try {
                        X509Certificate certificate = loadPemCertificate(cert);
                        TrustedCertificateEntry trustedCertificateEntry = new TrustedCertificateEntry(certificate);
@@ -189,139 +80,43 @@ class PkiUtils {
        }
 
        public static PrivateKey loadPemPrivateKey(Reader reader, char[] keyPassword) {
-               try (PEMParser pemParser = new PEMParser(reader)) {
-                       JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BC_SECURITY_PROVIDER);
-                       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");
+               try {
+                       StringBuilder key = new StringBuilder();
+                       try (BufferedReader in = new BufferedReader(reader)) {
+                               String line = in.readLine();
+                               if (!"-----BEGIN PRIVATE KEY-----".equals(line))
+                                       throw new IllegalArgumentException("Not a PEM private key");
+                               lines: while ((line = in.readLine()) != null) {
+                                       if ("-----END PRIVATE KEY-----".equals(line))
+                                               break lines;
+                                       key.append(line);
+                               }
                        }
-                       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(SUN_SECURITY_PROVIDER)
-                                       .getCertificate(certHolder);
-                       return cert;
-               } catch (IOException | CertificateException e) {
-                       throw new RuntimeException("Cannot read private key", e);
-               }
-       }
+                       byte[] encoded = Base64.getDecoder().decode(key.toString());
 
-       public static void main(String[] args) throws Exception {
-               final String ALGORITHM = "RSA";
-               final String provider = "BC";
-               SecureRandom secureRandom = new SecureRandom();
-               long begin = System.currentTimeMillis();
-               for (int i = 512; i < 1024; i = i + 2) {
-                       try {
-                               KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, provider);
-                               keyGen.initialize(i, secureRandom);
-                               keyGen.generateKeyPair();
-                       } catch (Exception e) {
-                               System.err.println(i + " : " + e.getMessage());
-                       }
+                       KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+                       PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
+                       return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
+               } catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
+                       throw new RuntimeException("Cannot load PEM key", e);
                }
-               System.out.println((System.currentTimeMillis() - begin) + " ms");
-
-               // // String text = "a";
-               // String text =
-               // "testtesttesttesttesttesttesttesttesttesttesttesttesttesttest";
-               // try {
-               // System.out.println(text);
-               // PrivateKey privateKey;
-               // PublicKey publicKey;
-               // char[] password = "changeit".toCharArray();
-               // String alias = "CN=test";
-               // KeyStore keyStore = KeyStore.getInstance("pkcs12");
-               // File p12file = new File("test.p12");
-               // p12file.delete();
-               // if (!p12file.exists()) {
-               // keyStore.load(null);
-               // generateSelfSignedCertificate(keyStore, new X500Principal(alias),
-               // 513, password);
-               // try (OutputStream out = new FileOutputStream(p12file)) {
-               // keyStore.store(out, password);
-               // }
-               // }
-               // try (InputStream in = new FileInputStream(p12file)) {
-               // keyStore.load(in, password);
-               // privateKey = (PrivateKey) keyStore.getKey(alias, password);
-               // publicKey = keyStore.getCertificateChain(alias)[0].getPublicKey();
-               // }
-               // // KeyPair key;
-               // // final KeyPairGenerator keyGen =
-               // // KeyPairGenerator.getInstance(ALGORITHM);
-               // // keyGen.initialize(4096, new SecureRandom());
-               // // long begin = System.currentTimeMillis();
-               // // key = keyGen.generateKeyPair();
-               // // System.out.println((System.currentTimeMillis() - begin) + " ms");
-               // // keyStore.load(null);
-               // // keyStore.setKeyEntry("test", key.getPrivate(), password, null);
-               // // try(OutputStream out=new FileOutputStream(p12file)) {
-               // // keyStore.store(out, password);
-               // // }
-               // // privateKey = key.getPrivate();
-               // // publicKey = key.getPublic();
-               //
-               // Cipher encrypt = Cipher.getInstance(ALGORITHM);
-               // encrypt.init(Cipher.ENCRYPT_MODE, publicKey);
-               // byte[] encrypted = encrypt.doFinal(text.getBytes());
-               // String encryptedBase64 =
-               // Base64.getEncoder().encodeToString(encrypted);
-               // System.out.println(encryptedBase64);
-               // byte[] encryptedFromBase64 =
-               // Base64.getDecoder().decode(encryptedBase64);
-               //
-               // Cipher decrypt = Cipher.getInstance(ALGORITHM);
-               // decrypt.init(Cipher.DECRYPT_MODE, privateKey);
-               // byte[] decrypted = decrypt.doFinal(encryptedFromBase64);
-               // System.out.println(new String(decrypted));
-               // } catch (Exception e) {
-               // e.printStackTrace();
-               // }
 
        }
 
-       public static void createSelfSignedKeyStore(Path keyStorePath, char[] keyStorePassword, String keyStoreType) {
-               // for (Provider provider : Security.getProviders())
-               // System.out.println(provider.getName());
-               // File keyStoreFile = keyStorePath.toFile();
-               char[] keyPwd = Arrays.copyOf(keyStorePassword, keyStorePassword.length);
-               if (!Files.exists(keyStorePath)) {
-                       try {
-                               Files.createDirectories(keyStorePath.getParent());
-                               KeyStore keyStore = getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
-                               generateSelfSignedCertificate(keyStore,
-                                               new X500Principal("CN=" + InetAddress.getLocalHost().getHostName() + ",OU=UNSECURE,O=UNSECURE"),
-                                               1024, keyPwd);
-                               saveKeyStore(keyStorePath, keyStorePassword, keyStore);
-                               if (log.isDebugEnabled())
-                                       log.debug("Created self-signed unsecure keystore " + keyStorePath);
-                       } catch (Exception 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");
+       public static X509Certificate loadPemCertificate(BufferedInputStream in) {
+               try {
+                       CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
+                       @SuppressWarnings("unchecked")
+                       Collection<X509Certificate> certificates = (Collection<X509Certificate>) certificateFactory
+                                       .generateCertificates(in);
+                       if (certificates.isEmpty())
+                               throw new IllegalArgumentException("No certificate found");
+                       if (certificates.size() != 1)
+                               throw new IllegalArgumentException(certificates.size() + " certificates found");
+                       return certificates.iterator().next();
+               } catch (CertificateException e) {
+                       throw new IllegalStateException("cannot load certifciate", e);
                }
        }
-
 }