├── .gitattributes ├── .gitignore ├── App.config ├── LICENSE ├── NativeMethods.cs ├── PasswordPrompt.Designer.cs ├── PasswordPrompt.cs ├── PasswordPrompt.resx ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── askpass.csproj ├── askpass.sln └── test.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=crlf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | obj/* 3 | *.suo 4 | *~ 5 | -------------------------------------------------------------------------------- /App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD-2-Clause 2 | 3 | Copyright 2014 Luke Sampson 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Runtime.InteropServices; 6 | 7 | /// This is copied from http://gitcredentialstore.codeplex.com/ 8 | namespace askpass { 9 | internal static class NativeMethods { 10 | [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)] 11 | public static extern bool CredWrite(ref CREDENTIAL userCredential, UInt32 flags); 12 | 13 | [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] 14 | public static extern bool CredRead(string target, CRED_TYPE type, int reservedFlag, out IntPtr credentialPtr); 15 | 16 | [DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)] 17 | public static extern bool CredDelete(string target, CRED_TYPE type, int flags); 18 | 19 | [DllImport("advapi32.dll")] 20 | public static extern void CredFree(IntPtr credentialPtr); 21 | 22 | [DllImport("credui.dll", CharSet = CharSet.Unicode)] 23 | public static extern CredUIReturnCodes CredUIPromptForWindowsCredentials( 24 | ref CREDUI_INFO uiInfo, 25 | int authError, 26 | ref int authPackage, 27 | IntPtr InAuthBuffer, 28 | int InAuthBufferSize, 29 | out IntPtr refOutAuthBuffer, 30 | out int refOutAuthBufferSize, 31 | ref bool fSave, 32 | PromptForWindowsCredentialsFlags flags); 33 | 34 | [DllImport("credui.dll", CharSet = CharSet.Auto)] 35 | public static extern bool CredUnPackAuthenticationBuffer( 36 | int dwFlags, 37 | IntPtr pAuthBuffer, 38 | int cbAuthBuffer, 39 | StringBuilder pszUserName, 40 | ref int pcchMaxUserName, 41 | StringBuilder pszDomainName, 42 | ref int pcchMaxDomainame, 43 | StringBuilder pszPassword, 44 | ref int pcchMaxPassword); 45 | 46 | [DllImport("credui.dll", CharSet = CharSet.Unicode, SetLastError = true)] 47 | public static extern bool CredPackAuthenticationBuffer( 48 | int dwFlags, 49 | string pszUserName, 50 | string pszPassword, 51 | IntPtr pPackedCredentials, 52 | ref int pcbPackedCredentials); 53 | 54 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 55 | public struct CREDUI_INFO { 56 | public int cbSize; 57 | public IntPtr hwndParent; 58 | public string pszMessageText; 59 | public string pszCaptionText; 60 | public IntPtr hbmBanner; 61 | } 62 | 63 | public enum PromptForWindowsCredentialsFlags { 64 | /// 65 | /// The caller is requesting that the credential provider return the user name and password in plain text. 66 | /// This value cannot be combined with SECURE_PROMPT. 67 | /// 68 | CREDUIWIN_GENERIC = 0x1, 69 | /// 70 | /// The Save check box is displayed in the dialog box. 71 | /// 72 | CREDUIWIN_CHECKBOX = 0x2, 73 | /// 74 | /// Only credential providers that support the authentication package specified by the authPackage parameter should be enumerated. 75 | /// This value cannot be combined with CREDUIWIN_IN_CRED_ONLY. 76 | /// 77 | CREDUIWIN_AUTHPACKAGE_ONLY = 0x10, 78 | /// 79 | /// Only the credentials specified by the InAuthBuffer parameter for the authentication package specified by the authPackage parameter should be enumerated. 80 | /// If this flag is set, and the InAuthBuffer parameter is NULL, the function fails. 81 | /// This value cannot be combined with CREDUIWIN_AUTHPACKAGE_ONLY. 82 | /// 83 | CREDUIWIN_IN_CRED_ONLY = 0x20, 84 | /// 85 | /// Credential providers should enumerate only administrators. This value is intended for User Account Control (UAC) purposes only. We recommend that external callers not set this flag. 86 | /// 87 | CREDUIWIN_ENUMERATE_ADMINS = 0x100, 88 | /// 89 | /// Only the incoming credentials for the authentication package specified by the authPackage parameter should be enumerated. 90 | /// 91 | CREDUIWIN_ENUMERATE_CURRENT_USER = 0x200, 92 | /// 93 | /// The credential dialog box should be displayed on the secure desktop. This value cannot be combined with CREDUIWIN_GENERIC. 94 | /// Windows Vista: This value is not supported until Windows Vista with SP1. 95 | /// 96 | CREDUIWIN_SECURE_PROMPT = 0x1000, 97 | /// 98 | /// The credential provider should align the credential BLOB pointed to by the refOutAuthBuffer parameter to a 32-bit boundary, even if the provider is running on a 64-bit system. 99 | /// 100 | CREDUIWIN_PACK_32_WOW = 0x10000000, 101 | } 102 | 103 | public enum CRED_TYPE : int { 104 | GENERIC = 1, 105 | DOMAIN_PASSWORD = 2, 106 | DOMAIN_CERTIFICATE = 3, 107 | DOMAIN_VISIBLE_PASSWORD = 4, 108 | MAXIMUM = 5 109 | } 110 | 111 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 112 | public struct CREDENTIAL { 113 | public int flags; 114 | public int type; 115 | [MarshalAs(UnmanagedType.LPWStr)] 116 | public string targetName; 117 | [MarshalAs(UnmanagedType.LPWStr)] 118 | public string comment; 119 | public System.Runtime.InteropServices.ComTypes.FILETIME lastWritten; 120 | public int credentialBlobSize; 121 | public IntPtr credentialBlob; 122 | public int persist; 123 | public int attributeCount; 124 | public IntPtr credAttribute; 125 | [MarshalAs(UnmanagedType.LPWStr)] 126 | public string targetAlias; 127 | [MarshalAs(UnmanagedType.LPWStr)] 128 | public string userName; 129 | } 130 | 131 | public enum CredUIReturnCodes : int { 132 | NO_ERROR = 0, 133 | ERROR_CANCELLED = 1223, 134 | ERROR_NO_SUCH_LOGON_SESSION = 1312, 135 | ERROR_NOT_FOUND = 1168, 136 | ERROR_INVALID_ACCOUNT_NAME = 1315, 137 | ERROR_INSUFFICIENT_BUFFER = 122, 138 | ERROR_INVALID_PARAMETER = 87, 139 | ERROR_INVALID_FLAGS = 1004 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /PasswordPrompt.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace askpass { 2 | partial class PasswordPrompt { 3 | /// 4 | /// Required designer variable. 5 | /// 6 | private System.ComponentModel.IContainer components = null; 7 | 8 | /// 9 | /// Clean up any resources being used. 10 | /// 11 | /// true if managed resources should be disposed; otherwise, false. 12 | protected override void Dispose(bool disposing) { 13 | if(disposing && (components != null)) { 14 | components.Dispose(); 15 | } 16 | base.Dispose(disposing); 17 | } 18 | 19 | #region Windows Form Designer generated code 20 | 21 | /// 22 | /// Required method for Designer support - do not modify 23 | /// the contents of this method with the code editor. 24 | /// 25 | private void InitializeComponent() { 26 | this.ok = new System.Windows.Forms.Button(); 27 | this.cancel = new System.Windows.Forms.Button(); 28 | this.panel1 = new System.Windows.Forms.Panel(); 29 | this.saveCheckbox = new System.Windows.Forms.CheckBox(); 30 | this.badPassLabel = new System.Windows.Forms.Label(); 31 | this.passwordLabel = new System.Windows.Forms.Label(); 32 | this.passwordText = new System.Windows.Forms.TextBox(); 33 | this.label1 = new System.Windows.Forms.Label(); 34 | this.panel1.SuspendLayout(); 35 | this.SuspendLayout(); 36 | // 37 | // ok 38 | // 39 | this.ok.Location = new System.Drawing.Point(236, 142); 40 | this.ok.Name = "ok"; 41 | this.ok.Size = new System.Drawing.Size(75, 25); 42 | this.ok.TabIndex = 2; 43 | this.ok.Text = "OK"; 44 | this.ok.UseVisualStyleBackColor = true; 45 | this.ok.Click += new System.EventHandler(this.ok_Click); 46 | // 47 | // cancel 48 | // 49 | this.cancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 50 | this.cancel.Location = new System.Drawing.Point(317, 142); 51 | this.cancel.Name = "cancel"; 52 | this.cancel.Size = new System.Drawing.Size(75, 25); 53 | this.cancel.TabIndex = 3; 54 | this.cancel.Text = "Cancel"; 55 | this.cancel.UseVisualStyleBackColor = true; 56 | // 57 | // panel1 58 | // 59 | this.panel1.BackColor = System.Drawing.SystemColors.Window; 60 | this.panel1.Controls.Add(this.saveCheckbox); 61 | this.panel1.Controls.Add(this.badPassLabel); 62 | this.panel1.Controls.Add(this.passwordLabel); 63 | this.panel1.Controls.Add(this.passwordText); 64 | this.panel1.Controls.Add(this.label1); 65 | this.panel1.ForeColor = System.Drawing.SystemColors.ControlText; 66 | this.panel1.Location = new System.Drawing.Point(-5, -8); 67 | this.panel1.Name = "panel1"; 68 | this.panel1.Size = new System.Drawing.Size(415, 144); 69 | this.panel1.TabIndex = 2; 70 | // 71 | // saveCheckbox 72 | // 73 | this.saveCheckbox.AutoSize = true; 74 | this.saveCheckbox.Location = new System.Drawing.Point(21, 113); 75 | this.saveCheckbox.Name = "saveCheckbox"; 76 | this.saveCheckbox.Size = new System.Drawing.Size(102, 17); 77 | this.saveCheckbox.TabIndex = 3; 78 | this.saveCheckbox.Text = "Save password"; 79 | this.saveCheckbox.UseVisualStyleBackColor = true; 80 | // 81 | // badPassLabel 82 | // 83 | this.badPassLabel.AutoSize = true; 84 | this.badPassLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 85 | this.badPassLabel.ForeColor = System.Drawing.Color.Red; 86 | this.badPassLabel.Location = new System.Drawing.Point(18, 41); 87 | this.badPassLabel.Name = "badPassLabel"; 88 | this.badPassLabel.Size = new System.Drawing.Size(137, 15); 89 | this.badPassLabel.TabIndex = 2; 90 | this.badPassLabel.Text = "Bad password, try again"; 91 | this.badPassLabel.Visible = false; 92 | // 93 | // passwordLabel 94 | // 95 | this.passwordLabel.AutoSize = true; 96 | this.passwordLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 97 | this.passwordLabel.Location = new System.Drawing.Point(18, 59); 98 | this.passwordLabel.Name = "passwordLabel"; 99 | this.passwordLabel.Size = new System.Drawing.Size(217, 15); 100 | this.passwordLabel.TabIndex = 0; 101 | this.passwordLabel.Text = "Password for /c/Users/Luke/.ssh//id_rsa"; 102 | // 103 | // passwordText 104 | // 105 | this.passwordText.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 106 | this.passwordText.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 107 | this.passwordText.ForeColor = System.Drawing.SystemColors.WindowText; 108 | this.passwordText.Location = new System.Drawing.Point(19, 82); 109 | this.passwordText.Name = "passwordText"; 110 | this.passwordText.Size = new System.Drawing.Size(378, 23); 111 | this.passwordText.TabIndex = 1; 112 | this.passwordText.UseSystemPasswordChar = true; 113 | this.passwordText.TextChanged += new System.EventHandler(this.passwordText_TextChanged); 114 | // 115 | // label1 116 | // 117 | this.label1.AutoSize = true; 118 | this.label1.Font = new System.Drawing.Font("Segoe UI", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 119 | this.label1.ForeColor = System.Drawing.Color.MediumBlue; 120 | this.label1.Location = new System.Drawing.Point(17, 17); 121 | this.label1.Name = "label1"; 122 | this.label1.Size = new System.Drawing.Size(191, 21); 123 | this.label1.TabIndex = 0; 124 | this.label1.Text = "SSH Private Key Password"; 125 | // 126 | // PasswordPrompt 127 | // 128 | this.AcceptButton = this.ok; 129 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 130 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 131 | this.CancelButton = this.cancel; 132 | this.ClientSize = new System.Drawing.Size(404, 179); 133 | this.Controls.Add(this.panel1); 134 | this.Controls.Add(this.cancel); 135 | this.Controls.Add(this.ok); 136 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 137 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 138 | this.Name = "PasswordPrompt"; 139 | this.ShowIcon = false; 140 | this.ShowInTaskbar = false; 141 | this.Text = "SSH"; 142 | this.panel1.ResumeLayout(false); 143 | this.panel1.PerformLayout(); 144 | this.ResumeLayout(false); 145 | 146 | } 147 | 148 | #endregion 149 | 150 | private System.Windows.Forms.Button ok; 151 | private System.Windows.Forms.Button cancel; 152 | private System.Windows.Forms.Panel panel1; 153 | private System.Windows.Forms.Label label1; 154 | private System.Windows.Forms.TextBox passwordText; 155 | private System.Windows.Forms.Label passwordLabel; 156 | private System.Windows.Forms.Label badPassLabel; 157 | private System.Windows.Forms.CheckBox saveCheckbox; 158 | } 159 | } -------------------------------------------------------------------------------- /PasswordPrompt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace askpass { 12 | public partial class PasswordPrompt : Form { 13 | public PasswordPrompt(string key, bool badPass) { 14 | InitializeComponent(); 15 | 16 | passwordLabel.Text = "Password for " + key; 17 | passwordText.Focus(); 18 | if(badPass) { badPassLabel.Visible = true; } 19 | } 20 | 21 | public string Password { 22 | get { return passwordText.Text; } 23 | } 24 | 25 | public bool Save { 26 | get { return saveCheckbox.Checked; } 27 | } 28 | 29 | private void ok_Click(object sender, EventArgs e) { 30 | this.DialogResult = DialogResult.OK; 31 | } 32 | 33 | private void passwordText_TextChanged(object sender, EventArgs e) { 34 | badPassLabel.Visible = false; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /PasswordPrompt.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | using System.Threading.Tasks; 11 | using System.Windows.Forms; 12 | 13 | namespace askpass { 14 | class Program { 15 | static string log = null; 16 | 17 | static void Main(string[] args) { 18 | log = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "askpass.log"); 19 | if(File.Exists(log)) { File.Delete(log); } 20 | 21 | var lastPassFail = false; 22 | var m = Regex.Match(args[0], "Enter passphrase for ([^:]+)", RegexOptions.IgnoreCase); 23 | if(!m.Success) { 24 | m = Regex.Match(args[0], "Bad passphrase, try again for ([^:]+)", RegexOptions.IgnoreCase); 25 | if(m.Success) { 26 | Log("last password failed"); 27 | lastPassFail = true; 28 | } else { 29 | Log("error parsing arguments: " + string.Join(", ", args)); 30 | Environment.Exit(1); 31 | } 32 | } 33 | 34 | var key = m.Groups[1].Value; 35 | 36 | // make it look like a windows path 37 | key = Regex.Replace(key, "/{2,}", "/"); // clean double slashes 38 | key = Regex.Replace(key, "^/([A-Za-z])/", "$1:/"); // drive 39 | key = Regex.Replace(key, "/", "\\"); // use backslashes 40 | 41 | Log("key: " + key); 42 | 43 | string password = null; 44 | if(lastPassFail) { 45 | Log("removing saved password"); 46 | DeletePassword(key); 47 | } else { 48 | password = SavedPassword(key); 49 | } 50 | 51 | if(password != null) { 52 | Log("found saved password"); 53 | Console.WriteLine(password); // send password to ssh-add 54 | } else { 55 | Log("no password saved for " + key); 56 | bool save; 57 | password = PromptForPassword(key, lastPassFail, out save); 58 | if(password == null) { // empty or cancelled 59 | // failed: agent should ignore output and not call askpass again 60 | Environment.Exit(1); 61 | } 62 | Log("collected password"); 63 | if(save) { 64 | Log("saving password"); 65 | SavePassword(key, password); 66 | } 67 | Console.WriteLine(password); // send password to ssh-add 68 | } 69 | } 70 | 71 | static string PromptForPassword(string key, bool lastPassFail, out bool save) { 72 | Application.EnableVisualStyles(); 73 | var prompt = new PasswordPrompt(key, lastPassFail); 74 | var res = prompt.ShowDialog(); 75 | save = false; 76 | if(res == DialogResult.OK) { 77 | if(string.IsNullOrEmpty(prompt.Password)) return null; 78 | save = prompt.Save; 79 | return prompt.Password; 80 | } 81 | return null; 82 | } 83 | 84 | static string SavedPassword(string key) { 85 | IntPtr credPtr = IntPtr.Zero; 86 | var ok = NativeMethods.CredRead("ssh:" + key, NativeMethods.CRED_TYPE.GENERIC, 0, out credPtr); 87 | if(!ok) return null; 88 | 89 | // Decode the credential 90 | var cred = (NativeMethods.CREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(NativeMethods.CREDENTIAL)); 91 | 92 | var bytes = new byte[cred.credentialBlobSize]; 93 | Marshal.Copy(cred.credentialBlob, bytes, 0, bytes.Length); 94 | return Encoding.Unicode.GetString(bytes); 95 | } 96 | 97 | static bool SavePassword(string key, string password) { 98 | string target = "ssh:" + key; 99 | NativeMethods.CREDENTIAL cred = new NativeMethods.CREDENTIAL() { 100 | type = 0x01, // Generic 101 | targetName = target, 102 | credentialBlob = Marshal.StringToCoTaskMemUni(password), 103 | persist = 0x02, // Local machine 104 | attributeCount = 0, 105 | userName = "(n/a)" 106 | }; 107 | cred.credentialBlobSize = Encoding.Unicode.GetByteCount(password); 108 | if(!NativeMethods.CredWrite(ref cred, 0)) { 109 | Log("Failed to write credential: " + GetLastErrorMessage()); 110 | return false; 111 | } 112 | return true; 113 | } 114 | 115 | static void DeletePassword(string key) { 116 | var target = "ssh:" + key; 117 | if (!NativeMethods.CredDelete(target, NativeMethods.CRED_TYPE.GENERIC, 0)) { 118 | Log("Failed to delete credential: " + GetLastErrorMessage()); 119 | } 120 | } 121 | 122 | static string GetLastErrorMessage() { 123 | return new Win32Exception(Marshal.GetLastWin32Error()).Message; 124 | } 125 | 126 | static void Log(string message) { 127 | File.AppendAllText(log, message + "\r\n"); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("askpass")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("askpass")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9c0acefc-f598-4cb9-b331-c790e3ddf049")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /askpass.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {7808B5D5-36F9-44C5-858B-B47F2BCA7592} 8 | Exe 9 | Properties 10 | askpass 11 | askpass 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Form 49 | 50 | 51 | PasswordPrompt.cs 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | PasswordPrompt.cs 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /askpass.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2012 for Windows Desktop 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "askpass", "askpass.csproj", "{7808B5D5-36F9-44C5-858B-B47F2BCA7592}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {7808B5D5-36F9-44C5-858B-B47F2BCA7592}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {7808B5D5-36F9-44C5-858B-B47F2BCA7592}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {7808B5D5-36F9-44C5-858B-B47F2BCA7592}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {7808B5D5-36F9-44C5-858B-B47F2BCA7592}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /test.ps1: -------------------------------------------------------------------------------- 1 | $env:SSH_ASKPASS = "$pwd\bin\debug\askpass.exe" 2 | $env:DISPLAY = "localhost:0.0" 3 | 4 | write-host "use ```$null | ssh-add`` to trigger" --------------------------------------------------------------------------------