[sgen] Fix pointer access.
[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 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Globalization;
31 using System.IO;
32 using System.Runtime.CompilerServices;
33 using System.Runtime.InteropServices;
34 using System.Security;
35 using System.Security.Cryptography;
36 using System.Security.Permissions;
37 using System.Text;
38
39 using Mono.Xml;
40
41 namespace Mono.Security.Cryptography {
42
43         /* File name
44          * [type][unique name][key number].xml
45          * 
46          * where
47          *      type            CspParameters.ProviderType
48          *      unique name     A unique name for the keypair, which is
49          *                      a. default (for a provider default keypair)
50          *                      b. a GUID derived from
51          *                              i. random if no container name was
52          *                              specified at generation time
53          *                              ii. the MD5 hash of the container
54          *                              name (CspParameters.KeyContainerName)
55          *      key number      CspParameters.KeyNumber
56          * 
57          * File format
58          * <KeyPair>
59          *      <Properties>
60          *              <Provider Name="" Type=""/>
61          *              <Container Name=""/>
62          *      </Properties>
63          *      <KeyValue Id="">
64          *              RSAKeyValue, DSAKeyValue ...
65          *      </KeyValue>
66          * </KeyPair>
67          */
68
69         /* NOTES
70          * 
71          * - There's NO confidentiality / integrity built in this
72          * persistance mechanism. The container directories (both user and
73          * machine) are created with restrited ACL. The ACL is also checked
74          * when a key is accessed (so totally public keys won't be used).
75          * see /mono/mono/metadata/security.c for implementation
76          * 
77          * - As we do not use CSP we limit ourselves to provider types (not 
78          * names). This means that for a same type and container type, but 
79          * two different provider names) will return the same keypair. This
80          * should work as CspParameters always requires a csp type in its
81          * constructors.
82          * 
83          * - Assert (CAS) are used so only the OS permission will limit access
84          * to the keypair files. I.e. this will work even in high-security 
85          * scenarios where users do not have access to file system (e.g. web 
86          * application). We can allow this because the filename used is 
87          * TOTALLY under our control (no direct user input is used).
88          * 
89          * - You CAN'T changes properties of the keypair once it's been
90          * created (saved). You must remove the container than save it 
91          * back. This is the same behaviour as CSP under Windows.
92          */
93
94 #if INSIDE_CORLIB
95         internal
96 #else
97         public 
98 #endif
99         class KeyPairPersistence {
100         
101                 private static bool _userPathExists; // check at 1st use
102                 private static string _userPath;
103                 
104                 private static bool _machinePathExists; // check at 1st use
105                 private static string _machinePath;
106
107                 private CspParameters _params;
108                 private string _keyvalue;
109                 private string _filename;
110                 private string _container;
111
112                 // constructors
113
114                 public KeyPairPersistence (CspParameters parameters) 
115                         : this (parameters, null)
116                 {
117                 }
118
119                 public KeyPairPersistence (CspParameters parameters, string keyPair) 
120                 {
121                         if (parameters == null)
122                                 throw new ArgumentNullException ("parameters");
123
124                         _params = Copy (parameters);
125                         _keyvalue = keyPair;
126                 }
127
128                 // properties
129
130                 public string Filename {
131                         get { 
132                                 if (_filename == null) {
133                                         _filename = String.Format (CultureInfo.InvariantCulture,
134                                                 "[{0}][{1}][{2}].xml", 
135                                                 _params.ProviderType, 
136                                                 this.ContainerName, 
137                                                 _params.KeyNumber);
138                                         if (UseMachineKeyStore)
139                                                 _filename = Path.Combine (MachinePath, _filename);
140                                         else
141                                                 _filename = Path.Combine (UserPath, _filename);
142                                 }
143                                 return _filename; 
144                         }
145                 }
146
147                 public string KeyValue {
148                         get { return _keyvalue; }
149                         set { 
150                                 if (this.CanChange)
151                                         _keyvalue = value; 
152                         }
153                 }
154
155                 // return a (read-only) copy
156                 public CspParameters Parameters {
157                         get { return Copy (_params); }
158                 }
159
160                 // methods
161
162                 public bool Load () 
163                 {
164                         // see NOTES
165 // FIXME                new FileIOPermission (FileIOPermissionAccess.Read, this.Filename).Assert ();
166
167                         bool result = File.Exists (this.Filename);
168                         if (result) {
169                                 using (StreamReader sr = File.OpenText (this.Filename)) {
170                                         FromXml (sr.ReadToEnd ());
171                                 }
172                         }
173                         return result;
174                 }
175
176                 public void Save () 
177                 {
178                         // see NOTES
179 // FIXME                new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
180
181                         using (FileStream fs = File.Open (this.Filename, FileMode.Create)) {
182                                 StreamWriter sw = new StreamWriter (fs, Encoding.UTF8);
183                                 sw.Write (this.ToXml ());
184                                 sw.Close ();
185                         }
186                         // apply protection to newly created files
187                         if (UseMachineKeyStore)
188                                 ProtectMachine (Filename);
189                         else
190                                 ProtectUser (Filename);
191                 }
192
193                 public void Remove () 
194                 {
195                         // see NOTES
196 // FIXME                new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
197
198                         File.Delete (this.Filename);
199                         // it's now possible to change the keypair un the container
200                 }
201
202                 // private static stuff
203
204                 static object lockobj = new object ();
205                 
206                 private static string UserPath {
207                         get {
208                                 lock (lockobj) {
209                                         if ((_userPath == null) || (!_userPathExists)) {
210                                                 _userPath = Path.Combine (
211                                                         Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
212                                                         ".mono");
213                                                 _userPath = Path.Combine (_userPath, "keypairs");
214
215                                                 _userPathExists = Directory.Exists (_userPath);
216                                                 if (!_userPathExists) {
217                                                         try {
218                                                                 Directory.CreateDirectory (_userPath);                                          
219                                                         }
220                                                         catch (Exception e) {
221                                                                 string msg = Locale.GetText ("Could not create user key store '{0}'.");
222                                                                 throw new CryptographicException (String.Format (msg, _userPath), e);
223                                                         }
224
225                                                         if (!ProtectUser (_userPath)) {
226                                                                 string msg = Locale.GetText ("Could not secure user key store '{0}'.");
227                                                                 throw new IOException (String.Format (msg, _userPath));
228                                                         } 
229
230                                                         _userPathExists = true;
231                                                 }
232                                         }
233                                 }
234                                 // is it properly protected ?
235                                 if (!IsUserProtected (_userPath)) {
236                                         string msg = Locale.GetText ("Improperly protected user's key pairs in '{0}'.");
237                                         throw new CryptographicException (String.Format (msg, _userPath));
238                                 }
239                                 return _userPath;
240                         }
241                 }
242
243                 private static string MachinePath {
244                         get {
245                                 lock (lockobj) {
246                                         if ((_machinePath == null) || (!_machinePathExists)) {
247                                                 _machinePath = Path.Combine (
248                                                         Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData),
249                                                         ".mono");
250                                                 _machinePath = Path.Combine (_machinePath, "keypairs");
251
252                                                 _machinePathExists = Directory.Exists (_machinePath);
253                                                 if (!_machinePathExists) {
254                                                         try {
255                                                                 Directory.CreateDirectory (_machinePath);
256                                                         }
257                                                         catch (Exception e) {
258                                                                 string msg = Locale.GetText ("Could not create machine key store '{0}'.");
259                                                                 throw new CryptographicException (String.Format (msg, _machinePath), e);
260                                                         }
261
262                                                         if (!ProtectMachine (_machinePath)) {
263                                                                 string msg = Locale.GetText ("Could not secure machine key store '{0}'.");
264                                                                 throw new IOException (String.Format (msg, _machinePath));
265                                                         }
266
267                                                         _machinePathExists = true;
268                                                 }
269                                         }
270                                 }
271                                 // is it properly protected ?
272                                 if (!IsMachineProtected (_machinePath)) {
273                                         string msg = Locale.GetText ("Improperly protected machine's key pairs in '{0}'.");
274                                         throw new CryptographicException (String.Format (msg, _machinePath));
275                                 }
276                                 return _machinePath;
277                         }
278                 }
279
280 #if INSIDE_CORLIB
281                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
282                 internal static extern bool _CanSecure (string root);
283
284                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
285                 internal static extern bool _ProtectUser (string path);
286
287                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
288                 internal static extern bool _ProtectMachine (string path);
289
290                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
291                 internal static extern bool _IsUserProtected (string path);
292
293                 [MethodImplAttribute (MethodImplOptions.InternalCall)]
294                 internal static extern bool _IsMachineProtected (string path);
295 #else
296                 // Mono.Security.dll assembly can't use the internal 
297                 // call (and still run with other runtimes)
298
299                 // Note: Class is only available in Mono.Security.dll as
300                 // a management helper (e.g. build a GUI app)
301
302                 internal static bool _CanSecure (string root) 
303                 {
304                         return true;
305                 }
306
307                 internal static bool _ProtectUser (string path)
308                 {
309                         return true;
310                 }
311
312                 internal static bool _ProtectMachine (string path)
313                 {
314                         return true;
315                 }
316
317                 internal static bool _IsUserProtected (string path)
318                 {
319                         return true;
320                 }
321
322                 internal static bool _IsMachineProtected (string path)
323                 {
324                         return true;
325                 }
326 #endif
327                 // private stuff
328
329                 private static bool CanSecure (string path) 
330                 {
331                         // we assume POSIX filesystems can always be secured
332
333                         // check for Unix platforms - see FAQ for more details
334                         // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
335                         int platform = (int) Environment.OSVersion.Platform;
336                         if ((platform == 4) || (platform == 128) || (platform == 6))
337                                 return true;
338
339                         // while we ask the runtime for Windows OS
340                         return _CanSecure (Path.GetPathRoot (path));
341                 }
342
343                 private static bool ProtectUser (string path)
344                 {
345                         // we cannot protect on some filsystem (like FAT)
346                         if (CanSecure (path)) {
347                                 return _ProtectUser (path);
348                         }
349                         // but Mono still needs to run on them :(
350                         return true;
351                 }
352
353                 private static bool ProtectMachine (string path)
354                 {
355                         // we cannot protect on some filsystem (like FAT)
356                         if (CanSecure (path)) {
357                                 return _ProtectMachine (path);
358                         }
359                         // but Mono still needs to run on them :(
360                         return true;
361                 }
362
363                 private static bool IsUserProtected (string path)
364                 {
365                         // we cannot protect on some filsystem (like FAT)
366                         if (CanSecure (path)) {
367                                 return _IsUserProtected (path);
368                         }
369                         // but Mono still needs to run on them :(
370                         return true;
371                 }
372
373                 private static bool IsMachineProtected (string path)
374                 {
375                         // we cannot protect on some filsystem (like FAT)
376                         if (CanSecure (path)) {
377                                 return _IsMachineProtected (path);
378                         }
379                         // but Mono still needs to run on them :(
380                         return true;
381                 }
382                 
383                 private bool CanChange {
384                         get { return (_keyvalue == null); }
385                 }
386
387                 private bool UseDefaultKeyContainer {
388                         get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); }
389                 }
390
391                 private bool UseMachineKeyStore {
392                         get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); }
393                 }
394
395                 private string ContainerName {
396                         get {
397                                 if (_container == null) {
398                                         if (UseDefaultKeyContainer) {
399                                                 // easy to spot
400                                                 _container = "default";
401                                         }
402                                         else if ((_params.KeyContainerName == null) || (_params.KeyContainerName.Length == 0)) {
403                                                 _container = Guid.NewGuid ().ToString ();
404                                         }
405                                         else {
406                                                 // we don't want to trust the key container name as we don't control it
407                                                 // anyway some characters may not be compatible with the file system
408                                                 byte[] data = Encoding.UTF8.GetBytes (_params.KeyContainerName);
409                                                 // Note: We use MD5 as it is faster than SHA1 and has the same length 
410                                                 // as a GUID. Recent problems found in MD5 (like collisions) aren't a
411                                                 // problem in this case.
412                                                 MD5 hash = MD5.Create ();
413                                                 byte[] result = hash.ComputeHash (data);
414                                                 _container = new Guid (result).ToString ();
415                                         }
416                                 }
417                                 return _container;
418                         }
419                 }
420
421                 // we do not want any changes after receiving the csp informations
422                 private CspParameters Copy (CspParameters p) 
423                 {
424                         CspParameters copy = new CspParameters (p.ProviderType, p.ProviderName, p.KeyContainerName);
425                         copy.KeyNumber = p.KeyNumber;
426                         copy.Flags = p.Flags;
427                         return copy;
428                 }
429
430                 private void FromXml (string xml) 
431                 {
432                         SecurityParser sp = new SecurityParser ();
433                         sp.LoadXml (xml);
434
435                         SecurityElement root = sp.ToXml ();
436                         if (root.Tag == "KeyPair") {
437                                 //SecurityElement prop = root.SearchForChildByTag ("Properties");
438                                 SecurityElement keyv = root.SearchForChildByTag ("KeyValue");
439                                 if (keyv.Children.Count > 0)
440                                         _keyvalue = keyv.Children [0].ToString ();
441                                 // Note: we do not read other stuff because 
442                                 // it can't be changed after key creation
443                         }
444                 }
445
446                 private string ToXml () 
447                 {
448                         // note: we do not use SecurityElement here because the
449                         // keypair is a XML string (requiring parsing)
450                         StringBuilder xml = new StringBuilder ();
451                         xml.AppendFormat ("<KeyPair>{0}\t<Properties>{0}\t\t<Provider ", Environment.NewLine);
452                         if ((_params.ProviderName != null) && (_params.ProviderName.Length != 0)) {
453                                 xml.AppendFormat ("Name=\"{0}\" ", _params.ProviderName);
454                         }
455                         xml.AppendFormat ("Type=\"{0}\" />{1}\t\t<Container ", _params.ProviderType, Environment.NewLine);
456                         xml.AppendFormat ("Name=\"{0}\" />{1}\t</Properties>{1}\t<KeyValue", this.ContainerName, Environment.NewLine);
457                         if (_params.KeyNumber != -1) {
458                                 xml.AppendFormat (" Id=\"{0}\" ", _params.KeyNumber);
459                         }
460                         xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine);
461                         return xml.ToString ();
462                 }
463         }
464 }