8

I work with the brazilian "Nota Fiscal Eletronica" project, in which they define a standart way to sign XML documents.

Recently, they started to require that there are absolutely no whitespaces between tags, including signature tags (*).

We happen to use apache's XMLSignature and I can't seem to produce an unindented signature.

If I remove the whitespaces after signing, the signature gets broken.

I cannot change the canonicalizer / transformers set either, since they're predefined.

I couldn't find an option or parameter in the XMLSignature API to control indentation or whitespaces.

Below is the code:

    // the element where to insert the signature
    Element element = ...;
    X509Certificate cert = ...;
    PrivateKey privateKey = ...;

    XMLSignature signer =
            new XMLSignature(doc, "http://xml-security",
            XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);

    element.appendChild(signer.getElement());

    Transforms transforms = new Transforms(doc);

    // Define as regras de transformação e canonicalização do documento
    // XML, necessário para fazer a verificação do parsing e da
    // assinatura pelos destinatários
    transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); //, xpath.getElementPlusReturns());

    transforms.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); //,xpath.getElementPlusReturns());

    String id = "";

    id = ((Element) element.getElementsByTagName("infNFe").item(0)).getAttributeNode("Id").getNodeValue();

    signer.addDocument("#" + id, transforms, 
                       Constants.ALGO_ID_DIGEST_SHA1);
    signer.addKeyInfo(cert);
    signer.sign(privateKey);

And below is the resulting signature (snippet):

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#NFe43110189716583000165550010000076011492273645">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>fas0ra5uRskQgRHSrIYhEjFEjKQ=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
2RGltUZy0HfNoiKtVanAeN+JUPyglWDuQNnMudSgA7kESoHBZ/q/GMbc+xMSN1eV8u7+2PxSKl1T
Zl592FWmCSAkL8pwMujDxJ4iTLU20Hf0dNF7oGcyB+g9GgbipW2udq0kwJLz6HzXUD/Evf/0y+3T
NtsXeIaA6A29ttD/UEs=
</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>
MIIFqTCCBJGgAwIBAgIEQeNSuzANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJicjETMBEGA1UE
ChMKSUNQLUJyYXNpbDEgMB4GA1UECxMXQ2FpeGEgRWNvbm9taWNhIEZlZGVyYWwxFDASBgNVBAMT
C0FDIENBSVhBIFBKMB4XDTEwMDYwODE5MjQwNVoXDTExMDYwODE5NTQwNVowgYQxCzAJBgNVBAYT
AmJyMRMwEQYDVQQKEwpJQ1AtQnJhc2lsMSAwHgYDVQQLExdDYWl4YSBFY29ub21pY2EgRmVkZXJh
bDEUMBIGA1UECxMLQUMgQ0FJWEEgUEoxKDAmBgNVBAMTH0EgQlVITEVSIFNBIENVUlRVTUU6NDA5
NDI0OTAwMTAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOFxgvG35RQWgXec4zVrzoUHolnJ
fP76rpO2Vo40593W9Gf0WwHt36gVmli0ZeQitFmzFSoE5KhgXQGZg6RpV3WJUFcIrPBHPdqOSfiB
988kf962P+j8fZ38BNmo7TV9H9hMBkV9bD/QOe73wFDc+rT6/9io++Z+7/wup/3glKntAgMBAAGj
ggLOMIICyjAOBgNVHQ8BAf8EBAMCBeAwVwYDVR0gBFAwTjBMBgZgTAECAQkwQjBABggrBgEFBQcC
ARY0aHR0cDovL2ljcC5jYWl4YS5nb3YuYnIvcmVwb3NpdG9yaW8vZHBjYWNjYWl4YXBqLnBkZjAp
BgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwgbYGA1UdEQSBrjCBq4EV
YnVobGVyQGFidWhsZXIuY29tLmJyoD4GBWBMAQMEoDUEMzE0MDkxOTQ2NDA5NDI0OTAwMTAxMDg0
NDcwODE3NTAwMDAwODAzMjkyMjM1NlNTUCBSU6AeBgVgTAEDAqAVBBNOQUlSIEJVSExFUiBTQ0hO
RUNLoBkGBWBMAQMDoBAEDjg5NzE2NTgzMDAwMTY1oBcGBWBMAQMHoA4EDDAwMDAwMDAwMDAwMDCC
ATIGA1UdHwSCASkwggElMIGuoIGroIGohjJodHRwOi8vaWNwLmNhaXhhLmdvdi5ici9yZXBvc2l0
b3Jpby9BQ0NBSVhBUEoxLmNybIY0aHR0cDovL2ljcDIuY2FpeGEuZ292LmJyL3JlcG9zaXRvcmlv
Mi9BQ0NBSVhBUEoxLmNybIY8aHR0cDovL3JlcG9zaXRvcmlvLmljcGJyYXNpbC5nb3YuYnIvbGNy
L2NhaXhhL0FDQ0FJWEFQSjEuY3JsMHKgcKBupGwwajELMAkGA1UEBhMCYnIxEzARBgNVBAoTCklD
UC1CcmFzaWwxIDAeBgNVBAsTF0NhaXhhIEVjb25vbWljYSBGZWRlcmFsMRQwEgYDVQQDEwtBQyBD
QUlYQSBQSjEOMAwGA1UEAxMFQ1JMNDEwHwYDVR0jBBgwFoAUjkAvCv4T1ao5oHZ0htO8fcfx5c8w
CQYDVR0TBAIwADAZBgkqhkiG9n0HQQAEDDAKGwRWNy4xAwIDqDANBgkqhkiG9w0BAQUFAAOCAQEA
nZHUvdnZsiCIDjKm1zHehbtuDtDJha4O4FZ03J74Y+AxyAFs/4JED+xUvZ5jFuEsdqgA0V/dxUFy
Uz/ca10Ievd578GQdGwYl1GFhRtO/SlxeaOEf7eDdGOWXO3VmUA3NmNo0X8RRTIoifnhpDXu7RbN
5sijyH/uXyRFWX9XH2N0U/r3oJtNKXsvoUlbDrkalgkuLzLKsaEj0TkwisXO3cmMoWGuBpAZC+46
e4x/2vTqOvYkzZO+O9NLi0YWSYY7OJKiKBjMC6MzdlPM9VTkIwO9WvWEMdbU0/jhO2cMcVMzNZc1
r6ZmdTDrwqV3elSTkQtJ0RIZNgMJUn+Y8c7Aog==
</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>

Notice the (unwanted) line-breaks.

Any help would be greatly appreciated.

Thanks a lot in advance.

(*) Clarification: the new rule forbids whitespaces (or any other text) between element-only tags. As an example, this would be allowed:

<a><b>
  text
  inside
  tag
</b></a>

while this would be forbidden:

<a>
<b>text</b>
</a>

because on the latter case, the whitespaces (line-breaks) are between two tags, or, in other words, placed inside an element-only tag.

Perception
  • 79,279
  • 19
  • 185
  • 195
Jonathas Carrijo
  • 743
  • 2
  • 7
  • 14
  • 9
    The problem is that their requirement doesn't make sense from XML and XMLDSig point of view. Most likely they have made some mistake in their code, and now instead of confirming the mistake and fixing the code they want everybody else deal with their broken implementation. – Eugene Mayevski 'Callback Jan 18 '11 at 19:55
  • I support @Eugene Mayevski 'EldoS Corp . I've handled xml like the one above without any problems. – Bozho Jan 18 '11 at 19:57
  • They claim that they stated this rule in order to reduce the network traffic (which is actually huge), since some softwares were including abusive amounts of indentation whitespaces. They are able to verify the signature successfully. There's this other (unrelated and new) rule that is applied which incurs in the rejection of the message. I agree, though, that they shouldn't impose this restriction on the signature portion. – Jonathas Carrijo Jan 18 '11 at 20:04
  • 2
    Whether is their mistake or not ( and I agree is likely to be their fault ), doesn't change Jonathas requirement. +1 for the question and I would like to know what the solution will be. – OscarRyz Jan 18 '11 at 20:30

8 Answers8

9

You can simply set -Dorg.apache.xml.security.ignoreLineBreaks=true for disabling '\n' in XML generation. original mail

bug description

Vitaly
  • 106
  • 1
  • 1
  • I couldn't test this solution, since by the time you answered, I had already applied a workaround solution by choosing another signature API. But I looked into the links and this seems to be exactly the simple and much less traumatic solution that I was looking for back than. – Jonathas Carrijo Mar 21 '13 at 00:03
  • 1
    @JonathasCarrijo what signature API did you choose? I'm working on a solution for the German EBICS protocol and I'm facing this problem because bank cant accept multiline SignatureValue tag data. – Apostolos Mar 31 '18 at 16:28
  • 1
    This does not work for me. Any idea why this property might not work? – Dinu Nicolae Apr 05 '23 at 15:43
5

You can try:

static {
    System.setProperty("com.sun.org.apache.xml.internal.security.ignoreLineBreaks", "true");
    com.sun.org.apache.xml.internal.security.Init.init();
}

Or

static {
    System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");
    org.apache.xml.security.Init.init();
}

Add this to the class which does signature job.

hilaldgdvrn
  • 51
  • 1
  • 3
4

the signature blocks are encoding binary information as Base64, which must follow some formating including line breaks (see http://en.wikipedia.org/wiki/Base64). So you simply can't remove them without alter the information.

a better way to redure network traffic, is to use comression before sending data.

lweller
  • 11,077
  • 3
  • 34
  • 38
  • +1, very good answer. The wikipedia article states that there is a maximum line length, and a line separator. Using compression is the right way to fix this. – Bozho Jan 18 '11 at 20:36
  • The new rule forbids whitespaces only inbetween tags. The text inside a leaf tag (such as the base64 encoded info) may contain whitespaces. I will update the question to clarify this. – Jonathas Carrijo Jan 19 '11 at 00:39
  • So we miss unterstood us. In this case you may get it work (see my other answer). – lweller Jan 19 '11 at 09:34
  • If they are converted to base64 shouldn't there won't be any new line and white space. Base64 does not contain that. Am I thinking wrong? – Suraj Jain May 29 '20 at 11:03
  • Line breaks are not a part of Base64 (as @SurajJain said). They are simply ignored when the content is decoded. Removing line breaks or from the SignatureValue should not change the signature. – Erlend Nov 04 '20 at 14:36
3

We just need to set the "true" value to the "ignoreLineBreaks" parameter, cause' the default value is false and this allows to the signature API to add LineBreaks

here is the code to avoid or remove LineBreaks

Field f = XMLUtils.class.getDeclaredField("ignoreLineBreaks");
f.setAccessible(true);
f.set(null, Boolean.TRUE);

then, we'll can make sure that the new value is true with the next code line

System.err.println(XMLUtils.ignoreLineBreaks());

I had the same problem and this worked for me.

2

You can try:

System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");
  • 1
    This had no effect for me. Still getting " " after every certificate and signature line – user3217883 Dec 06 '19 at 14:48
  • 2
    The property gets evaluated in org.apache.xml.security.utils.XMLUtils. This class will be loaded if org.apache.xml.securit.Init.init() is called. You have to set this property before you call the init() method. With this property set to true a RFC4648 Base64 encoder will be used which does not insert clrfs (like the default RFC2045 Base64 Mime encoder) which will be encoded to " " – Chr3is Jan 20 '20 at 09:32
  • @Chr3is tried it with org.apache.xml.securit.Init.init(), but it still does not work. – Dinu Nicolae Apr 05 '23 at 15:58
2

Fortunately XMLSignature is opensource, so I guess you'll have to get the source code and hack it your self.

Probably your solution will help others in the future so, create a patch and send it back to the project.

Good luck!

:) :)

OscarRyz
  • 196,001
  • 113
  • 385
  • 569
2

XML Signature signs part of an XML Document starting with a given element (i.e. a sub tree in DOM) after it is normalized with a C14N algorithm. The standard C14N algorithm you use preserves line breaks and white spaces (see http://www.w3.org/TR/xml-c14n#Example-WhitespaceInContent).

So all line breaks in the signed part of the original document (including between last tag of data and the <Signature> tag, and between </Signature> and the next closing tag) *must be preserved so as not to alter the signature. The line breaks and spaces in the Signature element itself are not important and may be removed without altering the signature.

Here an example:

<root id="signedpart">
  <data>
     ...
  </data>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
     <SignedInfo>
       <Reference URI="#signedpart">
          ...
       </Reference>
     </SignedInfo>
  </Signature>
</root> 

Here are your possible options:

  1. define your own C14N algorithm that will remove spaces and line breaks by it self. I would discourage this as the other side must also use this non standard C14N algorithm.

  2. remove line breaks an spaces from you XML before signing it (and potentially remove spaces in signature afterwards)

with the example this will give you the following signed XML:

<root id="signedpart"><data>...</data><Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
       <Reference URI="#signedpart">
          ...
       </Reference>
     </SignedInfo>
  </Signature></root>

and after removing spaces in signature

<root id="signedpart"><data>...</data><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><Reference URI="#signedpart">...</Reference></SignedInfo></Signature></root>
Todd Dill
  • 42
  • 6
lweller
  • 11,077
  • 3
  • 34
  • 38
  • Option 1. I'm not allowed to do so, since the canonicalizers ans transforms are predefined and cannot be changed. – Jonathas Carrijo Jan 19 '11 at 18:09
  • Option 2. If I remove the spaces from the signature afterwards, the signature unavoidably gets broken. I think the only way is to produce the signature straight without line-breaks. – Jonathas Carrijo Jan 19 '11 at 18:11
  • I tested it my self (with apache xmlsec 1.4.4) and removing line breaks and spaces **between** tags of signature does not alter it. – lweller Jan 19 '11 at 18:17
  • We double checked: we got an already signed XML with line breaks in the signature (regardless of the software used to produce it), and after removing a single line break from within the tag, the signature was broken. We use xmlsec 1.4.2. Did your signature get produced with line-breaks? If no, this could be a version issue. – Jonathas Carrijo Jan 19 '11 at 19:03
  • signature produced with xmlsec 1.4.4 also produces line breaks, but I can remove them without the breaking signature, what is not so surprising as signature is obviously not part of signed content – lweller Jan 19 '11 at 19:11
  • I believe, as of the XML Signature specification, the signature algorithm itself is applied over the content of SignedInfo tag, which contains all information about the c14n, transforms, and most important, the message digest value. See: (http://www.w3.org/TR/xmldsig-core/#sec-o-Simple). It says: [s02-12] The required SignedInfo element is the information that is actually signed. – Jonathas Carrijo Jan 19 '11 at 19:57
  • @JonathasCarrijo is correct. The signedInfo references point to the elements that are signed. Each element is canonicalized/transformed according to the methods inside the reference, and a hash is calculated and added as the digest value. Next canonicalization of the signedInfo object is performed, and then the canonicalized signedInfo is hashed and then a signature over the hash is created. In the original post, the element with ID "NFe43110189716583000165550010000076011492273645" is canonicalized and hashed with SHA1. The signed info element is then canonicalized, hashed with SHA1 and signed – Erlend Nov 04 '20 at 14:34
1

I found a (shameful) solution.

It's not the expected solution, though: replacing apache's API with javax.xml.crypto API.

Here's the changed code:

// the element where to insert the signature
Element element = ...;
X509Certificate cert = ...;
PrivateKey privateKey = ...;
// Create a DOM XMLSignatureFactory that will be used to
// generate the enveloped signature.
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

// Create a Reference to the enveloped document (in this case,
// you are signing the whole document, so a URI of "" signifies
// that, and also specify the SHA1 digest algorithm and
// the ENVELOPED Transform.
List<Transform> transformList = new ArrayList<Transform>();
TransformParameterSpec tps = null;
Transform envelopedTransform;
try {
    envelopedTransform = fac.newTransform(Transform.ENVELOPED,
            tps);
    Transform c14NTransform = fac.newTransform(
            "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", tps);

    transformList.add(envelopedTransform);
    transformList.add(c14NTransform);
} catch (NoSuchAlgorithmException e) {
    throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
} catch (InvalidAlgorithmParameterException e) {
    throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
}

// Create the KeyInfo containing the X509Data.
KeyInfoFactory kif = fac.getKeyInfoFactory();
List<Serializable> x509Content = new ArrayList<Serializable>();
x509Content.add(cert);
javax.xml.crypto.dsig.keyinfo.X509Data xd = kif.newX509Data(x509Content);
KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));

// Obtem elemento do documento a ser assinado, será criado uma
// REFERENCE para o mesmo
Element el = (Element) element.getElementsByTagName(subTag).item(0);
String id = el.getAttribute("Id");

// Create a DOM XMLSignatureFactory that will be used to
// generate the enveloped signature.

Reference ref;
javax.xml.crypto.dsig.SignedInfo si;
try {
    ref = fac.newReference("#" + id, fac.newDigestMethod(
            DigestMethod.SHA1, null), transformList, null, null);

    // Create the SignedInfo.
    si = fac.newSignedInfo(fac.newCanonicalizationMethod(
            CanonicalizationMethod.INCLUSIVE,
            (C14NMethodParameterSpec) null), fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
            Collections.singletonList(ref));
} catch (NoSuchAlgorithmException e) {
    throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
} catch (InvalidAlgorithmParameterException e) {
    throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
}

// Create the XMLSignature, but don't sign it yet.
javax.xml.crypto.dsig.XMLSignature signature = fac.newXMLSignature(si, ki);

// Marshal, generate, and sign the enveloped signature.
// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element.
DOMSignContext dsc = new DOMSignContext(privateKey, element);
signature.sign(dsc);

This API produces the signature with no whitespaces between tags at all.

Still would like to see a solution for apache's API, since this code was very mature already, and we wouldn't like to risk as much as changing the entire signature implementation.

Jonathas Carrijo
  • 743
  • 2
  • 7
  • 14
  • 1
    I am using javax.xml.crypto.dsig.SignedInfo and javax.xml.crypto.dsig.XMLSignature but the resulting certificate that gets added to the x509Content and the SignatureValue both contain " " after every line. – user3217883 Dec 06 '19 at 15:02
  • 1
    @user3217883 I am having the same exact problem. Did you manage to find a solution? – HomeIsWhereThePcIs Dec 08 '19 at 19:04
  • sorry, too much time has passed for me to remember the details. I may have just done a java replace like: signatureValue.replace(" ",''); – user3217883 May 07 '20 at 19:18