2 // EncryptedXml.cs - EncryptedXml implementation for XML Encryption
5 // Tim Coleman (tim@timcoleman.com)
7 // Copyright (C) Tim Coleman, 2004
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.Collections;
33 using System.Security.Cryptography;
34 using System.Security.Cryptography.X509Certificates;
35 using System.Security.Policy;
39 namespace System.Security.Cryptography.Xml {
40 public class EncryptedXml {
44 public const string XmlEncAES128KeyWrapUrl = XmlEncNamespaceUrl + "kw-aes128";
45 public const string XmlEncAES128Url = XmlEncNamespaceUrl + "aes128-cbc";
46 public const string XmlEncAES192KeyWrapUrl = XmlEncNamespaceUrl + "kw-aes192";
47 public const string XmlEncAES192Url = XmlEncNamespaceUrl + "aes192-cbc";
48 public const string XmlEncAES256KeyWrapUrl = XmlEncNamespaceUrl + "kw-aes256";
49 public const string XmlEncAES256Url = XmlEncNamespaceUrl + "aes256-cbc";
50 public const string XmlEncDESUrl = XmlEncNamespaceUrl + "des-cbc";
51 public const string XmlEncElementContentUrl = XmlEncNamespaceUrl + "Content";
52 public const string XmlEncElementUrl = XmlEncNamespaceUrl + "Element";
53 public const string XmlEncEncryptedKeyUrl = XmlEncNamespaceUrl + "EncryptedKey";
54 public const string XmlEncNamespaceUrl = "http://www.w3.org/2001/04/xmlenc#";
55 public const string XmlEncRSA15Url = XmlEncNamespaceUrl + "rsa-1_5";
56 public const string XmlEncRSAOAEPUrl = XmlEncNamespaceUrl + "rsa-oaep-mgf1p";
57 public const string XmlEncSHA256Url = XmlEncNamespaceUrl + "sha256";
58 public const string XmlEncSHA512Url = XmlEncNamespaceUrl + "sha512";
59 public const string XmlEncTripleDESKeyWrapUrl = XmlEncNamespaceUrl + "kw-tripledes";
60 public const string XmlEncTripleDESUrl = XmlEncNamespaceUrl + "tripledes-cbc";
62 Evidence documentEvidence;
63 Encoding encoding = Encoding.UTF8;
64 internal Hashtable keyNameMapping = new Hashtable ();
65 CipherMode mode = CipherMode.CBC;
66 PaddingMode padding = PaddingMode.ISO10126;
76 public EncryptedXml ()
81 public EncryptedXml (XmlDocument document)
83 this.document = document;
87 public EncryptedXml (XmlDocument document, Evidence evidence)
89 this.document = document;
90 DocumentEvidence = evidence;
93 #endregion // Constructors
97 public Evidence DocumentEvidence {
98 get { return documentEvidence; }
99 set { documentEvidence = value; }
102 public Encoding Encoding {
103 get { return encoding; }
104 set { encoding = value; }
107 public CipherMode Mode {
109 set { mode = value; }
112 public PaddingMode Padding {
113 get { return padding; }
114 set { padding = value; }
117 public string Recipient {
118 get { return recipient; }
119 set { recipient = value; }
122 public XmlResolver Resolver {
123 get { return resolver; }
124 set { resolver = value; }
127 #endregion // Properties
131 public void AddKeyNameMapping (string keyName, object keyObject)
133 keyNameMapping [keyName] = keyObject;
136 public void ClearKeyNameMappings ()
138 keyNameMapping.Clear ();
141 public byte[] DecryptData (EncryptedData encryptedData, SymmetricAlgorithm symAlg)
143 if (encryptedData == null)
144 throw new ArgumentNullException ("encryptedData");
146 throw new ArgumentNullException ("symAlg");
148 PaddingMode bak = symAlg.Padding;
150 symAlg.Padding = Padding;
151 return Transform (encryptedData.CipherData.CipherValue, symAlg.CreateDecryptor (), symAlg.BlockSize / 8, true);
153 symAlg.Padding = bak;
157 public void DecryptDocument ()
159 XmlNodeList nodes = document.GetElementsByTagName ("EncryptedData", XmlEncNamespaceUrl);
160 foreach (XmlNode node in nodes) {
161 EncryptedData encryptedData = new EncryptedData ();
162 encryptedData.LoadXml ((XmlElement) node);
163 SymmetricAlgorithm symAlg = GetDecryptionKey (encryptedData, encryptedData.EncryptionMethod.KeyAlgorithm);
164 ReplaceData ((XmlElement) node, DecryptData (encryptedData, symAlg));
168 public virtual byte[] DecryptEncryptedKey (EncryptedKey encryptedKey)
170 if (encryptedKey == null)
171 throw new ArgumentNullException ("encryptedKey");
173 object keyAlg = null;
174 foreach (KeyInfoClause innerClause in encryptedKey.KeyInfo) {
175 if (innerClause is KeyInfoName) {
176 keyAlg = keyNameMapping [((KeyInfoName) innerClause).Value];
180 switch (encryptedKey.EncryptionMethod.KeyAlgorithm) {
182 return DecryptKey (encryptedKey.CipherData.CipherValue, (RSA) keyAlg, false);
183 case XmlEncRSAOAEPUrl:
184 return DecryptKey (encryptedKey.CipherData.CipherValue, (RSA) keyAlg, true);
186 return DecryptKey (encryptedKey.CipherData.CipherValue, (SymmetricAlgorithm) keyAlg);
189 public static byte[] DecryptKey (byte[] keyData, SymmetricAlgorithm symAlg)
192 throw new ArgumentNullException ("keyData");
194 throw new ArgumentNullException ("symAlg");
196 if (symAlg is TripleDES)
197 return SymmetricKeyWrap.TripleDESKeyWrapDecrypt (symAlg.Key, keyData);
198 if (symAlg is Rijndael)
199 return SymmetricKeyWrap.AESKeyWrapDecrypt (symAlg.Key, keyData);
200 throw new CryptographicException ("The specified cryptographic transform is not supported.");
203 [MonoTODO ("Test this.")]
204 public static byte[] DecryptKey (byte[] keyData, RSA rsa, bool fOAEP)
206 AsymmetricKeyExchangeDeformatter deformatter = null;
208 deformatter = new RSAOAEPKeyExchangeDeformatter (rsa);
210 deformatter = new RSAPKCS1KeyExchangeDeformatter (rsa);
211 return deformatter.DecryptKeyExchange (keyData);
214 public EncryptedData Encrypt (XmlElement inputElement, string keyName)
216 // There are two keys of note here.
217 // 1) KeyAlg: the key-encryption-key is used to wrap a key. The keyName
218 // parameter will give us the KEK.
219 // 2) SymAlg: A 256-bit AES key will be generated to encrypt the contents.
220 // This key will be wrapped using the KEK.
222 SymmetricAlgorithm symAlg = SymmetricAlgorithm.Create ("Rijndael");
223 symAlg.KeySize = 256;
224 symAlg.GenerateKey ();
225 symAlg.GenerateIV ();
227 EncryptedData encryptedData = new EncryptedData ();
228 EncryptedKey encryptedKey = new EncryptedKey();
230 object keyAlg = keyNameMapping [keyName];
232 encryptedKey.EncryptionMethod = new EncryptionMethod (GetKeyWrapAlgorithmUri (keyAlg));
235 encryptedKey.CipherData = new CipherData (EncryptKey (symAlg.Key, (RSA) keyAlg, false));
237 encryptedKey.CipherData = new CipherData (EncryptKey (symAlg.Key, (SymmetricAlgorithm) keyAlg));
239 encryptedKey.KeyInfo = new KeyInfo();
240 encryptedKey.KeyInfo.AddClause (new KeyInfoName (keyName));
242 encryptedData.Type = XmlEncElementUrl;
243 encryptedData.EncryptionMethod = new EncryptionMethod (GetAlgorithmUri (symAlg));
244 encryptedData.KeyInfo = new KeyInfo ();
245 encryptedData.KeyInfo.AddClause (new KeyInfoEncryptedKey (encryptedKey));
246 encryptedData.CipherData = new CipherData (EncryptData (inputElement, symAlg, false));
248 return encryptedData;
252 public EncryptedData Encrypt (XmlElement inputElement, X509Certificate2 certificate)
254 throw new NotImplementedException ();
257 public byte[] EncryptData (byte[] plainText, SymmetricAlgorithm symAlg)
259 if (plainText == null)
260 throw new ArgumentNullException ("plainText");
262 throw new ArgumentNullException ("symAlg");
264 PaddingMode bak = symAlg.Padding;
266 symAlg.Padding = Padding;
267 return EncryptDataCore (plainText, symAlg);
269 symAlg.Padding = bak;
273 byte[] EncryptDataCore (byte[] plainText, SymmetricAlgorithm symAlg)
275 // Write the symmetric algorithm IV and ciphertext together.
276 // We use a memory stream to accomplish this.
277 MemoryStream stream = new MemoryStream ();
278 BinaryWriter writer = new BinaryWriter (stream);
280 writer.Write (symAlg.IV);
281 writer.Write (Transform (plainText, symAlg.CreateEncryptor ()));
284 byte [] output = stream.ToArray ();
292 public byte[] EncryptData (XmlElement inputElement, SymmetricAlgorithm symAlg, bool content)
294 if (inputElement == null)
295 throw new ArgumentNullException ("inputElement");
298 return EncryptData (Encoding.GetBytes (inputElement.InnerXml), symAlg);
300 return EncryptData (Encoding.GetBytes (inputElement.OuterXml), symAlg);
303 public static byte[] EncryptKey (byte[] keyData, SymmetricAlgorithm symAlg)
306 throw new ArgumentNullException ("keyData");
308 throw new ArgumentNullException ("symAlg");
310 if (symAlg is TripleDES)
311 return SymmetricKeyWrap.TripleDESKeyWrapEncrypt (symAlg.Key, keyData);
312 if (symAlg is Rijndael)
313 return SymmetricKeyWrap.AESKeyWrapEncrypt (symAlg.Key, keyData);
315 throw new CryptographicException ("The specified cryptographic transform is not supported.");
318 [MonoTODO ("Test this.")]
319 public static byte[] EncryptKey (byte[] keyData, RSA rsa, bool fOAEP)
321 AsymmetricKeyExchangeFormatter formatter = null;
323 formatter = new RSAOAEPKeyExchangeFormatter (rsa);
325 formatter = new RSAPKCS1KeyExchangeFormatter (rsa);
326 return formatter.CreateKeyExchange (keyData);
329 private static SymmetricAlgorithm GetAlgorithm (string symAlgUri)
331 SymmetricAlgorithm symAlg = null;
334 case XmlEncAES128Url:
335 case XmlEncAES128KeyWrapUrl:
336 symAlg = SymmetricAlgorithm.Create ("Rijndael");
337 symAlg.KeySize = 128;
339 case XmlEncAES192Url:
340 case XmlEncAES192KeyWrapUrl:
341 symAlg = SymmetricAlgorithm.Create ("Rijndael");
342 symAlg.KeySize = 192;
344 case XmlEncAES256Url:
345 case XmlEncAES256KeyWrapUrl:
346 symAlg = SymmetricAlgorithm.Create ("Rijndael");
347 symAlg.KeySize = 256;
350 symAlg = SymmetricAlgorithm.Create ("DES");
352 case XmlEncTripleDESUrl:
353 case XmlEncTripleDESKeyWrapUrl:
354 symAlg = SymmetricAlgorithm.Create ("TripleDES");
357 throw new CryptographicException ("symAlgUri");
363 private static string GetAlgorithmUri (SymmetricAlgorithm symAlg)
365 if (symAlg is Rijndael)
367 switch (symAlg.KeySize) {
369 return XmlEncAES128Url;
371 return XmlEncAES192Url;
373 return XmlEncAES256Url;
376 else if (symAlg is DES)
378 else if (symAlg is TripleDES)
379 return XmlEncTripleDESUrl;
381 throw new ArgumentException ("symAlg");
384 private static string GetKeyWrapAlgorithmUri (object keyAlg)
386 if (keyAlg is Rijndael)
388 switch (((Rijndael) keyAlg).KeySize) {
390 return XmlEncAES128KeyWrapUrl;
392 return XmlEncAES192KeyWrapUrl;
394 return XmlEncAES256KeyWrapUrl;
397 else if (keyAlg is RSA)
398 return XmlEncRSA15Url;
399 else if (keyAlg is TripleDES)
400 return XmlEncTripleDESKeyWrapUrl;
402 throw new ArgumentException ("keyAlg");
405 public virtual byte[] GetDecryptionIV (EncryptedData encryptedData, string symAlgUri)
407 if (encryptedData == null)
408 throw new ArgumentNullException ("encryptedData");
410 SymmetricAlgorithm symAlg = GetAlgorithm (symAlgUri);
411 byte[] iv = new Byte [symAlg.BlockSize / 8];
412 Buffer.BlockCopy (encryptedData.CipherData.CipherValue, 0, iv, 0, iv.Length);
416 public virtual SymmetricAlgorithm GetDecryptionKey (EncryptedData encryptedData, string symAlgUri)
418 if (encryptedData == null)
419 throw new ArgumentNullException ("encryptedData");
420 if (symAlgUri == null)
423 SymmetricAlgorithm symAlg = GetAlgorithm (symAlgUri);
424 symAlg.IV = GetDecryptionIV (encryptedData, encryptedData.EncryptionMethod.KeyAlgorithm);
425 KeyInfo keyInfo = encryptedData.KeyInfo;
426 foreach (KeyInfoClause clause in keyInfo) {
427 if (clause is KeyInfoEncryptedKey) {
428 symAlg.Key = DecryptEncryptedKey (((KeyInfoEncryptedKey) clause).EncryptedKey);
435 public virtual XmlElement GetIdElement (XmlDocument document, string idValue)
437 if ((document == null) || (idValue == null))
440 // this works only if there's a DTD or XSD available to define the ID
441 XmlElement xel = document.GetElementById (idValue);
443 // search an "undefined" ID
444 xel = (XmlElement) document.SelectSingleNode ("//*[@Id='" + idValue + "']");
449 public void ReplaceData (XmlElement inputElement, byte[] decryptedData)
451 if (inputElement == null)
452 throw new ArgumentNullException ("inputElement");
453 if (decryptedData == null)
454 throw new ArgumentNullException ("decryptedData");
456 XmlDocument ownerDocument = inputElement.OwnerDocument;
457 XmlTextReader reader = new XmlTextReader (new StringReader (Encoding.GetString (decryptedData, 0, decryptedData.Length)));
458 reader.MoveToContent ();
459 XmlNode node = ownerDocument.ReadNode (reader);
460 inputElement.ParentNode.ReplaceChild (node, inputElement);
463 public static void ReplaceElement (XmlElement inputElement, EncryptedData encryptedData, bool content)
465 if (inputElement == null)
466 throw new ArgumentNullException ("inputElement");
467 if (encryptedData == null)
468 throw new ArgumentNullException ("encryptedData");
470 XmlDocument ownerDocument = inputElement.OwnerDocument;
471 inputElement.ParentNode.ReplaceChild (encryptedData.GetXml (ownerDocument), inputElement);
474 private byte[] Transform (byte[] data, ICryptoTransform transform)
476 return Transform (data, transform, 0, false);
479 private byte[] Transform (byte[] data, ICryptoTransform transform, int blockOctetCount, bool trimPadding)
481 MemoryStream output = new MemoryStream ();
482 CryptoStream crypto = new CryptoStream (output, transform, CryptoStreamMode.Write);
483 crypto.Write (data, 0, data.Length);
485 crypto.FlushFinalBlock ();
487 // strip padding (see xmlenc spec 5.2)
490 trimSize = output.GetBuffer () [output.Length - 1];
491 // It should not happen, but somehow .NET allows such cipher
492 // data as if there were no padding.
493 if (trimSize > blockOctetCount)
495 byte[] result = new byte [output.Length - blockOctetCount - trimSize];
496 Array.Copy (output.GetBuffer (), blockOctetCount, result, 0, result.Length);
504 #endregion // Methods