001package ca.uhn.hl7v2.hoh.sign;
002
003import static ca.uhn.hl7v2.hoh.util.StringUtils.*;
004
005import java.security.GeneralSecurityException;
006import java.security.KeyStore;
007import java.security.KeyStoreException;
008import java.security.PrivateKey;
009import java.security.PublicKey;
010import java.security.Security;
011import java.security.cert.Certificate;
012import java.security.cert.X509Certificate;
013import java.util.ArrayList;
014import java.util.Iterator;
015import java.util.List;
016
017import org.bouncycastle.cert.jcajce.JcaCertStore;
018import org.bouncycastle.cms.CMSProcessable;
019import org.bouncycastle.cms.CMSProcessableByteArray;
020import org.bouncycastle.cms.CMSSignedData;
021import org.bouncycastle.cms.CMSSignedDataGenerator;
022import org.bouncycastle.cms.CMSSignerDigestMismatchException;
023import org.bouncycastle.cms.CMSTypedData;
024import org.bouncycastle.cms.SignerInformation;
025import org.bouncycastle.cms.SignerInformationStore;
026import org.bouncycastle.cms.SignerInformationVerifier;
027import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
028import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
029import org.bouncycastle.jce.provider.BouncyCastleProvider;
030import org.bouncycastle.operator.ContentSigner;
031import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
032import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
033import org.bouncycastle.util.Store;
034
035import ca.uhn.hl7v2.hoh.util.repackage.Base64;
036
037public class BouncyCastleCmsMessageSigner implements ISigner {
038
039        static final String MSG_KEY_IS_NOT_A_PRIVATE_KEY = "Key is not a private key: ";
040        static final String MSG_KEY_IS_NOT_A_PUBLIC_KEY = "Key is not a public key: ";
041        static final String MSG_KEYSTORE_DOES_NOT_CONTAIN_KEY_WITH_ALIAS = "Keystore does not contain key with alias: ";
042
043        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BouncyCastleCmsMessageSigner.class);
044
045        private String myAlgorithm = "SHA512withRSA";
046        private String myAliasPassword;
047        private String myKeyAlias;
048        private KeyStore myKeyStore;
049        private PrivateKey myPrivateKey;
050        private PublicKey myPublicKey;
051
052        /**
053         * Constructor
054         */
055        public BouncyCastleCmsMessageSigner() {
056                super();
057        }
058        
059        private PrivateKey getPrivateKey() throws GeneralSecurityException, SignatureFailureException {
060                if (myKeyStore == null) {
061                        throw new SignatureFailureException("Keystore is not set");
062                }
063                if (isBlank(myKeyAlias)) {
064                        throw new SignatureFailureException("Key alias is not set");
065                }
066                if (isBlank(myAliasPassword)) {
067                        throw new SignatureFailureException("Key alias password is not set");
068                }
069
070                if (this.myPrivateKey == null) {
071
072                        myPrivateKey = (PrivateKey) myKeyStore.getKey(myKeyAlias, myAliasPassword.toCharArray());
073                        if (myPrivateKey == null) {
074                                if (myKeyStore.containsAlias(myKeyAlias)) {
075                                        if (myKeyStore.isCertificateEntry(myKeyAlias)) {
076                                                throw new SignatureFailureException(MSG_KEY_IS_NOT_A_PRIVATE_KEY + myKeyAlias);
077                                        }
078                                } else {
079                                        throw new SignatureFailureException(MSG_KEYSTORE_DOES_NOT_CONTAIN_KEY_WITH_ALIAS + myKeyAlias);
080                                }
081                        }
082                }
083                return this.myPrivateKey;
084        }
085
086        private PublicKey getPublicKey() throws SignatureFailureException {
087                if (myKeyStore == null) {
088                        throw new SignatureFailureException("Keystore is not set");
089                }
090                if (isBlank(myKeyAlias)) {
091                        throw new SignatureFailureException("Key alias is not set");
092                }
093
094                if (myPublicKey == null) {
095                        try {
096                                Certificate pubCert = myKeyStore.getCertificate(myKeyAlias);
097                                myPublicKey = pubCert != null ? pubCert.getPublicKey() : null;
098                                if (myPublicKey == null) {
099                                        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}