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