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

feat: refactor describe-key command & add unit test #83

Merged
merged 3 commits into from
Mar 10, 2023
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
38 changes: 36 additions & 2 deletions cmd/notation-azure-kv/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import (
"fmt"
"io"

"github.com/Azure/notation-azure-kv/internal/signature"
"github.com/Azure/notation-azure-kv/internal/keyvault"
"github.com/notaryproject/notation-core-go/signature"
"github.com/notaryproject/notation-go/plugin/proto"
)

// newCertificateFromID is the function for generating a key vault certificate
// client. It will be overridden by unit test.
var newCertificateFromID = keyvault.NewCertificateFromID

func runDescribeKey(ctx context.Context, input io.Reader) (*proto.DescribeKeyResponse, error) {
// parse input request
var req proto.DescribeKeyRequest
if err := json.NewDecoder(input).Decode(&req); err != nil {
return nil, &proto.RequestError{
Expand All @@ -19,5 +25,33 @@ func runDescribeKey(ctx context.Context, input io.Reader) (*proto.DescribeKeyRes
}
}

return signature.Key(ctx, &req)
// get key spec for notation
keySpec, err := notationKeySpec(ctx, req.KeyID)
if err != nil {
return nil, err
}

return &proto.DescribeKeyResponse{
KeyID: req.KeyID,
KeySpec: keySpec,
}, nil
}

func notationKeySpec(ctx context.Context, keyID string) (proto.KeySpec, error) {
// get the certificate
certificate, err := newCertificateFromID(keyID)
if err != nil {
return "", err
}
cert, err := certificate.Certificate(ctx)
if err != nil {
return "", err
}

// extract key spec from certificate
keySpec, err := signature.ExtractKeySpec(cert)
if err != nil {
return "", err
}
return proto.EncodeKeySpec(keySpec)
}
134 changes: 134 additions & 0 deletions cmd/notation-azure-kv/key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package main

import (
"context"
"crypto/x509"
_ "embed"
"errors"
"strings"
"testing"

"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
"github.com/Azure/notation-azure-kv/internal/crypto"
"github.com/Azure/notation-azure-kv/internal/keyvault"
)

//go:embed testdata/aesPEMCert.pem
var aesPEMCert []byte

//go:embed testdata/validPEMCert.pem
var validPEMCert []byte

type certificateMock struct {
cert *x509.Certificate
err error
}

func (c *certificateMock) Sign(ctx context.Context, algorithm azkeys.JSONWebKeySignatureAlgorithm, digest []byte) ([]byte, error) {
panic("not implemented") // TODO: Implement
}

func (c *certificateMock) CertificateChain(ctx context.Context) ([]*x509.Certificate, error) {
panic("not implemented") // TODO: Implement
}

func (c *certificateMock) Certificate(ctx context.Context) (*x509.Certificate, error) {
return c.cert, c.err
}

func Test_runDescribeKey(t *testing.T) {
certs, err := crypto.ParseCertificates(validPEMCert, "application/x-pem-file")
if err != nil {
t.Fatalf("got err = %s, want no error", err)
}
if len(certs) != 1 {
t.Fatalf("got len(certs) = %d, want 1", len(certs))
}
sha1Certs, err := crypto.ParseCertificates(aesPEMCert, "application/x-pem-file")
if err != nil {
t.Fatalf("got err = %s, want no error", err)
}
if len(sha1Certs) != 1 {
t.Fatalf("got len(certs) = %d, want 1", len(certs))
}
type args struct {
ctx context.Context
input string
fun func(id string) (keyvault.Certificate, error)
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "invalid AES signed Certificate",
args: args{
input: `{"contractVersion":"1.0","keyId":"https://notationakvtest.vault.azure.net/keys/notationrsademo/version"}`,
fun: func(id string) (keyvault.Certificate, error) {
return &certificateMock{cert: sha1Certs[0]}, nil
},
},
wantErr: true,
},
{
name: "valid describe key",
args: args{
input: `{"contractVersion":"1.0","keyId":"https://notationakvtest.vault.azure.net/keys/notationrsademo/version"}`,
fun: func(id string) (keyvault.Certificate, error) {
return &certificateMock{cert: certs[0]}, nil
},
},
wantErr: false,
},
{
name: "newCertificateFromID error",
args: args{
input: `{"contractVersion":"1.0","keyId":"https://notationakvtest.vault.azure.net/keys/notationrsademo/version"}`,
fun: func(id string) (keyvault.Certificate, error) {
return &certificateMock{}, errors.New("error")
},
},
wantErr: true,
},
{
name: "get certificate failed",
args: args{
input: `{"contractVersion":"1.0","keyId":"https://notationakvtest.vault.azure.net/keys/notationrsademo/version"}`,
fun: func(id string) (keyvault.Certificate, error) {
return &certificateMock{err: errors.New("error")}, nil
},
},
wantErr: true,
},
{
name: "parse input error",
args: args{
input: `{"contractVersion":,"keyId":"https://notationakvtest.vault.azure.net/keys/notationrsademo/version"}`,
fun: func(id string) (keyvault.Certificate, error) {
return &certificateMock{}, nil
},
},
wantErr: true,
},
{
name: "keyID error",
args: args{
input: `{"contractVersion": "1.0","keyId":""}`,
fun: keyvault.NewCertificateFromID,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
input := strings.NewReader(tt.args.input)
newCertificateFromID = tt.args.fun
_, err := runDescribeKey(tt.args.ctx, input)
if (err != nil) != tt.wantErr {
t.Errorf("runDescribeKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
15 changes: 15 additions & 0 deletions cmd/notation-azure-kv/testdata/aesPEMCert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICZjCCAc+gAwIBAgIUS04A+GyzokWJ08hb+/KnFcPP+uAwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzAzMDgwMTM2MzVaFw0yNDAz
MDcwMTM2MzVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEB
BQADgY0AMIGJAoGBAKIFKlqbbDPCfNb9CXOmK5+EQ5t7HA0XEz3atgZjjJptsb/Z
qmerKfSw1Hz8H1i5cihUO/a8Go5KTCQiogyGTeiz14QxfhZYo6AtSt2M8K6JQ9fK
vsrgJkdA1E9ub32DvOkbIcRmrV5ku5XxWOoq4jNzNMYxU+DiAltNJUMqWeBjAgMB
AAGjUzBRMB0GA1UdDgQWBBTMpk91wv7LcerDS0QAuOWO2ZfhETAfBgNVHSMEGDAW
gBTMpk91wv7LcerDS0QAuOWO2ZfhETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4GBAIplSbQyv7kaufrHk96BLNxJbe57iq31S+qdQCKHynZdmTBupPrD
rQbm2StyYxrFePTYXpXc/IKRYdBEiUtWBC0XZxoz4kwd7oRHDOIe2/WzcdRnBpMj
AEbkigwfvEU7LsJslytJ/GMLArt3zxnTOo3BiCgZHeQ3CP1myPHzCMem
-----END CERTIFICATE-----
19 changes: 19 additions & 0 deletions cmd/notation-azure-kv/testdata/validPEMCert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAwIBAgIUYpaZZGMWT0GldzKimhoZRZRB/G8wDQYJKoZIhvcNAQEL
BQAwEjEQMA4GA1UEAwwHVGVzdCBDQTAeFw0yMzAyMDkwNjMwNTRaFw0yNDAyMDkw
NjMwNTRaMBIxEDAOBgNVBAMMB1Rlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQCQEAEzMdSwjXuTgpZErV6oFd/J5JumhU3aca4MHcbtLVpJSkzJ
SE49V5y8srvD6XKo15yqZcH5JqFKTnQhgXmaAece/Z2F7ZWNm17C0qvnp4bZpf+R
jxsOqEwj62JaABmWab8NQFWKdwWDR66vWENapZwAPVek+fpG5s5aRyt9aXbjyzfA
U5Vuc7ZvP1Kvpr1SGDERryX+WEhDJZHgP3TdOa7tSb6yidC/I0iR+x/vWWafxzF2
MopZ0YrOyc26LGW1wiElpNVB9PuhqnUTe6MmtoYwUIBsl5XQEfbcRu50hScsiNyd
JyXm4NzlCjTy9pRF7QBfSzlag8aM3uPuljXpAgMBAAGjYzBhMB0GA1UdDgQWBBSJ
00WHL+fkhKNfIo9IyasXpFbeUzAfBgNVHSMEGDAWgBSJ00WHL+fkhKNfIo9IyasX
pFbeUzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDANBgkqhkiG9w0B
AQsFAAOCAQEAThFd2H5wm2egZkw0oHu1QiqkqRiJ4yvd215l3OdENJ8LKua2KYlT
XI5LZcq+CBCnMHqJV6Buu4Xa0aDpAjpSfumC3fUfLQ+l0EsvnGu4TLo8ER/E9Unb
0fqgnleBK6UqeP7ZiaIIasXdoneNG8LHJabYD35XZ2L6iT7ht9mEOPcQF4MAyUBQ
S96ueYIlNciIvjbjIUpdSsM6Kk7NJaRlMUH0L+oF6b7Ls5+EJp/pBybRr5LpeBAI
b/IzkcGr6ffJmeZiLWO9h3HiNSf/AszcrkPNIN4NxRntsW/CZMKr3VMh4Ol8obzx
Z2UgfOQ/rh93JO9jFO0kaggkwOKSiogD1g==
-----END CERTIFICATE-----
29 changes: 20 additions & 9 deletions internal/keyvault/keyvault.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
"github.com/Azure/notation-azure-kv/internal/crypto"
"github.com/notaryproject/notation-go/plugin/proto"
)

// set of auth errors
Expand Down Expand Up @@ -56,8 +57,15 @@ type secretClient interface {
GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error)
}

// Certificate represents a Azure Certificate Vault.
type Certificate struct {
// Certificate includes Sign, CertificateChain, Certificate methods.
type Certificate interface {
Sign(ctx context.Context, algorithm azkeys.JSONWebKeySignatureAlgorithm, digest []byte) ([]byte, error)
CertificateChain(ctx context.Context) ([]*x509.Certificate, error)
Certificate(ctx context.Context) (*x509.Certificate, error)
}

// certificate represents a Azure certificate Vault.
type certificate struct {
keyClient keyClient
certificateClient certificateClient
secretClient secretClient
Expand All @@ -68,10 +76,13 @@ type Certificate struct {

// NewCertificateFromID creates a Certificate with given key identifier or
// certificate identifier.
func NewCertificateFromID(id string) (*Certificate, error) {
func NewCertificateFromID(id string) (Certificate, error) {
host, name, version, err := parseIdentifier(id)
if err != nil {
return nil, fmt.Errorf("invalid certificate/key identifier with error: %w. the preferred schema is https://{vaultHost}/[certificates|keys]/{keyName}/{version}", err)
return nil, &proto.RequestError{
Code: proto.ErrorCodeValidation,
Err: fmt.Errorf("invalid certificate/key identifier with error: %w. the preferred schema is https://{vaultHost}/[certificates|keys]/{keyName}/{version}", err),
}
}
return NewCertificate(host, name, version)
}
Expand All @@ -96,7 +107,7 @@ func parseIdentifier(id string) (string, string, string, error) {
}

// NewCertificate function creates a new instance of KeyVault struct
func NewCertificate(vaultHost, keyName, version string) (*Certificate, error) {
func NewCertificate(vaultHost, keyName, version string) (Certificate, error) {
// get credential
credential, err := azureCredential()
if err != nil {
Expand All @@ -118,7 +129,7 @@ func NewCertificate(vaultHost, keyName, version string) (*Certificate, error) {
return nil, err
}

return &Certificate{
return &certificate{
keyClient: keyClient,
certificateClient: certClient,
secretClient: secretClient,
Expand Down Expand Up @@ -162,7 +173,7 @@ func getAzureClientAuthMethod() authMethod {
}

// Sign signs the message digest with the algorithm provided.
func (k *Certificate) Sign(ctx context.Context, algorithm azkeys.JSONWebKeySignatureAlgorithm, digest []byte) ([]byte, error) {
func (k *certificate) Sign(ctx context.Context, algorithm azkeys.JSONWebKeySignatureAlgorithm, digest []byte) ([]byte, error) {
// Sign the message
res, err := k.keyClient.Sign(
ctx,
Expand All @@ -189,7 +200,7 @@ func (k *Certificate) Sign(ctx context.Context, algorithm azkeys.JSONWebKeySigna
}

// CertificateChain returns the X.509 certificate chain associated with the key.
func (k *Certificate) CertificateChain(ctx context.Context) ([]*x509.Certificate, error) {
func (k *certificate) CertificateChain(ctx context.Context) ([]*x509.Certificate, error) {
secret, err := k.secretClient.GetSecret(ctx, k.name, k.version, nil)
if err != nil {
return nil, err
Expand All @@ -203,7 +214,7 @@ func (k *Certificate) CertificateChain(ctx context.Context) ([]*x509.Certificate
}

// Certificate returns the X.509 certificate associated with the key.
func (k *Certificate) Certificate(ctx context.Context) (*x509.Certificate, error) {
func (k *certificate) Certificate(ctx context.Context) (*x509.Certificate, error) {
cert, err := k.certificateClient.GetCertificate(ctx, k.name, k.version, nil)
if err != nil {
return nil, err
Expand Down
Loading