Skip to content

Commit 918323e

Browse files
authored
Merge pull request #676 from smallstep/mariano/fix-kms-207
support for yubikey management key from a file
2 parents f7b81be + 7cf227d commit 918323e

File tree

4 files changed

+143
-98
lines changed

4 files changed

+143
-98
lines changed

kms/uri/uri.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,25 @@ func (u *URI) Pin() string {
194194
return ""
195195
}
196196

197+
// Read returns the raw content of the file in the given attribute key. This
198+
// method will return nil if the key is missing.
199+
func (u *URI) Read(key string) ([]byte, error) {
200+
path := u.Get(key)
201+
if path == "" {
202+
return nil, nil
203+
}
204+
return readFile(path)
205+
}
206+
197207
func readFile(path string) ([]byte, error) {
198208
u, err := url.Parse(path)
199-
if err == nil && (u.Scheme == "" || u.Scheme == "file") && u.Path != "" {
200-
path = u.Path
209+
if err == nil && (u.Scheme == "" || u.Scheme == "file") {
210+
switch {
211+
case u.Path != "":
212+
path = u.Path
213+
case u.Opaque != "":
214+
path = u.Opaque
215+
}
201216
}
202217
b, err := os.ReadFile(path)
203218
if err != nil {

kms/uri/uri_test.go

Lines changed: 92 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@ package uri
22

33
import (
44
"net/url"
5+
"os"
6+
"path/filepath"
57
"reflect"
68
"testing"
79

810
"github.com/stretchr/testify/assert"
911
"github.com/stretchr/testify/require"
1012
)
1113

14+
func mustParse(t *testing.T, s string) *URI {
15+
t.Helper()
16+
u, err := Parse(s)
17+
require.NoError(t, err)
18+
return u
19+
}
20+
1221
func TestNew(t *testing.T) {
1322
type args struct {
1423
scheme string
@@ -154,13 +163,6 @@ func TestParseWithScheme(t *testing.T) {
154163
}
155164

156165
func TestURI_Has(t *testing.T) {
157-
mustParse := func(s string) *URI {
158-
u, err := Parse(s)
159-
if err != nil {
160-
t.Fatal(err)
161-
}
162-
return u
163-
}
164166
type args struct {
165167
key string
166168
}
@@ -170,15 +172,15 @@ func TestURI_Has(t *testing.T) {
170172
args args
171173
want bool
172174
}{
173-
{"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, true},
174-
{"ok empty", mustParse("yubikey:slot-id="), args{"slot-id"}, true},
175-
{"ok query", mustParse("yubikey:pin=123456?slot-id="), args{"slot-id"}, true},
176-
{"ok empty no equal", mustParse("yubikey:slot-id"), args{"slot-id"}, true},
177-
{"ok query no equal", mustParse("yubikey:pin=123456?slot-id"), args{"slot-id"}, true},
178-
{"ok empty no equal other", mustParse("yubikey:slot-id;pin=123456"), args{"slot-id"}, true},
179-
{"ok query no equal other", mustParse("yubikey:pin=123456?slot-id&pin=123456"), args{"slot-id"}, true},
180-
{"fail", mustParse("yubikey:pin=123456"), args{"slot-id"}, false},
181-
{"fail with query", mustParse("yubikey:pin=123456?slot=9a"), args{"slot-id"}, false},
175+
{"ok", mustParse(t, "yubikey:slot-id=9a"), args{"slot-id"}, true},
176+
{"ok empty", mustParse(t, "yubikey:slot-id="), args{"slot-id"}, true},
177+
{"ok query", mustParse(t, "yubikey:pin=123456?slot-id="), args{"slot-id"}, true},
178+
{"ok empty no equal", mustParse(t, "yubikey:slot-id"), args{"slot-id"}, true},
179+
{"ok query no equal", mustParse(t, "yubikey:pin=123456?slot-id"), args{"slot-id"}, true},
180+
{"ok empty no equal other", mustParse(t, "yubikey:slot-id;pin=123456"), args{"slot-id"}, true},
181+
{"ok query no equal other", mustParse(t, "yubikey:pin=123456?slot-id&pin=123456"), args{"slot-id"}, true},
182+
{"fail", mustParse(t, "yubikey:pin=123456"), args{"slot-id"}, false},
183+
{"fail with query", mustParse(t, "yubikey:pin=123456?slot=9a"), args{"slot-id"}, false},
182184
}
183185
for _, tt := range tests {
184186
t.Run(tt.name, func(t *testing.T) {
@@ -190,13 +192,6 @@ func TestURI_Has(t *testing.T) {
190192
}
191193

192194
func TestURI_Get(t *testing.T) {
193-
mustParse := func(s string) *URI {
194-
u, err := Parse(s)
195-
if err != nil {
196-
t.Fatal(err)
197-
}
198-
return u
199-
}
200195
type args struct {
201196
key string
202197
}
@@ -206,12 +201,12 @@ func TestURI_Get(t *testing.T) {
206201
args args
207202
want string
208203
}{
209-
{"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, "9a"},
210-
{"ok first", mustParse("yubikey:slot-id=9a;slot-id=9b"), args{"slot-id"}, "9a"},
211-
{"ok multiple", mustParse("yubikey:slot-id=9a;foo=bar"), args{"foo"}, "bar"},
212-
{"ok in query", mustParse("yubikey:slot-id=9a?foo=bar"), args{"foo"}, "bar"},
213-
{"fail missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, ""},
214-
{"fail missing query", mustParse("yubikey:slot-id=9a?bar=zar"), args{"foo"}, ""},
204+
{"ok", mustParse(t, "yubikey:slot-id=9a"), args{"slot-id"}, "9a"},
205+
{"ok first", mustParse(t, "yubikey:slot-id=9a;slot-id=9b"), args{"slot-id"}, "9a"},
206+
{"ok multiple", mustParse(t, "yubikey:slot-id=9a;foo=bar"), args{"foo"}, "bar"},
207+
{"ok in query", mustParse(t, "yubikey:slot-id=9a?foo=bar"), args{"foo"}, "bar"},
208+
{"fail missing", mustParse(t, "yubikey:slot-id=9a"), args{"foo"}, ""},
209+
{"fail missing query", mustParse(t, "yubikey:slot-id=9a?bar=zar"), args{"foo"}, ""},
215210
}
216211
for _, tt := range tests {
217212
t.Run(tt.name, func(t *testing.T) {
@@ -223,13 +218,6 @@ func TestURI_Get(t *testing.T) {
223218
}
224219

225220
func TestURI_GetBool(t *testing.T) {
226-
mustParse := func(s string) *URI {
227-
u, err := Parse(s)
228-
if err != nil {
229-
t.Fatal(err)
230-
}
231-
return u
232-
}
233221
type args struct {
234222
key string
235223
}
@@ -239,13 +227,13 @@ func TestURI_GetBool(t *testing.T) {
239227
args args
240228
want bool
241229
}{
242-
{"true", mustParse("azurekms:name=foo;vault=bar;hsm=true"), args{"hsm"}, true},
243-
{"TRUE", mustParse("azurekms:name=foo;vault=bar;hsm=TRUE"), args{"hsm"}, true},
244-
{"tRUe query", mustParse("azurekms:name=foo;vault=bar?hsm=tRUe"), args{"hsm"}, true},
245-
{"false", mustParse("azurekms:name=foo;vault=bar;hsm=false"), args{"hsm"}, false},
246-
{"false query", mustParse("azurekms:name=foo;vault=bar?hsm=false"), args{"hsm"}, false},
247-
{"empty", mustParse("azurekms:name=foo;vault=bar;hsm=?bar=true"), args{"hsm"}, false},
248-
{"missing", mustParse("azurekms:name=foo;vault=bar"), args{"hsm"}, false},
230+
{"true", mustParse(t, "azurekms:name=foo;vault=bar;hsm=true"), args{"hsm"}, true},
231+
{"TRUE", mustParse(t, "azurekms:name=foo;vault=bar;hsm=TRUE"), args{"hsm"}, true},
232+
{"tRUe query", mustParse(t, "azurekms:name=foo;vault=bar?hsm=tRUe"), args{"hsm"}, true},
233+
{"false", mustParse(t, "azurekms:name=foo;vault=bar;hsm=false"), args{"hsm"}, false},
234+
{"false query", mustParse(t, "azurekms:name=foo;vault=bar?hsm=false"), args{"hsm"}, false},
235+
{"empty", mustParse(t, "azurekms:name=foo;vault=bar;hsm=?bar=true"), args{"hsm"}, false},
236+
{"missing", mustParse(t, "azurekms:name=foo;vault=bar"), args{"hsm"}, false},
249237
}
250238
for _, tt := range tests {
251239
t.Run(tt.name, func(t *testing.T) {
@@ -257,13 +245,6 @@ func TestURI_GetBool(t *testing.T) {
257245
}
258246

259247
func TestURI_GetEncoded(t *testing.T) {
260-
mustParse := func(s string) *URI {
261-
u, err := Parse(s)
262-
if err != nil {
263-
t.Fatal(err)
264-
}
265-
return u
266-
}
267248
type args struct {
268249
key string
269250
}
@@ -273,15 +254,15 @@ func TestURI_GetEncoded(t *testing.T) {
273254
args args
274255
want []byte
275256
}{
276-
{"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, []byte{0x9a}},
277-
{"ok prefix", mustParse("yubikey:slot-id=0x9a"), args{"slot-id"}, []byte{0x9a}},
278-
{"ok first", mustParse("yubikey:slot-id=9a9b;slot-id=9b"), args{"slot-id"}, []byte{0x9a, 0x9b}},
279-
{"ok percent", mustParse("yubikey:slot-id=9a;foo=%9a%9b%9c"), args{"foo"}, []byte{0x9a, 0x9b, 0x9c}},
280-
{"ok in query", mustParse("yubikey:slot-id=9a?foo=9a"), args{"foo"}, []byte{0x9a}},
281-
{"ok in query percent", mustParse("yubikey:slot-id=9a?foo=%9a"), args{"foo"}, []byte{0x9a}},
282-
{"ok missing", mustParse("yubikey:slot-id=9a"), args{"foo"}, nil},
283-
{"ok missing query", mustParse("yubikey:slot-id=9a?bar=zar"), args{"foo"}, nil},
284-
{"ok no hex", mustParse("yubikey:slot-id=09a?bar=zar"), args{"slot-id"}, []byte{'0', '9', 'a'}},
257+
{"ok", mustParse(t, "yubikey:slot-id=9a"), args{"slot-id"}, []byte{0x9a}},
258+
{"ok prefix", mustParse(t, "yubikey:slot-id=0x9a"), args{"slot-id"}, []byte{0x9a}},
259+
{"ok first", mustParse(t, "yubikey:slot-id=9a9b;slot-id=9b"), args{"slot-id"}, []byte{0x9a, 0x9b}},
260+
{"ok percent", mustParse(t, "yubikey:slot-id=9a;foo=%9a%9b%9c"), args{"foo"}, []byte{0x9a, 0x9b, 0x9c}},
261+
{"ok in query", mustParse(t, "yubikey:slot-id=9a?foo=9a"), args{"foo"}, []byte{0x9a}},
262+
{"ok in query percent", mustParse(t, "yubikey:slot-id=9a?foo=%9a"), args{"foo"}, []byte{0x9a}},
263+
{"ok missing", mustParse(t, "yubikey:slot-id=9a"), args{"foo"}, nil},
264+
{"ok missing query", mustParse(t, "yubikey:slot-id=9a?bar=zar"), args{"foo"}, nil},
265+
{"ok no hex", mustParse(t, "yubikey:slot-id=09a?bar=zar"), args{"slot-id"}, []byte{'0', '9', 'a'}},
285266
}
286267
for _, tt := range tests {
287268
t.Run(tt.name, func(t *testing.T) {
@@ -294,22 +275,15 @@ func TestURI_GetEncoded(t *testing.T) {
294275
}
295276

296277
func TestURI_Pin(t *testing.T) {
297-
mustParse := func(s string) *URI {
298-
u, err := Parse(s)
299-
if err != nil {
300-
t.Fatal(err)
301-
}
302-
return u
303-
}
304278
tests := []struct {
305279
name string
306280
uri *URI
307281
want string
308282
}{
309-
{"from value", mustParse("pkcs11:id=%72%73?pin-value=0123456789"), "0123456789"},
310-
{"from source", mustParse("pkcs11:id=%72%73?pin-source=testdata/pin.txt"), "trim-this-pin"},
311-
{"from missing", mustParse("pkcs11:id=%72%73"), ""},
312-
{"from source missing", mustParse("pkcs11:id=%72%73?pin-source=testdata/foo.txt"), ""},
283+
{"from value", mustParse(t, "pkcs11:id=%72%73?pin-value=0123456789"), "0123456789"},
284+
{"from source", mustParse(t, "pkcs11:id=%72%73?pin-source=testdata/pin.txt"), "trim-this-pin"},
285+
{"from missing", mustParse(t, "pkcs11:id=%72%73"), ""},
286+
{"from source missing", mustParse(t, "pkcs11:id=%72%73?pin-source=testdata/foo.txt"), ""},
313287
}
314288
for _, tt := range tests {
315289
t.Run(tt.name, func(t *testing.T) {
@@ -321,13 +295,6 @@ func TestURI_Pin(t *testing.T) {
321295
}
322296

323297
func TestURI_String(t *testing.T) {
324-
mustParse := func(s string) *URI {
325-
u, err := Parse(s)
326-
if err != nil {
327-
t.Fatal(err)
328-
}
329-
return u
330-
}
331298
tests := []struct {
332299
name string
333300
uri *URI
@@ -336,7 +303,7 @@ func TestURI_String(t *testing.T) {
336303
{"ok new", New("yubikey", url.Values{"slot-id": []string{"9a"}, "foo": []string{"bar"}}), "yubikey:foo=bar;slot-id=9a"},
337304
{"ok newOpaque", NewOpaque("cloudkms", "projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"), "cloudkms:projects/p/locations/l/keyRings/k/cryptoKeys/c/cryptoKeyVersions/1"},
338305
{"ok newFile", NewFile("/path/to/file.key"), "file:///path/to/file.key"},
339-
{"ok parse", mustParse("yubikey:slot-id=9a;foo=bar?bar=zar"), "yubikey:foo=bar;slot-id=9a?bar=zar"},
306+
{"ok parse", mustParse(t, "yubikey:slot-id=9a;foo=bar?bar=zar"), "yubikey:foo=bar;slot-id=9a?bar=zar"},
340307
}
341308
for _, tt := range tests {
342309
t.Run(tt.name, func(t *testing.T) {
@@ -349,13 +316,6 @@ func TestURI_String(t *testing.T) {
349316

350317
func TestURI_GetInt(t *testing.T) {
351318
seventy := int64(70)
352-
mustParse := func(s string) *URI {
353-
u, err := Parse(s)
354-
if err != nil {
355-
t.Fatal(err)
356-
}
357-
return u
358-
}
359319
type args struct {
360320
key string
361321
}
@@ -365,9 +325,9 @@ func TestURI_GetInt(t *testing.T) {
365325
args args
366326
want *int64
367327
}{
368-
{"ok", mustParse("tpmkms:renewal-percentage=70"), args{"renewal-percentage"}, &seventy},
369-
{"ok empty", mustParse("tpmkms:empty"), args{"renewal-percentage"}, nil},
370-
{"ok non-integer", mustParse("tpmkms:renewal-percentage=not-an-integer"), args{"renewal-percentage"}, nil},
328+
{"ok", mustParse(t, "tpmkms:renewal-percentage=70"), args{"renewal-percentage"}, &seventy},
329+
{"ok empty", mustParse(t, "tpmkms:empty"), args{"renewal-percentage"}, nil},
330+
{"ok non-integer", mustParse(t, "tpmkms:renewal-percentage=not-an-integer"), args{"renewal-percentage"}, nil},
371331
}
372332
for _, tt := range tests {
373333
t.Run(tt.name, func(t *testing.T) {
@@ -382,12 +342,6 @@ func TestURI_GetInt(t *testing.T) {
382342
}
383343

384344
func TestURI_GetHexEncoded(t *testing.T) {
385-
mustParse := func(t *testing.T, s string) *URI {
386-
t.Helper()
387-
u, err := Parse(s)
388-
require.NoError(t, err)
389-
return u
390-
}
391345
type args struct {
392346
key string
393347
}
@@ -418,3 +372,47 @@ func TestURI_GetHexEncoded(t *testing.T) {
418372
})
419373
}
420374
}
375+
376+
func TestURI_Read(t *testing.T) {
377+
// Read does not trim the contents of the file
378+
expected := []byte("trim-this-pin \n")
379+
380+
path := filepath.Join(t.TempDir(), "management.key")
381+
require.NoError(t, os.WriteFile(path, expected, 0600))
382+
managementKeyURI := &url.URL{
383+
Scheme: "file",
384+
Path: path,
385+
}
386+
pathURI := &URI{
387+
URL: &url.URL{Scheme: "yubikey"},
388+
Values: url.Values{
389+
"management-key-source": []string{managementKeyURI.String()},
390+
},
391+
}
392+
393+
type args struct {
394+
key string
395+
}
396+
tests := []struct {
397+
name string
398+
uri *URI
399+
args args
400+
want []byte
401+
assertion assert.ErrorAssertionFunc
402+
}{
403+
{"from attribute", mustParse(t, "yubikey:management-key-source=testdata/pin.txt"), args{"management-key-source"}, expected, assert.NoError},
404+
{"from query attribute", mustParse(t, "yubikey:?management-key-source=testdata/pin.txt"), args{"management-key-source"}, expected, assert.NoError},
405+
{"from uri path", pathURI, args{"management-key-source"}, expected, assert.NoError},
406+
{"from uri opaque", mustParse(t, "yubikey:management-key-source=file:testdata/pin.txt"), args{"management-key-source"}, expected, assert.NoError},
407+
{"from empty attribute", mustParse(t, "yubikey:management-source-key="), args{"management-key-source"}, nil, assert.NoError},
408+
{"from missing attribute", mustParse(t, "yubikey:slot-id=82"), args{"management-key-source"}, nil, assert.NoError},
409+
{"from missing file", mustParse(t, "yubikey:management-key-source=testdata/foo.txt"), args{"management-key-source"}, nil, assert.Error},
410+
}
411+
for _, tt := range tests {
412+
t.Run(tt.name, func(t *testing.T) {
413+
got, err := tt.uri.Read(tt.args.key)
414+
tt.assertion(t, err)
415+
assert.Equal(t, tt.want, got)
416+
})
417+
}
418+
}

kms/yubikey/yubikey.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package yubikey
55

66
import (
7+
"bytes"
78
"context"
89
"crypto"
910
"crypto/x509"
@@ -15,6 +16,7 @@ import (
1516
"strconv"
1617
"strings"
1718
"sync"
19+
"unicode"
1820

1921
"github.com/go-piv/piv-go/v2/piv"
2022
"github.com/pkg/errors"
@@ -85,14 +87,15 @@ func openCard(card string) (pivKey, error) {
8587
// support multiple cards at the same time.
8688
//
8789
// yubikey:management-key=001122334455667788990011223344556677889900112233?pin-value=123456
90+
// yubikey:management-key-source=/var/run/management.key?pin-source=/var/run/yubikey.pin
8891
// yubikey:serial=112233?pin-source=/var/run/yubikey.pin
8992
//
9093
// You can also define a slot id, this will be ignored in this method but can be
9194
// useful on CLI applications.
9295
//
9396
// yubikey:slot-id=9a?pin-value=123456
9497
//
95-
// If the pin or the management-key are not provided, we will use the default
98+
// If the pin or the management key are not provided, we will use the default
9699
// ones.
97100
func New(_ context.Context, opts apiv1.Options) (*YubiKey, error) {
98101
pin := "123456"
@@ -109,6 +112,14 @@ func New(_ context.Context, opts apiv1.Options) (*YubiKey, error) {
109112
}
110113
if v := u.Get("management-key"); v != "" {
111114
opts.ManagementKey = v
115+
} else if u.Has("management-key-source") {
116+
b, err := u.Read("management-key-source")
117+
if err != nil {
118+
return nil, err
119+
}
120+
if b = bytes.TrimFunc(b, unicode.IsSpace); len(b) > 0 {
121+
opts.ManagementKey = string(b)
122+
}
112123
}
113124
if v := u.Get("serial"); v != "" {
114125
serial = v
@@ -119,7 +130,7 @@ func New(_ context.Context, opts apiv1.Options) (*YubiKey, error) {
119130
if opts.ManagementKey != "" {
120131
b, err := hex.DecodeString(opts.ManagementKey)
121132
if err != nil {
122-
return nil, errors.Wrap(err, "error decoding managementKey")
133+
return nil, errors.Wrap(err, "error decoding management key")
123134
}
124135
if len(b) != 24 {
125136
return nil, errors.New("invalid managementKey: length is not 24 bytes")

0 commit comments

Comments
 (0)