1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
|
//
// KeyPairPersistence.cs: Keypair persistence
//
// Author:
// Sebastien Pouliot <sebastien@ximian.com>
//
// (C) 2004 Novell (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.
//
using System;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using Mono.Xml;
namespace Mono.Security.Cryptography {
/* File name
* [type][unique name][key number].xml
*
* where
* type CspParameters.ProviderType
* unique name A unique name for the keypair, which is
* a. default (for a provider default keypair)
* b. a GUID derived from
* i. random if no container name was
* specified at generation time
* ii. the MD5 hash of the container
* name (CspParameters.KeyContainerName)
* key number CspParameters.KeyNumber
*
* File format
* <KeyPair>
* <Properties>
* <Provider Name="" Type=""/>
* <Container Name=""/>
* </Properties>
* <KeyValue Id="">
* RSAKeyValue, DSAKeyValue ...
* </KeyValue>
* </KeyPair>
*/
/* NOTES
*
* - There's NO confidentiality / integrity built in this
* 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
* two different provider names) will return the same keypair. This
* should work as CspParameters always requires a csp type in its
* constructors.
*
* - Assert (CAS) are used so only the OS permission will limit access
* to the keypair files. I.e. this will work even in high-security
* scenarios where users do not have access to file system (e.g. web
* application). We can allow this because the filename used is
* TOTALLY under our control (no direct user input is used).
*
* - 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 CSP under Windows.
*/
#if INSIDE_CORLIB
internal
#else
public
#endif
class KeyPairPersistence {
private static bool _userPathExists = false; // check at 1st use
private static string _userPath;
private static bool _machinePathExists = false; // check at 1st use
private static string _machinePath;
private CspParameters _params;
private string _keyvalue;
private string _filename;
private string _container;
// constructors
public KeyPairPersistence (CspParameters parameters)
: this (parameters, null)
{
}
public KeyPairPersistence (CspParameters parameters, string keyPair)
{
if (parameters == null)
throw new ArgumentNullException ("parameters");
_params = Copy (parameters);
_keyvalue = keyPair;
}
// properties
public string Filename {
get {
if (_filename == null) {
_filename = String.Format (CultureInfo.InvariantCulture,
"[{0}][{1}][{2}].xml",
_params.ProviderType,
this.ContainerName,
_params.KeyNumber);
if (UseMachineKeyStore)
_filename = Path.Combine (MachinePath, _filename);
else
_filename = Path.Combine (UserPath, _filename);
}
return _filename;
}
}
public string KeyValue {
get { return _keyvalue; }
set {
if (this.CanChange)
_keyvalue = value;
}
}
// return a (read-only) copy
public CspParameters Parameters {
get { return Copy (_params); }
}
// methods
public bool Load ()
{
// see NOTES
new FileIOPermission (FileIOPermissionAccess.Read, this.Filename).Assert ();
bool result = File.Exists (this.Filename);
if (result) {
using (StreamReader sr = File.OpenText (this.Filename)) {
FromXml (sr.ReadToEnd ());
}
}
return result;
}
public void Save ()
{
// see NOTES
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, this.Filename).Assert ();
File.Delete (this.Filename);
// it's now possible to change the keypair un the container
}
// private static stuff
private static string UserPath {
get {
if ((_userPath == null) || (!_userPathExists)) {
lock (typeof (KeyPairPersistence)) {
_userPath = Path.Combine (
Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
".mono");
_userPath = Path.Combine (_userPath, "keypairs");
_userPathExists = Directory.Exists (_userPath);
if (!_userPathExists) {
try {
Directory.CreateDirectory (_userPath);
ProtectUser (_userPath);
_userPathExists = true;
}
catch (Exception e) {
throw new CryptographicException ("Could not create key store.", e);
}
}
}
}
// is it properly protected ?
if (!IsUserProtected (_userPath)) {
throw new CryptographicException ("Improperly protected key pairs.");
}
return _userPath;
}
}
private static string MachinePath {
get {
if ((_machinePath == null) || (!_machinePathExists)) {
lock (typeof (KeyPairPersistence)) {
_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) {
throw new CryptographicException ("Could not create key store.", e);
}
}
}
}
// is it properly protected ?
if (!IsMachineProtected (_machinePath)) {
throw new CryptographicException ("Improperly protected key pairs.");
}
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
if ((int) Environment.OSVersion.Platform == 128)
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); }
}
private bool UseDefaultKeyContainer {
get { return ((_params.Flags & CspProviderFlags.UseDefaultKeyContainer) == CspProviderFlags.UseDefaultKeyContainer); }
}
private bool UseMachineKeyStore {
get { return ((_params.Flags & CspProviderFlags.UseMachineKeyStore) == CspProviderFlags.UseMachineKeyStore); }
}
private string ContainerName {
get {
if (_container == null) {
if (UseDefaultKeyContainer) {
// easy to spot
_container = "default";
}
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
byte[] result = hash.ComputeHash (data);
_container = new Guid (result).ToString ();
}
}
return _container;
}
}
// we do not want any changes after receiving the csp informations
private CspParameters Copy (CspParameters p)
{
CspParameters copy = new CspParameters (p.ProviderType, p.ProviderName, p.KeyContainerName);
copy.KeyNumber = p.KeyNumber;
copy.Flags = p.Flags;
return copy;
}
private void FromXml (string xml)
{
SecurityParser sp = new SecurityParser ();
sp.LoadXml (xml);
SecurityElement root = sp.ToXml ();
if (root.Tag == "KeyPair") {
SecurityElement prop = root.SearchForChildByTag ("Properties");
SecurityElement keyv = root.SearchForChildByTag ("KeyValue");
if (keyv.Children.Count > 0)
_keyvalue = keyv.Children [0].ToString ();
// Note: we do not read other stuff because
// it can't be changed after key creation
}
}
private string ToXml ()
{
// note: we do not use SecurityElement here because the
// 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.Length != 0)) {
xml.AppendFormat ("Name=\"{0}\" ", _params.ProviderName);
}
xml.AppendFormat ("Type=\"{0}\" />{1}\t\t<Container ", _params.ProviderType, Environment.NewLine);
xml.AppendFormat ("Name=\"{0}\" />{1}\t</Properties>{1}\t<KeyValue", this.ContainerName, Environment.NewLine);
if (_params.KeyNumber != -1) {
xml.AppendFormat (" Id=\"{0}\" ", _params.KeyNumber);
}
xml.AppendFormat (">{1}\t\t{0}{1}\t</KeyValue>{1}</KeyPair>{1}", this.KeyValue, Environment.NewLine);
return xml.ToString ();
}
}
}
|