Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to use a client certificate stored in hardware #246

Merged
merged 3 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- name: Set up go
uses: actions/setup-go@v1
with:
go-version: "1.18"
go-version: "1.22.2"
- run: go test -coverprofile=coverage.txt -covermode=atomic ./...
if: matrix.os == 'ubuntu-latest'
- run: go test
Expand Down
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("PKCS11_PIN")
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