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
);
219 ProtectUser (_userPath
);
220 _userPathExists
= true;
222 catch (Exception e
) {
223 string msg
= Locale
.GetText ("Could not create user key store '{0}'.");
224 throw new CryptographicException (String
.Format (msg
, _userPath
), e
);
229 // is it properly protected ?
230 if (!IsUserProtected (_userPath
)) {
231 string msg
= Locale
.GetText ("Improperly protected user's key pairs in '{0}'.");
232 throw new CryptographicException (String
.Format (msg
, _userPath
));
238 private static string MachinePath
{
241 if ((_machinePath
== null) || (!_machinePathExists
)) {
242 _machinePath
= Path
.Combine (
243 Environment
.GetFolderPath (Environment
.SpecialFolder
.CommonApplicationData
),
245 _machinePath
= Path
.Combine (_machinePath
, "keypairs");
247 _machinePathExists
= Directory
.Exists (_machinePath
);
248 if (!_machinePathExists
) {
250 Directory
.CreateDirectory (_machinePath
);
251 ProtectMachine (_machinePath
);
252 _machinePathExists
= true;
254 catch (Exception e
) {
255 string msg
= Locale
.GetText ("Could not create machine key store '{0}'.");
256 throw new CryptographicException (String
.Format (msg
, _machinePath
), e
);
261 // is it properly protected ?
262 if (!IsMachineProtected (_machinePath
)) {
263 string msg
= Locale
.GetText ("Improperly protected machine's key pairs in '{0}'.");
264 throw new CryptographicException (String
.Format (msg
, _machinePath
));
271 [MethodImplAttribute (MethodImplOptions
.InternalCall
)]
272 internal static extern bool _CanSecure (string root
);
274 [MethodImplAttribute (MethodImplOptions
.InternalCall
)]
275 internal static extern bool _ProtectUser (string path
);
277 [MethodImplAttribute (MethodImplOptions
.InternalCall
)]
278 internal static extern bool _ProtectMachine (string path
);
280 [MethodImplAttribute (MethodImplOptions
.InternalCall
)]
281 internal static extern bool _IsUserProtected (string path
);
283 [MethodImplAttribute (MethodImplOptions
.InternalCall
)]
284 internal static extern bool _IsMachineProtected (string path
);
286 // Mono.Security.dll assembly can't use the internal
287 // call (and still run with other runtimes)
289 // Note: Class is only available in Mono.Security.dll as
290 // a management helper (e.g. build a GUI app)
292 internal static bool _CanSecure (string root
)
297 internal static bool _ProtectUser (string path
)
302 internal static bool _ProtectMachine (string path
)
307 internal static bool _IsUserProtected (string path
)
312 internal static bool _IsMachineProtected (string path
)
319 private static bool CanSecure (string path
)
321 // we assume POSIX filesystems can always be secured
323 // check for Unix platforms - see FAQ for more details
324 // http://www.mono-project.com/FAQ:_Technical#How_to_detect_the_execution_platform_.3F
325 int platform
= (int) Environment
.OSVersion
.Platform
;
326 if ((platform
== 4) || (platform
== 128) || (platform
== 6))
329 // while we ask the runtime for Windows OS
330 return _CanSecure (Path
.GetPathRoot (path
));
333 private static bool ProtectUser (string path
)
335 // we cannot protect on some filsystem (like FAT)
336 if (CanSecure (path
)) {
337 return _ProtectUser (path
);
339 // but Mono still needs to run on them :(
343 private static bool ProtectMachine (string path
)
345 // we cannot protect on some filsystem (like FAT)
346 if (CanSecure (path
)) {
347 return _ProtectMachine (path
);
349 // but Mono still needs to run on them :(
353 private static bool IsUserProtected (string path
)
355 // we cannot protect on some filsystem (like FAT)
356 if (CanSecure (path
)) {
357 return _IsUserProtected (path
);
359 // but Mono still needs to run on them :(
363 private static bool IsMachineProtected (string path
)
365 // we cannot protect on some filsystem (like FAT)
366 if (CanSecure (path
)) {
367 return _IsMachineProtected (path
);
369 // but Mono still needs to run on them :(
373 private bool CanChange
{
374 get { return (_keyvalue == null); }
377 private bool UseDefaultKeyContainer
{
378 get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); }
381 private bool UseMachineKeyStore
{
382 get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); }
385 private string ContainerName
{
387 if (_container
== null) {
388 if (UseDefaultKeyContainer
) {
390 _container
= "default";
392 else if ((_params
.KeyContainerName
== null) || (_params
.KeyContainerName
.Length
== 0)) {
393 _container
= Guid
.NewGuid ().ToString ();
396 // we don't want to trust the key container name as we don't control it
397 // anyway some characters may not be compatible with the file system
398 byte[] data
= Encoding
.UTF8
.GetBytes (_params
.KeyContainerName
);
399 // Note: We use MD5 as it is faster than SHA1 and has the same length
400 // as a GUID. Recent problems found in MD5 (like collisions) aren't a
401 // problem in this case.
402 MD5 hash
= MD5
.Create ();
403 byte[] result
= hash
.ComputeHash (data
);
404 _container
= new Guid (result
).ToString ();
411 // we do not want any changes after receiving the csp informations
412 private CspParameters
Copy (CspParameters p
)
414 CspParameters copy
= new CspParameters (p
.ProviderType
, p
.ProviderName
, p
.KeyContainerName
);
415 copy
.KeyNumber
= p
.KeyNumber
;
416 copy
.Flags
= p
.Flags
;
420 private void FromXml (string xml
)
422 SecurityParser sp
= new SecurityParser ();
425 SecurityElement root
= sp
.ToXml ();
426 if (root
.Tag
== "KeyPair") {
427 //SecurityElement prop = root.SearchForChildByTag ("Properties");
428 SecurityElement keyv
= root
.SearchForChildByTag ("KeyValue");
429 if (keyv
.Children
.Count
> 0)
430 _keyvalue
= keyv
.Children
[0].ToString ();
431 // Note: we do not read other stuff because
432 // it can't be changed after key creation
436 private string ToXml ()
438 // note: we do not use SecurityElement here because the
439 // keypair is a XML string (requiring parsing)
440 StringBuilder xml
= new StringBuilder ();
441 xml
.AppendFormat ("<KeyPair>{0}\t<Properties>{0}\t\t<Provider ", Environment
.NewLine
);
442 if ((_params
.ProviderName
!= null) && (_params
.ProviderName
.Length
!= 0)) {
443 xml
.AppendFormat ("Name=\"{0}\" ", _params
.ProviderName
);
445 xml
.AppendFormat ("Type=\"{0}\" />{1}\t\t<Container ", _params
.ProviderType
, Environment
.NewLine
);
446 xml
.AppendFormat ("Name=\"{0}\" />{1}\t</Properties>{1}\t<KeyValue", this.ContainerName
, Environment
.NewLine
);
447 if (_params
.KeyNumber
!= -1) {
448 xml
.AppendFormat (" Id=\"{0}\" ", _params
.KeyNumber
);
450 xml
.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue
, Environment
.NewLine
);
451 return xml
.ToString ();