/*******************************************************************************
* (C) Copyright 2014 Teknux.org (http://teknux.org/).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* "Pierre PINON"
* "Francois EYL"
* "Laurent MARCHAL"
*
*******************************************************************************/
package org.teknux.jettybootstrap.keystore;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Date;
import java.util.Objects;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
public class JettyKeystoreGeneratorBuilder extends AbstractJettyKeystore {
public static final String SIGNATURE_ALGORITHM_SHA256WITHRSA = "SHA256WithRSAEncryption";
public static final String DEFAULT_ALGORITHM = ALGORITHM_RSA;
public static final String DEFAULT_SIGNATURE_ALGORITHM = SIGNATURE_ALGORITHM_SHA256WITHRSA;
public static final int DEFAULT_DATE_NOT_BEFORE_NUMBER_OF_DAYS = 30;
public static final int DEFAULT_DATE_NOT_AFTER_NUMBER_OF_DAYS = 3650;
private static final Long DAY_IN_MILLIS = 1000L * 60 * 60 * 24;
private String algorithm = DEFAULT_ALGORITHM;
private String signatureAlgorithm = DEFAULT_SIGNATURE_ALGORITHM;
private String rdnOuValue;
private String rdnOValue;
private int dateNotBeforeNumberOfDays = DEFAULT_DATE_NOT_BEFORE_NUMBER_OF_DAYS;
private int dateNotAfterNumberOfDays = DEFAULT_DATE_NOT_AFTER_NUMBER_OF_DAYS;
public String getAlgorithm() {
return algorithm;
}
public JettyKeystoreGeneratorBuilder setAlgorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public String getSignatureAlgorithm() {
return signatureAlgorithm;
}
public JettyKeystoreGeneratorBuilder setSignatureAlgorithm(String signatureAlgorithm) {
this.signatureAlgorithm = signatureAlgorithm;
return this;
}
public String getRdnOuValue() {
return rdnOuValue;
}
public JettyKeystoreGeneratorBuilder setRdnOuValue(String rdnOuValue) {
this.rdnOuValue = rdnOuValue;
return this;
}
public String getRdnOValue() {
return rdnOValue;
}
public JettyKeystoreGeneratorBuilder setRdnOValue(String rdnOValue) {
this.rdnOValue = rdnOValue;
return this;
}
public int getDateNotBeforeNumberOfDays() {
return dateNotBeforeNumberOfDays;
}
public JettyKeystoreGeneratorBuilder setDateNotBeforeNumberOfDays(int dateNotBeforeNumberOfDays) {
this.dateNotBeforeNumberOfDays = dateNotBeforeNumberOfDays;
return this;
}
public int getDateNotAfterNumberOfDays() {
return dateNotAfterNumberOfDays;
}
public JettyKeystoreGeneratorBuilder setDateNotAfterNumberOfDays(int dateNotAfterNumberOfDays) {
this.dateNotAfterNumberOfDays = dateNotAfterNumberOfDays;
return this;
}
public KeyStore build(String domainName, String alias, String password) throws JettyKeystoreException {
return build(domainName, alias, password, true, false);
}
public KeyStore build(String domainName, String alias, String password, boolean checkValidity, boolean verifySignature) throws JettyKeystoreException {
Objects.requireNonNull(domainName, "DomainName is required");
Objects.requireNonNull(alias, "Alias is required");
Objects.requireNonNull(password, "Password is required");
KeyPair keyPair = generateKeyPair(algorithm);
Certificate certificate = generateCertificate(keyPair, domainName, signatureAlgorithm, rdnOuValue, rdnOValue, dateNotBeforeNumberOfDays, dateNotAfterNumberOfDays);
KeyStore keystore = createKeyStore(keyPair.getPrivate(), certificate, alias, password);
if (checkValidity | verifySignature) {
checkValidity(keystore, alias, checkValidity, verifySignature);
}
return keystore;
}
private static KeyPair generateKeyPair(String algorithm) throws JettyKeystoreException {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new JettyKeystoreException(JettyKeystoreException.ERROR_CREATE_KEYS, "Can not generate private and public keys", e);
}
}
private static Certificate generateCertificate(KeyPair keyPair, String domainName, String signatureAlgorithm, String rdnOuValue, String rdnOValue,
int dateNotBeforeNumberOfDays, int dateNotAfterNumberOfDays) throws JettyKeystoreException {
X500NameBuilder issuerX500Namebuilder = new X500NameBuilder(BCStyle.INSTANCE);
if (rdnOuValue != null) {
issuerX500Namebuilder.addRDN(BCStyle.OU, rdnOuValue);
}
if (rdnOValue != null) {
issuerX500Namebuilder.addRDN(BCStyle.O, rdnOValue);
}
X500Name issuer = issuerX500Namebuilder.addRDN(BCStyle.CN, domainName).build();
BigInteger serial = BigInteger.valueOf(Math.abs(new SecureRandom().nextInt()));
Date dateNotBefore = new Date(System.currentTimeMillis() - (dateNotBeforeNumberOfDays * DAY_IN_MILLIS));
Date dateNotAfter = new Date(System.currentTimeMillis() + (dateNotAfterNumberOfDays * DAY_IN_MILLIS));
X500NameBuilder subjectX500Namebuilder = new X500NameBuilder(BCStyle.INSTANCE);
if (rdnOuValue != null) {
subjectX500Namebuilder.addRDN(BCStyle.OU, rdnOuValue);
}
if (rdnOValue != null) {
subjectX500Namebuilder.addRDN(BCStyle.O, rdnOValue);
}
X500Name subject = subjectX500Namebuilder.addRDN(BCStyle.CN, domainName).build();
SubjectPublicKeyInfo publicKeyInfo = new SubjectPublicKeyInfo(ASN1Sequence.getInstance(keyPair.getPublic().getEncoded()));
X509v3CertificateBuilder x509v3CertificateBuilder = new X509v3CertificateBuilder(issuer, serial, dateNotBefore, dateNotAfter, subject, publicKeyInfo);
Provider provider = new BouncyCastleProvider();
try {
ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(provider).build(keyPair.getPrivate());
return new JcaX509CertificateConverter().setProvider(provider).getCertificate(x509v3CertificateBuilder.build(signer));
} catch (OperatorCreationException | CertificateException e) {
throw new JettyKeystoreException(JettyKeystoreException.ERROR_CREATE_CERTIFICATE, "Can not generate certificate", e);
}
}
public static void saveKeyStore(KeyStore keystore, File file, String password) throws JettyKeystoreException {
try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
keystore.store(fileOutputStream, password.toCharArray());
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
throw new JettyKeystoreException(JettyKeystoreException.ERROR_SAVE_KEYSTORE, "Can not save keystore file", e);
}
}
public void checkValidity(boolean checkValidity, boolean verifySignature) throws JettyKeystoreException {
build("testDomain", "testAlias", "testPassword", checkValidity, verifySignature);
}
}