diff options
Diffstat (limited to 'main/src/addins/MacPlatform/MacInterop/Keychain.cs')
-rw-r--r-- | main/src/addins/MacPlatform/MacInterop/Keychain.cs | 688 |
1 files changed, 149 insertions, 539 deletions
diff --git a/main/src/addins/MacPlatform/MacInterop/Keychain.cs b/main/src/addins/MacPlatform/MacInterop/Keychain.cs index 2196fe09df..64b76c6ff1 100644 --- a/main/src/addins/MacPlatform/MacInterop/Keychain.cs +++ b/main/src/addins/MacPlatform/MacInterop/Keychain.cs @@ -3,9 +3,11 @@ // // Authors: Michael Hutchinson <mhutchinson@novell.com> // Jeffrey Stedfast <jeff@xamarin.com> +// Javier Suárez <jsuarez@microsoft.com> // // Copyright (c) 2009 Novell, Inc. (http://www.novell.com) // Copyright (c) 2013 Xamarin Inc. (http://www.xamarin.com) +// Copyright (c) 2019 Microsoft Corporation (http://www.microsoft.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 @@ -26,624 +28,232 @@ // THE SOFTWARE. using System; -using System.Text; -using System.Runtime.InteropServices; using Security; +using Foundation; namespace MonoDevelop.MacInterop { public static class Keychain { - const string CoreFoundationLib = ObjCRuntime.Constants.CoreFoundationLibrary; - const string SecurityLib = ObjCRuntime.Constants.SecurityLibrary; - - internal static IntPtr CurrentKeychain = IntPtr.Zero; - - [DllImport (CoreFoundationLib, EntryPoint="CFRelease")] - static extern void CFReleaseInternal (IntPtr cfRef); - - static void CFRelease (IntPtr cfRef) + public static void AddInternetPassword (Uri uri, string password) { - if (cfRef != IntPtr.Zero) - CFReleaseInternal (cfRef); - } - - #region Managing Keychains - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainCreate (byte[] pathName, uint passwordLength, byte[] password, - bool promptUser, IntPtr initialAccess, out IntPtr keychain); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainDelete (IntPtr keychain); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainOpen (byte[] pathName, out IntPtr keychain); + // See if there is already a password there for this uri + var record = SecKeychainFindInternetPassword (uri, out SecRecord searchRecord); - internal static IntPtr CreateKeychain (string path, string password) - { - var passwd = Encoding.UTF8.GetBytes (password); + if (record == null) { + record = uri.ToSecRecord (password: password); - var status = SecKeychainCreate (path.ToNullTerminatedUtf8 (), (uint) passwd.Length, passwd, false, IntPtr.Zero, out IntPtr result); - if (status != SecStatusCode.Success) - throw new Exception (status.GetStatusDescription ()); + SecStatusCode result = SecKeyChain.Add (record); - return result; - } + if (result != SecStatusCode.Success && result != SecStatusCode.DuplicateItem) + throw new Exception ("Could not add internet password to keychain: " + result.GetStatusDescription ()); - internal static bool TryDeleteKeychain (string path) - { - var result = SecKeychainOpen (path.ToNullTerminatedUtf8 (), out IntPtr ptr); - try { - if (result == SecStatusCode.Success) { - DeleteKeychain (ptr); - return true; - } - } catch { - // If we call 'SecKeychainOpen' on a keychain path that does not exist, - // we get a return value of 'success' and we also get a non-null handle. - // As such there's no way for the test suite to safely check if a keychain - // exists so it can delete a pre-existing one before exceuting. Work around - // this by wrapping in a try/catch. The 'DeleteKeyChain (IntPtr)' method - // will throw a 'key chain does not exist' error if there is no pre-existing - // keychain - } finally { - CFRelease (ptr); + return; } - return false; - } - internal static void DeleteKeychain (IntPtr keychain) - { - var status = SecKeychainDelete (keychain); - if (status != SecStatusCode.Success) - throw new Exception (status.GetStatusDescription ()); - } - - #endregion - - #region Storing and Retrieving Passwords - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainAddInternetPassword (IntPtr keychain, uint serverNameLength, byte[] serverName, uint securityDomainLength, - byte[] securityDomain, uint accountNameLength, byte[] accountName, uint pathLength, - byte[] path, ushort port, SecProtocolType protocol, SecAuthenticationType authType, - uint passwordLength, byte[] passwordData, ref IntPtr itemRef); - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainFindInternetPassword (IntPtr keychain, uint serverNameLength, byte[] serverName, uint securityDomainLength, - byte[] securityDomain, uint accountNameLength, byte[] accountName, uint pathLength, - byte[] path, ushort port, SecProtocolType protocol, SecAuthenticationType authType, - out uint passwordLength, out IntPtr passwordData, ref IntPtr itemRef); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainFindInternetPassword (IntPtr keychain, uint serverNameLength, byte[] serverName, uint securityDomainLength, - byte[] securityDomain, uint accountNameLength, byte[] accountName, uint pathLength, - byte[] path, ushort port, SecProtocolType protocol, SecAuthenticationType authType, - IntPtr passwordLengthRef, IntPtr passwordDataRef, ref IntPtr itemRef); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainAddGenericPassword (IntPtr keychain, uint serviceNameLength, byte[] serviceName, - uint accountNameLength, byte[] accountName, uint passwordLength, - byte[] passwordData, ref IntPtr itemRef); - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainFindGenericPassword (IntPtr keychain, uint serviceNameLength, byte[] serviceName, - uint accountNameLength, byte[] accountName, out uint passwordLength, - out IntPtr passwordData, ref IntPtr itemRef); - - #endregion - - #region Creating and Deleting Keychain Items - - [StructLayout (LayoutKind.Sequential)] - struct SecKeychainAttributeList - { - public int Count; - public IntPtr Attrs; + record.ValueData = NSData.FromString (password); - public SecKeychainAttributeList (int count, IntPtr attrs) - { - Count = count; - Attrs = attrs; - } + // If there is, replace it with the new one + SecKeyChain.Update (searchRecord, record); } - [StructLayout (LayoutKind.Sequential)] - struct SecKeychainAttribute + public static void AddInternetPassword (Uri uri, string username, string password) { - public SecItemAttr Tag; - public uint Length; - public IntPtr Data; - - public SecKeychainAttribute (SecItemAttr tag, uint length, IntPtr data) - { - Tag = tag; - Length = length; - Data = data; - } - } + // See if there is already a password there for this uri + var record = SecKeychainFindInternetPassword (uri, out SecRecord searchRecord); - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemCreateFromContent (SecItemClass itemClass, SecKeychainAttributeList *attrList, - uint passwordLength, byte[] password, IntPtr keychain, - IntPtr initialAccess, IntPtr itemRef); + if (record == null) { + record = uri.ToSecRecord (username, password); - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainItemDelete (IntPtr itemRef); + SecStatusCode result = SecKeyChain.Add (record); - #endregion + if (result != SecStatusCode.Success && result != SecStatusCode.DuplicateItem) + throw new Exception ("Could not add internet password to keychain: " + result.GetStatusDescription ()); - #region Managing Keychain Items + return; + } - [StructLayout (LayoutKind.Sequential)] - unsafe struct SecKeychainAttributeInfo - { - public uint Count; - public int* Tag; - public int* Format; + // If there is, replace it with the new one + var update = uri.ToSecRecord (username, password); + SecKeyChain.Update (record, update); } - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemFreeAttributesAndData (SecKeychainAttributeList* list, IntPtr data); - - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemCopyAttributesAndData (IntPtr itemRef, SecKeychainAttributeInfo* info, ref SecItemClass itemClass, - SecKeychainAttributeList** attrList, IntPtr lengthRef, IntPtr outDataRef); - - [DllImport (SecurityLib)] - static extern unsafe SecStatusCode SecKeychainItemModifyAttributesAndData (IntPtr itemRef, SecKeychainAttributeList *attrList, uint length, byte [] data); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainItemCopyContent (IntPtr itemRef, ref SecItemClass itemClass, IntPtr attrList, ref uint length, ref IntPtr data); - - [DllImport (SecurityLib)] - static extern SecStatusCode SecKeychainItemFreeContent (IntPtr attrList, IntPtr data); - - #endregion - - static SecAuthenticationType GetSecAuthenticationType (string query) + public static string FindInternetPassword (Uri url) { - if (string.IsNullOrEmpty (query)) - return SecAuthenticationType.Any; + // See if there is already a password there for this uri + var record = SecKeychainFindInternetPassword (url, out _); - string auth = "default"; - foreach (var pair in query.Substring (1).Split (new char[] { '&' })) { - var kvp = pair.Split (new char[] { '=' }); - if (string.Equals (kvp[0], "auth", StringComparison.InvariantCultureIgnoreCase) && kvp.Length == 2) { - auth = kvp[1].ToLowerInvariant (); - break; - } - } + if (record != null) + return NSString.FromData (record.ValueData, NSStringEncoding.UTF8); - switch (auth) { - case "ntlm": return SecAuthenticationType.NTLM; - case "msn": return SecAuthenticationType.MSN; - case "dpa": return SecAuthenticationType.DPA; - case "rpa": return SecAuthenticationType.RPA; - case "httpbasic": case "basic": return SecAuthenticationType.HTTPBasic; - case "httpdigest": case "digest": return SecAuthenticationType.HTTPDigest; - case "htmlform": case "form": return SecAuthenticationType.HTMLForm; - case "default": return SecAuthenticationType.Default; - default: return SecAuthenticationType.Any; - } + return null; } - static SecProtocolType GetSecProtocolType (string protocol) + public static unsafe Tuple<string, string> FindInternetUserNameAndPassword (Uri uri) { - switch (protocol.ToLowerInvariant ()) { - case "ftp": return SecProtocolType.FTP; - case "ftpaccount": return SecProtocolType.FTPAccount; - case "http": return SecProtocolType.HTTP; - case "irc": return SecProtocolType.IRC; - case "nntp": return SecProtocolType.NNTP; - case "pop3": return SecProtocolType.POP3; - case "smtp": return SecProtocolType.SMTP; - case "socks": return SecProtocolType.SOCKS; - case "imap": return SecProtocolType.IMAP; - case "ldap": return SecProtocolType.LDAP; - case "appletalk": return SecProtocolType.AppleTalk; - case "afp": return SecProtocolType.AFP; - case "telnet": return SecProtocolType.Telnet; - case "ssh": return SecProtocolType.SSH; - case "ftps": return SecProtocolType.FTPS; - case "httpproxy": return SecProtocolType.HTTPProxy; - case "httpsproxy": return SecProtocolType.HTTPSProxy; - case "ftpproxy": return SecProtocolType.FTPProxy; - case "cifs": return SecProtocolType.CIFS; - case "smb": return SecProtocolType.SMB; - case "rtsp": return SecProtocolType.RTSP; - case "rtspproxy": return SecProtocolType.RTSPProxy; - case "daap": return SecProtocolType.DAAP; - case "eppc": return SecProtocolType.EPPC; - case "ipp": return SecProtocolType.IPP; - case "nntps": return SecProtocolType.NNTPS; - case "ldaps": return SecProtocolType.LDAPS; - case "telnets": return SecProtocolType.TelnetS; - case "imaps": return SecProtocolType.IMAPS; - case "ircs": return SecProtocolType.IRCS; - case "pop3s": return SecProtocolType.POP3S; - case "cvspserver": return SecProtocolType.CVSpserver; - case "svn": return SecProtocolType.SVN; - default: return SecProtocolType.Any; - } + return FindInternetUserNameAndPassword (uri, GetSecProtocolType (uri.Scheme)); } - static unsafe SecStatusCode ReplaceInternetPassword (IntPtr item, byte[] desc, byte[] passwd) + public static Tuple<string, string> FindInternetUserNameAndPassword (Uri url, SecProtocol protocol) { - fixed (byte* descPtr = desc) { - SecKeychainAttribute* attrs = stackalloc SecKeychainAttribute [1]; - int n = 0; + var record = SecKeychainFindInternetPassword (url, protocol, out _); - if (desc != null) - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Description, (uint) desc.Length, (IntPtr) descPtr); + if (record != null) { - SecKeychainAttributeList attrList = new SecKeychainAttributeList (n, (IntPtr) attrs); + string username = record.Account != null ? NSString.FromData (record.Account, NSStringEncoding.UTF8) : null; + string password = record.ValueData != null ? NSString.FromData (record.ValueData, NSStringEncoding.UTF8) : null; - return SecKeychainItemModifyAttributesAndData (item, &attrList, (uint) passwd.Length, passwd); + return Tuple.Create (username, password); } + return null; } - static unsafe SecStatusCode AddInternetPassword (byte[] label, byte[] desc, SecAuthenticationType auth, byte[] user, byte[] passwd, SecProtocolType protocol, byte[] host, int port, byte[] path) + public static void RemoveInternetPassword (Uri url) { - // Note: the following code does more-or-less the same as: - //SecKeychainAddInternetPassword (CurrentKeychain, (uint) host.Length, host, 0, null, - // (uint) user.Length, user, (uint) path.Length, path, (ushort) port, - // protocol, auth, (uint) passwd.Length, passwd, ref item); - - fixed (byte* labelPtr = label, descPtr = desc, userPtr = user, hostPtr = host, pathPtr = path) { - SecKeychainAttribute* attrs = stackalloc SecKeychainAttribute [8]; - int* protoPtr = (int*) &protocol; - int* authPtr = (int*) &auth; - int* portPtr = &port; - int n = 0; - - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Label, (uint) label.Length, (IntPtr) labelPtr); - if (desc != null) - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Description, (uint) desc.Length, (IntPtr) descPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Account, (uint) user.Length, (IntPtr) userPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Protocol, (uint) 4, (IntPtr) protoPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.AuthType, (uint) 4, (IntPtr) authPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Server, (uint) host.Length, (IntPtr) hostPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Port, (uint) 4, (IntPtr) portPtr); - attrs[n++] = new SecKeychainAttribute (SecItemAttr.Path, (uint) Math.Max (path.Length - 1, 0), (IntPtr) pathPtr); - - SecKeychainAttributeList attrList = new SecKeychainAttributeList (n, (IntPtr) attrs); - - var result = SecKeychainItemCreateFromContent (SecItemClass.InternetPassword, &attrList, (uint) passwd.Length, passwd, CurrentKeychain, IntPtr.Zero, IntPtr.Zero); - - return result; - } - } - - public static unsafe void AddInternetPassword (Uri uri, string username, string password) - { - byte[] path = uri.ToPathBytes (); - byte[] passwd = Encoding.UTF8.GetBytes (password); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - byte[] user = Encoding.UTF8.GetBytes (username); - var auth = GetSecAuthenticationType (uri.Query); - var protocol = GetSecProtocolType (uri.Scheme); - IntPtr item = IntPtr.Zero; - int port = uri.Port; - byte[] desc = null; + var record = SecKeychainFindInternetPassword (url, out _); - if (auth == SecAuthenticationType.HTMLForm) - desc = WebFormPassword; + if (record != null) { + var result = SecKeyChain.Remove (record); - // See if there is already a password there for this uri - var result = SecKeychainFindInternetPassword (CurrentKeychain, (uint)host.Length, host, 0, null, - (uint)user.Length, user, (uint)path.Length, path, (ushort)port, - protocol, auth, IntPtr.Zero, IntPtr.Zero, ref item); - - try { - if (result == SecStatusCode.Success) { - // If there is, replace it with the new one - result = ReplaceInternetPassword (item, desc, passwd); - } else { - var label = Encoding.UTF8.GetBytes (string.Format ("{0} ({1})", uri.Host, username)); - - result = AddInternetPassword (label, desc, auth, user, passwd, protocol, host, port, path); - } - } finally { - CFRelease (item); + if (result != SecStatusCode.Success) + throw new Exception ("Could not delete internet password from keychain: " + result.GetStatusDescription ()); } - - if (result != SecStatusCode.Success && result != SecStatusCode.DuplicateItem) - throw new Exception ("Could not add internet password to keychain: " + result.GetStatusDescription ()); } - static readonly byte[] WebFormPassword = Encoding.UTF8.GetBytes ("Web form password"); - - public static unsafe void AddInternetPassword (Uri uri, string password) => - AddInternetPassword (uri, Uri.UnescapeDataString (uri.UserInfo), password); - - static unsafe string GetUsernameFromKeychainItemRef (IntPtr itemRef) + public static void RemoveInternetUserNameAndPassword (Uri url) { - int[] formatConstants = { (int) CssmDbAttributeFormat.String }; - int[] attributeTags = { (int) SecItemAttr.Account }; + var record = SecKeychainFindInternetPassword (url, out _); - fixed (int* tags = attributeTags, formats = formatConstants) { - var attributeInfo = new SecKeychainAttributeInfo { - Count = 1, - Tag = tags, - Format = formats - }; - SecKeychainAttributeList* attributeList = null; - SecItemClass itemClass = 0; + if (record != null) { + SecStatusCode result = SecKeyChain.Remove (record); - try { - SecStatusCode status = SecKeychainItemCopyAttributesAndData (itemRef, &attributeInfo, ref itemClass, &attributeList, IntPtr.Zero, IntPtr.Zero); - - if (status == SecStatusCode.ItemNotFound) - throw new Exception ("Could not add internet password to keychain: " + status.GetStatusDescription ()); - - if (status != SecStatusCode.Success) - throw new Exception ("Could not find internet username and password: " + status.GetStatusDescription ()); - - var userNameAttr = (SecKeychainAttribute*)attributeList->Attrs; - - if (userNameAttr->Length == 0) - return null; - - return Marshal.PtrToStringAuto (userNameAttr->Data, (int)userNameAttr->Length); - } finally { - SecKeychainItemFreeAttributesAndData (attributeList, IntPtr.Zero); - } + if (result != SecStatusCode.Success) + throw new Exception ("Could not delete internet password from keychain: " + result.GetStatusDescription ()); } } - public static unsafe Tuple<string, string> FindInternetUserNameAndPassword (Uri uri) + static SecRecord SecKeychainFindInternetPassword (Uri uri, out SecRecord searchRecord) { - var protocol = GetSecProtocolType (uri.Scheme); - return FindInternetUserNameAndPassword (uri, protocol); + return SecKeychainFindInternetPassword (uri, GetSecProtocolType (uri.Scheme), out searchRecord); } - public static unsafe Tuple<string, string> FindInternetUserNameAndPassword (Uri uri, SecProtocolType protocol) + static SecRecord SecKeychainFindInternetPassword (Uri uri, SecProtocol protocol, out SecRecord searchRecord) { - byte[] path = uri.ToPathBytes (); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); - IntPtr passwordData; - IntPtr item = IntPtr.Zero; - uint passwordLength = 0; - - var result = SecKeychainFindInternetPassword ( - CurrentKeychain, (uint) host.Length, host, 0, null, - 0, null, (uint) path.Length, path, (ushort) uri.Port, - protocol, auth, out passwordLength, out passwordData, ref item); - - try { - if (result != SecStatusCode.Success) - return null; + // Look for an internet password for the given protocol and auth mechanism + searchRecord = uri.ToSecRecord (); + if (protocol != SecProtocol.Invalid) + searchRecord.Protocol = protocol; - var username = GetUsernameFromKeychainItemRef (item); - return Tuple.Create (username, Marshal.PtrToStringAuto (passwordData, (int) passwordLength)); - } finally { - SecKeychainItemFreeContent (IntPtr.Zero, passwordData); - CFRelease (item); - } - } + var data = SecKeyChain.QueryAsRecord (searchRecord, out SecStatusCode code); - public static string FindInternetPassword (Uri uri) - { - byte[] path = uri.ToPathBytes (); - byte[] user = Encoding.UTF8.GetBytes (Uri.UnescapeDataString (uri.UserInfo)); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); - var protocol = GetSecProtocolType (uri.Scheme); - IntPtr passwordData = IntPtr.Zero; - IntPtr item = IntPtr.Zero; - uint passwordLength = 0; - - try { - // Look for an internet password for the given protocol and auth mechanism - var result = SecKeychainFindInternetPassword (CurrentKeychain, (uint)host.Length, host, 0, null, - (uint)user.Length, user, (uint)path.Length, path, (ushort)uri.Port, - protocol, auth, out passwordLength, out passwordData, ref item); - - // Fall back to looking for a password for SecProtocolType.Any && SecAuthenticationType.Any - if (result == SecStatusCode.ItemNotFound && protocol != SecProtocolType.Any) - result = SecKeychainFindInternetPassword (CurrentKeychain, (uint)host.Length, host, 0, null, - (uint)user.Length, user, (uint)path.Length, path, (ushort)uri.Port, - 0, auth, out passwordLength, out passwordData, ref item); + if (code == SecStatusCode.ItemNotFound) { + // Fall back to looking for a password without use SecProtocol && SecAuthenticationType + searchRecord.Protocol = SecProtocol.Http; // Http is the default used by SecKeyChain internally + searchRecord.AuthenticationType = SecAuthenticationType.Default; - if (result != SecStatusCode.Success) - return null; + data = SecKeyChain.QueryAsRecord (searchRecord, out code); + } - return Marshal.PtrToStringAuto (passwordData, (int)passwordLength); - } finally { - CFRelease (item); + if (code != SecStatusCode.Success) + return null; - SecKeychainItemFreeContent (IntPtr.Zero, passwordData); - } + return data; } - public static void RemoveInternetPassword (Uri uri) + static readonly string WebFormPassword = "Web form password"; + + static SecRecord ToSecRecord (this Uri uri, string username = null, string password = null) { - byte[] path = uri.ToPathBytes (); - byte[] user = Encoding.UTF8.GetBytes (Uri.UnescapeDataString (uri.UserInfo)); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); + var record = new SecRecord (SecKind.InternetPassword) { + Server = uri.Host, + Path = string.Join (string.Empty, uri.Segments), + Port = uri.Port, + }; var protocol = GetSecProtocolType (uri.Scheme); - IntPtr item = IntPtr.Zero; + if (protocol != SecProtocol.Invalid) + record.Protocol = protocol; + var authType = GetSecAuthenticationType (uri.Query); + if (authType != SecAuthenticationType.Default) + record.AuthenticationType = authType; - // Look for an internet password for the given protocol and auth mechanism - var result = SecKeychainFindInternetPassword (CurrentKeychain, (uint) host.Length, host, 0, null, - (uint) user.Length, user, (uint) path.Length, path, (ushort) uri.Port, - protocol, auth, IntPtr.Zero, IntPtr.Zero, ref item); + if (record.AuthenticationType == SecAuthenticationType.HtmlForm) + record.Description = WebFormPassword; - // Fall back to looking for a password for SecProtocolType.Any && SecAuthenticationType.Any - if (result == SecStatusCode.ItemNotFound && protocol != SecProtocolType.Any) - result = SecKeychainFindInternetPassword (CurrentKeychain, (uint) host.Length, host, 0, null, - (uint) user.Length, user, (uint) path.Length, path, (ushort) uri.Port, - 0, auth, IntPtr.Zero, IntPtr.Zero, ref item); + var account = Uri.UnescapeDataString (uri.UserInfo); + if (string.IsNullOrEmpty (account)) // account from Uri has always priority + account = username; - try { - if (result != SecStatusCode.Success) - return; + if (!string.IsNullOrEmpty (account)) + record.Account = account; - SecKeychainItemDelete (item); - } finally { - CFRelease (item); - } + if (password != null) + record.ValueData = NSData.FromString (password); + + return record; } - public static void RemoveInternetUserNameAndPassword (Uri uri) + static SecProtocol GetSecProtocolType (string protocol) { - byte[] path = uri.ToPathBytes (); - byte[] host = Encoding.UTF8.GetBytes (uri.Host); - var auth = GetSecAuthenticationType (uri.Query); - IntPtr item = IntPtr.Zero; - - var result = SecKeychainFindInternetPassword ( - CurrentKeychain, (uint) host.Length, host, 0, null, - 0, null, (uint) path.Length, path, (ushort) uri.Port, - GetSecProtocolType (uri.Scheme), auth, IntPtr.Zero, IntPtr.Zero, ref item); - - try { - if (result != SecStatusCode.Success) - return; - - result = SecKeychainItemDelete (item); - } finally { - CFRelease (item); + switch (protocol.ToLowerInvariant ()) { + case "ftp": return SecProtocol.Ftp; + case "ftpaccount": return SecProtocol.FtpAccount; + case "http": return SecProtocol.Http; + case "https": return SecProtocol.Https; + case "irc": return SecProtocol.Irc; + case "nntp": return SecProtocol.Nntp; + case "pop3": return SecProtocol.Pop3; + case "pop3s": return SecProtocol.Pop3s; + case "smtp": return SecProtocol.Smtp; + case "socks": return SecProtocol.Socks; + case "imap": return SecProtocol.Imap; + case "imaps": return SecProtocol.Imaps; + case "ldap": return SecProtocol.Ldap; + case "ldaps": return SecProtocol.Ldaps; + case "appletalk": return SecProtocol.AppleTalk; + case "afp": return SecProtocol.Afp; + case "telnet": return SecProtocol.Telnet; + case "ssh": return SecProtocol.Ssh; + case "ftps": return SecProtocol.Ftps; + case "httpproxy": return SecProtocol.HttpProxy; + case "httpsproxy": return SecProtocol.HttpProxy; + case "ftpproxy": return SecProtocol.FtpProxy; + case "smb": return SecProtocol.Smb; + case "rtsp": return SecProtocol.Rtsp; + case "rtspproxy": return SecProtocol.RtspProxy; + case "daap": return SecProtocol.Daap; + case "eppc": return SecProtocol.Eppc; + case "ipp": return SecProtocol.Ipp; + case "nntps": return SecProtocol.Nntps; + case "telnets": return SecProtocol.Telnets; + case "ircs": return SecProtocol.Ircs; + default: return SecProtocol.Invalid; } } - static byte [] ToPathBytes (this Uri uri) + static SecAuthenticationType GetSecAuthenticationType (string query) { - var pathStr = string.Join (string.Empty, uri.Segments); - byte[] path = pathStr.Length > 0 ? pathStr.ToNullTerminatedUtf8 (1) : Array.Empty<byte> (); // don't include the leading '/' - return path; - } + if (string.IsNullOrEmpty (query)) + return SecAuthenticationType.Default; - // It seems that keychain APIs require null terminated native strings. - internal static byte [] ToNullTerminatedUtf8 (this string str, int offset = 0) - { - unsafe { - fixed (char* p = str) { - return ToNullTerminatedUtf8 (p + offset, str.Length - offset); + string auth = "default"; + foreach (var pair in query.Substring (1).Split (new char [] { '&' })) { + var kvp = pair.Split (new char [] { '=' }); + if (string.Equals (kvp [0], "auth", StringComparison.InvariantCultureIgnoreCase) && kvp.Length == 2) { + auth = kvp [1].ToLowerInvariant (); + break; } } - } - - static unsafe byte [] ToNullTerminatedUtf8 (char* p, int len) - { - if (len == 0) - return Array.Empty<byte> (); - var byteCount = Encoding.UTF8.GetByteCount (p, len); - var bytes = new byte [byteCount + 1]; - fixed (byte* b = bytes) { - Encoding.UTF8.GetBytes (p, len, b, byteCount); + switch (auth) { + case "ntlm": return SecAuthenticationType.Ntlm; + case "msn": return SecAuthenticationType.Msn; + case "dpa": return SecAuthenticationType.Dpa; + case "rpa": return SecAuthenticationType.Rpa; + case "httpbasic": case "basic": return SecAuthenticationType.HttpBasic; + case "httpdigest": case "digest": return SecAuthenticationType.HttpDigest; + case "htmlform": case "form": return SecAuthenticationType.HtmlForm; + default: return SecAuthenticationType.Default; } - return bytes; } } - - enum SecItemClass : uint - { - InternetPassword = 1768842612, // 'inet' - GenericPassword = 1734700656, // 'genp' - AppleSharePassword = 1634953328, // 'ashp' - Certificate = 0x80000000 + 0x1000, - PublicKey = 0x0000000A + 5, - PrivateKey = 0x0000000A + 6, - SymmetricKey = 0x0000000A + 7 - } - - enum SecItemAttr : int - { - CreationDate = 1667522932, - ModDate = 1835295092, - Description = 1684370275, - Comment = 1768123764, - Creator = 1668445298, - Type = 1954115685, - ScriptCode = 1935897200, - Label = 1818321516, - Invisible = 1768846953, - Negative = 1852139361, - CustomIcon = 1668641641, - Account = 1633903476, - Service = 1937138533, - Generic = 1734700641, - SecurityDomain = 1935961454, - Server = 1936881266, - AuthType = 1635023216, - Port = 1886351988, - Path = 1885434984, - Volume = 1986817381, - Address = 1633969266, - Signature = 1936943463, - Protocol = 1886675820, - CertificateType = 1668577648, - CertificateEncoding = 1667591779, - CrlType = 1668445296, - CrlEncoding = 1668443747, - Alias = 1634494835, - } - - enum SecAuthenticationType : int - { - NTLM = 1835824238, - MSN = 1634628461, - DPA = 1633775716, - RPA = 1633775730, - HTTPBasic = 1886680168, - HTTPDigest = 1685353576, - HTMLForm = 1836216166, - Default = 1953261156, - Any = 0 - } - - public enum SecProtocolType : int - { - FTP = 1718906912, - FTPAccount = 1718906977, - HTTP = 1752462448, - IRC = 1769104160, - NNTP = 1852732528, - POP3 = 1886351411, - SMTP = 1936553072, - SOCKS = 1936685088, - IMAP = 1768776048, - LDAP = 1818517872, - AppleTalk = 1635019883, - AFP = 1634103328, - Telnet = 1952803950, - SSH = 1936943136, - FTPS = 1718906995, - HTTPProxy = 1752461432, - HTTPSProxy = 1752462200, - FTPProxy = 1718907000, - CIFS = 1667851891, - SMB = 1936548384, - RTSP = 1920234352, - RTSPProxy = 1920234360, - DAAP = 1684103536, - EPPC = 1701867619, - IPP = 1768976416, - NNTPS = 1853124723, - LDAPS = 1818521715, - TelnetS = 1952803955, - IMAPS = 1768779891, - IRCS = 1769104243, - POP3S = 1886351475, - CVSpserver = 1668707184, - SVN = 1937141280, - Any = 0 - } - - enum CssmDbAttributeFormat : int - { - String = 0, - Int32 = 1, - UInt32 = 2, - BigNum = 3, - Real = 4, - DateTime = 5, - Blob = 6, - MultiUInt32 = 7, - Complex = 8 - } -} +}
\ No newline at end of file |