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