/* * Copyright (C) 2011-2012 Intel Corporation * All rights reserved. */ package com.intel.mtwilson; import com.intel.dcsg.cpg.crypto.CryptographyException; import com.intel.dcsg.cpg.crypto.RsaCredential; import com.intel.dcsg.cpg.crypto.RsaCredentialX509; import com.intel.dcsg.cpg.crypto.RsaUtil; import com.intel.dcsg.cpg.crypto.SimpleKeystore; import com.intel.dcsg.cpg.io.ByteArrayResource; import com.intel.mtwilson.datatypes.ApiClientCreateRequest; import com.intel.dcsg.cpg.io.FileResource; import com.intel.dcsg.cpg.io.Resource; import java.io.*; import java.net.URL; import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Properties; import java.util.Set; import javax.net.ssl.TrustManager; import com.intel.mtwilson.api.*; import com.intel.dcsg.cpg.tls.policy.impl.InsecureTlsPolicy; import com.intel.dcsg.cpg.tls.policy.TlsPolicy; import com.intel.dcsg.cpg.tls.policy.TlsUtil; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.MapConfiguration; import com.fasterxml.jackson.databind.ObjectMapper; import com.intel.dcsg.cpg.crypto.Sha1Digest; import com.intel.dcsg.cpg.crypto.Sha256Digest; import com.intel.mtwilson.attestation.client.jaxrs.CaCertificates; import com.intel.mtwilson.user.management.client.jaxrs.RegisterUsers; import com.intel.mtwilson.user.management.client.jaxrs.UserLoginCertificates; import com.intel.mtwilson.user.management.client.jaxrs.Users; import com.intel.mtwilson.user.management.rest.v2.model.RegisterUserWithCertificate; import com.intel.mtwilson.user.management.rest.v2.model.User; import com.intel.mtwilson.user.management.rest.v2.model.UserLoginCertificate; import java.util.LinkedHashMap; import java.util.Locale; import java.util.logging.Level; //import org.codehaus.jackson.map.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The Java "keytool" command can be used to manage the contents of the KeyStore * independently. However, there is the additional problem of maintaining a list * of which "trusted" (identity only) certificate is authorized for what purpose * -- which the keystore does not provide. * Option 1. Overload the "alias" to annotate the trusted purpose of each certificate * Option 2. Maintain a separate (signed) file with the list of trusted purpose of each certificate * * For ApiClient usage, the SimpleKeystore class was written for Mt Wilson specific * needs. * * This class remains available so we don't break existing integrations but * it should not be used. * * Another alternative to this class for general use is Java's "keytool" command. * * Set the "mtwilson.api.keystore" property to point to this file (by default * keystore.jks) * * These methods are conveniences for using the KeyStore class but many of the * functionality needed for the api client are in the SimpleKeystore class. * * @since 0.5.2 * @author jbuhacoff */ public class KeystoreUtil { private static final Logger log = LoggerFactory.getLogger(KeystoreUtil.class); /** * Does not close the input stream ; caller must close it. * * @param keystoreIn * @param keystorePassword * @return * @throws KeyStoreException * @throws IOException * @throws NoSuchAlgorithmException * @throws CertificateException */ public static KeyStore fromInputStream(InputStream keystoreIn, String keystorePassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // KeyStoreException. ks.load(keystoreIn, keystorePassword.toCharArray()); // IOException, NoSuchAlgorithmException, CertificateException return ks; } public static KeyStore fromFilename(String keystoreFilename, String keystorePassword) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException { File file = new File(keystoreFilename); if( file.exists() ) { try(InputStream in = new FileInputStream(keystoreFilename)) { return fromInputStream(in, keystorePassword); } } else { try(InputStream in = KeystoreUtil.class.getResourceAsStream(keystoreFilename)) { return fromInputStream(in, keystorePassword); } } } /** * * @param keystoreIn * @param keystorePassword * @return * @throws KeyStoreException * @throws IOException * @throws NoSuchAlgorithmException * @throws CertificateException * @deprecated use fromInputStream instead */ public static KeyStore open(InputStream keystoreIn, String keystorePassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // KeyStoreException. // load keystore try { ks.load(keystoreIn, keystorePassword.toCharArray()); // IOException, NoSuchAlgorithmException, CertificateException } finally { if (keystoreIn != null) { keystoreIn.close(); } } return ks; } /** * * @param config * @return * @throws KeyStoreException * @throws IOException * @throws NoSuchAlgorithmException * @throws CertificateException * @deprecated use the SimpleKeystore instead */ public static KeyStore open(Configuration config) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { String filename = config.getString("mtwilson.api.keystore", "keystore.jks"); String password = config.getString("mtwilson.api.keystore.password", "changeit"); File file = new File(filename); if( file.exists() ) { try(InputStream in = new FileInputStream(filename)) { return fromInputStream(in, password); } } else { try(InputStream in = KeystoreUtil.class.getResourceAsStream(filename)) { return fromInputStream(in, password); } } } /** * * @param keystore * @param certAlias * @return * @throws NoSuchAlgorithmException * @throws UnrecoverableEntryException * @throws KeyStoreException * @throws CertificateEncodingException */ public static X509Certificate loadX509Certificate(KeyStore keystore, String certAlias) throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException, CertificateEncodingException { // load the certificate KeyStore.TrustedCertificateEntry certEntry = (KeyStore.TrustedCertificateEntry)keystore.getEntry(certAlias, null); if( certEntry != null ) { Certificate myCertificate = certEntry.getTrustedCertificate(); if( myCertificate instanceof X509Certificate ) { //if( "X.509".equals(myCertificate.getType()) ) { return (X509Certificate)myCertificate; } throw new IllegalArgumentException("Certificate is not X509: "+myCertificate.getType()); //PublicKey myPublicKey = pkEntry.getCertificate().getPublicKey(); //return new RsaCredential(myPrivateKey, myPublicKey); } throw new KeyStoreException("Cannot load certificate with alias: "+certAlias); } /** * * @param keystore * @param keyAlias * @param keyPassword * @return * @throws NoSuchAlgorithmException * @throws UnrecoverableEntryException * @throws KeyStoreException * @throws CertificateEncodingException * @deprecated use the SimpleKeystore instead */ public static RsaCredentialX509 loadX509(KeyStore keystore, String keyAlias, String keyPassword) throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException, CertificateEncodingException, com.intel.dcsg.cpg.crypto.CryptographyException { // load the key pair KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry)keystore.getEntry(keyAlias, new KeyStore.PasswordProtection(keyPassword.toCharArray())); if( pkEntry != null ) { PrivateKey myPrivateKey = pkEntry.getPrivateKey(); Certificate myCertificate = pkEntry.getCertificate(); if( "X.509".equals(myCertificate.getType()) ) { return new RsaCredentialX509(myPrivateKey, (X509Certificate)myCertificate); } throw new IllegalArgumentException("Key has a certificate that is not X509: "+myCertificate.getType()); //PublicKey myPublicKey = pkEntry.getCertificate().getPublicKey(); //return new RsaCredential(myPrivateKey, myPublicKey); } // key pair not found throw new KeyStoreException("Cannot load key with alias: "+keyAlias); } /** * * @param config * @return * @throws KeyStoreException * @throws IOException * @throws NoSuchAlgorithmException * @throws CertificateException * @throws UnrecoverableEntryException * @deprecated use the SimpleKeystore instead */ public static RsaCredential loadX509(Configuration config) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableEntryException, com.intel.dcsg.cpg.crypto.CryptographyException { String keystore = config.getString("mtwilson.api.keystore", "keystore.jks"); String keystorePassword = config.getString("mtwilson.api.keystore.password", "changeit"); KeyStore ks = fromFilename(keystore, keystorePassword); RsaCredential rsa = loadX509(ks, config.getString("mtwilson.api.key.alias", "mykey"), config.getString("mtwilson.api.key.password", "changeit")); return rsa; } /** * The configuration must have the following properties: * * Keystore relative filename, absolute filename, or location in classpath: * mtwilson.api.keystore=keystore.jks * * Keystore password: * mtwilson.api.keystore.password=changeit * * Private key alias: * mtwilson.api.key.alias=mykey * * Private key password: * mtwilson.api.key.password=changeit * * @param config * @return * @throws KeyStoreException * @throws IOException * @throws NoSuchAlgorithmException * @throws CertificateException * @throws UnrecoverableEntryException * @deprecated use the SimpleKeystore instead */ public static RsaCredential fromKeystore(Configuration config) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableEntryException, com.intel.dcsg.cpg.crypto.CryptographyException { KeyStore keystore = open(config); return loadX509(keystore, config.getString("mtwilson.api.key.alias", "mykey"), config.getString("mtwilson.api.key.password", "changeit")); } /* public static void create(File file, String password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { KeyStore keystore = KeyStore.getInstance("JKS"); // KeyStoreException. keystore.load(null, null); keystore.store(new FileOutputStream(file), password.toCharArray()); // IOException, NoSuchAlgorithmException, CertificateException } */ public static void save(KeyStore keystore, String password, File outputFile) throws FileNotFoundException, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { try(OutputStream out = new FileOutputStream(outputFile)) { keystore.store(out, password.toCharArray()); // FileNotFoundException, KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException } } /* public static File save(KeyStore keystore, String password) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException { File tmp = File.createTempFile("keystore", ".jks"); // IOException keystore.store(new FileOutputStream(tmp), password.toCharArray()); // KeyStoreException, NoSuchAlgorithmException, CertificateException return tmp; } * * */ /** * Helper function for multi-user applications. This function creates a new * keystore with the given username and password, generates an RSA key and * X509 certificate for the user, registers the certificate with Mt Wilson, * and downloads the Mt Wilson SSL Certificate and SAML Signing Certificate * to the new keystore. * * The path to the new keystore will be "directory/username.jks" * * @param directory where the keystore should be saved * @param username arbitrary, needs to be unique only within the directory, should not contain any path-forming characters such as .. or slashes * @param password arbitrary * @param server URL to the Mt Wilson server like https://mtwilson.example.com:443 * @param roles like new String[] { Role.Attestation.toString(), Role.Whitelist.toString() } * @return the new keystore * @throws Exception * @deprecated this method uses an INSECURE policy for TLS and should not be used; replace with a newer method which requires caller to provide either Properties to give to a TlsPolicyFactory or a TlsPolicy object to be used directly */ public static SimpleKeystore createUserInDirectory(File directory, String username, String password, URL server, String[] roles) throws IOException, ApiException, CryptographyException, ClientException { if( username.contains("..") || username.contains(File.separator) || username.contains(" ") ) { throw new IllegalArgumentException("Username must not include path-forming characters"); } File keystoreFile = new File(directory.getAbsoluteFile() + File.separator + username + ".jks"); FileResource resource = new FileResource(keystoreFile); return createUserInResource(resource, username, password, server, roles); } /** * * @param resource * @param username * @param password * @return * @throws CryptographyException * @throws IOException */ private static SimpleKeystore createUserKeystoreInResource(Resource resource, String username, String password) throws CryptographyException, IOException { try { // create the keystore and a new credential SimpleKeystore keystore = new SimpleKeystore(resource, password); // KeyManagementException KeyPair keypair = RsaUtil.generateRsaKeyPair(RsaUtil.MINIMUM_RSA_KEY_SIZE); // NoSuchAlgorithmException X509Certificate certificate = RsaUtil.generateX509Certificate(/*"CN="+*/username, keypair, RsaUtil.DEFAULT_RSA_KEY_EXPIRES_DAYS); // GeneralSecurityException keystore.addKeyPairX509(keypair.getPrivate(), certificate, username, password); // KeyManagementException keystore.save(); // KeyStoreException, IOException, CertificateException return keystore; } catch(KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException e) { throw new CryptographyException("Cannot create keystore", e); } } /** * Helper function for multi-user applications. This function creates a new * keystore with the given username and password, generates an RSA key and * X509 certificate for the user, registers the certificate with Mt Wilson, * and downloads the Mt Wilson SSL Certificate and SAML Signing Certificate * to the new keystore. Also any CA certificates available will be added * to the keystore. * * The underlying Resource implementation determines the location where the * keystore will be saved. * * Implies Tls Policy INSECURE to allow automatic download & saving of the server SSL certificate. * * @param resource like FileResource or ByteArrayResource to which the keystore will be saved * @param username arbitrary, needs to be unique only within the resource container, and any restrictions on allowed characters are determined by the resource implmenetation * @param password arbitrary * @param server URL to the Mt Wilson server like https://mtwilson.example.com:443 * @param roles like new String[] { Role.Attestation.toString(), Role.Whitelist.toString() } * @return the new keystore, which is also saved in the resource * @throws Exception * @since 0.5.4 * @deprecated this method uses an INSECURE policy for TLS and should not be used; replace with a newer method which requires caller to provide either Properties to give to a TlsPolicyFactory or a TlsPolicy object to be used directly */ public static SimpleKeystore createUserInResource(Resource resource, String username, String password, URL server, String[] roles) throws IOException, ApiException, CryptographyException, ClientException { return createUserInResource(resource, username, password, server, new InsecureTlsPolicy(), roles); } public static SimpleKeystore createUserInResource(Resource resource, String username, String password, URL server, TlsPolicy tlsPolicy, String[] roles) throws IOException, ApiException, CryptographyException, ClientException { return createUserInResource(resource, username, password, server, tlsPolicy, roles, "TLS"); } public static SimpleKeystore createUserInResource(Resource resource, String username, String password, URL server, TlsPolicy tlsPolicy, String[] roles, String tlsProtocol) throws IOException, ApiException, CryptographyException, ClientException { URL baseUrl = new URL(server.getProtocol() + "://" + server.getAuthority()); SimpleKeystore keystore = createUserKeystoreInResource(resource, username, password); log.trace("URL Protocol: {}", baseUrl.getProtocol()); if( "https".equals(baseUrl.getProtocol()) ) { TlsUtil.addSslCertificatesToKeystore(keystore, baseUrl, tlsProtocol); //CryptographyException, IOException } if(log.isTraceEnabled()) { try { String[] aliases = keystore.aliases(); for(String alias : aliases) { log.trace("Certificate: "+keystore.getX509Certificate(alias).getSubjectX500Principal().getName()); } } catch(KeyStoreException | NoSuchAlgorithmException | UnrecoverableEntryException | CertificateEncodingException e) { log.trace("cannot display keystore: "+e.toString()); } } ApiClient c; try { // register the user with the server RsaCredentialX509 rsaCredential = keystore.getRsaCredentialX509(username, password); // CryptographyException, FileNotFoundException // c = new ApiClient(server, rsaCredential, keystore, config); //ClientException c = new ApiClient(server, rsaCredential, keystore, tlsPolicy); //ClientException ApiClientCreateRequest user = new ApiClientCreateRequest(); user.setCertificate(rsaCredential.getCertificate().getEncoded()); //CertificateEncodingException user.setRoles(roles); c.register(user); //IOException } catch(IOException e) { throw new IOException("Cannot register user", e); } catch(Exception e) { throw new CryptographyException("Cannot register user", e); } // download root ca certs from the server try { Set<X509Certificate> cacerts = c.getRootCaCertificates(); for(X509Certificate cacert : cacerts) { try { log.debug("Adding CA Certificate with alias {}, subject {}, fingerprint {}, from server {}", cacert.getSubjectX500Principal().getName(), cacert.getSubjectX500Principal().getName(), DigestUtils.shaHex(cacert.getEncoded()), server.getHost()); keystore.addTrustedCaCertificate(cacert, cacert.getSubjectX500Principal().getName()); } catch(CertificateEncodingException | KeyManagementException e) { log.error(e.toString()); } } } catch(IOException | ApiException | SignatureException e) { log.error(e.toString()); } // download privacy ca certs from server try { Set<X509Certificate> cacerts = c.getPrivacyCaCertificates(); for(X509Certificate cacert : cacerts) { try { log.debug("Adding Privacy CA Certificate with alias {}, subject {}, fingerprint {}, from server {}", cacert.getSubjectX500Principal().getName(), cacert.getSubjectX500Principal().getName(), DigestUtils.shaHex(cacert.getEncoded()), server.getHost()); keystore.addTrustedCaCertificate(cacert, cacert.getSubjectX500Principal().getName()); } catch(CertificateEncodingException | KeyManagementException e) { log.error(e.toString()); } } } catch(IOException | ApiException | SignatureException e) { log.error(e.toString()); } // download saml ca certs from server try { Set<X509Certificate> cacerts = c.getSamlCertificates(); for(X509Certificate cert : cacerts) { try { if( cert.getBasicConstraints() == -1 ) { // -1 indicates the certificate is not a CA cert; so we add it as the saml cert keystore.addTrustedSamlCertificate(cert, server.getHost()); log.debug("Added SAML Certificate with alias {}, subject {}, fingerprint {}, from server {}", cert.getSubjectX500Principal().getName(), cert.getSubjectX500Principal().getName(), DigestUtils.shaHex(cert.getEncoded()), server.getHost() ); } else { keystore.addTrustedCaCertificate(cert, cert.getSubjectX500Principal().getName()); log.debug("Added SAML CA Certificate with alias {}, subject {}, fingerprint {}, from server {}", cert.getSubjectX500Principal().getName(), cert.getSubjectX500Principal().getName(), DigestUtils.shaHex(cert.getEncoded()), server.getHost()); } } catch(KeyManagementException | CertificateEncodingException e) { log.error(e.toString()); } } } catch(IOException | ApiException | SignatureException e) { log.error(e.toString()); } try { keystore.save(); return keystore; } catch(KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { throw new CryptographyException("Cannot save keystore to resource: "+e.toString(), e); } } /** * Helper function for multi-user applications. This function loads the * keystore using the provided username and password, and creates an * ApiClient object using the credentials in the keystore. * * @param directory where the keystore is located * @param username the keystore filename (excluding the .jks extension) * @param password that was set when the keystore was created * @param server URL to the Mt Wilson server like https://mtwilson.example.com:443 * @return an ApiClient object configured with the credentials in the keystore * @throws Exception */ public static ApiClient clientForUserInDirectory(File directory, String username, String password, URL server) throws ClientException, ClientException, FileNotFoundException, FileNotFoundException, KeyStoreException, NoSuchAlgorithmException, NoSuchAlgorithmException, UnrecoverableEntryException, CertificateEncodingException, KeyManagementException, com.intel.dcsg.cpg.crypto.CryptographyException { if( username.contains("..") || username.contains(File.separator) || username.contains(" ") ) { throw new IllegalArgumentException("Username must not include path-forming characters"); } File keystoreFile = new File(directory.getAbsoluteFile() + File.separator + username + ".jks"); FileResource resource = new FileResource(keystoreFile); return clientForUserInResource(resource, username, password, server); } /** * Helper function for multi-user applications. This function loads the * keystore using the provided username and password, and creates an * ApiClient object using the credentials in the keystore. * * @param resource like FileResource or ByteArrayResource from which the keystore will be loaded * @param username the keystore filename (excluding the .jks extension) * @param password that was set when the keystore was created * @param server URL to the Mt Wilson server like https://mtwilson.example.com:443 * @return an ApiClient object configured with the credentials in the keystore * @throws Exception * @since 0.5.4 */ public static ApiClient clientForUserInResource(Resource resource, String username, String password, URL server) throws ClientException, FileNotFoundException, FileNotFoundException, KeyStoreException, KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException, CertificateEncodingException, KeyManagementException, com.intel.dcsg.cpg.crypto.CryptographyException { return clientForUserInResource(resource, username, password, server, new InsecureTlsPolicy()); } public static ApiClient clientForUserInResource(Resource resource, String username, String password, URL server, TlsPolicy tlsPolicy) throws ClientException, FileNotFoundException, FileNotFoundException, KeyStoreException, KeyStoreException, NoSuchAlgorithmException, NoSuchAlgorithmException, UnrecoverableEntryException, UnrecoverableEntryException, CertificateEncodingException, KeyManagementException, com.intel.dcsg.cpg.crypto.CryptographyException { SimpleKeystore keystore = new SimpleKeystore(resource, password); RsaCredentialX509 rsaCredential = keystore.getRsaCredentialX509(username, password); ApiClient c = new ApiClient(server, rsaCredential, keystore, tlsPolicy); return c; } }