<!-- 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>
85 lines
2.4 KiB
Go
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 ×tamp, signResp.Result, nil
|
|
}
|