Skip to content

Commit

Permalink
Add support for using mackms keys with no tags
Browse files Browse the repository at this point in the history
This commit allows to create, get, sign, and other operations using keys
without a tag. By default, the default tag used by the mackms package is
com.smallstep.crypto, this tag can be changed using the tag parameter in
the uri, e.g., mackms:label=test;tag=my-tag. But if we want to not use
a tag we need to provide the tag parameter empty, e,g.,
mackms:label=test;tag=

Fixes #595
  • Loading branch information
maraino committed Sep 25, 2024
1 parent 94bdbcb commit 1e71f99
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 32 deletions.
67 changes: 35 additions & 32 deletions kms/mackms/mackms.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,21 +189,22 @@ func (k *MacKMS) CreateKey(req *apiv1.CreateKeyRequest) (*apiv1.CreateKeyRespons
}

// Define key attributes
cfTag, err := cf.NewData([]byte(u.tag))
if err != nil {
return nil, fmt.Errorf("mackms CreateKey failed: %w", err)
}
defer cfTag.Release()

cfLabel, err := cf.NewString(u.label)
if err != nil {
return nil, fmt.Errorf("mackms CreateKey failed: %w", err)
}
defer cfLabel.Release()

keyAttributesDict := cf.Dictionary{
security.KSecAttrApplicationTag: cfTag,
security.KSecAttrIsPermanent: cf.True,
security.KSecAttrIsPermanent: cf.True,
}
if u.tag != "" {
cfTag, err := cf.NewData([]byte(u.tag))
if err != nil {
return nil, fmt.Errorf("mackms CreateKey failed: %w", err)
}
defer cfTag.Release()
keyAttributesDict[security.KSecAttrApplicationTag] = cfTag
}
if u.useSecureEnclave {
// After the first unlock, the data remains accessible until the next
Expand Down Expand Up @@ -491,12 +492,6 @@ func (*MacKMS) DeleteKey(req *apiv1.DeleteKeyRequest) error {
return fmt.Errorf("mackms DeleteKey failed: %w", err)
}

cfTag, err := cf.NewData([]byte(u.tag))
if err != nil {
return fmt.Errorf("mackms DeleteKey failed: %w", err)
}
defer cfTag.Release()

cfLabel, err := cf.NewString(u.label)
if err != nil {
return fmt.Errorf("mackms DeleteKey failed: %w", err)
Expand All @@ -505,10 +500,17 @@ func (*MacKMS) DeleteKey(req *apiv1.DeleteKeyRequest) error {

for _, keyClass := range []cf.TypeRef{security.KSecAttrKeyClassPublic, security.KSecAttrKeyClassPrivate} {
dict := cf.Dictionary{
security.KSecClass: security.KSecClassKey,
security.KSecAttrApplicationTag: cfTag,
security.KSecAttrLabel: cfLabel,
security.KSecAttrKeyClass: keyClass,
security.KSecClass: security.KSecClassKey,
security.KSecAttrLabel: cfLabel,
security.KSecAttrKeyClass: keyClass,
}
if u.tag != "" {
cfTag, err := cf.NewData([]byte(u.tag))
if err != nil {
return fmt.Errorf("mackms DeleteKey failed: %w", err)
}
defer cfTag.Release()
dict[security.KSecAttrApplicationTag] = cfTag
}
// Extract logic to deleteItem to avoid defer on loops
if err := deleteItem(dict, u.hash); err != nil {
Expand Down Expand Up @@ -672,25 +674,26 @@ func deleteItem(dict cf.Dictionary, hash []byte) error {
}

func getPrivateKey(u *keyAttributes) (*security.SecKeyRef, error) {
cfTag, err := cf.NewData([]byte(u.tag))
if err != nil {
return nil, err
}
defer cfTag.Release()

cfLabel, err := cf.NewString(u.label)
if err != nil {
return nil, err
}
defer cfLabel.Release()

dict := cf.Dictionary{
security.KSecClass: security.KSecClassKey,
security.KSecAttrApplicationTag: cfTag,
security.KSecAttrLabel: cfLabel,
security.KSecAttrKeyClass: security.KSecAttrKeyClassPrivate,
security.KSecReturnRef: cf.True,
security.KSecMatchLimit: security.KSecMatchLimitOne,
security.KSecClass: security.KSecClassKey,
security.KSecAttrLabel: cfLabel,
security.KSecAttrKeyClass: security.KSecAttrKeyClassPrivate,
security.KSecReturnRef: cf.True,
security.KSecMatchLimit: security.KSecMatchLimitOne,
}
if u.tag != "" {
cfTag, err := cf.NewData([]byte(u.tag))
if err != nil {
return nil, err
}
defer cfTag.Release()
dict[security.KSecAttrApplicationTag] = cfTag
}
if len(u.hash) > 0 {
d, err := cf.NewData(u.hash)
Expand Down Expand Up @@ -1013,7 +1016,7 @@ func parseURI(rawuri string) (*keyAttributes, error) {
return nil, fmt.Errorf("error parsing %q: label is required", rawuri)
}
tag := u.Get("tag")
if tag == "" {
if tag == "" && !u.Has("tag") {
tag = DefaultTag
}
return &keyAttributes{
Expand Down Expand Up @@ -1100,7 +1103,7 @@ func parseSearchURI(rawuri string) (*keySearchAttributes, error) {
// mackms:label=my-key;tag=my-tag;hash=010a...;se=true;bio=true
label := u.Get("label") // when searching, the label can be empty
tag := u.Get("tag")
if tag == "" {
if tag == "" && !u.Has("tag") {
tag = DefaultTag
}
return &keySearchAttributes{
Expand Down
21 changes: 21 additions & 0 deletions kms/mackms/mackms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ func TestMacKMS_GetPublicKey(t *testing.T) {
assertion assert.ErrorAssertionFunc
}{
{"ok", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: r1.Name}}, r1.PublicKey, assert.NoError},
{"ok no tag", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-p256;tag="}}, r1.PublicKey, assert.NoError},
{"ok private only ECDSA ", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "mackms:label=test-ecdsa"}}, r2.PublicKey, assert.NoError},
{"ok private only RSA ", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: r3.Name}}, r3.PublicKey, assert.NoError},
{"ok no uri", &MacKMS{}, args{&apiv1.GetPublicKeyRequest{Name: "test-p256"}}, r1.PublicKey, assert.NoError},
Expand All @@ -357,6 +358,9 @@ func TestMacKMS_CreateKey(t *testing.T) {
assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{
Name: "mackms:label=test-p256",
}))
assert.NoError(t, kms.DeleteKey(&apiv1.DeleteKeyRequest{
Name: "mackms:label=test-p256-2;tag=",
}))
})

type args struct {
Expand Down Expand Up @@ -384,6 +388,21 @@ func TestMacKMS_CreateKey(t *testing.T) {
require.NotEmpty(tt, u.tag)
require.NotEmpty(tt, u.hash)
}, assert.NoError},
{"ok no tag", &MacKMS{}, args{&apiv1.CreateKeyRequest{Name: "mackms:label=test-p256-2;tag="}},
func(tt require.TestingT, i1 interface{}, i2 ...interface{}) {
require.IsType(tt, &apiv1.CreateKeyResponse{}, i1)
resp := i1.(*apiv1.CreateKeyResponse)
require.NotEmpty(tt, resp.Name)
require.NotNil(tt, resp.PublicKey)
require.Nil(tt, resp.PrivateKey)
require.NotEmpty(tt, resp.CreateSignerRequest)

u, err := parseURI(resp.Name)
require.NoError(tt, err)
require.NotEmpty(tt, u.label)
require.Empty(tt, u.tag)
require.NotEmpty(tt, u.hash)
}, assert.NoError},
{"fail name", &MacKMS{}, args{&apiv1.CreateKeyRequest{}}, require.Nil, assert.Error},
{"fail uri", &MacKMS{}, args{&apiv1.CreateKeyRequest{Name: "mackms:name=test-p256"}}, require.Nil, assert.Error},
{"fail signatureAlgorithm", &MacKMS{}, args{&apiv1.CreateKeyRequest{
Expand Down Expand Up @@ -524,6 +543,8 @@ func Test_parseURI(t *testing.T) {
{"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},
{"ok label", args{"the-label"}, &keyAttributes{label: "the-label", tag: DefaultTag}, assert.NoError},
{"ok label uri", args{"mackms:label=the-label"}, &keyAttributes{label: "the-label", tag: DefaultTag}, assert.NoError},
{"ok label empty tag", args{"mackms:label=the-label;tag="}, &keyAttributes{label: "the-label", tag: ""}, assert.NoError},
{"ok label empty tag no equal", args{"mackms:label=the-label;tag"}, &keyAttributes{label: "the-label", tag: ""}, assert.NoError},
{"fail parse", args{"mackms:::label=the-label"}, nil, assert.Error},
{"fail missing label", args{"mackms:hash=0102abcd"}, nil, assert.Error},
}
Expand Down
5 changes: 5 additions & 0 deletions kms/uri/uri.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ func (u *URI) String() string {
return u.URL.String()
}

// Has checks whether a given key is set.
func (u *URI) Has(key string) bool {
return u.Values.Has(key) || u.URL.Query().Has(key)
}

// Get returns the first value in the uri with the given key, it will return
// empty string if that field is not present.
func (u *URI) Get(key string) string {
Expand Down
36 changes: 36 additions & 0 deletions kms/uri/uri_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,42 @@ func TestParseWithScheme(t *testing.T) {
}
}

func TestURI_Has(t *testing.T) {
mustParse := func(s string) *URI {
u, err := Parse(s)
if err != nil {
t.Fatal(err)
}
return u
}
type args struct {
key string
}
tests := []struct {
name string
uri *URI
args args
want bool
}{
{"ok", mustParse("yubikey:slot-id=9a"), args{"slot-id"}, true},
{"ok empty", mustParse("yubikey:slot-id="), args{"slot-id"}, true},
{"ok query", mustParse("yubikey:pin=123456?slot-id="), args{"slot-id"}, true},
{"ok empty no equal", mustParse("yubikey:slot-id"), args{"slot-id"}, true},
{"ok query no equal", mustParse("yubikey:pin=123456?slot-id"), args{"slot-id"}, true},
{"ok empty no equal other", mustParse("yubikey:slot-id;pin=123456"), args{"slot-id"}, true},
{"ok query no equal other", mustParse("yubikey:pin=123456?slot-id&pin=123456"), args{"slot-id"}, true},
{"fail", mustParse("yubikey:pin=123456"), args{"slot-id"}, false},
{"fail with query", mustParse("yubikey:pin=123456?slot=9a"), args{"slot-id"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.uri.Has(tt.args.key); got != tt.want {
t.Errorf("URI.Has() = %v, want %v", got, tt.want)
}
})
}
}

func TestURI_Get(t *testing.T) {
mustParse := func(s string) *URI {
u, err := Parse(s)
Expand Down

0 comments on commit 1e71f99

Please sign in to comment.