a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
551 lines
18 KiB
C#
551 lines
18 KiB
C#
//
|
|
// WSSecurityMessageHeader.cs
|
|
//
|
|
// Author:
|
|
// Atsushi Enomoto <atsushi@ximian.com>
|
|
//
|
|
// Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.IdentityModel.Selectors;
|
|
using System.IdentityModel.Tokens;
|
|
using System.Runtime.Serialization;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.Xml;
|
|
using System.ServiceModel;
|
|
using System.ServiceModel.Channels;
|
|
using System.ServiceModel.Dispatcher;
|
|
using System.ServiceModel.Security;
|
|
using System.ServiceModel.Security.Tokens;
|
|
using System.Text;
|
|
using System.Xml;
|
|
|
|
namespace System.ServiceModel.Channels.Security
|
|
{
|
|
internal class WSSecurityMessageHeaderReader
|
|
{
|
|
public WSSecurityMessageHeaderReader (WSSecurityMessageHeader header, SecurityTokenSerializer serializer, SecurityTokenResolver resolver, XmlDocument doc, XmlNamespaceManager nsmgr, List<SecurityToken> tokens)
|
|
{
|
|
this.header = header;
|
|
this.serializer = serializer;
|
|
this.resolver = resolver;
|
|
this.doc = doc;
|
|
this.nsmgr = nsmgr;
|
|
this.tokens = tokens;
|
|
}
|
|
|
|
WSSecurityMessageHeader header;
|
|
SecurityTokenSerializer serializer;
|
|
SecurityTokenResolver resolver;
|
|
XmlDocument doc;
|
|
XmlNamespaceManager nsmgr;
|
|
List<SecurityToken> tokens;
|
|
Dictionary<string, EncryptedData> encryptedDataList =
|
|
new Dictionary<string, EncryptedData> ();
|
|
|
|
public void ReadContents (XmlReader reader)
|
|
{
|
|
DerivedKeySecurityToken currentToken = null;
|
|
|
|
reader.MoveToContent ();
|
|
reader.ReadStartElement ("Security", Constants.WssNamespace);
|
|
do {
|
|
reader.MoveToContent ();
|
|
if (reader.NodeType == XmlNodeType.EndElement)
|
|
break;
|
|
object o = ReadContent (reader);
|
|
if (o is EncryptedData) {
|
|
EncryptedData ed = (EncryptedData) o;
|
|
encryptedDataList [ed.Id] = ed;
|
|
}
|
|
else if (o is ReferenceList && currentToken != null)
|
|
currentToken.ReferenceList = (ReferenceList) o;
|
|
else if (o is SecurityToken) {
|
|
if (o is DerivedKeySecurityToken)
|
|
currentToken = o as DerivedKeySecurityToken;
|
|
tokens.Add ((SecurityToken) o);
|
|
}
|
|
header.Contents.Add (o);
|
|
} while (true);
|
|
reader.ReadEndElement ();
|
|
}
|
|
|
|
object ReadContent (XmlReader reader)
|
|
{
|
|
reader.MoveToContent ();
|
|
if (reader.NodeType != XmlNodeType.Element)
|
|
throw new XmlException (String.Format ("Node type {0} is not expected as a WS-Security message header content.", reader.NodeType));
|
|
switch (reader.NamespaceURI) {
|
|
case Constants.WsuNamespace:
|
|
switch (reader.LocalName) {
|
|
case "Timestamp":
|
|
return ReadTimestamp (reader);
|
|
}
|
|
break;
|
|
//case Constants.WstNamespace:
|
|
case Constants.Wss11Namespace:
|
|
if (reader.LocalName == "SignatureConfirmation") {
|
|
return ReadSignatureConfirmation (reader, doc);
|
|
}
|
|
break;
|
|
case SignedXml.XmlDsigNamespaceUrl:
|
|
switch (reader.LocalName) {
|
|
case "Signature":
|
|
WSSignedXml sxml = new WSSignedXml (doc);
|
|
sxml.Signature.LoadXml ((XmlElement) doc.ReadNode (reader));
|
|
UpdateSignatureKeyInfo (sxml.Signature, doc, serializer);
|
|
return sxml;
|
|
}
|
|
break;
|
|
case EncryptedXml.XmlEncNamespaceUrl:
|
|
switch (reader.LocalName) {
|
|
case "EncryptedData":
|
|
XmlElement el = (XmlElement) doc.ReadNode (reader);
|
|
return CreateEncryptedData (el);
|
|
case "ReferenceList":
|
|
ReferenceList rl = new ReferenceList ();
|
|
reader.Read ();
|
|
for (reader.MoveToContent ();
|
|
reader.NodeType != XmlNodeType.EndElement;
|
|
reader.MoveToContent ()) {
|
|
switch (reader.LocalName) {
|
|
case "DataReference":
|
|
DataReference dref = new DataReference ();
|
|
dref.LoadXml ((XmlElement) doc.ReadNode (reader));
|
|
rl.Add (dref);
|
|
continue;
|
|
case "KeyReference":
|
|
KeyReference kref = new KeyReference ();
|
|
kref.LoadXml ((XmlElement) doc.ReadNode (reader));
|
|
rl.Add (kref);
|
|
continue;
|
|
}
|
|
throw new XmlException (String.Format ("Unexpected {2} node '{0}' in namespace '{1}' in ReferenceList.", reader.Name, reader.NamespaceURI, reader.NodeType));
|
|
}
|
|
reader.ReadEndElement ();
|
|
return rl;
|
|
}
|
|
break;
|
|
}
|
|
// SecurityTokenReference will be handled here.
|
|
// This order (Token->KeyIdentifierClause) is
|
|
// important because WrappedKey could be read
|
|
// in both context (but must be a token here).
|
|
if (serializer.CanReadToken (reader))
|
|
return serializer.ReadToken (reader, resolver);
|
|
else if (serializer.CanReadKeyIdentifierClause (reader))
|
|
return serializer.ReadKeyIdentifierClause (reader);
|
|
else
|
|
throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
|
|
}
|
|
|
|
void UpdateSignatureKeyInfo (Signature sig, XmlDocument doc, SecurityTokenSerializer serializer)
|
|
{
|
|
KeyInfo ki = new KeyInfo ();
|
|
ki.Id = sig.KeyInfo.Id;
|
|
foreach (KeyInfoClause kic in sig.KeyInfo) {
|
|
SecurityTokenReferenceKeyInfo r = new SecurityTokenReferenceKeyInfo (serializer, doc);
|
|
r.LoadXml (kic.GetXml ());
|
|
ki.AddClause (r);
|
|
}
|
|
sig.KeyInfo = ki;
|
|
}
|
|
|
|
#region Decryption
|
|
|
|
// returns the protection token
|
|
public void DecryptSecurity (SecureMessageDecryptor decryptor, SymmetricSecurityKey sym, byte [] dummyEncKey)
|
|
{
|
|
WSEncryptedXml encXml = new WSEncryptedXml (doc);
|
|
|
|
// default, unless overriden by the default DerivedKeyToken.
|
|
Rijndael aes = RijndaelManaged.Create (); // it is reused with every key
|
|
aes.Mode = CipherMode.CBC;
|
|
|
|
if (sym == null)
|
|
throw new MessageSecurityException ("Cannot find the encryption key in this message and context");
|
|
|
|
// decrypt the body with the decrypted key
|
|
Collection<string> references = new Collection<string> ();
|
|
|
|
foreach (ReferenceList rlist in header.FindAll<ReferenceList> ())
|
|
foreach (EncryptedReference encref in rlist)
|
|
references.Add (StripUri (encref.Uri));
|
|
|
|
foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ())
|
|
foreach (EncryptedReference er in wk.ReferenceList)
|
|
references.Add (StripUri (er.Uri));
|
|
|
|
Collection<XmlElement> list = new Collection<XmlElement> ();
|
|
foreach (string uri in references) {
|
|
XmlElement el = encXml.GetIdElement (doc, uri);
|
|
if (el != null)
|
|
list.Add (el);
|
|
else
|
|
throw new MessageSecurityException (String.Format ("On decryption, EncryptedData with Id '{0}', referenced by ReferenceData, was not found.", uri));
|
|
}
|
|
|
|
foreach (XmlElement el in list) {
|
|
EncryptedData ed2 = CreateEncryptedData (el);
|
|
byte [] key = GetEncryptionKeyForData (ed2, encXml, dummyEncKey);
|
|
aes.Key = key != null ? key : sym.GetSymmetricKey ();
|
|
byte [] decrypted = DecryptData (encXml, ed2, aes);
|
|
encXml.ReplaceData (el, decrypted);
|
|
EncryptedData existing;
|
|
// if it was a header content, replace
|
|
// corresponding one.
|
|
if (encryptedDataList.TryGetValue (ed2.Id, out existing)) {
|
|
// FIXME: it is kind of extraneous and could be replaced by XmlNodeReader
|
|
//Console.WriteLine ("DECRYPTED EncryptedData:");
|
|
//Console.WriteLine (Encoding.UTF8.GetString (decrypted));
|
|
object o = ReadContent (XmlReader.Create (new MemoryStream (decrypted)));
|
|
header.Contents.Remove (existing);
|
|
header.Contents.Add (o);
|
|
}
|
|
}
|
|
/*
|
|
Console.WriteLine ("======== Decrypted Document ========");
|
|
doc.PreserveWhitespace = false;
|
|
doc.Save (Console.Out);
|
|
doc.PreserveWhitespace = true;
|
|
*/
|
|
}
|
|
|
|
EncryptedData CreateEncryptedData (XmlElement el)
|
|
{
|
|
EncryptedData ed = new EncryptedData ();
|
|
ed.LoadXml (el);
|
|
if (ed.Id == null)
|
|
ed.Id = el.GetAttribute ("Id", Constants.WsuNamespace);
|
|
return ed;
|
|
}
|
|
|
|
byte [] GetEncryptionKeyForData (EncryptedData ed2, EncryptedXml encXml, byte [] dummyEncKey)
|
|
{
|
|
// Since ReferenceList could be embedded directly in wss_header without
|
|
// key indication, it must iterate all the derived keys to find out
|
|
// appropriate one.
|
|
foreach (DerivedKeySecurityToken dk in header.FindAll<DerivedKeySecurityToken> ()) {
|
|
if (dk.ReferenceList == null)
|
|
continue;
|
|
foreach (DataReference dr in dk.ReferenceList)
|
|
if (StripUri (dr.Uri) == ed2.Id)
|
|
return ((SymmetricSecurityKey) dk.SecurityKeys [0]).GetSymmetricKey ();
|
|
}
|
|
foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ()) {
|
|
if (wk.ReferenceList == null)
|
|
continue;
|
|
foreach (DataReference dr in wk.ReferenceList)
|
|
if (StripUri (dr.Uri) == ed2.Id)
|
|
return ((SymmetricSecurityKey) wk.SecurityKeys [0]).GetSymmetricKey ();
|
|
}
|
|
|
|
if (ed2.KeyInfo == null)
|
|
return null;
|
|
foreach (KeyInfoClause kic in ed2.KeyInfo) {
|
|
SecurityKeyIdentifierClause skic = serializer.ReadKeyIdentifierClause (new XmlNodeReader (kic.GetXml ()));
|
|
|
|
SecurityKey skey = null;
|
|
if (!resolver.TryResolveSecurityKey (skic, out skey))
|
|
throw new MessageSecurityException (String.Format ("The signing key could not be resolved from {0}", skic));
|
|
SymmetricSecurityKey ssk = skey as SymmetricSecurityKey;
|
|
if (ssk != null)
|
|
return ssk.GetSymmetricKey ();
|
|
}
|
|
return null; // no applicable key info clause.
|
|
}
|
|
|
|
// Probably it is a bug in .NET, but sometimes it does not contain
|
|
// proper padding bytes. For such cases, use PaddingMode.None
|
|
// instead. It must not be done in EncryptedXml class as it
|
|
// correctly rejects improper ISO10126 padding.
|
|
byte [] DecryptData (EncryptedXml encXml, EncryptedData ed, SymmetricAlgorithm symAlg)
|
|
{
|
|
PaddingMode bak = symAlg.Padding;
|
|
try {
|
|
byte [] bytes = ed.CipherData.CipherValue;
|
|
|
|
if (encXml.Padding != PaddingMode.None &&
|
|
encXml.Padding != PaddingMode.Zeros &&
|
|
bytes [bytes.Length - 1] > symAlg.BlockSize / 8)
|
|
symAlg.Padding = PaddingMode.None;
|
|
return encXml.DecryptData (ed, symAlg);
|
|
} finally {
|
|
symAlg.Padding = bak;
|
|
}
|
|
}
|
|
|
|
string StripUri (string src)
|
|
{
|
|
if (src == null || src.Length == 0)
|
|
return String.Empty;
|
|
if (src [0] != '#')
|
|
throw new NotSupportedException (String.Format ("Non-fragment URI in DataReference and KeyReference is not supported: '{0}'", src));
|
|
return src.Substring (1);
|
|
}
|
|
#endregion
|
|
|
|
static Wss11SignatureConfirmation ReadSignatureConfirmation (XmlReader reader, XmlDocument doc)
|
|
{
|
|
string id = reader.GetAttribute ("Id", Constants.WsuNamespace);
|
|
string value = reader.GetAttribute ("Value");
|
|
reader.Skip ();
|
|
return new Wss11SignatureConfirmation (id, value);
|
|
}
|
|
|
|
static WsuTimestamp ReadTimestamp (XmlReader reader)
|
|
{
|
|
WsuTimestamp ret = new WsuTimestamp ();
|
|
ret.Id = reader.GetAttribute ("Id", Constants.WsuNamespace);
|
|
reader.ReadStartElement ();
|
|
do {
|
|
reader.MoveToContent ();
|
|
if (reader.NodeType == XmlNodeType.EndElement)
|
|
break;
|
|
if (reader.NodeType != XmlNodeType.Element)
|
|
throw new XmlException (String.Format ("Node type {0} is not expected as a WS-Security 'Timestamp' content.", reader.NodeType));
|
|
switch (reader.NamespaceURI) {
|
|
case Constants.WsuNamespace:
|
|
switch (reader.LocalName) {
|
|
case "Created":
|
|
ret.Created = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
|
|
continue;
|
|
case "Expires":
|
|
ret.Expires = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
|
|
} while (true);
|
|
|
|
reader.ReadEndElement (); // </u:Timestamp>
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
internal class WSSecurityMessageHeader : MessageHeader
|
|
{
|
|
public WSSecurityMessageHeader (SecurityTokenSerializer serializer)
|
|
{
|
|
this.serializer = serializer;
|
|
}
|
|
|
|
SecurityTokenSerializer serializer;
|
|
Collection<object> contents = new Collection<object> ();
|
|
|
|
// Timestamp, BinarySecurityToken, EncryptedKey,
|
|
// [DerivedKeyToken]*, ReferenceList, EncryptedData
|
|
public Collection<object> Contents {
|
|
get { return contents; }
|
|
}
|
|
|
|
public override bool MustUnderstand {
|
|
get { return true; }
|
|
}
|
|
|
|
public override string Name {
|
|
get { return "Security"; }
|
|
}
|
|
|
|
public override string Namespace {
|
|
get { return Constants.WssNamespace; }
|
|
}
|
|
|
|
public void AddContent (object obj)
|
|
{
|
|
if (obj == null)
|
|
throw new ArgumentNullException ("obj");
|
|
Contents.Add (obj);
|
|
}
|
|
|
|
public T Find<T> ()
|
|
{
|
|
foreach (object o in Contents)
|
|
if (typeof (T).IsAssignableFrom (o.GetType ()))
|
|
return (T) o;
|
|
return default (T);
|
|
}
|
|
|
|
public Collection<T> FindAll<T> ()
|
|
{
|
|
Collection<T> c = new Collection<T> ();
|
|
foreach (object o in Contents)
|
|
if (typeof (T).IsAssignableFrom (o.GetType ()))
|
|
c.Add ((T) o);
|
|
return c;
|
|
}
|
|
|
|
protected override void OnWriteStartHeader (XmlDictionaryWriter writer, MessageVersion version)
|
|
{
|
|
writer.WriteStartElement ("o", this.Name, this.Namespace);
|
|
WriteHeaderAttributes (writer, version);
|
|
}
|
|
|
|
protected override void OnWriteHeaderContents (XmlDictionaryWriter writer, MessageVersion version)
|
|
{
|
|
// FIXME: it should use XmlDictionaryWriter that CanCanonicalize the output (which is not possible in any built-in writer types, so we'll have to hack it).
|
|
|
|
foreach (object obj in Contents) {
|
|
if (obj is WsuTimestamp) {
|
|
WsuTimestamp ts = (WsuTimestamp) obj;
|
|
ts.WriteTo (writer);
|
|
} else if (obj is SecurityToken) {
|
|
serializer.WriteToken (writer, (SecurityToken) obj);
|
|
} else if (obj is EncryptedKey) {
|
|
((EncryptedKey) obj).GetXml ().WriteTo (writer);
|
|
} else if (obj is ReferenceList) {
|
|
writer.WriteStartElement ("ReferenceList", EncryptedXml.XmlEncNamespaceUrl);
|
|
foreach (EncryptedReference er in (ReferenceList) obj)
|
|
er.GetXml ().WriteTo (writer);
|
|
writer.WriteEndElement ();
|
|
} else if (obj is EncryptedData) {
|
|
((EncryptedData) obj).GetXml ().WriteTo (writer);
|
|
} else if (obj is Signature) {
|
|
((Signature) obj).GetXml ().WriteTo (writer);
|
|
} else if (obj is Wss11SignatureConfirmation) {
|
|
Wss11SignatureConfirmation sc = (Wss11SignatureConfirmation) obj;
|
|
writer.WriteStartElement ("k", "SignatureConfirmation", Constants.Wss11Namespace);
|
|
writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, sc.Id);
|
|
writer.WriteAttributeString ("Value", sc.Value);
|
|
writer.WriteEndElement ();
|
|
}
|
|
else
|
|
throw new ArgumentException (String.Format ("Unrecognized header item {0}", obj ?? "(null)"));
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class WsuTimestamp
|
|
{
|
|
string id;
|
|
DateTime created, expires;
|
|
|
|
public string Id {
|
|
get { return id; }
|
|
set { id = value; }
|
|
}
|
|
|
|
public DateTime Created {
|
|
get { return created; }
|
|
set { created = value; }
|
|
}
|
|
|
|
public DateTime Expires {
|
|
get { return expires; }
|
|
set { expires = value; }
|
|
}
|
|
|
|
public void WriteTo (XmlWriter writer)
|
|
{
|
|
writer.WriteStartElement ("u", "Timestamp", Constants.WsuNamespace);
|
|
writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, Id);
|
|
writer.WriteStartElement ("u", "Created", Constants.WsuNamespace);
|
|
writer.WriteValue (FormatAsUtc (Created));
|
|
writer.WriteEndElement ();
|
|
writer.WriteStartElement ("u", "Expires", Constants.WsuNamespace);
|
|
writer.WriteValue (FormatAsUtc (Expires));
|
|
writer.WriteEndElement ();
|
|
writer.WriteEndElement ();
|
|
}
|
|
|
|
string FormatAsUtc (DateTime date)
|
|
{
|
|
return date.ToUniversalTime ().ToString (
|
|
"yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
|
|
CultureInfo.InvariantCulture);
|
|
}
|
|
}
|
|
|
|
internal class SecurityTokenReferenceKeyInfo : KeyInfoClause
|
|
{
|
|
SecurityKeyIdentifierClause clause;
|
|
SecurityTokenSerializer serializer;
|
|
XmlDocument doc;
|
|
|
|
// for LoadXml()
|
|
public SecurityTokenReferenceKeyInfo (
|
|
SecurityTokenSerializer serializer,
|
|
XmlDocument doc)
|
|
: this (null, serializer, doc)
|
|
{
|
|
}
|
|
|
|
// for GetXml()
|
|
public SecurityTokenReferenceKeyInfo (
|
|
SecurityKeyIdentifierClause clause,
|
|
SecurityTokenSerializer serializer,
|
|
XmlDocument doc)
|
|
{
|
|
this.clause = clause;
|
|
this.serializer = serializer;
|
|
if (doc == null)
|
|
doc = new XmlDocument ();
|
|
this.doc = doc;
|
|
}
|
|
|
|
public SecurityKeyIdentifierClause Clause {
|
|
get { return clause; }
|
|
}
|
|
|
|
public override XmlElement GetXml ()
|
|
{
|
|
XmlDocumentFragment df = doc.CreateDocumentFragment ();
|
|
XmlWriter w = df.CreateNavigator ().AppendChild ();
|
|
serializer.WriteKeyIdentifierClause (w, clause);
|
|
w.Close ();
|
|
return (XmlElement) df.FirstChild;
|
|
}
|
|
|
|
public override void LoadXml (XmlElement element)
|
|
{
|
|
clause = serializer.ReadKeyIdentifierClause (new XmlNodeReader (element));
|
|
}
|
|
}
|
|
|
|
internal class Wss11SignatureConfirmation
|
|
{
|
|
string id, value;
|
|
|
|
public Wss11SignatureConfirmation (string id, string value)
|
|
{
|
|
this.id = id;
|
|
this.value = value;
|
|
}
|
|
|
|
public string Id {
|
|
get { return id; }
|
|
set { id = value; }
|
|
}
|
|
|
|
public string Value {
|
|
get { return value; }
|
|
set { this.value = value; }
|
|
}
|
|
}
|
|
}
|