Skip to content

Commit cb53a6a

Browse files
authored
Merge pull request #600 from smallstep/mariano/nulltags
Add support for using mackms keys with no tags
2 parents 94bdbcb + 6844893 commit cb53a6a

File tree

4 files changed

+100
-33
lines changed

4 files changed

+100
-33
lines changed

kms/mackms/mackms.go

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes
9595
// CreateKey methods can create keys with the following URIs:
9696
// - mackms:label=my-name
9797
// - mackms:label=my-name;tag=com.smallstep.crypto
98+
// - mackms;label=my-name;tag=
9899
// - mackms;label=my-name;se=true;bio=true
99100
//
100101
// GetPublicKey and CreateSigner accepts the above URIs as well as the following
@@ -107,7 +108,8 @@ var signatureAlgorithmMapping = map[apiv1.SignatureAlgorithm]algorithmAttributes
107108
// represents the key name. You will be able to see the keys in the Keychain,
108109
// looking for the value.
109110
// - "tag" corresponds with kSecAttrApplicationTag. It defaults to
110-
// com.smallstep.crypto.
111+
// com.smallstep.crypto. If tag is an empty string ("tag="), the attribute
112+
// will not be set.
111113
// - "se" is a boolean. If set to true, it will store the key in the
112114
// Secure Enclave. This option requires the application to be code-signed
113115
// with the appropriate entitlements.
@@ -189,21 +191,22 @@ func (k *MacKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons
189191
}
190192

191193
// Define key attributes
192-
cfTag, err := cf.NewData([]byte(u.tag))
193-
if err != nil {
194-
return nil, fmt.Errorf("mackms CreateKey failed: %w", err)
195-
}
196-
defer cfTag.Release()
197-
198194
cfLabel, err := cf.NewString(u.label)
199195
if err != nil {
200196
return nil, fmt.Errorf("mackms CreateKey failed: %w", err)
201197
}
202198
defer cfLabel.Release()
203199

204200
keyAttributesDict := cf.Dictionary{
205-
security.KSecAttrApplicationTag: cfTag,
206-
security.KSecAttrIsPermanent: cf.True,
201+
security.KSecAttrIsPermanent: cf.True,
202+
}
203+
if u.tag != "" {
204+
cfTag, err := cf.NewData([]byte(u.tag))
205+
if err != nil {
206+
return nil, fmt.Errorf("mackms CreateKey failed: %w", err)
207+
}
208+
defer cfTag.Release()
209+
keyAttributesDict[security.KSecAttrApplicationTag] = cfTag
207210
}
208211
if u.useSecureEnclave {
209212
// After the first unlock, the data remains accessible until the next
@@ -491,12 +494,6 @@ func (*MacKMS) DeleteKey(req *apiv1.DeleteKeyRequest) error {
491494
return fmt.Errorf("mackms DeleteKey failed: %w", err)
492495
}
493496

494-
cfTag, err := cf.NewData([]byte(u.tag))
495-
if err != nil {
496-
return fmt.Errorf("mackms DeleteKey failed: %w", err)
497-
}
498-
defer cfTag.Release()
499-
500497
cfLabel, err := cf.NewString(u.label)
501498
if err != nil {
502499
return fmt.Errorf("mackms DeleteKey failed: %w", err)
@@ -505,10 +502,17 @@ func (*MacKMS) DeleteKey(req *apiv1.DeleteKeyRequest) error {
505502

506503
for _, keyClass := range []cf.TypeRef{security.KSecAttrKeyClassPublic, security.KSecAttrKeyClassPrivate} {
507504
dict := cf.Dictionary{
508-
security.KSecClass: security.KSecClassKey,
509-
security.KSecAttrApplicationTag: cfTag,
510-
security.KSecAttrLabel: cfLabel,
511-
security.KSecAttrKeyClass: keyClass,
505+
security.KSecClass: security.KSecClassKey,
506+
security.KSecAttrLabel: cfLabel,
507+
security.KSecAttrKeyClass: keyClass,
508+
}
509+
if u.tag != "" {
510+
cfTag, err := cf.NewData([]byte(u.tag))
511+
if err != nil {
512+
return fmt.Errorf("mackms DeleteKey failed: %w", err)
513+
}
514+
defer cfTag.Release()
515+
dict[security.KSecAttrApplicationTag] = cfTag
512516
}
513517
// Extract logic to deleteItem to avoid defer on loops
514518
if err := deleteItem(dict, u.hash); err != nil {
@@ -672,25 +676,26 @@ func deleteItem(dict cf.Dictionary, hash []byte) error {
672676
}
673677

674678
func getPrivateKey(u *keyAttributes) (*security.SecKeyRef, error) {
675-
cfTag, err := cf.NewData([]byte(u.tag))
676-
if err != nil {
677-
return nil, err
678-
}
679-
defer cfTag.Release()
680-
681679
cfLabel, err := cf.NewString(u.label)
682680
if err != nil {
683681
return nil, err
684682
}
685683
defer cfLabel.Release()
686684

687685
dict := cf.Dictionary{
688-
security.KSecClass: security.KSecClassKey,
689-
security.KSecAttrApplicationTag: cfTag,
690-
security.KSecAttrLabel: cfLabel,
691-
security.KSecAttrKeyClass: security.KSecAttrKeyClassPrivate,
692-
security.KSecReturnRef: cf.True,
693-
security.KSecMatchLimit: security.KSecMatchLimitOne,
686+
security.KSecClass: security.KSecClassKey,
687+
security.KSecAttrLabel: cfLabel,
688+
security.KSecAttrKeyClass: security.KSecAttrKeyClassPrivate,
689+
security.KSecReturnRef: cf.True,
690+
security.KSecMatchLimit: security.KSecMatchLimitOne,
691+
}
692+
if u.tag != "" {
693+
cfTag, err := cf.NewData([]byte(u.tag))
694+
if err != nil {
695+
return nil, err
696+
}
697+
defer cfTag.Release()
698+
dict[security.KSecAttrApplicationTag] = cfTag
694699
}
695700
if len(u.hash) > 0 {
696701
d, err := cf.NewData(u.hash)
@@ -1013,7 +1018,7 @@ func parseURI(rawuri string) (*keyAttributes, error) {
10131018
return nil, fmt.Errorf("error parsing %q: label is required", rawuri)
10141019
}
10151020
tag := u.Get("tag")
1016-
if tag == "" {
1021+
if tag == "" && !u.Has("tag") {
10171022
tag = DefaultTag
10181023
}
10191024
return &keyAttributes{
@@ -1100,7 +1105,7 @@ func parseSearchURI(rawuri string) (*keySearchAttributes, error) {
11001105
// mackms:label=my-key;tag=my-tag;hash=010a...;se=true;bio=true
11011106
label := u.Get("label") // when searching, the label can be empty
11021107
tag := u.Get("tag")
1103-
if tag == "" {
1108+
if tag == "" && !u.Has("tag") {
11041109
tag = DefaultTag
11051110
}
11061111
return &keySearchAttributes{

kms/mackms/mackms_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ func TestMacKMS_GetPublicKey(t *testing.T) {
332332
assertion assert.ErrorAssertionFunc
333333
}{
334334
{"ok", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: r1.Name}}, r1.PublicKey, assert.NoError},
335+
{"ok no tag", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-p256;tag="}}, r1.PublicKey, assert.NoError},
335336
{"ok private only ECDSA ", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-ecdsa"}}, r2.PublicKey, assert.NoError},
336337
{"ok private only RSA ", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: r3.Name}}, r3.PublicKey, assert.NoError},
337338
{"ok no uri", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "test-p256"}}, r1.PublicKey, assert.NoError},
@@ -357,6 +358,9 @@ func TestMacKMS_CreateKey(t *testing.T) {
357358
assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{
358359
Name: "mackms:label=test-p256",
359360
}))
361+
assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{
362+
Name: "mackms:label=test-p256-2;tag=",
363+
}))
360364
})
361365

362366
type args struct {
@@ -384,6 +388,21 @@ func TestMacKMS_CreateKey(t *testing.T) {
384388
require.NotEmpty(tt, u.tag)
385389
require.NotEmpty(tt, u.hash)
386390
}, assert.NoError},
391+
{"ok no tag", &MacKMS{}, args{&apiv1.CreateKeyRequest{Name: "mackms:label=test-p256-2;tag="}},
392+
func(tt require.TestingT, i1 interface{}, i2 ...interface{}) {
393+
require.IsType(tt, &apiv1.CreateKeyResponse{}, i1)
394+
resp := i1.(*apiv1.CreateKeyResponse)
395+
require.NotEmpty(tt, resp.Name)
396+
require.NotNil(tt, resp.PublicKey)
397+
require.Nil(tt, resp.PrivateKey)
398+
require.NotEmpty(tt, resp.CreateSignerRequest)
399+
400+
u, err := parseURI(resp.Name)
401+
require.NoError(tt, err)
402+
require.NotEmpty(tt, u.label)
403+
require.Empty(tt, u.tag)
404+
require.NotEmpty(tt, u.hash)
405+
}, assert.NoError},
387406
{"fail name", &MacKMS{}, args{&apiv1.CreateKeyRequest{}}, require.Nil, assert.Error},
388407
{"fail uri", &MacKMS{}, args{&apiv1.CreateKeyRequest{Name: "mackms:name=test-p256"}}, require.Nil, assert.Error},
389408
{"fail signatureAlgorithm", &MacKMS{}, args{&apiv1.CreateKeyRequest{
@@ -524,6 +543,8 @@ func Test_parseURI(t *testing.T) {
524543
{"ok", args{"mackms:label=the-label;tag=the-tag;hash=0102abcd"}, &keyAttributes{label: "the-label", tag: "the-tag", hash: []byte{1, 2, 171, 205}}, assert.NoError},
525544
{"ok label", args{"the-label"}, &keyAttributes{label: "the-label", tag: DefaultTag}, assert.NoError},
526545
{"ok label uri", args{"mackms:label=the-label"}, &keyAttributes{label: "the-label", tag: DefaultTag}, assert.NoError},
546+
{"ok label empty tag", args{"mackms:label=the-label;tag="}, &keyAttributes{label: "the-label", tag: ""}, assert.NoError},
547+
{"ok label empty tag no equal", args{"mackms:label=the-label;tag"}, &keyAttributes{label: "the-label", tag: ""}, assert.NoError},
527548
{"fail parse", args{"mackms:::label=the-label"}, nil, assert.Error},
528549
{"fail missing label", args{"mackms:hash=0102abcd"}, nil, assert.Error},
529550
}

kms/uri/uri.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ func (u *URI) String() string {
105105
return u.URL.String()
106106
}
107107

108+
// Has checks whether a given key is set.
109+
func (u *URI) Has(key string) bool {
110+
return u.Values.Has(key) || u.URL.Query().Has(key)
111+
}
112+
108113
// Get returns the first value in the uri with the given key, it will return
109114
// empty string if that field is not present.
110115
func (u *URI) Get(key string) string {

kms/uri/uri_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,42 @@ func TestParseWithScheme(t *testing.T) {
153153
}
154154
}
155155

156+
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+
}
164+
type args struct {
165+
key string
166+
}
167+
tests := []struct {
168+
name string
169+
uri *URI
170+
args args
171+
want bool
172+
}{
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},
182+
}
183+
for _, tt := range tests {
184+
t.Run(tt.name, func(t *testing.T) {
185+
if got := tt.uri.Has(tt.args.key); got != tt.want {
186+
t.Errorf("URI.Has() = %v, want %v", got, tt.want)
187+
}
188+
})
189+
}
190+
}
191+
156192
func TestURI_Get(t *testing.T) {
157193
mustParse := func(s string) *URI {
158194
u, err := Parse(s)

0 commit comments

Comments
 (0)