EncryptedXml.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. //
  2. // EncryptedXml.cs - EncryptedXml implementation for XML Encryption
  3. //
  4. // Author:
  5. // Tim Coleman ([email protected])
  6. //
  7. // Copyright (C) Tim Coleman, 2004
  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.Collections;
  29. using System.IO;
  30. using System.Security.Cryptography;
  31. using System.Security.Cryptography.X509Certificates;
  32. using System.Security.Policy;
  33. using System.Text;
  34. using System.Xml;
  35. namespace System.Security.Cryptography.Xml {
  36. public class EncryptedXml {
  37. #region Fields
  38. public const string XmlEncAES128KeyWrapUrl = XmlEncNamespaceUrl + "kw-aes128";
  39. public const string XmlEncAES128Url = XmlEncNamespaceUrl + "aes128-cbc";
  40. public const string XmlEncAES192KeyWrapUrl = XmlEncNamespaceUrl + "kw-aes192";
  41. public const string XmlEncAES192Url = XmlEncNamespaceUrl + "aes192-cbc";
  42. public const string XmlEncAES256KeyWrapUrl = XmlEncNamespaceUrl + "kw-aes256";
  43. public const string XmlEncAES256Url = XmlEncNamespaceUrl + "aes256-cbc";
  44. public const string XmlEncDESUrl = XmlEncNamespaceUrl + "des-cbc";
  45. public const string XmlEncElementContentUrl = XmlEncNamespaceUrl + "Content";
  46. public const string XmlEncElementUrl = XmlEncNamespaceUrl + "Element";
  47. public const string XmlEncEncryptedKeyUrl = XmlEncNamespaceUrl + "EncryptedKey";
  48. public const string XmlEncNamespaceUrl = "http://www.w3.org/2001/04/xmlenc#";
  49. public const string XmlEncRSA15Url = XmlEncNamespaceUrl + "rsa-1_5";
  50. public const string XmlEncRSAOAEPUrl = XmlEncNamespaceUrl + "rsa-oaep-mgf1p";
  51. public const string XmlEncSHA256Url = XmlEncNamespaceUrl + "sha256";
  52. public const string XmlEncSHA512Url = XmlEncNamespaceUrl + "sha512";
  53. public const string XmlEncTripleDESKeyWrapUrl = XmlEncNamespaceUrl + "kw-tripledes";
  54. public const string XmlEncTripleDESUrl = XmlEncNamespaceUrl + "tripledes-cbc";
  55. Evidence documentEvidence;
  56. Encoding encoding = Encoding.UTF8;
  57. internal Hashtable keyNameMapping = new Hashtable ();
  58. CipherMode mode = CipherMode.CBC;
  59. PaddingMode padding = PaddingMode.ISO10126;
  60. string recipient;
  61. XmlResolver resolver;
  62. XmlDocument document;
  63. #endregion // Fields
  64. #region Constructors
  65. [MonoTODO]
  66. public EncryptedXml ()
  67. {
  68. }
  69. [MonoTODO]
  70. public EncryptedXml (XmlDocument document)
  71. {
  72. this.document = document;
  73. }
  74. [MonoTODO]
  75. public EncryptedXml (XmlDocument document, Evidence evidence)
  76. {
  77. this.document = document;
  78. DocumentEvidence = evidence;
  79. }
  80. #endregion // Constructors
  81. #region Properties
  82. public Evidence DocumentEvidence {
  83. get { return documentEvidence; }
  84. set { documentEvidence = value; }
  85. }
  86. public Encoding Encoding {
  87. get { return encoding; }
  88. set { encoding = value; }
  89. }
  90. public CipherMode Mode {
  91. get { return mode; }
  92. set { mode = value; }
  93. }
  94. public PaddingMode Padding {
  95. get { return padding; }
  96. set { padding = value; }
  97. }
  98. public string Recipient {
  99. get { return recipient; }
  100. set { recipient = value; }
  101. }
  102. public XmlResolver Resolver {
  103. get { return resolver; }
  104. set { resolver = value; }
  105. }
  106. #endregion // Properties
  107. #region Methods
  108. public void AddKeyNameMapping (string keyName, object keyObject)
  109. {
  110. keyNameMapping [keyName] = keyObject;
  111. }
  112. public void ClearKeyNameMappings ()
  113. {
  114. keyNameMapping.Clear ();
  115. }
  116. public byte[] DecryptData (EncryptedData encryptedData, SymmetricAlgorithm symmetricAlgorithm)
  117. {
  118. if (encryptedData == null)
  119. throw new ArgumentNullException ("encryptedData");
  120. if (symmetricAlgorithm == null)
  121. throw new ArgumentNullException ("symmetricAlgorithm");
  122. PaddingMode bak = symmetricAlgorithm.Padding;
  123. try {
  124. symmetricAlgorithm.Padding = Padding;
  125. return Transform (encryptedData.CipherData.CipherValue, symmetricAlgorithm.CreateDecryptor (), symmetricAlgorithm.BlockSize / 8, true);
  126. } finally {
  127. symmetricAlgorithm.Padding = bak;
  128. }
  129. }
  130. public void DecryptDocument ()
  131. {
  132. XmlNodeList nodes = document.GetElementsByTagName ("EncryptedData", XmlEncNamespaceUrl);
  133. foreach (XmlNode node in nodes) {
  134. EncryptedData encryptedData = new EncryptedData ();
  135. encryptedData.LoadXml ((XmlElement) node);
  136. SymmetricAlgorithm symAlg = GetDecryptionKey (encryptedData, encryptedData.EncryptionMethod.KeyAlgorithm);
  137. ReplaceData ((XmlElement) node, DecryptData (encryptedData, symAlg));
  138. }
  139. }
  140. public virtual byte[] DecryptEncryptedKey (EncryptedKey encryptedKey)
  141. {
  142. if (encryptedKey == null)
  143. throw new ArgumentNullException ("encryptedKey");
  144. object keyAlg = null;
  145. foreach (KeyInfoClause innerClause in encryptedKey.KeyInfo) {
  146. if (innerClause is KeyInfoName) {
  147. keyAlg = keyNameMapping [((KeyInfoName) innerClause).Value];
  148. break;
  149. }
  150. }
  151. switch (encryptedKey.EncryptionMethod.KeyAlgorithm) {
  152. case XmlEncRSA15Url:
  153. return DecryptKey (encryptedKey.CipherData.CipherValue, (RSA) keyAlg, false);
  154. case XmlEncRSAOAEPUrl:
  155. return DecryptKey (encryptedKey.CipherData.CipherValue, (RSA) keyAlg, true);
  156. }
  157. return DecryptKey (encryptedKey.CipherData.CipherValue, (SymmetricAlgorithm) keyAlg);
  158. }
  159. public static byte[] DecryptKey (byte[] keyData, SymmetricAlgorithm symmetricAlgorithm)
  160. {
  161. if (keyData == null)
  162. throw new ArgumentNullException ("keyData");
  163. if (symmetricAlgorithm == null)
  164. throw new ArgumentNullException ("symmetricAlgorithm");
  165. if (symmetricAlgorithm is TripleDES)
  166. return SymmetricKeyWrap.TripleDESKeyWrapDecrypt (symmetricAlgorithm.Key, keyData);
  167. if (symmetricAlgorithm is Rijndael)
  168. return SymmetricKeyWrap.AESKeyWrapDecrypt (symmetricAlgorithm.Key, keyData);
  169. throw new CryptographicException ("The specified cryptographic transform is not supported.");
  170. }
  171. [MonoTODO ("Test this.")]
  172. public static byte[] DecryptKey (byte[] keyData, RSA rsa, bool useOAEP)
  173. {
  174. AsymmetricKeyExchangeDeformatter deformatter = null;
  175. if (useOAEP)
  176. deformatter = new RSAOAEPKeyExchangeDeformatter (rsa);
  177. else
  178. deformatter = new RSAPKCS1KeyExchangeDeformatter (rsa);
  179. return deformatter.DecryptKeyExchange (keyData);
  180. }
  181. public EncryptedData Encrypt (XmlElement inputElement, string keyName)
  182. {
  183. // There are two keys of note here.
  184. // 1) KeyAlg: the key-encryption-key is used to wrap a key. The keyName
  185. // parameter will give us the KEK.
  186. // 2) SymAlg: A 256-bit AES key will be generated to encrypt the contents.
  187. // This key will be wrapped using the KEK.
  188. SymmetricAlgorithm symAlg = SymmetricAlgorithm.Create ("Rijndael");
  189. symAlg.KeySize = 256;
  190. symAlg.GenerateKey ();
  191. symAlg.GenerateIV ();
  192. EncryptedData encryptedData = new EncryptedData ();
  193. EncryptedKey encryptedKey = new EncryptedKey();
  194. object keyAlg = keyNameMapping [keyName];
  195. encryptedKey.EncryptionMethod = new EncryptionMethod (GetKeyWrapAlgorithmUri (keyAlg));
  196. if (keyAlg is RSA)
  197. encryptedKey.CipherData = new CipherData (EncryptKey (symAlg.Key, (RSA) keyAlg, false));
  198. else
  199. encryptedKey.CipherData = new CipherData (EncryptKey (symAlg.Key, (SymmetricAlgorithm) keyAlg));
  200. encryptedKey.KeyInfo = new KeyInfo();
  201. encryptedKey.KeyInfo.AddClause (new KeyInfoName (keyName));
  202. encryptedData.Type = XmlEncElementUrl;
  203. encryptedData.EncryptionMethod = new EncryptionMethod (GetAlgorithmUri (symAlg));
  204. encryptedData.KeyInfo = new KeyInfo ();
  205. encryptedData.KeyInfo.AddClause (new KeyInfoEncryptedKey (encryptedKey));
  206. encryptedData.CipherData = new CipherData (EncryptData (inputElement, symAlg, false));
  207. return encryptedData;
  208. }
  209. [MonoTODO]
  210. public EncryptedData Encrypt (XmlElement inputElement, X509Certificate2 certificate)
  211. {
  212. throw new NotImplementedException ();
  213. }
  214. public byte[] EncryptData (byte[] plaintext, SymmetricAlgorithm symmetricAlgorithm)
  215. {
  216. if (plaintext == null)
  217. throw new ArgumentNullException ("plaintext");
  218. if (symmetricAlgorithm == null)
  219. throw new ArgumentNullException ("symmetricAlgorithm");
  220. PaddingMode bak = symmetricAlgorithm.Padding;
  221. try {
  222. symmetricAlgorithm.Padding = Padding;
  223. return EncryptDataCore (plaintext, symmetricAlgorithm);
  224. } finally {
  225. symmetricAlgorithm.Padding = bak;
  226. }
  227. }
  228. byte[] EncryptDataCore (byte[] plainText, SymmetricAlgorithm symAlg)
  229. {
  230. // Write the symmetric algorithm IV and ciphertext together.
  231. // We use a memory stream to accomplish this.
  232. MemoryStream stream = new MemoryStream ();
  233. BinaryWriter writer = new BinaryWriter (stream);
  234. writer.Write (symAlg.IV);
  235. writer.Write (Transform (plainText, symAlg.CreateEncryptor ()));
  236. writer.Flush ();
  237. byte [] output = stream.ToArray ();
  238. writer.Close ();
  239. stream.Close ();
  240. return output;
  241. }
  242. public byte[] EncryptData (XmlElement inputElement, SymmetricAlgorithm symmetricAlgorithm, bool content)
  243. {
  244. if (inputElement == null)
  245. throw new ArgumentNullException ("inputElement");
  246. if (content)
  247. return EncryptData (Encoding.GetBytes (inputElement.InnerXml), symmetricAlgorithm);
  248. else
  249. return EncryptData (Encoding.GetBytes (inputElement.OuterXml), symmetricAlgorithm);
  250. }
  251. public static byte[] EncryptKey (byte[] keyData, SymmetricAlgorithm symmetricAlgorithm)
  252. {
  253. if (keyData == null)
  254. throw new ArgumentNullException ("keyData");
  255. if (symmetricAlgorithm == null)
  256. throw new ArgumentNullException ("symmetricAlgorithm");
  257. if (symmetricAlgorithm is TripleDES)
  258. return SymmetricKeyWrap.TripleDESKeyWrapEncrypt (symmetricAlgorithm.Key, keyData);
  259. if (symmetricAlgorithm is Rijndael)
  260. return SymmetricKeyWrap.AESKeyWrapEncrypt (symmetricAlgorithm.Key, keyData);
  261. throw new CryptographicException ("The specified cryptographic transform is not supported.");
  262. }
  263. [MonoTODO ("Test this.")]
  264. public static byte[] EncryptKey (byte[] keyData, RSA rsa, bool useOAEP)
  265. {
  266. AsymmetricKeyExchangeFormatter formatter = null;
  267. if (useOAEP)
  268. formatter = new RSAOAEPKeyExchangeFormatter (rsa);
  269. else
  270. formatter = new RSAPKCS1KeyExchangeFormatter (rsa);
  271. return formatter.CreateKeyExchange (keyData);
  272. }
  273. private static SymmetricAlgorithm GetAlgorithm (string symAlgUri)
  274. {
  275. SymmetricAlgorithm symAlg = null;
  276. switch (symAlgUri) {
  277. case XmlEncAES128Url:
  278. case XmlEncAES128KeyWrapUrl:
  279. symAlg = SymmetricAlgorithm.Create ("Rijndael");
  280. symAlg.KeySize = 128;
  281. break;
  282. case XmlEncAES192Url:
  283. case XmlEncAES192KeyWrapUrl:
  284. symAlg = SymmetricAlgorithm.Create ("Rijndael");
  285. symAlg.KeySize = 192;
  286. break;
  287. case XmlEncAES256Url:
  288. case XmlEncAES256KeyWrapUrl:
  289. symAlg = SymmetricAlgorithm.Create ("Rijndael");
  290. symAlg.KeySize = 256;
  291. break;
  292. case XmlEncDESUrl:
  293. symAlg = SymmetricAlgorithm.Create ("DES");
  294. break;
  295. case XmlEncTripleDESUrl:
  296. case XmlEncTripleDESKeyWrapUrl:
  297. symAlg = SymmetricAlgorithm.Create ("TripleDES");
  298. break;
  299. default:
  300. throw new CryptographicException ("symAlgUri");
  301. }
  302. return symAlg;
  303. }
  304. private static string GetAlgorithmUri (SymmetricAlgorithm symAlg)
  305. {
  306. if (symAlg is Rijndael)
  307. {
  308. switch (symAlg.KeySize) {
  309. case 128:
  310. return XmlEncAES128Url;
  311. case 192:
  312. return XmlEncAES192Url;
  313. case 256:
  314. return XmlEncAES256Url;
  315. }
  316. }
  317. else if (symAlg is DES)
  318. return XmlEncDESUrl;
  319. else if (symAlg is TripleDES)
  320. return XmlEncTripleDESUrl;
  321. throw new ArgumentException ("symAlg");
  322. }
  323. private static string GetKeyWrapAlgorithmUri (object keyAlg)
  324. {
  325. if (keyAlg is Rijndael)
  326. {
  327. switch (((Rijndael) keyAlg).KeySize) {
  328. case 128:
  329. return XmlEncAES128KeyWrapUrl;
  330. case 192:
  331. return XmlEncAES192KeyWrapUrl;
  332. case 256:
  333. return XmlEncAES256KeyWrapUrl;
  334. }
  335. }
  336. else if (keyAlg is RSA)
  337. return XmlEncRSA15Url;
  338. else if (keyAlg is TripleDES)
  339. return XmlEncTripleDESKeyWrapUrl;
  340. throw new ArgumentException ("keyAlg");
  341. }
  342. public virtual byte[] GetDecryptionIV (EncryptedData encryptedData, string symmetricAlgorithmUri)
  343. {
  344. if (encryptedData == null)
  345. throw new ArgumentNullException ("encryptedData");
  346. SymmetricAlgorithm symAlg = GetAlgorithm (symmetricAlgorithmUri);
  347. byte[] iv = new Byte [symAlg.BlockSize / 8];
  348. Buffer.BlockCopy (encryptedData.CipherData.CipherValue, 0, iv, 0, iv.Length);
  349. return iv;
  350. }
  351. public virtual SymmetricAlgorithm GetDecryptionKey (EncryptedData encryptedData, string symmetricAlgorithmUri)
  352. {
  353. if (encryptedData == null)
  354. throw new ArgumentNullException ("encryptedData");
  355. if (symmetricAlgorithmUri == null)
  356. return null;
  357. SymmetricAlgorithm symAlg = GetAlgorithm (symmetricAlgorithmUri);
  358. symAlg.IV = GetDecryptionIV (encryptedData, encryptedData.EncryptionMethod.KeyAlgorithm);
  359. KeyInfo keyInfo = encryptedData.KeyInfo;
  360. foreach (KeyInfoClause clause in keyInfo) {
  361. if (clause is KeyInfoEncryptedKey) {
  362. symAlg.Key = DecryptEncryptedKey (((KeyInfoEncryptedKey) clause).EncryptedKey);
  363. break;
  364. }
  365. }
  366. return symAlg;
  367. }
  368. public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
  369. {
  370. if ((document == null) || (idValue == null))
  371. return null;
  372. // this works only if there's a DTD or XSD available to define the ID
  373. XmlElement xel = document.GetElementById (idValue);
  374. if (xel == null) {
  375. // search an "undefined" ID
  376. xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
  377. }
  378. return xel;
  379. }
  380. public void ReplaceData (XmlElement inputElement, byte[] decryptedData)
  381. {
  382. if (inputElement == null)
  383. throw new ArgumentNullException ("inputElement");
  384. if (decryptedData == null)
  385. throw new ArgumentNullException ("decryptedData");
  386. XmlDocument ownerDocument = inputElement.OwnerDocument;
  387. XmlTextReader reader = new XmlTextReader (new StringReader (Encoding.GetString (decryptedData, 0, decryptedData.Length)));
  388. reader.MoveToContent ();
  389. XmlNode node = ownerDocument.ReadNode (reader);
  390. inputElement.ParentNode.ReplaceChild (node, inputElement);
  391. }
  392. public static void ReplaceElement (XmlElement inputElement, EncryptedData encryptedData, bool content)
  393. {
  394. if (inputElement == null)
  395. throw new ArgumentNullException ("inputElement");
  396. if (encryptedData == null)
  397. throw new ArgumentNullException ("encryptedData");
  398. XmlDocument ownerDocument = inputElement.OwnerDocument;
  399. inputElement.ParentNode.ReplaceChild (encryptedData.GetXml (ownerDocument), inputElement);
  400. }
  401. private byte[] Transform (byte[] data, ICryptoTransform transform)
  402. {
  403. return Transform (data, transform, 0, false);
  404. }
  405. private byte[] Transform (byte[] data, ICryptoTransform transform, int blockOctetCount, bool trimPadding)
  406. {
  407. MemoryStream output = new MemoryStream ();
  408. CryptoStream crypto = new CryptoStream (output, transform, CryptoStreamMode.Write);
  409. crypto.Write (data, 0, data.Length);
  410. crypto.FlushFinalBlock ();
  411. // strip padding (see xmlenc spec 5.2)
  412. int trimSize = 0;
  413. if (trimPadding)
  414. trimSize = output.GetBuffer () [output.Length - 1];
  415. // It should not happen, but somehow .NET allows such cipher
  416. // data as if there were no padding.
  417. if (trimSize > blockOctetCount)
  418. trimSize = 0;
  419. byte[] result = new byte [output.Length - blockOctetCount - trimSize];
  420. Array.Copy (output.GetBuffer (), blockOctetCount, result, 0, result.Length);
  421. crypto.Close ();
  422. output.Close ();
  423. return result;
  424. }
  425. #endregion // Methods
  426. }
  427. }