2 // KeyPairPersistence.cs: Keypair persistence
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
30 using System.Globalization;
32 using System.Runtime.CompilerServices;
33 using System.Runtime.InteropServices;
34 using System.Security;
35 using System.Security.Cryptography;
36 using System.Security.Permissions;
41 namespace Mono.Security.Cryptography {
44 * [type][unique name][key number].xml
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
60 * <Provider Name="" Type=""/>
61 * <Container Name=""/>
64 * RSAKeyValue, DSAKeyValue ...
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
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
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).
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.
99 class KeyPairPersistence {
101 private static bool _userPathExists; // check at 1st use
102 private static string _userPath;
104 private static bool _machinePathExists; // check at 1st use
105 private static string _machinePath;
107 private CspParameters _params;
108 private string _keyvalue;
109 private string _filename;
110 private string _container;
114 public KeyPairPersistence (CspParameters parameters)
115 : this (parameters, null)
119 public KeyPairPersistence (CspParameters parameters, string keyPair)
121 if (parameters == null)
122 throw new ArgumentNullException ("parameters");
124 _params = Copy (parameters);
130 public string Filename {
132 if (_filename == null) {
133 _filename = String.Format (CultureInfo.InvariantCulture,
134 "[{0}][{1}][{2}].xml",
135 _params.ProviderType,
138 if (UseMachineKeyStore)
139 _filename = Path.Combine (MachinePath, _filename);
141 _filename = Path.Combine (UserPath, _filename);
147 public string KeyValue {
148 get { return _keyvalue; }
155 // return a (read-only) copy
156 public CspParameters Parameters {
157 get { return Copy (_params); }
165 // FIXME new FileIOPermission (FileIOPermissionAccess.Read, this.Filename).Assert ();
167 bool result = File.Exists (this.Filename);
169 using (StreamReader sr = File.OpenText (this.Filename)) {
170 FromXml (sr.ReadToEnd ());
179 // FIXME new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
181 using (FileStream fs = File.Open (this.Filename, FileMode.Create)) {
182 StreamWriter sw = new StreamWriter (fs, Encoding.UTF8);
183 sw.Write (this.ToXml ());
186 // apply protection to newly created files
187 if (UseMachineKeyStore)
188 ProtectMachine (Filename);
190 ProtectUser (Filename);
193 public void Remove ()
196 // FIXME new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
198 File.Delete (this.Filename);
199 // it's now possible to change the keypair un the container
202 // private static stuff
204 static object lockobj = new object ();
206 private static string UserPath {
209 if ((_userPath == null) || (!_userPathExists)) {
210 _userPath = Path.Combine (
211 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
213 _userPath = Path.Combine (_userPath, "keypairs");
215 _userPathExists = Directory.Exists (_userPath);
216 if (!_userPathExists) {
218 Directory.CreateDirectory (_userPath);
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);
225 if (!ProtectUser (_userPath)) {
226 string msg = Locale.GetText ("Could not secure user key store '{0}'.");
227 throw new IOException (String.Format (msg, _userPath));
230 _userPathExists = true;
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));
243 private static string MachinePath {
246 if ((_machinePath == null) || (!_machinePathExists)) {
247 _machinePath = Path.Combine (
248 Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData),
250 _machinePath = Path.Combine (_machinePath, "keypairs");
252 _machinePathExists = Directory.Exists (_machinePath);
253 if (!_machinePathExists) {
255 Directory.CreateDirectory (_machinePath);
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);
262 if (!ProtectMachine (_machinePath)) {
263 string msg = Locale.GetText ("Could not secure machine key store '{0}'.");
264 throw new IOException (String.Format (msg, _machinePath));
267 _machinePathExists = true;
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));
281 [MethodImplAttribute (MethodImplOptions.InternalCall)]
282 internal static extern bool _CanSecure (string root);
284 [MethodImplAttribute (MethodImplOptions.InternalCall)]
285 internal static extern bool _ProtectUser (string path);
287 [MethodImplAttribute (MethodImplOptions.InternalCall)]
288 internal static extern bool _ProtectMachine (string path);
290 [MethodImplAttribute (MethodImplOptions.InternalCall)]
291 internal static extern bool _IsUserProtected (string path);
293 [MethodImplAttribute (MethodImplOptions.InternalCall)]
294 internal static extern bool _IsMachineProtected (string path);
296 // Mono.Security.dll assembly can't use the internal
297 // call (and still run with other runtimes)
299 // Note: Class is only available in Mono.Security.dll as
300 // a management helper (e.g. build a GUI app)
302 internal static bool _CanSecure (string root)
307 internal static bool _ProtectUser (string path)
312 internal static bool _ProtectMachine (string path)
317 internal static bool _IsUserProtected (string path)
322 internal static bool _IsMachineProtected (string path)
329 private static bool CanSecure (string path)
331 // we assume POSIX filesystems can always be secured
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))
339 // while we ask the runtime for Windows OS
340 return _CanSecure (Path.GetPathRoot (path));
343 private static bool ProtectUser (string path)
345 // we cannot protect on some filsystem (like FAT)
346 if (CanSecure (path)) {
347 return _ProtectUser (path);
349 // but Mono still needs to run on them :(
353 private static bool ProtectMachine (string path)
355 // we cannot protect on some filsystem (like FAT)
356 if (CanSecure (path)) {
357 return _ProtectMachine (path);
359 // but Mono still needs to run on them :(
363 private static bool IsUserProtected (string path)
365 // we cannot protect on some filsystem (like FAT)
366 if (CanSecure (path)) {
367 return _IsUserProtected (path);
369 // but Mono still needs to run on them :(
373 private static bool IsMachineProtected (string path)
375 // we cannot protect on some filsystem (like FAT)
376 if (CanSecure (path)) {
377 return _IsMachineProtected (path);
379 // but Mono still needs to run on them :(
383 private bool CanChange {
384 get { return (_keyvalue == null); }
387 private bool UseDefaultKeyContainer {
388 get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); }
391 private bool UseMachineKeyStore {
392 get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); }
395 private string ContainerName {
397 if (_container == null) {
398 if (UseDefaultKeyContainer) {
400 _container = "default";
402 else if ((_params.KeyContainerName == null) || (_params.KeyContainerName.Length == 0)) {
403 _container = Guid.NewGuid ().ToString ();
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 ();
421 // we do not want any changes after receiving the csp informations
422 private CspParameters Copy (CspParameters p)
424 CspParameters copy = new CspParameters (p.ProviderType, p.ProviderName, p.KeyContainerName);
425 copy.KeyNumber = p.KeyNumber;
426 copy.Flags = p.Flags;
430 private void FromXml (string xml)
432 SecurityParser sp = new SecurityParser ();
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
446 private string ToXml ()
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);
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);
460 xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine);
461 return xml.ToString ();