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 KeyPairPersistence(CspParameters parameters)114 public KeyPairPersistence (CspParameters parameters) 115 : this (parameters, null) 116 { 117 } 118 KeyPairPersistence(CspParameters parameters, string keyPair)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 Load()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 Save()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 Remove()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 _userPathExists = true; 225 } 226 } 227 if (!IsUserProtected (_userPath) && !ProtectUser (_userPath)) { 228 string msg = Locale.GetText ("Could not secure user key store '{0}'."); 229 throw new IOException (String.Format (msg, _userPath)); 230 } 231 } 232 // is it properly protected ? 233 if (!IsUserProtected (_userPath)) { 234 string msg = Locale.GetText ("Improperly protected user's key pairs in '{0}'."); 235 throw new CryptographicException (String.Format (msg, _userPath)); 236 } 237 return _userPath; 238 } 239 } 240 241 private static string MachinePath { 242 get { 243 lock (lockobj) { 244 if ((_machinePath == null) || (!_machinePathExists)) { 245 _machinePath = Path.Combine ( 246 Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), 247 "mono"); 248 _machinePath = Path.Combine (_machinePath, "keypairs"); 249 250 _machinePathExists = Directory.Exists (_machinePath); 251 if (!_machinePathExists) { 252 try { 253 Directory.CreateDirectory (_machinePath); 254 } 255 catch (Exception e) { 256 string msg = Locale.GetText ("Could not create machine key store '{0}'."); 257 throw new CryptographicException (String.Format (msg, _machinePath), e); 258 } 259 _machinePathExists = true; 260 } 261 } 262 if (!IsMachineProtected (_machinePath) && !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 // is it properly protected ? 268 if (!IsMachineProtected (_machinePath)) { 269 string msg = Locale.GetText ("Improperly protected machine's key pairs in '{0}'."); 270 throw new CryptographicException (String.Format (msg, _machinePath)); 271 } 272 return _machinePath; 273 } 274 } 275 276 #if INSIDE_CORLIB 277 [MethodImplAttribute (MethodImplOptions.InternalCall)] _CanSecure(string root)278 internal static extern bool _CanSecure (string root); 279 280 [MethodImplAttribute (MethodImplOptions.InternalCall)] _ProtectUser(string path)281 internal static extern bool _ProtectUser (string path); 282 283 [MethodImplAttribute (MethodImplOptions.InternalCall)] _ProtectMachine(string path)284 internal static extern bool _ProtectMachine (string path); 285 286 [MethodImplAttribute (MethodImplOptions.InternalCall)] _IsUserProtected(string path)287 internal static extern bool _IsUserProtected (string path); 288 289 [MethodImplAttribute (MethodImplOptions.InternalCall)] _IsMachineProtected(string path)290 internal static extern bool _IsMachineProtected (string path); 291 #else 292 // Mono.Security.dll assembly can't use the internal 293 // call (and still run with other runtimes) 294 295 // Note: Class is only available in Mono.Security.dll as 296 // a management helper (e.g. build a GUI app) 297 _CanSecure(string root)298 internal static bool _CanSecure (string root) 299 { 300 return true; 301 } 302 _ProtectUser(string path)303 internal static bool _ProtectUser (string path) 304 { 305 return true; 306 } 307 _ProtectMachine(string path)308 internal static bool _ProtectMachine (string path) 309 { 310 return true; 311 } 312 _IsUserProtected(string path)313 internal static bool _IsUserProtected (string path) 314 { 315 return true; 316 } 317 _IsMachineProtected(string path)318 internal static bool _IsMachineProtected (string path) 319 { 320 return true; 321 } 322 #endif 323 // private stuff 324 CanSecure(string path)325 private static bool CanSecure (string path) 326 { 327 // we assume POSIX filesystems can always be secured 328 329 // check for Unix platforms - see FAQ for more details 330 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F 331 int platform = (int) Environment.OSVersion.Platform; 332 if ((platform == 4) || (platform == 128) || (platform == 6)) 333 return true; 334 335 // while we ask the runtime for Windows OS 336 return _CanSecure (Path.GetPathRoot (path)); 337 } 338 ProtectUser(string path)339 private static bool ProtectUser (string path) 340 { 341 // we cannot protect on some filsystem (like FAT) 342 if (CanSecure (path)) { 343 return _ProtectUser (path); 344 } 345 // but Mono still needs to run on them :( 346 return true; 347 } 348 ProtectMachine(string path)349 private static bool ProtectMachine (string path) 350 { 351 // we cannot protect on some filsystem (like FAT) 352 if (CanSecure (path)) { 353 return _ProtectMachine (path); 354 } 355 // but Mono still needs to run on them :( 356 return true; 357 } 358 IsUserProtected(string path)359 private static bool IsUserProtected (string path) 360 { 361 // we cannot protect on some filsystem (like FAT) 362 if (CanSecure (path)) { 363 return _IsUserProtected (path); 364 } 365 // but Mono still needs to run on them :( 366 return true; 367 } 368 IsMachineProtected(string path)369 private static bool IsMachineProtected (string path) 370 { 371 // we cannot protect on some filsystem (like FAT) 372 if (CanSecure (path)) { 373 return _IsMachineProtected (path); 374 } 375 // but Mono still needs to run on them :( 376 return true; 377 } 378 379 private bool CanChange { 380 get { return (_keyvalue == null); } 381 } 382 383 private bool UseDefaultKeyContainer { 384 get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); } 385 } 386 387 private bool UseMachineKeyStore { 388 get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); } 389 } 390 391 private string ContainerName { 392 get { 393 if (_container == null) { 394 if (UseDefaultKeyContainer) { 395 // easy to spot 396 _container = "default"; 397 } 398 else if ((_params.KeyContainerName == null) || (_params.KeyContainerName.Length == 0)) { 399 _container = Guid.NewGuid ().ToString (); 400 } 401 else { 402 // we don't want to trust the key container name as we don't control it 403 // anyway some characters may not be compatible with the file system 404 byte[] data = Encoding.UTF8.GetBytes (_params.KeyContainerName); 405 // Note: We use MD5 as it is faster than SHA1 and has the same length 406 // as a GUID. Recent problems found in MD5 (like collisions) aren't a 407 // problem in this case. 408 MD5 hash = MD5.Create (); 409 byte[] result = hash.ComputeHash (data); 410 _container = new Guid (result).ToString (); 411 } 412 } 413 return _container; 414 } 415 } 416 417 // we do not want any changes after receiving the csp informations Copy(CspParameters p)418 private CspParameters Copy (CspParameters p) 419 { 420 CspParameters copy = new CspParameters (p.ProviderType, p.ProviderName, p.KeyContainerName); 421 copy.KeyNumber = p.KeyNumber; 422 copy.Flags = p.Flags; 423 return copy; 424 } 425 FromXml(string xml)426 private void FromXml (string xml) 427 { 428 SecurityParser sp = new SecurityParser (); 429 sp.LoadXml (xml); 430 431 SecurityElement root = sp.ToXml (); 432 if (root.Tag == "KeyPair") { 433 //SecurityElement prop = root.SearchForChildByTag ("Properties"); 434 SecurityElement keyv = root.SearchForChildByTag ("KeyValue"); 435 if (keyv.Children.Count > 0) 436 _keyvalue = keyv.Children [0].ToString (); 437 // Note: we do not read other stuff because 438 // it can't be changed after key creation 439 } 440 } 441 ToXml()442 private string ToXml () 443 { 444 // note: we do not use SecurityElement here because the 445 // keypair is a XML string (requiring parsing) 446 StringBuilder xml = new StringBuilder (); 447 xml.AppendFormat ("<KeyPair>{0}\t<Properties>{0}\t\t<Provider ", Environment.NewLine); 448 if ((_params.ProviderName != null) && (_params.ProviderName.Length != 0)) { 449 xml.AppendFormat ("Name=\"{0}\" ", _params.ProviderName); 450 } 451 xml.AppendFormat ("Type=\"{0}\" />{1}\t\t<Container ", _params.ProviderType, Environment.NewLine); 452 xml.AppendFormat ("Name=\"{0}\" />{1}\t</Properties>{1}\t<KeyValue", this.ContainerName, Environment.NewLine); 453 if (_params.KeyNumber != -1) { 454 xml.AppendFormat (" Id=\"{0}\" ", _params.KeyNumber); 455 } 456 xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine); 457 return xml.ToString (); 458 } 459 } 460 } 461