2004-09-17 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Authenticode / PrivateKey.cs
1 //
2 // PrivateKey.cs - Private Key (PVK) Format Implementation
3 //
4 // Author:
5 //      Sebastien Pouliot <sebastien@ximian.com>
6 //
7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com)
8 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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 using System;
31 using System.Globalization;
32 using System.IO;
33 using System.Security.Cryptography;
34 using System.Text;
35
36 using Mono.Security.Cryptography;
37
38 namespace Mono.Security.Authenticode {
39
40         // References:
41         // a.   http://www.drh-consultancy.demon.co.uk/pvk.html
42
43         public class PrivateKey {
44
45                 private const uint magic = 0xb0b5f11e;
46
47                 private bool encrypted;
48                 private RSA rsa;
49                 private bool weak;
50                 private int keyType;
51
52                 public PrivateKey () 
53                 {
54                         keyType = 2;    // required for MS makecert !!!
55                 }
56
57                 public PrivateKey (byte[] data, string password) 
58                 {
59                         if (data == null)
60                                 throw new ArgumentNullException ("data");
61
62                         if (!Decode (data, password)) {
63                                 throw new CryptographicException (
64                                         Locale.GetText ("Invalid data and/or password"));
65                         }
66                 }
67
68                 public bool Encrypted {
69                         get { return encrypted; }
70                 }
71
72                 public int KeyType {
73                         get { return keyType; }
74                         set { keyType = value; }
75                 }
76
77                 public RSA RSA {
78                         get { return rsa; }
79                         set { rsa = value; }
80                 }
81
82                 public bool Weak {
83                         get { return ((encrypted) ? weak : true); }
84                         set { weak = value; }
85                 }
86
87                 private byte[] DeriveKey (byte[] salt, string password) 
88                 {
89                         byte[] pwd = Encoding.ASCII.GetBytes (password);
90                         SHA1 sha1 = (SHA1)SHA1.Create ();
91                         sha1.TransformBlock (salt, 0, salt.Length, salt, 0);
92                         sha1.TransformFinalBlock (pwd, 0, pwd.Length);
93                         byte[] key = new byte [16];
94                         Buffer.BlockCopy (sha1.Hash, 0, key, 0, 16);
95                         sha1.Clear ();
96                         Array.Clear (pwd, 0, pwd.Length);
97                         return key;     
98                 }
99
100                 private bool Decode (byte[] pvk, string password) 
101                 {
102                         // DWORD magic
103                         if (BitConverter.ToUInt32 (pvk, 0) != magic)
104                                 return false;
105                         // DWORD reserved
106                         if (BitConverter.ToUInt32 (pvk, 4) != 0x0)
107                                 return false;
108                         // DWORD keytype
109                         keyType = BitConverter.ToInt32 (pvk, 8);
110                         // DWORD encrypted
111                         encrypted = (BitConverter.ToUInt32 (pvk, 12) == 1);
112                         // DWORD saltlen
113                         int saltlen = BitConverter.ToInt32 (pvk, 16);
114                         // DWORD keylen
115                         int keylen = BitConverter.ToInt32 (pvk, 20);
116                         byte[] keypair = new byte [keylen];
117                         Buffer.BlockCopy (pvk, 24 + saltlen, keypair, 0, keylen);
118                         // read salt (if present)
119                         if (saltlen > 0) {
120                                 if (password == null)
121                                         return false;
122
123                                 byte[] salt = new byte [saltlen];
124                                 Buffer.BlockCopy (pvk, 24, salt, 0, saltlen);
125                                 // first try with full (128) bits
126                                 byte[] key = DeriveKey (salt, password);
127                                 // decrypt in place and try this
128                                 RC4 rc4 = RC4.Create ();
129                                 ICryptoTransform dec = rc4.CreateDecryptor (key, null);
130                                 dec.TransformBlock (keypair, 8, keypair.Length - 8, keypair, 8);
131                                 try {
132                                         rsa = CryptoConvert.FromCapiPrivateKeyBlob (keypair);
133                                         weak = false;
134                                 }
135                                 catch (CryptographicException) {
136                                         weak = true;
137                                         // second chance using weak crypto
138                                         Buffer.BlockCopy (pvk, 24 + saltlen, keypair, 0, keylen);
139                                         // truncate the key to 40 bits
140                                         Array.Clear (key, 5, 11);
141                                         // decrypt
142                                         RC4 rc4b = RC4.Create ();
143                                         dec = rc4b.CreateDecryptor (key, null);
144                                         dec.TransformBlock (keypair, 8, keypair.Length - 8, keypair, 8);
145                                         rsa = CryptoConvert.FromCapiPrivateKeyBlob (keypair);
146                                 }
147                                 Array.Clear (key, 0, key.Length);
148                         }
149                         else  {
150                                 weak = true;
151                                 // read unencrypted keypair
152                                 rsa = CryptoConvert.FromCapiPrivateKeyBlob (keypair);
153                                 Array.Clear (keypair, 0, keypair.Length);
154                         }
155
156                         // zeroize pvk (which could contain the unencrypted private key)
157                         Array.Clear (pvk, 0, pvk.Length);
158                         
159                         return (rsa != null);
160                 }
161
162                 public void Save (string filename) 
163                 {
164                         Save (filename, null);
165                 }
166
167                 public void Save (string filename, string password) 
168                 {
169                         if (filename == null)
170                                 throw new ArgumentNullException ("filename");
171
172                         byte[] blob = null;
173                         FileStream fs = File.Open (filename, FileMode.Create, FileAccess.Write);
174                         try {
175                                 // header
176                                 byte[] empty = new byte [4];
177                                 byte[] data = BitConverter.GetBytes (magic);
178                                 fs.Write (data, 0, 4);  // magic
179                                 fs.Write (empty, 0, 4); // reserved
180                                 data = BitConverter.GetBytes (keyType);
181                                 fs.Write (data, 0, 4);  // key type
182
183                                 encrypted = (password != null);
184                                 blob = CryptoConvert.ToCapiPrivateKeyBlob (rsa);
185                                 if (encrypted) {
186                                         data = BitConverter.GetBytes (1);
187                                         fs.Write (data, 0, 4);  // encrypted
188                                         data = BitConverter.GetBytes (16);
189                                         fs.Write (data, 0, 4);  // saltlen
190                                         data = BitConverter.GetBytes (blob.Length);
191                                         fs.Write (data, 0, 4);          // keylen
192
193                                         byte[] salt = new byte [16];
194                                         RC4 rc4 = RC4.Create ();
195                                         byte[] key = null;
196                                         try {
197                                                 // generate new salt (16 bytes)
198                                                 RandomNumberGenerator rng = RandomNumberGenerator.Create ();
199                                                 rng.GetBytes (salt);
200                                                 fs.Write (salt, 0, salt.Length);
201                                                 key = DeriveKey (salt, password);
202                                                 if (Weak)
203                                                         Array.Clear (key, 5, 11);
204                                                 ICryptoTransform enc = rc4.CreateEncryptor (key, null);
205                                                 // we don't encrypt the header part of the BLOB
206                                                 enc.TransformBlock (blob, 8, blob.Length - 8, blob, 8);
207                                         }
208                                         finally {
209                                                 Array.Clear (salt, 0, salt.Length);
210                                                 Array.Clear (key, 0, key.Length);
211                                                 rc4.Clear ();
212                                         }
213                                 }
214                                 else {
215                                         fs.Write (empty, 0, 4); // encrypted
216                                         fs.Write (empty, 0, 4); // saltlen
217                                         data = BitConverter.GetBytes (blob.Length);
218                                         fs.Write (data, 0, 4);          // keylen
219                                 }
220                 
221                                 fs.Write (blob, 0, blob.Length);
222                         }
223                         finally {
224                                 // BLOB may include an uncrypted keypair
225                                 Array.Clear (blob, 0, blob.Length);
226                                 fs.Close ();
227                         }
228                 }
229
230                 static public PrivateKey CreateFromFile (string filename) 
231                 {
232                         return CreateFromFile (filename, null);
233                 }
234
235                 static public PrivateKey CreateFromFile (string filename, string password) 
236                 {
237                         if (filename == null)
238                                 throw new ArgumentNullException ("filename");
239
240                         byte[] pvk = null;                              
241                         using (FileStream fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
242                                 pvk = new byte [fs.Length];
243                                 fs.Read (pvk, 0, pvk.Length);
244                                 fs.Close ();
245                         }
246                         return new PrivateKey (pvk, password);
247                 }
248         }
249 }