View Javadoc
1   /*-
2    * #%L
3    * io.earcam.utilitarian.security
4    * %%
5    * Copyright (C) 2017 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    *
9    * You <b>must</b> choose to accept, in full - any individual or combination of
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
18   */
19  package io.earcam.utilitarian.security;
20  
21  import java.io.IOException;
22  import java.security.GeneralSecurityException;
23  import java.security.cert.Certificate;
24  import java.security.cert.X509Certificate;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collection;
28  import java.util.List;
29  
30  import org.bouncycastle.cert.X509CertificateHolder;
31  import org.bouncycastle.cert.jcajce.JcaCertStore;
32  import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
33  import org.bouncycastle.cms.CMSException;
34  import org.bouncycastle.cms.CMSProcessableByteArray;
35  import org.bouncycastle.cms.CMSSignedData;
36  import org.bouncycastle.cms.CMSSignedDataGenerator;
37  import org.bouncycastle.cms.CMSSignedDataParser;
38  import org.bouncycastle.cms.CMSTypedData;
39  import org.bouncycastle.cms.SignerInfoGenerator;
40  import org.bouncycastle.cms.SignerInformation;
41  import org.bouncycastle.cms.SignerInformationStore;
42  import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
43  import org.bouncycastle.jce.provider.BouncyCastleProvider;
44  import org.bouncycastle.operator.ContentSigner;
45  import org.bouncycastle.operator.DigestCalculatorProvider;
46  import org.bouncycastle.operator.OperatorCreationException;
47  import org.bouncycastle.operator.OperatorException;
48  import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
49  import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
50  import org.bouncycastle.util.Store;
51  
52  import io.earcam.unexceptional.Exceptional;
53  import io.earcam.unexceptional.UncheckedSecurityException;
54  
55  public final class Signatures {
56  
57  	private static final BouncyCastleProvider PROVIDER = new BouncyCastleProvider();
58  
59  
60  	private Signatures()
61  	{}
62  
63  
64  	public static byte[] sign(byte[] contents, OpenedKeyStore keyStore, String signatureAlgorithm)
65  	{
66  		try {
67  			CMSSignedDataGenerator gen = createSignedDataGenerator(keyStore, signatureAlgorithm);
68  
69  			CMSTypedData cmsData = new CMSProcessableByteArray(contents);
70  			CMSSignedData signedData = gen.generate(cmsData);
71  			return signedData.getEncoded();
72  		} catch(CMSException | OperatorException | IllegalArgumentException | IOException | GeneralSecurityException e) {
73  			throw new UncheckedSecurityException(new GeneralSecurityException(e));
74  		}
75  	}
76  
77  
78  	private static CMSSignedDataGenerator createSignedDataGenerator(OpenedKeyStore openedKeyStore, String signatureAlgorithm)
79  			throws GeneralSecurityException, OperatorException, CMSException
80  	{
81  		List<Certificate> certChain = Arrays.asList(openedKeyStore.getCertificateChain());
82  		JcaCertStore certStore = new JcaCertStore(certChain);
83  		Certificate cert = openedKeyStore.getCertificate();
84  		ContentSigner signer = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(PROVIDER).build(openedKeyStore.privateKey());
85  		CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
86  		DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider(PROVIDER).build();
87  		SignerInfoGenerator sig = new JcaSignerInfoGeneratorBuilder(dcp).build(signer, (X509Certificate) cert);
88  		generator.addSignerInfoGenerator(sig);
89  		generator.addCertificates(certStore);
90  		return generator;
91  	}
92  
93  
94  	public static List<X509Certificate> certificatesFromSignature(byte[] encapSigData)
95  	{
96  		try {
97  			CMSSignedDataParser parser = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider(PROVIDER).build(), encapSigData);
98  
99  			@SuppressWarnings("unchecked")
100 			Store<X509CertificateHolder> certStore = parser.getCertificates();
101 			SignerInformationStore signers = parser.getSignerInfos();
102 
103 			List<X509Certificate> certificates = new ArrayList<>();
104 
105 			JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(PROVIDER);
106 
107 			for(SignerInformation signer : signers.getSigners()) {
108 				@SuppressWarnings("unchecked")
109 				Collection<X509CertificateHolder> holders = certStore.getMatches(signer.getSID());
110 
111 				for(X509CertificateHolder holder : holders) {
112 					X509Certificate certificate = Exceptional.apply(converter::getCertificate, holder);
113 					certificates.add(certificate);
114 				}
115 			}
116 			return certificates;
117 		} catch(CMSException | OperatorCreationException e) {
118 			throw new UncheckedSecurityException(new GeneralSecurityException(e));
119 		}
120 	}
121 
122 }