I have already lost a tremenodus amount of time with this problem, so I'm posting a question (or at least debate) here before giving up.
In a .NET project (C#) I have to sign an XML. I have created a test project. Currently, I can sign a document and successfully validate signature by adding a transform to the XAdES reference (XmlDsigExcC14NTransform()) or by appending contents of the node outside of the element. Both of these things break the desired schema.
Example of a valid XML signature:
<ds:Signature Id="SignatureId">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://ift.tt/y9fQ1c"></ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://ift.tt/zf1Wx4"></ds:SignatureMethod>
<ds:Reference Type="http://ift.tt/1x6HRkm" URI="#data">
<ds:DigestMethod Algorithm="http://ift.tt/yuvO4a"></ds:DigestMethod>
<ds:DigestValue>FRFv9FCSvEud/vgoM1Y5/id9u9Y=</ds:DigestValue>
</ds:Reference>
<ds:Reference Type="http://ift.tt/1xHz0s0" URI="#SignedPropertiesId">
<ds:DigestMethod Algorithm="http://ift.tt/yuvO4a"></ds:DigestMethod>
<ds:DigestValue>8idTuWyKmpJ8uNmha2OtjIZyvcQ=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
<!-- signature -->
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
<!-- cert -->
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
<ds:Object>
<xds:QualifyingProperties Target="#SignatureId">
<xds:SignedProperties Id="SignedPropertiesId">
<xds:SignedSignatureProperties>
<xds:SigningTime>2012-02-07T06:44:39.447Z</xds:SigningTime>
<xds:SigningCertificate>
<xds:Cert>
<xds:CertDigest>
<xds:DigestMethod Algorithm="http://ift.tt/yuvO4a"></xds:DigestMethod>
<xds:DigestValue>ES21gCRWbird7sXyaJt5sgC9c6Y=</xds:DigestValue>
</xds:CertDigest>
<xds:IssuerSerial>
<ds:X509IssuerName><!-- issuer --></ds:X509IssuerName>
<ds:X509SerialNumber><!-- serial --></ds:X509SerialNumber>
</xds:IssuerSerial>
</xds:SigningCertificate>
</xds:Cert>
<xds:SignaturePolicyIdentifier>
<xds:SignaturePolicyImplied></xds:SignaturePolicyImplied>
</xds:SignaturePolicyIdentifier>
</xds:SignedSignatureProperties>
</xds:SignedProperties>
</xds:QualifyingProperties>
</ds:Object>
</ds:Signature>
Because of the inability to create a prefixed signature I'm able to create the signature without prefix and with inline schema definition. For now, I'm exploring the GetIdElement method and it's influence.
With this code
public override XmlElement GetIdElement(XmlDocument document, string idValue)
{
XmlElement elem = base.GetIdElement(document, idValue);
if (elem == null)
{
string nodePath = String.Format("//*[@Id = '{0}']", idValue);
foreach (DataObject data in base.Signature.ObjectList)
{
foreach (XmlNode node in data.Data)
{
XmlElement n = (XmlElement)node.SelectSingleNode(nodePath);
return n;
}
}
}
return elem;
}
I get this signature (invalid, but the document is OK considering the schema):
<Signature Id="SignatureId" xmlns="http://ift.tt/uq6naF">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://ift.tt/y9fQ1c"/>
<SignatureMethod Algorithm="http://ift.tt/zf1Wx4"/>
<Reference URI="#data" Type="http://ift.tt/1x6HRkm">
<DigestMethod Algorithm="http://ift.tt/yuvO4a"/>
<DigestValue>ifp7gZyWRzR2dIcr9QhR/PXzp+4=</DigestValue>
</Reference>
<Reference URI="#SignedPropertiesId" Type="http://ift.tt/1xHz0s0">
<DigestMethod Algorithm="http://ift.tt/yuvO4a"/>
<DigestValue>sq33gg5OCHQQOdYGtEvBW7Pe28I=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue><!-- signature --></SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate><!-- cert --></X509Certificate>
</X509Data>
</KeyInfo>
<Object>
<xds:QualifyingProperties Target="#SignatureId">
<xds:SignedProperties Id="SignedPropertiesId">
<xds:SignedSignatureProperties>
<xds:SigningTime>2014-12-23T12:56:01.6933138+01:00</xds:SigningTime>
<xds:SigningCertificate>
<xds:Cert>
<xds:CertDigest>
<xds:DigestMethod Algorithm="http://ift.tt/yuvO4a"/>
<xds:DigestValue>9KZXYaGYIVLSfCOAOBYDcQppt/g=</xds:DigestValue>
</xds:CertDigest>
<xds:IssuerSerial>
<ds:X509IssuerName xmlns:ds="http://ift.tt/uq6naF"><!-- issuer --></ds:X509IssuerName>
<ds:X509SerialNumber xmlns:ds="http://ift.tt/uq6naF"><!-- serial --></ds:X509SerialNumber>
</xds:IssuerSerial>
</xds:Cert>
</xds:SigningCertificate>
<xds:SignaturePolicyIdentifier>
<xds:SignaturePolicyImplied/>
</xds:SignaturePolicyIdentifier>
</xds:SignedSignatureProperties>
</xds:SignedProperties>
</xds:QualifyingProperties>
</Object>
</Signature>
But when changing the code to this:
public override XmlElement GetIdElement(XmlDocument document, string idValue)
{
XmlElement elem = base.GetIdElement(document, idValue);
if (elem == null)
{
string nodePath = String.Format("//*[@Id = '{0}']", idValue);
foreach (DataObject data in base.Signature.ObjectList)
{
foreach (XmlNode node in data.Data)
{
XmlNode signatureNode = document.ImportNode(node, true);
document.DocumentElement.AppendChild(signatureNode);
XmlElement n = (XmlElement)document.DocumentElement.SelectSingleNode(nodePath);
return n;
}
}
}
return elem;
}
I get this XML (valid signature but obviously does not conform to the given schema):
<xds:QualifyingProperties Target="#SignatureId">
<xds:SignedProperties Id="SignedPropertiesId">
<xds:SignedSignatureProperties>
<xds:SigningTime>2014-12-23T13:03:03.0731414+01:00</xds:SigningTime>
<xds:SigningCertificate>
<xds:Cert>
<xds:CertDigest>
<xds:DigestMethod Algorithm="http://ift.tt/yuvO4a"/>
<xds:DigestValue>9KZXYaGYIVLSfCOAOBYDcQppt/g=</xds:DigestValue>
</xds:CertDigest>
<xds:IssuerSerial>
<ds:X509IssuerName xmlns:ds="http://ift.tt/uq6naF">OU=sigen-ca, O=state-institutions, C=si</ds:X509IssuerName>
<ds:X509SerialNumber xmlns:ds="http://ift.tt/uq6naF">994417425</ds:X509SerialNumber>
</xds:IssuerSerial>
</xds:Cert>
</xds:SigningCertificate>
<xds:SignaturePolicyIdentifier>
<xds:SignaturePolicyImplied/>
</xds:SignaturePolicyIdentifier>
</xds:SignedSignatureProperties>
</xds:SignedProperties>
</xds:QualifyingProperties>
<Signature Id="SignatureId" xmlns="http://ift.tt/uq6naF">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://ift.tt/y9fQ1c"/>
<SignatureMethod Algorithm="http://ift.tt/zf1Wx4"/>
<Reference URI="#data" Type="http://ift.tt/1x6HRkm">
<DigestMethod Algorithm="http://ift.tt/yuvO4a"/>
<DigestValue>ifp7gZyWRzR2dIcr9QhR/PXzp+4=</DigestValue>
</Reference>
<Reference URI="#SignedPropertiesId" Type="http://ift.tt/1xHz0s0">
<DigestMethod Algorithm="http://ift.tt/yuvO4a"/>
<DigestValue>NyMmXQcfGhe1XuoC65yNoLZQ3Yc=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue><!-- signature --></SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate><!-- cert --></X509Certificate>
</X509Data>
</KeyInfo>
<Object>
<!-- this inner element is obviously reduntant -->
<xds:QualifyingProperties Target="#SignatureId">
<xds:SignedProperties Id="SignedPropertiesId">
<xds:SignedSignatureProperties>
<xds:SigningTime>2014-12-23T13:03:03.0731414+01:00</xds:SigningTime>
<xds:SigningCertificate>
<xds:Cert>
<xds:CertDigest>
<xds:DigestMethod Algorithm="http://ift.tt/yuvO4a"/>
<xds:DigestValue>9KZXYaGYIVLSfCOAOBYDcQppt/g=</xds:DigestValue>
</xds:CertDigest>
<xds:IssuerSerial>
<ds:X509IssuerName xmlns:ds="http://ift.tt/uq6naF"><!-- issuer --></ds:X509IssuerName>
<ds:X509SerialNumber xmlns:ds="http://ift.tt/uq6naF"><!-- serial --></ds:X509SerialNumber>
</xds:IssuerSerial>
</xds:Cert>
</xds:SigningCertificate>
<xds:SignaturePolicyIdentifier>
<xds:SignaturePolicyImplied/>
</xds:SignaturePolicyIdentifier>
</xds:SignedSignatureProperties>
</xds:SignedProperties>
</xds:QualifyingProperties>
</Object>
</Signature>
The code that basically creates signature:
public static XmlDocument AddSignature(XmlDocument sourceDocument, X509Certificate2 certificate)
{
sourceDocument.PreserveWhitespace = true;
Dictionary<string, string> parts = new Dictionary<string, string>();
parts.Add("#data", namespaceESlogType); // the content part, this signature validates OK even without transform (not included in the examples)!
parts.Add("#SignedPropertiesId", namespaceETSIType);
SignedXml signedXml = new XAdESSignedXml(sourceDocument); // custom class containing the GetIdElement method
Reference reference = null;
// sign with certificates private key;
if (false == certificate.HasPrivateKey)
throw new InvalidOperationException("The certificate must contain private key!");
signedXml.SigningKey = certificate.PrivateKey;
// add certificate to signature
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new KeyInfoX509Data(certificate));
signedXml.KeyInfo = keyInfo;
signedXml.Signature.Id = "SignatureId";
XmlElement elem = sourceDocument.CreateElement("xds", "QualifyingProperties", namespaceETSI);
elem.Attributes.Append(sourceDocument.CreateAttribute("Target"));
elem.Attributes["Target"].Value = String.Format(CultureInfo.InvariantCulture, "#{0}", signedXml.Signature.Id);
XmlElement signedProperties = sourceDocument.CreateElement("xds", "SignedProperties", namespaceETSI);
XmlAttribute idAttribute = sourceDocument.CreateAttribute("Id");
idAttribute.Value = "SignedPropertiesId";
signedProperties.Attributes.Append(idAttribute);
XmlElement signedSignatureProperties = sourceDocument.CreateElement("xds", "SignedSignatureProperties", namespaceETSI);
XmlElement signingTime = sourceDocument.CreateElement("xds", "SigningTime", namespaceETSI);
signingTime.InnerText = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz");
signedSignatureProperties.AppendChild(signingTime);
// Add certificate properties
XmlElement signingCertificateElement = CreateSigningCertificateElement(sourceDocument, certificate);
signedSignatureProperties.AppendChild(signingCertificateElement);
// Add policy identifier
XmlElement signaturePolicyIdentifierElement = sourceDocument.CreateElement("xds", "SignaturePolicyIdentifier", namespaceETSI);
XmlElement signaturePolicyImpliedElement = sourceDocument.CreateElement("xds", "SignaturePolicyImplied", namespaceETSI);
signaturePolicyIdentifierElement.AppendChild(signaturePolicyImpliedElement);
signedSignatureProperties.AppendChild(signaturePolicyIdentifierElement);
signedProperties.AppendChild(signedSignatureProperties);
elem.AppendChild(signedProperties);
// Add QualifyingProperties to signature object data
DataObject dataObject = new DataObject(null, null, null, elem);
signedXml.AddObject(dataObject);
// Add parts to be signed.
foreach (KeyValuePair<string, string> kv in parts)
{
reference = new Reference();
if (!String.IsNullOrEmpty(kv.Value))
reference.Type = kv.Value;
reference.Uri = kv.Key;
//reference.AddTransform(new XmlDsigExcC14NTransform()); // adding the transform makes the signature valid, but not schema-conformant
signedXml.AddReference(reference);
}
signedXml.ComputeSignature();
XmlNode signatureNode = sourceDocument.ImportNode(signedXml.GetXml(), true);
sourceDocument.DocumentElement.AppendChild(signatureNode);
return sourceDocument;
}
What does a XmlDsigExcC14NTransform do to the signature because adding this transform makes the generated signature valid? Could there be something with namespaces that makes the #SignedPropertiesId node somehow invisible when inside the same element? Any idea?
No comments:
Post a Comment