package ch.ge.ve.offlineadmin.services;
/*-
* #%L
* Admin offline
* %%
* Copyright (C) 2015 - 2016 République et Canton de Genève
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import ch.ge.ve.commons.crypto.ballot.BallotCiphersProvider;
import ch.ge.ve.commons.crypto.utils.CertificateUtils;
import ch.ge.ve.commons.crypto.utils.SecureRandomFactory;
import ch.ge.ve.commons.properties.PropertyConfigurationException;
import ch.ge.ve.commons.properties.PropertyConfigurationService;
import ch.ge.ve.offlineadmin.exception.KeyGenerationRuntimeException;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import static ch.ge.ve.offlineadmin.util.SecurityConstants.*;
/**
* Generator of the keys used for the ballots box
*/
public class KeyGenerator {
public static final String DN_FORMAT = "cn=%1$s, ou=%2$s, o=%3$s, c=%4$s";
/**
* As per RFC-3280, section 4.1.2.2, a certificate serial number must be a positive integer fitting in 20 bytes.
* Therefore it's bitLength can be of <tt>160 (20 * 8)</tt> and max value is <tt>2^160 - 1</tt>.
*/
public static final int CERT_SERIAL_NUMBER_BIT_SIZE = 160;
private final SecureRandom secureRandom;
private final PropertyConfigurationService propertyConfigurationService;
public KeyGenerator(PropertyConfigurationService propertyConfigurationService) {
this.propertyConfigurationService = propertyConfigurationService;
secureRandom = SecureRandomFactory.createPRNG();
}
/**
* Generates the ballots box integrity key
*
* @return the integrity key
* @throws PropertyConfigurationException
*/
public SecretKey generateSecretKey() throws PropertyConfigurationException {
String algorithm = propertyConfigurationService.getConfigValue(BallotCiphersProvider.BALLOT_INTEGRITY_CHECK_CRYPTING_ALGORITHM);
Integer keyLengthInBits = propertyConfigurationService.getConfigValueAsInt(BallotCiphersProvider.BALLOT_INTEGRITY_CHECK_CRYPTING_KEY_SIZE);
byte[] keyBytes = new byte[keyLengthInBits / BITS_PER_BYTE];
secureRandom.nextBytes(keyBytes);
return new SecretKeySpec(keyBytes, algorithm);
}
/**
* Generates the key pair
*
* @return the key pair
* @throws KeyGenerationRuntimeException thrown if the configuration of the key pair is wrong
* @throws PropertyConfigurationException
*/
public KeyPair generateKeyPair() throws PropertyConfigurationException {
String algorithm = propertyConfigurationService.getConfigValue(BallotCiphersProvider.BALLOT_KEY_CRYPTING_ALGORITHM);
Integer keyLengthInBits = propertyConfigurationService.getConfigValueAsInt(BallotCiphersProvider.BALLOT_KEY_CRYPTING_KEY_SIZE);
KeyPairGenerator keyPairGenerator = null;
try {
keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new KeyGenerationRuntimeException("key pair configuration error", e);
}
keyPairGenerator.initialize(keyLengthInBits, secureRandom);
return keyPairGenerator.generateKeyPair();
}
/**
* Creates a {@link KeyStore} for the secret key
*
* @param privateKey the private key
* @param certificate the certificate
* @param password the password
* @return a keystore protected by password
* @throws KeyGenerationRuntimeException thrown if the key store cannot be created
* @throws PropertyConfigurationException
*/
public KeyStore createKeyStore(PrivateKey privateKey, X509Certificate certificate, char[] password) throws PropertyConfigurationException {
String certAlias = propertyConfigurationService.getConfigValue(BallotCiphersProvider.PRIVATE_KEY_ALIAS);
KeyStore store = null;
try {
store = CertificateUtils.createPKCS12KeyStore();
store.load(null);
X509Certificate[] chain = new X509Certificate[1];
chain[0] = certificate;
store.setKeyEntry(certAlias, privateKey, password, chain);
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
throw new KeyGenerationRuntimeException("keystore creation error", e);
}
return store;
}
/**
* Generates a certificate corresponding to the given key pair
*
* @param keyPair the key pair
* @return the certificate
* @throws KeyGenerationRuntimeException thrown if the x509 structure or certificate cannot be generated
* @throws PropertyConfigurationException
*/
public X509Certificate generateCertificate(KeyPair keyPair) throws PropertyConfigurationException {
try {
X509v3CertificateBuilder certificateBuilder = createCertificateBuilder(keyPair);
ContentSigner signer = createSigner(keyPair);
return (X509Certificate) createCertificate(certificateBuilder, signer);
} catch (OperatorCreationException | CertificateException | IOException e) {
throw new KeyGenerationRuntimeException("error when generating the x509 certificate", e);
}
}
private X509v3CertificateBuilder createCertificateBuilder(KeyPair keyPair) throws PropertyConfigurationException, CertIOException {
X500NameBuilder nameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
nameBuilder.addRDN(BCStyle.CN, propertyConfigurationService.getConfigValue(CERT_COMMON_NAME_PROPERTY));
nameBuilder.addRDN(BCStyle.O, propertyConfigurationService.getConfigValue(CERT_ORGANISATION_PROPERTY));
nameBuilder.addRDN(BCStyle.OU, propertyConfigurationService.getConfigValue(CERT_ORGANISATIONAL_UNIT_PROPERTY));
nameBuilder.addRDN(BCStyle.C, propertyConfigurationService.getConfigValue(CERT_COUNTRY_PROPERTY));
X500Name x500Name = nameBuilder.build();
BigInteger serial = new BigInteger(CERT_SERIAL_NUMBER_BIT_SIZE, SecureRandomFactory.createPRNG());
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
Date startDate = new Date();
Date endDate = Date.from(startDate.toInstant().plus(propertyConfigurationService.getConfigValueAsInt(CERT_VALIDITY_DAYS_PROPERTY), ChronoUnit.DAYS));
X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(x500Name, serial, startDate, endDate, x500Name, publicKeyInfo);
String certFriendlyName = propertyConfigurationService.getConfigValue(CERT_PRIVATE_FRIENDLY_NAME_PROPERTY);
certificateBuilder.addExtension(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, false, new DERBMPString(certFriendlyName));
return certificateBuilder;
}
private ContentSigner createSigner(KeyPair keyPair) throws PropertyConfigurationException, OperatorCreationException {
ContentSigner signer;
String hashAlgo = propertyConfigurationService.getConfigValue(CERT_HASH_ALGORITHM);
if (keyPair.getPrivate() instanceof RSAPrivateKey) {
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(hashAlgo + "withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(
new RSAKeyParameters(true, privateKey.getModulus(), privateKey.getPrivateExponent())
);
} else {
throw new KeyGenerationRuntimeException("Unsupported key type");
}
return signer;
}
private java.security.cert.Certificate createCertificate(X509v3CertificateBuilder certificateBuilder, ContentSigner signer) throws CertificateException, IOException {
X509CertificateHolder certificateHolder = certificateBuilder.build(signer);
return CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(certificateHolder.getEncoded()));
}
}