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