org.certificateservices.messages.utils.XMLSigner Maven / Gradle / Ivy
/************************************************************************
* *
* Certificate Service - Messages *
* *
* This software is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public License *
* License as published by the Free Software Foundation; either *
* version 3 of the License, or any later version. *
* *
* See terms of license at gnu.org. *
* *
*************************************************************************/
package org.certificateservices.messages.utils;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.logging.Logger;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.xml.security.utils.Base64;
import org.certificateservices.messages.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.certificateservices.messages.ContextMessageSecurityProvider.Context;
/**
* Class containing help methods for digital XML signatures
*
* @author Philip Vendil
*
*/
public class XMLSigner {
public static String XMLDSIG_NAMESPACE = "http://www.w3.org/2000/09/xmldsig#";
private static String ENVELOPE_TRANSFORM = "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
private static String C14N_TRANSFORM = "http://www.w3.org/2001/10/xml-exc-c14n#";
static Logger log = Logger.getLogger(XMLSigner.class.getName());
static SystemTime systemTime = new DefaultSystemTime();
private DocumentBuilderFactory documentBuilderFactory;
private DocumentBuilder documentBuilder;
private CertificateFactory cf;
private SignatureLocationFinder defaultSignatureLocationFinder;
private OrganisationLookup defaultOrganisationLookup;
private Set supportedDigestsAlgorithm;
private Set supportedSignatureAlgorithm;
private MessageSecurityProvider messageSecurityProvider;
private Transformer transformer;
private boolean signMessages;
/**
* Constructor used for context based message security providers using
* an existing DocumentBuilder instance.
*
* @param messageSecurityProvider MessageSecurityProvider instance to use.
* @param documentBuilder DocumentBuilder to use when processing and parsing XML
* @param signMessages If messages should be signed or not.
* @param defaultSignatureLocationFinder SignatureLocationFinder instance to use.
* @param defaultOrganisationLookup OrganisationLookup instance to use.
* @throws MessageProcessingException If an error occurs while performing signature operation.
* @deprecated This constructor will result in an XMLSigner instance that is not thread safe,
* please use constructor that does not explicitly specify an existing DocumentBuilder instance.
*/
@Deprecated
public XMLSigner(MessageSecurityProvider messageSecurityProvider,
DocumentBuilder documentBuilder,
boolean signMessages,
SignatureLocationFinder defaultSignatureLocationFinder,
OrganisationLookup defaultOrganisationLookup) throws MessageProcessingException{
this(messageSecurityProvider, signMessages, defaultSignatureLocationFinder, defaultOrganisationLookup);
this.documentBuilder = documentBuilder;
}
/**
* Constructor used for context based message security providers.
*
* @param messageSecurityProvider MessageSecurityProvider instance to use.
* @param signMessages If messages should be signed or not.
* @param defaultSignatureLocationFinder SignatureLocationFinder instance to use.
* @param defaultOrganisationLookup OrganisationLookup instance to use.
* @throws MessageProcessingException If an error occurs while performing signature operation.
*/
public XMLSigner(MessageSecurityProvider messageSecurityProvider,
boolean signMessages,
SignatureLocationFinder defaultSignatureLocationFinder,
OrganisationLookup defaultOrganisationLookup) throws MessageProcessingException{
this.messageSecurityProvider = messageSecurityProvider;
this.defaultSignatureLocationFinder = defaultSignatureLocationFinder;
this.defaultOrganisationLookup = defaultOrganisationLookup;
this.signMessages = signMessages;
this.documentBuilderFactory = DocumentBuilderFactory.newInstance();
this.documentBuilderFactory.setNamespaceAware(true);
try {
cf = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
throw new MessageProcessingException("Error instantiating CertificateFactory for XMLSigner: " + e.getMessage(),e);
}
supportedDigestsAlgorithm = new HashSet();
supportedSignatureAlgorithm = new HashSet();
for(SigningAlgorithmScheme scheme : SigningAlgorithmScheme.values()){
supportedDigestsAlgorithm.add(scheme.getHashAlgorithmURI());
supportedSignatureAlgorithm.add(scheme.getSignatureAlgorithmURI());
}
TransformerFactory tf = TransformerFactory.newInstance();
try {
transformer = tf.newTransformer();
} catch (TransformerConfigurationException e) {
throw new MessageProcessingException("Error creating Transformer for XMLSigner: " + e.getMessage(),e);
}
}
/**
* Help method to verify a signed enveloped message and performs the following checks. Using
* the default context.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* This method does not perform and authorization call towards message security provider.
*
* @param message the message to verify signature of.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occurred verifying the signature.
*/
@Deprecated
public void verifyEnvelopedSignature(byte[] message) throws MessageContentException, MessageProcessingException{
verifyEnvelopedSignature(ContextMessageSecurityProvider.DEFAULT_CONTEXT,message, false);
}
/**
* Help method to verify a signed enveloped message and performs the following checks.
*
*
That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* This method does not perform and authorization call towards message security provider.
*
* @param context the related message security context
* @param message the message to verify signature of.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occurred verifying the signature.
*/
public void verifyEnvelopedSignature(Context context, byte[] message) throws MessageContentException, MessageProcessingException{
verifyEnvelopedSignature(context, message, false);
}
/**
* Help method to verify a signed enveloped message and performs the following checks.
* Using the default message security context.
*
*
That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param message the message to verify signature of.
* @param authorizeAgainstOrganisation true if the message security provider should perform
* any authorization to the related organisation, that must exist in the message of true.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
@Deprecated
public void verifyEnvelopedSignature(byte[] message, boolean authorizeAgainstOrganisation) throws MessageContentException, MessageProcessingException{
verifyEnvelopedSignature(ContextMessageSecurityProvider.DEFAULT_CONTEXT,message,authorizeAgainstOrganisation);
}
/**
* Help method to verify a signed enveloped message and performs the following checks.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param context the related message security context
* @param message the message to verify signature of.
* @param authorizeAgainstOrganisation true if the message security provider should perform
* any authorization to the related organisation, that must exist in the message of true.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
public void verifyEnvelopedSignature(Context context, byte[] message, boolean authorizeAgainstOrganisation) throws MessageContentException, MessageProcessingException{
Document doc;
try{
doc = getDocumentBuilder().parse(new ByteArrayInputStream(message));
}catch(Exception e){
throw new MessageContentException("Error validating signature of message: " + e.getMessage(),e);
}
verifyEnvelopedSignature(context, doc, authorizeAgainstOrganisation);
}
/**
* Help method to verify a signed enveloped CS message and performs the following checks.
* Using the default message security context.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param doc the message to verify signature of.
* @param performValidation true if the message security provider should perform
* validate that the signing certificate is valid and authorized for related organisation.
* Otherwise must validation be performed manually after the message is parsed.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
@Deprecated
public void verifyEnvelopedSignature(Document doc, boolean performValidation) throws MessageContentException, MessageProcessingException{
verifyEnvelopedSignature(ContextMessageSecurityProvider.DEFAULT_CONTEXT,doc,performValidation);
}
/**
* Help method to verify a signed enveloped CS message and performs the following checks.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param context the related message security context
* @param doc the message to verify signature of.
* @param performValidation true if the message security provider should perform
* validate that the signing certificate is valid and authorized for related organisation.
* Otherwise must validation be performed manually after the message is parsed.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
public void verifyEnvelopedSignature(Context context, Document doc, boolean performValidation) throws MessageContentException, MessageProcessingException{
verifyEnvelopedSignature(context, doc, (performValidation ? defaultOrganisationLookup : null));
}
/**
* Help method to verify a signed enveloped message and performs the following checks. Using the
* default signature location finder.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param context the related message security context
* @param doc the message to verify signature of.
* @param organisationLookup implementation to extract organsiation name from a given XML message.
* If null must validation be performed manually after the message is parsed. It is possible to use the
* checkBasicCertificateValidation help method for this.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
public void verifyEnvelopedSignature(Context context, Document doc, OrganisationLookup organisationLookup) throws MessageContentException, MessageProcessingException{
verifyEnvelopedSignature(context, doc,defaultSignatureLocationFinder,organisationLookup);
}
/**
* Help method to verify a signed enveloped message and performs the following checks. Using the
* default signature location finder.
* Using the default message security context.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param doc the message to verify signature of.
* @param organisationLookup implementation to extract organsiation name from a given XML message.
* If null must validation be performed manually after the message is parsed. It is possible to use the
* checkBasicCertificateValidation help method for this.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
@Deprecated
public void verifyEnvelopedSignature(Document doc, OrganisationLookup organisationLookup) throws MessageContentException, MessageProcessingException{
verifyEnvelopedSignature(doc,defaultSignatureLocationFinder,organisationLookup);
}
/**
* Help method to verify a signed enveloped message and performs the following checks.
* Using the default message security context.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param message the message to verify signature of.
* @param signatureLocationFinder reference to implementation finding the signature element of a document. (Required)
* @param organisationLookup implementation to extract organsiation name from a given XML message.
* If null must validation be performed manually after the message is parsed. It is possible to use the
* checkBasicCertificateValidation help method for this.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
@Deprecated
public void verifyEnvelopedSignature(byte[] message, SignatureLocationFinder signatureLocationFinder, OrganisationLookup organisationLookup) throws MessageContentException, MessageProcessingException {
verifyEnvelopedSignature(ContextMessageSecurityProvider.DEFAULT_CONTEXT,message,signatureLocationFinder,organisationLookup);
}
/**
* Help method to verify a signed enveloped message and performs the following checks.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param context the related message security context
* @param message the message to verify signature of.
* @param signatureLocationFinder reference to implementation finding the signature element of a document. (Required)
* @param organisationLookup implementation to extract organsiation name from a given XML message.
* If null must validation be performed manually after the message is parsed. It is possible to use the
* checkBasicCertificateValidation help method for this.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
public void verifyEnvelopedSignature(Context context, byte[] message, SignatureLocationFinder signatureLocationFinder, OrganisationLookup organisationLookup) throws MessageContentException, MessageProcessingException {
Document doc;
try{
doc = getDocumentBuilder().parse(new ByteArrayInputStream(message));
}catch(Exception e){
throw new MessageContentException("Error validating signature of message: " + e.getMessage(),e);
}
verifyEnvelopedSignature(context, doc, signatureLocationFinder, organisationLookup);
}
/**
* Help method to verify a signed enveloped message and performs the following checks, using
* the default context.
* Using the default message security context.
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param doc the message to verify signature of.
* @param signatureLocationFinder reference to implementation finding the signature element of a document. (Required)
* @param organisationLookup implementation to extract organsiation name from a given XML message.
* If null is basic validation performed such as key usage and expiration, but no revocation checks.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
@Deprecated
public void verifyEnvelopedSignature(Document doc, SignatureLocationFinder signatureLocationFinder, OrganisationLookup organisationLookup) throws MessageContentException, MessageProcessingException {
verifyEnvelopedSignature(ContextMessageSecurityProvider.DEFAULT_CONTEXT,doc,signatureLocationFinder,organisationLookup);
}
/**
* Help method to verify a signed enveloped message and performs the following checks.
*
* That the signature if included X509Certificate verifies.
* That the signatures algorithms is one of supported signature schemes.
* That the signature method is enveloped.
*
* @param context the related message security context
* @param doc the message to verify signature of.
* @param signatureLocationFinder reference to implementation finding the signature element of a document. (Required)
* @param organisationLookup implementation to extract organsiation name from a given XML message.
* If null is basic validation performed such as key usage and expiration, but no revocation checks.
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occured verifying the signature.
*/
public void verifyEnvelopedSignature(Context context, Document doc, SignatureLocationFinder signatureLocationFinder, OrganisationLookup organisationLookup) throws MessageContentException, MessageProcessingException{
try{
Element[] signedElements = signatureLocationFinder.getSignatureLocations(doc);
for(Element signedElement: signedElements) {
Element signature = findSignatureElementInObject(signedElement);
checkValidSignatureURI(signature);
checkValidDigestURI(signature);
checkValidTransform(signature);
checkValidReferenceURI(signedElement, signature, signatureLocationFinder.getIDAttribute());
X509Certificate signerCert = null;
NodeList certList = signature.getElementsByTagNameNS(XMLDSIG_NAMESPACE, "X509Certificate");
if (certList.getLength() > 0) {
String certData = certList.item(0).getFirstChild().getNodeValue();
if (certData != null) {
signerCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(certData)));
}
}
if (signerCert == null) {
throw new MessageContentException("Invalid signature, no related certificate found.");
}
DOMValidateContext validationContext = new DOMValidateContext(signerCert.getPublicKey(), signature);
String idAttribute = signatureLocationFinder.getIDAttribute();
if(idAttribute != null) {
validationContext.setIdAttributeNS(signedElement, null, signatureLocationFinder.getIDAttribute());
}
XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI());
XMLSignature sig = signatureFactory.unmarshalXMLSignature(validationContext);
if (!sig.validate(validationContext)) {
throw new MessageContentException("Error, signed message didn't pass validation.");
}
String organisation = null;
if (organisationLookup != null) {
organisation = organisationLookup.findOrganisation(doc);
}
if(messageSecurityProvider instanceof ContextMessageSecurityProvider){
if (!((ContextMessageSecurityProvider) messageSecurityProvider).isValidAndAuthorized(context,signerCert, organisation)) {
throw new MessageContentException("A certificate with DN " + signerCert.getSubjectDN().toString() + " signing a message wasn't authorized or valid.");
}
}else{
if (!messageSecurityProvider.isValidAndAuthorized(signerCert, organisation)) {
throw new MessageContentException("A certificate with DN " + signerCert.getSubjectDN().toString() + " signing a message wasn't authorized or valid.");
}
}
}
}catch(Exception e){
if(e instanceof MessageContentException ){
throw (MessageContentException) e;
}
if(e instanceof MessageProcessingException){
throw (MessageProcessingException) e;
}
throw new MessageContentException("Error validating signature of message: " + e.getMessage(),e);
}
}
/**
* Method to find first certificate found in signature element using the default signature finder.
*
* @param message the message to extract certificate of.
* @return the certificate in signature.
*
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occurred parsing the certificate.
*/
public X509Certificate findSignerCertificate(byte[] message) throws MessageContentException, MessageProcessingException{
return findSignerCertificate(message,defaultSignatureLocationFinder);
}
/**
* Method to find first certificate found in signature element with specifieddefault signature finder.
*
* @param message the message to extract certificate of.
* @param signatureLocationFinder the custom signature location finder.
* @return the certificate in signature.
*
* @throws MessageContentException if message content was faulty
* @throws MessageProcessingException if internal error occurred parsing the certificate.
*/
public X509Certificate findSignerCertificate(byte[] message, SignatureLocationFinder signatureLocationFinder) throws MessageContentException, MessageProcessingException{
try{
Document doc = getDocumentBuilder().parse(new ByteArrayInputStream(message));
Element signedElement = signatureLocationFinder.getSignatureLocations(doc)[0];
Element signature = findSignatureElementInObject(signedElement);
X509Certificate signerCert = null;
NodeList certList = signature.getElementsByTagNameNS(XMLDSIG_NAMESPACE, "X509Certificate");
if(certList.getLength() > 0){
String certData = certList.item(0).getFirstChild().getNodeValue();
if(certData != null){
signerCert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(certData)));
}
}
if(signerCert == null){
throw new MessageContentException("Invalid signature, no related certificate found.");
}
return signerCert;
}catch(Exception e){
if(e instanceof MessageContentException ){
throw (MessageContentException) e;
}
if(e instanceof MessageProcessingException){
throw (MessageProcessingException) e;
}
throw new MessageContentException("Error parsing the certificate from signature in message: " + e.getMessage(),e);
}
}
/**
* Help method that checks that certificate has:
* Has digital signature key usage
* current time is withing not before and not after.
* @param cert the certificate to check.
* @return true if valid otherwise false.
*/
public static boolean checkBasicCertificateValidation(X509Certificate cert){
boolean[] keyUsage = cert.getKeyUsage();
if (keyUsage != null && !keyUsage[0]) {
log.severe("Error processing Certificate Services message signing certificate expired has not keyUsage digitalSignature.");
return false;
}
Date currentTime = systemTime.getSystemTime();
if(currentTime.after(cert.getNotAfter())){
log.severe("Error processing Certificate Services message signing certificate expired: " + cert.getNotAfter());
return false;
}
if(currentTime.before(cert.getNotBefore())){
log.severe("Error processing Certificate Services message signing certificate not yet valid: " + cert.getNotBefore());
return false;
}
return true;
}
/**
* Help method to sign an XML Document using default context
*
* Method that generates the signature and marshalls the message to byte array in UTF-8 format.
* @param doc a XML document about to be signed.
* @param signatureLocationFinder to find in which element the signature should be placed.
*
* @throws MessageProcessingException if problems occurred when processing the message.
* @throws MessageContentException if unsupported version is detected in message.
*/
@Deprecated
public void sign(Document doc, SignatureLocationFinder signatureLocationFinder) throws MessageProcessingException, MessageContentException {
sign(ContextMessageSecurityProvider.DEFAULT_CONTEXT,doc,signatureLocationFinder);
}
/**
* Help method to sign an XML Document
*
* Method that generates the signature and marshalls the message to byte array in UTF-8 format.
*
* @param context the related message security context
* @param doc a XML document about to be signed.
* @param signatureLocationFinder to find in which element the signature should be placed.
*
* @throws MessageProcessingException if problems occurred when processing the message.
* @throws MessageContentException if unsupported version is detected in message.
*/
public void sign(Context context, Document doc, SignatureLocationFinder signatureLocationFinder) throws MessageProcessingException, MessageContentException{
try {
if(signMessages){
Element[] signatureLocations = signatureLocationFinder.getSignatureLocations(doc);
for(Element signatureLocation : signatureLocations) {
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM",new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI());
SigningAlgorithmScheme scheme = (messageSecurityProvider instanceof ContextMessageSecurityProvider ? ((ContextMessageSecurityProvider) messageSecurityProvider).getSigningAlgorithmScheme(context) : messageSecurityProvider.getSigningAlgorithmScheme());
DigestMethod digestMethod = fac.newDigestMethod(scheme.getHashAlgorithmURI(), null);
String messageID = signatureLocationFinder.getIDValue(signatureLocation);
List transFormList = new ArrayList();
transFormList.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null));
transFormList.add(fac.newTransform(C14N_TRANSFORM, (TransformParameterSpec)null));
Reference ref = fac.newReference((messageID == null ? "" : "#" + messageID),digestMethod, transFormList, null, null);
ArrayList refList = new ArrayList();
refList.add(ref);
CanonicalizationMethod cm = fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE,(C14NMethodParameterSpec) null);
SignatureMethod sm = fac.newSignatureMethod(scheme.getSignatureAlgorithmURI(),null);
SignedInfo signedInfo =fac.newSignedInfo(cm,sm,refList);
List beforeSiblings = signatureLocationFinder.getSiblingsBeforeSignature(signatureLocation);
Node siblingNode = null;
if (beforeSiblings != null) {
for (QName name : beforeSiblings) {
NodeList foundList = signatureLocation.getElementsByTagNameNS(name.getNamespaceURI(), name.getLocalPart());
if (foundList.getLength() > 0) {
siblingNode = foundList.item(0);
break;
}
}
}
PrivateKey signingKey = (messageSecurityProvider instanceof ContextMessageSecurityProvider ? ((ContextMessageSecurityProvider) messageSecurityProvider).getSigningKey(context) : messageSecurityProvider.getSigningKey());
DOMSignContext signContext;
if (siblingNode != null) {
signContext = new DOMSignContext(signingKey, signatureLocation, siblingNode);
} else {
signContext = new DOMSignContext(signingKey, signatureLocation);
}
String idAttribute = signatureLocationFinder.getIDAttribute();
if(idAttribute != null) {
signContext.setIdAttributeNS(signatureLocation, null, idAttribute);
}
signContext.putNamespacePrefix("http://www.w3.org/2000/09/xmldsig#", "ds");
KeyInfoFactory kif = KeyInfoFactory.getInstance("DOM", new org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI());
List certs = new ArrayList();
X509Certificate cert = (messageSecurityProvider instanceof ContextMessageSecurityProvider ? ((ContextMessageSecurityProvider) messageSecurityProvider).getSigningCertificate(context) : messageSecurityProvider.getSigningCertificate());
certs.add(cert);
X509Data x509Data = kif.newX509Data(certs);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509Data));
XMLSignature signature = fac.newXMLSignature(signedInfo, ki);
Provider signProvider;
if(messageSecurityProvider instanceof ContextMessageSecurityProvider){
signProvider = Security.getProvider(((ContextMessageSecurityProvider) messageSecurityProvider).getProvider(context));
} else{
signProvider = Security.getProvider(messageSecurityProvider.getProvider());
}
if(signProvider != null) {
log.fine("Performing xml signature using provider: " + signProvider.getName());
signContext.setProperty("org.jcp.xml.dsig.internal.dom.SignatureProvider", signProvider);
}
signature.sign(signContext);
}
}
} catch (NoSuchAlgorithmException e) {
throw new MessageProcessingException("Error signing the XML, " + e.getMessage(),e);
} catch (InvalidAlgorithmParameterException e) {
throw new MessageProcessingException("Error signing the XML, " + e.getMessage(),e);
} catch (MarshalException e) {
throw new MessageProcessingException("Error signing the XML, " + e.getMessage(),e);
} catch (XMLSignatureException e) {
throw new MessageProcessingException("Error signing the XML, " + e.getMessage(),e);
}
}
/**
* Method to convert a Document to a UTF-8 encoded byte array
* @param doc the document to convert
* @return a marshalled byte array in UTF-8 format.
* @throws MessageProcessingException if problems occurred when processing the message.
* @throws MessageContentException if unsupported version is detected in message.
*/
public byte[] marshallDoc(Document doc) throws MessageProcessingException, MessageContentException{
try {
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
String output = writer.getBuffer().toString();
return output.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new MessageProcessingException("Error marshalling to XML, " + e.getMessage(),e);
} catch (TransformerException e) {
throw new MessageProcessingException("Error marshalling to XML, " + e.getMessage(),e);
}
}
/**
* Help method to marshall and sign an XML Document
*
* Method that generates the signature and marshalls the message to byte array in UTF-8 format.
* @param context related message security context.
* @param doc a XML document about to be signed.
* @param signatureLocationFinder to find in which element the signature should be placed.
* @return a marshalled and signed message.
* @throws MessageProcessingException if problems occurred when processing the message.
* @throws MessageContentException if unsupported version is detected in message.
*/
public byte[] marshallAndSign(Context context, Document doc, SignatureLocationFinder signatureLocationFinder) throws MessageProcessingException, MessageContentException{
sign(context,doc,signatureLocationFinder);
return marshallDoc(doc);
}
/**
* Help method to marshall and sign an XML Document
*
* Method that generates the signature and marshalls the message to byte array in UTF-8 format.
* @param doc a XML document about to be signed.
* @param signatureLocationFinder to find in which element the signature should be placed.
* @return a marshalled and signed message.
* @throws MessageProcessingException if problems occurred when processing the message.
* @throws MessageContentException if unsupported version is detected in message.
*/
@Deprecated
public byte[] marshallAndSign(Document doc, SignatureLocationFinder signatureLocationFinder) throws MessageProcessingException, MessageContentException{
sign(ContextMessageSecurityProvider.DEFAULT_CONTEXT,doc,signatureLocationFinder);
return marshallDoc(doc);
}
/**
* Help method to find ds:Signature element among direct childs to this element.
*/
private Element findSignatureElementInObject(Element signedElement) throws MessageContentException{
NodeList childs = signedElement.getChildNodes();
for(int i = 0; i < childs.getLength(); i++){
Node next = childs.item(i);
if(next instanceof Element) {
if (((Element) next).getLocalName().equals("Signature") && ((Element) next).getNamespaceURI().equals(XMLDSIG_NAMESPACE)) {
return (Element) next;
}
}
}
throw new MessageContentException("Required digital signature not found in message.");
}
/**
* Method that checks the referenced URI actually is the same as ID of the enveloped signed object otherwise MessageContentException
*/
private void checkValidReferenceURI(Element signedElement, Element signature, String signedElementIDAttr) throws MessageContentException{
try{
if(signedElementIDAttr != null) {
String objectID = signedElement.getAttribute(signedElementIDAttr);
Element transform = (Element) signature.getElementsByTagNameNS(XMLDSIG_NAMESPACE, "Reference").item(0);
String referenceID = transform.getAttribute("URI");
if (!referenceID.equals("#" + objectID)) {
throw new MessageContentException("Error checking reference URI of digital signature it doesn't match the id of the signed element.");
}
}
}catch(Exception e){
if(e instanceof MessageContentException){
throw (MessageContentException) e;
}
throw new MessageContentException("Error checking Reference URI with the signed object: " + e.getMessage(),e);
}
}
/**
* Method that returns DocumentBuilder instance to use. This is either an explicit
* instance specified when XMLSigner was constructed, or a new instance is created
* each time.
*
* @return DocumentBuilder instance to use.
* @throws ParserConfigurationException If an error occurs while retrieving DocumentBuilder.
*/
private DocumentBuilder getDocumentBuilder() throws ParserConfigurationException {
if(documentBuilder != null){
return documentBuilder;
}
return documentBuilderFactory.newDocumentBuilder();
}
/**
* Method that check that the transform is set to enveloped otherwise MessageContentException
*/
private void checkValidTransform(Element signature) throws MessageContentException {
try{
Element transform = (Element) signature.getElementsByTagNameNS(XMLDSIG_NAMESPACE, "Transform").item(0);
String algorithm = transform.getAttribute("Algorithm");
if(!algorithm.equals(ENVELOPE_TRANSFORM)){
throw new MessageContentException("Error unsupported transform in digital sigature: " + algorithm + " only enveloped signatures are supported.");
}
}catch(Exception e){
if(e instanceof MessageContentException){
throw (MessageContentException) e;
}
throw new MessageContentException("Error extracting transform from digital sigature: " + e.getMessage(),e);
}
}
/**
* Method that checks supported digestURI from available SigningAlgorithmScheme otherwise MessageContentException
*/
private void checkValidDigestURI(Element signature) throws MessageContentException{
try{
Element transform = (Element) signature.getElementsByTagNameNS(XMLDSIG_NAMESPACE, "DigestMethod").item(0);
String algorithm = transform.getAttribute("Algorithm");
if(!supportedDigestsAlgorithm.contains(algorithm)){
throw new MessageContentException("Error unsupported digest algorithm in digital sigature: " + algorithm + ".");
}
}catch(Exception e){
if(e instanceof MessageContentException){
throw (MessageContentException) e;
}
throw new MessageContentException("Error extracting digest algorithm from digital sigature: " + e.getMessage(),e);
}
}
/**
* Method that checks supported signature algorithm from available SigningAlgorithmScheme otherwise MessageContentException
*/
private void checkValidSignatureURI(Element signature) throws MessageContentException{
try{
Element transform = (Element) signature.getElementsByTagNameNS(XMLDSIG_NAMESPACE, "SignatureMethod").item(0);
String algorithm = transform.getAttribute("Algorithm");
if(!supportedSignatureAlgorithm.contains(algorithm)){
throw new MessageContentException("Error unsupported digest algorithm in digital sigature: " + algorithm + ".");
}
}catch(Exception e){
if(e instanceof MessageContentException){
throw (MessageContentException) e;
}
throw new MessageContentException("Error extracting digest algorithm from digital sigature: " + e.getMessage(),e);
}
}
/**
* Interface used to find the location and ID of a signed object.
*
* @author Philip Vendil
*
*/
public interface SignatureLocationFinder{
/**
* Return the element inside a document that should be signed.
*/
Element[] getSignatureLocations(Document doc) throws MessageContentException;
/**
*
* @return the name of the ID attribute referenced by the envelope signature.
*/
String getIDAttribute();
/**
* @param signedElement the element that should be signed, and ID value for should be fetched.
* @return the signature reference ID value
*
*/
String getIDValue(Element signedElement) throws MessageContentException;
/**
* Method that should return the possible siblings that should be placed before the signature element in the
* specified element. If a specified sibling isn't found should the next be used.
* @param element the element about to be signed and those siblings should be found.
* @return a list of siblings that should be before signature, if the first doesn't exist due to optional is the next
* in list use. return null to place signature last in element.
* @throws MessageContentException if problems was found with the supplied element
*/
List getSiblingsBeforeSignature(Element element) throws MessageContentException;
}
/**
* Interface for determining organisation related to a XML message.
*
* Created by philip on 31/12/16.
*/
public interface OrganisationLookup {
String UNKNOWN = "UNKNOWN";
/**
* Method that should extract the organisiation name from a given xml message.
* @param doc the document to extract organisation from.
* @return might the organisation found, it might return UNKNWON if supported with used MessageSecurityProvider.
* @throws MessageContentException if organisation couldn't be extracted from message.
* @throws MessageProcessingException if internal problems occurred extractign the organisation name.
*/
String findOrganisation(Document doc) throws MessageContentException, MessageProcessingException;
}
}