WSSecurityMessageHeader.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. //
  2. // WSSecurityMessageHeader.cs
  3. //
  4. // Author:
  5. // Atsushi Enomoto <[email protected]>
  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. using System;
  29. using System.Collections.Generic;
  30. using System.Collections.ObjectModel;
  31. using System.Globalization;
  32. using System.IO;
  33. using System.IdentityModel.Selectors;
  34. using System.IdentityModel.Tokens;
  35. using System.Runtime.Serialization;
  36. using System.Security.Cryptography;
  37. using System.Security.Cryptography.Xml;
  38. using System.ServiceModel;
  39. using System.ServiceModel.Channels;
  40. using System.ServiceModel.Dispatcher;
  41. using System.ServiceModel.Security;
  42. using System.ServiceModel.Security.Tokens;
  43. using System.Text;
  44. using System.Xml;
  45. namespace System.ServiceModel.Channels.Security
  46. {
  47. internal class WSSecurityMessageHeaderReader
  48. {
  49. public WSSecurityMessageHeaderReader (WSSecurityMessageHeader header, SecurityTokenSerializer serializer, SecurityTokenResolver resolver, XmlDocument doc, XmlNamespaceManager nsmgr, List<SecurityToken> tokens)
  50. {
  51. this.header = header;
  52. this.serializer = serializer;
  53. this.resolver = resolver;
  54. this.doc = doc;
  55. this.nsmgr = nsmgr;
  56. this.tokens = tokens;
  57. }
  58. WSSecurityMessageHeader header;
  59. SecurityTokenSerializer serializer;
  60. SecurityTokenResolver resolver;
  61. XmlDocument doc;
  62. XmlNamespaceManager nsmgr;
  63. List<SecurityToken> tokens;
  64. Dictionary<string, EncryptedData> encryptedDataList =
  65. new Dictionary<string, EncryptedData> ();
  66. public void ReadContents (XmlReader reader)
  67. {
  68. DerivedKeySecurityToken currentToken = null;
  69. reader.MoveToContent ();
  70. reader.ReadStartElement ("Security", Constants.WssNamespace);
  71. do {
  72. reader.MoveToContent ();
  73. if (reader.NodeType == XmlNodeType.EndElement)
  74. break;
  75. object o = ReadContent (reader);
  76. if (o is EncryptedData) {
  77. EncryptedData ed = (EncryptedData) o;
  78. encryptedDataList [ed.Id] = ed;
  79. }
  80. else if (o is ReferenceList && currentToken != null)
  81. currentToken.ReferenceList = (ReferenceList) o;
  82. else if (o is SecurityToken) {
  83. if (o is DerivedKeySecurityToken)
  84. currentToken = o as DerivedKeySecurityToken;
  85. tokens.Add ((SecurityToken) o);
  86. }
  87. header.Contents.Add (o);
  88. } while (true);
  89. reader.ReadEndElement ();
  90. }
  91. object ReadContent (XmlReader reader)
  92. {
  93. reader.MoveToContent ();
  94. if (reader.NodeType != XmlNodeType.Element)
  95. throw new XmlException (String.Format ("Node type {0} is not expected as a WS-Security message header content.", reader.NodeType));
  96. switch (reader.NamespaceURI) {
  97. case Constants.WsuNamespace:
  98. switch (reader.LocalName) {
  99. case "Timestamp":
  100. return ReadTimestamp (reader);
  101. }
  102. break;
  103. //case Constants.WstNamespace:
  104. case Constants.Wss11Namespace:
  105. if (reader.LocalName == "SignatureConfirmation") {
  106. return ReadSignatureConfirmation (reader, doc);
  107. }
  108. break;
  109. case SignedXml.XmlDsigNamespaceUrl:
  110. switch (reader.LocalName) {
  111. case "Signature":
  112. WSSignedXml sxml = new WSSignedXml (doc);
  113. sxml.Signature.LoadXml ((XmlElement) doc.ReadNode (reader));
  114. UpdateSignatureKeyInfo (sxml.Signature, doc, serializer);
  115. return sxml;
  116. }
  117. break;
  118. case EncryptedXml.XmlEncNamespaceUrl:
  119. switch (reader.LocalName) {
  120. case "EncryptedData":
  121. XmlElement el = (XmlElement) doc.ReadNode (reader);
  122. return CreateEncryptedData (el);
  123. case "ReferenceList":
  124. ReferenceList rl = new ReferenceList ();
  125. reader.Read ();
  126. for (reader.MoveToContent ();
  127. reader.NodeType != XmlNodeType.EndElement;
  128. reader.MoveToContent ()) {
  129. switch (reader.LocalName) {
  130. case "DataReference":
  131. DataReference dref = new DataReference ();
  132. dref.LoadXml ((XmlElement) doc.ReadNode (reader));
  133. rl.Add (dref);
  134. continue;
  135. case "KeyReference":
  136. KeyReference kref = new KeyReference ();
  137. kref.LoadXml ((XmlElement) doc.ReadNode (reader));
  138. rl.Add (kref);
  139. continue;
  140. }
  141. throw new XmlException (String.Format ("Unexpected {2} node '{0}' in namespace '{1}' in ReferenceList.", reader.Name, reader.NamespaceURI, reader.NodeType));
  142. }
  143. reader.ReadEndElement ();
  144. return rl;
  145. }
  146. break;
  147. }
  148. // SecurityTokenReference will be handled here.
  149. // This order (Token->KeyIdentifierClause) is
  150. // important because WrappedKey could be read
  151. // in both context (but must be a token here).
  152. if (serializer.CanReadToken (reader))
  153. return serializer.ReadToken (reader, resolver);
  154. else if (serializer.CanReadKeyIdentifierClause (reader))
  155. return serializer.ReadKeyIdentifierClause (reader);
  156. else
  157. throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
  158. }
  159. void UpdateSignatureKeyInfo (Signature sig, XmlDocument doc, SecurityTokenSerializer serializer)
  160. {
  161. KeyInfo ki = new KeyInfo ();
  162. ki.Id = sig.KeyInfo.Id;
  163. foreach (KeyInfoClause kic in sig.KeyInfo) {
  164. SecurityTokenReferenceKeyInfo r = new SecurityTokenReferenceKeyInfo (serializer, doc);
  165. r.LoadXml (kic.GetXml ());
  166. ki.AddClause (r);
  167. }
  168. sig.KeyInfo = ki;
  169. }
  170. #region Decryption
  171. // returns the protection token
  172. public void DecryptSecurity (SecureMessageDecryptor decryptor, SymmetricSecurityKey sym, byte [] dummyEncKey)
  173. {
  174. WSEncryptedXml encXml = new WSEncryptedXml (doc);
  175. // default, unless overriden by the default DerivedKeyToken.
  176. Rijndael aes = RijndaelManaged.Create (); // it is reused with every key
  177. aes.Mode = CipherMode.CBC;
  178. if (sym == null)
  179. throw new MessageSecurityException ("Cannot find the encryption key in this message and context");
  180. // decrypt the body with the decrypted key
  181. Collection<string> references = new Collection<string> ();
  182. foreach (ReferenceList rlist in header.FindAll<ReferenceList> ())
  183. foreach (EncryptedReference encref in rlist)
  184. references.Add (StripUri (encref.Uri));
  185. foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ())
  186. foreach (EncryptedReference er in wk.ReferenceList)
  187. references.Add (StripUri (er.Uri));
  188. Collection<XmlElement> list = new Collection<XmlElement> ();
  189. foreach (string uri in references) {
  190. XmlElement el = encXml.GetIdElement (doc, uri);
  191. if (el != null)
  192. list.Add (el);
  193. else
  194. throw new MessageSecurityException (String.Format ("On decryption, EncryptedData with Id '{0}', referenced by ReferenceData, was not found.", uri));
  195. }
  196. foreach (XmlElement el in list) {
  197. EncryptedData ed2 = CreateEncryptedData (el);
  198. byte [] key = GetEncryptionKeyForData (ed2, encXml, dummyEncKey);
  199. aes.Key = key != null ? key : sym.GetSymmetricKey ();
  200. byte [] decrypted = DecryptData (encXml, ed2, aes);
  201. encXml.ReplaceData (el, decrypted);
  202. EncryptedData existing;
  203. // if it was a header content, replace
  204. // corresponding one.
  205. if (encryptedDataList.TryGetValue (ed2.Id, out existing)) {
  206. // FIXME: it is kind of extraneous and could be replaced by XmlNodeReader
  207. //Console.WriteLine ("DECRYPTED EncryptedData:");
  208. //Console.WriteLine (Encoding.UTF8.GetString (decrypted));
  209. object o = ReadContent (XmlReader.Create (new MemoryStream (decrypted)));
  210. header.Contents.Remove (existing);
  211. header.Contents.Add (o);
  212. }
  213. }
  214. /*
  215. Console.WriteLine ("======== Decrypted Document ========");
  216. doc.PreserveWhitespace = false;
  217. doc.Save (Console.Out);
  218. doc.PreserveWhitespace = true;
  219. */
  220. }
  221. EncryptedData CreateEncryptedData (XmlElement el)
  222. {
  223. EncryptedData ed = new EncryptedData ();
  224. ed.LoadXml (el);
  225. if (ed.Id == null)
  226. ed.Id = el.GetAttribute ("Id", Constants.WsuNamespace);
  227. return ed;
  228. }
  229. byte [] GetEncryptionKeyForData (EncryptedData ed2, EncryptedXml encXml, byte [] dummyEncKey)
  230. {
  231. // Since ReferenceList could be embedded directly in wss_header without
  232. // key indication, it must iterate all the derived keys to find out
  233. // appropriate one.
  234. foreach (DerivedKeySecurityToken dk in header.FindAll<DerivedKeySecurityToken> ()) {
  235. if (dk.ReferenceList == null)
  236. continue;
  237. foreach (DataReference dr in dk.ReferenceList)
  238. if (StripUri (dr.Uri) == ed2.Id)
  239. return ((SymmetricSecurityKey) dk.SecurityKeys [0]).GetSymmetricKey ();
  240. }
  241. foreach (WrappedKeySecurityToken wk in header.FindAll<WrappedKeySecurityToken> ()) {
  242. if (wk.ReferenceList == null)
  243. continue;
  244. foreach (DataReference dr in wk.ReferenceList)
  245. if (StripUri (dr.Uri) == ed2.Id)
  246. return ((SymmetricSecurityKey) wk.SecurityKeys [0]).GetSymmetricKey ();
  247. }
  248. if (ed2.KeyInfo == null)
  249. return null;
  250. foreach (KeyInfoClause kic in ed2.KeyInfo) {
  251. SecurityKeyIdentifierClause skic = serializer.ReadKeyIdentifierClause (new XmlNodeReader (kic.GetXml ()));
  252. SecurityKey skey = null;
  253. if (!resolver.TryResolveSecurityKey (skic, out skey))
  254. throw new MessageSecurityException (String.Format ("The signing key could not be resolved from {0}", skic));
  255. SymmetricSecurityKey ssk = skey as SymmetricSecurityKey;
  256. if (ssk != null)
  257. return ssk.GetSymmetricKey ();
  258. }
  259. return null; // no applicable key info clause.
  260. }
  261. // Probably it is a bug in .NET, but sometimes it does not contain
  262. // proper padding bytes. For such cases, use PaddingMode.None
  263. // instead. It must not be done in EncryptedXml class as it
  264. // correctly rejects improper ISO10126 padding.
  265. byte [] DecryptData (EncryptedXml encXml, EncryptedData ed, SymmetricAlgorithm symAlg)
  266. {
  267. PaddingMode bak = symAlg.Padding;
  268. try {
  269. byte [] bytes = ed.CipherData.CipherValue;
  270. if (encXml.Padding != PaddingMode.None &&
  271. encXml.Padding != PaddingMode.Zeros &&
  272. bytes [bytes.Length - 1] > symAlg.BlockSize / 8)
  273. symAlg.Padding = PaddingMode.None;
  274. return encXml.DecryptData (ed, symAlg);
  275. } finally {
  276. symAlg.Padding = bak;
  277. }
  278. }
  279. string StripUri (string src)
  280. {
  281. if (src == null || src.Length == 0)
  282. return String.Empty;
  283. if (src [0] != '#')
  284. throw new NotSupportedException (String.Format ("Non-fragment URI in DataReference and KeyReference is not supported: '{0}'", src));
  285. return src.Substring (1);
  286. }
  287. #endregion
  288. static Wss11SignatureConfirmation ReadSignatureConfirmation (XmlReader reader, XmlDocument doc)
  289. {
  290. string id = reader.GetAttribute ("Id", Constants.WsuNamespace);
  291. string value = reader.GetAttribute ("Value");
  292. reader.Skip ();
  293. return new Wss11SignatureConfirmation (id, value);
  294. }
  295. static WsuTimestamp ReadTimestamp (XmlReader reader)
  296. {
  297. WsuTimestamp ret = new WsuTimestamp ();
  298. ret.Id = reader.GetAttribute ("Id", Constants.WsuNamespace);
  299. reader.ReadStartElement ();
  300. do {
  301. reader.MoveToContent ();
  302. if (reader.NodeType == XmlNodeType.EndElement)
  303. break;
  304. if (reader.NodeType != XmlNodeType.Element)
  305. throw new XmlException (String.Format ("Node type {0} is not expected as a WS-Security 'Timestamp' content.", reader.NodeType));
  306. switch (reader.NamespaceURI) {
  307. case Constants.WsuNamespace:
  308. switch (reader.LocalName) {
  309. case "Created":
  310. ret.Created = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
  311. continue;
  312. case "Expires":
  313. ret.Expires = (DateTime) reader.ReadElementContentAs (typeof (DateTime), null);
  314. continue;
  315. }
  316. break;
  317. }
  318. throw new XmlException (String.Format ("Unexpected element '{0}' in namespace '{1}' as a WS-Security message header content.", reader.Name, reader.NamespaceURI));
  319. } while (true);
  320. reader.ReadEndElement (); // </u:Timestamp>
  321. return ret;
  322. }
  323. }
  324. internal class WSSecurityMessageHeader : MessageHeader
  325. {
  326. public WSSecurityMessageHeader (SecurityTokenSerializer serializer)
  327. {
  328. this.serializer = serializer;
  329. }
  330. SecurityTokenSerializer serializer;
  331. Collection<object> contents = new Collection<object> ();
  332. // Timestamp, BinarySecurityToken, EncryptedKey,
  333. // [DerivedKeyToken]*, ReferenceList, EncryptedData
  334. public Collection<object> Contents {
  335. get { return contents; }
  336. }
  337. public override bool MustUnderstand {
  338. get { return true; }
  339. }
  340. public override string Name {
  341. get { return "Security"; }
  342. }
  343. public override string Namespace {
  344. get { return Constants.WssNamespace; }
  345. }
  346. public void AddContent (object obj)
  347. {
  348. if (obj == null)
  349. throw new ArgumentNullException ("obj");
  350. Contents.Add (obj);
  351. }
  352. public T Find<T> ()
  353. {
  354. foreach (object o in Contents)
  355. if (typeof (T).IsAssignableFrom (o.GetType ()))
  356. return (T) o;
  357. return default (T);
  358. }
  359. public Collection<T> FindAll<T> ()
  360. {
  361. Collection<T> c = new Collection<T> ();
  362. foreach (object o in Contents)
  363. if (typeof (T).IsAssignableFrom (o.GetType ()))
  364. c.Add ((T) o);
  365. return c;
  366. }
  367. protected override void OnWriteStartHeader (XmlDictionaryWriter writer, MessageVersion version)
  368. {
  369. writer.WriteStartElement ("o", this.Name, this.Namespace);
  370. WriteHeaderAttributes (writer, version);
  371. }
  372. protected override void OnWriteHeaderContents (XmlDictionaryWriter writer, MessageVersion version)
  373. {
  374. // 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).
  375. foreach (object obj in Contents) {
  376. if (obj is WsuTimestamp) {
  377. WsuTimestamp ts = (WsuTimestamp) obj;
  378. ts.WriteTo (writer);
  379. } else if (obj is SecurityToken) {
  380. serializer.WriteToken (writer, (SecurityToken) obj);
  381. } else if (obj is EncryptedKey) {
  382. ((EncryptedKey) obj).GetXml ().WriteTo (writer);
  383. } else if (obj is ReferenceList) {
  384. writer.WriteStartElement ("ReferenceList", EncryptedXml.XmlEncNamespaceUrl);
  385. foreach (EncryptedReference er in (ReferenceList) obj)
  386. er.GetXml ().WriteTo (writer);
  387. writer.WriteEndElement ();
  388. } else if (obj is EncryptedData) {
  389. ((EncryptedData) obj).GetXml ().WriteTo (writer);
  390. } else if (obj is Signature) {
  391. ((Signature) obj).GetXml ().WriteTo (writer);
  392. } else if (obj is Wss11SignatureConfirmation) {
  393. Wss11SignatureConfirmation sc = (Wss11SignatureConfirmation) obj;
  394. writer.WriteStartElement ("k", "SignatureConfirmation", Constants.Wss11Namespace);
  395. writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, sc.Id);
  396. writer.WriteAttributeString ("Value", sc.Value);
  397. writer.WriteEndElement ();
  398. }
  399. else
  400. throw new ArgumentException (String.Format ("Unrecognized header item {0}", obj ?? "(null)"));
  401. }
  402. }
  403. }
  404. internal class WsuTimestamp
  405. {
  406. string id;
  407. DateTime created, expires;
  408. public string Id {
  409. get { return id; }
  410. set { id = value; }
  411. }
  412. public DateTime Created {
  413. get { return created; }
  414. set { created = value; }
  415. }
  416. public DateTime Expires {
  417. get { return expires; }
  418. set { expires = value; }
  419. }
  420. public void WriteTo (XmlWriter writer)
  421. {
  422. writer.WriteStartElement ("u", "Timestamp", Constants.WsuNamespace);
  423. writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, Id);
  424. writer.WriteStartElement ("u", "Created", Constants.WsuNamespace);
  425. writer.WriteValue (FormatAsUtc (Created));
  426. writer.WriteEndElement ();
  427. writer.WriteStartElement ("u", "Expires", Constants.WsuNamespace);
  428. writer.WriteValue (FormatAsUtc (Expires));
  429. writer.WriteEndElement ();
  430. writer.WriteEndElement ();
  431. }
  432. string FormatAsUtc (DateTime date)
  433. {
  434. return date.ToUniversalTime ().ToString (
  435. "yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
  436. CultureInfo.InvariantCulture);
  437. }
  438. }
  439. internal class SecurityTokenReferenceKeyInfo : KeyInfoClause
  440. {
  441. SecurityKeyIdentifierClause clause;
  442. SecurityTokenSerializer serializer;
  443. XmlDocument doc;
  444. // for LoadXml()
  445. public SecurityTokenReferenceKeyInfo (
  446. SecurityTokenSerializer serializer,
  447. XmlDocument doc)
  448. : this (null, serializer, doc)
  449. {
  450. }
  451. // for GetXml()
  452. public SecurityTokenReferenceKeyInfo (
  453. SecurityKeyIdentifierClause clause,
  454. SecurityTokenSerializer serializer,
  455. XmlDocument doc)
  456. {
  457. this.clause = clause;
  458. this.serializer = serializer;
  459. if (doc == null)
  460. doc = new XmlDocument ();
  461. this.doc = doc;
  462. }
  463. public SecurityKeyIdentifierClause Clause {
  464. get { return clause; }
  465. }
  466. public override XmlElement GetXml ()
  467. {
  468. XmlDocumentFragment df = doc.CreateDocumentFragment ();
  469. XmlWriter w = df.CreateNavigator ().AppendChild ();
  470. serializer.WriteKeyIdentifierClause (w, clause);
  471. w.Close ();
  472. return (XmlElement) df.FirstChild;
  473. }
  474. public override void LoadXml (XmlElement element)
  475. {
  476. clause = serializer.ReadKeyIdentifierClause (new XmlNodeReader (element));
  477. }
  478. }
  479. internal class Wss11SignatureConfirmation
  480. {
  481. string id, value;
  482. public Wss11SignatureConfirmation (string id, string value)
  483. {
  484. this.id = id;
  485. this.value = value;
  486. }
  487. public string Id {
  488. get { return id; }
  489. set { id = value; }
  490. }
  491. public string Value {
  492. get { return value; }
  493. set { this.value = value; }
  494. }
  495. }
  496. }