/* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ /* ==================================================================== This product contains an ASLv2 licensed version of the OOXML signer package from the eID Applet project http://code.google.com/p/eid-applet/source/browse/trunk/README.txt Copyright (C) 2008-2014 FedICT. ================================================================= */ package org.apache.poi.poifs.crypt.dsig; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; 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.security.interfaces.RSAPublicKey; import java.security.spec.RSAKeyGenParameterSpec; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.security.auth.x500.X500Principal; import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream; import org.apache.poi.poifs.crypt.CryptoFunctions; import org.apache.poi.poifs.storage.RawDataUtil; import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.RandomSingleton; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.CRLNumber; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extensions; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CRLHolder; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509ExtensionUtils; import org.bouncycastle.cert.X509v2CRLBuilder; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CRLConverter; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.ocsp.BasicOCSPResp; import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder; import org.bouncycastle.cert.ocsp.CertificateID; import org.bouncycastle.cert.ocsp.CertificateStatus; import org.bouncycastle.cert.ocsp.OCSPException; import org.bouncycastle.cert.ocsp.OCSPReq; import org.bouncycastle.cert.ocsp.OCSPReqBuilder; import org.bouncycastle.cert.ocsp.OCSPResp; import org.bouncycastle.cert.ocsp.OCSPRespBuilder; import org.bouncycastle.cert.ocsp.Req; import org.bouncycastle.crypto.params.RSAKeyParameters; import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DigestCalculator; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; public class DummyKeystore { public static class KeyCertPair { private final PrivateKey key; private final List x509chain; public KeyCertPair(PrivateKey key, Certificate[] x509chain) { this.key = key; this.x509chain = Stream.of(x509chain).map(X509Certificate.class::cast).collect(Collectors.toList()); } public PrivateKey getKey() { return key; } public X509Certificate getX509() { return x509chain.get(0); } public List getX509Chain() { return x509chain; } } private static final String DUMMY_ALIAS = "Test"; private static final String DUMMY_PASS = "test"; private final KeyStore keystore; public DummyKeystore(String storePass) throws GeneralSecurityException, IOException { this((File)null, storePass); } public DummyKeystore(File storeFile, String storePass) throws GeneralSecurityException, IOException { CryptoFunctions.registerBouncyCastle(); keystore = KeyStore.getInstance("PKCS12"); try (InputStream fis = storeFile != null && storeFile.exists() ? new FileInputStream(storeFile) : null) { keystore.load(fis, storePass.toCharArray()); } } public DummyKeystore(String pfxInput, String storePass) throws GeneralSecurityException, IOException { CryptoFunctions.registerBouncyCastle(); keystore = KeyStore.getInstance("PKCS12"); try (InputStream fis = new UnsynchronizedByteArrayInputStream(RawDataUtil.decompress(pfxInput))) { keystore.load(fis, storePass.toCharArray()); } } /** * Create dummy key * @return the alias of the dummy key */ public KeyCertPair createDummyKey() throws GeneralSecurityException, IOException, OperatorCreationException { return addEntry(DUMMY_ALIAS, DUMMY_PASS, 2048, 24); } public KeyCertPair addEntryFromPEM(File pemFile, String keyPass) throws IOException, CertificateException, KeyStoreException { PrivateKey key = null; X509Certificate x509 = null; try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(pemFile), StandardCharsets.ISO_8859_1))) { PEMParser parser = new PEMParser(br); for (Object obj; (obj = parser.readObject()) != null; ) { if (obj instanceof PrivateKeyInfo) { key = new JcaPEMKeyConverter().setProvider("BC").getPrivateKey((PrivateKeyInfo)obj); } else if (obj instanceof X509CertificateHolder) { x509 = new JcaX509CertificateConverter().setProvider("BC").getCertificate((X509CertificateHolder)obj); } } } if (key == null || x509 == null) { throw new IOException("Please add private key and certificate in the PEM file."); } String alias = x509.getSubjectX500Principal().getName(); keystore.setKeyEntry(alias, key, keyPass.toCharArray(), new Certificate[]{x509}); return new KeyCertPair(key, new Certificate[]{x509}); } /** * Add an entry with password, keySize and expiry values. Ignore if alias is already in keystore * @param keySize multiple of 1024, e.g. 1024, 2048 */ public KeyCertPair addEntry(String keyAlias, String keyPass, int keySize, int expiryInMonths) throws GeneralSecurityException, IOException, OperatorCreationException { if (!keystore.isKeyEntry(keyAlias)) { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(new RSAKeyGenParameterSpec(keySize, RSAKeyGenParameterSpec.F4), RandomSingleton.getInstance()); KeyPair pair = keyPairGenerator.generateKeyPair(); Date notBefore = new Date(); Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC); cal.add(Calendar.MONTH, expiryInMonths); Date notAfter = cal.getTime(); KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature); X509Certificate x509 = generateCertificate(pair.getPublic(), notBefore, notAfter, pair.getPrivate(), keyUsage); keystore.setKeyEntry(keyAlias, pair.getPrivate(), keyPass.toCharArray(), new Certificate[]{x509}); return new KeyCertPair(pair.getPrivate(), new X509Certificate[]{x509}); } else { return new KeyCertPair(getKey(keyAlias, keyPass), keystore.getCertificateChain(keyAlias)); } } public KeyCertPair getKeyPair(String keyAlias, String keyPass) throws GeneralSecurityException { return new KeyCertPair(getKey(keyAlias, keyPass), keystore.getCertificateChain(keyAlias)); } public KeyCertPair getKeyPair(int index, String keyPass) throws GeneralSecurityException { Map.Entry me = getKeyByIndex(index, keyPass); return me != null ? getKeyPair(me.getKey(), keyPass) : null; } public PrivateKey getKey(String keyAlias, String keyPass) throws GeneralSecurityException { return (PrivateKey)keystore.getKey(keyAlias, keyPass.toCharArray()); } public PrivateKey getKey(int index, String keyPass) throws GeneralSecurityException { Map.Entry me = getKeyByIndex(index, keyPass); return me != null ? me.getValue() : null; } public X509Certificate getFirstX509(String alias) throws KeyStoreException { return (X509Certificate)keystore.getCertificate(alias); } private Map.Entry getKeyByIndex(int index, String keyPass) throws GeneralSecurityException { for (String a : Collections.list(keystore.aliases())) { try { PrivateKey pk = (PrivateKey) keystore.getKey(a, keyPass.toCharArray()); if (pk != null) { return new AbstractMap.SimpleEntry<>(a, pk); } } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) { break; } } return null; } public void save(File storeFile, String storePass) throws IOException, GeneralSecurityException { try (FileOutputStream fos = new FileOutputStream(storeFile)) { keystore.store(fos, storePass.toCharArray()); } } public X509CRL generateCrl(KeyCertPair certPair) throws GeneralSecurityException, IOException, OperatorCreationException { PrivateKey issuerPrivateKey = certPair.getKey(); X509Certificate issuer = certPair.getX509(); X509CertificateHolder holder = new X509CertificateHolder(issuer.getEncoded()); X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(holder.getIssuer(), new Date()); crlBuilder.setNextUpdate(new Date(new Date().getTime() + 100000)); JcaContentSignerBuilder contentBuilder = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC"); CRLNumber crlNumber = new CRLNumber(new BigInteger("1234")); crlBuilder.addExtension(Extension.cRLNumber, false, crlNumber); X509CRLHolder x509Crl = crlBuilder.build(contentBuilder.build(issuerPrivateKey)); return new JcaX509CRLConverter().setProvider("BC").getCRL(x509Crl); } private static X509Certificate generateCertificate(PublicKey subjectPublicKey, Date notBefore, Date notAfter, PrivateKey issuerPrivateKey, KeyUsage keyUsage) throws IOException, OperatorCreationException, CertificateException { final String signatureAlgorithm = "SHA1withRSA"; final String subjectDn = "CN=Test"; X500Name issuerName = new X500Name(subjectDn); RSAPublicKey rsaPubKey = (RSAPublicKey)subjectPublicKey; RSAKeyParameters rsaSpec = new RSAKeyParameters(false, rsaPubKey.getModulus(), rsaPubKey.getPublicExponent()); SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(rsaSpec); DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder() .setProvider("BC").build().get(CertificateID.HASH_SHA1); X509v3CertificateBuilder certificateGenerator = new X509v3CertificateBuilder( issuerName , new BigInteger(128, RandomSingleton.getInstance()) , notBefore , notAfter , new X500Name(subjectDn) , subjectPublicKeyInfo ); X509ExtensionUtils exUtils = new X509ExtensionUtils(digestCalc); SubjectKeyIdentifier subKeyId = exUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo); AuthorityKeyIdentifier autKeyId = exUtils.createAuthorityKeyIdentifier(subjectPublicKeyInfo); certificateGenerator.addExtension(Extension.subjectKeyIdentifier, false, subKeyId); certificateGenerator.addExtension(Extension.authorityKeyIdentifier, false, autKeyId); BasicConstraints bc = new BasicConstraints(0); certificateGenerator.addExtension(Extension.basicConstraints, false, bc); if (null != keyUsage) { certificateGenerator.addExtension(Extension.keyUsage, true, keyUsage); } JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm); signerBuilder.setProvider("BC"); X509CertificateHolder certHolder = certificateGenerator.build(signerBuilder.build(issuerPrivateKey)); return new JcaX509CertificateConverter().getCertificate(certHolder); } public OCSPResp createOcspResp(KeyCertPair certPair, long nonceTimeinMillis) throws OperatorCreationException, OCSPException, CertificateEncodingException, IOException { X509Certificate certificate = certPair.getX509(); X509Certificate issuerCertificate = certPair.getX509(); X509Certificate ocspResponderCertificate = certPair.getX509(); PrivateKey ocspResponderPrivateKey = certPair.getKey(); DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder() .setProvider("BC").build().get(CertificateID.HASH_SHA1); X509CertificateHolder issuerHolder = new X509CertificateHolder(issuerCertificate.getEncoded()); CertificateID certId = new CertificateID(digestCalc, issuerHolder, certificate.getSerialNumber()); // request //create a nonce to avoid replay attack BigInteger nonce = BigInteger.valueOf(nonceTimeinMillis); DEROctetString nonceDer = new DEROctetString(nonce.toByteArray()); Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, nonceDer); Extensions exts = new Extensions(ext); OCSPReqBuilder ocspReqBuilder = new OCSPReqBuilder(); ocspReqBuilder.addRequest(certId); ocspReqBuilder.setRequestExtensions(exts); OCSPReq ocspReq = ocspReqBuilder.build(); SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo (CertificateID.HASH_SHA1, ocspResponderCertificate.getPublicKey().getEncoded()); BasicOCSPRespBuilder basicOCSPRespBuilder = new BasicOCSPRespBuilder(keyInfo, digestCalc); basicOCSPRespBuilder.setResponseExtensions(exts); // request processing Req[] requestList = ocspReq.getRequestList(); for (Req ocspRequest : requestList) { CertificateID certificateID = ocspRequest.getCertID(); CertificateStatus certificateStatus = CertificateStatus.GOOD; basicOCSPRespBuilder.addResponse(certificateID, certificateStatus); } // basic response generation X509CertificateHolder[] chain = null; if (!ocspResponderCertificate.equals(issuerCertificate)) { // TODO: HorribleProxy can't convert array input params yet chain = new X509CertificateHolder[] { new X509CertificateHolder(ocspResponderCertificate.getEncoded()), issuerHolder }; } ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA") .setProvider("BC").build(ocspResponderPrivateKey); BasicOCSPResp basicOCSPResp = basicOCSPRespBuilder.build(contentSigner, chain, new Date(nonceTimeinMillis)); OCSPRespBuilder ocspRespBuilder = new OCSPRespBuilder(); return ocspRespBuilder.build(OCSPRespBuilder.SUCCESSFUL, basicOCSPResp); } public void importX509(File file) throws CertificateException, KeyStoreException, IOException { try (InputStream is = new FileInputStream(file)) { X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is); keystore.setCertificateEntry(cert.getSubjectX500Principal().getName(), cert); } } public void importKeystore(File file, String storePass, String keyPass, Function otherKeyPass) throws GeneralSecurityException, IOException { DummyKeystore dk = new DummyKeystore(file, storePass); Map myCerts = new HashMap<>(); for (String a : Collections.list(keystore.aliases())) { Certificate[] chain = keystore.getCertificateChain(a); if (chain == null) { Certificate cert = keystore.getCertificate(a); if (cert == null) { continue; } chain = new Certificate[]{cert}; } Arrays.stream(chain) .map(X509Certificate.class::cast) .filter(c -> !myCerts.containsKey(c.getSubjectX500Principal().getName())) .forEach(c -> myCerts.put(c.getSubjectX500Principal().getName(), c)); } for (String a : Collections.list(dk.keystore.aliases())) { KeyCertPair keyPair = dk.getKeyPair(a, otherKeyPass.apply(a)); ArrayList chain = new ArrayList<>(keyPair.getX509Chain()); Set names = chain.stream().map(X509Certificate::getSubjectX500Principal).map(X500Principal::getName).collect(Collectors.toSet()); X509Certificate last = chain.get(chain.size() - 1); do { String issuer = last.getIssuerX500Principal().getName(); X509Certificate parent = myCerts.get(issuer); if (names.contains(issuer) || parent == null) { break; } else { chain.add(parent); names.add(issuer); } last = parent; } while (true); keystore.setKeyEntry(a, keyPair.getKey(), keyPass.toCharArray(), chain.toArray(new X509Certificate[0])); } } }