我想通过添加存档时间戳将 XAdES-XL 签名扩展到 XAdES-A。我已遵循 ETSI EN 319 132-1 V1.2.1 (2022-02) 第 5.5.2.2 节中的每个步骤,但我仍然收到一条错误,指出存档时间戳哈希值不正确。
这里是签名文件链接:XAdES-XL
using var finalOctetStream = new MemoryStream();
// ETSI EN 319 132-1 V1.2.1 (2022-02)
// 5.5.2.2 >> Article 3
// Take all the ds:Reference elements in their order of appearance within ds:SignedInfo referencing whatever the signer wants to sign including the SignedProperties element.
// Process each one as indicated below:
foreach (Reference reference in XL_signatureDocument.XadesSignature.SignedInfo.References)
{
var uri = reference.Uri;
byte[] referenceData;
if (string.IsNullOrEmpty(uri))
{
//Full document
referenceData = Encoding.UTF8.GetBytes(xmlDoc.OuterXml);
}
else
{
//Select Uri target element
var referencedNode = xmlDoc.SelectSingleNode($"//*[@Id='{uri.Substring(1)}']");
referenceData = Encoding.UTF8.GetBytes(referencedNode.OuterXml);
}
if (reference.TransformChain != null && reference.TransformChain.Count > 0)
{
// 5.5.2.2 >> Artcile 3-C
// If the ds:Reference element contains the ds:Transforms element, then apply all the transforms indicated within the ds:Transform children elements. After that:
foreach (var transform in reference.TransformChain)
referenceData = XMLUtil.ApplyTransform(referenceData, (System.Security.Cryptography.Xml.Transform)transform);
// c) if the output of the last transform is a XML node-set according to XMLDSIG [1], canonicalize it as specified in clause 4.5 of the present document;
if (IsXmlNodeSet(referenceData))
referenceData = XMLUtil.ApplyTransform(referenceData, new XmlDsigC14NTransform());
}
else
{
//b) if the retrieved data object is an XML node-set, then canonicalize it as specified in clause 4.5 of the present document;
if (IsXmlNodeSet(referenceData))
referenceData = XMLUtil.ApplyTransform(referenceData, new XmlDsigC14NTransform());
}
//d) Concatenate the resulting octets to the final octet stream.
finalOctetStream.Write(referenceData, 0, referenceData.Length);
}
// 5.5.2.2 >>> Article 4
// Take the following XMLDSIG elements in the order they are listed below, canonicalize each one as specified in clause 4.5, and concatenate each resulting octet stream to the final octet stream:
// The ds:SignedInfo element.
// The ds:SignatureValue element.
// The ds:KeyInfo element, if present.
var signatureValueElementXpaths = new ArrayList() { "ds:SignedInfo", "ds:SignatureValue", "ds:KeyInfo" };
// 5.5.2.2 >>> Article 5 and 6
// Take the unsigned signature qualifying properties that appear before the current ArchiveTimeStamp in the order they appear within the UnsignedSignatureProperties,
// canonicalize each one as specified in clause 4.5, and concatenate each resulting octet stream to the final octet stream. While concatenating, the following rules apply:
signatureValueElementXpaths.Add("ds:Object/xades:QualifyingProperties/xades:UnsignedProperties/xades:UnsignedSignatureProperties/xades:SignatureTimeStamp");
signatureValueElementXpaths.Add("ds:Object/xades:QualifyingProperties/xades:UnsignedProperties/xades:UnsignedSignatureProperties/xades:CompleteCertificateRefs");
signatureValueElementXpaths.Add("ds:Object/xades:QualifyingProperties/xades:UnsignedProperties/xades:UnsignedSignatureProperties/xades:CompleteRevocationRefs");
signatureValueElementXpaths.Add("ds:Object/xades:QualifyingProperties/xades:UnsignedProperties/xades:UnsignedSignatureProperties/xades:CertificateValues");
signatureValueElementXpaths.Add("ds:Object/xades:QualifyingProperties/xades:UnsignedProperties/xades:UnsignedSignatureProperties/xades:RevocationValues");
var canonizeData = XMLUtil.ComputeCanonicalizedValueOfElementList1(XL_signatureDocument.XadesSignature, signatureValueElementXpaths, new XmlDsigC14NTransform());
finalOctetStream.Write(canonizeData, 0, canonizeData.Length);
// And send to timestamp server
var tstoken = GetTimeStampToken(SignatureTsa, finalOctetStream.ToArray(), SignatureType != SignatureType.XAdES_A);
var archiveTimeStamp = new TimeStamp("ArchiveTimeStamp") {
Id = "ArchiveTimeStamp-" + XL_signatureDocument.XadesSignature.Signature.Id,
CanonicalizationMethod = new CanonicalizationMethod()
{
Algorithm = new XmlDsigC14NTransform().Algorithm
}
};
archiveTimeStamp.EncapsulatedTimeStamp.PkiData = tstoken;
archiveTimeStamp.EncapsulatedTimeStamp.Id = "ArchiveTimeStamp-" + Guid.NewGuid();
unsignedProperties = XL_signatureDocument.XadesSignature.UnsignedProperties;
unsignedProperties.UnsignedSignatureProperties.ArchiveTimeStampCollection.Add(archiveTimeStamp);
XL_signatureDocument.XadesSignature.UnsignedProperties = unsignedProperties;
XL_signatureDocument.UpdateDocument();
我正在尝试使用 XAdES Conformance Checker 和 DSS
执行验证我解决了这个问题。这是我的代码,它将 XAdES-XL 签名扩展到 XAdES-A。感谢esig/dss
private static readonly byte[] xmlWithBomPreamble = new byte[] { 0xEF, 0xBB, 0xBF, (byte)'<' };
private static readonly byte[] xmlPreamble = new byte[] { (byte)'<' };
private static readonly HashSet<string> transformsWithNodeSetOutput = new HashSet<string>();
private static readonly string XP_WITH_ID_OPEN = "#xpointer(id(";
protected internal override XAdESSignedDocument ExtendXAdESSignature(XAdESSignedDocument xadesSignedDocument, UnsignedProperties unsignedProperties, SignatureParameters parameters,
SignatureType currentSignatureType, IDocument originalData)
{
if (SignatureType.XAdES_A >= currentSignatureType)
{
var XL_signatureDocument = base.ExtendXAdESSignature(xadesSignedDocument, unsignedProperties, parameters, currentSignatureType, originalData);
var xadesSginature = new XAdESSignature(XL_signatureDocument, XL_signatureDocument.XadesSignature);
var messageInprint = xadesSginature.getArchiveTimestampData(0, originalData);
#region Arşiv Zaman Damgası Oluştur
var tstoken = GetTimeStampToken(SignatureTsa, messageInprint, SignatureType != SignatureType.XAdES_A);
var archiveTimeStamp = new TimeStamp("ArchiveTimeStamp", "xades141", "http://uri.etsi.org/01903/v1.4.1#")
{
Id = "ArchiveTimeStamp-" + XL_signatureDocument.XadesSignature.Signature.Id,
CanonicalizationMethod = new CanonicalizationMethod()
{
Algorithm = XL_signatureDocument.XadesSignature.SignedInfo.CanonicalizationMethodObject.Algorithm
}
};
archiveTimeStamp.EncapsulatedTimeStamp.PkiData = tstoken;
archiveTimeStamp.EncapsulatedTimeStamp.Id = "ArchiveTimeStamp-" + Guid.NewGuid();
#endregion
#region İmzayı Güncelle
unsignedProperties = XL_signatureDocument.XadesSignature.UnsignedProperties;
unsignedProperties.UnsignedSignatureProperties.ArchiveTimeStampCollection.Clear();
XL_signatureDocument.XadesSignature.UnsignedProperties = unsignedProperties;
//Eski arşiv zaman damgalarını kaldır.
XL_signatureDocument.UpdateDocument(true);
unsignedProperties = XL_signatureDocument.XadesSignature.UnsignedProperties;
unsignedProperties.UnsignedSignatureProperties.ArchiveTimeStampCollection.Add(archiveTimeStamp);
XL_signatureDocument.XadesSignature.UnsignedProperties = unsignedProperties;
XL_signatureDocument.UpdateDocument();
#endregion
return XL_signatureDocument
}
return xadesSignedDocument
}
public virtual byte[] getArchiveTimestampData(int index, IDocument originalDocument)
{
try
{
var XL_signatureDocument = this.XAdEsSignedDocument;
var xmlTransform = XL_signatureDocument.XadesSignature.SignedInfo.CanonicalizationMethodObject;
using var finalOctetStream = new MemoryStream();
#region Referansları Al
foreach (Reference reference in XL_signatureDocument.XadesSignature.SignedInfo.References)
{
var referenceData = BuildRefernceUriData(reference, XL_signatureDocument, originalDocument);
writeReferenceBytes(XL_signatureDocument.XadesSignature, finalOctetStream, reference, referenceData, xmlTransform);
}
#endregion
#region SignedIn fo, SignatureValue, KeyInfo Al
var signatureValueElementXpaths = new ArrayList() { "ds:SignedInfo", "ds:SignatureValue", "ds:KeyInfo" };
var canonizeData = XMLHelper.DoCanonicalizeElementList(XL_signatureDocument.XadesSignature, signatureValueElementXpaths, xmlTransform);
finalOctetStream.Write(canonizeData, 0, canonizeData.Length);
#endregion
#region Geçerli Arşiv Zaman Damgasından Önceki elemanları tek tek sırasıyla al
var xmlNamespaceManager = new XmlNamespaceManager(XL_signatureDocument.Document.NameTable);
xmlNamespaceManager.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
xmlNamespaceManager.AddNamespace("xades", XadesSignedXml.XadesNamespaceUri);
var unsignedSignaturePropertiesNode = XL_signatureDocument.XadesSignature.GetSignatureElement().SelectSingleNode("//xades:UnsignedProperties/xades:UnsignedSignatureProperties", xmlNamespaceManager);
if (unsignedSignaturePropertiesNode != null)
{
// UnsignedSignatureProperties altındakileri döngüyle gez
foreach (XmlNode node in unsignedSignaturePropertiesNode.ChildNodes)
{
if (node.LocalName == "ArchiveTimeStamp")
break;
canonizeData = XMLHelper.ComputeCanonicalizedValueOfElement(XL_signatureDocument.XadesSignature, node, xmlTransform);
finalOctetStream.Write(canonizeData, 0, canonizeData.Length);
}
}
#endregion
#region Objects elementslerini al. QualifyingProperties hariç.
var objList = XL_signatureDocument.XadesSignature.Signature.ObjectList;
foreach (DataObject crnObj in objList)
{
// DataObject'un birinci seviye alt elemanlarını al
var dataObjectXml = crnObj.GetXml();
var firstLevelChildNodes = dataObjectXml.ChildNodes;
// İlk seviye çocuk elemanlar içinde "xades:QualifyingProperties" olup olmadığını kontrol et
var containsQualifyingProperties = firstLevelChildNodes
.Cast<XmlNode>()
.Any(childNode => childNode.LocalName == "QualifyingProperties" && childNode.NamespaceURI == "http://uri.etsi.org/01903/v1.3.2#");
if (containsQualifyingProperties)
continue;
// Eğer "xades:QualifyingProperties" yoksa, canonicalization işlemini uygula
canonizeData = XMLHelper.ComputeCanonicalizedValueOfElement(XL_signatureDocument.XadesSignature, dataObjectXml, xmlTransform);
finalOctetStream.Write(canonizeData, 0, canonizeData.Length);
}
#endregion
return finalOctetStream.ToArray();
}
catch (IOException e)
{
Logger.Error(e.Message, new TamgaESException("XAdESSignature / getArchiveTimestampDataV2", e.Message, TamgaESErrorCodes.UYGULAMA_IC_HATA));
}
catch (Exception e)
{
Logger.Error(e.Message, new TamgaESException("XAdESSignature / getArchiveTimestampDataV2", e.Message, TamgaESErrorCodes.UYGULAMA_IC_HATA));
}
return null;
}
private byte[] BuildRefernceUriData(Reference reference, XAdESSignedDocument signedDocument, IDocument originalDetachedData)
{
//Console.WriteLine("Refrans Digest Value : >> " + Convert.ToBase64String(reference.DigestValue));
if (string.IsNullOrEmpty(reference.Uri))
{
return Encoding.UTF8.GetBytes(signedDocument.Document.DocumentElement.OuterXml);
}
else if (reference.Uri.StartsWith("#"))
{
///İmzalanan Veri Belge İçinde bir referans
var referencedNode = signedDocument.Document.SelectSingleNode($"//*[@Id='{reference.Uri.Substring(1)}' or @id='{reference.Uri.Substring(1)}']");
return Encoding.UTF8.GetBytes(referencedNode.OuterXml);
}
else
{
return originalDetachedData.Content;
}
return null;
}
private void writeReferenceBytes(XadesSignedXml xadesSignedXml, Stream finalOctetStream, Reference reference, byte[] referencedBytes, System.Security.Cryptography.Xml.Transform xmlTransform)
{
//Referansın transformalarını uygula.
foreach (var transform in reference.TransformChain)
referencedBytes = XMLHelper.ApplyTransform(referencedBytes, (System.Security.Cryptography.Xml.Transform)transform);
//Çıktı XML Node mu Octet Stream mi
if (getReferenceOutputType(reference) == ReferenceOutputType.NODE_SET && isDOM(referencedBytes))
{
//var xmlString = Encoding.UTF8.GetString(referencedBytes);
var xmlDoc = XMLHelper.LoadDocument(new MemoryStream(referencedBytes));
referencedBytes = XMLHelper.ComputeCanonicalizedValueOfElement(xadesSignedXml, xmlDoc.DocumentElement, xmlTransform);
}
Console.WriteLine("Referans ZD Diges Değeri : >> " + Convert.ToBase64String(DigestUtilities.CalculateDigest(DigestAlgorithms.SHA256.OID, referencedBytes)));
finalOctetStream.Write(referencedBytes, 0, referencedBytes.Length);
}
private bool isDOM(byte[] bytes)
{
try
{
return startsWithXmlPreamble(bytes) && BuildDOM(bytes) != null;
}
catch (Exception e)
{
// NOT DOM
return false;
}
}
private XmlDocument BuildDOM(byte[] bytes)
{
if (bytes == null)
{
throw new ArgumentNullException(nameof(bytes), "bytes is required");
}
using (var stream = new MemoryStream(bytes))
{
return BuildDOM(stream);
}
}
private XmlDocument BuildDOM(Stream inputStream)
{
try
{
var document = new XmlDocument { XmlResolver = null };
var settings = GetSecureXmlReaderSettings();
using var reader = XmlReader.Create(inputStream, settings);
document.Load(reader);
return document;
}
catch (XmlException e)
{
throw new Exception($"Unable to parse content (XML expected): {e.Message}", e);
}
catch (IOException e)
{
throw new Exception($"An error occurred while reading InputStream: {e.Message}", e);
}
}
private XmlReaderSettings GetSecureXmlReaderSettings()
{
var settings = new XmlReaderSettings
{
DtdProcessing = DtdProcessing.Prohibit, // DTD'leri yasakla
XmlResolver = null, // Dış varlıkların çözümlemesini devre dışı bırak
IgnoreWhitespace = true,
IgnoreProcessingInstructions = true,
IgnoreComments = true
};
return settings;
}
private bool startsWithXmlPreamble(byte[] byteArray)
{
return StartsWith(byteArray, xmlPreamble) || StartsWith(byteArray, xmlWithBomPreamble);
}
private bool StartsWith(byte[] source, byte[] prefix)
{
if (source == null || prefix == null || source.Length < prefix.Length)
return false;
for (int i = 0; i < prefix.Length; i++)
{
if (source[i] != prefix[i])
return false;
}
return true;
}
private ReferenceOutputType getReferenceOutputType(Reference reference)
{
var outputType = getDereferenceOutputType(reference.Uri);
if (reference.TransformChain.Count > 0)
{
foreach (System.Security.Cryptography.Xml.Transform transform in reference.TransformChain)
{
var algorithmUri = transform.Algorithm;
outputType = getTransformOutputType(algorithmUri);
}
}
return outputType;
}
private ReferenceOutputType getDereferenceOutputType(string referenceUri)
{
return IsSameDocumentReference(referenceUri) ? ReferenceOutputType.NODE_SET : ReferenceOutputType.OCTET_STREAM;
}
private ReferenceOutputType getTransformOutputType(string algorithmUri)
{
return transformsWithNodeSetOutput.Contains(algorithmUri) ? ReferenceOutputType.NODE_SET : ReferenceOutputType.OCTET_STREAM;
}
private bool IsSameDocumentReference(string referenceUri)
{
return string.IsNullOrEmpty(referenceUri) || referenceUri.StartsWith("#");
}
public enum ReferenceOutputType
{
OCTET_STREAM,
NODE_SET
}