/* * * Copyright (c) 2013 - 2017 Lijun Liao * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License version 3 * as published by the Free Software Foundation with the addition of the * following permission added to Section 15 as permitted in Section 7(a): * * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY * THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT * OF THIRD PARTY RIGHTS. * * 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 Affero 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/>. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License. * * You can be released from the requirements of the license by purchasing * a commercial license. Buying such a license is mandatory as soon as you * develop commercial activities involving the XiPKI software without * disclosing the source code of your own applications. * * For more information, please contact Lijun Liao at this * address: lijun.liao@gmail.com */ package org.xipki.commons.security; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import org.bouncycastle.asn1.pkcs.CertificationRequest; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.crypto.RuntimeCryptoException; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.operator.ContentVerifierProvider; import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.DigestAlgorithmIdentifierFinder; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.bc.BcContentVerifierProviderBuilder; import org.bouncycastle.operator.bc.BcDSAContentVerifierProviderBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.PKCSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.common.ObjectCreationException; import org.xipki.commons.common.util.LogUtil; import org.xipki.commons.common.util.ParamUtil; import org.xipki.commons.password.PasswordResolver; import org.xipki.commons.security.bcbugfix.XipkiECContentVerifierProviderBuilder; import org.xipki.commons.security.bcbugfix.XipkiRSAContentVerifierProviderBuilder; import org.xipki.commons.security.exception.NoIdleSignerException; import org.xipki.commons.security.util.AlgorithmUtil; import org.xipki.commons.security.util.KeyUtil; import org.xipki.commons.security.util.X509Util; /** * @author Lijun Liao * @since 2.0.0 */ public class SecurityFactoryImpl extends AbstractSecurityFactory { private static final Logger LOG = LoggerFactory.getLogger(SecurityFactoryImpl.class); private static final DigestAlgorithmIdentifierFinder DIGESTALG_IDENTIFIER_FINDER = new DefaultDigestAlgorithmIdentifierFinder(); private static final Map<String, BcContentVerifierProviderBuilder> VERIFIER_PROVIDER_BUILDER = new HashMap<>(); private int defaultSignerParallelism = 32; private PasswordResolver passwordResolver; private SignerFactoryRegister signerFactoryRegister; private boolean strongRandom4KeyEnabled; private boolean strongRandom4SignEnabled; public SecurityFactoryImpl() { } public boolean isStrongRandom4KeyEnabled() { return strongRandom4KeyEnabled; } public void setStrongRandom4KeyEnabled(final boolean strongRandom4KeyEnabled) { this.strongRandom4KeyEnabled = strongRandom4KeyEnabled; } public boolean isStrongRandom4SignEnabled() { return strongRandom4SignEnabled; } public void setStrongRandom4SignEnabled(final boolean strongRandom4SignEnabled) { this.strongRandom4SignEnabled = strongRandom4SignEnabled; } @Override public ConcurrentContentSigner createSigner(final String type, final SignerConf conf, final X509Certificate[] certificateChain) throws ObjectCreationException { ConcurrentContentSigner signer = signerFactoryRegister.newSigner(this, type, conf, certificateChain); validateSigner(signer, type, conf); return signer; } @Override public ContentVerifierProvider getContentVerifierProvider(final PublicKey publicKey) throws InvalidKeyException { ParamUtil.requireNonNull("publicKey", publicKey); String keyAlg = publicKey.getAlgorithm().toUpperCase(); BcContentVerifierProviderBuilder builder = VERIFIER_PROVIDER_BUILDER.get(keyAlg); if (builder == null) { if ("RSA".equals(keyAlg)) { builder = new XipkiRSAContentVerifierProviderBuilder(DIGESTALG_IDENTIFIER_FINDER); } else if ("DSA".equals(keyAlg)) { builder = new BcDSAContentVerifierProviderBuilder(DIGESTALG_IDENTIFIER_FINDER); } else if ("EC".equals(keyAlg) || "ECDSA".equals(keyAlg)) { builder = new XipkiECContentVerifierProviderBuilder(DIGESTALG_IDENTIFIER_FINDER); } else { throw new InvalidKeyException("unknown key algorithm of the public key " + keyAlg); } VERIFIER_PROVIDER_BUILDER.put(keyAlg, builder); } AsymmetricKeyParameter keyParam = KeyUtil.generatePublicKeyParameter(publicKey); try { return builder.build(keyParam); } catch (OperatorCreationException ex) { throw new InvalidKeyException("could not build ContentVerifierProvider: " + ex.getMessage(), ex); } } @Override public PublicKey generatePublicKey(final SubjectPublicKeyInfo subjectPublicKeyInfo) throws InvalidKeyException { try { return KeyUtil.generatePublicKey(subjectPublicKeyInfo); } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { throw new InvalidKeyException(ex.getMessage(), ex); } } @Override public boolean verifyPopo(final CertificationRequest csr, final AlgorithmValidator algoValidator) { return verifyPopo(new PKCS10CertificationRequest(csr), algoValidator); } @Override public boolean verifyPopo(final PKCS10CertificationRequest csr, final AlgorithmValidator algoValidator) { if (algoValidator != null) { AlgorithmIdentifier algId = csr.getSignatureAlgorithm(); if (!algoValidator.isAlgorithmPermitted(algId)) { String algoName; try { algoName = AlgorithmUtil.getSignatureAlgoName(algId); } catch (NoSuchAlgorithmException ex) { algoName = algId.getAlgorithm().getId(); } LOG.error("POPO signature algorithm {} not permitted", algoName); return false; } } try { SubjectPublicKeyInfo pkInfo = csr.getSubjectPublicKeyInfo(); PublicKey pk = KeyUtil.generatePublicKey(pkInfo); ContentVerifierProvider cvp = getContentVerifierProvider(pk); return csr.isSignatureValid(cvp); } catch (InvalidKeyException | PKCSException | NoSuchAlgorithmException | InvalidKeySpecException ex) { LogUtil.error(LOG, ex, "could not validate POPO of CSR"); return false; } } @Override public int getDefaultSignerParallelism() { return defaultSignerParallelism; } public void setDefaultSignerParallelism(final int defaultSignerParallelism) { this.defaultSignerParallelism = ParamUtil.requireMin("defaultSignerParallelism", defaultSignerParallelism, 1); } public void setSignerFactoryRegister(final SignerFactoryRegister signerFactoryRegister) { this.signerFactoryRegister = signerFactoryRegister; } public void setPasswordResolver(final PasswordResolver passwordResolver) { this.passwordResolver = passwordResolver; } @Override public PasswordResolver getPasswordResolver() { return passwordResolver; } @Override public KeyCertPair createPrivateKeyAndCert(final String type, final SignerConf conf, final X509Certificate cert) throws ObjectCreationException { conf.putConfEntry("parallelism", Integer.toString(1)); X509Certificate[] certs = null; if (cert != null) { certs = new X509Certificate[]{cert}; } ConcurrentContentSigner signer = signerFactoryRegister.newSigner(this, type, conf, certs); return new KeyCertPair(signer.getPrivateKey(), signer.getCertificate()); } @Override public SecureRandom getRandom4Key() { return getSecureRandom(strongRandom4KeyEnabled); } @Override public SecureRandom getRandom4Sign() { return getSecureRandom(strongRandom4SignEnabled); } @Override public byte[] extractMinimalKeyStore(final String keystoreType, final byte[] keystoreBytes, final String keyname, final char[] password, final X509Certificate[] newCertChain) throws KeyStoreException { ParamUtil.requireNonBlank("keystoreType", keystoreType); ParamUtil.requireNonNull("keystoreBytes", keystoreBytes); try { KeyStore ks = KeyUtil.getKeyStore(keystoreType); ks.load(new ByteArrayInputStream(keystoreBytes), password); String tmpKeyname = keyname; if (tmpKeyname == null) { Enumeration<String> aliases = ks.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); if (ks.isKeyEntry(alias)) { tmpKeyname = alias; break; } } } else { if (!ks.isKeyEntry(tmpKeyname)) { throw new KeyStoreException("unknown key named " + tmpKeyname); } } Enumeration<String> aliases = ks.aliases(); int numAliases = 0; while (aliases.hasMoreElements()) { aliases.nextElement(); numAliases++; } Certificate[] certs; if (newCertChain == null || newCertChain.length < 1) { if (numAliases == 1) { return keystoreBytes; } certs = ks.getCertificateChain(tmpKeyname); } else { certs = newCertChain; } KeyStore newKs = KeyUtil.getKeyStore(keystoreType); newKs.load(null, password); PrivateKey key = (PrivateKey) ks.getKey(tmpKeyname, password); newKs.setKeyEntry(tmpKeyname, key, password, certs); ByteArrayOutputStream bout = new ByteArrayOutputStream(); newKs.store(bout, password); byte[] bytes = bout.toByteArray(); bout.close(); return bytes; } catch (Exception ex) { if (ex instanceof KeyStoreException) { throw (KeyStoreException) ex; } else { throw new KeyStoreException(ex.getMessage(), ex); } } } // method extractMinimalKeyStore private static SecureRandom getSecureRandom(final boolean strong) { if (!strong) { return new SecureRandom(); } try { return SecureRandom.getInstanceStrong(); } catch (NoSuchAlgorithmException ex) { throw new RuntimeCryptoException( "could not get strong SecureRandom: " + ex.getMessage()); } } private static void validateSigner(final ConcurrentContentSigner signer, final String signerType, final SignerConf signerConf) throws ObjectCreationException { if (signer.getPublicKey() == null) { return; } String signatureAlgoName; try { signatureAlgoName = AlgorithmUtil.getSignatureAlgoName( signer.getAlgorithmIdentifier()); } catch (NoSuchAlgorithmException ex) { throw new ObjectCreationException(ex.getMessage(), ex); } try { byte[] dummyContent = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Signature verifier = Signature.getInstance(signatureAlgoName, "BC"); byte[] signatureValue = signer.sign(dummyContent); verifier.initVerify(signer.getPublicKey()); verifier.update(dummyContent); boolean valid = verifier.verify(signatureValue); if (!valid) { StringBuilder sb = new StringBuilder(); sb.append("private key and public key does not match, "); sb.append("key type='").append(signerType).append("'; "); String pwd = signerConf.getConfValue("password"); if (pwd != null) { signerConf.putConfEntry("password", "****"); } signerConf.putConfEntry("algo", signatureAlgoName); sb.append("conf='").append(signerConf.getConf()); X509Certificate cert = signer.getCertificate(); if (cert != null) { String subject = X509Util.getRfc4519Name(cert.getSubjectX500Principal()); sb.append("', certificate subject='").append(subject).append("'"); } throw new ObjectCreationException(sb.toString()); } } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException | NoIdleSignerException ex) { throw new ObjectCreationException(ex.getMessage(), ex); } } // method validateSigner }