Skip to content

Commit

Permalink
release 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
buptczq committed May 18, 2019
1 parent 73091e7 commit 9b2bcfe
Show file tree
Hide file tree
Showing 26 changed files with 1,770 additions and 12 deletions.
13 changes: 1 addition & 12 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -175,18 +175,7 @@

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright [yyyy] [name of copyright owner]
Copyright 2019 BUPTCZQ.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# WinCrypt SSH Agent

## Introduction

A SSH Agent based-on Windows CryptoAPI.

This project allows other programs to access SSH keys stored in your Windows Certificate Store for authentication.

Benefit by Windows Certificate Management, this project natively supports the use of windows user certificates or smart cards, e.g., Yubikey PIV, for authentication.

## Feature

* Work with smart cards natively without installing any driver in Windows (PIV only)
* Support for OpenSSH certificates (so you can use your smart card with an additional OpenSSH certificate)
* Good compatibility

## Compatibility

There are many different OpenSSH agent implementations in Windows. This project implements 4 popular protocols in Windows:

* Cygwin UNIX Socket
* Windows UNIX Socket (Windows 10 1803 or later)
* Named pipe
* Pageant SSH Agent Protocol

With the support of these protocols, this project is compatible with most SSH clients in Windows. For example:

* Git for Windows
* Windows Subsystem for Linux
* Window OpenSSH
* Putty
* Jetbrains
* SecureCRT
* XShell
* Cygwin
* MINGW
* ...

## Installing

Stable versions can be obtained from the release page.

Additionally, you may make an shortcut of this application to the startup folder.

## Usage

### Basic Usage

1. Start WinCrypt SSH Agent
2. Right-click the icon on your taskbar
3. You can get necessary information by selecting your interesting item in the menu

Note: Some SSH clients using Pageant Protocol, e.g., Putty, XShell and Jetbrains, needn't any setting in system wide, thus you can't see Pageant in the menu.

### OpenSSH Certificates

OpenSSH supports authentication using SSH certificates. Certificates contain a public key, identity information and are signed with a standard SSH key.

Unlike TLS using X.509, OpenSSH is using a special certificate format, thus we can't convert your X.509 certificate into OpenSSH format.

To deal with OpenSSH Certificates, this project introduces a public key override mechanism.

If you want to work with OpenSSH certificates, you should put your OpenSSH Certificates in the same folder of this application, rename them to `<Your Certificate Common Name>-cert.pub` or `<Your Certificate Serial Number>-cert.pub`.
Binary file added assets/icon.ico
Binary file not shown.
2 changes: 2 additions & 0 deletions build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
go generate
go build -ldflags -H=windowsgui
191 changes: 191 additions & 0 deletions capi/wincapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package capi

import (
"crypto/x509"
"fmt"
"github.com/fullsailor/pkcs7"
"syscall"
"unsafe"
)

const (
ALG_RSA_SHA1RSA = "1.2.840.113549.1.1.5"
ALG_RSA_SHA256RSA = "1.2.840.113549.1.1.11"
ALG_RSA_SHA384RSA = "1.2.840.113549.1.1.12"
ALG_RSA_SHA512RSA = "1.2.840.113549.1.1.13"
ALG_ECDSA_SHA1 = "1.2.840.10045.4.1"
ALG_ECDSA_SPECIFIED = "1.2.840.10045.4.3"
ALG_ECDSA_SHA256 = "1.2.840.10045.4.3.2"
ALG_ECDSA_SHA384 = "1.2.840.10045.4.3.3"
ALG_ECDSA_SHA512 = "1.2.840.10045.4.3.4"
)

var (
modcrypt32 = syscall.NewLazyDLL("crypt32.dll")
procCryptSignMessage = modcrypt32.NewProc("CryptSignMessage")
procCertDuplicateCertificateContext = modcrypt32.NewProc("CertDuplicateCertificateContext")
)

type cryptoapiBlob struct {
DataSize uint32
Data uintptr
}

type cryptAlgorithmIdentifier struct {
ObjId uintptr
Parameters cryptoapiBlob
}

type cryptSignMessagePara struct {
CbSize uint32
MsgEncodingType uint32
SigningCert uintptr
HashAlgorithm cryptAlgorithmIdentifier
HashAuxInfo uintptr
MsgCertSize uint32
MsgCert uintptr
MsgCrlSize uint32
MsgCrl uintptr
AuthAttrSize uint32
AuthAttr uintptr
UnauthAttrSize uint32
UnauthAttr uintptr
Flags uint32
InnerContentType uint32
HashEncryptionAlgorithm cryptAlgorithmIdentifier
HashEncryptionAuxInfo uintptr
}

func cryptSignMessage(para *cryptSignMessagePara, data []byte) (sign []byte, err error) {
dataPtr := uintptr(unsafe.Pointer(&data[0]))
dataSize := uint32(len(data))
dataSizePtr := uintptr(unsafe.Pointer(&dataSize))
result := make([]byte, 0x2000)
size := uint32(0x2000)
sizePtr := uintptr(unsafe.Pointer(&size))
resultPtr := uintptr(unsafe.Pointer(&result[0]))
r0, _, e1 := syscall.Syscall9(
procCryptSignMessage.Addr(),
7,
uintptr(unsafe.Pointer(para)),
1,
1,
uintptr(unsafe.Pointer(&dataPtr)),
dataSizePtr,
resultPtr,
sizePtr,
0,
0,
)
if e1 != syscall.Errno(0) {
return nil, e1
}
if r0 == 0 {
return nil, fmt.Errorf("failed to sign")
}
return result[:size], nil
}

func certDuplicateCertificateContext(context *syscall.CertContext) (uintptr, error) {
r0, _, e1 := syscall.Syscall(procCertDuplicateCertificateContext.Addr(), 1, uintptr(unsafe.Pointer(context)), 0, 0)
if e1 != syscall.Errno(0) {
return 0, e1
}
return r0, nil
}

type Certificate struct {
certContext uintptr
*x509.Certificate
}

func (s *Certificate) Free() error {
return syscall.CertFreeCertificateContext((*syscall.CertContext)(unsafe.Pointer(s.certContext)))
}

func (s *Certificate) Copy() (*Certificate, error) {
context := (*syscall.CertContext)(unsafe.Pointer(s.certContext))
certContext, err := certDuplicateCertificateContext(context)
if err != nil {
return nil, err
}
return &Certificate{
certContext: certContext,
Certificate: s.Certificate,
}, nil
}

func LoadUserCerts() ([]*Certificate, error) {
const (
CERT_STORE_PROV_SYSTEM_A = 9
CERT_SYSTEM_STORE_CURRENT_USER = 0x00010000
CERT_STORE_READONLY_FLAG = 0x00008000
CRYPT_E_NOT_FOUND = 0x80092004
)
ptr, _ := syscall.BytePtrFromString("My")
store, err := syscall.CertOpenStore(
CERT_STORE_PROV_SYSTEM_A,
0,
0,
CERT_SYSTEM_STORE_CURRENT_USER|CERT_STORE_READONLY_FLAG,
uintptr(unsafe.Pointer(ptr)),
)
if err != nil {
return nil, err
}
defer syscall.CertCloseStore(store, 0)

certs := make([]*Certificate, 0)
var cert *syscall.CertContext
for {
cert, err = syscall.CertEnumCertificatesInStore(store, cert)
if err != nil {
if errno, ok := err.(syscall.Errno); ok {
if errno == CRYPT_E_NOT_FOUND {
break
}
}
return nil, err
}
if cert == nil {
break
}
// Copy the buf, since ParseCertificate does not create its own copy.
buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
buf2 := make([]byte, cert.Length)
copy(buf2, buf)
if c, err := x509.ParseCertificate(buf2); err == nil {
cc, err := certDuplicateCertificateContext(cert)
if err != nil {
continue
}
certs = append(certs, &Certificate{
cc,
c,
})
}
}
return certs, nil
}

func Sign(alg string, cert *Certificate, data []byte) (*pkcs7.PKCS7, error) {
const (
X509_ASN_ENCODING = 0x1
PKCS_7_ASN_ENCODING = 0x10000
)
algptr, err := syscall.BytePtrFromString(alg)
if err != nil {
return nil, err
}
sign, err := cryptSignMessage(&cryptSignMessagePara{
CbSize: uint32(unsafe.Sizeof(cryptSignMessagePara{})),
MsgEncodingType: X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
SigningCert: cert.certContext,
HashAlgorithm: cryptAlgorithmIdentifier{ObjId: uintptr(unsafe.Pointer(algptr))},
HashEncryptionAlgorithm: cryptAlgorithmIdentifier{ObjId: uintptr(unsafe.Pointer(algptr))},
}, data)
if err != nil {
return nil, err
}
return pkcs7.Parse(sign)
}
50 changes: 50 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package common

import (
"sync"
)

const (
WSL_SOCK = "wincrypt-wsl.sock"
CYGWIN_SOCK = "wincrypt-cygwin.sock"
NAMED_PIPE = "\\\\.\\pipe\\openssh-ssh-agent"
APP_CYGWIN = iota
APP_WSL
APP_WINSSH
APP_SECURECRT
MENU_QUIT
)

type AppId int

var appIdToName = map[AppId]string{
APP_CYGWIN: "Cygwin",
APP_WSL: "WSL",
APP_WINSSH: "WinSSH",
APP_SECURECRT: "SecureCRT",
}

var appIdToFullName = map[AppId]string{
APP_CYGWIN: "Cygwin (MinGW64 & MSYS2)",
APP_WSL: "Windows Subsystem for Linux",
APP_WINSSH: "Windows OpenSSH",
APP_SECURECRT: "SecureCRT",
}

func (id AppId) String() string {
return appIdToName[id]
}

func (id AppId) FullName() string {
return appIdToFullName[id]
}

type ServiceStatus struct {
Running bool
Help string
}

type Services struct {
Service map[AppId]*ServiceStatus
sync.RWMutex
}
Loading

0 comments on commit 9b2bcfe

Please sign in to comment.