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