// Author:
// Sebastien Pouliot <sebastien@ximian.com>
//
-// (C) 2004 Novell (http://www.novell.com)
+// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+#if !MOONLIGHT
using System;
+using System.Globalization;
using System.IO;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
/* NOTES
*
* - There's NO confidentiality / integrity built in this
- * persistance mechanism. You better protect your directories
- * ACL correctly!
+ * persistance mechanism. The container directories (both user and
+ * machine) are created with restrited ACL. The ACL is also checked
+ * when a key is accessed (so totally public keys won't be used).
+ * see /mono/mono/metadata/security.c for implementation
*
* - As we do not use CSP we limit ourselves to provider types (not
* names). This means that for a same type and container type, but
*
* - You CAN'T changes properties of the keypair once it's been
* created (saved). You must remove the container than save it
- * back. This is the same behaviour as windows.
- */
-
- /* TODO
- *
- * - Where do we store the machine keys ?
- * - zeroize _keyvalue before setting to null !!!
+ * back. This is the same behaviour as CSP under Windows.
*/
#if INSIDE_CORLIB
public
#endif
class KeyPairPersistence {
-
- private static bool _pathExists = false; // check at 1st use
- private static string _path;
+
+ private static bool _userPathExists; // check at 1st use
+ private static string _userPath;
+
+ private static bool _machinePathExists; // check at 1st use
+ private static string _machinePath;
private CspParameters _params;
private string _keyvalue;
// constructors
public KeyPairPersistence (CspParameters parameters)
- : this (parameters, null) {}
+ : this (parameters, null)
+ {
+ }
- public KeyPairPersistence (CspParameters parameters, string keypair)
+ public KeyPairPersistence (CspParameters parameters, string keyPair)
{
if (parameters == null)
throw new ArgumentNullException ("parameters");
_params = Copy (parameters);
- _keyvalue = keypair;
- }
-
- ~KeyPairPersistence ()
- {
- Clear ();
+ _keyvalue = keyPair;
}
// properties
public string Filename {
get {
if (_filename == null) {
- _filename = String.Format ("[{0}][{1}][{2}].xml",
+ _filename = String.Format (CultureInfo.InvariantCulture,
+ "[{0}][{1}][{2}].xml",
_params.ProviderType,
this.ContainerName,
_params.KeyNumber);
- _filename = System.IO.Path.Combine (Path, _filename);
+ if (UseMachineKeyStore)
+ _filename = Path.Combine (MachinePath, _filename);
+ else
+ _filename = Path.Combine (UserPath, _filename);
}
return _filename;
}
// methods
- public void Clear ()
- {
- Zeroize (ref _keyvalue);
- }
-
public bool Load ()
{
// see NOTES
- new FileIOPermission (FileIOPermissionAccess.Read, Path).Assert ();
+// FIXME new FileIOPermission (FileIOPermissionAccess.Read, this.Filename).Assert ();
bool result = File.Exists (this.Filename);
if (result) {
- string xml = null;
- try {
- using (StreamReader sr = File.OpenText (this.Filename)) {
- xml = sr.ReadToEnd ();
- }
- FromXml (xml);
- }
- finally {
- Zeroize (ref xml);
+ using (StreamReader sr = File.OpenText (this.Filename)) {
+ FromXml (sr.ReadToEnd ());
}
}
return result;
public void Save ()
{
// see NOTES
- new FileIOPermission (FileIOPermissionAccess.Write, Path).Assert ();
+// FIXME new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
using (FileStream fs = File.Open (this.Filename, FileMode.Create)) {
StreamWriter sw = new StreamWriter (fs, Encoding.UTF8);
sw.Write (this.ToXml ());
sw.Close ();
}
+ // apply protection to newly created files
+ if (UseMachineKeyStore)
+ ProtectMachine (Filename);
+ else
+ ProtectUser (Filename);
}
public void Remove ()
{
// see NOTES
- new FileIOPermission (FileIOPermissionAccess.Write, Path).Assert ();
+// FIXME new FileIOPermission (FileIOPermissionAccess.Write, this.Filename).Assert ();
- Clear ();
File.Delete (this.Filename);
// it's now possible to change the keypair un the container
}
- // private stuff
+ // private static stuff
- private static string Path {
+ static object lockobj = new object ();
+
+ private static string UserPath {
get {
- if (_path == null) {
- lock (typeof (KeyPairPersistence)) {
- // TODO ? where to put machine key pairs ?
- _path = System.IO.Path.Combine (
+ lock (lockobj) {
+ if ((_userPath == null) || (!_userPathExists)) {
+ _userPath = Path.Combine (
Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
".mono");
- _path = System.IO.Path.Combine (_path, "keypairs");
+ _userPath = Path.Combine (_userPath, "keypairs");
+
+ _userPathExists = Directory.Exists (_userPath);
+ if (!_userPathExists) {
+ try {
+ Directory.CreateDirectory (_userPath);
+ ProtectUser (_userPath);
+ _userPathExists = true;
+ }
+ catch (Exception e) {
+ string msg = Locale.GetText ("Could not create user key store '{0}'.");
+ throw new CryptographicException (String.Format (msg, _userPath), e);
+ }
+ }
+ }
+ }
+ // is it properly protected ?
+ if (!IsUserProtected (_userPath)) {
+ string msg = Locale.GetText ("Improperly protected user's key pairs in '{0}'.");
+ throw new CryptographicException (String.Format (msg, _userPath));
+ }
+ return _userPath;
+ }
+ }
- if (!_pathExists) {
- _pathExists = Directory.Exists (_path);
- if (!_pathExists) {
- Directory.CreateDirectory (_path);
+ private static string MachinePath {
+ get {
+ lock (lockobj) {
+ if ((_machinePath == null) || (!_machinePathExists)) {
+ _machinePath = Path.Combine (
+ Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData),
+ ".mono");
+ _machinePath = Path.Combine (_machinePath, "keypairs");
+
+ _machinePathExists = Directory.Exists (_machinePath);
+ if (!_machinePathExists) {
+ try {
+ Directory.CreateDirectory (_machinePath);
+ ProtectMachine (_machinePath);
+ _machinePathExists = true;
+ }
+ catch (Exception e) {
+ string msg = Locale.GetText ("Could not create machine key store '{0}'.");
+ throw new CryptographicException (String.Format (msg, _machinePath), e);
}
}
}
}
- return _path;
+ // is it properly protected ?
+ if (!IsMachineProtected (_machinePath)) {
+ string msg = Locale.GetText ("Improperly protected machine's key pairs in '{0}'.");
+ throw new CryptographicException (String.Format (msg, _machinePath));
+ }
+ return _machinePath;
}
}
+#if INSIDE_CORLIB
+ [MethodImplAttribute (MethodImplOptions.InternalCall)]
+ internal static extern bool _CanSecure (string root);
+
+ [MethodImplAttribute (MethodImplOptions.InternalCall)]
+ internal static extern bool _ProtectUser (string path);
+
+ [MethodImplAttribute (MethodImplOptions.InternalCall)]
+ internal static extern bool _ProtectMachine (string path);
+
+ [MethodImplAttribute (MethodImplOptions.InternalCall)]
+ internal static extern bool _IsUserProtected (string path);
+
+ [MethodImplAttribute (MethodImplOptions.InternalCall)]
+ internal static extern bool _IsMachineProtected (string path);
+#else
+ // Mono.Security.dll assembly can't use the internal
+ // call (and still run with other runtimes)
+
+ // Note: Class is only available in Mono.Security.dll as
+ // a management helper (e.g. build a GUI app)
+
+ internal static bool _CanSecure (string root)
+ {
+ return true;
+ }
+
+ internal static bool _ProtectUser (string path)
+ {
+ return true;
+ }
+
+ internal static bool _ProtectMachine (string path)
+ {
+ return true;
+ }
+
+ internal static bool _IsUserProtected (string path)
+ {
+ return true;
+ }
+
+ internal static bool _IsMachineProtected (string path)
+ {
+ return true;
+ }
+#endif
+ // private stuff
+
+ private static bool CanSecure (string path)
+ {
+ // we assume POSIX filesystems can always be secured
+
+ // check for Unix platforms - see FAQ for more details
+ // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
+ int platform = (int) Environment.OSVersion.Platform;
+ if ((platform == 4) || (platform == 128) || (platform == 6))
+ return true;
+
+ // while we ask the runtime for Windows OS
+ return _CanSecure (Path.GetPathRoot (path));
+ }
+
+ private static bool ProtectUser (string path)
+ {
+ // we cannot protect on some filsystem (like FAT)
+ if (CanSecure (path)) {
+ return _ProtectUser (path);
+ }
+ // but Mono still needs to run on them :(
+ return true;
+ }
+
+ private static bool ProtectMachine (string path)
+ {
+ // we cannot protect on some filsystem (like FAT)
+ if (CanSecure (path)) {
+ return _ProtectMachine (path);
+ }
+ // but Mono still needs to run on them :(
+ return true;
+ }
+
+ private static bool IsUserProtected (string path)
+ {
+ // we cannot protect on some filsystem (like FAT)
+ if (CanSecure (path)) {
+ return _IsUserProtected (path);
+ }
+ // but Mono still needs to run on them :(
+ return true;
+ }
+
+ private static bool IsMachineProtected (string path)
+ {
+ // we cannot protect on some filsystem (like FAT)
+ if (CanSecure (path)) {
+ return _IsMachineProtected (path);
+ }
+ // but Mono still needs to run on them :(
+ return true;
+ }
+
private bool CanChange {
get { return (_keyvalue == null); }
}
// easy to spot
_container = "default";
}
- else if ((_params.KeyContainerName == null) || (_params.KeyContainerName == String.Empty)) {
+ else if ((_params.KeyContainerName == null) || (_params.KeyContainerName.Length == 0)) {
_container = Guid.NewGuid ().ToString ();
}
else {
// we don't want to trust the key container name as we don't control it
// anyway some characters may not be compatible with the file system
byte[] data = Encoding.UTF8.GetBytes (_params.KeyContainerName);
- MD5 hash = MD5.Create (); // faster than SHA1, same length as GUID
+ // Note: We use MD5 as it is faster than SHA1 and has the same length
+ // as a GUID. Recent problems found in MD5 (like collisions) aren't a
+ // problem in this case.
+ MD5 hash = MD5.Create ();
byte[] result = hash.ComputeHash (data);
_container = new Guid (result).ToString ();
}
SecurityElement root = sp.ToXml ();
if (root.Tag == "KeyPair") {
- SecurityElement prop = root.SearchForChildByTag ("Properties");
+ //SecurityElement prop = root.SearchForChildByTag ("Properties");
SecurityElement keyv = root.SearchForChildByTag ("KeyValue");
if (keyv.Children.Count > 0)
_keyvalue = keyv.Children [0].ToString ();
// keypair is a XML string (requiring parsing)
StringBuilder xml = new StringBuilder ();
xml.AppendFormat ("<KeyPair>{0}\t<Properties>{0}\t\t<Provider ", Environment.NewLine);
- if ((_params.ProviderName != null) && (_params.ProviderName != String.Empty)) {
+ if ((_params.ProviderName != null) && (_params.ProviderName.Length != 0)) {
xml.AppendFormat ("Name=\"{0}\" ", _params.ProviderName);
}
xml.AppendFormat ("Type=\"{0}\" />{1}\t\t<Container ", _params.ProviderType, Environment.NewLine);
xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine);
return xml.ToString ();
}
-
- private void Zeroize (ref string s)
- {
- if (s != null) {
- // TODO - zeroize the private information ?
- // how can we track how it was used by other objects (copies?)
- // and/or reverting to unsafe code ?
- s = null;
- }
- }
}
}
+
+#endif
+