Skip to content

Commit

Permalink
Make it possible to use a client certificate stored in hardware
Browse files Browse the repository at this point in the history
This has been tested with a Yubikey and the OpenSC PKCS#11 interface on Linux.
  • Loading branch information
jeffallen committed Mar 28, 2024
1 parent d16bdd7 commit 10fa914
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 4 deletions.
16 changes: 12 additions & 4 deletions cli/apiconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ type APIAuth struct {

// TLSConfig contains the TLS setup for the HTTP client
type TLSConfig struct {
InsecureSkipVerify bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure"`
Cert string `json:"cert,omitempty" yaml:"cert,omitempty"`
Key string `json:"key,omitempty" yaml:"key,omitempty"`
CACert string `json:"ca_cert,omitempty" yaml:"ca_cert,omitempty" mapstructure:"ca_cert"`
InsecureSkipVerify bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure"`
Cert string `json:"cert,omitempty" yaml:"cert,omitempty"`
Key string `json:"key,omitempty" yaml:"key,omitempty"`
CACert string `json:"ca_cert,omitempty" yaml:"ca_cert,omitempty" mapstructure:"ca_cert"`
PKCS11 *PKCS11Config `json:"pkcs11,omitempty" yaml:"pkcs11,omitempty"`
}

// PKCS11Config contains information about how to get a client certificate
// from a hardware device via PKCS#11.
type PKCS11Config struct {
Path string `json:"path,omitempty" yaml:"path,omitempty"`
Label string `json:"label" yaml:"label"`
}

// APIProfile contains account-specific API information
Expand Down
66 changes: 66 additions & 0 deletions cli/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
"strings"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/ThalesIgnite/crypto11"
"github.com/danielgtaylor/shorthand/v2"
"github.com/spf13/viper"
)
Expand Down Expand Up @@ -207,6 +210,11 @@ func MakeRequest(req *http.Request, options ...requestOption) (*http.Response, e
LogWarning("Disabling TLS security checks")
t.TLSClientConfig.InsecureSkipVerify = config.TLS.InsecureSkipVerify
}

if config.TLS.PKCS11 != nil {
t.TLSClientConfig.GetClientCertificate = getCertFromPkcs11(config.TLS.PKCS11)
}

if config.TLS.Cert != "" {
cert, err := tls.LoadX509KeyPair(config.TLS.Cert, config.TLS.Key)
if err != nil {
Expand Down Expand Up @@ -276,6 +284,64 @@ func MakeRequest(req *http.Request, options ...requestOption) (*http.Response, e
return resp, nil
}

func getCertFromPkcs11(config *PKCS11Config) func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
path := config.Path

// Try to give a useful default if they don't give a path to the plugin.
if path == "" {
if _, err := os.Stat("/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"); !errors.Is(err, os.ErrNotExist) {
path = "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"
}
if _, err := os.Stat("/usr/lib/pkcs11/opensc-pkcs11.so"); !errors.Is(err, os.ErrNotExist) {
path = "/usr/lib/pkcs11/opensc-pkcs11.so"
}
// macos
if _, err := os.Stat("/opt/homebrew/lib/opensc-pkcs11.so"); !errors.Is(err, os.ErrNotExist) {
path = "/opt/homebrew/lib/opensc-pkcs11.so"
}
}

pin := os.Getenv("YBPIN")
if pin == "" {
err := survey.AskOne(&survey.Password{Message: "PIN for your PKCS11 device:"}, &pin)
if err == terminal.InterruptErr {
os.Exit(0)
}
if err != nil {
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return nil, err }
}
}

cfg := &crypto11.Config{
Path: path,
TokenLabel: config.Label,
Pin: pin,
}
context, err := crypto11.Configure(cfg)
if err != nil {
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return nil, err }
}

certificates, err := context.FindAllPairedCertificates()
if err != nil {
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return nil, err }
}

if len(certificates) == 0 {
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return nil, errors.New("no certificate found in your pkcs11 device")
}
}

if len(certificates) > 1 {
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
return nil, errors.New("got more than one certificate")
}
}

return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return &certificates[0], nil }
}

// isRetryable returns true if a request should be retried.
func isRetryable(code int) bool {
if code == /* 408 */ http.StatusRequestTimeout ||
Expand Down
14 changes: 14 additions & 0 deletions docs/schemas/apis.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@
"ca_cert": {
"type": "string",
"description": "The local filename of a TLS certificate authority."
},
"pkcs11": {
"type": "object",
"description": "Settings related to getting a certificate from a hardware device via PKCS#11.",
"properties": {
"path": {
"type": "string",
"description": "Path to the PKCS#11 plugin shared object. (optional)"
},
"label": {
"type": "string",
"description": "The label of the certificate to fetch from the device. (required)"
}
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.18

require (
github.com/AlecAivazis/survey/v2 v2.3.6
github.com/ThalesIgnite/crypto11 v1.2.5
github.com/alecthomas/chroma v0.10.0
github.com/alexeyco/simpletable v1.0.0
github.com/amzn/ion-go v1.1.3
Expand Down Expand Up @@ -65,18 +66,21 @@ require (
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.13.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
github.com/twpayne/httpcache v1.0.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E=
github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/alexeyco/simpletable v1.0.0 h1:ZQ+LvJ4bmoeHb+dclF64d0LX+7QAi7awsfCrptZrpHk=
Expand Down Expand Up @@ -235,6 +237,8 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
Expand Down Expand Up @@ -272,6 +276,8 @@ github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvI
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down Expand Up @@ -320,6 +326,8 @@ github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 h1:/Bsw4C+DEdqPjt8vAqaC9LAqpAQnaCQQqmolqq3S1T4=
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8=
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
github.com/twpayne/httpcache v1.0.0 h1:Nm2b8ui5gBot+1sMrodBqUfKuphZ5GqW4tglz3sa2PU=
github.com/twpayne/httpcache v1.0.0/go.mod h1:76t45GFyg2v+ymifs7XHpomYXG6nRde+K6eTWnAqwhY=
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
Expand Down
Binary file added restish
Binary file not shown.

0 comments on commit 10fa914

Please sign in to comment.