Xamarin Public Jenkins (auto-signing) 0abdbe5a7d Imported Upstream version 5.18.0.142
Former-commit-id: 7467d4b717762eeaf652d77f1486dd11ffb1ff1f
2018-10-09 08:20:59 +00:00

471 lines
14 KiB
C#

//
// KeyPairPersistence.cs: Keypair persistence
//
// Author:
// Sebastien Pouliot <sebastien@ximian.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.
//
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; // 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;
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
// FIXME 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
// 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
// FIXME 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
static object lockobj = new object ();
private static string UserPath {
get {
lock (lockobj) {
if ((_userPath == null) || (!_userPathExists)) {
_userPath = Path.Combine (
Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
".mono");
_userPath = Path.Combine (_userPath, "keypairs");
_userPathExists = Directory.Exists (_userPath);
if (!_userPathExists) {
try {
Directory.CreateDirectory (_userPath);
}
catch (Exception e) {
string msg = Locale.GetText ("Could not create user key store '{0}'.");
throw new CryptographicException (String.Format (msg, _userPath), e);
}
_userPathExists = true;
}
}
if (!IsUserProtected (_userPath) && !ProtectUser (_userPath)) {
string msg = Locale.GetText ("Could not secure user key store '{0}'.");
throw new IOException (String.Format (msg, _userPath));
}
}
// 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;
}
}
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);
}
catch (Exception e) {
string msg = Locale.GetText ("Could not create machine key store '{0}'.");
throw new CryptographicException (String.Format (msg, _machinePath), e);
}
_machinePathExists = true;
}
}
if (!IsMachineProtected (_machinePath) && !ProtectMachine (_machinePath)) {
string msg = Locale.GetText ("Could not secure machine key store '{0}'.");
throw new IOException (String.Format (msg, _machinePath));
}
}
// 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)]
unsafe internal static extern bool _CanSecure (char* root);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
unsafe internal static extern bool _ProtectUser (char* path);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
unsafe internal static extern bool _ProtectMachine (char* path);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
unsafe internal static extern bool _IsUserProtected (char* path);
[MethodImplAttribute (MethodImplOptions.InternalCall)]
unsafe internal static extern bool _IsMachineProtected (char* 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)
unsafe internal static bool _CanSecure (char* root)
{
return true;
}
unsafe internal static bool _ProtectUser (char* path)
{
return true;
}
unsafe internal static bool _ProtectMachine (char* path)
{
return true;
}
unsafe internal static bool _IsUserProtected (char* path)
{
return true;
}
unsafe internal static bool _IsMachineProtected (char* path)
{
return true;
}
#endif
// private stuff
unsafe 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
fixed (char* fpath = path) {
return _CanSecure (fpath);
}
}
unsafe private static bool ProtectUser (string path)
{
// we cannot protect on some filsystem (like FAT)
if (CanSecure (path)) {
fixed (char* fpath = path) {
return _ProtectUser (fpath);
}
}
// but Mono still needs to run on them :(
return true;
}
unsafe private static bool ProtectMachine (string path)
{
// we cannot protect on some filsystem (like FAT)
if (CanSecure (path)) {
fixed (char* fpath = path) {
return _ProtectMachine (fpath);
}
}
// but Mono still needs to run on them :(
return true;
}
unsafe private static bool IsUserProtected (string path)
{
// we cannot protect on some filsystem (like FAT)
if (CanSecure (path)) {
fixed (char* fpath = path) {
return _IsUserProtected (fpath);
}
}
// but Mono still needs to run on them :(
return true;
}
unsafe private static bool IsMachineProtected (string path)
{
// we cannot protect on some filsystem (like FAT)
if (CanSecure (path)) {
fixed (char* fpath = path) {
return _IsMachineProtected (fpath);
}
}
// 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);
// 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 ();
}
}
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 ();
}
}
}