bring Mono Security to monotouch
[mcs.git] / class / corlib / Mono.Security.Cryptography / KeyPairPersistence.cs
blob9a2cad9ba6c99df20f6897aadfbda34cd156ea10
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.
29 #if !NET_2_1 || MONOTOUCH
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;
41 using Mono.Xml;
43 namespace Mono.Security.Cryptography {
45 /* File name
46 * [type][unique name][key number].xml
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
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>
71 /* NOTES
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
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.
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).
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.
96 #if INSIDE_CORLIB
97 internal
98 #else
99 public
100 #endif
101 class KeyPairPersistence {
103 private static bool _userPathExists = false; // check at 1st use
104 private static string _userPath;
106 private static bool _machinePathExists = false; // check at 1st use
107 private static string _machinePath;
109 private CspParameters _params;
110 private string _keyvalue;
111 private string _filename;
112 private string _container;
114 // constructors
116 public KeyPairPersistence (CspParameters parameters)
117 : this (parameters, null)
121 public KeyPairPersistence (CspParameters parameters, string keyPair)
123 if (parameters == null)
124 throw new ArgumentNullException ("parameters");
126 _params = Copy (parameters);
127 _keyvalue = keyPair;
130 // properties
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);
145 return _filename;
149 public string KeyValue {
150 get { return _keyvalue; }
151 set {
152 if (this.CanChange)
153 _keyvalue = value;
157 // return a (read-only) copy
158 public CspParameters Parameters {
159 get { return Copy (_params); }
162 // methods
164 public bool Load ()
166 // see NOTES
167 // FIXME new FileIOPermission (FileIOPermissionAccess.Read, this.Filename).Assert ();
169 bool result = File.Exists (this.Filename);
170 if (result) {
171 using (StreamReader sr = File.OpenText (this.Filename)) {
172 FromXml (sr.ReadToEnd ());
175 return result;
178 public void Save ()
180 // see NOTES
181 // FIXME new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
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 ();
188 // apply protection to newly created files
189 if (UseMachineKeyStore)
190 ProtectMachine (Filename);
191 else
192 ProtectUser (Filename);
195 public void Remove ()
197 // see NOTES
198 // FIXME new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
200 File.Delete (this.Filename);
201 // it's now possible to change the keypair un the container
204 // private static stuff
206 static object lockobj = new object ();
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");
217 _userPathExists = Directory.Exists (_userPath);
218 if (!_userPathExists) {
219 try {
220 Directory.CreateDirectory (_userPath);
221 ProtectUser (_userPath);
222 _userPathExists = true;
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);
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));
236 return _userPath;
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");
249 _machinePathExists = Directory.Exists (_machinePath);
250 if (!_machinePathExists) {
251 try {
252 Directory.CreateDirectory (_machinePath);
253 ProtectMachine (_machinePath);
254 _machinePathExists = true;
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);
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));
268 return _machinePath;
272 #if INSIDE_CORLIB
273 [MethodImplAttribute (MethodImplOptions.InternalCall)]
274 internal static extern bool _CanSecure (string root);
276 [MethodImplAttribute (MethodImplOptions.InternalCall)]
277 internal static extern bool _ProtectUser (string path);
279 [MethodImplAttribute (MethodImplOptions.InternalCall)]
280 internal static extern bool _ProtectMachine (string path);
282 [MethodImplAttribute (MethodImplOptions.InternalCall)]
283 internal static extern bool _IsUserProtected (string path);
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)
291 // Note: Class is only available in Mono.Security.dll as
292 // a management helper (e.g. build a GUI app)
294 internal static bool _CanSecure (string root)
296 return true;
299 internal static bool _ProtectUser (string path)
301 return true;
304 internal static bool _ProtectMachine (string path)
306 return true;
309 internal static bool _IsUserProtected (string path)
311 return true;
314 internal static bool _IsMachineProtected (string path)
316 return true;
318 #endif
319 // private stuff
321 private static bool CanSecure (string path)
323 // we assume POSIX filesystems can always be secured
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;
331 // while we ask the runtime for Windows OS
332 return _CanSecure (Path.GetPathRoot (path));
335 private static bool ProtectUser (string path)
337 // we cannot protect on some filsystem (like FAT)
338 if (CanSecure (path)) {
339 return _ProtectUser (path);
341 // but Mono still needs to run on them :(
342 return true;
345 private static bool ProtectMachine (string path)
347 // we cannot protect on some filsystem (like FAT)
348 if (CanSecure (path)) {
349 return _ProtectMachine (path);
351 // but Mono still needs to run on them :(
352 return true;
355 private static bool IsUserProtected (string path)
357 // we cannot protect on some filsystem (like FAT)
358 if (CanSecure (path)) {
359 return _IsUserProtected (path);
361 // but Mono still needs to run on them :(
362 return true;
365 private static bool IsMachineProtected (string path)
367 // we cannot protect on some filsystem (like FAT)
368 if (CanSecure (path)) {
369 return _IsMachineProtected (path);
371 // but Mono still needs to run on them :(
372 return true;
375 private bool CanChange {
376 get { return (_keyvalue == null); }
379 private bool UseDefaultKeyContainer {
380 get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); }
383 private bool UseMachineKeyStore {
384 get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); }
387 private string ContainerName {
388 get {
389 if (_container == null) {
390 if (UseDefaultKeyContainer) {
391 // easy to spot
392 _container = "default";
394 else if ((_params.KeyContainerName == null) || (_params.KeyContainerName.Length == 0)) {
395 _container = Guid.NewGuid ().ToString ();
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 ();
409 return _container;
413 // we do not want any changes after receiving the csp informations
414 private CspParameters Copy (CspParameters p)
416 CspParameters copy = new CspParameters (p.ProviderType, p.ProviderName, p.KeyContainerName);
417 copy.KeyNumber = p.KeyNumber;
418 copy.Flags = p.Flags;
419 return copy;
422 private void FromXml (string xml)
424 SecurityParser sp = new SecurityParser ();
425 sp.LoadXml (xml);
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
438 private string ToXml ()
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);
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);
452 xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine);
453 return xml.ToString ();
458 #endif