registry/cmd/publisher/auth/azurekeyvault/common.go
Joel Verhagen 88c2e1be16
Enable Google KMS and Azure Key Vault for publisher login tool (#696)
<!-- Provide a brief summary of your changes -->

## Motivation and Context

This allows the private key used for DNS or HTTP based authentication to
be stored securely in a cloud key management system. These services
often provide HSM storage which makes it very hard to leak the private
key.

The full context is described in my design document here:

https://github.com/modelcontextprotocol/registry/issues/482#issuecomment-3437117171

## How Has This Been Tested?

I have tested it against PROD using an Ed25519 key stored in Google KMS.
I have added unit tests. I have tested both ECDSA P-384 and Ed25519 with
Az KV and Google KMS against a locally running server.

## Breaking Changes

None intended.

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

---------

Co-authored-by: adam jones <domdomegg+git@gmail.com>
2025-10-29 13:23:20 +00:00

85 lines
2.4 KiB
Go

package azurekeyvault
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha512"
"fmt"
"math/big"
"os"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys"
"github.com/modelcontextprotocol/registry/cmd/publisher/auth"
)
func GetSignatureProvider(vaultName, keyName string) (auth.Signer, error) {
if vaultName == "" {
return nil, fmt.Errorf("--vault option (vault name) is required")
}
if keyName == "" {
return nil, fmt.Errorf("--key option (key name) is required")
}
return Signer{
vaultName: vaultName,
keyName: keyName,
}, nil
}
type Signer struct {
vaultName string
keyName string
}
func (d Signer) GetSignedTimestamp(ctx context.Context) (*string, []byte, error) {
fmt.Fprintf(os.Stdout, "Signing using Azure Key Vault %s and key %s\n", d.vaultName, d.keyName)
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
return nil, nil, fmt.Errorf("authentication to Azure failed: %w", err)
}
vaultURL := fmt.Sprintf("https://%s.vault.azure.net/", d.vaultName)
client, err := azkeys.NewClient(vaultURL, cred, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to create Key Vault client: %w", err)
}
keyResp, err := client.GetKey(ctx, d.keyName, "", nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve key for public parameters: %w", err)
}
if *keyResp.Key.Kty != azkeys.KeyTypeEC && *keyResp.Key.Kty != azkeys.KeyTypeECHSM {
return nil, nil, fmt.Errorf("unsupported key type: kty: %s (only EC or EC-HSM keys are supported)", *keyResp.Key.Kty)
}
if *keyResp.Key.Crv != azkeys.CurveNameP384 {
return nil, nil, fmt.Errorf("unsupported curve: %s (only P-384 is supported)", *keyResp.Key.Crv)
}
fmt.Fprintln(os.Stdout, "Successfully read the public key from Key Vault.")
auth.PrintEcdsaP384KeyInfo(ecdsa.PublicKey{
Curve: elliptic.P384(),
X: new(big.Int).SetBytes(keyResp.Key.X),
Y: new(big.Int).SetBytes(keyResp.Key.Y),
})
timestamp := auth.GetTimestamp()
digest := sha512.Sum384([]byte(timestamp))
alg := azkeys.SignatureAlgorithmES384
fmt.Fprintln(os.Stdout, "Executing the sign request...")
signResp, err := client.Sign(ctx, d.keyName, "", azkeys.SignParameters{
Algorithm: &alg,
Value: digest[:],
}, nil)
if err != nil {
return nil, nil, fmt.Errorf("failed to sign message: %w", err)
}
return &timestamp, signResp.Result, nil
}