|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Globalization;
using System.Numerics;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
namespace System.Security.Cryptography.Xml
{
public class KeyInfoX509Data : KeyInfoClause
{
// An array of certificates representing the certificate chain
private ArrayList? _certificates;
// An array of issuer serial structs
private ArrayList? _issuerSerials;
// An array of SKIs
private ArrayList? _subjectKeyIds;
// An array of subject names
private ArrayList? _subjectNames;
// A raw byte data representing a certificate revocation list
private byte[]? _CRL;
//
// public constructors
//
public KeyInfoX509Data() { }
public KeyInfoX509Data(byte[] rgbCert)
{
// Compat: this accepts null arrays for certificate data and would not throw. X509CertificateLoader throws
// for a null input. This uses the X509Certificate2 constructor for null inputs to preserve the existing
// behavior. Since the input is null and there is nothing to decode, the input is safe for the constructor.
#pragma warning disable SYSLIB0057
X509Certificate2 certificate = rgbCert is null ?
new X509Certificate2((byte[])null!) :
X509CertificateLoader.LoadCertificate(rgbCert);
#pragma warning restore SYSLIB0057
AddCertificate(certificate);
}
public KeyInfoX509Data(X509Certificate cert)
{
AddCertificate(cert);
}
public KeyInfoX509Data(X509Certificate cert, X509IncludeOption includeOption)
{
if (cert is null)
{
throw new ArgumentNullException(nameof(cert));
}
X509Certificate2 certificate = new X509Certificate2(cert);
X509ChainElementCollection elements;
X509Chain chain;
switch (includeOption)
{
case X509IncludeOption.ExcludeRoot:
// Build the certificate chain
chain = new X509Chain();
chain.Build(certificate);
// Can't honor the option if we only have a partial chain.
if ((chain.ChainStatus.Length > 0) &&
((chain.ChainStatus[0].Status & X509ChainStatusFlags.PartialChain) == X509ChainStatusFlags.PartialChain))
{
throw new CryptographicException(SR.Cryptography_Partial_Chain);
}
elements = (X509ChainElementCollection)chain.ChainElements;
for (int index = 0; index < (Utils.IsSelfSigned(chain) ? 1 : elements.Count - 1); index++)
{
AddCertificate(elements[index].Certificate);
}
break;
case X509IncludeOption.EndCertOnly:
AddCertificate(certificate);
break;
case X509IncludeOption.WholeChain:
// Build the certificate chain
chain = new X509Chain();
chain.Build(certificate);
// Can't honor the option if we only have a partial chain.
if ((chain.ChainStatus.Length > 0) &&
((chain.ChainStatus[0].Status & X509ChainStatusFlags.PartialChain) == X509ChainStatusFlags.PartialChain))
{
throw new CryptographicException(SR.Cryptography_Partial_Chain);
}
elements = (X509ChainElementCollection)chain.ChainElements;
foreach (X509ChainElement element in elements)
{
AddCertificate(element.Certificate);
}
break;
}
}
//
// public properties
//
public ArrayList? Certificates
{
get { return _certificates; }
}
public void AddCertificate(X509Certificate certificate)
{
if (certificate is null)
{
throw new ArgumentNullException(nameof(certificate));
}
_certificates ??= new ArrayList();
X509Certificate2 x509 = new X509Certificate2(certificate);
_certificates.Add(x509);
}
public ArrayList? SubjectKeyIds
{
get { return _subjectKeyIds; }
}
public void AddSubjectKeyId(byte[] subjectKeyId)
{
_subjectKeyIds ??= new ArrayList();
_subjectKeyIds.Add(subjectKeyId);
}
public void AddSubjectKeyId(string subjectKeyId)
{
_subjectKeyIds ??= new ArrayList();
_subjectKeyIds.Add(Utils.DecodeHexString(subjectKeyId));
}
public ArrayList? SubjectNames
{
get { return _subjectNames; }
}
public void AddSubjectName(string subjectName)
{
_subjectNames ??= new ArrayList();
_subjectNames.Add(subjectName);
}
public ArrayList? IssuerSerials
{
get { return _issuerSerials; }
}
public void AddIssuerSerial(string issuerName, string serialNumber)
{
if (string.IsNullOrEmpty(issuerName))
throw new ArgumentException(SR.Arg_EmptyOrNullString, nameof(issuerName));
if (string.IsNullOrEmpty(serialNumber))
throw new ArgumentException(SR.Arg_EmptyOrNullString, nameof(serialNumber));
BigInteger h;
if (!BigInteger.TryParse(serialNumber, NumberStyles.AllowHexSpecifier, NumberFormatInfo.CurrentInfo, out h))
throw new ArgumentException(SR.Cryptography_Xml_InvalidX509IssuerSerialNumber, nameof(serialNumber));
// NetFx compat: .NET Framework treats the input as unsigned and we need to write down the X509SerialNumber
// as a positive number.
if (h < BigInteger.Zero)
{
byte[] bytes = h.ToByteArray();
Array.Resize(ref bytes, bytes.Length + 1);
h = new BigInteger(bytes);
}
_issuerSerials ??= new ArrayList();
_issuerSerials.Add(Utils.CreateX509IssuerSerial(issuerName, h.ToString()));
}
// When we load an X509Data from Xml, we know the serial number is in decimal representation.
internal void InternalAddIssuerSerial(string issuerName, string serialNumber)
{
_issuerSerials ??= new ArrayList();
_issuerSerials.Add(Utils.CreateX509IssuerSerial(issuerName, serialNumber));
}
public byte[]? CRL
{
get { return _CRL; }
set { _CRL = value; }
}
//
// private methods
//
private void Clear()
{
_CRL = null;
_subjectKeyIds?.Clear();
_subjectNames?.Clear();
_issuerSerials?.Clear();
_certificates?.Clear();
}
//
// public methods
//
public override XmlElement GetXml()
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = true;
return GetXml(xmlDocument);
}
internal override XmlElement GetXml(XmlDocument xmlDocument)
{
XmlElement x509DataElement = xmlDocument.CreateElement("X509Data", SignedXml.XmlDsigNamespaceUrl);
if (_issuerSerials != null)
{
foreach (X509IssuerSerial issuerSerial in _issuerSerials)
{
XmlElement issuerSerialElement = xmlDocument.CreateElement("X509IssuerSerial", SignedXml.XmlDsigNamespaceUrl);
XmlElement issuerNameElement = xmlDocument.CreateElement("X509IssuerName", SignedXml.XmlDsigNamespaceUrl);
issuerNameElement.AppendChild(xmlDocument.CreateTextNode(issuerSerial.IssuerName));
issuerSerialElement.AppendChild(issuerNameElement);
XmlElement serialNumberElement = xmlDocument.CreateElement("X509SerialNumber", SignedXml.XmlDsigNamespaceUrl);
serialNumberElement.AppendChild(xmlDocument.CreateTextNode(issuerSerial.SerialNumber));
issuerSerialElement.AppendChild(serialNumberElement);
x509DataElement.AppendChild(issuerSerialElement);
}
}
if (_subjectKeyIds != null)
{
foreach (byte[] subjectKeyId in _subjectKeyIds)
{
XmlElement subjectKeyIdElement = xmlDocument.CreateElement("X509SKI", SignedXml.XmlDsigNamespaceUrl);
subjectKeyIdElement.AppendChild(xmlDocument.CreateTextNode(Convert.ToBase64String(subjectKeyId)));
x509DataElement.AppendChild(subjectKeyIdElement);
}
}
if (_subjectNames != null)
{
foreach (string subjectName in _subjectNames)
{
XmlElement subjectNameElement = xmlDocument.CreateElement("X509SubjectName", SignedXml.XmlDsigNamespaceUrl);
subjectNameElement.AppendChild(xmlDocument.CreateTextNode(subjectName));
x509DataElement.AppendChild(subjectNameElement);
}
}
if (_certificates != null)
{
foreach (X509Certificate certificate in _certificates)
{
XmlElement x509Element = xmlDocument.CreateElement("X509Certificate", SignedXml.XmlDsigNamespaceUrl);
x509Element.AppendChild(xmlDocument.CreateTextNode(Convert.ToBase64String(certificate.GetRawCertData())));
x509DataElement.AppendChild(x509Element);
}
}
if (_CRL != null)
{
XmlElement crlElement = xmlDocument.CreateElement("X509CRL", SignedXml.XmlDsigNamespaceUrl);
crlElement.AppendChild(xmlDocument.CreateTextNode(Convert.ToBase64String(_CRL)));
x509DataElement.AppendChild(crlElement);
}
return x509DataElement;
}
public override void LoadXml(XmlElement element)
{
if (element is null)
{
throw new ArgumentNullException(nameof(element));
}
XmlNamespaceManager nsm = new XmlNamespaceManager(element.OwnerDocument.NameTable);
nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);
XmlNodeList x509IssuerSerialNodes = element.SelectNodes("ds:X509IssuerSerial", nsm)!;
XmlNodeList x509SKINodes = element.SelectNodes("ds:X509SKI", nsm)!;
XmlNodeList x509SubjectNameNodes = element.SelectNodes("ds:X509SubjectName", nsm)!;
XmlNodeList x509CertificateNodes = element.SelectNodes("ds:X509Certificate", nsm)!;
XmlNodeList x509CRLNodes = element.SelectNodes("ds:X509CRL", nsm)!;
if ((x509CRLNodes.Count == 0 && x509IssuerSerialNodes.Count == 0 && x509SKINodes.Count == 0
&& x509SubjectNameNodes.Count == 0 && x509CertificateNodes.Count == 0)) // Bad X509Data tag, or Empty tag
throw new CryptographicException(SR.Cryptography_Xml_InvalidElement, "X509Data");
// Flush anything in the lists
Clear();
if (x509CRLNodes.Count != 0)
_CRL = Convert.FromBase64String(Utils.DiscardWhiteSpaces(x509CRLNodes.Item(0)!.InnerText));
foreach (XmlNode issuerSerialNode in x509IssuerSerialNodes)
{
XmlNode? x509IssuerNameNode = issuerSerialNode.SelectSingleNode("ds:X509IssuerName", nsm);
XmlNode? x509SerialNumberNode = issuerSerialNode.SelectSingleNode("ds:X509SerialNumber", nsm);
if (x509IssuerNameNode == null || x509SerialNumberNode == null)
throw new CryptographicException(SR.Cryptography_Xml_InvalidElement, "IssuerSerial");
InternalAddIssuerSerial(x509IssuerNameNode.InnerText.Trim(), x509SerialNumberNode.InnerText.Trim());
}
foreach (XmlNode node in x509SKINodes)
{
AddSubjectKeyId(Convert.FromBase64String(Utils.DiscardWhiteSpaces(node.InnerText)));
}
foreach (XmlNode node in x509SubjectNameNodes)
{
AddSubjectName(node.InnerText.Trim());
}
foreach (XmlNode node in x509CertificateNodes)
{
AddCertificate(
X509CertificateLoader.LoadCertificate(
Convert.FromBase64String(Utils.DiscardWhiteSpaces(node.InnerText))));
}
}
}
}
|