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())); } }