
org.openas2.lib.helper.BCCryptoHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openas2-server Show documentation
Show all versions of openas2-server Show documentation
Open source implementation of the AS2 standard for signed encrypted and compressed document transfer
package org.openas2.lib.helper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import javax.activation.CommandMap;
import javax.activation.MailcapCommandMap;
import javax.mail.MessagingException;
import javax.mail.internet.ContentType;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMultipart;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cms.CMSAlgorithm;
import org.bouncycastle.cms.CMSAttributeTableGenerator;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
import org.bouncycastle.cms.KeyTransRecipientId;
import org.bouncycastle.cms.KeyTransRecipientInformation;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.bc.BcRSAKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.cms.jcajce.ZlibCompressor;
import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMECompressed;
import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
import org.bouncycastle.mail.smime.SMIMEEnveloped;
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
import org.bouncycastle.mail.smime.SMIMEException;
import org.bouncycastle.mail.smime.SMIMESigned;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.bouncycastle.mail.smime.SMIMESignedParser;
import org.bouncycastle.mail.smime.SMIMEUtil;
import org.bouncycastle.mail.smime.util.CRLFOutputStream;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.OutputCompressor;
import org.bouncycastle.operator.OutputEncryptor;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;
import org.openas2.DispositionException;
import org.openas2.OpenAS2Exception;
import org.openas2.Session;
import org.openas2.message.AS2Message;
import org.openas2.message.Message;
import org.openas2.processor.receiver.AS2ReceiverModule;
import org.openas2.util.AS2Util;
import org.openas2.util.DispositionType;
public class BCCryptoHelper implements ICryptoHelper {
private Log logger = LogFactory.getLog(BCCryptoHelper.class.getSimpleName());
public boolean isEncrypted(MimeBodyPart part) throws MessagingException
{
ContentType contentType = new ContentType(part.getContentType());
String baseType = contentType.getBaseType().toLowerCase();
if (baseType.equalsIgnoreCase("application/pkcs7-mime"))
{
String smimeType = contentType.getParameter("smime-type");
boolean checkResult = (smimeType != null) && smimeType.equalsIgnoreCase("enveloped-data");
if (!checkResult && logger.isDebugEnabled())
{
logger.debug("Check for encrypted data failed on SMIME content type: " + smimeType);
}
return (checkResult);
}
if (logger.isDebugEnabled())
{
logger.debug("Check for encrypted data failed on BASE content type: " + baseType);
}
return false;
}
public boolean isSigned(MimeBodyPart part) throws MessagingException
{
ContentType contentType = new ContentType(part.getContentType());
String baseType = contentType.getBaseType().toLowerCase();
return baseType.equalsIgnoreCase("multipart/signed");
}
public boolean isCompressed(MimeBodyPart part) throws MessagingException
{
ContentType contentType = new ContentType(part.getContentType());
String baseType = contentType.getBaseType().toLowerCase();
if (logger.isTraceEnabled())
{
try
{
logger.trace("Compression check. MIME Base Content-Type:" + contentType.getBaseType());
logger.trace("Compression check. SMIME-TYPE:" + contentType.getParameter("smime-type"));
logger.trace("Compressed MIME msg AFTER COMPRESSION Content-Disposition:" + part.getDisposition());
} catch (MessagingException e)
{
logger.trace("Compression check: no data available.");
}
}
if (baseType.equalsIgnoreCase("application/pkcs7-mime"))
{
String smimeType = contentType.getParameter("smime-type");
boolean checkResult = (smimeType != null) && smimeType.equalsIgnoreCase("compressed-data");
if (!checkResult && logger.isDebugEnabled())
{
logger.debug("Check for compressed data failed on SMIME content type: " + smimeType);
}
return (checkResult);
}
if (logger.isDebugEnabled())
{
logger.debug("Check for compressed data failed on BASE content type: " + baseType);
}
return false;
}
public String calculateMIC(MimeBodyPart part, String digest, boolean includeHeaders)
throws GeneralSecurityException, MessagingException, IOException
{
return calculateMIC(part, digest, includeHeaders, false);
}
public String calculateMIC(MimeBodyPart part, String digest, boolean includeHeaders, boolean noCanonicalize)
throws GeneralSecurityException, MessagingException, IOException
{
String micAlg = convertAlgorithm(digest, true);
if (logger.isDebugEnabled())
{
logger.debug("Calc MIC called with digest: " + digest + " ::: Incl headers? " + includeHeaders
+ " ::: Prevent canonicalization: " + noCanonicalize + " ::: Encoding: " + part.getEncoding());
}
MessageDigest md = MessageDigest.getInstance(micAlg, "BC");
if (includeHeaders && logger.isTraceEnabled())
{
logger.trace("Calculating MIC on MIMEPART Headers: " + AS2Util.printHeaders(part.getAllHeaders()));
}
// convert the Mime data to a byte array, then to an InputStream
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
// Canonicalize the data if not binary content transfer encoding
OutputStream os = null;
String encoding = part.getEncoding();
// Default encoding in case the bodypart does not have a transfer encoding set
if (encoding == null)
{
encoding = Session.DEFAULT_CONTENT_TRANSFER_ENCODING;
}
if ("binary".equals(encoding) || noCanonicalize)
{
os = bOut;
} else
{
os = new CRLFOutputStream(bOut);
}
if (includeHeaders)
{
part.writeTo(os);
} else
{
IOUtils.copy(part.getInputStream(), os);
}
byte[] data = bOut.toByteArray();
InputStream bIn = trimCRLFPrefix(data);
// calculate the hash of the data and mime header
DigestInputStream digIn = new DigestInputStream(bIn, md);
byte[] buf = new byte[4096];
while (digIn.read(buf) >= 0)
{
}
bOut.close();
byte[] mic = digIn.getMessageDigest().digest();
String micString = new String(Base64.encode(mic));
StringBuffer micResult = new StringBuffer(micString);
micResult.append(", ").append(digest);
return micResult.toString();
}
public MimeBodyPart decrypt(MimeBodyPart part, Certificate cert, Key key)
throws GeneralSecurityException, MessagingException, CMSException, IOException,
SMIMEException
{
// Make sure the data is encrypted
if (!isEncrypted(part))
{
throw new GeneralSecurityException("Content-Type indicates data isn't encrypted");
}
// Cast parameters to what BC needs
X509Certificate x509Cert = castCertificate(cert);
// Parse the MIME body into an SMIME envelope object
SMIMEEnveloped envelope = new SMIMEEnveloped(part);
// Get the recipient object for decryption
if (logger.isDebugEnabled())
{
logger.debug("Extracted X500 info:: PRINCIPAL : " + x509Cert.getIssuerX500Principal()
+ " :: NAME : " + x509Cert.getIssuerX500Principal().getName());
}
X500Name x500Name = new X500Name(x509Cert.getIssuerX500Principal().getName());
KeyTransRecipientId certRecId = new KeyTransRecipientId(x500Name, x509Cert.getSerialNumber());
RecipientInformationStore recipientInfoStore = envelope.getRecipientInfos();
Collection recipients = recipientInfoStore.getRecipients();
if (recipients == null)
{
throw new GeneralSecurityException("Certificate recipients could not be extracted");
}
//RecipientInformation recipientInfo = recipientInfoStore.get(recId);
//Object recipient = null;
boolean foundRecipient = false;
for (Iterator iterator = recipients.iterator(); iterator.hasNext(); )
{
RecipientInformation recipientInfo = iterator.next();
//recipient = iterator.next();
if (recipientInfo instanceof KeyTransRecipientInformation)
{
// X509CertificateHolder x509CertHolder = new X509CertificateHolder(x509Cert.getEncoded());
//RecipientId rid = recipientInfo.getRID();
if (certRecId.match(recipientInfo) && !foundRecipient)
{
foundRecipient = true;
// byte[] decryptedData = recipientInfo.getContent(new JceKeyTransEnvelopedRecipient((PrivateKey)key).setProvider("BC"));
byte[] decryptedData = recipientInfo.getContent(
new BcRSAKeyTransEnvelopedRecipient(PrivateKeyFactory.createKey(PrivateKeyInfo.getInstance(key.getEncoded()))));
return SMIMEUtil.toMimeBodyPart(decryptedData);
} else
{
if (logger.isDebugEnabled())
{
logger.debug("Failed match on recipient ID's:\n RID from msg:"
+ recipientInfo.getRID().toString() + " \n RID from priv cert: " + certRecId.toString());
}
}
}
}
throw new GeneralSecurityException("Matching certificate recipient could not be found");
}
public void deinitialize()
{
}
public MimeBodyPart encrypt(MimeBodyPart part, Certificate cert, String algorithm, String contentTxfrEncoding)
throws GeneralSecurityException, SMIMEException, MessagingException
{
X509Certificate x509Cert = castCertificate(cert);
SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator();
gen.setContentTransferEncoding(getEncoding(contentTxfrEncoding));
if (logger.isDebugEnabled())
{
logger.debug("Encrypting on MIME part containing the following headers: " + AS2Util.printHeaders(part.getAllHeaders()));
}
gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(x509Cert).setProvider("BC"));
return gen.generate(part, getOutputEncryptor(algorithm));
}
public void initialize()
{
Security.addProvider(new BouncyCastleProvider());
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
mc.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
mc.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
mc.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
mc.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
CommandMap.setDefaultCommandMap(mc);
}
public MimeBodyPart sign(MimeBodyPart part, Certificate cert, Key key, String digest, String contentTxfrEncoding
, boolean adjustDigestToOldName, boolean isRemoveCmsAlgorithmProtectionAttr)
throws GeneralSecurityException, SMIMEException, MessagingException
{
//String signDigest = convertAlgorithm(digest, true);
X509Certificate x509Cert = castCertificate(cert);
PrivateKey privKey = castKey(key);
String encryptAlg = cert.getPublicKey().getAlgorithm();
// Fix copied from https://github.com/phax/as2-lib/commit/ed08dd00b6d721ec3e3e7255f642045c9cbee9c3
SMIMESignedGenerator sGen = new SMIMESignedGenerator(
adjustDigestToOldName ? SMIMESignedGenerator.RFC3851_MICALGS : SMIMESignedGenerator.RFC5751_MICALGS);
sGen.setContentTransferEncoding(getEncoding(contentTxfrEncoding));
SignerInfoGenerator sig;
try
{
if (logger.isDebugEnabled())
{
logger.debug("Params for creating SMIME signed generator:: SIGN DIGEST: " + digest
+ " PUB ENCRYPT ALG: " + encryptAlg
+ " X509 CERT: " + x509Cert);
logger.debug("Signing on MIME part containing the following headers: " + AS2Util.printHeaders(part.getAllHeaders()));
}
// Remove the dash for SHA based digest for signing call
if (digest.toUpperCase().startsWith("SHA-"))
{
digest = digest.replaceAll("-", "");
}
JcaSimpleSignerInfoGeneratorBuilder jSig = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC");
sig = jSig.build(digest + "with" + encryptAlg, privKey, x509Cert);
// Some AS2 systems cannot handle certain OID's ...
if (isRemoveCmsAlgorithmProtectionAttr)
{
final CMSAttributeTableGenerator sAttrGen = sig.getSignedAttributeTableGenerator();
sig = new SignerInfoGenerator(sig, new DefaultSignedAttributeTableGenerator() {
@Override
public AttributeTable getAttributes(@SuppressWarnings("rawtypes") Map parameters)
{
AttributeTable ret = sAttrGen.getAttributes(parameters);
return ret.remove(CMSAttributes.cmsAlgorithmProtect);
}
}, sig.getUnsignedAttributeTableGenerator());
}
} catch (OperatorCreationException e)
{
throw new GeneralSecurityException(e);
}
sGen.addSignerInfoGenerator(sig);
MimeMultipart signedData;
signedData = sGen.generate(part);
MimeBodyPart tmpBody = new MimeBodyPart();
tmpBody.setContent(signedData);
// Content-type header is required, unit tests fail badly on async MDNs if not set.
tmpBody.setHeader("Content-Type", signedData.getContentType());
return tmpBody;
}
public MimeBodyPart verifySignature(MimeBodyPart part, Certificate cert)
throws GeneralSecurityException, IOException, MessagingException, CMSException, OperatorCreationException
{
// Make sure the data is signed
if (!isSigned(part))
{
throw new GeneralSecurityException("Content-Type indicates data isn't signed");
}
X509Certificate x509Cert = castCertificate(cert);
MimeMultipart mainParts = (MimeMultipart) part.getContent();
SMIMESigned signedPart = new SMIMESigned(mainParts);
//SignerInformationStore signers = signedPart.getSignerInfos();
DigestCalculatorProvider dcp = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
String contentTxfrEnc = signedPart.getContent().getEncoding();
if (contentTxfrEnc == null || contentTxfrEnc.length() < 1)
{
contentTxfrEnc = Session.DEFAULT_CONTENT_TRANSFER_ENCODING;
}
SMIMESignedParser ssp = new SMIMESignedParser(dcp, mainParts, contentTxfrEnc);
SignerInformationStore sis = ssp.getSignerInfos();
if (logger.isTraceEnabled())
{
String headers = null;
try
{
headers = AS2Util.printHeaders(part.getAllHeaders());
logger.trace("Headers on MimeBodyPart passed in to signature verifier: " + headers);
headers = AS2Util.printHeaders(ssp.getContent().getAllHeaders());
logger.trace("Checking signature on SIGNED MIME part extracted from multipart contains headers: " + headers);
} catch (Throwable e)
{
logger.trace("Error logging mime part for signer: " + org.openas2.logging.Log.getExceptionMsg(e), e);
}
}
Iterator it = sis.getSigners().iterator();
SignerInformationVerifier signerInfoVerifier = new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(x509Cert);
while (it.hasNext())
{
SignerInformation signer = it.next();
if (logger.isTraceEnabled())
{
try { // Code block below does not do null-checks or other encoding error checking.
Map
© 2015 - 2025 Weber Informatics LLC | Privacy Policy