View Javadoc
1   package ca.uhn.hl7v2.hoh.sign;
2   
3   import static ca.uhn.hl7v2.hoh.util.StringUtils.*;
4   
5   import java.security.GeneralSecurityException;
6   import java.security.KeyStore;
7   import java.security.KeyStoreException;
8   import java.security.PrivateKey;
9   import java.security.PublicKey;
10  import java.security.Security;
11  import java.security.cert.Certificate;
12  import java.security.cert.X509Certificate;
13  import java.util.ArrayList;
14  import java.util.List;
15  
16  import org.bouncycastle.cert.jcajce.JcaCertStore;
17  import org.bouncycastle.cms.CMSProcessable;
18  import org.bouncycastle.cms.CMSProcessableByteArray;
19  import org.bouncycastle.cms.CMSSignedData;
20  import org.bouncycastle.cms.CMSSignedDataGenerator;
21  import org.bouncycastle.cms.CMSSignerDigestMismatchException;
22  import org.bouncycastle.cms.CMSTypedData;
23  import org.bouncycastle.cms.SignerInformation;
24  import org.bouncycastle.cms.SignerInformationStore;
25  import org.bouncycastle.cms.SignerInformationVerifier;
26  import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
27  import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
28  import org.bouncycastle.jce.provider.BouncyCastleProvider;
29  import org.bouncycastle.operator.ContentSigner;
30  import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
31  import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
32  import org.bouncycastle.util.Store;
33  
34  import ca.uhn.hl7v2.hoh.util.repackage.Base64;
35  
36  public class BouncyCastleCmsMessageSigner implements ISigner {
37  
38  	static final String MSG_KEY_IS_NOT_A_PRIVATE_KEY = "Key is not a private key: ";
39  	static final String MSG_KEY_IS_NOT_A_PUBLIC_KEY = "Key is not a public key: ";
40  	static final String MSG_KEYSTORE_DOES_NOT_CONTAIN_KEY_WITH_ALIAS = "Keystore does not contain key with alias: ";
41  
42  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BouncyCastleCmsMessageSigner.class);
43  
44  	private final String myAlgorithm = "SHA512withRSA";
45  	private String myAliasPassword;
46  	private String myKeyAlias;
47  	private KeyStore myKeyStore;
48  	private PrivateKey myPrivateKey;
49  	private PublicKey myPublicKey;
50  
51  	/**
52  	 * Constructor
53  	 */
54  	public BouncyCastleCmsMessageSigner() {
55  		super();
56  	}
57  	
58  	private PrivateKey getPrivateKey() throws GeneralSecurityException, SignatureFailureException {
59  		if (myKeyStore == null) {
60  			throw new SignatureFailureException("Keystore is not set");
61  		}
62  		if (isBlank(myKeyAlias)) {
63  			throw new SignatureFailureException("Key alias is not set");
64  		}
65  		if (isBlank(myAliasPassword)) {
66  			throw new SignatureFailureException("Key alias password is not set");
67  		}
68  
69  		if (this.myPrivateKey == null) {
70  
71  			myPrivateKey = (PrivateKey) myKeyStore.getKey(myKeyAlias, myAliasPassword.toCharArray());
72  			if (myPrivateKey == null) {
73  				if (myKeyStore.containsAlias(myKeyAlias)) {
74  					if (myKeyStore.isCertificateEntry(myKeyAlias)) {
75  						throw new SignatureFailureException(MSG_KEY_IS_NOT_A_PRIVATE_KEY + myKeyAlias);
76  					}
77  				} else {
78  					throw new SignatureFailureException(MSG_KEYSTORE_DOES_NOT_CONTAIN_KEY_WITH_ALIAS + myKeyAlias);
79  				}
80  			}
81  		}
82  		return this.myPrivateKey;
83  	}
84  
85  	private PublicKey getPublicKey() throws SignatureFailureException {
86  		if (myKeyStore == null) {
87  			throw new SignatureFailureException("Keystore is not set");
88  		}
89  		if (isBlank(myKeyAlias)) {
90  			throw new SignatureFailureException("Key alias is not set");
91  		}
92  
93  		if (myPublicKey == null) {
94  			try {
95  				Certificate pubCert = myKeyStore.getCertificate(myKeyAlias);
96  				myPublicKey = pubCert != null ? pubCert.getPublicKey() : null;
97  				if (myPublicKey == null) {
98  					if (myKeyStore.containsAlias(myKeyAlias)) {
99  						if (myKeyStore.isKeyEntry(myKeyAlias)) {
100 							throw new SignatureFailureException(MSG_KEY_IS_NOT_A_PUBLIC_KEY + myKeyAlias);
101 						}
102 					} else {
103 						throw new SignatureFailureException(MSG_KEYSTORE_DOES_NOT_CONTAIN_KEY_WITH_ALIAS + myKeyAlias);
104 					}
105 				}
106 			} catch (KeyStoreException e) {
107 				throw new SignatureFailureException("Failed to retrieve key with alias " + myKeyAlias + " from keystore", e);
108 			}
109 
110 		}
111 		return myPublicKey;
112 	}
113 
114 	/**
115 	 * @param theAliasPassword
116 	 *            the aliasPassword to set
117 	 */
118 	public void setAliasPassword(String theAliasPassword) {
119 		myAliasPassword = theAliasPassword;
120 	}
121 
122 	/**
123 	 * @param theKeyAlias
124 	 *            the keyAlias to set
125 	 */
126 	public void setKeyAlias(String theKeyAlias) {
127 		myKeyAlias = theKeyAlias;
128 	}
129 
130 	/**
131 	 * @param theKeyStore
132 	 *            the keyStore to set
133 	 */
134 	public void setKeyStore(KeyStore theKeyStore) {
135 		if (theKeyStore == null) {
136 			throw new NullPointerException("Keystore can not be null");
137 		}
138 		myKeyStore = theKeyStore;
139 	}
140 
141 	/**
142 	 * {@inheritDoc}
143 	 */
144 	public String sign(byte[] theBytes) throws SignatureFailureException {
145 		try {
146 			Security.addProvider(new BouncyCastleProvider());
147 
148 			List<X509Certificate> certList = new ArrayList<>();
149 			CMSTypedData msg = new CMSProcessableByteArray(theBytes);
150 
151 			X509Certificate signCert = (X509Certificate) myKeyStore.getCertificate(myKeyAlias);
152 			certList.add(signCert);
153 
154 			Store certs = new JcaCertStore(certList);
155 
156 			CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
157 			ContentSigner sha1Signer = new JcaContentSignerBuilder(myAlgorithm).setProvider("BC").build(getPrivateKey());
158 
159 			gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, signCert));
160 
161 			gen.addCertificates(certs);
162 
163 			CMSSignedData sigData = gen.generate(msg, false);
164 			return myAlgorithm + ' ' + Base64.encodeBase64String(sigData.getEncoded());
165 //			return Base64.encodeBase64String(sigData.getEncoded());
166 
167 		} catch (Exception e) {
168 			throw new SignatureFailureException(e);
169 		}
170 	}
171 
172 	/**
173 	 * {@inheritDoc}
174 	 */
175 	public void verify(byte[] theBytes, String theSignature) throws SignatureVerificationException, SignatureFailureException {
176 		PublicKey pubKey = getPublicKey();
177 
178 		try {
179 
180 			int spaceIndex = theSignature.indexOf(' ');
181 			if (spaceIndex == -1) {
182 				throw new SignatureVerificationException("No algorithm found in signature block: " + theSignature);
183 			}
184 
185 			theSignature = theSignature.substring(spaceIndex + 1);
186 
187 			CMSProcessable content = new CMSProcessableByteArray(theBytes);
188 			CMSSignedData s = new CMSSignedData(content, Base64.decodeBase64(theSignature));
189 
190 			ourLog.debug("Verifying message against public key with alias[{}]", myKeyAlias);
191 
192 			SignerInformationVerifier vib = new JcaSimpleSignerInfoVerifierBuilder().build(pubKey);
193 
194 			SignerInformationStore signers = s.getSignerInfos();
195 			boolean verified = false;
196 
197 			for (Object o : signers.getSigners()) {
198 				SignerInformation signer = (SignerInformation) o;
199 				try {
200 
201 					ourLog.debug("Signer: {}", signer.getSID());
202 
203 					if (signer.verify(vib)) {
204 						verified = true;
205 					}
206 				} catch (CMSSignerDigestMismatchException e) {
207 					throw new SignatureVerificationException(e);
208 				}
209 
210 			}
211 
212 			if (!verified) {
213 				throw new SignatureVerificationException();
214 			}
215 
216 		} catch (SignatureVerificationException e) {
217 			throw e;
218 		} catch (Exception e) {
219 			throw new SignatureFailureException(e);
220 		}
221 
222 	}
223 
224 }