2004-02-07 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Cryptography / KeyPairPersistence.cs
1 //
2 // KeyPairPersistence.cs: Keypair persistence
3 //
4 // Author:
5 //      Sebastien Pouliot <sebastien@ximian.com>
6 //
7 // (C) 2004 Novell (http://www.novell.com)
8 //
9
10 using System;
11 using System.IO;
12 using System.Runtime.InteropServices;
13 using System.Security;
14 using System.Security.Cryptography;
15 using System.Security.Permissions;
16 using System.Text;
17
18 using Mono.Xml;
19
20 namespace Mono.Security.Cryptography {
21
22         /* File name
23          * [type][unique name][key number].xml
24          * 
25          * where
26          *      type            CspParameters.ProviderType
27          *      unique name     A unique name for the keypair, which is
28          *                      a. default (for a provider default keypair)
29          *                      b. a GUID derived from
30          *                              i. random if no container name was
31          *                              specified at generation time
32          *                              ii. the MD5 hash of the container
33          *                              name (CspParameters.KeyContainerName)
34          *      key number      CspParameters.KeyNumber
35          * 
36          * File format
37          * <KeyPair>
38          *      <Properties>
39          *              <Provider Name="" Type=""/>
40          *              <Container Name=""/>
41          *      </Properties>
42          *      <KeyValue Id="">
43          *              RSAKeyValue, DSAKeyValue ...
44          *      </KeyValue>
45          * </KeyPair>
46          */
47
48         /* NOTES
49          * 
50          * - There's NO confidentiality / integrity built in this
51          * persistance mechanism. You better protect your directories
52          * ACL correctly!
53          * 
54          * - As we do not use CSP we limit ourselves to provider types (not 
55          * names). This means that for a same type and container type, but 
56          * two different provider names) will return the same keypair. This
57          * should work as CspParameters always requires a csp type in its
58          * constructors.
59          * 
60          * - Assert (CAS) are used so only the OS permission will limit access
61          * to the keypair files. I.e. this will work even in high-security 
62          * scenarios where users do not have access to file system (e.g. web 
63          * application). We can allow this because the filename used is 
64          * TOTALLY under our control (no direct user input is used).
65          * 
66          * - You CAN'T changes properties of the keypair once it's been
67          * created (saved). You must remove the container than save it 
68          * back. This is the same behaviour as windows.
69          */
70
71         /* TODO
72          * 
73          * - Where do we store the machine keys ?
74          * - zeroize _keyvalue before setting to null !!!
75          */
76
77 #if INSIDE_CORLIB
78         internal
79 #else
80         public 
81 #endif
82         class KeyPairPersistence {
83
84                 private static bool _pathExists = false; // check at 1st use
85                 private static string _path;
86
87                 private CspParameters _params;
88                 private string _keyvalue;
89                 private string _filename;
90                 private string _container;
91
92                 // constructors
93
94                 public KeyPairPersistence (CspParameters parameters) 
95                         : this (parameters, null) {}
96
97                 public KeyPairPersistence (CspParameters parameters, string keypair) 
98                 {
99                         if (parameters == null)
100                                 throw new ArgumentNullException ("parameters");
101
102                         _params = Copy (parameters);
103                         _keyvalue = keypair;
104                 }
105
106                 ~KeyPairPersistence () 
107                 {
108                         Clear ();
109                 }
110
111                 // properties
112
113                 public string Filename {
114                         get { 
115                                 if (_filename == null) {
116                                         _filename = String.Format ("[{0}][{1}][{2}].xml", 
117                                                 _params.ProviderType, 
118                                                 this.ContainerName, 
119                                                 _params.KeyNumber);
120                                         _filename = System.IO.Path.Combine (Path, _filename);
121                                 }
122                                 return _filename; 
123                         }
124                 }
125
126                 public string KeyValue {
127                         get { return _keyvalue; }
128                         set { 
129                                 if (this.CanChange)
130                                         _keyvalue = value; 
131                         }
132                 }
133
134                 // return a (read-only) copy
135                 public CspParameters Parameters {
136                         get { return Copy (_params); }
137                 }
138
139                 // methods
140
141                 public void Clear () 
142                 {
143                         Zeroize (ref _keyvalue);
144                 }
145
146                 public bool Load () 
147                 {
148                         // see NOTES
149                         new FileIOPermission (FileIOPermissionAccess.Read, Path).Assert ();
150
151                         bool result = File.Exists (this.Filename);
152                         if (result) {
153                                 string xml = null;
154                                 try {
155                                         using (StreamReader sr = File.OpenText (this.Filename)) {
156                                                 xml = sr.ReadToEnd ();
157                                         }
158                                         FromXml (xml);
159                                 }
160                                 finally {
161                                         Zeroize (ref xml);
162                                 }
163                         }
164                         return result;
165                 }
166
167                 public void Save () 
168                 {
169                         // see NOTES
170                         new FileIOPermission (FileIOPermissionAccess.Write, Path).Assert ();
171
172                         using (FileStream fs = File.Open (this.Filename, FileMode.Create)) {
173                                 StreamWriter sw = new StreamWriter (fs, Encoding.UTF8);
174                                 sw.Write (this.ToXml ());
175                                 sw.Close ();
176                         }
177                 }
178
179                 public void Remove () 
180                 {
181                         // see NOTES
182                         new FileIOPermission (FileIOPermissionAccess.Write, Path).Assert ();
183
184                         Clear ();
185                         File.Delete (this.Filename);
186                         // it's now possible to change the keypair un the container
187                 }
188
189                 // private stuff
190
191                 private static string Path {
192                         get {
193                                 if (_path == null) {
194                                         lock (typeof (KeyPairPersistence)) {
195                                                 // TODO ? where to put machine key pairs ?
196                                                 _path = System.IO.Path.Combine (
197                                                         Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
198                                                         ".mono");
199                                                 _path = System.IO.Path.Combine (_path, "keypairs");
200
201                                                 if (!_pathExists) {
202                                                         _pathExists = Directory.Exists (_path);
203                                                         if (!_pathExists) {
204                                                                 Directory.CreateDirectory (_path);
205                                                         }
206                                                 }
207                                         }
208                                 }
209                                 return _path;
210                         }
211                 }
212
213                 private bool CanChange {
214                         get { return (_keyvalue == null); }
215                 }
216
217                 private bool UseDefaultKeyContainer {
218                         get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); }
219                 }
220
221                 private bool UseMachineKeyStore {
222                         get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); }
223                 }
224
225                 private string ContainerName {
226                         get {
227                                 if (_container == null) {
228                                         if (UseDefaultKeyContainer) {
229                                                 // easy to spot
230                                                 _container = "default";
231                                         }
232                                         else if ((_params.KeyContainerName == null) || (_params.KeyContainerName == String.Empty)) {
233                                                 _container = Guid.NewGuid ().ToString ();
234                                         }
235                                         else {
236                                                 // we don't want to trust the key container name as we don't control it
237                                                 // anyway some characters may not be compatible with the file system
238                                                 byte[] data = Encoding.UTF8.GetBytes (_params.KeyContainerName);
239                                                 MD5 hash = MD5.Create ();       // faster than SHA1, same length as GUID
240                                                 byte[] result = hash.ComputeHash (data);
241                                                 _container = new Guid (result).ToString ();
242                                         }
243                                 }
244                                 return _container;
245                         }
246                 }
247
248                 // we do not want any changes after receiving the csp informations
249                 private CspParameters Copy (CspParameters p) 
250                 {
251                         CspParameters copy = new CspParameters (p.ProviderType, p.ProviderName, p.KeyContainerName);
252                         copy.KeyNumber = p.KeyNumber;
253                         copy.Flags = p.Flags;
254                         return copy;
255                 }
256
257                 private void FromXml (string xml) 
258                 {
259                         SecurityParser sp = new SecurityParser ();
260                         sp.LoadXml (xml);
261
262                         SecurityElement root = sp.ToXml ();
263                         if (root.Tag == "KeyPair") {
264                                 SecurityElement prop = root.SearchForChildByTag ("Properties");
265                                 SecurityElement keyv = root.SearchForChildByTag ("KeyValue");
266                                 if (keyv.Children.Count > 0)
267                                         _keyvalue = keyv.Children [0].ToString ();
268                                 // Note: we do not read other stuff because 
269                                 // it can't be changed after key creation
270                         }
271                 }
272
273                 private string ToXml () 
274                 {
275                         // note: we do not use SecurityElement here because the
276                         // keypair is a XML string (requiring parsing)
277                         StringBuilder xml = new StringBuilder ();
278                         xml.AppendFormat ("<KeyPair>{0}\t<Properties>{0}\t\t<Provider ", Environment.NewLine);
279                         if ((_params.ProviderName != null) && (_params.ProviderName != String.Empty)) {
280                                 xml.AppendFormat ("Name=\"{0}\" ", _params.ProviderName);
281                         }
282                         xml.AppendFormat ("Type=\"{0}\" />{1}\t\t<Container ", _params.ProviderType, Environment.NewLine);
283                         xml.AppendFormat ("Name=\"{0}\" />{1}\t</Properties>{1}\t<KeyValue", this.ContainerName, Environment.NewLine);
284                         if (_params.KeyNumber != -1) {
285                                 xml.AppendFormat (" Id=\"{0}\" ", _params.KeyNumber);
286                         }
287                         xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine);
288                         return xml.ToString ();
289                 }
290
291                 private void Zeroize (ref string s) 
292                 {
293                         if (s != null) {
294                                 // TODO - zeroize the private information ?
295                                 // how can we track how it was used by other objects (copies?)
296                                 // and/or reverting to unsafe code ?
297                                 s = null;
298                         }
299                 }
300         }
301 }