Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 0 additions & 10 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,3 @@ jobs:

- name: Test
run: go test -v ./... -coverprofile=c.out

- name: Publish code coverage
uses: paambaati/[email protected]
env:
# Set CC_TEST_REPORTER_ID as secret of your repo
CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}}
with:
coverageLocations: |
${{github.workspace}}/c.out:gocov
prefix: github.com/nuts-foundation/go-didx509-toolkit
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,17 @@ docker run --rm -v "$(pwd)/certs:/certs" nutsfoundation/go-didx509-toolkit:main
vc /certs/certificate-chain.pem /certs/key.pem "CN=Fake Root CA" did:web:example.com
```

### Validating `X509Credential`s
### Azure Key Vault Integration

TODO
You can sign using a key stored in Azure Key Vault by specifying the key URL instead of a local file as `signing_key_file`.
The URL may include a version, e.g.:

- Wih version: `https://my-key-vault.vault.azure.net/keys/my-certificate/1234567890abcdef1234567890abcdef`
- Without version: `https://my-key-vault.vault.azure.net/keys/my-certificate`

## Common errors

- `failed to find path from signingCert to root`: This error indicates that the certificate chain provided does not contain a valid path to a root CA. Ensure that the certificate chain is complete and includes all necessary intermediate certificates.

## Limitations

Expand Down
6 changes: 3 additions & 3 deletions credential_issuer/issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package credential_issuer

import (
"context"
"crypto/rsa"
"crypto"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
Expand Down Expand Up @@ -41,7 +41,7 @@ var defaultIssueOptions = &issueOptions{
subjectAttributes: []x509_cert.SubjectTypeName{},
}

func Issue(chain []*x509.Certificate, caFingerprintCert *x509.Certificate, key *rsa.PrivateKey, subject string, optionFns ...Option) (*vc.VerifiableCredential, error) {
func Issue(chain []*x509.Certificate, caFingerprintCert *x509.Certificate, key crypto.Signer, subject string, optionFns ...Option) (*vc.VerifiableCredential, error) {
options := *defaultIssueOptions
for _, fn := range optionFns {
fn(&options)
Expand Down Expand Up @@ -120,7 +120,7 @@ func Issue(chain []*x509.Certificate, caFingerprintCert *x509.Certificate, key *
return "", err
}

sign, err := jwt.Sign(token, jwt.WithKey(jwa.PS256, *key, jws.WithProtectedHeaders(hdrs)))
sign, err := jwt.Sign(token, jwt.WithKey(jwa.PS256, key, jws.WithProtectedHeaders(hdrs)))
return string(sign), err
})
}
Expand Down
15 changes: 13 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module github.com/nuts-foundation/go-didx509-toolkit
go 1.23.1

require (
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0
github.com/alecthomas/kong v1.8.1
github.com/google/uuid v1.6.0
github.com/lestrrat-go/jwx/v2 v2.1.3
Expand All @@ -11,9 +14,14 @@ require (
)

require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
Expand All @@ -23,10 +31,13 @@ require (
github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shengdoushi/base58 v1.0.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
52 changes: 47 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,20 +1,50 @@
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/kong v1.8.1 h1:6aamvWBE/REnR/BCq10EcozmcpUPc5aGI1lPAWdB0EE=
github.com/alecthomas/kong v1.8.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
Expand All @@ -37,8 +67,14 @@ github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivnc
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
github.com/nuts-foundation/go-did v0.15.0 h1:aNl6KC8jiyRJGl9PPKFBboLLC0wUm5h+tjE1UBDQEPw=
github.com/nuts-foundation/go-did v0.15.0/go.mod h1:swjCJvcRxc+i1nyieIERWEb3vFb4N7iYC+qen2OIbNg=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs=
Expand All @@ -48,12 +84,18 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
182 changes: 182 additions & 0 deletions internal/azure/keyvault.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package azure

import (
"context"
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"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/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
"github.com/lestrrat-go/jwx/v2/jwk"
"io"
"net/http"
"os"
"strings"
)

var AzureHttpRequestDoer HttpRequestDoer = http.DefaultClient

type HttpRequestDoer interface {
Do(req *http.Request) (*http.Response, error)
}

func GetSigningKey(ctx context.Context, keyURL string, authType string) (crypto.Signer, error) {
credential, err := createCredential(authType)
if err != nil {
return nil, err
}
vaultURL, keyName, keyVersion, err := parseKeyURL(keyURL)
if err != nil {
return nil, err
}
client, err := azkeys.NewClient(vaultURL, credential, &azkeys.ClientOptions{
ClientOptions: azcore.ClientOptions{
Transport: AzureHttpRequestDoer,
},
})
if err != nil {
return nil, fmt.Errorf("unable to create Azure Key Vault client: %w", err)
}
return getPrivateKey(ctx, client, keyName, keyVersion)
}

func parseKeyURL(keyURL string) (string, string, string, error) {
// URL should be in the form of:
// https://someinstance.vault.azure.net/keys/name(/version, optional)
idx := strings.Index(keyURL, "/keys/")
if idx < 0 {
return "", "", "", fmt.Errorf("invalid Azure Key Vault key URL: %s", keyURL)
}
vaultURL := keyURL[:idx]
keyNameAndVersion := keyURL[idx+6:] // +6 to skip "/keys/"
idx = strings.Index(keyNameAndVersion, "/")
var keyName string
var keyVersion string
if idx < 0 {
keyName = keyNameAndVersion
} else {
keyName = keyNameAndVersion[:idx]
keyVersion = keyNameAndVersion[idx+1:] // +1 to skip the "/"
}
return vaultURL, keyName, keyVersion, nil
}

func createCredential(credentialType string) (azcore.TokenCredential, error) {
switch credentialType {
case "default":
return azidentity.NewDefaultAzureCredential(nil)
case "managed_identity":
opts := &azidentity.ManagedIdentityCredentialOptions{
ClientOptions: azcore.ClientOptions{},
}
// For UserAssignedManagedIdentity, client ID needs to be explicitly set.
// Taken from github.com/!azure/azure-sdk-for-go/sdk/[email protected]/default_azure_credential.go:100
if ID, ok := os.LookupEnv("AZURE_CLIENT_ID"); ok {
opts.ID = azidentity.ClientID(ID)
}
return azidentity.NewManagedIdentityCredential(opts)
default:
return nil, fmt.Errorf("unsupported Azure Key Vault credential type: %s", credentialType)
}
}

func getPrivateKey(ctx context.Context, client *azkeys.Client, keyName string, version string) (crypto.Signer, error) {
response, err := client.GetKey(ctx, keyName, version, nil)
if err != nil {
// other error
return nil, fmt.Errorf("unable to get key from Azure Key Vault (name=%s): %w", keyName, err)
}
publicKey, signingAlgorithm, parsedKeyVersion, err := parseKey(response.Key)
if err != nil {
return nil, err
}
return &azureSigningKey{
ctx: ctx,
client: client,
keyName: keyName,
keyVersion: parsedKeyVersion,
publicKey: publicKey,
signingAlgorithm: signingAlgorithm,
}, nil
}

// parseKey parses an Azure Key Vault key into a crypto.PublicKey and selects the azkeys.SignatureAlgorithm.
func parseKey(key *azkeys.JSONWebKey) (publicKey crypto.PublicKey, keyType azkeys.SignatureAlgorithm, version string, err error) {
jwkData, _ := json.Marshal(key)
keyAsJWK, err := jwk.ParseKey(jwkData)
if err != nil {
err = fmt.Errorf("unable to parse key from Azure Key Vault as JWK: %w", err)
return
}
if err = keyAsJWK.Raw(&publicKey); err != nil {
err = fmt.Errorf("unable to convert key from Azure Key Vault Key to crypto.PublicKey: %w", err)
return
}
if !(*key.Kty == azkeys.KeyTypeRSA) {
err = errors.New("only RSA keys are supported")
return
}
keyType = azkeys.SignatureAlgorithmPS256
if key.KID == nil {
err = errors.New("missing KID in key")
return
}
version = key.KID.Version()
return
}

var _ crypto.Signer = &azureSigningKey{}

type azureSigningKey struct {
ctx context.Context
client keyVaultClient
keyName string
keyVersion string
publicKey crypto.PublicKey
signingAlgorithm azkeys.SignatureAlgorithm
}

// keyVaultClient is an interface for the Azure Key Vault client, to support mocking.
type keyVaultClient interface {
Sign(ctx context.Context, name string, version string, parameters azkeys.SignParameters, options *azkeys.SignOptions) (azkeys.SignResponse, error)
}

func (a azureSigningKey) Public() crypto.PublicKey {
return a.publicKey
}

func (a azureSigningKey) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
// Sanity check
if opts != nil && opts.HashFunc() == 0 {
return nil, errors.New("hashing should've been done")
}
var signingAlgorithm azkeys.SignatureAlgorithm
if pssOpts, ok := opts.(*rsa.PSSOptions); ok {
switch pssOpts.Hash.Size() {
case sha256.Size:
signingAlgorithm = azkeys.SignatureAlgorithmPS256
default:
return nil, fmt.Errorf("unsupported PSS hash size: %d", pssOpts.Hash.Size())
}
} else {
switch opts.HashFunc().Size() {
case sha256.Size:
signingAlgorithm = azkeys.SignatureAlgorithmRS256
default:
return nil, errors.New("unsupported RSA hash size")
}
}
response, err := a.client.Sign(a.ctx, a.keyName, a.keyVersion, azkeys.SignParameters{
Algorithm: to.Ptr(signingAlgorithm),
Value: digest,
}, nil)
if err != nil {
return nil, fmt.Errorf("unable to sign with Azure KeyVault: %w", err)
}
return response.Result, nil
}
Loading
Loading