/* * * 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.pkcs12; import java.io.IOException; import java.io.InputStream; 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.Security; import java.security.Signature; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.DSAPrivateKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.RSAPrivateKey; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.crypto.NoSuchPaddingException; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Signer; import org.bouncycastle.crypto.params.AsymmetricKeyParameter; import org.bouncycastle.crypto.signers.DSADigestSigner; import org.bouncycastle.crypto.signers.DSASigner; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.RSADigestSigner; import org.bouncycastle.jcajce.provider.asymmetric.dsa.DSAUtil; import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.bc.BcContentSignerBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.common.util.CollectionUtil; import org.xipki.commons.common.util.ParamUtil; import org.xipki.commons.security.ConcurrentContentSigner; import org.xipki.commons.security.DefaultConcurrentContentSigner; import org.xipki.commons.security.SignatureSigner; import org.xipki.commons.security.XiSecurityConstants; import org.xipki.commons.security.exception.XiSecurityException; import org.xipki.commons.security.util.AlgorithmUtil; import org.xipki.commons.security.util.KeyUtil; import org.xipki.commons.security.util.SignerUtil; import org.xipki.commons.security.util.X509Util; /** * @author Lijun Liao * @since 2.0.0 */ public class SoftTokenContentSignerBuilder { // CHECKSTYLE:SKIP private static class RSAContentSignerBuilder extends BcContentSignerBuilder { private final boolean useNssForPss; private RSAContentSignerBuilder(final AlgorithmIdentifier signatureAlgId, final boolean useNssForPss) throws NoSuchAlgorithmException, NoSuchPaddingException { super(signatureAlgId, AlgorithmUtil.extractDigesetAlgId(signatureAlgId)); this.useNssForPss = useNssForPss; } protected Signer createSigner(final AlgorithmIdentifier sigAlgId, final AlgorithmIdentifier digAlgId) throws OperatorCreationException { if (!AlgorithmUtil.isRSASigAlgId(sigAlgId)) { throw new OperatorCreationException( "the given algorithm is not a valid RSA signature algirthm '" + sigAlgId.getAlgorithm().getId() + "'"); } if (!PKCSObjectIdentifiers.id_RSASSA_PSS.equals(sigAlgId.getAlgorithm())) { Digest dig = digestProvider.get(digAlgId); return new RSADigestSigner(dig); } if (useNssForPss) { if (Security.getProvider(XiSecurityConstants.PROVIDER_NAME_NSS) == null) { throw new OperatorCreationException("Provider " + XiSecurityConstants.PROVIDER_CORENAME_NSS + " is not available"); } try { NssPlainRSASigner plainSigner = new NssPlainRSASigner(); return SignerUtil.createPSSRSASigner(sigAlgId, plainSigner); } catch (XiSecurityException | NoSuchPaddingException | NoSuchProviderException | NoSuchAlgorithmException ex) { throw new OperatorCreationException( "could not create PSS signer using underlying provider " + XiSecurityConstants.PROVIDER_NAME_NSS, ex); } } else { try { return SignerUtil.createPSSRSASigner(sigAlgId); } catch (XiSecurityException ex) { throw new OperatorCreationException(ex.getMessage(), ex); } } } } // class RSAContentSignerBuilder // CHECKSTYLE:SKIP private static class DSAContentSignerBuilder extends BcContentSignerBuilder { private final boolean plain; private DSAContentSignerBuilder(final AlgorithmIdentifier signatureAlgId, final boolean plain) throws NoSuchAlgorithmException { super(signatureAlgId, AlgorithmUtil.extractDigesetAlgId(signatureAlgId)); this.plain = plain; } protected Signer createSigner(final AlgorithmIdentifier sigAlgId, final AlgorithmIdentifier digAlgId) throws OperatorCreationException { if (!AlgorithmUtil.isDSASigAlg(sigAlgId)) { throw new OperatorCreationException( "the given algorithm is not a valid DSA signature algirthm '" + sigAlgId.getAlgorithm().getId() + "'"); } Digest dig = digestProvider.get(digAlgId); DSASigner dsaSigner = new DSASigner(); return plain ? new DSAPlainDigestSigner(dsaSigner, dig) : new DSADigestSigner(dsaSigner, dig); } } // class DSAContentSignerBuilder // CHECKSTYLE:SKIP private static class ECDSAContentSignerBuilder extends BcContentSignerBuilder { private final boolean plain; private ECDSAContentSignerBuilder(final AlgorithmIdentifier signatureAlgId, final boolean plain) throws NoSuchAlgorithmException { super(signatureAlgId, AlgorithmUtil.extractDigesetAlgId(signatureAlgId)); this.plain = plain; } protected Signer createSigner(final AlgorithmIdentifier sigAlgId, final AlgorithmIdentifier digAlgId) throws OperatorCreationException { if (!AlgorithmUtil.isECSigAlg(sigAlgId)) { throw new OperatorCreationException( "the given algorithm is not a valid EC signature algorithm '" + sigAlgId.getAlgorithm().getId() + "'"); } Digest dig = digestProvider.get(digAlgId); ECDSASigner dsaSigner = new ECDSASigner(); return plain ? new DSAPlainDigestSigner(dsaSigner, dig) : new DSADigestSigner(dsaSigner, dig); } } // class ECDSAContentSignerBuilder private static final Logger LOG = LoggerFactory.getLogger(SoftTokenContentSignerBuilder.class); private final PrivateKey key; private final PublicKey publicKey; private final X509Certificate[] certificateChain; public SoftTokenContentSignerBuilder(final PrivateKey privateKey, final PublicKey publicKey) throws XiSecurityException { this.key = ParamUtil.requireNonNull("privateKey", privateKey); this.publicKey = ParamUtil.requireNonNull("publicKey", publicKey); this.certificateChain = null; } public SoftTokenContentSignerBuilder(final String keystoreType, final InputStream keystoreStream, final char[] keystorePassword, final String keyname, final char[] keyPassword, final X509Certificate[] certificateChain) throws XiSecurityException { if (!("PKCS12".equalsIgnoreCase(keystoreType) || "JKS".equalsIgnoreCase(keystoreType))) { throw new IllegalArgumentException("unsupported keystore type: " + keystoreType); } ParamUtil.requireNonNull("keystoreStream", keystoreStream); ParamUtil.requireNonNull("keystorePassword", keystorePassword); ParamUtil.requireNonNull("keyPassword", keyPassword); try { KeyStore ks = KeyUtil.getKeyStore(keystoreType); ks.load(keystoreStream, keystorePassword); 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 XiSecurityException("unknown key named " + tmpKeyname); } } this.key = (PrivateKey) ks.getKey(tmpKeyname, keyPassword); if (!(key instanceof RSAPrivateKey || key instanceof DSAPrivateKey || key instanceof ECPrivateKey)) { throw new XiSecurityException("unsupported key " + key.getClass().getName()); } Set<Certificate> caCerts = new HashSet<>(); X509Certificate cert; if (certificateChain != null && certificateChain.length > 0) { cert = certificateChain[0]; final int n = certificateChain.length; if (n > 1) { for (int i = 1; i < n; i++) { caCerts.add(certificateChain[i]); } } } else { cert = (X509Certificate) ks.getCertificate(tmpKeyname); } Certificate[] certsInKeystore = ks.getCertificateChain(tmpKeyname); if (certsInKeystore.length > 1) { for (int i = 1; i < certsInKeystore.length; i++) { caCerts.add(certsInKeystore[i]); } } this.publicKey = cert.getPublicKey(); this.certificateChain = X509Util.buildCertPath(cert, caCerts); } catch (KeyStoreException | NoSuchProviderException | NoSuchAlgorithmException | CertificateException | IOException | UnrecoverableKeyException | ClassCastException ex) { throw new XiSecurityException(ex.getMessage(), ex); } } public ConcurrentContentSigner createSigner(final AlgorithmIdentifier signatureAlgId, final int parallelism, final SecureRandom random) throws XiSecurityException, NoSuchPaddingException { ParamUtil.requireNonNull("signatureAlgId", signatureAlgId); ParamUtil.requireMin("parallelism", parallelism, 1); List<ContentSigner> signers = new ArrayList<>(parallelism); ASN1ObjectIdentifier algOid = signatureAlgId.getAlgorithm(); final String provName = XiSecurityConstants.PROVIDER_NAME_NSS; if (Security.getProvider(provName) != null && !(key instanceof ECPrivateKey)) { String algoName; try { algoName = AlgorithmUtil.getSignatureAlgoName(signatureAlgId); } catch (NoSuchAlgorithmException ex) { throw new XiSecurityException(ex.getMessage()); } boolean useGivenProvider = true; if (algOid.equals(PKCSObjectIdentifiers.id_RSASSA_PSS)) { BcContentSignerBuilder signerBuilder; if (!(key instanceof RSAPrivateKey)) { throw new XiSecurityException("unsupported key " + key.getClass().getName() + " for RSAPSS"); } try { AsymmetricKeyParameter keyparam; try { signerBuilder = new RSAContentSignerBuilder(signatureAlgId, true); keyparam = SignerUtil.generateRSAPrivateKeyParameter((RSAPrivateKey) key); } catch (NoSuchAlgorithmException ex) { throw new OperatorCreationException("no such algorithm", ex); } for (int i = 0; i < parallelism; i++) { if (random != null) { signerBuilder.setSecureRandom(random); } ContentSigner signer = signerBuilder.build(keyparam); signers.add(signer); } } catch (Exception ex) { useGivenProvider = false; signers.clear(); } } else { try { for (int i = 0; i < parallelism; i++) { Signature signature = Signature.getInstance(algoName, provName); signature.initSign(key); if (i == 0) { signature.update(new byte[]{1, 2, 3, 4}); signature.sign(); } ContentSigner signer = new SignatureSigner(signatureAlgId, signature, key); signers.add(signer); } } catch (Exception ex) { useGivenProvider = false; signers.clear(); } } if (useGivenProvider) { LOG.info("use {} to sign {} signature", provName, algoName); } else { LOG.info("could not use {} to sign {} signature", provName, algoName); } } if (CollectionUtil.isEmpty(signers)) { BcContentSignerBuilder signerBuilder; AsymmetricKeyParameter keyparam; try { if (key instanceof RSAPrivateKey) { keyparam = SignerUtil.generateRSAPrivateKeyParameter((RSAPrivateKey) key); signerBuilder = new RSAContentSignerBuilder(signatureAlgId, false); } else if (key instanceof DSAPrivateKey) { keyparam = DSAUtil.generatePrivateKeyParameter(key); signerBuilder = new DSAContentSignerBuilder(signatureAlgId, AlgorithmUtil.isDSAPlainSigAlg(signatureAlgId)); } else if (key instanceof ECPrivateKey) { keyparam = ECUtil.generatePrivateKeyParameter(key); signerBuilder = new ECDSAContentSignerBuilder(signatureAlgId, AlgorithmUtil.isDSAPlainSigAlg(signatureAlgId)); } else { throw new XiSecurityException("unsupported key " + key.getClass().getName()); } } catch (InvalidKeyException ex) { throw new XiSecurityException("invalid key", ex); } catch (NoSuchAlgorithmException ex) { throw new XiSecurityException("no such algorithm", ex); } for (int i = 0; i < parallelism; i++) { if (random != null) { signerBuilder.setSecureRandom(random); } ContentSigner signer; try { signer = signerBuilder.build(keyparam); } catch (OperatorCreationException ex) { throw new XiSecurityException("operator creation error", ex); } signers.add(signer); } } ConcurrentContentSigner concurrentSigner; try { concurrentSigner = new DefaultConcurrentContentSigner(signers, key); } catch (NoSuchAlgorithmException ex) { throw new XiSecurityException(ex.getMessage(), ex); } if (certificateChain != null) { concurrentSigner.setCertificateChain(certificateChain); } else { concurrentSigner.setPublicKey(publicKey); } return concurrentSigner; } // createSigner public X509Certificate getCert() { return (certificateChain != null && certificateChain.length > 0) ? certificateChain[0] : null; } public X509Certificate[] getCertificateChain() { return certificateChain; } public PrivateKey getKey() { return key; } }