2 // WSSecurityMessageHeader.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
7 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections.Generic;
31 using System.Collections.ObjectModel;
32 using System.Globalization;
34 using System.IdentityModel.Selectors;
35 using System.IdentityModel.Tokens;
36 using System.Runtime.Serialization;
37 using System.Security.Cryptography;
38 using System.Security.Cryptography.Xml;
39 using System.ServiceModel;
40 using System.ServiceModel.Channels;
41 using System.ServiceModel.Dispatcher;
42 using System.ServiceModel.Security;
43 using System.ServiceModel.Security.Tokens;
47 namespace System.ServiceModel.Channels
49 internal class WSSecurityMessageHeaderReader
51 public WSSecurityMessageHeaderReader (WSSecurityMessageHeader header, SecurityTokenSerializer serializer, SecurityTokenResolver resolver, XmlDocument doc, XmlNamespaceManager nsmgr, List<SecurityToken> tokens)
54 this.serializer = serializer;
55 this.resolver = resolver;
61 WSSecurityMessageHeader header;
62 SecurityTokenSerializer serializer;
63 SecurityTokenResolver resolver;
65 XmlNamespaceManager nsmgr;
66 List<SecurityToken> tokens;
67 Dictionary<string, EncryptedData> encryptedDataList =
68 new Dictionary<string, EncryptedData> ();
70 public void ReadContents (XmlReader reader)
72 DerivedKeySecurityToken currentToken = null;
74 reader.MoveToContent ();
75 reader.ReadStartElement ("Security", Constants.WssNamespace);
77 reader.MoveToContent ();
78 if (reader.NodeType == XmlNodeType.EndElement)
80 object o = ReadContent (reader);
81 if (o is EncryptedData) {
82 EncryptedData ed = (EncryptedData) o;
83 encryptedDataList [ed.Id] = ed;
85 else if (o is ReferenceList && currentToken != null)
86 currentToken.ReferenceList = (ReferenceList) o;
87 else if (o is SecurityToken) {
88 if (o is DerivedKeySecurityToken)
89 currentToken = o as DerivedKeySecurityToken;
90 tokens.Add ((SecurityToken) o);
92 header.Contents.Add (o);
94 reader.ReadEndElement ();
97 object ReadContent (XmlReader reader)
99 reader.MoveToContent ();
100 if (reader.NodeType != XmlNodeType.Element)
101 throw new XmlException (String.Format ("Node type {0} is not expected as a WS-Security message header content.", reader.NodeType));
102 switch (reader.NamespaceURI) {
103 case Constants.WsuNamespace:
104 switch (reader.LocalName) {
106 return ReadTimestamp (reader);
109 //case Constants.WstNamespace:
110 case Constants.Wss11Namespace:
111 if (reader.LocalName == "SignatureConfirmation") {
112 return ReadSignatureConfirmation (reader, doc);
115 case SignedXml.XmlDsigNamespaceUrl:
116 switch (reader.LocalName) {
118 WSSignedXml sxml = new WSSignedXml (doc);
119 sxml.Signature.LoadXml ((XmlElement) doc.ReadNode (reader));
120 UpdateSignatureKeyInfo (sxml.Signature, doc, serializer);
124 case EncryptedXml.XmlEncNamespaceUrl:
125 switch (reader.LocalName) {
126 case "EncryptedData":
127 XmlElement el = (XmlElement) doc.ReadNode (reader);
128 return CreateEncryptedData (el);
129 case "ReferenceList":
130 ReferenceList rl = new ReferenceList ();
132 for (reader.MoveToContent ();
133 reader.NodeType != XmlNodeType.EndElement;
134 reader.MoveToContent ()) {
135 switch (reader.LocalName) {
136 case "DataReference":
137 DataReference dref = new DataReference ();
138 dref.LoadXml ((XmlElement) doc.ReadNode (reader));
142 KeyReference kref = new KeyReference ();
143 kref.LoadXml ((XmlElement) doc.ReadNode (reader));
147 throw new XmlException (String.Format ("Unexpected {2} node '{0}' in namespace '{1}' in ReferenceList.", reader.Name, reader.NamespaceURI, reader.NodeType));
149 reader.ReadEndElement ();
154 // SecurityTokenReference will be handled here.
155 // This order (Token->KeyIdentifierClause) is
156 // important because WrappedKey could be read
157 // in both context (but must be a token here).
158 if (serializer.CanReadToken (reader))
159 return serializer.ReadToken (reader, resolver);
160 else if (serializer.CanReadKeyIdentifierClause (reader))
161 return serializer.ReadKeyIdentifierClause (reader);
163 throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
166 void UpdateSignatureKeyInfo (Signature sig, XmlDocument doc, SecurityTokenSerializer serializer)
168 KeyInfo ki = new KeyInfo ();
169 ki.Id = sig.KeyInfo.Id;
170 foreach (KeyInfoClause kic in sig.KeyInfo) {
171 SecurityTokenReferenceKeyInfo r = new SecurityTokenReferenceKeyInfo (serializer, doc);
172 r.LoadXml (kic.GetXml ());
180 // returns the protection token
181 public void DecryptSecurity (SecureMessageDecryptor decryptor, SymmetricSecurityKey sym, byte [] dummyEncKey)
183 WSEncryptedXml encXml = new WSEncryptedXml (doc);
185 // default, unless overriden by the default DerivedKeyToken.
186 Rijndael aes = RijndaelManaged.Create (); // it is reused with every key
187 aes.Mode = CipherMode.CBC;
190 throw new MessageSecurityException ("Cannot find the encryption key in this message and context");
192 // decrypt the body with the decrypted key
193 Collection<string> references = new Collection<string> ();
195 foreach (ReferenceList rlist in header.FindAll<ReferenceList> ())
196 foreach (EncryptedReference encref in rlist)
197 references.Add (StripUri (encref.Uri));
199 foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ())
200 foreach (EncryptedReference er in wk.ReferenceList)
201 references.Add (StripUri (er.Uri));
203 Collection<XmlElement> list = new Collection<XmlElement> ();
204 foreach (string uri in references) {
205 XmlElement el = encXml.GetIdElement (doc, uri);
209 throw new MessageSecurityException (String.Format ("On decryption, EncryptedData with Id '{0}', referenced by ReferenceData, was not found.", uri));
212 foreach (XmlElement el in list) {
213 EncryptedData ed2 = CreateEncryptedData (el);
214 byte [] key = GetEncryptionKeyForData (ed2, encXml, dummyEncKey);
215 aes.Key = key != null ? key : sym.GetSymmetricKey ();
216 byte [] decrypted = DecryptData (encXml, ed2, aes);
217 encXml.ReplaceData (el, decrypted);
218 EncryptedData existing;
219 // if it was a header content, replace
220 // corresponding one.
221 if (encryptedDataList.TryGetValue (ed2.Id, out existing)) {
222 // FIXME: it is kind of extraneous and could be replaced by XmlNodeReader
223 //Console.WriteLine ("DECRYPTED EncryptedData:");
224 //Console.WriteLine (Encoding.UTF8.GetString (decrypted));
225 object o = ReadContent (XmlReader.Create (new MemoryStream (decrypted)));
226 header.Contents.Remove (existing);
227 header.Contents.Add (o);
231 Console.WriteLine ("======== Decrypted Document ========");
232 doc.PreserveWhitespace = false;
233 doc.Save (Console.Out);
234 doc.PreserveWhitespace = true;
238 EncryptedData CreateEncryptedData (XmlElement el)
240 EncryptedData ed = new EncryptedData ();
243 ed.Id = el.GetAttribute ("Id", Constants.WsuNamespace);
247 byte [] GetEncryptionKeyForData (EncryptedData ed2, EncryptedXml encXml, byte [] dummyEncKey)
249 // Since ReferenceList could be embedded directly in wss_header without
250 // key indication, it must iterate all the derived keys to find out
252 foreach (DerivedKeySecurityToken dk in header.FindAll<DerivedKeySecurityToken> ()) {
253 if (dk.ReferenceList == null)
255 foreach (DataReference dr in dk.ReferenceList)
256 if (StripUri (dr.Uri) == ed2.Id)
257 return ((SymmetricSecurityKey) dk.SecurityKeys [0]).GetSymmetricKey ();
259 foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ()) {
260 if (wk.ReferenceList == null)
262 foreach (DataReference dr in wk.ReferenceList)
263 if (StripUri (dr.Uri) == ed2.Id)
264 return ((SymmetricSecurityKey) wk.SecurityKeys [0]).GetSymmetricKey ();
267 if (ed2.KeyInfo == null)
269 foreach (KeyInfoClause kic in ed2.KeyInfo) {
270 SecurityKeyIdentifierClause skic = serializer.ReadKeyIdentifierClause (new XmlNodeReader (kic.GetXml ()));
272 SecurityKey skey = null;
273 if (!resolver.TryResolveSecurityKey (skic, out skey))
274 throw new MessageSecurityException (String.Format ("The signing key could not be resolved from {0}", skic));
275 SymmetricSecurityKey ssk = skey as SymmetricSecurityKey;
277 return ssk.GetSymmetricKey ();
279 return null; // no applicable key info clause.
282 // Probably it is a bug in .NET, but sometimes it does not contain
283 // proper padding bytes. For such cases, use PaddingMode.None
284 // instead. It must not be done in EncryptedXml class as it
285 // correctly rejects improper ISO10126 padding.
286 byte [] DecryptData (EncryptedXml encXml, EncryptedData ed, SymmetricAlgorithm symAlg)
288 PaddingMode bak = symAlg.Padding;
290 byte [] bytes = ed.CipherData.CipherValue;
292 if (encXml.Padding != PaddingMode.None &&
293 encXml.Padding != PaddingMode.Zeros &&
294 bytes [bytes.Length - 1] > symAlg.BlockSize / 8)
295 symAlg.Padding = PaddingMode.None;
296 return encXml.DecryptData (ed, symAlg);
298 symAlg.Padding = bak;
302 string StripUri (string src)
304 if (src == null || src.Length == 0)
307 throw new NotSupportedException (String.Format ("Non-fragment URI in DataReference and KeyReference is not supported: '{0}'", src));
308 return src.Substring (1);
312 static Wss11SignatureConfirmation ReadSignatureConfirmation (XmlReader reader, XmlDocument doc)
314 string id = reader.GetAttribute ("Id", Constants.WsuNamespace);
315 string value = reader.GetAttribute ("Value");
317 return new Wss11SignatureConfirmation (id, value);
320 static WsuTimestamp ReadTimestamp (XmlReader reader)
322 WsuTimestamp ret = new WsuTimestamp ();
323 ret.Id = reader.GetAttribute ("Id", Constants.WsuNamespace);
324 reader.ReadStartElement ();
326 reader.MoveToContent ();
327 if (reader.NodeType == XmlNodeType.EndElement)
329 if (reader.NodeType != XmlNodeType.Element)
330 throw new XmlException (String.Format ("Node type {0} is not expected as a WS-Security 'Timestamp' content.", reader.NodeType));
331 switch (reader.NamespaceURI) {
332 case Constants.WsuNamespace:
333 switch (reader.LocalName) {
335 ret.Created = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
338 ret.Expires = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
343 throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
346 reader.ReadEndElement (); // </u:Timestamp>
351 internal class WSSecurityMessageHeader : MessageHeader
353 public WSSecurityMessageHeader (SecurityTokenSerializer serializer)
355 this.serializer = serializer;
358 SecurityTokenSerializer serializer;
359 Collection<object> contents = new Collection<object> ();
361 // Timestamp, BinarySecurityToken, EncryptedKey,
362 // [DerivedKeyToken]*, ReferenceList, EncryptedData
363 public Collection<object> Contents {
364 get { return contents; }
367 public override bool MustUnderstand {
371 public override string Name {
372 get { return "Security"; }
375 public override string Namespace {
376 get { return Constants.WssNamespace; }
379 public void AddContent (object obj)
382 throw new ArgumentNullException ("obj");
388 foreach (object o in Contents)
389 if (typeof (T).IsAssignableFrom (o.GetType ()))
394 public Collection<T> FindAll<T> ()
396 Collection<T> c = new Collection<T> ();
397 foreach (object o in Contents)
398 if (typeof (T).IsAssignableFrom (o.GetType ()))
403 protected override void OnWriteStartHeader (XmlDictionaryWriter writer, MessageVersion version)
405 writer.WriteStartElement ("o", this.Name, this.Namespace);
406 WriteHeaderAttributes (writer, version);
409 protected override void OnWriteHeaderContents (XmlDictionaryWriter writer, MessageVersion version)
411 // 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).
413 foreach (object obj in Contents) {
414 if (obj is WsuTimestamp) {
415 WsuTimestamp ts = (WsuTimestamp) obj;
417 } else if (obj is SecurityToken) {
418 serializer.WriteToken (writer, (SecurityToken) obj);
419 } else if (obj is EncryptedKey) {
420 ((EncryptedKey) obj).GetXml ().WriteTo (writer);
421 } else if (obj is ReferenceList) {
422 writer.WriteStartElement ("ReferenceList", EncryptedXml.XmlEncNamespaceUrl);
423 foreach (EncryptedReference er in (ReferenceList) obj)
424 er.GetXml ().WriteTo (writer);
425 writer.WriteEndElement ();
426 } else if (obj is EncryptedData) {
427 ((EncryptedData) obj).GetXml ().WriteTo (writer);
428 } else if (obj is Signature) {
429 ((Signature) obj).GetXml ().WriteTo (writer);
430 } else if (obj is Wss11SignatureConfirmation) {
431 Wss11SignatureConfirmation sc = (Wss11SignatureConfirmation) obj;
432 writer.WriteStartElement ("k", "SignatureConfirmation", Constants.Wss11Namespace);
433 writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, sc.Id);
434 writer.WriteAttributeString ("Value", sc.Value);
435 writer.WriteEndElement ();
438 throw new ArgumentException (String.Format ("Unrecognized header item {0}", obj ?? "(null)"));
443 internal class WsuTimestamp
446 DateTime created, expires;
453 public DateTime Created {
454 get { return created; }
455 set { created = value; }
458 public DateTime Expires {
459 get { return expires; }
460 set { expires = value; }
463 public void WriteTo (XmlWriter writer)
465 writer.WriteStartElement ("u", "Timestamp", Constants.WsuNamespace);
466 writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, Id);
467 writer.WriteStartElement ("u", "Created", Constants.WsuNamespace);
468 writer.WriteValue (FormatAsUtc (Created));
469 writer.WriteEndElement ();
470 writer.WriteStartElement ("u", "Expires", Constants.WsuNamespace);
471 writer.WriteValue (FormatAsUtc (Expires));
472 writer.WriteEndElement ();
473 writer.WriteEndElement ();
476 string FormatAsUtc (DateTime date)
478 return date.ToUniversalTime ().ToString (
479 "yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
480 CultureInfo.InvariantCulture);
484 internal class SecurityTokenReferenceKeyInfo : KeyInfoClause
486 SecurityKeyIdentifierClause clause;
487 SecurityTokenSerializer serializer;
491 public SecurityTokenReferenceKeyInfo (
492 SecurityTokenSerializer serializer,
494 : this (null, serializer, doc)
499 public SecurityTokenReferenceKeyInfo (
500 SecurityKeyIdentifierClause clause,
501 SecurityTokenSerializer serializer,
504 this.clause = clause;
505 this.serializer = serializer;
507 doc = new XmlDocument ();
511 public SecurityKeyIdentifierClause Clause {
512 get { return clause; }
515 public override XmlElement GetXml ()
517 XmlDocumentFragment df = doc.CreateDocumentFragment ();
518 XmlWriter w = df.CreateNavigator ().AppendChild ();
519 serializer.WriteKeyIdentifierClause (w, clause);
521 return (XmlElement) df.FirstChild;
524 public override void LoadXml (XmlElement element)
526 clause = serializer.ReadKeyIdentifierClause (new XmlNodeReader (element));
530 internal class Wss11SignatureConfirmation
534 public Wss11SignatureConfirmation (string id, string value)
545 public string Value {
546 get { return value; }
547 set { this.value = value; }