WSSecurityMessageHeader.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  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
  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. foreach (object obj in Contents) {
  375. if (obj is WsuTimestamp) {
  376. WsuTimestamp ts = (WsuTimestamp) obj;
  377. ts.WriteTo (writer);
  378. } else if (obj is SecurityToken) {
  379. serializer.WriteToken (writer, (SecurityToken) obj);
  380. } else if (obj is EncryptedKey) {
  381. ((EncryptedKey) obj).GetXml ().WriteTo (writer);
  382. } else if (obj is ReferenceList) {
  383. writer.WriteStartElement ("ReferenceList", EncryptedXml.XmlEncNamespaceUrl);
  384. foreach (EncryptedReference er in (ReferenceList) obj)
  385. er.GetXml ().WriteTo (writer);
  386. writer.WriteEndElement ();
  387. } else if (obj is EncryptedData) {
  388. ((EncryptedData) obj).GetXml ().WriteTo (writer);
  389. } else if (obj is Signature) {
  390. ((Signature) obj).GetXml ().WriteTo (writer);
  391. } else if (obj is Wss11SignatureConfirmation) {
  392. Wss11SignatureConfirmation sc = (Wss11SignatureConfirmation) obj;
  393. writer.WriteStartElement ("k", "SignatureConfirmation", Constants.Wss11Namespace);
  394. writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, sc.Id);
  395. writer.WriteAttributeString ("Value", sc.Value);
  396. writer.WriteEndElement ();
  397. }
  398. else
  399. throw new ArgumentException (String.Format ("Unrecognized header item {0}", obj ?? "(null)"));
  400. }
  401. }
  402. }
  403. internal class WsuTimestamp
  404. {
  405. string id;
  406. DateTime created, expires;
  407. public string Id {
  408. get { return id; }
  409. set { id = value; }
  410. }
  411. public DateTime Created {
  412. get { return created; }
  413. set { created = value; }
  414. }
  415. public DateTime Expires {
  416. get { return expires; }
  417. set { expires = value; }
  418. }
  419. public void WriteTo (XmlWriter writer)
  420. {
  421. writer.WriteStartElement ("u", "Timestamp", Constants.WsuNamespace);
  422. writer.WriteAttributeString ("u", "Id", Constants.WsuNamespace, Id);
  423. writer.WriteStartElement ("u", "Created", Constants.WsuNamespace);
  424. writer.WriteValue (FormatAsUtc (Created));
  425. writer.WriteEndElement ();
  426. writer.WriteStartElement ("u", "Expires", Constants.WsuNamespace);
  427. writer.WriteValue (FormatAsUtc (Expires));
  428. writer.WriteEndElement ();
  429. writer.WriteEndElement ();
  430. }
  431. string FormatAsUtc (DateTime date)
  432. {
  433. return date.ToUniversalTime ().ToString (
  434. "yyyy-MM-dd'T'HH:mm:ss.fff'Z'",
  435. CultureInfo.InvariantCulture);
  436. }
  437. }
  438. internal class SecurityTokenReferenceKeyInfo : KeyInfoClause
  439. {
  440. SecurityKeyIdentifierClause clause;
  441. SecurityTokenSerializer serializer;
  442. XmlDocument doc;
  443. // for LoadXml()
  444. public SecurityTokenReferenceKeyInfo (
  445. SecurityTokenSerializer serializer,
  446. XmlDocument doc)
  447. : this (null, serializer, doc)
  448. {
  449. }
  450. // for GetXml()
  451. public SecurityTokenReferenceKeyInfo (
  452. SecurityKeyIdentifierClause clause,
  453. SecurityTokenSerializer serializer,
  454. XmlDocument doc)
  455. {
  456. this.clause = clause;
  457. this.serializer = serializer;
  458. if (doc == null)
  459. doc = new XmlDocument ();
  460. this.doc = doc;
  461. }
  462. public SecurityKeyIdentifierClause Clause {
  463. get { return clause; }
  464. }
  465. public override XmlElement GetXml ()
  466. {
  467. XmlDocumentFragment df = doc.CreateDocumentFragment ();
  468. XmlWriter w = df.CreateNavigator ().AppendChild ();
  469. serializer.WriteKeyIdentifierClause (w, clause);
  470. w.Close ();
  471. return (XmlElement) df.FirstChild;
  472. }
  473. public override void LoadXml (XmlElement element)
  474. {
  475. clause = serializer.ReadKeyIdentifierClause (new XmlNodeReader (element));
  476. }
  477. }
  478. internal class Wss11SignatureConfirmation
  479. {
  480. string id, value;
  481. public Wss11SignatureConfirmation (string id, string value)
  482. {
  483. this.id = id;
  484. this.value = value;
  485. }
  486. public string Id {
  487. get { return id; }
  488. set { id = value; }
  489. }
  490. public string Value {
  491. get { return value; }
  492. set { this.value = value; }
  493. }
  494. }
  495. }