2 // KeyPairPersistence.cs: Keypair persistence
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // (C) 2004 Novell (http://www.novell.com)
12 using System.Runtime.InteropServices;
13 using System.Security;
14 using System.Security.Cryptography;
15 using System.Security.Permissions;
20 namespace Mono.Security.Cryptography {
23 * [type][unique name][key number].xml
26 * type CspParameters.ProviderType
27 * unique name A unique name for the keypair, which is
28 * a. default (for a provider default keypair)
29 * b. a GUID derived from
30 * i. random if no container name was
31 * specified at generation time
32 * ii. the MD5 hash of the container
33 * name (CspParameters.KeyContainerName)
34 * key number CspParameters.KeyNumber
39 * <Provider Name="" Type=""/>
40 * <Container Name=""/>
43 * RSAKeyValue, DSAKeyValue ...
50 * - There's NO confidentiality / integrity built in this
51 * persistance mechanism. You better protect your directories
54 * - As we do not use CSP we limit ourselves to provider types (not
55 * names). This means that for a same type and container type, but
56 * two different provider names) will return the same keypair. This
57 * should work as CspParameters always requires a csp type in its
60 * - Assert (CAS) are used so only the OS permission will limit access
61 * to the keypair files. I.e. this will work even in high-security
62 * scenarios where users do not have access to file system (e.g. web
63 * application). We can allow this because the filename used is
64 * TOTALLY under our control (no direct user input is used).
66 * - You CAN'T changes properties of the keypair once it's been
67 * created (saved). You must remove the container than save it
68 * back. This is the same behaviour as windows.
73 * - Where do we store the machine keys ?
74 * - zeroize _keyvalue before setting to null !!!
82 class KeyPairPersistence {
84 private static bool _pathExists = false; // check at 1st use
85 private static string _path;
87 private CspParameters _params;
88 private string _keyvalue;
89 private string _filename;
90 private string _container;
94 public KeyPairPersistence (CspParameters parameters)
95 : this (parameters, null) {}
97 public KeyPairPersistence (CspParameters parameters, string keypair)
99 if (parameters == null)
100 throw new ArgumentNullException ("parameters");
102 _params = Copy (parameters);
106 ~KeyPairPersistence ()
113 public string Filename {
115 if (_filename == null) {
116 _filename = String.Format ("[{0}][{1}][{2}].xml",
117 _params.ProviderType,
120 _filename = System.IO.Path.Combine (Path, _filename);
126 public string KeyValue {
127 get { return _keyvalue; }
134 // return a (read-only) copy
135 public CspParameters Parameters {
136 get { return Copy (_params); }
143 Zeroize (ref _keyvalue);
149 new FileIOPermission (FileIOPermissionAccess.Read, Path).Assert ();
151 bool result = File.Exists (this.Filename);
155 using (StreamReader sr = File.OpenText (this.Filename)) {
156 xml = sr.ReadToEnd ();
170 new FileIOPermission (FileIOPermissionAccess.Write, Path).Assert ();
172 using (FileStream fs = File.Open (this.Filename, FileMode.Create)) {
173 StreamWriter sw = new StreamWriter (fs, Encoding.UTF8);
174 sw.Write (this.ToXml ());
179 public void Remove ()
182 new FileIOPermission (FileIOPermissionAccess.Write, Path).Assert ();
185 File.Delete (this.Filename);
186 // it's now possible to change the keypair un the container
191 private static string Path {
194 lock (typeof (KeyPairPersistence)) {
195 // TODO ? where to put machine key pairs ?
196 _path = System.IO.Path.Combine (
197 Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
199 _path = System.IO.Path.Combine (_path, "keypairs");
202 _pathExists = Directory.Exists (_path);
204 Directory.CreateDirectory (_path);
213 private bool CanChange {
214 get { return (_keyvalue == null); }
217 private bool UseDefaultKeyContainer {
218 get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); }
221 private bool UseMachineKeyStore {
222 get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); }
225 private string ContainerName {
227 if (_container == null) {
228 if (UseDefaultKeyContainer) {
230 _container = "default";
232 else if ((_params.KeyContainerName == null) || (_params.KeyContainerName == String.Empty)) {
233 _container = Guid.NewGuid ().ToString ();
236 // we don't want to trust the key container name as we don't control it
237 // anyway some characters may not be compatible with the file system
238 byte[] data = Encoding.UTF8.GetBytes (_params.KeyContainerName);
239 MD5 hash = MD5.Create (); // faster than SHA1, same length as GUID
240 byte[] result = hash.ComputeHash (data);
241 _container = new Guid (result).ToString ();
248 // we do not want any changes after receiving the csp informations
249 private CspParameters Copy (CspParameters p)
251 CspParameters copy = new CspParameters (p.ProviderType, p.ProviderName, p.KeyContainerName);
252 copy.KeyNumber = p.KeyNumber;
253 copy.Flags = p.Flags;
257 private void FromXml (string xml)
259 SecurityParser sp = new SecurityParser ();
262 SecurityElement root = sp.ToXml ();
263 if (root.Tag == "KeyPair") {
264 SecurityElement prop = root.SearchForChildByTag ("Properties");
265 SecurityElement keyv = root.SearchForChildByTag ("KeyValue");
266 if (keyv.Children.Count > 0)
267 _keyvalue = keyv.Children [0].ToString ();
268 // Note: we do not read other stuff because
269 // it can't be changed after key creation
273 private string ToXml ()
275 // note: we do not use SecurityElement here because the
276 // keypair is a XML string (requiring parsing)
277 StringBuilder xml = new StringBuilder ();
278 xml.AppendFormat ("<KeyPair>{0}\t<Properties>{0}\t\t<Provider ", Environment.NewLine);
279 if ((_params.ProviderName != null) && (_params.ProviderName != String.Empty)) {
280 xml.AppendFormat ("Name=\"{0}\" ", _params.ProviderName);
282 xml.AppendFormat ("Type=\"{0}\" />{1}\t\t<Container ", _params.ProviderType, Environment.NewLine);
283 xml.AppendFormat ("Name=\"{0}\" />{1}\t</Properties>{1}\t<KeyValue", this.ContainerName, Environment.NewLine);
284 if (_params.KeyNumber != -1) {
285 xml.AppendFormat (" Id=\"{0}\" ", _params.KeyNumber);
287 xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine);
288 return xml.ToString ();
291 private void Zeroize (ref string s)
294 // TODO - zeroize the private information ?
295 // how can we track how it was used by other objects (copies?)
296 // and/or reverting to unsafe code ?