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