From 00cac02e066863cc07cf40ef262046b526cff333 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Fri, 21 Jul 2023 18:10:39 -0700 Subject: [PATCH 1/3] Add tracing from azcore --- client.go | 92 +++++++++++++++++++++++++++----- go.mod | 10 +++- go.sum | 24 ++++++++- internal/algorithm/aes.go | 4 ++ internal/algorithm/aes_test.go | 1 + internal/algorithm/algorithm.go | 12 ++++- internal/algorithm/ecdsa.go | 4 ++ internal/algorithm/ecdsa_test.go | 1 + internal/algorithm/rsa.go | 4 ++ internal/algorithm/rsa_test.go | 1 + tracing.go | 45 ++++++++++++++++ tracing_example_test.go | 72 +++++++++++++++++++++++++ 12 files changed, 253 insertions(+), 17 deletions(-) create mode 100644 tracing.go create mode 100644 tracing_example_test.go diff --git a/client.go b/client.go index f37a78a..5a06d30 100644 --- a/client.go +++ b/client.go @@ -13,6 +13,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" "github.com/heaths/azcrypto/internal" alg "github.com/heaths/azcrypto/internal/algorithm" @@ -26,10 +27,11 @@ type Client struct { keyVersion string remoteClient *azkeys.Client - localClient any + localClient alg.Algorithm - _init sync.Once - rand io.Reader + init sync.Once + rand io.Reader + tracer tracing.Tracer } type ClientOptions struct { @@ -57,6 +59,7 @@ func NewClient(keyID string, credential azcore.TokenCredential, options *ClientO if rand == nil { rand = rng.Reader } + tracer := options.TracingProvider.NewTracer("azcrypto.Client", "v0.0.0") // TODO vaultURL, name, version := internal.ParseID(&keyID) if vaultURL == nil || name == nil { @@ -77,10 +80,11 @@ func NewClient(keyID string, credential azcore.TokenCredential, options *ClientO keyVersion: *version, remoteClient: remoteClient, rand: rand, + tracer: tracer, } if options.remoteOnly { - client._init.Do(func() {}) + client.init.Do(func() {}) } return client, nil @@ -97,6 +101,7 @@ func NewClientFromJSONWebKey(key azkeys.JSONWebKey, options *ClientOptions) (*Cl if rand == nil { rand = rng.Reader } + tracer := options.TracingProvider.NewTracer("azcrypto.Client", "v0.0.0") // TODO var keyID string if key.KID != nil { @@ -112,8 +117,9 @@ func NewClientFromJSONWebKey(key azkeys.JSONWebKey, options *ClientOptions) (*Cl keyID: string(keyID), localClient: localClient, rand: rand, + tracer: tracer, } - client._init.Do(func() {}) + client.init.Do(func() {}) return client, nil } @@ -123,8 +129,12 @@ func (client *Client) KeyID() string { return client.keyID } -func (client *Client) init(ctx context.Context) { - client._init.Do(func() { +func (client *Client) cache(ctx context.Context) { + client.init.Do(func() { + var err error + ctx, span := client.startSpan(ctx, "cache") + defer func() { span.End(err) }() + response, err := client.remoteClient.GetKey(ctx, client.keyName, client.keyVersion, nil) if err != nil { return @@ -163,12 +173,17 @@ type EncryptResult struct { // Encrypt encrypts the plaintext using the specified algorithm. func (client *Client) Encrypt(ctx context.Context, algorithm EncryptAlgorithm, plaintext []byte, options *EncryptOptions) (EncryptResult, error) { - client.init(ctx) + var err error + ctx, span := client.startSpan(ctx, "Encrypt") + defer func() { span.End(err) }() + + client.cache(ctx) var encrypter alg.Encrypter if alg.As(client.localClient, &encrypter) { result, err := encrypter.Encrypt(algorithm, plaintext) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return EncryptResult{ Algorithm: result.Algorithm, KeyID: result.KeyID, @@ -236,7 +251,11 @@ type EncryptAESCBCResult struct { // // You should not use CBC without first ensuring the integrity of the ciphertext using an HMAC. func (client *Client) EncryptAESCBC(ctx context.Context, algorithm EncryptAESCBCAlgorithm, plaintext, iv []byte, options *EncryptAESCBCOptions) (EncryptAESCBCResult, error) { - client.init(ctx) + var err error + ctx, span := client.startSpan(ctx, "EncryptAESCBC") + defer func() { span.End(err) }() + + client.cache(ctx) if options == nil { options = &EncryptAESCBCOptions{} @@ -253,6 +272,7 @@ func (client *Client) EncryptAESCBC(ctx context.Context, algorithm EncryptAESCBC if alg.As(client.localClient, &encrypter) { result, err := encrypter.EncryptAESCBC(algorithm, plaintext, iv) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return EncryptAESCBCResult{ Algorithm: result.Algorithm, KeyID: result.KeyID, @@ -322,7 +342,11 @@ type EncryptAESGCMResult struct { // EncryptAESGCM encrypts the plaintext using the specified algorithm and optional authenticated data which is not encrypted. func (client *Client) EncryptAESGCM(ctx context.Context, algorithm EncryptAESCBCAlgorithm, plaintext, additionalAuthenticatedData []byte, options *EncryptAESGCMOptions) (EncryptAESGCMResult, error) { - client.init(ctx) + var err error + ctx, span := client.startSpan(ctx, "EncryptAESGCM") + defer func() { span.End(err) }() + + client.cache(ctx) if options == nil { options = &EncryptAESGCMOptions{} @@ -337,6 +361,7 @@ func (client *Client) EncryptAESGCM(ctx context.Context, algorithm EncryptAESCBC result, err := encrypter.EncryptAESGCM(algorithm, plaintext, nonce, additionalAuthenticatedData) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return EncryptAESGCMResult{ Algorithm: result.Algorithm, KeyID: result.KeyID, @@ -392,11 +417,16 @@ type DecryptResult = alg.DecryptResult // Decrypt decrypts the ciphertext using the specified algorithm. func (client *Client) Decrypt(ctx context.Context, algorithm EncryptAlgorithm, ciphertext []byte, options *DecryptOptions) (DecryptResult, error) { + var err error + ctx, span := client.startSpan(ctx, "Decrypt") + defer func() { span.End(err) }() + // Decrypting requires access to a private key, which Key Vault does not provide by default. var encrypter alg.Encrypter if alg.As(client.localClient, &encrypter) { result, err := encrypter.Decrypt(algorithm, ciphertext) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return DecryptResult{ Algorithm: result.Algorithm, KeyID: result.KeyID, @@ -449,11 +479,16 @@ type DecryptAESCBCResult = alg.DecryptResult // DecryptAESCBC decrypts the ciphertext using the specified algorithm. func (client *Client) DecryptAESCBC(ctx context.Context, algorithm EncryptAESCBCAlgorithm, ciphertext, iv []byte, options *DecryptAESCBCOptions) (DecryptAESCBCResult, error) { + var err error + ctx, span := client.startSpan(ctx, "DecryptAESCBC") + defer func() { span.End(err) }() + // Decrypting requires access to a private key, which Key Vault does not provide by default. var encrypter alg.AESEncrypter if alg.As(client.localClient, &encrypter) { result, err := encrypter.DecryptAESCBC(algorithm, ciphertext, iv) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return DecryptAESCBCResult{ Algorithm: result.Algorithm, KeyID: result.KeyID, @@ -507,11 +542,16 @@ type DecryptAESGCMResult = alg.DecryptResult // DecryptAESGCM decrypts the ciphertext using the specified algorithm. func (client *Client) DecryptAESGCM(ctx context.Context, algorithm EncryptAESGCMAlgorithm, ciphertext, nonce, authenticationTag, additionalAuthenticatedData []byte, options *DecryptAESGCMOptions) (DecryptAESGCMResult, error) { + var err error + ctx, span := client.startSpan(ctx, "DecryptAESGCM") + defer func() { span.End(err) }() + // Decrypting requires access to a private key, which Key Vault does not provide by default. var encrypter alg.AESEncrypter if alg.As(client.localClient, &encrypter) { result, err := encrypter.DecryptAESGCM(algorithm, ciphertext, nonce, authenticationTag, additionalAuthenticatedData) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return DecryptAESCBCResult{ Algorithm: result.Algorithm, KeyID: result.KeyID, @@ -567,11 +607,16 @@ type SignResult = alg.SignResult // Sign signs the specified digest using the specified algorithm. func (client *Client) Sign(ctx context.Context, algorithm SignAlgorithm, digest []byte, options *SignOptions) (SignResult, error) { + var err error + ctx, span := client.startSpan(ctx, "Sign") + defer func() { span.End(err) }() + // Signing requires access to a private key, which Key Vault does not provide by default. var signer alg.Signer if alg.As(client.localClient, &signer) { result, err := signer.Sign(algorithm, digest) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return result, err } } @@ -617,6 +662,10 @@ type SignDataOptions struct { // SignData hashes the data using a suitable hash based on the specified algorithm. func (client *Client) SignData(ctx context.Context, algorithm SignAlgorithm, data []byte, options *SignDataOptions) (SignResult, error) { + var err error + ctx, span := client.startSpan(ctx, "SignData") + defer func() { span.End(err) }() + hash, err := alg.GetHash(algorithm) if err != nil { return SignResult{}, err @@ -643,11 +692,16 @@ type VerifyResult = alg.VerifyResult // Verify verifies that the specified digest is valid using the specified signature and algorithm. func (client *Client) Verify(ctx context.Context, algorithm SignAlgorithm, digest, signature []byte, options *VerifyOptions) (VerifyResult, error) { - client.init(ctx) + var err error + ctx, span := client.startSpan(ctx, "Verify") + defer func() { span.End(err) }() + + client.cache(ctx) var signer alg.Signer if alg.As(client.localClient, &signer) { result, err := signer.Verify(algorithm, digest, signature) + span.SetLocal(string(algorithm)) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { return result, err } @@ -690,6 +744,10 @@ type VerifyDataOptions struct { // VerifyData verifies the digest of the data is valid using a suitable hash based on the specified algorithm. func (client *Client) VerifyData(ctx context.Context, algorithm SignAlgorithm, data, signature []byte, options *VerifyDataOptions) (VerifyResult, error) { + var err error + ctx, span := client.startSpan(ctx, "VerifyData") + defer func() { span.End(err) }() + hash, err := alg.GetHash(algorithm) if err != nil { return VerifyResult{}, err @@ -716,12 +774,17 @@ type WrapKeyResult = alg.WrapKeyResult // WrapKey encrypts the specified key using the specified algorithm. Asymmetric encryption is typically used to wrap a symmetric key used for streaming ciphers. func (client *Client) WrapKey(ctx context.Context, algorithm WrapKeyAlgorithm, key []byte, options *WrapKeyOptions) (WrapKeyResult, error) { - client.init(ctx) + var err error + ctx, span := client.startSpan(ctx, "WrapKey") + defer func() { span.End(err) }() + + client.cache(ctx) var keyWrapper alg.KeyWrapper if alg.As(client.localClient, &keyWrapper) { result, err := keyWrapper.WrapKey(algorithm, key) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return result, err } } @@ -770,11 +833,16 @@ type UnwrapKeyResult = alg.UnwrapKeyResult // UnwrapKey decrypts the specified key using the specified algorithm. Asymmetric decryption is typically used to unwrap a symmetric key used for streaming ciphers. func (client *Client) UnwrapKey(ctx context.Context, algorithm WrapKeyAlgorithm, encryptedKey []byte, options *UnwrapKeyOptions) (UnwrapKeyResult, error) { + var err error + ctx, span := client.startSpan(ctx, "UnwrapKey") + defer func() { span.End(err) }() + // Unwrapping a key requires access to a private key, which Key Vault does not provide by default. var keyWrapper alg.KeyWrapper if alg.As(client.localClient, &keyWrapper) { result, err := keyWrapper.UnwrapKey(algorithm, encryptedKey) if client.localOnly() || !errors.Is(err, internal.ErrUnsupported) { + span.SetLocal(string(algorithm)) return result, err } } diff --git a/go.mod b/go.mod index 5e0cb61..bc24053 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,16 @@ module github.com/heaths/azcrypto go 1.18 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0-beta.1 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 + github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel v0.2.0 github.com/azure/azure-dev v0.0.0-20230718204335-175a4da25e48 github.com/joho/godotenv v1.5.1 github.com/stretchr/testify v1.8.4 + go.opentelemetry.io/otel v1.16.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 + go.opentelemetry.io/otel/sdk v1.16.0 golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 gopkg.in/dnaeon/go-vcr.v3 v3.1.2 ) @@ -18,6 +22,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/kr/text v0.2.0 // indirect @@ -25,6 +31,8 @@ require ( github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sethvargo/go-retry v0.2.4 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect golang.org/x/crypto v0.11.0 // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/go.sum b/go.sum index 6b0fbeb..a9fb4ab 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,16 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0-beta.1 h1:8t6ZZtkOCl+rx7uBn40Nj62ABVGkXK69U/En44wJIlE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0-beta.1/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0/go.mod h1:Q28U+75mpCaSCDowNEmhIo/rmgdkqmkmzI7N6TGR4UY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0/go.mod h1:cw4zVQgBby0Z5f2v0itn6se2dDP17nTjbZFXW5uPyHA= +github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel v0.2.0 h1:YKBjwPHQqOOIZ2TijXDnGylbf70M4moGJsdU40MKZ58= +github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel v0.2.0/go.mod h1:apWvi7CuVOuSuGn4h8cSNV5gHs+vhLTicndWY7p2Ggk= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= github.com/azure/azure-dev v0.0.0-20230718204335-175a4da25e48 h1:IvPqn1m6Z4nSgjstXlg7F8l7WTwCxB8PELDejuZ+juA= @@ -16,8 +19,14 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= @@ -35,6 +44,17 @@ github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08O github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/exporters/jaeger v1.16.0 h1:YhxxmXZ011C0aDZKoNw+juVWAmEfv/0W2XBOv9aHTaA= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0 h1:+XWJd3jf75RXJq29mxbuXhCXFDG3S3R4vBUeSI2P7tE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.16.0/go.mod h1:hqgzBPTf4yONMFgdZvL/bK42R/iinTyVQtiWihs3SZc= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= +go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= diff --git a/internal/algorithm/aes.go b/internal/algorithm/aes.go index 554ea10..5705434 100644 --- a/internal/algorithm/aes.go +++ b/internal/algorithm/aes.go @@ -56,6 +56,10 @@ func newAES(key azkeys.JSONWebKey) (AES, error) { }, nil } +func (a AES) KeyType() string { + return "oct" +} + func (a AES) EncryptAESCBC(algorithm EncryptAESCBCAlgorithm, plaintext, iv []byte) (EncryptResult, error) { // TODO: Consider implementing local PKCS7 padding support should we need local encryption support. if !supportsAlgorithm( diff --git a/internal/algorithm/aes_test.go b/internal/algorithm/aes_test.go index fbed05c..ab4b92a 100644 --- a/internal/algorithm/aes_test.go +++ b/internal/algorithm/aes_test.go @@ -79,6 +79,7 @@ func TestNewAES(t *testing.T) { } require.NoError(t, err) require.Equal(t, tt.keyID, alg.keyID) + require.Equal(t, "oct", alg.KeyType()) require.Equal(t, tt.blockSize, alg.block.BlockSize()) var encrypter AESEncrypter diff --git a/internal/algorithm/algorithm.go b/internal/algorithm/algorithm.go index b848487..44687cf 100644 --- a/internal/algorithm/algorithm.go +++ b/internal/algorithm/algorithm.go @@ -20,7 +20,12 @@ type EncryptAESGCMAlgorithm = azkeys.EncryptionAlgorithm type SignAlgorithm = azkeys.SignatureAlgorithm type WrapKeyAlgorithm = azkeys.EncryptionAlgorithm +type Algorithm interface { + KeyType() string +} + type AESEncrypter interface { + Algorithm EncryptAESCBC(algorithm EncryptAESCBCAlgorithm, plaintext, iv []byte) (EncryptResult, error) DecryptAESCBC(algorithm EncryptAESCBCAlgorithm, ciphertext, iv []byte) (DecryptResult, error) EncryptAESGCM(algorithm EncryptAESGCMAlgorithm, plaintext, nonce, additionalAuthenticatedData []byte) (EncryptResult, error) @@ -28,21 +33,24 @@ type AESEncrypter interface { } type Encrypter interface { + Algorithm Encrypt(algorithm EncryptAlgorithm, plaintext []byte) (EncryptResult, error) Decrypt(algorithm EncryptAlgorithm, ciphertext []byte) (DecryptResult, error) } type Signer interface { + Algorithm Sign(algorithm SignAlgorithm, digest []byte) (SignResult, error) Verify(algorithm SignAlgorithm, digest, signature []byte) (VerifyResult, error) } type KeyWrapper interface { + Algorithm WrapKey(algorithm WrapKeyAlgorithm, key []byte) (WrapKeyResult, error) UnwrapKey(algorithm WrapKeyAlgorithm, encryptedKey []byte) (UnwrapKeyResult, error) } -func As[T any](algorithm any, target *T) bool { +func As[T any](algorithm Algorithm, target *T) bool { if algorithm == nil { return false } @@ -56,7 +64,7 @@ func As[T any](algorithm any, target *T) bool { return false } -func NewAlgorithm(key azkeys.JSONWebKey, rand io.Reader) (any, error) { +func NewAlgorithm(key azkeys.JSONWebKey, rand io.Reader) (Algorithm, error) { if key.Kty == nil { return nil, internal.ErrUnsupported } diff --git a/internal/algorithm/ecdsa.go b/internal/algorithm/ecdsa.go index 49e1882..99247d1 100644 --- a/internal/algorithm/ecdsa.go +++ b/internal/algorithm/ecdsa.go @@ -80,6 +80,10 @@ func fromCurve(crv azkeys.CurveName) (elliptic.Curve, error) { } } +func (c ECDsa) KeyType() string { + return "EC" +} + func (c ECDsa) Sign(algorithm SignAlgorithm, digest []byte) (SignResult, error) { if !supportsAlgorithm( algorithm, diff --git a/internal/algorithm/ecdsa_test.go b/internal/algorithm/ecdsa_test.go index ce27374..c505f56 100644 --- a/internal/algorithm/ecdsa_test.go +++ b/internal/algorithm/ecdsa_test.go @@ -96,6 +96,7 @@ func TestNewECDsa(t *testing.T) { } require.NoError(t, err) require.Equal(t, tt.keyID, alg.keyID) + require.Equal(t, "EC", alg.KeyType()) require.NotNil(t, alg.rand) if len(tt.key.D) > 0 { diff --git a/internal/algorithm/rsa.go b/internal/algorithm/rsa.go index 894c400..0dd3485 100644 --- a/internal/algorithm/rsa.go +++ b/internal/algorithm/rsa.go @@ -76,6 +76,10 @@ func newRSA(key azkeys.JSONWebKey, rand io.Reader) (RSA, error) { }, nil } +func (r RSA) KeyType() string { + return "RSA" +} + func (r RSA) Encrypt(algorithm EncryptAlgorithm, plaintext []byte) (EncryptResult, error) { var ciphertext []byte var err error diff --git a/internal/algorithm/rsa_test.go b/internal/algorithm/rsa_test.go index c507767..ee5ed62 100644 --- a/internal/algorithm/rsa_test.go +++ b/internal/algorithm/rsa_test.go @@ -82,6 +82,7 @@ func TestNewRSA(t *testing.T) { } require.NoError(t, err) require.Equal(t, tt.keyID, alg.keyID) + require.Equal(t, "RSA", alg.KeyType()) require.NotNil(t, alg.rand) var encrypter Encrypter diff --git a/tracing.go b/tracing.go new file mode 100644 index 0000000..b5e246c --- /dev/null +++ b/tracing.go @@ -0,0 +1,45 @@ +// Copyright 2023 Heath Stewart. +// Licensed under the MIT License. See LICENSE.txt in the project root for license information. + +package azcrypto + +import ( + "context" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore/tracing" +) + +func (client *Client) startSpan(ctx context.Context, name string) (context.Context, span) { + if !client.tracer.Enabled() { + return ctx, span{} + } + + ctx, s := client.tracer.Start(ctx, "Client."+name, &tracing.SpanOptions{ + // https://opentelemetry.io/docs/specs/otel/trace/api/#spankind + Kind: tracing.SpanKindInternal, + Attributes: []tracing.Attribute{ + {Key: "azcrypto.kid", Value: client.keyID}, + }, + }) + + return ctx, span{s, client} +} + +type span struct { + tracing.Span + *Client +} + +func (s span) SetLocal(algorithm string) { + s.Span.AddEvent("azcrypto.local", + tracing.Attribute{Key: "azcrypto.kty", Value: s.Client.localClient.KeyType()}, + tracing.Attribute{Key: "azcrypto.alg", Value: algorithm}, + ) +} + +func (s span) End(err error) { + if err != nil { + s.Span.SetStatus(tracing.SpanStatusError, err.Error()) + } + s.Span.End(nil) +} diff --git a/tracing_example_test.go b/tracing_example_test.go new file mode 100644 index 0000000..00c510d --- /dev/null +++ b/tracing_example_test.go @@ -0,0 +1,72 @@ +package azcrypto_test + +// cspell:ignore azotel opentelemetry otel stdouttrace otrace semconv +import ( + "context" + "fmt" + "os" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" + "github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" + "github.com/heaths/azcrypto" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" + otrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +func Example_tracing() { + exporter, err := stdouttrace.New( + stdouttrace.WithWriter(os.Stderr), + stdouttrace.WithPrettyPrint(), + ) + if err != nil { + // TODO: handle error + } + + provider := otrace.NewTracerProvider( + otrace.WithBatcher(exporter), + otrace.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String("Example_tracing"), + )), + ) + defer func() { + if err := provider.Shutdown(context.TODO()); err != nil { + // TODO: handle error + } + }() + + credential, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + // TODO: handle error + } + + client, err := azcrypto.NewClient("https://heathskv.vault.azure.net/keys/test-ec-1", credential, &azcrypto.ClientOptions{ + ClientOptions: azkeys.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + TracingProvider: azotel.NewTracingProvider(provider, nil), + }, + }, + }) + if err != nil { + // TODO: handle error + } + + signed, err := client.SignData(context.TODO(), azcrypto.SignAlgorithmES256, []byte("plaintext"), nil) + if err != nil { + // TODO: handle error + } + + verified, err := client.VerifyData(context.TODO(), signed.Algorithm, []byte("plaintext"), signed.Signature, nil) + if err != nil { + // TODO: handle error + } + + fmt.Printf("Valid: %t\n", verified.Valid) + + // Output: + // Valid: false +} From 9ade3338c2986fc254b6a2dd2dd80e98179db403 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Sat, 22 Jul 2023 21:15:39 -0700 Subject: [PATCH 2/3] Test with go1.19 go.mod still specifies 1.18, but example_tracing_test.go requires 1.19. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c22cf0e..69c2f8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,7 +42,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version-file: go.mod + go-version: '^1.19' - name: Test run: GOOS=${{ matrix.goos }} go test ./... -cover -coverprofile=coverage.txt -race -v - name: Report coverage @@ -56,7 +56,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version-file: go.mod + go-version: '^1.19' - name: Lint # cspell:ignore golangci uses: golangci/golangci-lint-action@v3 From dbbbe166af411b9cb2ac652259cfe9c304a88ca3 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Sat, 22 Jul 2023 21:28:05 -0700 Subject: [PATCH 3/3] Fix tests --- tracing.go | 13 ++++++++----- tracing_example_test.go | 17 +++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/tracing.go b/tracing.go index b5e246c..c22a712 100644 --- a/tracing.go +++ b/tracing.go @@ -14,12 +14,16 @@ func (client *Client) startSpan(ctx context.Context, name string) (context.Conte return ctx, span{} } + attrs := []tracing.Attribute{ + {Key: "azcrypto.kid", Value: client.keyID}, + } + if client.localClient != nil { + attrs = append(attrs, tracing.Attribute{Key: "azcrypto.kty", Value: client.localClient.KeyType()}) + } ctx, s := client.tracer.Start(ctx, "Client."+name, &tracing.SpanOptions{ // https://opentelemetry.io/docs/specs/otel/trace/api/#spankind - Kind: tracing.SpanKindInternal, - Attributes: []tracing.Attribute{ - {Key: "azcrypto.kid", Value: client.keyID}, - }, + Kind: tracing.SpanKindInternal, + Attributes: attrs, }) return ctx, span{s, client} @@ -32,7 +36,6 @@ type span struct { func (s span) SetLocal(algorithm string) { s.Span.AddEvent("azcrypto.local", - tracing.Attribute{Key: "azcrypto.kty", Value: s.Client.localClient.KeyType()}, tracing.Attribute{Key: "azcrypto.alg", Value: algorithm}, ) } diff --git a/tracing_example_test.go b/tracing_example_test.go index 00c510d..3df8036 100644 --- a/tracing_example_test.go +++ b/tracing_example_test.go @@ -44,13 +44,17 @@ func Example_tracing() { // TODO: handle error } - client, err := azcrypto.NewClient("https://heathskv.vault.azure.net/keys/test-ec-1", credential, &azcrypto.ClientOptions{ - ClientOptions: azkeys.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - TracingProvider: azotel.NewTracingProvider(provider, nil), + client, err := azcrypto.NewClient( + "https://{vault-name}.vault.azure.net/keys/{key-name}/{key-version}", + credential, + &azcrypto.ClientOptions{ + ClientOptions: azkeys.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + TracingProvider: azotel.NewTracingProvider(provider, nil), + }, }, }, - }) + ) if err != nil { // TODO: handle error } @@ -66,7 +70,4 @@ func Example_tracing() { } fmt.Printf("Valid: %t\n", verified.Valid) - - // Output: - // Valid: false }