2010-03-12 Jb Evain <jbevain@novell.com>
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / WSSecurityMessageHeader.cs
1 //
2 // WSSecurityMessageHeader.cs
3 //
4 // Author:
5 //      Atsushi Enomoto  <atsushi@ximian.com>
6 //
7 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
8 //
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:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
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.
27 //
28
29 using System;
30 using System.Collections.Generic;
31 using System.Collections.ObjectModel;
32 using System.Globalization;
33 using System.IO;
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;
44 using System.Text;
45 using System.Xml;
46
47 namespace System.ServiceModel.Channels
48 {
49         internal class WSSecurityMessageHeaderReader
50         {
51                 public WSSecurityMessageHeaderReader (WSSecurityMessageHeader header, SecurityTokenSerializer serializer, SecurityTokenResolver resolver, XmlDocument doc, XmlNamespaceManager nsmgr, List<SecurityToken> tokens)
52                 {
53                         this.header = header;
54                         this.serializer = serializer;
55                         this.resolver = resolver;
56                         this.doc = doc;
57                         this.nsmgr = nsmgr;
58                         this.tokens = tokens;
59                 }
60
61                 WSSecurityMessageHeader header;
62                 SecurityTokenSerializer serializer;
63                 SecurityTokenResolver resolver;
64                 XmlDocument doc;
65                 XmlNamespaceManager nsmgr;
66                 List<SecurityToken> tokens;
67                 Dictionary<string, EncryptedData> encryptedDataList =
68                         new Dictionary<string, EncryptedData> ();
69
70                 public void ReadContents (XmlReader reader)
71                 {
72                         DerivedKeySecurityToken currentToken = null;
73
74                         reader.MoveToContent ();
75                         reader.ReadStartElement ("Security", Constants.WssNamespace);
76                         do {
77                                 reader.MoveToContent ();
78                                 if (reader.NodeType == XmlNodeType.EndElement)
79                                         break;
80                                 object o = ReadContent (reader);
81                                 if (o is EncryptedData) {
82                                         EncryptedData ed = (EncryptedData) o;
83                                         encryptedDataList [ed.Id] = ed;
84                                 }
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);
91                                 }
92                                 header.Contents.Add (o);
93                         } while (true);
94                         reader.ReadEndElement ();
95                 }
96
97                 object ReadContent (XmlReader reader)
98                 {
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) {
105                                 case "Timestamp":
106                                         return ReadTimestamp (reader);
107                                 }
108                                 break;
109                         //case Constants.WstNamespace:
110                         case Constants.Wss11Namespace:
111                                 if (reader.LocalName == "SignatureConfirmation") {
112                                         return ReadSignatureConfirmation (reader, doc);
113                                 }
114                                 break;
115                         case SignedXml.XmlDsigNamespaceUrl:
116                                 switch (reader.LocalName) {
117                                 case "Signature":
118                                         WSSignedXml sxml = new WSSignedXml (doc);
119                                         sxml.Signature.LoadXml ((XmlElement) doc.ReadNode (reader));
120                                         UpdateSignatureKeyInfo (sxml.Signature, doc, serializer);
121                                         return sxml;
122                                 }
123                                 break;
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 ();
131                                         reader.Read ();
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));
139                                                         rl.Add (dref);
140                                                         continue;
141                                                 case "KeyReference":
142                                                         KeyReference kref = new KeyReference ();
143                                                         kref.LoadXml ((XmlElement) doc.ReadNode (reader));
144                                                         rl.Add (kref);
145                                                         continue;
146                                                 }
147                                                 throw new XmlException (String.Format ("Unexpected {2} node '{0}' in namespace '{1}' in ReferenceList.", reader.Name, reader.NamespaceURI, reader.NodeType));
148                                         }
149                                         reader.ReadEndElement ();
150                                         return rl;
151                                 }
152                                 break;
153                         }
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);
162                         else
163                                 throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
164                 }
165
166                 void UpdateSignatureKeyInfo (Signature sig, XmlDocument doc, SecurityTokenSerializer serializer)
167                 {
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 ());
173                                 ki.AddClause (r);
174                         }
175                         sig.KeyInfo = ki;
176                 }
177
178                 #region Decryption
179
180                 // returns the protection token
181                 public void DecryptSecurity (SecureMessageDecryptor decryptor, SymmetricSecurityKey sym, byte [] dummyEncKey)
182                 {
183                         WSEncryptedXml encXml = new WSEncryptedXml (doc);
184
185                         // default, unless overriden by the default DerivedKeyToken.
186                         Rijndael aes = RijndaelManaged.Create (); // it is reused with every key
187                         aes.Mode = CipherMode.CBC;
188
189                         if (sym == null)
190                                 throw new MessageSecurityException ("Cannot find the encryption key in this message and context");
191
192                         // decrypt the body with the decrypted key
193                         Collection<string> references = new Collection<string> ();
194
195                         foreach (ReferenceList rlist in header.FindAll<ReferenceList> ())
196                                 foreach (EncryptedReference encref in rlist)
197                                         references.Add (StripUri (encref.Uri));
198
199                         foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ())
200                                 foreach (EncryptedReference er in wk.ReferenceList)
201                                         references.Add (StripUri (er.Uri));
202
203                         Collection<XmlElement> list = new Collection<XmlElement> ();
204                         foreach (string uri in references) {
205                                 XmlElement el = encXml.GetIdElement (doc, uri);
206                                 if (el != null)
207                                         list.Add (el);
208                                 else
209                                         throw new MessageSecurityException (String.Format ("On decryption, EncryptedData with Id '{0}', referenced by ReferenceData, was not found.", uri));
210                         }
211
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);
228                                 }
229                         }
230 /*
231 Console.WriteLine ("======== Decrypted Document ========");
232 doc.PreserveWhitespace = false;
233 doc.Save (Console.Out);
234 doc.PreserveWhitespace = true;
235 */
236                 }
237
238                 EncryptedData CreateEncryptedData (XmlElement el)
239                 {
240                         EncryptedData ed = new EncryptedData ();
241                         ed.LoadXml (el);
242                         if (ed.Id == null)
243                                 ed.Id = el.GetAttribute ("Id", Constants.WsuNamespace);
244                         return ed;
245                 }
246
247                 byte [] GetEncryptionKeyForData (EncryptedData ed2, EncryptedXml encXml, byte [] dummyEncKey)
248                 {
249                         // Since ReferenceList could be embedded directly in wss_header without
250                         // key indication, it must iterate all the derived keys to find out
251                         // appropriate one.
252                         foreach (DerivedKeySecurityToken dk in header.FindAll<DerivedKeySecurityToken> ()) {
253                                 if (dk.ReferenceList == null)
254                                         continue;
255                                 foreach (DataReference dr in dk.ReferenceList)
256                                         if (StripUri (dr.Uri) == ed2.Id)
257                                                 return ((SymmetricSecurityKey) dk.SecurityKeys [0]).GetSymmetricKey ();
258                         }
259                         foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ()) {
260                                 if (wk.ReferenceList == null)
261                                         continue;
262                                 foreach (DataReference dr in wk.ReferenceList)
263                                         if (StripUri (dr.Uri) == ed2.Id)
264                                                 return ((SymmetricSecurityKey) wk.SecurityKeys [0]).GetSymmetricKey ();
265                         }
266
267                         if (ed2.KeyInfo == null)
268                                 return null;
269                         foreach (KeyInfoClause kic in ed2.KeyInfo) {
270                                 SecurityKeyIdentifierClause skic = serializer.ReadKeyIdentifierClause (new XmlNodeReader (kic.GetXml ()));
271
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;
276                                 if (ssk != null)
277                                         return ssk.GetSymmetricKey ();
278                         }
279                         return null; // no applicable key info clause.
280                 }
281
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)
287                 {
288                         PaddingMode bak = symAlg.Padding;
289                         try {
290                                 byte [] bytes = ed.CipherData.CipherValue;
291
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);
297                         } finally {
298                                 symAlg.Padding = bak;
299                         }
300                 }
301
302                 string StripUri (string src)
303                 {
304                         if (src == null || src.Length == 0)
305                                 return String.Empty;
306                         if (src [0] != '#')
307                                 throw new NotSupportedException (String.Format ("Non-fragment URI in DataReference and KeyReference is not supported: '{0}'", src));
308                         return src.Substring (1);
309                 }
310                 #endregion
311
312                 static Wss11SignatureConfirmation ReadSignatureConfirmation (XmlReader reader, XmlDocument doc)
313                 {
314                         string id = reader.GetAttribute ("Id", Constants.WsuNamespace);
315                         string value = reader.GetAttribute ("Value");
316                         reader.Skip ();
317                         return new Wss11SignatureConfirmation (id, value);
318                 }
319
320                 static WsuTimestamp ReadTimestamp (XmlReader reader)
321                 {
322                         WsuTimestamp ret = new WsuTimestamp ();
323                         ret.Id = reader.GetAttribute ("Id", Constants.WsuNamespace);
324                         reader.ReadStartElement ();
325                         do {
326                                 reader.MoveToContent ();
327                                 if (reader.NodeType == XmlNodeType.EndElement)
328                                         break;
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) {
334                                         case "Created":
335                                                 ret.Created = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
336                                                 continue;
337                                         case "Expires":
338                                                 ret.Expires = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
339                                                 continue;
340                                         }
341                                         break;
342                                 }
343                                 throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
344                         } while (true);
345
346                         reader.ReadEndElement (); // </u:Timestamp>
347                         return ret;
348                 }
349         }
350
351         internal class WSSecurityMessageHeader : MessageHeader
352         {
353                 public WSSecurityMessageHeader (SecurityTokenSerializer serializer)
354                 {
355                         this.serializer = serializer;
356                 }
357
358                 SecurityTokenSerializer serializer;
359                 Collection<object> contents = new Collection<object> ();
360
361                 // Timestamp, BinarySecurityToken, EncryptedKey,
362                 // [DerivedKeyToken]*, ReferenceList, EncryptedData
363                 public Collection<object> Contents {
364                         get { return contents; }
365                 }
366
367                 public override bool MustUnderstand {
368                         get { return true; }
369                 }
370
371                 public override string Name {
372                         get { return "Security"; }
373                 }
374
375                 public override string Namespace {
376                         get { return Constants.WssNamespace; }
377                 }
378
379                 public void AddContent (object obj)
380                 {
381                         if (obj == null)
382                                 throw new ArgumentNullException ("obj");
383                         Contents.Add (obj);
384                 }
385
386                 public T Find<T> ()
387                 {
388                         foreach (object o in Contents)
389                                 if (typeof (T).IsAssignableFrom (o.GetType ()))
390                                         return (T) o;
391                         return default (T);
392                 }
393
394                 public Collection<T> FindAll<T> ()
395                 {
396                         Collection<T> c = new Collection<T> ();
397                         foreach (object o in Contents)
398                                 if (typeof (T).IsAssignableFrom (o.GetType ()))
399                                         c.Add ((T) o);
400                         return c;
401                 }
402
403                 protected override void OnWriteStartHeader (XmlDictionaryWriter writer, MessageVersion version)
404                 {
405                         writer.WriteStartElement ("o", this.Name, this.Namespace);
406                         WriteHeaderAttributes (writer, version);
407                 }
408
409                 protected override void OnWriteHeaderContents (XmlDictionaryWriter writer, MessageVersion version)
410                 {
411                         foreach (object obj in Contents) {
412                                 if (obj is WsuTimestamp) {
413                                         WsuTimestamp ts = (WsuTimestamp) obj;
414                                         ts.WriteTo (writer);
415                                 } else if (obj is SecurityToken) {
416                                         serializer.WriteToken (writer, (SecurityToken) obj);
417                                 } else if (obj is EncryptedKey) {
418                                         ((EncryptedKey) obj).GetXml ().WriteTo (writer);
419                                 } else if (obj is ReferenceList) {
420                                         writer.WriteStartElement ("ReferenceList", EncryptedXml.XmlEncNamespaceUrl);
421                                         foreach (EncryptedReference er in (ReferenceList) obj)
422                                                 er.GetXml ().WriteTo (writer);
423                                         writer.WriteEndElement ();
424                                 } else if (obj is EncryptedData) {
425                                         ((EncryptedData) obj).GetXml ().WriteTo (writer);
426                                 } else if (obj is Signature) {
427                                         ((Signature) obj).GetXml ().WriteTo (writer);
428                                 } else if (obj is Wss11SignatureConfirmation) {
429                                         Wss11SignatureConfirmation sc = (Wss11SignatureConfirmation) obj;
430                                         writer.WriteStartElement ("k", "SignatureConfirmation", Constants.Wss11Namespace);
431                                         writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, sc.Id);
432                                         writer.WriteAttributeString ("Value", sc.Value);
433                                         writer.WriteEndElement ();
434                                 }
435                                 else
436                                         throw new ArgumentException (String.Format ("Unrecognized header item {0}", obj ?? "(null)"));
437                         }
438                 }
439         }
440
441         internal class WsuTimestamp
442         {
443                 string id;
444                 DateTime created, expires;
445
446                 public string Id {
447                         get { return id; }
448                         set { id = value; }
449                 }
450
451                 public DateTime Created {
452                         get { return created; }
453                         set { created = value; }
454                 }
455
456                 public DateTime Expires {
457                         get { return expires; }
458                         set { expires = value; }
459                 }
460
461                 public void WriteTo (XmlWriter writer)
462                 {
463                         writer.WriteStartElement ("u", "Timestamp", Constants.WsuNamespace);
464                         writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, Id);
465                         writer.WriteStartElement ("u", "Created", Constants.WsuNamespace);
466                         writer.WriteValue (FormatAsUtc (Created));
467                         writer.WriteEndElement ();
468                         writer.WriteStartElement ("u", "Expires", Constants.WsuNamespace);
469                         writer.WriteValue (FormatAsUtc (Expires));
470                         writer.WriteEndElement ();
471                         writer.WriteEndElement ();
472                 }
473
474                 string FormatAsUtc (DateTime date)
475                 {
476                         return date.ToUniversalTime ().ToString (
477                                 "yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
478                                 CultureInfo.InvariantCulture);
479                 }
480         }
481
482         internal class SecurityTokenReferenceKeyInfo : KeyInfoClause
483         {
484                 SecurityKeyIdentifierClause clause;
485                 SecurityTokenSerializer serializer;
486                 XmlDocument doc;
487
488                 // for LoadXml()
489                 public SecurityTokenReferenceKeyInfo (
490                         SecurityTokenSerializer serializer,
491                         XmlDocument doc)
492                         : this (null, serializer, doc)
493                 {
494                 }
495
496                 // for GetXml()
497                 public SecurityTokenReferenceKeyInfo (
498                         SecurityKeyIdentifierClause clause,
499                         SecurityTokenSerializer serializer,
500                         XmlDocument doc)
501                 {
502                         this.clause = clause;
503                         this.serializer = serializer;
504                         if (doc == null)
505                                 doc = new XmlDocument ();
506                         this.doc = doc;
507                 }
508
509                 public SecurityKeyIdentifierClause Clause {
510                         get { return clause; }
511                 }
512
513                 public override XmlElement GetXml ()
514                 {
515                         XmlDocumentFragment df = doc.CreateDocumentFragment ();
516                         XmlWriter w = df.CreateNavigator ().AppendChild ();
517                         serializer.WriteKeyIdentifierClause (w, clause);
518                         w.Close ();
519                         return (XmlElement) df.FirstChild;
520                 }
521
522                 public override void LoadXml (XmlElement element)
523                 {
524                         clause = serializer.ReadKeyIdentifierClause (new XmlNodeReader (element));
525                 }
526         }
527
528         internal class Wss11SignatureConfirmation
529         {
530                 string id, value;
531
532                 public Wss11SignatureConfirmation (string id, string value)
533                 {
534                         this.id = id;
535                         this.value = value;
536                 }
537
538                 public string Id {
539                         get { return id; }
540                         set { id = value; }
541                 }
542
543                 public string Value {
544                         get { return value; }
545                         set { this.value = value; }
546                 }
547         }
548 }