123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418 |
- /*
- * Password Management Servlets (PWM)
- * http://www.pwm-project.org
- *
- * Copyright (c) 2006-2009 Novell, Inc.
- * Copyright (c) 2009-2018 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 org.bouncycastle.asn1.x500.X500NameBuilder;
- import org.bouncycastle.asn1.x500.style.BCStyle;
- import org.bouncycastle.asn1.x509.BasicConstraints;
- import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
- import org.bouncycastle.asn1.x509.Extension;
- import org.bouncycastle.asn1.x509.KeyPurposeId;
- import org.bouncycastle.asn1.x509.KeyUsage;
- 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.operator.ContentSigner;
- import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
- import password.pwm.AppProperty;
- import password.pwm.PwmApplication;
- import password.pwm.PwmConstants;
- import password.pwm.bean.PrivateKeyCertificate;
- import password.pwm.config.Configuration;
- import password.pwm.config.PwmSetting;
- import password.pwm.config.StoredValue;
- import password.pwm.config.stored.StoredConfiguration;
- import password.pwm.config.value.PrivateKeyValue;
- import password.pwm.error.ErrorInformation;
- import password.pwm.error.PwmError;
- import password.pwm.error.PwmUnrecoverableException;
- import password.pwm.util.PasswordData;
- import password.pwm.util.java.JsonUtil;
- import password.pwm.util.java.StringUtil;
- import password.pwm.util.logging.PwmLogger;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- import java.math.BigInteger;
- import java.net.URI;
- import java.net.URISyntaxException;
- 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.X509Certificate;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Date;
- import java.util.Enumeration;
- import java.util.List;
- import java.util.concurrent.TimeUnit;
- public class HttpsServerCertificateManager
- {
- private static final PwmLogger LOGGER = PwmLogger.forClass( HttpsServerCertificateManager.class );
- private static boolean bouncyCastleInitialized;
- private static void initBouncyCastleProvider( )
- {
- if ( !bouncyCastleInitialized )
- {
- Security.addProvider( new BouncyCastleProvider() );
- bouncyCastleInitialized = true;
- }
- }
- public static KeyStore keyStoreForApplication(
- final PwmApplication pwmApplication,
- final PasswordData passwordData,
- final String alias
- )
- throws PwmUnrecoverableException
- {
- KeyStore keyStore = null;
- keyStore = exportKey( pwmApplication.getConfig(), KeyStoreFormat.JKS, passwordData, alias );
- if ( keyStore == null )
- {
- keyStore = makeSelfSignedCert( pwmApplication, passwordData, alias );
- }
- return keyStore;
- }
- private static KeyStore exportKey(
- final Configuration configuration,
- final KeyStoreFormat format,
- final PasswordData passwordData,
- final String alias
- )
- throws PwmUnrecoverableException
- {
- final PrivateKeyCertificate privateKeyCertificate = configuration.readSettingAsPrivateKey( PwmSetting.HTTPS_CERT );
- if ( privateKeyCertificate == null )
- {
- return null;
- }
- final KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection( passwordData.getStringValue().toCharArray() );
- try
- {
- final KeyStore keyStore = KeyStore.getInstance( format.toString() );
- //load of null is required to init keystore.
- keyStore.load( null, passwordData.getStringValue().toCharArray() );
- keyStore.setEntry(
- alias,
- new KeyStore.PrivateKeyEntry(
- privateKeyCertificate.getKey(),
- privateKeyCertificate.getCertificates().toArray( new X509Certificate[ privateKeyCertificate.getCertificates().size() ] )
- ),
- passwordProtection
- );
- return keyStore;
- }
- catch ( Exception e )
- {
- throw new PwmUnrecoverableException( new ErrorInformation( PwmError.CONFIG_FORMAT_ERROR, "error generating keystore file;: " + e.getMessage() ) );
- }
- }
- private static KeyStore makeSelfSignedCert( final PwmApplication pwmApplication, final PasswordData password, final String alias )
- throws PwmUnrecoverableException
- {
- final Configuration configuration = pwmApplication.getConfig();
- try
- {
- final SelfCertGenerator selfCertGenerator = new SelfCertGenerator( configuration );
- return selfCertGenerator.makeSelfSignedCert( pwmApplication, password, alias );
- }
- catch ( Exception e )
- {
- throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, "unable to generate self signed certificate: " + e.getMessage() ) );
- }
- }
- public static class StoredCertData implements Serializable
- {
- private final X509Certificate x509Certificate;
- private String keypairb64;
- public StoredCertData( final X509Certificate x509Certificate, final KeyPair keypair )
- throws IOException
- {
- this.x509Certificate = x509Certificate;
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- final ObjectOutputStream oos = new ObjectOutputStream( baos );
- oos.writeObject( keypair );
- final byte[] ba = baos.toByteArray();
- keypairb64 = StringUtil.base64Encode( ba );
- }
- public X509Certificate getX509Certificate( )
- {
- return x509Certificate;
- }
- public KeyPair getKeypair( )
- throws IOException, ClassNotFoundException
- {
- final byte[] ba = StringUtil.base64Decode( keypairb64 );
- final ByteArrayInputStream bais = new ByteArrayInputStream( ba );
- final ObjectInputStream ois = new ObjectInputStream( bais );
- return ( KeyPair ) ois.readObject();
- }
- }
- public static class SelfCertGenerator
- {
- private final Configuration config;
- public SelfCertGenerator( final Configuration config )
- {
- this.config = config;
- }
- public KeyStore makeSelfSignedCert( final PwmApplication pwmApplication, final PasswordData password, final String alias )
- throws Exception
- {
- final String cnName = makeSubjectName();
- final KeyStore keyStore = KeyStore.getInstance( "jks" );
- keyStore.load( null, password.getStringValue().toCharArray() );
- StoredCertData storedCertData = pwmApplication.readAppAttribute( PwmApplication.AppAttribute.HTTPS_SELF_CERT, StoredCertData.class );
- if ( storedCertData != null )
- {
- if ( !cnName.equals( storedCertData.getX509Certificate().getSubjectDN().getName() ) )
- {
- LOGGER.info( "replacing stored self cert, subject name does not match configured site url" );
- storedCertData = null;
- }
- else if ( storedCertData.getX509Certificate().getNotBefore().after( new Date() ) )
- {
- LOGGER.info( "replacing stored self cert, not-before date is in the future" );
- storedCertData = null;
- }
- else if ( storedCertData.getX509Certificate().getNotAfter().before( new Date() ) )
- {
- LOGGER.info( "replacing stored self cert, not-after date is in the past" );
- storedCertData = null;
- }
- }
- if ( storedCertData == null )
- {
- storedCertData = makeSelfSignedCert( cnName );
- pwmApplication.writeAppAttribute( PwmApplication.AppAttribute.HTTPS_SELF_CERT, storedCertData );
- }
- keyStore.setKeyEntry(
- alias,
- storedCertData.getKeypair().getPrivate(),
- password.getStringValue().toCharArray(),
- new X509Certificate[]
- {
- storedCertData.getX509Certificate(),
- }
- );
- return keyStore;
- }
- public String makeSubjectName( )
- throws Exception
- {
- String cnName = PwmConstants.PWM_APP_NAME.toLowerCase() + ".example.com";
- {
- final String siteURL = config.readSettingAsString( PwmSetting.PWM_SITE_URL );
- if ( siteURL != null && !siteURL.isEmpty() )
- {
- try
- {
- final URI uri = new URI( siteURL );
- if ( uri.getHost() != null && !uri.getHost().isEmpty() )
- {
- cnName = uri.getHost();
- }
- }
- catch ( URISyntaxException e )
- {
- // disregard
- }
- }
- }
- return cnName;
- }
- public StoredCertData makeSelfSignedCert( final String cnName )
- throws Exception
- {
- initBouncyCastleProvider();
- LOGGER.debug( "creating self-signed certificate with cn of " + cnName );
- final KeyPair keyPair = generateRSAKeyPair( config );
- final long futureSeconds = Long.parseLong( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_FUTURESECONDS ) );
- final X509Certificate certificate = generateV3Certificate( keyPair, cnName, futureSeconds );
- return new StoredCertData( certificate, keyPair );
- }
- public static X509Certificate generateV3Certificate( final KeyPair pair, final String cnValue, final long futureSeconds )
- throws Exception
- {
- final X500NameBuilder subjectName = new X500NameBuilder( BCStyle.INSTANCE );
- subjectName.addRDN( BCStyle.CN, cnValue );
- final SimpleDateFormat formatter = new SimpleDateFormat( "yyyyMMddhhmmss" );
- final String serNumStr = formatter.format( new Date( System.currentTimeMillis() ) );
- final BigInteger serialNumber = new BigInteger( serNumStr );
- // 2 days in the past
- final Date notBefore = new Date( System.currentTimeMillis() - TimeUnit.DAYS.toMillis( 2 ) );
- final Date notAfter = new Date( System.currentTimeMillis() + ( futureSeconds * 1000 ) );
- final X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
- subjectName.build(),
- serialNumber,
- notBefore,
- notAfter,
- subjectName.build(),
- pair.getPublic()
- );
- // false == not a CA
- final BasicConstraints basic = new BasicConstraints( false );
- // OID, critical, ASN.1 encoded value
- certGen.addExtension( Extension.basicConstraints, true, basic.getEncoded() );
- // sign and key encipher
- final KeyUsage keyUsage = new KeyUsage( KeyUsage.digitalSignature | KeyUsage.keyEncipherment );
- // OID, critical, ASN.1 encoded value
- certGen.addExtension( Extension.keyUsage, true, keyUsage.getEncoded() );
- // server authentication
- final ExtendedKeyUsage extKeyUsage = new ExtendedKeyUsage( KeyPurposeId.id_kp_serverAuth );
- // OID, critical, ASN.1 encoded value
- certGen.addExtension( Extension.extendedKeyUsage, true, extKeyUsage.getEncoded() );
- final ContentSigner sigGen = new JcaContentSignerBuilder( "SHA256WithRSAEncryption" ).setProvider( "BC" ).build( pair.getPrivate() );
- return new JcaX509CertificateConverter().setProvider( "BC" ).getCertificate( certGen.build( sigGen ) );
- }
- static KeyPair generateRSAKeyPair( final Configuration config )
- throws Exception
- {
- final int keySize = Integer.parseInt( config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_KEY_SIZE ) );
- final String keyAlg = config.readAppProperty( AppProperty.SECURITY_HTTPSSERVER_SELF_ALG );
- final KeyPairGenerator kpGen = KeyPairGenerator.getInstance( keyAlg, "BC" );
- kpGen.initialize( keySize, new SecureRandom() );
- return kpGen.generateKeyPair();
- }
- }
- public enum KeyStoreFormat
- {
- PKCS12,
- JKS,
- }
- public static void importKey(
- final StoredConfiguration storedConfiguration,
- final KeyStoreFormat keyStoreFormat,
- final InputStream inputStream,
- final PasswordData password,
- final String alias
- ) throws PwmUnrecoverableException
- {
- final char[] charPassword = password == null ? new char[ 0 ] : password.getStringValue().toCharArray();
- final PrivateKeyCertificate privateKeyCertificate;
- try
- {
- final KeyStore keyStore = KeyStore.getInstance( keyStoreFormat.toString() );
- keyStore.load( inputStream, charPassword );
- final String effectiveAlias;
- {
- final List<String> allAliases = new ArrayList<>();
- for ( final Enumeration enu = keyStore.aliases(); enu.hasMoreElements(); )
- {
- final String value = ( String ) enu.nextElement();
- allAliases.add( value );
- }
- effectiveAlias = allAliases.size() == 1 ? allAliases.iterator().next() : alias;
- }
- final KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection( charPassword );
- final KeyStore.PrivateKeyEntry entry = ( KeyStore.PrivateKeyEntry ) keyStore.getEntry( effectiveAlias, passwordProtection );
- if ( entry == null )
- {
- final String errorMsg = "unable to import https key entry with alias '" + alias + "'";
- throw new PwmUnrecoverableException( new ErrorInformation(
- PwmError.ERROR_CERTIFICATE_ERROR,
- errorMsg,
- new String[]
- {
- "no key entry alias '" + alias + "' in keystore",
- }
- ) );
- }
- final PrivateKey key = entry.getPrivateKey();
- final List<X509Certificate> certificates = Arrays.asList( ( X509Certificate[] ) entry.getCertificateChain() );
- LOGGER.debug( "importing certificate chain: " + JsonUtil.serializeCollection( X509Utils.makeDebugInfoMap( certificates ) ) );
- privateKeyCertificate = new PrivateKeyCertificate( certificates, key );
- }
- catch ( Exception e )
- {
- final String errorMsg = "unable to load configured https certificate: " + e.getMessage();
- final String[] errorDetail = new String[]
- {
- e.getMessage(),
- };
- throw new PwmUnrecoverableException( new ErrorInformation( PwmError.ERROR_CERTIFICATE_ERROR, errorMsg, errorDetail ) );
- }
- final StoredValue storedValue = new PrivateKeyValue( privateKeyCertificate );
- storedConfiguration.writeSetting( PwmSetting.HTTPS_CERT, storedValue, null );
- }
- }
|