//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.IdentityModel { using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IdentityModel.Tokens; using System.IdentityModel.Selectors; using System.Security.Cryptography; using System.Text; using System.Xml; sealed class SignedXml : ISignatureValueSecurityElement { internal const string DefaultPrefix = XmlSignatureStrings.Prefix; SecurityTokenSerializer tokenSerializer; readonly Signature signature; TransformFactory transformFactory; DictionaryManager dictionaryManager; public SignedXml(DictionaryManager dictionaryManager, SecurityTokenSerializer tokenSerializer) : this(new StandardSignedInfo(dictionaryManager), dictionaryManager, tokenSerializer) { } internal SignedXml(SignedInfo signedInfo, DictionaryManager dictionaryManager, SecurityTokenSerializer tokenSerializer) { if (signedInfo == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("signedInfo")); } if (dictionaryManager == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("dictionaryManager"); } if (tokenSerializer == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenSerializer"); } this.transformFactory = StandardTransformFactory.Instance; this.tokenSerializer = tokenSerializer; this.signature = new Signature(this, signedInfo); this.dictionaryManager = dictionaryManager; } public bool HasId { get { return true; } } public string Id { get { return this.signature.Id; } set { this.signature.Id = value; } } public SecurityTokenSerializer SecurityTokenSerializer { get { return this.tokenSerializer; } } public Signature Signature { get { return this.signature; } } public TransformFactory TransformFactory { get { return this.transformFactory; } set { this.transformFactory = value; } } void ComputeSignature(HashAlgorithm hash, AsymmetricSignatureFormatter formatter, string signatureMethod) { this.Signature.SignedInfo.ComputeReferenceDigests(); this.Signature.SignedInfo.ComputeHash(hash); byte[] signature; if (SecurityUtils.RequiresFipsCompliance && signatureMethod == SecurityAlgorithms.RsaSha256Signature) { // This is to avoid the RSAPKCS1SignatureFormatter.CreateSignature from using SHA256Managed (non-FIPS-Compliant). // Hence we precompute the hash using SHA256CSP (FIPS compliant) and pass it to method. // NOTE: RSAPKCS1SignatureFormatter does not understand SHA256CSP inherently and hence this workaround. formatter.SetHashAlgorithm("SHA256"); signature = formatter.CreateSignature(hash.Hash); } else { signature = formatter.CreateSignature(hash); } this.Signature.SetSignatureValue(signature); } void ComputeSignature(KeyedHashAlgorithm hash) { this.Signature.SignedInfo.ComputeReferenceDigests(); this.Signature.SignedInfo.ComputeHash(hash); byte[] signature = hash.Hash; this.Signature.SetSignatureValue(signature); } public void ComputeSignature(SecurityKey signingKey) { string signatureMethod = this.Signature.SignedInfo.SignatureMethod; SymmetricSecurityKey symmetricKey = signingKey as SymmetricSecurityKey; if (symmetricKey != null) { using (KeyedHashAlgorithm algorithm = symmetricKey.GetKeyedHashAlgorithm(signatureMethod)) { if (algorithm == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR.GetString(SR.UnableToCreateKeyedHashAlgorithm, symmetricKey, signatureMethod))); } ComputeSignature(algorithm); } } else { AsymmetricSecurityKey asymmetricKey = signingKey as AsymmetricSecurityKey; if (asymmetricKey == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR.GetString(SR.UnknownICryptoType, signingKey))); } using (HashAlgorithm hash = asymmetricKey.GetHashAlgorithmForSignature(signatureMethod)) { if (hash == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR.GetString(SR.UnableToCreateHashAlgorithmFromAsymmetricCrypto, signatureMethod, asymmetricKey))); } AsymmetricSignatureFormatter formatter = asymmetricKey.GetSignatureFormatter(signatureMethod); if (formatter == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR.GetString(SR.UnableToCreateSignatureFormatterFromAsymmetricCrypto, signatureMethod, asymmetricKey))); } ComputeSignature(hash, formatter, signatureMethod); } } } public void CompleteSignatureVerification() { this.Signature.SignedInfo.EnsureAllReferencesVerified(); } public void EnsureDigestValidity(string id, object resolvedXmlSource) { this.Signature.SignedInfo.EnsureDigestValidity(id, resolvedXmlSource); } public bool EnsureDigestValidityIfIdMatches(string id, object resolvedXmlSource) { return this.Signature.SignedInfo.EnsureDigestValidityIfIdMatches(id, resolvedXmlSource); } public byte[] GetSignatureValue() { return this.Signature.GetSignatureBytes(); } public void ReadFrom(XmlReader reader) { ReadFrom(XmlDictionaryReader.CreateDictionaryReader(reader)); } public void ReadFrom(XmlDictionaryReader reader) { this.signature.ReadFrom(reader, this.dictionaryManager); } void VerifySignature(KeyedHashAlgorithm hash) { this.Signature.SignedInfo.ComputeHash(hash); if (!CryptoHelper.IsEqual(hash.Hash, GetSignatureValue())) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.SignatureVerificationFailed))); } } void VerifySignature(HashAlgorithm hash, AsymmetricSignatureDeformatter deformatter, string signatureMethod) { this.Signature.SignedInfo.ComputeHash(hash); bool result; if (SecurityUtils.RequiresFipsCompliance && signatureMethod == SecurityAlgorithms.RsaSha256Signature) { // This is to avoid the RSAPKCS1SignatureFormatter.VerifySignature from using SHA256Managed (non-FIPS-Compliant). // Hence we precompute the hash using SHA256CSP (FIPS compliant) and pass it to method. // NOTE: RSAPKCS1SignatureFormatter does not understand SHA256CSP inherently and hence this workaround. deformatter.SetHashAlgorithm("SHA256"); result = deformatter.VerifySignature(hash.Hash, GetSignatureValue()); } else { result = deformatter.VerifySignature(hash, GetSignatureValue()); } if (!result) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.SignatureVerificationFailed))); } } public void StartSignatureVerification(SecurityKey verificationKey) { string signatureMethod = this.Signature.SignedInfo.SignatureMethod; SymmetricSecurityKey symmetricKey = verificationKey as SymmetricSecurityKey; if (symmetricKey != null) { using (KeyedHashAlgorithm hash = symmetricKey.GetKeyedHashAlgorithm(signatureMethod)) { if (hash == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.UnableToCreateKeyedHashAlgorithmFromSymmetricCrypto, signatureMethod, symmetricKey))); } VerifySignature(hash); } } else { AsymmetricSecurityKey asymmetricKey = verificationKey as AsymmetricSecurityKey; if (asymmetricKey == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.UnknownICryptoType, verificationKey))); } using (HashAlgorithm hash = asymmetricKey.GetHashAlgorithmForSignature(signatureMethod)) { if (hash == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.UnableToCreateHashAlgorithmFromAsymmetricCrypto, signatureMethod, asymmetricKey))); } AsymmetricSignatureDeformatter deformatter = asymmetricKey.GetSignatureDeformatter(signatureMethod); if (deformatter == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.UnableToCreateSignatureDeformatterFromAsymmetricCrypto, signatureMethod, asymmetricKey))); } VerifySignature(hash, deformatter, signatureMethod); } } } public void WriteTo(XmlDictionaryWriter writer) { this.WriteTo(writer, this.dictionaryManager); } public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { this.signature.WriteTo(writer, dictionaryManager); } } sealed class Signature { SignedXml signedXml; string id; SecurityKeyIdentifier keyIdentifier; string prefix = SignedXml.DefaultPrefix; readonly SignatureValueElement signatureValueElement = new SignatureValueElement(); readonly SignedInfo signedInfo; public Signature(SignedXml signedXml, SignedInfo signedInfo) { this.signedXml = signedXml; this.signedInfo = signedInfo; } public string Id { get { return this.id; } set { this.id = value; } } public SecurityKeyIdentifier KeyIdentifier { get { return this.keyIdentifier; } set { this.keyIdentifier = value; } } public SignedInfo SignedInfo { get { return this.signedInfo; } } public ISignatureValueSecurityElement SignatureValue { get { return this.signatureValueElement; } } public byte[] GetSignatureBytes() { return this.signatureValueElement.Value; } public void ReadFrom(XmlDictionaryReader reader, DictionaryManager dictionaryManager) { reader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.Signature, dictionaryManager.XmlSignatureDictionary.Namespace); this.prefix = reader.Prefix; this.Id = reader.GetAttribute(dictionaryManager.UtilityDictionary.IdAttribute, null); reader.Read(); this.signedInfo.ReadFrom(reader, signedXml.TransformFactory, dictionaryManager); this.signatureValueElement.ReadFrom(reader, dictionaryManager); if (signedXml.SecurityTokenSerializer.CanReadKeyIdentifier(reader)) { this.keyIdentifier = signedXml.SecurityTokenSerializer.ReadKeyIdentifier(reader); } reader.ReadEndElement(); // Signature } public void SetSignatureValue(byte[] signatureValue) { this.signatureValueElement.Value = signatureValue; } public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { writer.WriteStartElement(this.prefix, dictionaryManager.XmlSignatureDictionary.Signature, dictionaryManager.XmlSignatureDictionary.Namespace); if (this.id != null) { writer.WriteAttributeString(dictionaryManager.UtilityDictionary.IdAttribute, null, this.id); } this.signedInfo.WriteTo(writer, dictionaryManager); this.signatureValueElement.WriteTo(writer, dictionaryManager); if (this.keyIdentifier != null) { this.signedXml.SecurityTokenSerializer.WriteKeyIdentifier(writer, this.keyIdentifier); } writer.WriteEndElement(); // Signature } sealed class SignatureValueElement : ISignatureValueSecurityElement { string id; string prefix = SignedXml.DefaultPrefix; byte[] signatureValue; string signatureText; public bool HasId { get { return true; } } public string Id { get { return this.id; } set { this.id = value; } } internal byte[] Value { get { return this.signatureValue; } set { this.signatureValue = value; this.signatureText = null; } } public void ReadFrom(XmlDictionaryReader reader, DictionaryManager dictionaryManager) { reader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.SignatureValue, dictionaryManager.XmlSignatureDictionary.Namespace); this.prefix = reader.Prefix; this.Id = reader.GetAttribute(UtilityStrings.IdAttribute, null); reader.Read(); this.signatureText = reader.ReadString(); this.signatureValue = System.Convert.FromBase64String(signatureText.Trim()); reader.ReadEndElement(); // SignatureValue } public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { writer.WriteStartElement(this.prefix, dictionaryManager.XmlSignatureDictionary.SignatureValue, dictionaryManager.XmlSignatureDictionary.Namespace); if (this.id != null) { writer.WriteAttributeString(dictionaryManager.UtilityDictionary.IdAttribute, null, this.id); } if (this.signatureText != null) { writer.WriteString(this.signatureText); } else { writer.WriteBase64(this.signatureValue, 0, this.signatureValue.Length); } writer.WriteEndElement(); // SignatureValue } byte[] ISignatureValueSecurityElement.GetSignatureValue() { return this.Value; } } } internal interface ISignatureReaderProvider { XmlDictionaryReader GetReader(object callbackContext); } abstract class SignedInfo : ISecurityElement { readonly ExclusiveCanonicalizationTransform canonicalizationMethodElement = new ExclusiveCanonicalizationTransform(true); string id; ElementWithAlgorithmAttribute signatureMethodElement; SignatureResourcePool resourcePool; DictionaryManager dictionaryManager; MemoryStream canonicalStream; ISignatureReaderProvider readerProvider; object signatureReaderProviderCallbackContext; bool sendSide = true; protected SignedInfo(DictionaryManager dictionaryManager) { if (dictionaryManager == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("dictionaryManager"); this.signatureMethodElement = new ElementWithAlgorithmAttribute(dictionaryManager.XmlSignatureDictionary.SignatureMethod); this.dictionaryManager = dictionaryManager; } protected DictionaryManager DictionaryManager { get { return this.dictionaryManager; } } protected MemoryStream CanonicalStream { get { return this.canonicalStream; } set { this.canonicalStream = value; } } protected bool SendSide { get { return this.sendSide; } set { this.sendSide = value; } } public ISignatureReaderProvider ReaderProvider { get { return this.readerProvider; } set { this.readerProvider = value; } } public object SignatureReaderProviderCallbackContext { get { return this.signatureReaderProviderCallbackContext; } set { this.signatureReaderProviderCallbackContext = value; } } public string CanonicalizationMethod { get { return this.canonicalizationMethodElement.Algorithm; } set { if (value != this.canonicalizationMethodElement.Algorithm) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.UnsupportedTransformAlgorithm))); } } } public XmlDictionaryString CanonicalizationMethodDictionaryString { set { if (value != null && value.Value != this.canonicalizationMethodElement.Algorithm) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.UnsupportedTransformAlgorithm))); } } } public bool HasId { get { return true; } } public string Id { get { return this.id; } set { this.id = value; } } public abstract int ReferenceCount { get; } public string SignatureMethod { get { return this.signatureMethodElement.Algorithm; } set { this.signatureMethodElement.Algorithm = value; } } public XmlDictionaryString SignatureMethodDictionaryString { get { return this.signatureMethodElement.AlgorithmDictionaryString; } set { this.signatureMethodElement.AlgorithmDictionaryString = value; } } public SignatureResourcePool ResourcePool { get { if (this.resourcePool == null) { this.resourcePool = new SignatureResourcePool(); } return this.resourcePool; } set { this.resourcePool = value; } } public void ComputeHash(HashAlgorithm algorithm) { if ((this.CanonicalizationMethod != SecurityAlgorithms.ExclusiveC14n) && (this.CanonicalizationMethod != SecurityAlgorithms.ExclusiveC14nWithComments)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.UnsupportedTransformAlgorithm))); } HashStream hashStream = this.ResourcePool.TakeHashStream(algorithm); ComputeHash(hashStream); hashStream.FlushHash(); } protected virtual void ComputeHash(HashStream hashStream) { if (this.sendSide) { XmlDictionaryWriter utf8Writer = this.ResourcePool.TakeUtf8Writer(); utf8Writer.StartCanonicalization(hashStream, false, null); WriteTo(utf8Writer, this.dictionaryManager); utf8Writer.EndCanonicalization(); } else if (this.canonicalStream != null) { this.canonicalStream.WriteTo(hashStream); } else { if (this.readerProvider == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.InclusiveNamespacePrefixRequiresSignatureReader))); XmlDictionaryReader signatureReader = this.readerProvider.GetReader(this.signatureReaderProviderCallbackContext); DiagnosticUtility.DebugAssert(signatureReader != null, "Require a Signature reader to validate signature."); if (!signatureReader.CanCanonicalize) { MemoryStream stream = new MemoryStream(); XmlDictionaryWriter bufferingWriter = XmlDictionaryWriter.CreateBinaryWriter(stream, this.DictionaryManager.ParentDictionary); string[] inclusivePrefix = GetInclusivePrefixes(); if (inclusivePrefix != null) { bufferingWriter.WriteStartElement("a"); for (int i = 0; i < inclusivePrefix.Length; ++i) { string ns = GetNamespaceForInclusivePrefix(inclusivePrefix[i]); if (ns != null) { bufferingWriter.WriteXmlnsAttribute(inclusivePrefix[i], ns); } } } signatureReader.MoveToContent(); bufferingWriter.WriteNode(signatureReader, false); if (inclusivePrefix != null) bufferingWriter.WriteEndElement(); bufferingWriter.Flush(); byte[] buffer = stream.ToArray(); int bufferLength = (int)stream.Length; bufferingWriter.Close(); signatureReader.Close(); // Create a reader around the buffering Stream. signatureReader = XmlDictionaryReader.CreateBinaryReader(buffer, 0, bufferLength, this.DictionaryManager.ParentDictionary, XmlDictionaryReaderQuotas.Max); if (inclusivePrefix != null) signatureReader.ReadStartElement("a"); } signatureReader.ReadStartElement(dictionaryManager.XmlSignatureDictionary.Signature, dictionaryManager.XmlSignatureDictionary.Namespace); signatureReader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.SignedInfo, dictionaryManager.XmlSignatureDictionary.Namespace); signatureReader.StartCanonicalization(hashStream, false, GetInclusivePrefixes()); signatureReader.Skip(); signatureReader.EndCanonicalization(); signatureReader.Close(); } } public abstract void ComputeReferenceDigests(); protected string[] GetInclusivePrefixes() { return this.canonicalizationMethodElement.GetInclusivePrefixes(); } protected virtual string GetNamespaceForInclusivePrefix(string prefix) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } public abstract void EnsureAllReferencesVerified(); public void EnsureDigestValidity(string id, object resolvedXmlSource) { if (!EnsureDigestValidityIfIdMatches(id, resolvedXmlSource)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.RequiredTargetNotSigned, id))); } } public abstract bool EnsureDigestValidityIfIdMatches(string id, object resolvedXmlSource); public virtual bool HasUnverifiedReference(string id) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException()); } protected void ReadCanonicalizationMethod(XmlDictionaryReader reader, DictionaryManager dictionaryManager) { // we will ignore any comments in the SignedInfo elemnt when verifying signature this.canonicalizationMethodElement.ReadFrom(reader, dictionaryManager, false); } public abstract void ReadFrom(XmlDictionaryReader reader, TransformFactory transformFactory, DictionaryManager dictionaryManager); protected void ReadSignatureMethod(XmlDictionaryReader reader, DictionaryManager dictionaryManager) { this.signatureMethodElement.ReadFrom(reader, dictionaryManager); } protected void WriteCanonicalizationMethod(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { this.canonicalizationMethodElement.WriteTo(writer, dictionaryManager); } protected void WriteSignatureMethod(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { this.signatureMethodElement.WriteTo(writer, dictionaryManager); } public abstract void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager); } // whitespace preservation convention: ws1 immediately inside open tag; ws2 immediately after end tag. class StandardSignedInfo : SignedInfo { string prefix = SignedXml.DefaultPrefix; List references; Dictionary context; public StandardSignedInfo(DictionaryManager dictionaryManager) : base(dictionaryManager) { this.references = new List(); } public override int ReferenceCount { get { return this.references.Count; } } public Reference this[int index] { get { return this.references[index]; } } public void AddReference(Reference reference) { reference.ResourcePool = this.ResourcePool; this.references.Add(reference); } public override void EnsureAllReferencesVerified() { for (int i = 0; i < this.references.Count; i++) { if (!this.references[i].Verified) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new CryptographicException(SR.GetString(SR.UnableToResolveReferenceUriForSignature, this.references[i].Uri))); } } } public override bool EnsureDigestValidityIfIdMatches(string id, object resolvedXmlSource) { for (int i = 0; i < this.references.Count; i++) { if (this.references[i].EnsureDigestValidityIfIdMatches(id, resolvedXmlSource)) { return true; } } return false; } public override bool HasUnverifiedReference(string id) { for (int i = 0; i < this.references.Count; i++) { if (!this.references[i].Verified && this.references[i].ExtractReferredId() == id) { return true; } } return false; } public override void ComputeReferenceDigests() { if (this.references.Count == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.AtLeastOneReferenceRequired))); } for (int i = 0; i < this.references.Count; i++) { this.references[i].ComputeAndSetDigest(); } } public override void ReadFrom(XmlDictionaryReader reader, TransformFactory transformFactory, DictionaryManager dictionaryManager) { this.SendSide = false; if (reader.CanCanonicalize) { this.CanonicalStream = new MemoryStream(); reader.StartCanonicalization(this.CanonicalStream, false, null); } reader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.SignedInfo, dictionaryManager.XmlSignatureDictionary.Namespace); this.prefix = reader.Prefix; this.Id = reader.GetAttribute(dictionaryManager.UtilityDictionary.IdAttribute, null); reader.Read(); ReadCanonicalizationMethod(reader, dictionaryManager); ReadSignatureMethod(reader, dictionaryManager); while (reader.IsStartElement(dictionaryManager.XmlSignatureDictionary.Reference, dictionaryManager.XmlSignatureDictionary.Namespace)) { Reference reference = new Reference(dictionaryManager); reference.ReadFrom(reader, transformFactory, dictionaryManager); AddReference(reference); } reader.ReadEndElement(); // SignedInfo if (reader.CanCanonicalize) reader.EndCanonicalization(); string[] inclusivePrefixes = GetInclusivePrefixes(); if (inclusivePrefixes != null) { // Clear the canonicalized stream. We cannot use this while inclusive prefixes are // specified. this.CanonicalStream = null; this.context = new Dictionary(inclusivePrefixes.Length); for (int i = 0; i < inclusivePrefixes.Length; i++) { this.context.Add(inclusivePrefixes[i], reader.LookupNamespace(inclusivePrefixes[i])); } } } public override void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { writer.WriteStartElement(this.prefix, dictionaryManager.XmlSignatureDictionary.SignedInfo, dictionaryManager.XmlSignatureDictionary.Namespace); if (this.Id != null) { writer.WriteAttributeString(dictionaryManager.UtilityDictionary.IdAttribute, null, this.Id); } WriteCanonicalizationMethod(writer, dictionaryManager); WriteSignatureMethod(writer, dictionaryManager); for (int i = 0; i < this.references.Count; i++) { this.references[i].WriteTo(writer, dictionaryManager); } writer.WriteEndElement(); // SignedInfo } protected override string GetNamespaceForInclusivePrefix(string prefix) { if (this.context == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException()); if (prefix == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("prefix"); return context[prefix]; } protected string Prefix { get { return prefix; } set { prefix = value; } } protected Dictionary Context { get { return context; } set { context = value; } } } sealed class WifSignedInfo : StandardSignedInfo, IDisposable { MemoryStream _bufferedStream; string _defaultNamespace = String.Empty; bool _disposed; public WifSignedInfo(DictionaryManager dictionaryManager) : base(dictionaryManager) { } ~WifSignedInfo() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { // // Free all of our managed resources // if (_bufferedStream != null) { _bufferedStream.Close(); _bufferedStream = null; } } // Free native resources, if any. _disposed = true; } protected override void ComputeHash(HashStream hashStream) { if (SendSide) { using (XmlDictionaryWriter utf8Writer = XmlDictionaryWriter.CreateTextWriter(Stream.Null, Encoding.UTF8, false)) { utf8Writer.StartCanonicalization(hashStream, false, null); WriteTo(utf8Writer, DictionaryManager); utf8Writer.EndCanonicalization(); } } else if (CanonicalStream != null) { CanonicalStream.WriteTo(hashStream); } else { _bufferedStream.Position = 0; // We are creating a XmlDictionaryReader with a hard-coded Max XmlDictionaryReaderQuotas. This is a reader that we // are creating over an already buffered content. The content was initially read off user provided XmlDictionaryReader // with the correct quotas and hence we know the data is valid. // Note: signedinfoReader will close _bufferedStream on Dispose. using (XmlDictionaryReader signedinfoReader = XmlDictionaryReader.CreateTextReader(_bufferedStream, XmlDictionaryReaderQuotas.Max)) { signedinfoReader.MoveToContent(); using (XmlDictionaryWriter bufferingWriter = XmlDictionaryWriter.CreateTextWriter(Stream.Null, Encoding.UTF8, false)) { bufferingWriter.WriteStartElement("a", _defaultNamespace); string[] inclusivePrefix = GetInclusivePrefixes(); for (int i = 0; i < inclusivePrefix.Length; ++i) { string ns = GetNamespaceForInclusivePrefix(inclusivePrefix[i]); if (ns != null) { bufferingWriter.WriteXmlnsAttribute(inclusivePrefix[i], ns); } } bufferingWriter.StartCanonicalization(hashStream, false, inclusivePrefix); bufferingWriter.WriteNode(signedinfoReader, false); bufferingWriter.EndCanonicalization(); bufferingWriter.WriteEndElement(); } } } } public override void ReadFrom(XmlDictionaryReader reader, TransformFactory transformFactory, DictionaryManager dictionaryManager) { reader.MoveToStartElement(XmlSignatureConstants.Elements.SignedInfo, XmlSignatureConstants.Namespace); SendSide = false; _defaultNamespace = reader.LookupNamespace(String.Empty); _bufferedStream = new MemoryStream(); XmlWriterSettings settings = new XmlWriterSettings(); settings.Encoding = Encoding.UTF8; settings.NewLineHandling = NewLineHandling.None; using (XmlWriter bufferWriter = XmlTextWriter.Create(_bufferedStream, settings)) { bufferWriter.WriteNode(reader, true); bufferWriter.Flush(); } _bufferedStream.Position = 0; // // We are creating a XmlDictionaryReader with a hard-coded Max XmlDictionaryReaderQuotas. This is a reader that we // are creating over an already buffered content. The content was initially read off user provided XmlDictionaryReader // with the correct quotas and hence we know the data is valid. // Note: effectiveReader will close _bufferedStream on Dispose. // using (XmlDictionaryReader effectiveReader = XmlDictionaryReader.CreateTextReader(_bufferedStream, XmlDictionaryReaderQuotas.Max)) { CanonicalStream = new MemoryStream(); effectiveReader.StartCanonicalization(CanonicalStream, false, null); effectiveReader.MoveToStartElement(XmlSignatureConstants.Elements.SignedInfo, XmlSignatureConstants.Namespace); Prefix = effectiveReader.Prefix; Id = effectiveReader.GetAttribute(WSSecurityUtilityConstants.Attributes.Id, null); effectiveReader.Read(); ReadCanonicalizationMethod(effectiveReader, DictionaryManager); ReadSignatureMethod(effectiveReader, DictionaryManager); while (effectiveReader.IsStartElement(XmlSignatureConstants.Elements.Reference, XmlSignatureConstants.Namespace)) { Reference reference = new Reference(DictionaryManager); reference.ReadFrom(effectiveReader, transformFactory, DictionaryManager); AddReference(reference); } effectiveReader.ReadEndElement(); effectiveReader.EndCanonicalization(); } string[] inclusivePrefixes = GetInclusivePrefixes(); if (inclusivePrefixes != null) { // Clear the canonicalized stream. We cannot use this while inclusive prefixes are // specified. CanonicalStream = null; Context = new Dictionary(inclusivePrefixes.Length); for (int i = 0; i < inclusivePrefixes.Length; i++) { Context.Add(inclusivePrefixes[i], reader.LookupNamespace(inclusivePrefixes[i])); } } } } sealed class Reference { ElementWithAlgorithmAttribute digestMethodElement; DigestValueElement digestValueElement = new DigestValueElement(); string id; string prefix = SignedXml.DefaultPrefix; object resolvedXmlSource; readonly TransformChain transformChain = new TransformChain(); string type; string uri; SignatureResourcePool resourcePool; bool verified; string referredId; DictionaryManager dictionaryManager; public Reference(DictionaryManager dictionaryManager) : this(dictionaryManager, null) { } public Reference(DictionaryManager dictionaryManager, string uri) : this(dictionaryManager, uri, null) { } public Reference(DictionaryManager dictionaryManager, string uri, object resolvedXmlSource) { if (dictionaryManager == null) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("dictionaryManager"); this.dictionaryManager = dictionaryManager; this.digestMethodElement = new ElementWithAlgorithmAttribute(dictionaryManager.XmlSignatureDictionary.DigestMethod); this.uri = uri; this.resolvedXmlSource = resolvedXmlSource; } public string DigestMethod { get { return this.digestMethodElement.Algorithm; } set { this.digestMethodElement.Algorithm = value; } } public XmlDictionaryString DigestMethodDictionaryString { get { return this.digestMethodElement.AlgorithmDictionaryString; } set { this.digestMethodElement.AlgorithmDictionaryString = value; } } public string Id { get { return this.id; } set { this.id = value; } } public SignatureResourcePool ResourcePool { get { return this.resourcePool; } set { this.resourcePool = value; } } public TransformChain TransformChain { get { return this.transformChain; } } public int TransformCount { get { return this.transformChain.TransformCount; } } public string Type { get { return this.type; } set { this.type = value; } } public string Uri { get { return this.uri; } set { this.uri = value; } } public bool Verified { get { return this.verified; } } public void AddTransform(Transform transform) { this.transformChain.Add(transform); } public void EnsureDigestValidity(string id, byte[] computedDigest) { if (!EnsureDigestValidityIfIdMatches(id, computedDigest)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.RequiredTargetNotSigned, id))); } } public void EnsureDigestValidity(string id, object resolvedXmlSource) { if (!EnsureDigestValidityIfIdMatches(id, resolvedXmlSource)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.RequiredTargetNotSigned, id))); } } public bool EnsureDigestValidityIfIdMatches(string id, byte[] computedDigest) { if (this.verified || id != ExtractReferredId()) { return false; } if (!CryptoHelper.IsEqual(computedDigest, GetDigestValue())) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new CryptographicException(SR.GetString(SR.DigestVerificationFailedForReference, this.uri))); } this.verified = true; return true; } public bool EnsureDigestValidityIfIdMatches(string id, object resolvedXmlSource) { if (this.verified) { return false; } // During StrTransform the extractedReferredId on the reference will point to STR and hence will not be // equal to the referred element ie security token Id. if (id != ExtractReferredId() && !this.IsStrTranform()) { return false; } this.resolvedXmlSource = resolvedXmlSource; if (!CheckDigest()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new CryptographicException(SR.GetString(SR.DigestVerificationFailedForReference, this.uri))); } this.verified = true; return true; } public bool IsStrTranform() { return this.TransformChain.TransformCount == 1 && this.TransformChain[0].Algorithm == SecurityAlgorithms.StrTransform; } public string ExtractReferredId() { if (this.referredId == null) { if (StringComparer.OrdinalIgnoreCase.Equals(uri, String.Empty)) { return String.Empty; } if (this.uri == null || this.uri.Length < 2 || this.uri[0] != '#') { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( new CryptographicException(SR.GetString(SR.UnableToResolveReferenceUriForSignature, this.uri))); } this.referredId = this.uri.Substring(1); } return this.referredId; } /// /// We look at the URI reference to decide if we should preserve comments while canonicalization. /// Only when the reference is xpointer(/) or xpointer(id(SomeId)) do we preserve comments during canonicalization /// of the reference element for computing the digest. /// /// The Uri reference /// true if comments should be preserved. private static bool ShouldPreserveComments(string uri) { bool preserveComments = false; if (!String.IsNullOrEmpty(uri)) { //removes the hash string idref = uri.Substring(1); if (idref == "xpointer(/)") { preserveComments = true; } else if (idref.StartsWith("xpointer(id(", StringComparison.Ordinal) && (idref.IndexOf(")", StringComparison.Ordinal) > 0)) { // Dealing with XPointer of type #xpointer(id("ID")). Other XPointer support isn't handled here and is anyway optional preserveComments = true; } } return preserveComments; } public bool CheckDigest() { byte[] computedDigest = ComputeDigest(); bool result = CryptoHelper.IsEqual(computedDigest, GetDigestValue()); #if LOG_DIGESTS Console.WriteLine(">>> Checking digest for reference '{0}', result {1}", uri, result); Console.WriteLine(" Computed digest {0}", Convert.ToBase64String(computedDigest)); Console.WriteLine(" Received digest {0}", Convert.ToBase64String(GetDigestValue())); #endif return result; } public void ComputeAndSetDigest() { this.digestValueElement.Value = ComputeDigest(); } public byte[] ComputeDigest() { if (this.transformChain.TransformCount == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.EmptyTransformChainNotSupported))); } if (this.resolvedXmlSource == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.UnableToResolveReferenceUriForSignature, this.uri))); } return this.transformChain.TransformToDigest(this.resolvedXmlSource, this.ResourcePool, this.DigestMethod, this.dictionaryManager); } public byte[] GetDigestValue() { return this.digestValueElement.Value; } public void ReadFrom(XmlDictionaryReader reader, TransformFactory transformFactory, DictionaryManager dictionaryManager) { reader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.Reference, dictionaryManager.XmlSignatureDictionary.Namespace); this.prefix = reader.Prefix; this.Id = reader.GetAttribute(UtilityStrings.IdAttribute, null); this.Uri = reader.GetAttribute(dictionaryManager.XmlSignatureDictionary.URI, null); this.Type = reader.GetAttribute(dictionaryManager.XmlSignatureDictionary.Type, null); reader.Read(); if (reader.IsStartElement(dictionaryManager.XmlSignatureDictionary.Transforms, dictionaryManager.XmlSignatureDictionary.Namespace)) { this.transformChain.ReadFrom(reader, transformFactory, dictionaryManager, ShouldPreserveComments(this.Uri)); } this.digestMethodElement.ReadFrom(reader, dictionaryManager); this.digestValueElement.ReadFrom(reader, dictionaryManager); reader.MoveToContent(); reader.ReadEndElement(); // Reference } public void SetResolvedXmlSource(object resolvedXmlSource) { this.resolvedXmlSource = resolvedXmlSource; } public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { writer.WriteStartElement(this.prefix, dictionaryManager.XmlSignatureDictionary.Reference, dictionaryManager.XmlSignatureDictionary.Namespace); if (this.id != null) { writer.WriteAttributeString(dictionaryManager.UtilityDictionary.IdAttribute, null, this.id); } if (this.uri != null) { writer.WriteAttributeString(dictionaryManager.XmlSignatureDictionary.URI, null, this.uri); } if (this.type != null) { writer.WriteAttributeString(dictionaryManager.XmlSignatureDictionary.Type, null, this.type); } if (this.transformChain.TransformCount > 0) { this.transformChain.WriteTo(writer, dictionaryManager); } this.digestMethodElement.WriteTo(writer, dictionaryManager); this.digestValueElement.WriteTo(writer, dictionaryManager); writer.WriteEndElement(); // Reference } struct DigestValueElement { byte[] digestValue; string digestText; string prefix; internal byte[] Value { get { return this.digestValue; } set { this.digestValue = value; this.digestText = null; } } public void ReadFrom(XmlDictionaryReader reader, DictionaryManager dictionaryManager) { reader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.DigestValue, dictionaryManager.XmlSignatureDictionary.Namespace); this.prefix = reader.Prefix; reader.Read(); reader.MoveToContent(); this.digestText = reader.ReadString(); this.digestValue = System.Convert.FromBase64String(digestText.Trim()); reader.MoveToContent(); reader.ReadEndElement(); // DigestValue } public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { writer.WriteStartElement(this.prefix ?? XmlSignatureStrings.Prefix, dictionaryManager.XmlSignatureDictionary.DigestValue, dictionaryManager.XmlSignatureDictionary.Namespace); if (this.digestText != null) { writer.WriteString(this.digestText); } else { writer.WriteBase64(this.digestValue, 0, this.digestValue.Length); } writer.WriteEndElement(); // DigestValue } } } sealed class TransformChain { string prefix = SignedXml.DefaultPrefix; MostlySingletonList transforms; public TransformChain() { } public int TransformCount { get { return this.transforms.Count; } } public Transform this[int index] { get { return this.transforms[index]; } } public bool NeedsInclusiveContext { get { for (int i = 0; i < this.TransformCount; i++) { if (this[i].NeedsInclusiveContext) { return true; } } return false; } } public void Add(Transform transform) { this.transforms.Add(transform); } public void ReadFrom(XmlDictionaryReader reader, TransformFactory transformFactory, DictionaryManager dictionaryManager, bool preserveComments) { reader.MoveToStartElement(dictionaryManager.XmlSignatureDictionary.Transforms, dictionaryManager.XmlSignatureDictionary.Namespace); this.prefix = reader.Prefix; reader.Read(); while (reader.IsStartElement(dictionaryManager.XmlSignatureDictionary.Transform, dictionaryManager.XmlSignatureDictionary.Namespace)) { string transformAlgorithmUri = reader.GetAttribute(dictionaryManager.XmlSignatureDictionary.Algorithm, null); Transform transform = transformFactory.CreateTransform(transformAlgorithmUri); transform.ReadFrom(reader, dictionaryManager, preserveComments); Add(transform); } reader.MoveToContent(); reader.ReadEndElement(); // Transforms if (this.TransformCount == 0) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.AtLeastOneTransformRequired))); } } public byte[] TransformToDigest(object data, SignatureResourcePool resourcePool, string digestMethod, DictionaryManager dictionaryManager) { DiagnosticUtility.DebugAssert(TransformCount > 0, ""); for (int i = 0; i < this.TransformCount - 1; i++) { data = this[i].Process(data, resourcePool, dictionaryManager); } return this[this.TransformCount - 1].ProcessAndDigest(data, resourcePool, digestMethod, dictionaryManager); } public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { writer.WriteStartElement(this.prefix, dictionaryManager.XmlSignatureDictionary.Transforms, dictionaryManager.XmlSignatureDictionary.Namespace); for (int i = 0; i < this.TransformCount; i++) { this[i].WriteTo(writer, dictionaryManager); } writer.WriteEndElement(); // Transforms } } struct ElementWithAlgorithmAttribute { readonly XmlDictionaryString elementName; string algorithm; XmlDictionaryString algorithmDictionaryString; string prefix; public ElementWithAlgorithmAttribute(XmlDictionaryString elementName) { if (elementName == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentNullException("elementName")); } this.elementName = elementName; this.algorithm = null; this.algorithmDictionaryString = null; this.prefix = SignedXml.DefaultPrefix; } public string Algorithm { get { return this.algorithm; } set { this.algorithm = value; } } public XmlDictionaryString AlgorithmDictionaryString { get { return this.algorithmDictionaryString; } set { this.algorithmDictionaryString = value; } } public void ReadFrom(XmlDictionaryReader reader, DictionaryManager dictionaryManager) { reader.MoveToStartElement(this.elementName, dictionaryManager.XmlSignatureDictionary.Namespace); this.prefix = reader.Prefix; bool isEmptyElement = reader.IsEmptyElement; this.algorithm = reader.GetAttribute(dictionaryManager.XmlSignatureDictionary.Algorithm, null); if (this.algorithm == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException( SR.GetString(SR.RequiredAttributeMissing, dictionaryManager.XmlSignatureDictionary.Algorithm, this.elementName))); } reader.Read(); reader.MoveToContent(); if (!isEmptyElement) { reader.MoveToContent(); reader.ReadEndElement(); } } public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager) { writer.WriteStartElement(this.prefix, this.elementName, dictionaryManager.XmlSignatureDictionary.Namespace); writer.WriteStartAttribute(dictionaryManager.XmlSignatureDictionary.Algorithm, null); if (this.algorithmDictionaryString != null) { writer.WriteString(this.algorithmDictionaryString); } else { writer.WriteString(this.algorithm); } writer.WriteEndAttribute(); writer.WriteEndElement(); } } }