From 3fafae24479aafa0ca3ea1a9eac7d1b60b313c6e Mon Sep 17 00:00:00 2001 From: Pierre-Henri Symoneaux Date: Wed, 23 Oct 2024 16:38:57 +0200 Subject: [PATCH] refactor: simplify API usage with higher level methods Signed-off-by: Pierre-Henri Symoneaux --- apis.go | 16 +-- client.go | 253 ++++++++++++++++++++++++++++-------- datakey.go | 13 +- errors.go | 3 +- example_test.go | 54 ++++---- examples/datakeys.go | 33 ++--- examples/encrypt_decrypt.go | 9 +- examples/generate_keys.go | 23 +--- examples/list_get.go | 11 +- examples/sign_verify.go | 16 +-- go.mod | 1 + go.sum | 2 + internal/utils/int.go | 10 ++ internal/utils/int_test.go | 18 +++ iter.go | 10 ++ signer.go | 21 ++- types/ext_jwk.go | 1 + 17 files changed, 324 insertions(+), 170 deletions(-) diff --git a/apis.go b/apis.go index 807da7b..c76ccae 100644 --- a/apis.go +++ b/apis.go @@ -7,6 +7,7 @@ // ANY KIND, either express or implied. See the License for the specific language // governing permissions and limitations under the License. +// Package okms is a client for interacting with OVHcloud KMS REST-API. package okms import ( @@ -16,10 +17,10 @@ import ( "github.com/ovh/okms-sdk-go/types" ) -var _ Client = (*RestAPIClient)(nil) +var _ API = (*Client)(nil) -// Client is the interface abstracting the KMS clients methods. -type Client interface { +// API is the interface abstracting the KMS clients methods. +type API interface { // RandomApi DataKeyApi SignatureApi @@ -27,6 +28,7 @@ type Client interface { ServiceKeyApi // SecretApi // Ping(ctx context.Context) error + SetCustomHeader(key, value string) } // type RandomApi interface { @@ -74,18 +76,10 @@ type ServiceKeyApi interface { // ListServiceKeys returns a page of service keys. The response contains a continuationToken that must be passed to the // subsequent calls in order to get the next page. The state parameter when no nil is used to query keys having a specific state. ListServiceKeys(ctx context.Context, continuationToken *string, maxKey *int32, state *types.KeyStates) (*types.ListServiceKeysResponse, error) - // ListAllServiceKeys returns an iterator to go through all the keys without having to deal with pagination. - ListAllServiceKeys(pageSize *int32, state *types.KeyStates) KeyIter // UpdateServiceKey updates some service key metadata. UpdateServiceKey(ctx context.Context, keyId uuid.UUID, body types.PatchServiceKeyRequest) (*types.GetServiceKeyResponse, error) } -// type ServiceKeyApi2 interface { -// GenerateSymmetricKey(ctx context.Context, name string, size types.KeySizes, usage ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) -// GenerateRSAKeyPair(ctx context.Context, name string, size types.KeySizes, usage ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) -// GenerateECDSAKeyPair(ctx context.Context, name string, curve types.Curves, usage ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) -// } - // type SecretApi interface { // GetSecretsMetadata(ctx context.Context, path string, list bool) (*types.GetMetadataResponse, error) // PatchSecretMetadata(ctx context.Context, path string, body types.SecretUpdatableMetadata) error diff --git a/client.go b/client.go index f110250..b4f0d7e 100644 --- a/client.go +++ b/client.go @@ -11,7 +11,10 @@ package okms import ( "context" + "crypto" "crypto/tls" + "crypto/x509" + "encoding/pem" "errors" "fmt" "io" @@ -26,12 +29,172 @@ import ( "github.com/hashicorp/go-retryablehttp" "github.com/ovh/okms-sdk-go/internal" "github.com/ovh/okms-sdk-go/types" + "golang.org/x/crypto/ssh" ) const DefaultHTTPClientTimeout = 30 * time.Second -// RestAPIClient is the main client to the KMS rest api. -type RestAPIClient struct { +type Client struct { + API +} + +// WithCustomHeader adds additional HTTP headers that will be sent with every outgoing requests. +func (client *Client) WithCustomHeader(key, value string) *Client { + client.SetCustomHeader(key, value) + return client +} + +// GenerateSymmetricKey asks the KMS to generate a symmetric key with the given bits length, name and usage. The keyCtx parameter can be left empty if not needed. +func (client *Client) GenerateSymmetricKey(ctx context.Context, bitSize types.KeySizes, name, keyCtx string, ops ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) { + var keyContext *string + if keyCtx != "" { + keyContext = &keyCtx + } + kTy := types.Oct + body := types.CreateImportServiceKeyRequest{ + Context: keyContext, + Name: name, + Type: &kTy, + Operations: &ops, + Size: &bitSize, + Keys: nil, + } + return client.CreateImportServiceKey(ctx, nil, body) +} + +// GenerateRSAKeyPair asks the KMS to generate an RSA asymmetric key-pair with the given bits length, name and usage. The keyCtx parameter can be left empty if not needed. +func (client *Client) GenerateRSAKeyPair(ctx context.Context, bitSize types.KeySizes, name, keyCtx string, ops ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) { + var keyContext *string + if keyCtx != "" { + keyContext = &keyCtx + } + kTy := types.RSA + body := types.CreateImportServiceKeyRequest{ + Context: keyContext, + Name: name, + Type: &kTy, + Operations: &ops, + Size: &bitSize, + Keys: nil, + } + return client.CreateImportServiceKey(ctx, nil, body) +} + +// GenerateECKeyPair asks the KMS to generate an EC asymmetric key-pair with the given elliptic curve, name and usage. The keyCtx parameter can be left empty if not needed. +func (client *Client) GenerateECKeyPair(ctx context.Context, curve types.Curves, name, keyCtx string, ops ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) { + var keyContext *string + if keyCtx != "" { + keyContext = &keyCtx + } + kTy := types.EC + body := types.CreateImportServiceKeyRequest{ + Context: keyContext, + Name: name, + Type: &kTy, + Operations: &ops, + Curve: &curve, + Keys: nil, + } + return client.CreateImportServiceKey(ctx, nil, body) +} + +func (client *Client) importJWK(ctx context.Context, jwk types.JsonWebKey, name, keyCtx string, ops ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) { + var keyContext *string + if keyCtx != "" { + keyContext = &keyCtx + } + req := types.CreateImportServiceKeyRequest{ + Context: keyContext, + Name: name, + Operations: &ops, + Keys: &[]types.JsonWebKey{jwk}, + } + format := types.Jwk + return client.CreateImportServiceKey(ctx, &format, req) +} + +// ImportKey imports a key into the KMS. keyCtx can be left empty if not needed. +// +// The accepted types of the key parameter are +// - *rsa.PrivateKey +// - *ecdsa.PrivateKey +// - types.JsonWebKey and *types.JsonWebKey +// - []byte for importing symmetric keys. +func (client *Client) ImportKey(ctx context.Context, key any, name, keyCtx string, ops ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) { + switch k := key.(type) { + case types.JsonWebKey: + return client.importJWK(ctx, k, name, keyCtx, ops...) + case *types.JsonWebKey: + return client.importJWK(ctx, *k, name, keyCtx, ops...) + } + jwk, err := types.NewJsonWebKey(key, ops, name) + if err != nil { + return nil, err + } + return client.importJWK(ctx, jwk, name, keyCtx, ops...) +} + +// ImportKeyPairPEM imports a PEM formated key into the KMS. keyCtx can be left empty if not needed. +// +// Supported PEM types are: +// - PKCS8 +// - PKCS1 private keys +// - SEC1 +// - OpenSSH private keys +func (client *Client) ImportKeyPairPEM(ctx context.Context, privateKeyPem []byte, name, keyCtx string, ops ...types.CryptographicUsages) (*types.GetServiceKeyResponse, error) { + block, _ := pem.Decode(privateKeyPem) + if block == nil { + return nil, errors.New("No key to import") + } + var k any + var err error + switch block.Type { + case "PRIVATE KEY": + k, err = x509.ParsePKCS8PrivateKey(block.Bytes) + case "EC PRIVATE KEY": + k, err = x509.ParseECPrivateKey(block.Bytes) + case "RSA PRIVATE KEY": + k, err = x509.ParsePKCS1PrivateKey(block.Bytes) + case "OPENSSH PRIVATE KEY": + k, err = ssh.ParseRawPrivateKey(privateKeyPem) + default: + return nil, fmt.Errorf("Unsupported PEM type: %q", block.Type) + } + if err != nil { + return nil, err + } + return client.ImportKey(ctx, k, name, keyCtx, ops...) +} + +// ExportJwkPublicKey returns the public part of a key pair ans a Json Web Key. +func (client *Client) ExportJwkPublicKey(ctx context.Context, keyID uuid.UUID) (*types.JsonWebKey, error) { + format := types.Jwk + k, err := client.GetServiceKey(ctx, keyID, &format) + if err != nil { + return nil, err + } + if k.Attributes != nil && (*k.Attributes)["state"] != "active" { + return nil, fmt.Errorf("The key is not active (state is %q)", (*k.Attributes)["state"]) + } + if k.Keys == nil || len(*k.Keys) == 0 { + return nil, errors.New("The server returned no public key") + } + return &(*k.Keys)[0], nil +} + +// ExportPublicKey returns the public part of a key pair as a [crypto.PublicKey]. +// +// The returned key can then be cast into *rsa.PublicKey or *ecdsa.PublicKey. +func (client *Client) ExportPublicKey(ctx context.Context, keyID uuid.UUID) (crypto.PublicKey, error) { + k, err := client.ExportJwkPublicKey(ctx, keyID) + if err != nil { + return nil, err + } + return k.PublicKey() +} + +// apiClient is the main implementation of KMS rest api http client. +type apiClient struct { inner internal.ClientWithResponsesInterface customHeaders map[string]string } @@ -91,7 +254,7 @@ func DebugTransport(out io.Writer) func(http.RoundTripper) http.RoundTripper { // NewRestAPIClient creates and initializes a new HTTP connection to the KMS at url `endpoint` // using the provided client configuration. It allows configuring retries, timeouts and loggers. -func NewRestAPIClient(endpoint string, clientCfg ClientConfig) (*RestAPIClient, error) { +func NewRestAPIClient(endpoint string, clientCfg ClientConfig) (*Client, error) { client := retryablehttp.NewClient() client.HTTPClient.Timeout = DefaultHTTPClientTimeout client.Logger = nil @@ -127,8 +290,8 @@ func NewRestAPIClient(endpoint string, clientCfg ClientConfig) (*RestAPIClient, // connection to the KMS at url `endpoint` using the provided [http.Client]. // // The client must be configured with an appropriate tls.Config using client TLS certificates for authentication. -func NewRestAPIClientWithHttp(endpoint string, c *http.Client) (*RestAPIClient, error) { - restClient := &RestAPIClient{} +func NewRestAPIClientWithHttp(endpoint string, c *http.Client) (*Client, error) { + restClient := &apiClient{} baseUrl := strings.TrimRight(endpoint, "/") client, err := internal.NewClientWithResponses(baseUrl, internal.WithHTTPClient(c), internal.WithRequestEditorFn(restClient.addRequestHeaders)) @@ -136,7 +299,7 @@ func NewRestAPIClientWithHttp(endpoint string, c *http.Client) (*RestAPIClient, return nil, fmt.Errorf("Failed to initialize KMS REST client: %w", err) } restClient.inner = client - return restClient, nil + return &Client{restClient}, nil } // InternalHttpClient is the low level, internal http client generated by oapi-codegen. @@ -145,21 +308,19 @@ type InternalHttpClient = internal.Client // GetInternalClient returns the internal client wrapped. // It is an escape hatch to some low level features. Use at your own risk. -func (client *RestAPIClient) GetInternalClient() *InternalHttpClient { +func (client *apiClient) GetInternalClient() *InternalHttpClient { c := client.inner.(*internal.ClientWithResponses) return c.ClientInterface.(*internal.Client) } -// WithCustomHeader adds additional HTTP headers that will be sent with every outgoing requests. -func (client *RestAPIClient) WithCustomHeader(key, value string) *RestAPIClient { +func (client *apiClient) SetCustomHeader(key, value string) { if client.customHeaders == nil { client.customHeaders = make(map[string]string) } client.customHeaders[key] = value - return client } -func (client *RestAPIClient) addRequestHeaders(ctx context.Context, req *http.Request) error { +func (client *apiClient) addRequestHeaders(ctx context.Context, req *http.Request) error { for k, v := range client.customHeaders { req.Header.Set(k, v) } @@ -171,12 +332,12 @@ func (client *RestAPIClient) addRequestHeaders(ctx context.Context, req *http.Re return nil } -// func (client *RestAPIClient) Ping(ctx context.Context) error { +// func (client *apiClient) Ping(ctx context.Context) error { // _, err := client.GenerateRandomBytes(ctx, 1) // return err // } -// func (client *RestAPIClient) GenerateRandomBytes(ctx context.Context, length int) (*types.GetRandomResponse, error) { +// func (client *apiClient) GenerateRandomBytes(ctx context.Context, length int) (*types.GetRandomResponse, error) { // l := int32(length) // r, err := mapRestErr(client.inner.GenerateRandomBytesWithResponse(ctx, &types.GenerateRandomBytesParams{Length: &l})) // if err != nil { @@ -186,7 +347,7 @@ func (client *RestAPIClient) addRequestHeaders(ctx context.Context, req *http.Re // } // GetServiceKey returns a key metadata. If format is not nil, then the public key material is also returned. -func (client *RestAPIClient) GetServiceKey(ctx context.Context, keyId uuid.UUID, format *types.KeyFormats) (*types.GetServiceKeyResponse, error) { +func (client *apiClient) GetServiceKey(ctx context.Context, keyId uuid.UUID, format *types.KeyFormats) (*types.GetServiceKeyResponse, error) { params := &types.GetServiceKeyParams{Format: format} r, err := mapRestErr(client.inner.GetServiceKeyWithResponse(ctx, keyId, params)) if err != nil { @@ -197,7 +358,7 @@ func (client *RestAPIClient) GetServiceKey(ctx context.Context, keyId uuid.UUID, // ListServiceKeys returns a page of service keys. The response contains a continuationToken that must be passed to the // subsequent calls in order to get the next page. The state parameter when no nil is used to query keys having a specific state. -func (client *RestAPIClient) ListServiceKeys(ctx context.Context, continuationToken *string, maxKeys *int32, state *types.KeyStates) (*types.ListServiceKeysResponse, error) { +func (client *apiClient) ListServiceKeys(ctx context.Context, continuationToken *string, maxKeys *int32, state *types.KeyStates) (*types.ListServiceKeysResponse, error) { params := &types.ListServiceKeysParams{ContinuationToken: continuationToken, Max: maxKeys, State: state} r, err := mapRestErr(client.inner.ListServiceKeysWithResponse(ctx, params)) if err != nil { @@ -206,24 +367,14 @@ func (client *RestAPIClient) ListServiceKeys(ctx context.Context, continuationTo return r.JSON200, err } -// ListAllServiceKeys returns an iterator to go through all the keys without having to deal with pagination. -func (client *RestAPIClient) ListAllServiceKeys(pageSize *int32, state *types.KeyStates) KeyIter { - return KeyIter{ - client: client, - pageSize: pageSize, - buf: nil, - state: state, - } -} - // ActivateServiceKey activates or re-activates a service key. -func (client *RestAPIClient) ActivateServiceKey(ctx context.Context, keyId uuid.UUID) error { +func (client *apiClient) ActivateServiceKey(ctx context.Context, keyId uuid.UUID) error { _, err := mapRestErr(client.inner.ActivateServiceKeyWithResponse(ctx, keyId)) return err } // UpdateServiceKey updates some service key metadata. -func (client *RestAPIClient) UpdateServiceKey(ctx context.Context, keyId uuid.UUID, body types.PatchServiceKeyRequest) (*types.GetServiceKeyResponse, error) { +func (client *apiClient) UpdateServiceKey(ctx context.Context, keyId uuid.UUID, body types.PatchServiceKeyRequest) (*types.GetServiceKeyResponse, error) { r, err := mapRestErr(client.inner.PatchServiceKeyWithResponse(ctx, keyId, body)) if err != nil { return nil, err @@ -231,8 +382,8 @@ func (client *RestAPIClient) UpdateServiceKey(ctx context.Context, keyId uuid.UU return r.JSON200, err } -// CreateImportServiceKey is used to either generate a new key securely in the KMS, or to import a plain key into the KMS domain. -func (client *RestAPIClient) CreateImportServiceKey(ctx context.Context, format *types.KeyFormats, body types.CreateImportServiceKeyRequest) (*types.GetServiceKeyResponse, error) { +// CreateImportServiceKey is the low level API used to either generate a new key securely in the KMS, or to import a plain key into the KMS domain. +func (client *apiClient) CreateImportServiceKey(ctx context.Context, format *types.KeyFormats, body types.CreateImportServiceKeyRequest) (*types.GetServiceKeyResponse, error) { r, err := mapRestErr(client.inner.CreateImportServiceKeyWithResponse(ctx, &types.CreateImportServiceKeyParams{Format: format}, body)) if err != nil { return nil, err @@ -241,20 +392,20 @@ func (client *RestAPIClient) CreateImportServiceKey(ctx context.Context, format } // DeactivateServiceKey deactivates a service key with the given deactivation reason. -func (client *RestAPIClient) DeactivateServiceKey(ctx context.Context, keyId uuid.UUID, reason types.RevocationReasons) error { +func (client *apiClient) DeactivateServiceKey(ctx context.Context, keyId uuid.UUID, reason types.RevocationReasons) error { _, err := mapRestErr(client.inner.DeactivateServiceKeyWithResponse(ctx, keyId, types.DeactivateServicekeyRequest{Reason: reason})) return err } // DeleteServiceKey deletes a deactivated service key. The key cannot be recovered after deletion. // It will fail if the key is not deactivated. -func (client *RestAPIClient) DeleteServiceKey(ctx context.Context, keyId uuid.UUID) error { +func (client *apiClient) DeleteServiceKey(ctx context.Context, keyId uuid.UUID) error { _, err := mapRestErr(client.inner.DeleteServiceKeyWithResponse(ctx, keyId)) return err } // DecryptDataKey decrypts a JWE encrypted data key protected by the service key with the ID `keyId`. -func (client *RestAPIClient) DecryptDataKey(ctx context.Context, keyId uuid.UUID, encryptedKey string) ([]byte, error) { +func (client *apiClient) DecryptDataKey(ctx context.Context, keyId uuid.UUID, encryptedKey string) ([]byte, error) { r, err := mapRestErr(client.inner.DecryptDataKeyWithResponse(ctx, keyId, types.DecryptDataKeyRequest{Key: encryptedKey})) if err != nil { return nil, err @@ -267,7 +418,7 @@ func (client *RestAPIClient) DecryptDataKey(ctx context.Context, keyId uuid.UUID // GenerateDataKey creates a new data key of the given size, protected by the service key with the ID `keyId`. // It returns the plain datakey, and the JWE encrypted version of it, which can be decrypted by calling the DecryptDataKey method. -func (client *RestAPIClient) GenerateDataKey(ctx context.Context, keyId uuid.UUID, name string, size int32) (plain []byte, encrypted string, err error) { +func (client *apiClient) GenerateDataKey(ctx context.Context, keyId uuid.UUID, name string, size int32) (plain []byte, encrypted string, err error) { req := types.GenerateDataKeyRequest{Size: size} if name != "" { req.Name = &name @@ -283,7 +434,7 @@ func (client *RestAPIClient) GenerateDataKey(ctx context.Context, keyId uuid.UUI } // Decrypt decrypts JWE `data` previously encrypted with the remote symmetric key having the ID `keyId`. -func (client *RestAPIClient) Decrypt(ctx context.Context, keyId uuid.UUID, keyCtx, data string) ([]byte, error) { +func (client *apiClient) Decrypt(ctx context.Context, keyId uuid.UUID, keyCtx, data string) ([]byte, error) { req := types.DecryptRequest{Ciphertext: data} if keyCtx != "" { req.Context = &keyCtx @@ -296,7 +447,7 @@ func (client *RestAPIClient) Decrypt(ctx context.Context, keyId uuid.UUID, keyCt } // Encrypt encrypts `data` with the remote symmetric key having the ID `keyId`. Returns a JWE (Json Web Encryption) string. -func (client *RestAPIClient) Encrypt(ctx context.Context, keyId uuid.UUID, keyCtx string, data []byte) (string, error) { +func (client *apiClient) Encrypt(ctx context.Context, keyId uuid.UUID, keyCtx string, data []byte) (string, error) { req := types.EncryptRequest{Plaintext: data} if keyCtx != "" { req.Context = &keyCtx @@ -309,7 +460,7 @@ func (client *RestAPIClient) Encrypt(ctx context.Context, keyId uuid.UUID, keyCt } // Sign signs the given message with the remote private key having the ID `keyId`. The message can be pre-hashed or not. -func (client *RestAPIClient) Sign(ctx context.Context, keyId uuid.UUID, alg types.DigitalSignatureAlgorithms, preHashed bool, msg []byte) (string, error) { +func (client *apiClient) Sign(ctx context.Context, keyId uuid.UUID, alg types.DigitalSignatureAlgorithms, preHashed bool, msg []byte) (string, error) { req := types.SignRequest{ Alg: alg, Isdigest: &preHashed, @@ -326,7 +477,7 @@ func (client *RestAPIClient) Sign(ctx context.Context, keyId uuid.UUID, alg type } // Verify checks the signature of given message against the remote public key having the ID `keyId`. The message can be pre-hashed or not. -func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg types.DigitalSignatureAlgorithms, preHashed bool, msg []byte, sig string) (bool, error) { +func (client *apiClient) Verify(ctx context.Context, keyId uuid.UUID, alg types.DigitalSignatureAlgorithms, preHashed bool, msg []byte, sig string) (bool, error) { req := types.VerifyRequest{ Alg: alg, Isdigest: &preHashed, @@ -340,22 +491,22 @@ func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg ty return r.JSON200.Result, nil } -// func (client *RestAPIClient) DeleteSecretMetadata(ctx context.Context, path string) error { +// func (client *apiClient) DeleteSecretMetadata(ctx context.Context, path string) error { // _, err := mapRestErr(client.inner.DeleteSecretMetadataWithResponse(ctx, path)) // return err // } -// func (client *RestAPIClient) DeleteSecretRequest(ctx context.Context, path string) error { +// func (client *apiClient) DeleteSecretRequest(ctx context.Context, path string) error { // _, err := mapRestErr(client.inner.DeleteSecretRequestWithResponse(ctx, path)) // return err // } -// func (client *RestAPIClient) DeleteSecretVersions(ctx context.Context, path string, versions []int32) error { +// func (client *apiClient) DeleteSecretVersions(ctx context.Context, path string, versions []int32) error { // _, err := mapRestErr(client.inner.DeleteSecretVersionsWithResponse(ctx, path, types.SecretVersionsRequest{Versions: versions})) // return err // } -// func (client *RestAPIClient) GetSecretConfig(ctx context.Context) (*types.GetConfigResponse, error) { +// func (client *apiClient) GetSecretConfig(ctx context.Context) (*types.GetConfigResponse, error) { // r, err := mapRestErr(client.inner.GetSecretConfigWithResponse(ctx)) // if err != nil { // return nil, err @@ -363,7 +514,7 @@ func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg ty // return r.JSON200, err // } -// func (client *RestAPIClient) GetSecretRequest(ctx context.Context, path string, version *int32) (*types.GetSecretResponse, error) { +// func (client *apiClient) GetSecretRequest(ctx context.Context, path string, version *int32) (*types.GetSecretResponse, error) { // r, err := mapRestErr(client.inner.GetSecretRequestWithResponse(ctx, path, &types.GetSecretRequestParams{Version: version})) // if err != nil { // return nil, err @@ -371,7 +522,7 @@ func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg ty // return r.JSON200, err // } -// func (client *RestAPIClient) GetSecretSubkeys(ctx context.Context, path string, depth, version *int32) (*types.GetSecretSubkeysResponse, error) { +// func (client *apiClient) GetSecretSubkeys(ctx context.Context, path string, depth, version *int32) (*types.GetSecretSubkeysResponse, error) { // r, err := mapRestErr(client.inner.GetSecretSubkeysWithResponse(ctx, path, &types.GetSecretSubkeysParams{Depth: depth, Version: version})) // if err != nil { // return nil, err @@ -379,7 +530,7 @@ func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg ty // return r.JSON200, err // } -// func (client *RestAPIClient) GetSecretsMetadata(ctx context.Context, path string, list bool) (*types.GetMetadataResponse, error) { +// func (client *apiClient) GetSecretsMetadata(ctx context.Context, path string, list bool) (*types.GetMetadataResponse, error) { // r, err := mapRestErr(client.inner.GetSecretsMetadataWithResponse(ctx, path, &types.GetSecretsMetadataParams{List: &list})) // if err != nil { // return nil, err @@ -387,12 +538,12 @@ func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg ty // return r.JSON200, err // } -// func (client *RestAPIClient) PatchSecretMetadata(ctx context.Context, path string, body types.SecretUpdatableMetadata) error { +// func (client *apiClient) PatchSecretMetadata(ctx context.Context, path string, body types.SecretUpdatableMetadata) error { // _, err := mapRestErr(client.inner.PatchSecretMetadataWithResponse(ctx, path, body)) // return err // } -// func (client *RestAPIClient) PatchSecretRequest(ctx context.Context, path string, body types.PostSecretRequest) (*types.PatchSecretResponse, error) { +// func (client *apiClient) PatchSecretRequest(ctx context.Context, path string, body types.PostSecretRequest) (*types.PatchSecretResponse, error) { // r, err := mapRestErr(client.inner.PatchSecretRequestWithResponse(ctx, path, body)) // if err != nil { // return nil, err @@ -400,22 +551,22 @@ func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg ty // return r.JSON200, err // } -// func (client *RestAPIClient) PostSecretConfig(ctx context.Context, body types.PostConfigRequest) error { +// func (client *apiClient) PostSecretConfig(ctx context.Context, body types.PostConfigRequest) error { // _, err := mapRestErr(client.inner.PostSecretConfigWithResponse(ctx, body)) // return err // } -// func (client *RestAPIClient) PostSecretDestroy(ctx context.Context, path string, versions []int32) error { +// func (client *apiClient) PostSecretDestroy(ctx context.Context, path string, versions []int32) error { // _, err := mapRestErr(client.inner.PostSecretDestroyWithResponse(ctx, path, types.SecretVersionsRequest{Versions: versions})) // return err // } -// func (client *RestAPIClient) PostSecretMetadata(ctx context.Context, path string, body types.SecretUpdatableMetadata) error { +// func (client *apiClient) PostSecretMetadata(ctx context.Context, path string, body types.SecretUpdatableMetadata) error { // _, err := mapRestErr(client.inner.PostSecretMetadataWithResponse(ctx, path, body)) // return err // } -// func (client *RestAPIClient) PostSecretRequest(ctx context.Context, path string, body types.PostSecretRequest) (*types.PostSecretResponse, error) { +// func (client *apiClient) PostSecretRequest(ctx context.Context, path string, body types.PostSecretRequest) (*types.PostSecretResponse, error) { // r, err := mapRestErr(client.inner.PostSecretRequestWithResponse(ctx, path, body)) // if err != nil { // return nil, err @@ -423,7 +574,7 @@ func (client *RestAPIClient) Verify(ctx context.Context, keyId uuid.UUID, alg ty // return r.JSON200, err // } -// func (client *RestAPIClient) PostSecretUndelete(ctx context.Context, path string, versions []int32) error { +// func (client *apiClient) PostSecretUndelete(ctx context.Context, path string, versions []int32) error { // _, err := mapRestErr(client.inner.PostSecretUndeleteWithResponse(ctx, path, types.SecretVersionsRequest{Versions: versions})) // return err // } diff --git a/datakey.go b/datakey.go index 0f73676..cd9df39 100644 --- a/datakey.go +++ b/datakey.go @@ -20,9 +20,15 @@ import ( "math" "github.com/google/uuid" + "github.com/ovh/okms-sdk-go/internal/utils" "github.com/ovh/okms-sdk-go/internal/xcrypto" ) +// DataKeys creates a new datakey provider for the given service key. +func (client *Client) DataKeys(serviceKeyID uuid.UUID) *DataKeyProvider { + return newDataKeyProvider(client, serviceKeyID) +} + // DataKeyProvider is a helper provider that wraps an API client // and provides helpers functions to repeatedly generate or decrypt datakeys // protected by the same service key. @@ -33,9 +39,9 @@ type DataKeyProvider struct { keyId uuid.UUID } -// NewDataKeyProvider creates a new datakey provider for the given service key, +// newDataKeyProvider creates a new datakey provider for the given service key, // using the given [DataKeyApi] api client. -func NewDataKeyProvider(api DataKeyApi, keyId uuid.UUID) *DataKeyProvider { +func newDataKeyProvider(api DataKeyApi, keyId uuid.UUID) *DataKeyProvider { return &DataKeyProvider{ api: api, keyId: keyId, @@ -52,8 +58,7 @@ func (sk *DataKeyProvider) GenerateDataKey(ctx context.Context, name string, siz return nil, nil, errors.New("key size is out of bound") } // Let's first ask the KMS to generate a new DK - //nolint:gosec // integer bounds are checked right before - plain, encryptedKey, err := sk.api.GenerateDataKey(ctx, sk.keyId, name, int32(size)) + plain, encryptedKey, err := sk.api.GenerateDataKey(ctx, sk.keyId, name, utils.ToInt32(size)) if err != nil { return nil, nil, err } diff --git a/errors.go b/errors.go index c50eaf0..f53e1c6 100644 --- a/errors.go +++ b/errors.go @@ -14,6 +14,7 @@ import ( "errors" "fmt" + "github.com/ovh/okms-sdk-go/internal/utils" "github.com/ovh/okms-sdk-go/types" ) @@ -204,7 +205,7 @@ func newKmsErrorFromRestResponse(resp types.ErrorResponse) *KmsError { kmsErr.ErrorId = *resp.ErrorId } if resp.ErrorCode != nil { - kmsErr.ErrorCode = ErrorCode(*resp.ErrorCode) + kmsErr.ErrorCode = ErrorCode(utils.ToUint32(*resp.ErrorCode)) } if resp.Errors != nil { for _, er := range *resp.Errors { diff --git a/example_test.go b/example_test.go index 8acbc7e..4a64d56 100644 --- a/example_test.go +++ b/example_test.go @@ -48,8 +48,8 @@ func ExampleNewRestAPIClientWithHttp() { } // Generate an 256 bits AES key -func ExampleRestAPIClient_CreateImportServiceKey_generateAES() { - var kmsClient *okms.RestAPIClient // Initialize client +func ExampleClient_CreateImportServiceKey_generateAES() { + var kmsClient *okms.Client // Initialize client kType := types.Oct kSize := types.N256 ops := []types.CryptographicUsages{types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey} @@ -67,13 +67,13 @@ func ExampleRestAPIClient_CreateImportServiceKey_generateAES() { } // Generate a 2048 bits RSA key pair -func ExampleRestAPIClient_CreateImportServiceKey_generateRSA() { - var kmsClient *okms.RestAPIClient // Initialize client +func ExampleClient_CreateImportServiceKey_generateRSA() { + var kmsClient *okms.Client // Initialize client kType := types.RSA kSize := types.N2048 ops := []types.CryptographicUsages{types.Sign, types.Verify} // Create a new RSA 2048 key-pair - respAes, err := kmsClient.CreateImportServiceKey(context.Background(), nil, types.CreateImportServiceKeyRequest{ + respRSA, err := kmsClient.CreateImportServiceKey(context.Background(), nil, types.CreateImportServiceKeyRequest{ Name: "RSA key-pair example", Type: &kType, Size: &kSize, @@ -82,17 +82,17 @@ func ExampleRestAPIClient_CreateImportServiceKey_generateRSA() { if err != nil { panic(err) } - fmt.Println("RSA KEY:", respAes.Id) + fmt.Println("RSA KEY:", respRSA.Id) } // Generate an ECDSA key pair on the P-256 curve -func ExampleRestAPIClient_CreateImportServiceKey_generateECDSA() { - var kmsClient *okms.RestAPIClient // Initialize client +func ExampleClient_CreateImportServiceKey_generateECDSA() { + var kmsClient *okms.Client // Initialize client kType := types.EC curve := types.P256 ops := []types.CryptographicUsages{types.Sign, types.Verify} // Create a new ECDSA P-256 key-pair - respAes, err := kmsClient.CreateImportServiceKey(context.Background(), nil, types.CreateImportServiceKeyRequest{ + respEC, err := kmsClient.CreateImportServiceKey(context.Background(), nil, types.CreateImportServiceKeyRequest{ Name: "ECDSA key-pair example", Type: &kType, Curve: &curve, @@ -101,12 +101,12 @@ func ExampleRestAPIClient_CreateImportServiceKey_generateECDSA() { if err != nil { panic(err) } - fmt.Println("ECDSA KEY:", respAes.Id) + fmt.Println("ECDSA KEY:", respEC.Id) } -func ExampleRestAPIClient_Sign() { - var kmsClient *okms.RestAPIClient // Initialize client - data := "Hello World !!!" // Data to sign +func ExampleClient_Sign() { + var kmsClient *okms.Client // Initialize client + data := "Hello World !!!" // Data to sign signResponse, err := kmsClient.Sign(context.Background(), uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58"), types.ES256, false, []byte(data)) if err != nil { panic(err) @@ -114,10 +114,10 @@ func ExampleRestAPIClient_Sign() { fmt.Println("Signature:", signResponse) } -func ExampleRestAPIClient_Verify() { - var kmsClient *okms.RestAPIClient // Initialize client - var signature string // Base64 encoded signature - data := "Hello World !!!" // Data to sign +func ExampleClient_Verify() { + var kmsClient *okms.Client // Initialize client + var signature string // Base64 encoded signature + data := "Hello World !!!" // Data to sign result, err := kmsClient.Verify(context.Background(), uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58"), types.ES256, false, []byte(data), signature) if err != nil { panic(err) @@ -126,10 +126,10 @@ func ExampleRestAPIClient_Verify() { } func ExampleDataKeyProvider_helpers() { - var kmsClient *okms.RestAPIClient // Initialize client + var kmsClient *okms.Client // Initialize client data := "Hello World !!!" // Data to encrypt - dkProvider := okms.NewDataKeyProvider(kmsClient, uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58")) + dkProvider := kmsClient.DataKeys(uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58")) // Unless you want to use another algorithm than AES-GCM 256 bits, you can use the 2 following helper methods: encryptedData, encryptedKey, nonce, err := dkProvider.EncryptGCM(context.Background(), "Example DK", []byte(data), []byte("Some additional data")) @@ -145,9 +145,9 @@ func ExampleDataKeyProvider_helpers() { } func ExampleDataKeyProvider_GenerateDataKey() { - var kmsClient *okms.RestAPIClient // Initialize client - data := "Hello World !!!" // Data to encrypt - dkProvider := okms.NewDataKeyProvider(kmsClient, uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58")) + var kmsClient *okms.Client // Initialize client + data := "Hello World !!!" // Data to encrypt + dkProvider := kmsClient.DataKeys(uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58")) // Generate a new datakey plain, encrypted, err := dkProvider.GenerateDataKey(context.Background(), "Example DK", 256) @@ -178,11 +178,11 @@ func ExampleDataKeyProvider_GenerateDataKey() { } func ExampleDataKeyProvider_DecryptDataKey() { - var kmsClient *okms.RestAPIClient // Initialize client - var encryptedData []byte // Some encrypted data - var encryptedKey []byte // Encrypted datakey - var nonce []byte // Nonce used for data encryption - dkProvider := okms.NewDataKeyProvider(kmsClient, uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58")) + var kmsClient *okms.Client // Initialize client + var encryptedData []byte // Some encrypted data + var encryptedKey []byte // Encrypted datakey + var nonce []byte // Nonce used for data encryption + dkProvider := kmsClient.DataKeys(uuid.MustParse("2dab95dc-d7d3-482b-a07b-6b4dfae89d58")) // Decrypt data key plain, err := dkProvider.DecryptDataKey(context.Background(), encryptedKey) diff --git a/examples/datakeys.go b/examples/datakeys.go index cf7c97c..c99d770 100644 --- a/examples/datakeys.go +++ b/examples/datakeys.go @@ -23,21 +23,16 @@ import ( "github.com/ovh/okms-sdk-go/types" ) -func dataKeyEncryptDecrypt(ctx context.Context, kmsClient okms.Client) { +func dataKeyEncryptDecrypt(ctx context.Context, kmsClient *okms.Client) { // Create a new AES 256 key - respAes, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "AES key example", - Type: ptrTo(types.Oct), - Size: ptrTo(types.N256), - Operations: ptrTo([]types.CryptographicUsages{types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey}), - }) + respAes, err := kmsClient.GenerateSymmetricKey(ctx, types.N256, "AES key example", "", types.WrapKey, types.UnwrapKey) if err != nil { panic(err) } data := "Hello World !!!" // Data to encrypt - dkProvider := okms.NewDataKeyProvider(kmsClient, respAes.Id) + dkProvider := kmsClient.DataKeys(respAes.Id) // ENCRYPTION @@ -108,19 +103,14 @@ func dataKeyEncryptDecrypt(ctx context.Context, kmsClient okms.Client) { fmt.Println("Decrypted:", string(plainData)) } -func dataKeyEncryptStream(ctx context.Context, kmsClient okms.Client) { +func dataKeyEncryptStream(ctx context.Context, kmsClient *okms.Client) { // Create a new AES 256 key - respAes, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "AES key example", - Type: ptrTo(types.Oct), - Size: ptrTo(types.N256), - Operations: ptrTo([]types.CryptographicUsages{types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey}), - }) + respAes, err := kmsClient.GenerateSymmetricKey(ctx, types.N256, "AES key example", "", types.WrapKey, types.UnwrapKey) if err != nil { panic(err) } - dkProvider := okms.NewDataKeyProvider(kmsClient, respAes.Id) + dkProvider := kmsClient.DataKeys(respAes.Id) sourceFile, err := os.Open("10GB_Plain_File.txt") if err != nil { @@ -146,19 +136,14 @@ func dataKeyEncryptStream(ctx context.Context, kmsClient okms.Client) { } } -func dataKeyDecryptStream(ctx context.Context, kmsClient okms.Client) { +func dataKeyDecryptStream(ctx context.Context, kmsClient *okms.Client) { // Create a new AES 256 key - respAes, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "AES key example", - Type: ptrTo(types.Oct), - Size: ptrTo(types.N256), - Operations: ptrTo([]types.CryptographicUsages{types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey}), - }) + respAes, err := kmsClient.GenerateSymmetricKey(ctx, types.N256, "AES key example", "", types.WrapKey, types.UnwrapKey) if err != nil { panic(err) } - dkProvider := okms.NewDataKeyProvider(kmsClient, respAes.Id) + dkProvider := kmsClient.DataKeys(respAes.Id) sourceFile, err := os.Create("Encrypted_File.bin") if err != nil { diff --git a/examples/encrypt_decrypt.go b/examples/encrypt_decrypt.go index 226cad2..8163186 100644 --- a/examples/encrypt_decrypt.go +++ b/examples/encrypt_decrypt.go @@ -17,14 +17,9 @@ import ( "github.com/ovh/okms-sdk-go/types" ) -func encryptDecrypt(ctx context.Context, kmsClient okms.Client) { +func encryptDecrypt(ctx context.Context, kmsClient *okms.Client) { // Create a new AES 256 key - respAes, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "AES key example", - Type: ptrTo(types.Oct), - Size: ptrTo(types.N256), - Operations: ptrTo([]types.CryptographicUsages{types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey}), - }) + respAes, err := kmsClient.GenerateSymmetricKey(ctx, types.N256, "AES key example", "", types.Encrypt, types.Decrypt) if err != nil { panic(err) } diff --git a/examples/generate_keys.go b/examples/generate_keys.go index 93f2e3d..13c8613 100644 --- a/examples/generate_keys.go +++ b/examples/generate_keys.go @@ -17,38 +17,23 @@ import ( "github.com/ovh/okms-sdk-go/types" ) -func generateKeys(ctx context.Context, kmsClient okms.Client) { +func generateKeys(ctx context.Context, kmsClient *okms.Client) { // Create a new AES 256 key - respAes, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "AES key example", - Type: ptrTo(types.Oct), - Size: ptrTo(types.N256), - Operations: ptrTo([]types.CryptographicUsages{types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey}), - }) + respAes, err := kmsClient.GenerateSymmetricKey(ctx, types.N256, "AES key example", "", types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey) if err != nil { panic(err) } fmt.Println("AES KEY:", respAes.Id) // Create a new RSA 2048 key-pair - respRSA, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "RSA key-pair example", - Type: ptrTo(types.RSA), - Size: ptrTo(types.N2048), - Operations: ptrTo([]types.CryptographicUsages{types.Sign, types.Verify}), - }) + respRSA, err := kmsClient.GenerateRSAKeyPair(ctx, types.N2048, "RSA key-pair example", "", types.Sign, types.Verify) if err != nil { panic(err) } fmt.Println("RSA KEY:", respRSA.Id) // Create a new ECDSA P-256 key-pair - respECDSA, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "ECDSA key-pair example", - Type: ptrTo(types.EC), - Curve: ptrTo(types.P256), - Operations: ptrTo([]types.CryptographicUsages{types.Sign, types.Verify}), - }) + respECDSA, err := kmsClient.GenerateECKeyPair(ctx, types.P256, "ECDSA key-pair example", "", types.Sign, types.Verify) if err != nil { panic(err) } diff --git a/examples/list_get.go b/examples/list_get.go index afbde17..fdfdf71 100644 --- a/examples/list_get.go +++ b/examples/list_get.go @@ -17,7 +17,7 @@ import ( "github.com/ovh/okms-sdk-go/types" ) -func listKeys(ctx context.Context, kmsClient okms.Client) { +func listKeys(ctx context.Context, kmsClient *okms.Client) { it := kmsClient.ListAllServiceKeys(nil, nil) for it.Next(ctx) { key, err := it.Value() @@ -36,14 +36,9 @@ func listKeys(ctx context.Context, kmsClient okms.Client) { } } -func getKey(ctx context.Context, kmsClient okms.Client) { +func getKey(ctx context.Context, kmsClient *okms.Client) { // Create a new AES 256 key - respAes, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "AES key example", - Type: ptrTo(types.Oct), - Size: ptrTo(types.N256), - Operations: ptrTo([]types.CryptographicUsages{types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey}), - }) + respAes, err := kmsClient.GenerateSymmetricKey(ctx, types.N256, "AES key example", "", types.Encrypt, types.Decrypt, types.WrapKey, types.UnwrapKey) if err != nil { panic(err) } diff --git a/examples/sign_verify.go b/examples/sign_verify.go index 3ad2bef..295d910 100644 --- a/examples/sign_verify.go +++ b/examples/sign_verify.go @@ -21,14 +21,9 @@ import ( "github.com/ovh/okms-sdk-go/types" ) -func signVerify(ctx context.Context, kmsClient okms.Client) { +func signVerify(ctx context.Context, kmsClient *okms.Client) { // Create a new ECDSA P-256 key-pair. Sign / Verify also works with RSA keys - respECDSA, err := kmsClient.CreateImportServiceKey(ctx, nil, types.CreateImportServiceKeyRequest{ - Name: "ECDSA key-pair example", - Type: ptrTo(types.EC), - Curve: ptrTo(types.P256), - Operations: ptrTo([]types.CryptographicUsages{types.Sign, types.Verify}), - }) + respECDSA, err := kmsClient.GenerateECKeyPair(ctx, types.P256, "ECDSA key-pair example", "", types.Sign, types.Verify) if err != nil { panic(err) } @@ -47,15 +42,10 @@ func signVerify(ctx context.Context, kmsClient okms.Client) { fmt.Println("Is valid:", result) // You can also instantiate an stdlib crypto.Signer - pubKey, err := kmsClient.GetServiceKey(ctx, respECDSA.Id, ptrTo(types.Jwk)) + signer, err := kmsClient.NewSigner(ctx, respECDSA.Id) if err != nil { panic(err) } - signer, err := okms.NewSigner(kmsClient, (*pubKey.Keys)[0]) - if err != nil { - panic(err) - } - digest := sha256.Sum256([]byte(data)) signature, err := signer.Sign(rand.Reader, digest[:], crypto.SHA256) if err != nil { diff --git a/go.mod b/go.mod index 51f9d76..6efd19f 100644 --- a/go.mod +++ b/go.mod @@ -16,5 +16,6 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect + golang.org/x/sys v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 26cd9c6..851cd72 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/utils/int.go b/internal/utils/int.go index fc7257a..332d40b 100644 --- a/internal/utils/int.go +++ b/internal/utils/int.go @@ -18,6 +18,16 @@ type Integer interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr } +// ToInt32 safely casts the given integer to int32 +// by checking bounds and panicking if overflow occures. +// It's used to avoid unexpected issues when casting integers. +func ToInt32[N Integer](n N) int32 { + if (n > 0 && uint64(n) > math.MaxInt32) || (n < 0 && int64(n) < math.MinInt32) { + panic("Integer overflow") + } + return int32(n) +} + // ToUint32 safely casts the given integer to uint32 // by checking bounds and panicking if overflow occures. // It's used to avoid unexpected issues when casting integers. diff --git a/internal/utils/int_test.go b/internal/utils/int_test.go index 7265253..947f0ae 100644 --- a/internal/utils/int_test.go +++ b/internal/utils/int_test.go @@ -16,6 +16,24 @@ import ( "github.com/stretchr/testify/assert" ) +func TestToInt32(t *testing.T) { + assert.Equal(t, int32(12), ToInt32(int8(12))) + assert.Equal(t, int32(-12), ToInt32(int8(-12))) + assert.Equal(t, int32(12), ToInt32(uint8(12))) + assert.Equal(t, int32(12), ToInt32(int64(12))) + assert.Equal(t, int32(12), ToInt32(uint64(12))) + assert.Equal(t, int32(0), ToInt32(int64(0))) + assert.Equal(t, int32(0), ToInt32(uint64(0))) + + assert.Panics(t, func() { + ToInt32(uint32(math.MaxInt32 + 1)) + }) + + assert.Panics(t, func() { + ToInt32(int64(math.MinInt32 - 1)) + }) +} + func TestToUint32(t *testing.T) { assert.Equal(t, uint32(12), ToUint32(int8(12))) assert.Equal(t, uint32(12), ToUint32(uint8(12))) diff --git a/iter.go b/iter.go index abc4807..3a6601d 100644 --- a/iter.go +++ b/iter.go @@ -16,6 +16,16 @@ import ( "github.com/ovh/okms-sdk-go/types" ) +// ListAllServiceKeys returns an iterator to go through all the keys without having to deal with pagination. +func (client *Client) ListAllServiceKeys(pageSize *int32, state *types.KeyStates) KeyIter { + return KeyIter{ + client: client.API, + pageSize: pageSize, + buf: nil, + state: state, + } +} + // KeyIter is an iterator for service keys. It helps in iterating efficiently over multiple pages // without having to deal with the pagination. type KeyIter struct { diff --git a/signer.go b/signer.go index a6f575f..4465c1a 100644 --- a/signer.go +++ b/signer.go @@ -24,11 +24,22 @@ import ( "golang.org/x/crypto/cryptobyte/asn1" ) -// NewSigner creates a new [crypto.Signer] using the given public JsonWebKey and -// its remote private key. +// NewSigner creates a new [crypto.Signer] for the given key-pair. // // NewSigner cannot be used with symetric keys. -func NewSigner(api SignatureApi, jwk types.JsonWebKey) (crypto.Signer, error) { +func (client *Client) NewSigner(ctx context.Context, serviceKeyID uuid.UUID) (crypto.Signer, error) { + k, err := client.ExportJwkPublicKey(ctx, serviceKeyID) + if err != nil { + return nil, err + } + return newSigner(client, k) +} + +// newSigner creates a new [crypto.Signer] using the given public JsonWebKey and +// its remote private key. +// +// newSigner cannot be used with symetric keys. +func newSigner(api SignatureApi, jwk *types.JsonWebKey) (crypto.Signer, error) { pubKey, err := jwk.PublicKey() if err != nil { return nil, err @@ -42,7 +53,7 @@ func NewSigner(api SignatureApi, jwk types.JsonWebKey) (crypto.Signer, error) { } type jwkSigner struct { - types.JsonWebKey + *types.JsonWebKey api SignatureApi pubKey crypto.PublicKey } @@ -88,7 +99,7 @@ func (sign *jwkSigner) signRsaPkcs15(digest []byte, hash crypto.Hash) ([]byte, e func (sign *jwkSigner) signRsaPss(digest []byte, opts *rsa.PSSOptions) ([]byte, error) { // The size of the salt value is the same size as the hash function output as defined in https://www.rfc-editor.org/rfc/rfc7518#section-3.5 - if opts.SaltLength != opts.Hash.Size() { + if opts.SaltLength != rsa.PSSSaltLengthAuto && opts.SaltLength != rsa.PSSSaltLengthEqualsHash && opts.SaltLength != opts.Hash.Size() { return nil, errors.New("Invalid PSS salt length") } return sign.doSign(digest, opts.HashFunc(), "PS") diff --git a/types/ext_jwk.go b/types/ext_jwk.go index 385cd42..dbb85c8 100644 --- a/types/ext_jwk.go +++ b/types/ext_jwk.go @@ -7,6 +7,7 @@ // ANY KIND, either express or implied. See the License for the specific language // governing permissions and limitations under the License. +// Package types holds the REST API type definitions, including requests, responses, and enums. package types import (