Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit dc11c79

Browse files
authoredMay 29, 2024··
Merge pull request #246 from exoscale/pkcs11
Make it possible to use a client certificate stored in hardware
2 parents 3e66ad6 + 4c2b845 commit dc11c79

File tree

6 files changed

+105
-5
lines changed

6 files changed

+105
-5
lines changed
 

‎.github/workflows/ci.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- name: Set up go
1515
uses: actions/setup-go@v1
1616
with:
17-
go-version: "1.18"
17+
go-version: "1.22.2"
1818
- run: go test -coverprofile=coverage.txt -covermode=atomic ./...
1919
if: matrix.os == 'ubuntu-latest'
2020
- run: go test

‎cli/apiconfig.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,18 @@ type APIAuth struct {
2727

2828
// TLSConfig contains the TLS setup for the HTTP client
2929
type TLSConfig struct {
30-
InsecureSkipVerify bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure"`
31-
Cert string `json:"cert,omitempty" yaml:"cert,omitempty"`
32-
Key string `json:"key,omitempty" yaml:"key,omitempty"`
33-
CACert string `json:"ca_cert,omitempty" yaml:"ca_cert,omitempty" mapstructure:"ca_cert"`
30+
InsecureSkipVerify bool `json:"insecure,omitempty" yaml:"insecure,omitempty" mapstructure:"insecure"`
31+
Cert string `json:"cert,omitempty" yaml:"cert,omitempty"`
32+
Key string `json:"key,omitempty" yaml:"key,omitempty"`
33+
CACert string `json:"ca_cert,omitempty" yaml:"ca_cert,omitempty" mapstructure:"ca_cert"`
34+
PKCS11 *PKCS11Config `json:"pkcs11,omitempty" yaml:"pkcs11,omitempty"`
35+
}
36+
37+
// PKCS11Config contains information about how to get a client certificate
38+
// from a hardware device via PKCS#11.
39+
type PKCS11Config struct {
40+
Path string `json:"path,omitempty" yaml:"path,omitempty"`
41+
Label string `json:"label" yaml:"label"`
3442
}
3543

3644
// APIProfile contains account-specific API information

‎cli/request.go

+66
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"strings"
1616
"time"
1717

18+
"github.com/AlecAivazis/survey/v2"
19+
"github.com/AlecAivazis/survey/v2/terminal"
20+
"github.com/ThalesIgnite/crypto11"
1821
"github.com/danielgtaylor/shorthand/v2"
1922
"github.com/spf13/viper"
2023
)
@@ -207,6 +210,11 @@ func MakeRequest(req *http.Request, options ...requestOption) (*http.Response, e
207210
LogWarning("Disabling TLS security checks")
208211
t.TLSClientConfig.InsecureSkipVerify = config.TLS.InsecureSkipVerify
209212
}
213+
214+
if config.TLS.PKCS11 != nil {
215+
t.TLSClientConfig.GetClientCertificate = getCertFromPkcs11(config.TLS.PKCS11)
216+
}
217+
210218
if config.TLS.Cert != "" {
211219
cert, err := tls.LoadX509KeyPair(config.TLS.Cert, config.TLS.Key)
212220
if err != nil {
@@ -276,6 +284,64 @@ func MakeRequest(req *http.Request, options ...requestOption) (*http.Response, e
276284
return resp, nil
277285
}
278286

287+
func getCertFromPkcs11(config *PKCS11Config) func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
288+
path := config.Path
289+
290+
// Try to give a useful default if they don't give a path to the plugin.
291+
if path == "" {
292+
if _, err := os.Stat("/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"); !errors.Is(err, os.ErrNotExist) {
293+
path = "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"
294+
}
295+
if _, err := os.Stat("/usr/lib/pkcs11/opensc-pkcs11.so"); !errors.Is(err, os.ErrNotExist) {
296+
path = "/usr/lib/pkcs11/opensc-pkcs11.so"
297+
}
298+
// macos
299+
if _, err := os.Stat("/opt/homebrew/lib/opensc-pkcs11.so"); !errors.Is(err, os.ErrNotExist) {
300+
path = "/opt/homebrew/lib/opensc-pkcs11.so"
301+
}
302+
}
303+
304+
pin := os.Getenv("PKCS11_PIN")
305+
if pin == "" {
306+
err := survey.AskOne(&survey.Password{Message: "PIN for your PKCS11 device:"}, &pin)
307+
if err == terminal.InterruptErr {
308+
os.Exit(0)
309+
}
310+
if err != nil {
311+
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return nil, err }
312+
}
313+
}
314+
315+
cfg := &crypto11.Config{
316+
Path: path,
317+
TokenLabel: config.Label,
318+
Pin: pin,
319+
}
320+
context, err := crypto11.Configure(cfg)
321+
if err != nil {
322+
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return nil, err }
323+
}
324+
325+
certificates, err := context.FindAllPairedCertificates()
326+
if err != nil {
327+
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return nil, err }
328+
}
329+
330+
if len(certificates) == 0 {
331+
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
332+
return nil, errors.New("no certificate found in your pkcs11 device")
333+
}
334+
}
335+
336+
if len(certificates) > 1 {
337+
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
338+
return nil, errors.New("got more than one certificate")
339+
}
340+
}
341+
342+
return func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return &certificates[0], nil }
343+
}
344+
279345
// isRetryable returns true if a request should be retried.
280346
func isRetryable(code int) bool {
281347
if code == /* 408 */ http.StatusRequestTimeout ||

‎docs/schemas/apis.json

+14
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,20 @@
216216
"ca_cert": {
217217
"type": "string",
218218
"description": "The local filename of a TLS certificate authority."
219+
},
220+
"pkcs11": {
221+
"type": "object",
222+
"description": "Settings related to getting a certificate from a hardware device via PKCS#11.",
223+
"properties": {
224+
"path": {
225+
"type": "string",
226+
"description": "Path to the PKCS#11 plugin shared object. (optional)"
227+
},
228+
"label": {
229+
"type": "string",
230+
"description": "The label of the certificate to fetch from the device. (required)"
231+
}
232+
}
219233
}
220234
}
221235
}

‎go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.18
44

55
require (
66
github.com/AlecAivazis/survey/v2 v2.3.6
7+
github.com/ThalesIgnite/crypto11 v1.2.5
78
github.com/alecthomas/chroma v0.10.0
89
github.com/alexeyco/simpletable v1.0.0
910
github.com/amzn/ion-go v1.1.3
@@ -65,18 +66,21 @@ require (
6566
github.com/mattn/go-runewidth v0.0.14 // indirect
6667
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
6768
github.com/microcosm-cc/bluemonday v1.0.21 // indirect
69+
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f // indirect
6870
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
6971
github.com/muesli/reflow v0.3.0 // indirect
7072
github.com/muesli/termenv v0.13.0 // indirect
7173
github.com/olekukonko/tablewriter v0.0.5 // indirect
7274
github.com/pelletier/go-toml v1.9.5 // indirect
7375
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
7476
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
77+
github.com/pkg/errors v0.9.1 // indirect
7578
github.com/pmezard/go-difflib v1.0.0 // indirect
7679
github.com/rivo/uniseg v0.4.3 // indirect
7780
github.com/spf13/cast v1.5.0 // indirect
7881
github.com/spf13/jwalterweatherman v1.1.0 // indirect
7982
github.com/subosito/gotenv v1.4.1 // indirect
83+
github.com/thales-e-security/pool v0.0.2 // indirect
8084
github.com/twpayne/httpcache v1.0.0 // indirect
8185
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
8286
github.com/x448/float16 v0.8.4 // indirect

‎go.sum

+8
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
4242
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
4343
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
4444
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
45+
github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E=
46+
github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE=
4547
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
4648
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
4749
github.com/alexeyco/simpletable v1.0.0 h1:ZQ+LvJ4bmoeHb+dclF64d0LX+7QAi7awsfCrptZrpHk=
@@ -235,6 +237,8 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ
235237
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
236238
github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg=
237239
github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM=
240+
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f h1:eVB9ELsoq5ouItQBr5Tj334bhPJG/MX+m7rTchmzVUQ=
241+
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
238242
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
239243
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
240244
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -272,6 +276,8 @@ github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvI
272276
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
273277
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
274278
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
279+
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
280+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
275281
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
276282
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
277283
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -320,6 +326,8 @@ github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNG
320326
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
321327
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 h1:/Bsw4C+DEdqPjt8vAqaC9LAqpAQnaCQQqmolqq3S1T4=
322328
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8=
329+
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
330+
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
323331
github.com/twpayne/httpcache v1.0.0 h1:Nm2b8ui5gBot+1sMrodBqUfKuphZ5GqW4tglz3sa2PU=
324332
github.com/twpayne/httpcache v1.0.0/go.mod h1:76t45GFyg2v+ymifs7XHpomYXG6nRde+K6eTWnAqwhY=
325333
github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=

0 commit comments

Comments
 (0)
Please sign in to comment.