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}