/* * Password Management Servlets (PWM) * http://www.pwm-project.org * * Copyright (c) 2006-2009 Novell, Inc. * Copyright (c) 2009-2017 The PWM Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package password.pwm.util.secure; import password.pwm.AppProperty; import password.pwm.config.Configuration; import password.pwm.error.ErrorInformation; import password.pwm.error.PwmError; import password.pwm.error.PwmOperationalException; import password.pwm.error.PwmUnrecoverableException; import password.pwm.http.PwmURL; import password.pwm.util.java.JavaHelper; import password.pwm.util.java.StringUtil; import password.pwm.util.logging.PwmLogger; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public abstract class X509Utils { private static final PwmLogger LOGGER = PwmLogger.forClass(X509Utils.class); public static X509Certificate[] readRemoteCertificates(final URI uri) throws PwmOperationalException { final String host = uri.getHost(); final int port = PwmURL.portForUriSchema(uri); return readRemoteCertificates(host, port); } public static X509Certificate[] readRemoteCertificates(final String host, final int port) throws PwmOperationalException { LOGGER.debug("ServerCertReader: beginning certificate read procedure to import certificates from host=" + host + ", port=" + port); final CertReaderTrustManager certReaderTm = new CertReaderTrustManager(); try { // use custom trust manager to read the certificates final SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, new TrustManager[]{certReaderTm}, new SecureRandom()); final SSLSocketFactory factory = ctx.getSocketFactory(); final SSLSocket sslSock = (SSLSocket) factory.createSocket(host,port); LOGGER.debug("ServerCertReader: socket established to host=" + host + ", port=" + port); sslSock.isConnected(); LOGGER.debug("ServerCertReader: connected to host=" + host + ", port=" + port); sslSock.startHandshake(); LOGGER.debug("ServerCertReader: handshake completed to host=" + host + ", port=" + port); sslSock.close(); LOGGER.debug("ServerCertReader: certificate information read from host=" + host + ", port=" + port); } catch (Exception e) { final StringBuilder errorMsg = new StringBuilder(); errorMsg.append("unable to read server certificates from host="); errorMsg.append(host).append(", port=").append(port); errorMsg.append(" error: "); errorMsg.append(e.getMessage()); LOGGER.error("ServerCertReader: " + errorMsg); final ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_CERTIFICATE_ERROR, errorMsg.toString(), new String[]{errorMsg.toString()}); throw new PwmOperationalException(errorInformation); } final X509Certificate[] certs = certReaderTm.getCertificates(); if (certs == null) { LOGGER.debug("ServerCertReader: unable to read certificates: null returned from CertReaderTrustManager.getCertificates()"); } else { for (final X509Certificate certificate : certs) { LOGGER.debug("ServerCertReader: read x509 Certificate from host=" + host + ", port=" + port + ": \n" + certificate.toString()); } } LOGGER.debug("ServerCertReader: process completed"); return certs; } public static boolean testIfLdapServerCertsInDefaultKeystore(final URI serverURI) { final String ldapHost = serverURI.getHost(); final int ldapPort = serverURI.getPort(); try { // use default socket factory to test if certs work with it final SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); final SSLSocket sslSock = (SSLSocket) factory.createSocket(ldapHost,ldapPort); sslSock.isConnected(); sslSock.getOutputStream().write("data!".getBytes()); sslSock.close(); return true; } catch (Exception e) { /* noop */ } return false; } private static class CertReaderTrustManager implements X509TrustManager { private X509Certificate[] certificates; public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {} public X509Certificate[] getAcceptedIssuers() { return null; } public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { certificates = chain; } public X509Certificate[] getCertificates() { return certificates; } } public static class PromiscuousTrustManager implements X509TrustManager { public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } public void checkClientTrusted(final X509Certificate[] certs, final String authType) { logMsg(certs,authType); } public void checkServerTrusted(final X509Certificate[] certs, final String authType) { logMsg(certs,authType); } private static void logMsg(final X509Certificate[] certs, final String authType) { if (certs != null) { for (final X509Certificate cert : certs) { try { LOGGER.warn("blind trusting certificate during authType=" + authType + ", subject=" + cert.getSubjectDN().toString()); } catch (Exception e) { LOGGER.error("error while decoding certificate: " + e.getMessage()); throw new IllegalStateException(e); } } } } } public static class CertMatchingTrustManager implements X509TrustManager { final X509Certificate[] certificates; final boolean validateTimestamps; public CertMatchingTrustManager(final Configuration config, final X509Certificate[] certificates) { this.certificates = certificates; validateTimestamps = config != null && Boolean.parseBoolean(config.readAppProperty(AppProperty.SECURITY_CERTIFICATES_VALIDATE_TIMESTAMPS)); } @Override public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s) throws CertificateException { } @Override public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s) throws CertificateException { if (x509Certificates == null) { final String errorMsg = "no certificates in configuration trust store for this operation"; throw new CertificateException(errorMsg); } for (final X509Certificate loopCert : x509Certificates) { boolean certTrusted = false; for (final X509Certificate storedCert : certificates) { if (loopCert.equals(storedCert)) { if (validateTimestamps) { loopCert.checkValidity(); } certTrusted = true; } } if (!certTrusted) { final String errorMsg = "server certificate {subject=" + loopCert.getSubjectDN().getName() + "} does not match a certificate in the configuration trust store."; throw new CertificateException(errorMsg); } //LOGGER.trace("trusting configured certificate: " + makeDebugText(loopCert)); } } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; //To change body of implemented methods use File | Settings | File Templates. } } public static String hexSerial(final X509Certificate x509Certificate) { String result = x509Certificate.getSerialNumber().toString(16).toUpperCase(); while (result.length() % 2 != 0) { result = "0" + result; } return result; } public static String makeDetailText(final X509Certificate x509Certificate) throws CertificateEncodingException, PwmUnrecoverableException { return x509Certificate.toString() + "\n:MD5 checksum: " + SecureEngine.hash(new ByteArrayInputStream(x509Certificate.getEncoded()), PwmHashAlgorithm.MD5) + "\n:SHA1 checksum: " + SecureEngine.hash(new ByteArrayInputStream(x509Certificate.getEncoded()), PwmHashAlgorithm.SHA1) + "\n:SHA2-256 checksum: " + SecureEngine.hash(new ByteArrayInputStream(x509Certificate.getEncoded()), PwmHashAlgorithm.SHA256) + "\n:SHA2-512 checksum: " + SecureEngine.hash(new ByteArrayInputStream(x509Certificate.getEncoded()), PwmHashAlgorithm.SHA512); } public static String makeDebugText(final X509Certificate x509Certificate) { return "subject=" + x509Certificate.getSubjectDN().getName() + ", serial=" + x509Certificate.getSerialNumber(); } enum CertDebugInfoKey { subject, serial, issuer, issueDate, expireDate, md5Hash, sha1Hash, sha512Hash, detail, } public enum DebugInfoFlag { IncludeCertificateDetail } public static List<Map<String,String>> makeDebugInfoMap(final X509Certificate[] certificates, final DebugInfoFlag... flags) { final List<Map<String,String>> returnList = new ArrayList<>(); if (certificates != null) { for (final X509Certificate cert : certificates) { returnList.add(makeDebugInfoMap(cert, flags)); } } return returnList; } public static Map<String,String> makeDebugInfoMap(final X509Certificate cert, final DebugInfoFlag... flags) { final Map<String,String> returnMap = new LinkedHashMap<>(); returnMap.put(CertDebugInfoKey.subject.toString(), cert.getSubjectDN().toString()); returnMap.put(CertDebugInfoKey.serial.toString(), X509Utils.hexSerial(cert)); returnMap.put(CertDebugInfoKey.issuer.toString(), cert.getIssuerDN().toString()); returnMap.put(CertDebugInfoKey.issueDate.toString(), JavaHelper.toIsoDate(cert.getNotBefore())); returnMap.put(CertDebugInfoKey.expireDate.toString(), JavaHelper.toIsoDate(cert.getNotAfter())); try { returnMap.put(CertDebugInfoKey.md5Hash.toString(), SecureEngine.hash(new ByteArrayInputStream(cert.getEncoded()), PwmHashAlgorithm.MD5)); returnMap.put(CertDebugInfoKey.sha1Hash.toString(), SecureEngine.hash(new ByteArrayInputStream(cert.getEncoded()), PwmHashAlgorithm.SHA1)); returnMap.put(CertDebugInfoKey.sha512Hash.toString(), SecureEngine.hash(new ByteArrayInputStream(cert.getEncoded()), PwmHashAlgorithm.SHA512)); if (JavaHelper.enumArrayContainsValue(flags, DebugInfoFlag.IncludeCertificateDetail)) { returnMap.put(CertDebugInfoKey.detail.toString(),X509Utils.makeDetailText(cert)); } } catch (PwmUnrecoverableException | CertificateEncodingException e) { LOGGER.warn("error generating hash for certificate: " + e.getMessage()); } return returnMap; } enum KeyDebugInfoKey { algorithm, format, } public static Map<String,String> makeDebugInfoMap(final PrivateKey key) { final Map<String,String> returnMap = new LinkedHashMap<>(); returnMap.put(KeyDebugInfoKey.algorithm.toString(), key.getAlgorithm()); returnMap.put(KeyDebugInfoKey.format.toString(), key.getFormat()); return returnMap; } public static X509Certificate certificateFromBase64(final String b64encodedStr) throws CertificateException, IOException { final CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); final byte[] certByteValue = StringUtil.base64Decode(b64encodedStr); return (X509Certificate)certificateFactory.generateCertificate(new ByteArrayInputStream(certByteValue)); } public static String certificateToBase64(final X509Certificate certificate) throws CertificateEncodingException { return StringUtil.base64Encode(certificate.getEncoded()); } }