/* * * 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.pki.scep.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CRLException; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509CRL; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Set; import org.bouncycastle.asn1.DERNull; import org.bouncycastle.asn1.DERPrintableString; import org.bouncycastle.asn1.cms.Attribute; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.cms.SignedData; import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; import org.bouncycastle.asn1.pkcs.CertificationRequest; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.pkcs.RSAPublicKey; import org.bouncycastle.asn1.pkcs.RSASSAPSSparams; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.Certificate; import org.bouncycastle.asn1.x509.CertificateList; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extensions; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; import org.bouncycastle.cert.CertIOException; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.jce.X509KeyUsage; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xipki.commons.common.util.ParamUtil; import org.xipki.pki.scep.crypto.KeyUsage; import org.xipki.pki.scep.crypto.ScepHashAlgoType; /** * @author Lijun Liao * @since 2.0.0 */ public class ScepUtil { private static final Logger LOG = LoggerFactory.getLogger(ScepUtil.class); private static final long MIN_IN_MS = 60L * 1000; private static final long DAY_IN_MS = 24L * 60 * MIN_IN_MS; private static CertificateFactory certFact; private static Object certFactLock = new Object(); private ScepUtil() { } public static SubjectPublicKeyInfo createSubjectPublicKeyInfo(final PublicKey publicKey) throws IOException { ParamUtil.requireNonNull("publicKey", publicKey); if (publicKey instanceof java.security.interfaces.RSAPublicKey) { java.security.interfaces.RSAPublicKey rsaPubKey = (java.security.interfaces.RSAPublicKey) publicKey; return new SubjectPublicKeyInfo( new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE), new RSAPublicKey(rsaPubKey.getModulus(), rsaPubKey.getPublicExponent())); } else { throw new IllegalArgumentException("unsupported public key " + publicKey); } } public static PKCS10CertificationRequest generateRequest(final PrivateKey privatekey, final SubjectPublicKeyInfo subjectPublicKeyInfo, final X500Name subjectDn, final Map<ASN1ObjectIdentifier, ASN1Encodable> attributes) throws OperatorCreationException { ParamUtil.requireNonNull("privatekey", privatekey); ParamUtil.requireNonNull("subjectPublicKeyInfo", subjectPublicKeyInfo); ParamUtil.requireNonNull("subjectDn", subjectDn); PKCS10CertificationRequestBuilder csrBuilder = new PKCS10CertificationRequestBuilder(subjectDn, subjectPublicKeyInfo); if (attributes != null) { for (ASN1ObjectIdentifier attrType : attributes.keySet()) { csrBuilder.addAttribute(attrType, attributes.get(attrType)); } } ContentSigner contentSigner = new JcaContentSignerBuilder( getSignatureAlgorithm(privatekey, ScepHashAlgoType.SHA1)).build(privatekey); return csrBuilder.build(contentSigner); } public static PKCS10CertificationRequest generateRequest(final PrivateKey privatekey, final SubjectPublicKeyInfo subjectPublicKeyInfo, final X500Name subjectDn, final String challengePassword, final List<Extension> extensions) throws OperatorCreationException { ParamUtil.requireNonNull("privatekey", privatekey); ParamUtil.requireNonNull("subjectPublicKeyInfo", subjectPublicKeyInfo); ParamUtil.requireNonNull("subjectDn", subjectDn); Map<ASN1ObjectIdentifier, ASN1Encodable> attributes = new HashMap<ASN1ObjectIdentifier, ASN1Encodable>(); if (challengePassword != null && !challengePassword.isEmpty()) { DERPrintableString asn1Pwd = new DERPrintableString(challengePassword); attributes.put(PKCSObjectIdentifiers.pkcs_9_at_challengePassword, asn1Pwd); } if (extensions != null && !extensions.isEmpty()) { Extensions asn1Extensions = new Extensions(extensions.toArray(new Extension[0])); attributes.put(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, asn1Extensions); } return generateRequest(privatekey, subjectPublicKeyInfo, subjectDn, attributes); } public static X509Certificate generateSelfsignedCert(final CertificationRequest csr, final PrivateKey identityKey) throws CertificateException { ParamUtil.requireNonNull("csr", csr); return generateSelfsignedCert(csr.getCertificationRequestInfo().getSubject(), csr.getCertificationRequestInfo().getSubjectPublicKeyInfo(), identityKey); } public static X509Certificate generateSelfsignedCert(final X500Name subjectDn, final PublicKey pubKey, final PrivateKey identityKey) throws CertificateException { SubjectPublicKeyInfo pubKeyInfo; try { pubKeyInfo = createSubjectPublicKeyInfo(pubKey); } catch (IOException ex) { throw new CertificateException(ex.getMessage(), ex); } return generateSelfsignedCert(subjectDn, pubKeyInfo, identityKey); } public static X509Certificate generateSelfsignedCert(final X500Name subjectDn, final SubjectPublicKeyInfo pubKeyInfo, final PrivateKey identityKey) throws CertificateException { ParamUtil.requireNonNull("subjectDn", subjectDn); ParamUtil.requireNonNull("pubKeyInfo", pubKeyInfo); ParamUtil.requireNonNull("identityKey", identityKey); Date notBefore = new Date(System.currentTimeMillis() - 5 * MIN_IN_MS); Date notAfter = new Date(notBefore.getTime() + 30 * DAY_IN_MS); X509v3CertificateBuilder certGenerator = new X509v3CertificateBuilder(subjectDn, BigInteger.ONE, notBefore, notAfter, subjectDn, pubKeyInfo); X509KeyUsage ku = new X509KeyUsage( X509KeyUsage.digitalSignature | X509KeyUsage.dataEncipherment | X509KeyUsage.keyAgreement | X509KeyUsage.keyEncipherment); try { certGenerator.addExtension(Extension.keyUsage, true, ku); } catch (CertIOException ex) { throw new CertificateException( "could not generate self-signed certificate: " + ex.getMessage(), ex); } String sigAlgorithm = ScepUtil.getSignatureAlgorithm(identityKey, ScepHashAlgoType.SHA1); ContentSigner contentSigner; try { contentSigner = new JcaContentSignerBuilder(sigAlgorithm).build(identityKey); } catch (OperatorCreationException ex) { throw new CertificateException("error while creating signer", ex); } Certificate asn1Cert = certGenerator.build(contentSigner).toASN1Structure(); return toX509Cert(asn1Cert); } // method generateSelfsignedCert /** * The first one is a non-CA certificate if there exists one non-CA certificate. */ public static List<X509Certificate> getCertsFromSignedData(final SignedData signedData) throws CertificateException { ParamUtil.requireNonNull("signedData", signedData); ASN1Set set = signedData.getCertificates(); if (set == null) { return Collections.emptyList(); } final int n = set.size(); if (n == 0) { return Collections.emptyList(); } List<X509Certificate> certs = new LinkedList<X509Certificate>(); X509Certificate eeCert = null; for (int i = 0; i < n; i++) { X509Certificate cert; try { cert = toX509Cert(Certificate.getInstance(set.getObjectAt(i))); } catch (IllegalArgumentException ex) { throw new CertificateException(ex); } if (eeCert == null && cert.getBasicConstraints() == -1) { eeCert = cert; } else { certs.add(cert); } } if (eeCert != null) { certs.add(0, eeCert); } return certs; } // method getCertsFromSignedData public static X509CRL getCrlFromPkiMessage(final SignedData signedData) throws CRLException { ParamUtil.requireNonNull("signedData", signedData); ASN1Set set = signedData.getCRLs(); if (set == null || set.size() == 0) { return null; } try { CertificateList cl = CertificateList.getInstance(set.getObjectAt(0)); return ScepUtil.toX509Crl(cl); } catch (IllegalArgumentException | CertificateException | CRLException ex) { throw new CRLException(ex); } } public static String getSignatureAlgorithm(final PrivateKey key, final ScepHashAlgoType hashAlgo) { ParamUtil.requireNonNull("key", key); ParamUtil.requireNonNull("hashAlgo", hashAlgo); String algorithm = key.getAlgorithm(); if ("RSA".equalsIgnoreCase(algorithm)) { return hashAlgo.getName() + "withRSA"; } else { throw new UnsupportedOperationException( "getSignatureAlgorithm() for non-RSA is not supported yet."); } } public static X509Certificate toX509Cert( final org.bouncycastle.asn1.x509.Certificate asn1Cert) throws CertificateException { byte[] encodedCert; try { encodedCert = asn1Cert.getEncoded(); } catch (IOException ex) { throw new CertificateEncodingException("could not get encoded certificate", ex); } return parseCert(encodedCert); } public static X509CRL toX509Crl(final CertificateList asn1CertList) throws CertificateException, CRLException { byte[] encodedCrl; try { encodedCrl = asn1CertList.getEncoded(); } catch (IOException ex) { throw new CRLException("could not get encoded CRL", ex); } return parseCrl(encodedCrl); } public static X509CRL parseCrl(final byte[] encodedCrl) throws CertificateException, CRLException { ParamUtil.requireNonNull("encodedCrl", encodedCrl); return parseCrl(new ByteArrayInputStream(encodedCrl)); } public static X509CRL parseCrl(final InputStream crlStream) throws CertificateException, CRLException { ParamUtil.requireNonNull("crlStream", crlStream); X509CRL crl = (X509CRL) getCertFactory().generateCRL(crlStream); if (crl == null) { throw new CRLException( "the given one is not a valid X.509 CRL"); } return crl; } public static X509Certificate parseCert(final byte[] certBytes) throws CertificateException { ParamUtil.requireNonNull("certBytes", certBytes); return parseCert(new ByteArrayInputStream(certBytes)); } private static X509Certificate parseCert(final InputStream certStream) throws CertificateException { ParamUtil.requireNonNull("certStream", certStream); return (X509Certificate) getCertFactory().generateCertificate(certStream); } private static byte[] extractSki(final X509Certificate cert) throws CertificateEncodingException { byte[] extValue = getCoreExtValue(cert, Extension.subjectKeyIdentifier); if (extValue == null) { return null; } try { return ASN1OctetString.getInstance(extValue).getOctets(); } catch (IllegalArgumentException ex) { throw new CertificateEncodingException(ex.getMessage()); } } private static byte[] extractAki(final X509Certificate cert) throws CertificateEncodingException { byte[] extValue = getCoreExtValue(cert, Extension.authorityKeyIdentifier); if (extValue == null) { return null; } try { AuthorityKeyIdentifier aki = AuthorityKeyIdentifier.getInstance(extValue); return aki.getKeyIdentifier(); } catch (IllegalArgumentException ex) { throw new CertificateEncodingException( "invalid extension AuthorityKeyIdentifier: " + ex.getMessage()); } } public static boolean hasKeyusage(final X509Certificate cert, final KeyUsage usage) { boolean[] keyusage = cert.getKeyUsage(); if (keyusage != null && keyusage.length > usage.getBit()) { return keyusage[usage.getBit()]; } return false; } private static byte[] getCoreExtValue(final X509Certificate cert, final ASN1ObjectIdentifier type) throws CertificateEncodingException { ParamUtil.requireNonNull("cert", cert); ParamUtil.requireNonNull("type", type); byte[] fullExtValue = cert.getExtensionValue(type.getId()); if (fullExtValue == null) { return null; } try { return ASN1OctetString.getInstance(fullExtValue).getOctets(); } catch (IllegalArgumentException ex) { throw new CertificateEncodingException("invalid extension " + type.getId() + ": " + ex.getMessage()); } } public static boolean isSelfSigned(final X509Certificate cert) { ParamUtil.requireNonNull("cert", cert); boolean equals = cert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()); if (!equals) { return false; } try { byte[] ski = extractSki(cert); byte[] aki = extractAki(cert); return (ski != null && aki != null) ? Arrays.equals(ski, aki) : true; } catch (CertificateEncodingException ex) { return false; } } public static boolean issues(final X509Certificate issuerCert, final X509Certificate cert) throws CertificateEncodingException { ParamUtil.requireNonNull("issuerCert", issuerCert); ParamUtil.requireNonNull("cert", cert); boolean isCa = issuerCert.getBasicConstraints() >= 0; if (!isCa) { return false; } boolean issues = issuerCert.getSubjectX500Principal().equals(cert.getIssuerX500Principal()); if (issues) { byte[] ski = extractSki(issuerCert); byte[] aki = extractAki(cert); if (ski != null) { issues = Arrays.equals(ski, aki); } } if (issues) { long issuerNotBefore = issuerCert.getNotBefore().getTime(); long issuerNotAfter = issuerCert.getNotAfter().getTime(); long notBefore = cert.getNotBefore().getTime(); issues = notBefore <= issuerNotAfter && notBefore >= issuerNotBefore; } return issues; } public static ASN1ObjectIdentifier extractDigesetAlgorithmIdentifier(final String sigOid, final byte[] sigParams) throws NoSuchAlgorithmException { ParamUtil.requireNonBlank("sigOid", sigOid); ASN1ObjectIdentifier algOid = new ASN1ObjectIdentifier(sigOid); ASN1ObjectIdentifier digestAlgOid; if (PKCSObjectIdentifiers.md5WithRSAEncryption.equals(algOid)) { digestAlgOid = PKCSObjectIdentifiers.md5; } else if (PKCSObjectIdentifiers.sha1WithRSAEncryption.equals(algOid)) { digestAlgOid = X509ObjectIdentifiers.id_SHA1; } else if (PKCSObjectIdentifiers.sha224WithRSAEncryption.equals(algOid)) { digestAlgOid = NISTObjectIdentifiers.id_sha224; } else if (PKCSObjectIdentifiers.sha256WithRSAEncryption.equals(algOid)) { digestAlgOid = NISTObjectIdentifiers.id_sha256; } else if (PKCSObjectIdentifiers.sha384WithRSAEncryption.equals(algOid)) { digestAlgOid = NISTObjectIdentifiers.id_sha384; } else if (PKCSObjectIdentifiers.sha512WithRSAEncryption.equals(algOid)) { digestAlgOid = NISTObjectIdentifiers.id_sha512; } else if (PKCSObjectIdentifiers.id_RSASSA_PSS.equals(algOid)) { RSASSAPSSparams param = RSASSAPSSparams.getInstance(sigParams); digestAlgOid = param.getHashAlgorithm().getAlgorithm(); } else { throw new NoSuchAlgorithmException("unknown signature algorithm" + algOid.getId()); } return digestAlgOid; } public static ASN1Encodable getFirstAttrValue(final AttributeTable attrs, final ASN1ObjectIdentifier type) { ParamUtil.requireNonNull("attrs", attrs); ParamUtil.requireNonNull("type", type); Attribute attr = attrs.get(type); if (attr == null) { return null; } ASN1Set set = attr.getAttrValues(); return (set.size() == 0) ? null : set.getObjectAt(0); } public static byte[] read(final InputStream in) throws IOException { ParamUtil.requireNonNull("in", in); try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); int readed = 0; byte[] buffer = new byte[2048]; while ((readed = in.read(buffer)) != -1) { bout.write(buffer, 0, readed); } return bout.toByteArray(); } finally { try { in.close(); } catch (IOException ex) { LOG.error("could not close stream: {}", ex.getMessage()); } } } public static void addCmsCertSet(final CMSSignedDataGenerator generator, final X509Certificate[] cmsCertSet) throws CertificateEncodingException, CMSException { if (cmsCertSet == null || cmsCertSet.length == 0) { return; } ParamUtil.requireNonNull("geneator", generator); Collection<X509Certificate> certColl = new LinkedList<X509Certificate>(); for (X509Certificate m : cmsCertSet) { certColl.add(m); } JcaCertStore certStore = new JcaCertStore(certColl); generator.addCertificates(certStore); } private static CertificateFactory getCertFactory() throws CertificateException { synchronized (certFactLock) { if (certFact == null) { try { certFact = CertificateFactory.getInstance("X.509", "BC"); } catch (NoSuchProviderException ex) { throw new CertificateException("NoSuchProviderException: " + ex.getMessage()); } } return certFact; } } }