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