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
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
117
118
119 public void setAliasPassword(String theAliasPassword) {
120 myAliasPassword = theAliasPassword;
121 }
122
123
124
125
126
127 public void setKeyAlias(String theKeyAlias) {
128 myKeyAlias = theKeyAlias;
129 }
130
131
132
133
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
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
167
168 } catch (Exception e) {
169 throw new SignatureFailureException(e);
170 }
171 }
172
173
174
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 }