From e49c7db0d5741ba6df78e610f638b65e80f06772 Mon Sep 17 00:00:00 2001 From: Raal Goff Date: Mon, 31 Oct 2022 12:33:06 +0800 Subject: [PATCH 1/2] re-enable adding new keys by using MSPCP --- README.md | 33 +++++++++++++----- go.mod | 1 + keyman/keymanager.go | 38 ++++++++++++++++++++- keyman/utils.go | 10 ++++++ ncrypt/ncrypt.go | 20 ++++++++++- ui/createnew.go | 80 +++++++++++++++++++++----------------------- ui/keyspage.go | 42 ++++++++++++++++++----- 7 files changed, 163 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index f94babe..24618d0 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,43 @@ nCryptAgent Ever been jealous of macOS users and their fancy Secure Enclave backed SSH Keys? Or wanted a nice GUI for managing keys like [Secretive](https://github.com/maxgoedjen/secretive)? nCryptAgent is your answer! -Use any smart card as an SSH key source, and manage them using a nice-ish GUI! Don't have a physical smart card or security key like a Yubikey? No problem -- Create a Virtual Smart Card that is backed by your TPM for hardware backed keys! +Use any smart card as an SSH key source, and manage them using a nice-ish GUI! Don't have a physical smart card or security key like a Yubikey? No problem -- Use the Microsoft Platform Crpyto Provider that is backed by your TPM for hardware backed keys! Use your WebAuthN authenticator as your SSH key with `sk-ssh-ed25519@openssh.com` and `sk-ecdsa-sha2-nistp256@openssh.com` key types. ## Features +* Create TPM-backed hardware keys using the `Microsoft Platform Crypto Provider` (PCP) * Create and use OpenSSH SK keys without middleware * `sk-ssh-ed25519@openssh.com` and `sk-ecdsa-sha2-nistp256@openssh.com` key types, along with their matching certificates -* Import and use nCrypt keys that are backed by hardware - * Windows TPM Smart Card +* Import and use nCrypt keys that are backed by Smart Cards * Yubikeys - * Other smart cards -* An acceptable GUI for managing your hardware-backed keys + * [Virtual Smart Cards](https://learn.microsoft.com/en-us/windows/security/identity-protection/virtual-smart-cards/virtual-smart-card-overview) + * ...any other smart card or PIV applet supported the `Microsoft Smart Card Key Storage Provider` +* A nice-ish GUI for managing your hardware-backed keys * Supports multiple SSH Agent listeners: * OpenSSH for Windows * PuTTY/Pageant * WSL2 * Cygwin/mSys/MinGW * Notifications so you know when your key is being used -* Configurable PIN cache, so you don't have to re-enter your PIN for rapid successive key usage (smart cards only) +* Configurable PIN/Password cache, so you don't have to re-enter your PIN/Password for rapid successive key usage (not available for WebAuthN keys) * OpenSSH Certificates * Adds support for OpenSSH certificates to PuTTY! +## Getting Started + +* Download the latest release +* Click `Create Key` and enter a key name and container name. + * Key name is a friendly descriptive name for the SSH Key + * Container name is the nCrypt key container identifier which will be used. You can enter a memorable name such as `MY_KEY` or something random like a UUID. +* Select your Key Algorithm +* Enter a password or PIN + * This can be empty if you wish to be a bit less secure +* Click save +* You now have a new SSH key, you can click the `Copy Key` button to copy the `authorized_keys` content to the clipboard and save it to the remote server. Alternatively you can copy the public key's path for use as a command line arg, or opening with another program. + +You can use the key by configuring your SSH client to use nCryptAgent as its SSH agent. For OpenSSH for Windows and PuTTY this should work automatically, as long as those listeners are enabled in the `Config` tab. For WSL2 and Cygwin, you will need to set your `SSH_AUTH_SOCK` environment variable. The commands for doing this are available in the `Config` tab. + ## Getting Started with WebAuthN Security Keys * From the nCryptAgent main window, select the dropdown arrow in the bottom left and click on `Create new webauthn key` @@ -58,7 +73,7 @@ If you don't already have a certificate on your smart card, you'll need to creat Once you have a key added to nCryptAgent you can use it by configuring your SSH client to use nCryptAgent as its SSH agent. For OpenSSH for Windows and PuTTY this should work automatically, as long as those listeners are enabled in the `Config` tab. For WSL2 and Cygwin, you will need to set your `SSH_AUTH_SOCK` environment variable. The commands for doing this are available in the `Config` tab. -## Using existing keys +## Using existing Smart Card keys If you have already generated a key on your smart card (for instance you have existing credentials on your Yubikey) you can import that key by clicking on the dropdown next to `Create Key` and selecting `Add existing nCrypt key`. Select your smart card reader from the dropdown, then select your existing key and enter a name. Click save and your existing key will be ready for use. @@ -74,7 +89,7 @@ Users without a physical key can create a TPM-backed smart card: * Ensure your TPM is enabled in BIOS or UEFI. Different manufacturers name the setting differently. * Open a command prompt * Run `tpmvscmgr create /name /AdminKey DEFAULT /pin PROMPT /pinpolicy minlen 4 /generate` where `` is a name you choose -* You should now be able to add new keys in nCryptAgent +* You can use `certreq` and `certutil` to load a certificate onto the smart card, after which you can `Add existing nCrypt Key` to import your Smart Card credentials into nCryptAgent You can delete your TPM smart card with: * `tpmvscmgr.exe destroy /instance ` where `` is the id of the tpm smart card. If you only have one tpm smart card, this will be `ROOT\SMARTCARDREADER\0000` @@ -92,4 +107,4 @@ I'll get around to making a proper build script at some point... ## Known Issues * Sometimes the PIN prompt does not obtain focus correctly and will pop up in the background. -* Sometimes the 'Copy' buttons do not correctly copy to clipboard. \ No newline at end of file +* Sometimes the 'Copy' buttons do not correctly copy to clipboard. diff --git a/go.mod b/go.mod index 2af6ac8..20ea2d5 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/lxn/win v0.0.0-20210218163916-a377121e959e golang.org/x/crypto v0.1.0 golang.org/x/sys v0.1.0 + golang.org/x/text v0.4.0 ) require ( diff --git a/keyman/keymanager.go b/keyman/keymanager.go index 750533a..0a4190f 100644 --- a/keyman/keymanager.go +++ b/keyman/keymanager.go @@ -6,6 +6,7 @@ import ( "crypto" "crypto/elliptic" "crypto/rand" + "crypto/sha1" "encoding/asn1" "encoding/base64" "encoding/binary" @@ -27,6 +28,7 @@ import ( "strconv" "strings" "sync" + "syscall" "unsafe" ) @@ -711,7 +713,7 @@ func (km *KeyManager) getProviderHandle(providerName string) (uintptr, error) { return pHandle, nil } -func (km *KeyManager) CreateNewNCryptKey(keyName string, containerName string, providerName string, algorithm string, bits int) (*Key, error) { +func (km *KeyManager) CreateNewNCryptKey(keyName string, containerName string, providerName string, algorithm string, bits int, password string) (*Key, error) { if _, keyNameExists := km.Keys[keyName]; keyNameExists { return nil, fmt.Errorf("key named %s already exists", keyName) @@ -722,6 +724,10 @@ func (km *KeyManager) CreateNewNCryptKey(keyName string, containerName string, p containerName = containerUUID.String() } + if providerName == ncrypt.ProviderMSSC { + return nil, fmt.Errorf("creating keys on smartcards is not supported") + } + algorithmOK := false for _, i := range ncrypt.AVAILABLE_ALGORITHMS { if i == algorithm { @@ -752,6 +758,31 @@ func (km *KeyManager) CreateNewNCryptKey(keyName string, containerName string, p } } + if password != "" { + // If the provider is platform, set the property to a UI compatible one + utf16Str, err := syscall.UTF16FromString(password) + if err != nil { + return nil, err + } + bytesStr := make([]byte, len(utf16Str)*2) + j := 0 + for _, utf16 := range utf16Str { + b := make([]byte, 2) + // LPCSTR (Windows' representation of utf16) is always little endian. + binary.LittleEndian.PutUint16(b, utf16) + bytesStr[j] = b[0] + bytesStr[j+1] = b[1] + j += 2 + } + + digest := sha1.Sum(bytesStr[:len(bytesStr)-2]) + + err = ncrypt.NCryptSetProperty(kh, ncrypt.NCRYPT_PCP_USAGE_AUTH_PROPERTY, digest[:], 0) + if err != nil { + fmt.Printf("error setting password: %v\n", err) + } + } + err = ncrypt.NCryptFinalizeKey(kh, 0) if err != nil { ncrypt.NCryptFreeObject(kh) @@ -806,6 +837,11 @@ func (km *KeyManager) CreateNewNCryptKey(keyName string, containerName string, p err = km.SaveConfig() + // empty the password so we are prompted on first use + if password != "" { + ncrypt.NCryptSetProperty(kh, ncrypt.NCRYPT_PIN_PROPERTY, "", 0) + } + return &k, err } diff --git a/keyman/utils.go b/keyman/utils.go index d78b9a9..a36f3d6 100644 --- a/keyman/utils.go +++ b/keyman/utils.go @@ -129,6 +129,16 @@ func getPublicKey(kh uintptr) (crypto.PublicKey, error) { if err != nil { return nil, fmt.Errorf("failed to retrieve ECC curve name: %w", err) } + + if _, ok := ncrypt.CurveNames[curveName]; !ok { + fmt.Printf("Curve name not found, attempting to retrieve NCRYPT_ECC_CURVE_NAME_PROPERTY") + curveName, err = ncrypt.NCryptGetPropertyStr(kh, ncrypt.NCRYPT_ECC_CURVE_NAME_PROPERTY) + if err != nil { + return nil, fmt.Errorf("failed to retrieve ECC curve name: %w", err) + } + } + + fmt.Printf("CurveName is %s\n", curveName) pub, err = unmarshalECC(buf, ncrypt.CurveNames[curveName]) if err != nil { return nil, fmt.Errorf("failed to unmarshal ECC public key: %w", err) diff --git a/ncrypt/ncrypt.go b/ncrypt/ncrypt.go index 99f090a..e4ea6bf 100644 --- a/ncrypt/ncrypt.go +++ b/ncrypt/ncrypt.go @@ -27,7 +27,7 @@ const ( NCRYPT_READER_PROPERTY = "SmartCardReader" NCRYPT_ALGORITHM_PROPERTY = "Algorithm Name" NCRYPT_WINDOW_HANDLE_PROPERTY = "HWND Handle" - + NCRYPT_PCP_USAGE_AUTH_PROPERTY = "PCP_USAGEAUTH" // Key Storage Flags NCRYPT_MACHINE_KEY_FLAG = 0x00000001 NCRYPT_SILENT_FLAG = 0x40 @@ -106,6 +106,9 @@ var ( ALG_ECDSA_P256: elliptic.P256(), ALG_ECDSA_P384: elliptic.P384(), ALG_ECDSA_P521: elliptic.P521(), + "nistP256": elliptic.P256(), // BCRYPT_ECC_CURVE_NISTP256 + "nistP384": elliptic.P384(), // BCRYPT_ECC_CURVE_NISTP384 + "nistP521": elliptic.P521(), // BCRYPT_ECC_CURVE_NISTP521 } CurveMagicMap = map[string]uint32{ @@ -331,6 +334,21 @@ func NCryptSetProperty(keyHandle uintptr, propertyName string, propertyValue int return nil } + if byteVal, isBytes := propertyValue.([]byte); isBytes { + fmt.Printf("Setting bytes\n") + r, _, err := procNCryptSetProperty.Call( + keyHandle, + uintptr(unsafe.Pointer(wide(propertyName))), + uintptr(unsafe.Pointer(&byteVal[0])), + uintptr(len(byteVal)), + uintptr(flags)) + if r != 0 { + return fmt.Errorf("NCryptSetProperty \"%v\" returned %v: %v", propertyName, errNoToStr(uint32(r)), err) + } + + return nil + } + if strVal, isStr := propertyValue.(string); isStr { l := len(strVal) diff --git a/ui/createnew.go b/ui/createnew.go index e36ba32..d55c98c 100644 --- a/ui/createnew.go +++ b/ui/createnew.go @@ -15,6 +15,15 @@ var algorithmChoices = []string{ "ECDSA-P521", } +type NewKeyConfig struct { + Name string + Type string + ContainerName string + ProviderName string + Password string + Algorithm string +} + type CreateNewKey struct { *walk.Dialog nameEdit *walk.LineEdit @@ -25,10 +34,11 @@ type CreateNewKey struct { saveButton *walk.PushButton cancelButton *walk.PushButton - config keyman.KeyConfig + config NewKeyConfig + passwordEdit *walk.LineEdit } -func runCreateKeyDialog(owner walk.Form, km *keyman.KeyManager) *keyman.KeyConfig { +func runCreateKeyDialog(owner walk.Form, km *keyman.KeyManager) *NewKeyConfig { dlg, err := newCreateKeyDialog(owner, km) if showError(err, owner) { return nil @@ -79,6 +89,7 @@ func newCreateKeyDialog(owner walk.Form, km *keyman.KeyManager) (*CreateNewKey, } layout.SetRange(dlg.nameEdit, walk.Rectangle{1, 0, 1, 1}) dlg.nameEdit.SetText(dlg.config.Name) + dlg.nameEdit.SetAlignment(walk.AlignHFarVCenter) //Setup the containerName containerLabel, err := walk.NewTextLabel(dlg) @@ -94,37 +105,46 @@ func newCreateKeyDialog(owner walk.Form, km *keyman.KeyManager) (*CreateNewKey, } layout.SetRange(dlg.containerNameEdit, walk.Rectangle{1, 1, 1, 1}) err = dlg.containerNameEdit.SetText(dlg.config.ContainerName) + dlg.containerNameEdit.SetAlignment(walk.AlignHFarVCenter) + + //Setup the password field + passwordLabel, err := walk.NewTextLabel(dlg) + if err != nil { + return nil, err + } + layout.SetRange(passwordLabel, walk.Rectangle{0, 2, 1, 1}) + passwordLabel.SetTextAlignment(walk.AlignHFarVCenter) + passwordLabel.SetText(fmt.Sprintf("&Password/PIN:")) + + if dlg.passwordEdit, err = walk.NewLineEdit(dlg); err != nil { + return nil, err + } + layout.SetRange(dlg.passwordEdit, walk.Rectangle{1, 2, 1, 1}) + err = dlg.passwordEdit.SetText(dlg.config.Password) + dlg.passwordEdit.SetPasswordMode(true) + dlg.passwordEdit.SetAlignment(walk.AlignHFarVCenter) //Setup the algorithm list dropdown algorithmLabel, err := walk.NewTextLabel(dlg) if err != nil { return nil, err } - layout.SetRange(algorithmLabel, walk.Rectangle{0, 2, 1, 1}) + layout.SetRange(algorithmLabel, walk.Rectangle{0, 3, 1, 1}) algorithmLabel.SetTextAlignment(walk.AlignHFarVCenter) algorithmLabel.SetText(fmt.Sprintf("&Key Algorithm:")) if dlg.algorithmDropdown, err = walk.NewDropDownBox(dlg); err != nil { return nil, err } - //ddl := DropdownList{ - // values: map[string]string{ - // "RSA-2048": "RSA 2048", - // "RSA-4096": "RSA 4096", - // "ECDSA_P256": "ECDSA P256", - // "ECDSA_P384": "ECDSA P384", - // "ECDSA_P521": "ECDSAP521", - // }, - //} dlg.algorithmDropdown.SetModel(algorithmChoices) dlg.algorithmDropdown.SetCurrentIndex(0) - layout.SetRange(dlg.algorithmDropdown, walk.Rectangle{1, 2, 1, 1}) + layout.SetRange(dlg.algorithmDropdown, walk.Rectangle{1, 3, 1, 1}) buttonsContainer, err := walk.NewComposite(dlg) if err != nil { return nil, err } - layout.SetRange(buttonsContainer, walk.Rectangle{0, 3, 2, 1}) + layout.SetRange(buttonsContainer, walk.Rectangle{0, 4, 2, 1}) buttonsContainer.SetLayout(walk.NewHBoxLayout()) buttonsContainer.Layout().SetMargins(walk.Margins{}) @@ -153,37 +173,13 @@ func newCreateKeyDialog(owner walk.Form, km *keyman.KeyManager) (*CreateNewKey, func (dlg *CreateNewKey) onSaveButtonClicked() { - var algorithm string - var length int - switch dlg.algorithmDropdown.Text() { - case "RSA-2048": - algorithm = "RSA" - length = 2048 - case "RSA-4096": - algorithm = "RSA" - length = 4096 - case "ECDSA-P256": - algorithm = "ECDSA_P256" - length = 0 - case "ECDSA-P384": - algorithm = "ECDSA_P384" - length = 0 - case "ECDSA-P521": - algorithm = "ECDSA_P521" - length = 0 - default: - algorithm = "RSA" - length = 2048 - } - - dlg.config = keyman.KeyConfig{ + dlg.config = NewKeyConfig{ Name: dlg.nameEdit.Text(), Type: "NCRYPT", - Algorithm: algorithm, - Length: length, + Algorithm: dlg.algorithmDropdown.Text(), ContainerName: dlg.containerNameEdit.Text(), - ProviderName: ncrypt.ProviderMSSC, - SSHPublicKey: "", + Password: dlg.passwordEdit.Text(), + ProviderName: ncrypt.ProviderMSPlatform, } dlg.Accept() diff --git a/ui/keyspage.go b/ui/keyspage.go index d190703..1904fc5 100644 --- a/ui/keyspage.go +++ b/ui/keyspage.go @@ -133,12 +133,12 @@ func (kp *KeysPage) CreateToolbar() error { } kp.AddDisposable(addMenu) - //createAction := walk.NewAction() - //createAction.SetText(fmt.Sprintf("Create &nCrypt Key…")) - //createActionIcon, _ := loadSystemIcon("imageres", 77, 16) - //createAction.SetImage(createActionIcon) - //createAction.Triggered().Attach(kp.onCreateKey) - //addMenu.Actions().Add(createAction) + createAction := walk.NewAction() + createAction.SetText(fmt.Sprintf("Create &nCrypt Key…")) + createActionIcon, _ := loadSystemIcon("imageres", 77, 16) + createAction.SetImage(createActionIcon) + createAction.Triggered().Attach(kp.onCreateKey) + addMenu.Actions().Add(createAction) createWebAuthN := walk.NewAction() createWebAuthN.SetText(fmt.Sprintf("Create &WebAuthN Key…")) @@ -221,11 +221,37 @@ func (kp *KeysPage) updateKeyView() { func (kp *KeysPage) onCreateKey() { if config := runCreateKeyDialog(kp.Form(), kp.keyManager); config != nil { go func() { + + var algorithm string + var length int + + switch config.Algorithm { + case "RSA-2048": + algorithm = "RSA" + length = 2048 + case "RSA-4096": + algorithm = "RSA" + length = 4096 + case "ECDSA-P256": + algorithm = "ECDSA_P256" + length = 0 + case "ECDSA-P384": + algorithm = "ECDSA_P384" + length = 0 + case "ECDSA-P521": + algorithm = "ECDSA_P521" + length = 0 + default: + algorithm = "RSA" + length = 2048 + } + _, err := kp.keyManager.CreateNewNCryptKey(config.Name, config.ContainerName, config.ProviderName, - config.Algorithm, - config.Length, + algorithm, + length, + config.Password, ) if err != nil { From 78be18efc71758f1dd47fe08e4ab664b8164ea8d Mon Sep 17 00:00:00 2001 From: Raal Goff Date: Mon, 31 Oct 2022 12:42:27 +0800 Subject: [PATCH 2/2] readme fixes --- README.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 24618d0..130d01d 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Use your WebAuthN authenticator as your SSH key with `sk-ssh-ed25519@openssh.com * Download the latest release * Click `Create Key` and enter a key name and container name. * Key name is a friendly descriptive name for the SSH Key - * Container name is the nCrypt key container identifier which will be used. You can enter a memorable name such as `MY_KEY` or something random like a UUID. + * Container name is the nCrypt key container identifier which will be used - it will be shown in the password prompt when signing is requested. * Select your Key Algorithm * Enter a password or PIN * This can be empty if you wish to be a bit less secure @@ -68,14 +68,22 @@ If you don't already have a certificate on your smart card, you'll need to creat ### TPM Smart Cards -* Create a card using the instructions below -* Use `certreq` and `certutil` to generate a self-signed certificate and install it to the smart card +* Create a card if you dont have one: + * Ensure your TPM is enabled in BIOS or UEFI. Different manufacturers name the setting differently. + * Open a command prompt + * Run `tpmvscmgr create /name /AdminKey DEFAULT /pin PROMPT /pinpolicy minlen 4 /generate` where `` is a name you choose +* You can use `certreq` and `certutil` to load a certificate onto the smart card, after which you can `Add existing nCrypt Key` to import your Smart Card credentials into nCryptAgent -Once you have a key added to nCryptAgent you can use it by configuring your SSH client to use nCryptAgent as its SSH agent. For OpenSSH for Windows and PuTTY this should work automatically, as long as those listeners are enabled in the `Config` tab. For WSL2 and Cygwin, you will need to set your `SSH_AUTH_SOCK` environment variable. The commands for doing this are available in the `Config` tab. +### Add Smart Card Key to nCryptAgent + +If you have a key on your smart card (for instance you have existing credentials on your Yubikey) you can import that key by clicking on the dropdown next to `Create Key` and selecting `Add existing nCrypt key`. Select your smart card reader from the dropdown, then select your existing key and enter a name. Click save and your existing key will be ready for use. -## Using existing Smart Card keys +## Client Configuration + +Once you have a key added to nCryptAgent you can use it by configuring your SSH client to use nCryptAgent as its SSH agent. For OpenSSH for Windows and PuTTY this should work automatically, as long as those listeners are enabled in the `Config` tab. For WSL2 and Cygwin, you will need to set your `SSH_AUTH_SOCK` environment variable. The commands for doing this are available in the `Config` tab. -If you have already generated a key on your smart card (for instance you have existing credentials on your Yubikey) you can import that key by clicking on the dropdown next to `Create Key` and selecting `Add existing nCrypt key`. Select your smart card reader from the dropdown, then select your existing key and enter a name. Click save and your existing key will be ready for use. +* If you are using the Named Pipe listener, ensure the `OpenSSH Authentication Agent` service is stopped in `Services` +* If you are using the Pageant listener, ensure pageant is not running ## OpenSSH Certificates @@ -83,18 +91,6 @@ Since `ssh-add` does [not support adding certificates without a private key](htt For example, if an nCrypt key has a location of `%AppData%\nCryptAgent\PublicKeys\deadbeefd530ca2d01b3b74c8641fe29.pub` the matching certificate will be named `%AppData%\nCryptAgent\PublicKeys\deadbeefd530ca2d01b3b74c8641fe29-cert.pub`. -## Creating a TPM-backed Smart Card - -Users without a physical key can create a TPM-backed smart card: -* Ensure your TPM is enabled in BIOS or UEFI. Different manufacturers name the setting differently. -* Open a command prompt -* Run `tpmvscmgr create /name /AdminKey DEFAULT /pin PROMPT /pinpolicy minlen 4 /generate` where `` is a name you choose -* You can use `certreq` and `certutil` to load a certificate onto the smart card, after which you can `Add existing nCrypt Key` to import your Smart Card credentials into nCryptAgent - -You can delete your TPM smart card with: -* `tpmvscmgr.exe destroy /instance ` where `` is the id of the tpm smart card. If you only have one tpm smart card, this will be `ROOT\SMARTCARDREADER\0000` -* To get a list of `DeviceIDs` run `wmic path win32_PnPEntity where "DeviceID like '%smartcardreader%'" get DeviceID,Name,Status` - ## Building * To build you'll need `windres` which can be obtained by downloading the latest release of [llvm-mingw](https://github.com/mstorsjo/llvm-mingw)