Created
June 24, 2024 12:23
-
-
Save Oskang09/f7796554e4e91cbb1f583f6f2b6e83f3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class UblSignature | |
{ | |
private const string SignatureID = "signature"; | |
private const string SignaturePropertiesID = "id-xades-signed-props"; | |
public static XmlElement SignWithXAdES(X509Certificate2 signingCertificate, XmlDocument xmlDocument) | |
{ | |
var signedXml = new XadesSignedXml(xmlDocument); | |
signedXml.Signature.Id = SignatureID; | |
signedXml.SigningKey = signingCertificate.GetRSAPrivateKey(); | |
var signedReference = new Reference { Id = "id-doc-signed-data", Uri = "" }; | |
signedReference.AddTransform(new XmlDsigC14NTransform()); | |
signedXml.AddReference(signedReference); | |
var keyInfo = new KeyInfo(); | |
keyInfo.AddClause(new KeyInfoX509Data(signingCertificate)); | |
signedXml.KeyInfo = keyInfo; | |
AddXAdESProperties(xmlDocument, signedXml, signingCertificate); | |
signedXml.ComputeSignature(); | |
var signed = signedXml.GetXml(); | |
XNamespace nsSig = "urn:oasis:names:specification:ubl:schema:xsd:CommonSignatureComponents-2"; | |
XNamespace nsSac = "urn:oasis:names:specification:ubl:schema:xsd:SignatureAggregateComponents-2"; | |
XNamespace nsSbc = "urn:oasis:names:specification:ubl:schema:xsd:SignatureBasicComponents-2"; | |
XNamespace nsCbc = "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"; | |
var udsWrapper = new XElement(nsSig + "UBLDocumentSignatures", | |
new XAttribute(XNamespace.Xmlns + "sig", nsSig), | |
new XAttribute(XNamespace.Xmlns + "sac", nsSac), | |
new XAttribute(XNamespace.Xmlns + "sbc", nsSbc), | |
new XAttribute(XNamespace.Xmlns + "cbc", nsCbc), | |
new XElement(nsSac + "SignatureInformation", | |
new XElement(nsCbc + "ID", "urn:oasis:names:specification:ubl:signature:1"), | |
new XElement(nsSbc + "ReferencedSignatureID", "urn:oasis:names:specification:ubl:signature:Invoice"), | |
XElement.Parse(signed.OuterXml) | |
) | |
); | |
var signDoc = new XmlDocument(); | |
using (var udsReader = udsWrapper.CreateReader()) | |
{ | |
signDoc.Load(udsWrapper.CreateReader()); | |
} | |
return signDoc.DocumentElement; | |
} | |
private static void AddXAdESProperties(XmlDocument document, XadesSignedXml xadesSignedXml, X509Certificate2 signingCertificate) | |
{ | |
var parametersSignature = new Reference | |
{ | |
Uri = $"#{SignaturePropertiesID}", | |
Type = XadesSignedXml.XmlDsigSignatureProperties, | |
}; | |
xadesSignedXml.AddReference(parametersSignature); | |
// <Object> | |
var objectNode = document.CreateElement("Object", SignedXml.XmlDsigNamespaceUrl); | |
// <Object><QualifyingProperties> | |
var qualifyingPropertiesNode = document.CreateElement(XadesSignedXml.XadesPrefix, "QualifyingProperties", XadesSignedXml.XadesNamespaceUrl); | |
qualifyingPropertiesNode.SetAttribute("Target", $"#{SignatureID}"); | |
objectNode.AppendChild(qualifyingPropertiesNode); | |
// <Object><QualifyingProperties><SignedProperties> | |
var signedPropertiesNode = document.CreateElement(XadesSignedXml.XadesPrefix, "SignedProperties", XadesSignedXml.XadesNamespaceUrl); | |
signedPropertiesNode.SetAttribute("Id", SignaturePropertiesID); | |
qualifyingPropertiesNode.AppendChild(signedPropertiesNode); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties> | |
var signedSignaturePropertiesNode = document.CreateElement(XadesSignedXml.XadesPrefix, "SignedSignatureProperties", XadesSignedXml.XadesNamespaceUrl); | |
signedPropertiesNode.AppendChild(signedSignaturePropertiesNode); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties> </SigningTime> | |
var signingTime = document.CreateElement(XadesSignedXml.XadesPrefix, "SigningTime", XadesSignedXml.XadesNamespaceUrl); | |
signingTime.InnerText = $"{DateTime.UtcNow:s}Z"; | |
signedSignaturePropertiesNode.AppendChild(signingTime); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate> | |
var signingCertificateNode = document.CreateElement(XadesSignedXml.XadesPrefix, "SigningCertificate", XadesSignedXml.XadesNamespaceUrl); | |
signedSignaturePropertiesNode.AppendChild(signingCertificateNode); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert> | |
var certNode = document.CreateElement(XadesSignedXml.XadesPrefix, "Cert", XadesSignedXml.XadesNamespaceUrl); | |
signingCertificateNode.AppendChild(certNode); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest> | |
var certDigestNode = document.CreateElement(XadesSignedXml.XadesPrefix, "CertDigest", XadesSignedXml.XadesNamespaceUrl); | |
certNode.AppendChild(certDigestNode); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest> </DigestMethod> | |
var digestMethod = document.CreateElement("DigestMethod", SignedXml.XmlDsigNamespaceUrl); | |
var digestMethodAlgorithmAtribute = document.CreateAttribute("Algorithm"); | |
digestMethodAlgorithmAtribute.InnerText = SignedXml.XmlDsigSHA256Url; | |
digestMethod.Attributes.Append(digestMethodAlgorithmAtribute); | |
certDigestNode.AppendChild(digestMethod); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><CertDigest> </DigestMethod> | |
var digestValue = document.CreateElement("DigestValue", SignedXml.XmlDsigNamespaceUrl); | |
digestValue.InnerText = Convert.ToBase64String(signingCertificate.GetCertHash(HashAlgorithmName.SHA256)); | |
certDigestNode.AppendChild(digestValue); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial> | |
var issuerSerialNode = document.CreateElement(XadesSignedXml.XadesPrefix, "IssuerSerial", XadesSignedXml.XadesNamespaceUrl); | |
certNode.AppendChild(issuerSerialNode); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial> </X509IssuerName> | |
var x509IssuerName = document.CreateElement("X509IssuerName", SignedXml.XmlDsigNamespaceUrl); | |
x509IssuerName.InnerText = signingCertificate.Issuer; | |
issuerSerialNode.AppendChild(x509IssuerName); | |
// <Object><QualifyingProperties><SignedProperties><SignedSignatureProperties><SigningCertificate><Cert><IssuerSerial> </X509SerialNumber> | |
var x509SerialNumber = document.CreateElement("X509SerialNumber", SignedXml.XmlDsigNamespaceUrl); | |
x509SerialNumber.InnerText = ToDecimalString(signingCertificate.SerialNumber); | |
issuerSerialNode.AppendChild(x509SerialNumber); | |
var dataObject = new DataObject(); | |
dataObject.Data = qualifyingPropertiesNode.SelectNodes("."); | |
xadesSignedXml.AddObject(dataObject); | |
} | |
private static string ToDecimalString(string serialNumber) | |
{ | |
BigInteger bi; | |
if (BigInteger.TryParse(serialNumber, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out bi)) | |
{ | |
return bi.ToString(CultureInfo.InvariantCulture); | |
} | |
else | |
{ | |
return serialNumber; | |
} | |
} | |
} | |
public class XadesSignedXml : SignedXml | |
{ | |
public const string XadesPrefix = "xades"; | |
public const string DsigPrefix = "ds"; | |
public const string XmlDsigSignatureProperties = "http://www.w3.org/2000/09/xmldsig#SignatureProperties"; | |
public const string XadesNamespaceUrl = "http://uri.etsi.org/01903/v1.3.2#"; | |
public XmlElement PropertiesNode { get; set; } | |
private readonly List<DataObject> _dataObjects = new List<DataObject>(); | |
public XadesSignedXml(XmlDocument document) : base(document) { } | |
public override XmlElement GetIdElement(XmlDocument document, string idValue) | |
{ | |
if (string.IsNullOrEmpty(idValue)) | |
return null; | |
var xmlElement = base.GetIdElement(document, idValue); | |
if (xmlElement != null) | |
return xmlElement; | |
if (_dataObjects.Count == 0) | |
return null; | |
foreach (var dataObject in _dataObjects) | |
{ | |
var nodeWithSameID = FindNodeWithPrefix(dataObject.Data, "Id", idValue); | |
if (nodeWithSameID != null) | |
return nodeWithSameID; | |
} | |
return null; | |
} | |
public new void AddObject(DataObject dataObject) | |
{ | |
base.AddObject(dataObject); | |
_dataObjects.Add(dataObject); | |
} | |
private XmlElement FindNodeWithPrefix(XmlNodeList nodeList, string idKey, string idValue) | |
{ | |
foreach (XmlNode node in nodeList) | |
{ | |
var attribute = node.Attributes[idKey]; | |
if (attribute != null && attribute.Value == idValue) | |
return (XmlElement)node; | |
if (!node.HasChildNodes) continue; | |
var foundNode = FindNodeWithPrefix(node.ChildNodes, idKey, idValue); | |
if (foundNode != null) return foundNode; | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment