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.
32 using System
.Collections
;
34 using System
.Security
.Cryptography
;
35 using System
.Security
.Cryptography
.X509Certificates
;
36 using System
.Security
.Policy
;
40 namespace System
.Security
.Cryptography
.Xml
{
41 public class EncryptedXml
{
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";
63 Evidence documentEvidence
;
64 Encoding encoding
= Encoding
.UTF8
;
65 internal Hashtable keyNameMapping
= new Hashtable ();
66 CipherMode mode
= CipherMode
.CBC
;
67 PaddingMode padding
= PaddingMode
.ISO10126
;
77 public EncryptedXml ()
82 public EncryptedXml (XmlDocument document
)
84 this.document
= document
;
88 public EncryptedXml (XmlDocument document
, Evidence evidence
)
90 this.document
= document
;
91 DocumentEvidence
= evidence
;
94 #endregion // Constructors
98 public Evidence DocumentEvidence
{
99 get { return documentEvidence; }
100 set { documentEvidence = value; }
103 public Encoding Encoding
{
104 get { return encoding; }
105 set { encoding = value; }
108 public CipherMode Mode
{
110 set { mode = value; }
113 public PaddingMode Padding
{
114 get { return padding; }
115 set { padding = value; }
118 public string Recipient
{
119 get { return recipient; }
120 set { recipient = value; }
123 public XmlResolver Resolver
{
124 get { return resolver; }
125 set { resolver = value; }
128 #endregion // Properties
132 public void AddKeyNameMapping (string keyName
, object keyObject
)
134 keyNameMapping
[keyName
] = keyObject
;
137 public void ClearKeyNameMappings ()
139 keyNameMapping
.Clear ();
142 public byte[] DecryptData (EncryptedData encryptedData
, SymmetricAlgorithm symAlg
)
144 if (encryptedData
== null)
145 throw new ArgumentNullException ("encryptedData");
147 throw new ArgumentNullException ("symAlg");
149 PaddingMode bak
= symAlg
.Padding
;
151 symAlg
.Padding
= Padding
;
152 return Transform (encryptedData
.CipherData
.CipherValue
, symAlg
.CreateDecryptor (), symAlg
.BlockSize
/ 8, true);
154 symAlg
.Padding
= bak
;
158 public void DecryptDocument ()
160 XmlNodeList nodes
= document
.GetElementsByTagName ("EncryptedData", XmlEncNamespaceUrl
);
161 foreach (XmlNode node
in nodes
) {
162 EncryptedData encryptedData
= new EncryptedData ();
163 encryptedData
.LoadXml ((XmlElement
) node
);
164 SymmetricAlgorithm symAlg
= GetDecryptionKey (encryptedData
, encryptedData
.EncryptionMethod
.KeyAlgorithm
);
165 ReplaceData ((XmlElement
) node
, DecryptData (encryptedData
, symAlg
));
169 public virtual byte[] DecryptEncryptedKey (EncryptedKey encryptedKey
)
171 if (encryptedKey
== null)
172 throw new ArgumentNullException ("encryptedKey");
174 object keyAlg
= null;
175 foreach (KeyInfoClause innerClause
in encryptedKey
.KeyInfo
) {
176 if (innerClause
is KeyInfoName
) {
177 keyAlg
= keyNameMapping
[((KeyInfoName
) innerClause
).Value
];
181 switch (encryptedKey
.EncryptionMethod
.KeyAlgorithm
) {
183 return DecryptKey (encryptedKey
.CipherData
.CipherValue
, (RSA
) keyAlg
, false);
184 case XmlEncRSAOAEPUrl
:
185 return DecryptKey (encryptedKey
.CipherData
.CipherValue
, (RSA
) keyAlg
, true);
187 return DecryptKey (encryptedKey
.CipherData
.CipherValue
, (SymmetricAlgorithm
) keyAlg
);
190 public static byte[] DecryptKey (byte[] keyData
, SymmetricAlgorithm symAlg
)
193 throw new ArgumentNullException ("keyData");
195 throw new ArgumentNullException ("symAlg");
197 if (symAlg
is TripleDES
)
198 return SymmetricKeyWrap
.TripleDESKeyWrapDecrypt (symAlg
.Key
, keyData
);
199 if (symAlg
is Rijndael
)
200 return SymmetricKeyWrap
.AESKeyWrapDecrypt (symAlg
.Key
, keyData
);
201 throw new CryptographicException ("The specified cryptographic transform is not supported.");
204 [MonoTODO ("Test this.")]
205 public static byte[] DecryptKey (byte[] keyData
, RSA rsa
, bool fOAEP
)
207 AsymmetricKeyExchangeDeformatter deformatter
= null;
209 deformatter
= new RSAOAEPKeyExchangeDeformatter (rsa
);
211 deformatter
= new RSAPKCS1KeyExchangeDeformatter (rsa
);
212 return deformatter
.DecryptKeyExchange (keyData
);
215 public EncryptedData
Encrypt (XmlElement inputElement
, string keyName
)
217 // There are two keys of note here.
218 // 1) KeyAlg: the key-encryption-key is used to wrap a key. The keyName
219 // parameter will give us the KEK.
220 // 2) SymAlg: A 256-bit AES key will be generated to encrypt the contents.
221 // This key will be wrapped using the KEK.
223 SymmetricAlgorithm symAlg
= SymmetricAlgorithm
.Create ("Rijndael");
224 symAlg
.KeySize
= 256;
225 symAlg
.GenerateKey ();
226 symAlg
.GenerateIV ();
228 EncryptedData encryptedData
= new EncryptedData ();
229 EncryptedKey encryptedKey
= new EncryptedKey();
231 object keyAlg
= keyNameMapping
[keyName
];
233 encryptedKey
.EncryptionMethod
= new EncryptionMethod (GetKeyWrapAlgorithmUri (keyAlg
));
236 encryptedKey
.CipherData
= new CipherData (EncryptKey (symAlg
.Key
, (RSA
) keyAlg
, false));
238 encryptedKey
.CipherData
= new CipherData (EncryptKey (symAlg
.Key
, (SymmetricAlgorithm
) keyAlg
));
240 encryptedKey
.KeyInfo
= new KeyInfo();
241 encryptedKey
.KeyInfo
.AddClause (new KeyInfoName (keyName
));
243 encryptedData
.Type
= XmlEncElementUrl
;
244 encryptedData
.EncryptionMethod
= new EncryptionMethod (GetAlgorithmUri (symAlg
));
245 encryptedData
.KeyInfo
= new KeyInfo ();
246 encryptedData
.KeyInfo
.AddClause (new KeyInfoEncryptedKey (encryptedKey
));
247 encryptedData
.CipherData
= new CipherData (EncryptData (inputElement
, symAlg
, false));
249 return encryptedData
;
253 public EncryptedData
Encrypt (XmlElement inputElement
, X509Certificate2 certificate
)
255 throw new NotImplementedException ();
258 public byte[] EncryptData (byte[] plainText
, SymmetricAlgorithm symAlg
)
260 if (plainText
== null)
261 throw new ArgumentNullException ("plainText");
263 throw new ArgumentNullException ("symAlg");
265 PaddingMode bak
= symAlg
.Padding
;
267 symAlg
.Padding
= Padding
;
268 return EncryptDataCore (plainText
, symAlg
);
270 symAlg
.Padding
= bak
;
274 byte[] EncryptDataCore (byte[] plainText
, SymmetricAlgorithm symAlg
)
276 // Write the symmetric algorithm IV and ciphertext together.
277 // We use a memory stream to accomplish this.
278 MemoryStream stream
= new MemoryStream ();
279 BinaryWriter writer
= new BinaryWriter (stream
);
281 writer
.Write (symAlg
.IV
);
282 writer
.Write (Transform (plainText
, symAlg
.CreateEncryptor ()));
285 byte [] output
= stream
.ToArray ();
293 public byte[] EncryptData (XmlElement inputElement
, SymmetricAlgorithm symAlg
, bool content
)
295 if (inputElement
== null)
296 throw new ArgumentNullException ("inputElement");
299 return EncryptData (Encoding
.GetBytes (inputElement
.InnerXml
), symAlg
);
301 return EncryptData (Encoding
.GetBytes (inputElement
.OuterXml
), symAlg
);
304 public static byte[] EncryptKey (byte[] keyData
, SymmetricAlgorithm symAlg
)
307 throw new ArgumentNullException ("keyData");
309 throw new ArgumentNullException ("symAlg");
311 if (symAlg
is TripleDES
)
312 return SymmetricKeyWrap
.TripleDESKeyWrapEncrypt (symAlg
.Key
, keyData
);
313 if (symAlg
is Rijndael
)
314 return SymmetricKeyWrap
.AESKeyWrapEncrypt (symAlg
.Key
, keyData
);
316 throw new CryptographicException ("The specified cryptographic transform is not supported.");
319 [MonoTODO ("Test this.")]
320 public static byte[] EncryptKey (byte[] keyData
, RSA rsa
, bool fOAEP
)
322 AsymmetricKeyExchangeFormatter formatter
= null;
324 formatter
= new RSAOAEPKeyExchangeFormatter (rsa
);
326 formatter
= new RSAPKCS1KeyExchangeFormatter (rsa
);
327 return formatter
.CreateKeyExchange (keyData
);
330 private static SymmetricAlgorithm
GetAlgorithm (string symAlgUri
)
332 SymmetricAlgorithm symAlg
= null;
335 case XmlEncAES128Url
:
336 case XmlEncAES128KeyWrapUrl
:
337 symAlg
= SymmetricAlgorithm
.Create ("Rijndael");
338 symAlg
.KeySize
= 128;
340 case XmlEncAES192Url
:
341 case XmlEncAES192KeyWrapUrl
:
342 symAlg
= SymmetricAlgorithm
.Create ("Rijndael");
343 symAlg
.KeySize
= 192;
345 case XmlEncAES256Url
:
346 case XmlEncAES256KeyWrapUrl
:
347 symAlg
= SymmetricAlgorithm
.Create ("Rijndael");
348 symAlg
.KeySize
= 256;
351 symAlg
= SymmetricAlgorithm
.Create ("DES");
353 case XmlEncTripleDESUrl
:
354 case XmlEncTripleDESKeyWrapUrl
:
355 symAlg
= SymmetricAlgorithm
.Create ("TripleDES");
358 throw new CryptographicException ("symAlgUri");
364 private static string GetAlgorithmUri (SymmetricAlgorithm symAlg
)
366 if (symAlg
is Rijndael
)
368 switch (symAlg
.KeySize
) {
370 return XmlEncAES128Url
;
372 return XmlEncAES192Url
;
374 return XmlEncAES256Url
;
377 else if (symAlg
is DES
)
379 else if (symAlg
is TripleDES
)
380 return XmlEncTripleDESUrl
;
382 throw new ArgumentException ("symAlg");
385 private static string GetKeyWrapAlgorithmUri (object keyAlg
)
387 if (keyAlg
is Rijndael
)
389 switch (((Rijndael
) keyAlg
).KeySize
) {
391 return XmlEncAES128KeyWrapUrl
;
393 return XmlEncAES192KeyWrapUrl
;
395 return XmlEncAES256KeyWrapUrl
;
398 else if (keyAlg
is RSA
)
399 return XmlEncRSA15Url
;
400 else if (keyAlg
is TripleDES
)
401 return XmlEncTripleDESKeyWrapUrl
;
403 throw new ArgumentException ("keyAlg");
406 public virtual byte[] GetDecryptionIV (EncryptedData encryptedData
, string symAlgUri
)
408 if (encryptedData
== null)
409 throw new ArgumentNullException ("encryptedData");
411 SymmetricAlgorithm symAlg
= GetAlgorithm (symAlgUri
);
412 byte[] iv
= new Byte
[symAlg
.BlockSize
/ 8];
413 Buffer
.BlockCopy (encryptedData
.CipherData
.CipherValue
, 0, iv
, 0, iv
.Length
);
417 public virtual SymmetricAlgorithm
GetDecryptionKey (EncryptedData encryptedData
, string symAlgUri
)
419 if (encryptedData
== null)
420 throw new ArgumentNullException ("encryptedData");
421 if (symAlgUri
== null)
424 SymmetricAlgorithm symAlg
= GetAlgorithm (symAlgUri
);
425 symAlg
.IV
= GetDecryptionIV (encryptedData
, encryptedData
.EncryptionMethod
.KeyAlgorithm
);
426 KeyInfo keyInfo
= encryptedData
.KeyInfo
;
427 foreach (KeyInfoClause clause
in keyInfo
) {
428 if (clause
is KeyInfoEncryptedKey
) {
429 symAlg
.Key
= DecryptEncryptedKey (((KeyInfoEncryptedKey
) clause
).EncryptedKey
);
436 public virtual XmlElement
GetIdElement (XmlDocument document
, string idValue
)
438 if ((document
== null) || (idValue
== null))
441 // this works only if there's a DTD or XSD available to define the ID
442 XmlElement xel
= document
.GetElementById (idValue
);
444 // search an "undefined" ID
445 xel
= (XmlElement
) document
.SelectSingleNode ("//*[@Id='" + idValue
+ "']");
450 public void ReplaceData (XmlElement inputElement
, byte[] decryptedData
)
452 if (inputElement
== null)
453 throw new ArgumentNullException ("inputElement");
454 if (decryptedData
== null)
455 throw new ArgumentNullException ("decryptedData");
457 XmlDocument ownerDocument
= inputElement
.OwnerDocument
;
458 XmlTextReader reader
= new XmlTextReader (new StringReader (Encoding
.GetString (decryptedData
, 0, decryptedData
.Length
)));
459 reader
.MoveToContent ();
460 XmlNode node
= ownerDocument
.ReadNode (reader
);
461 inputElement
.ParentNode
.ReplaceChild (node
, inputElement
);
464 public static void ReplaceElement (XmlElement inputElement
, EncryptedData encryptedData
, bool content
)
466 if (inputElement
== null)
467 throw new ArgumentNullException ("inputElement");
468 if (encryptedData
== null)
469 throw new ArgumentNullException ("encryptedData");
471 XmlDocument ownerDocument
= inputElement
.OwnerDocument
;
472 inputElement
.ParentNode
.ReplaceChild (encryptedData
.GetXml (ownerDocument
), inputElement
);
475 private byte[] Transform (byte[] data
, ICryptoTransform transform
)
477 return Transform (data
, transform
, 0, false);
480 private byte[] Transform (byte[] data
, ICryptoTransform transform
, int blockOctetCount
, bool trimPadding
)
482 MemoryStream output
= new MemoryStream ();
483 CryptoStream crypto
= new CryptoStream (output
, transform
, CryptoStreamMode
.Write
);
484 crypto
.Write (data
, 0, data
.Length
);
486 crypto
.FlushFinalBlock ();
488 // strip padding (see xmlenc spec 5.2)
491 trimSize
= output
.GetBuffer () [output
.Length
- 1];
492 // It should not happen, but somehow .NET allows such cipher
493 // data as if there were no padding.
494 if (trimSize
> blockOctetCount
)
496 byte[] result
= new byte [output
.Length
- blockOctetCount
- trimSize
];
497 Array
.Copy (output
.GetBuffer (), blockOctetCount
, result
, 0, result
.Length
);
505 #endregion // Methods