@@ -8,11 +8,14 @@ import (
88 "crypto/elliptic"
99 "crypto/rand"
1010 "crypto/sha512"
11+ "encoding/base64"
1112 "encoding/hex"
1213 "encoding/json"
1314 "fmt"
1415 "io"
1516 "net/http"
17+ "os"
18+ "strings"
1619 "time"
1720)
1821
@@ -29,75 +32,103 @@ const (
2932
3033// CryptoProvider provides common functionality for DNS and HTTP authentication
3134type CryptoProvider struct {
32- registryURL string
33- domain string
34- privateKey string
35- cryptoAlgorithm CryptoAlgorithm
36- authMethod string
35+ registryURL string
36+ domain string
37+ signer Signer
38+ authMethod string
3739}
3840
39- // GetToken retrieves the registry JWT token using cryptographic authentication
40- func (c * CryptoProvider ) GetToken (ctx context.Context ) (string , error ) {
41- if c .domain == "" {
42- return "" , fmt .Errorf ("%s domain is required" , c .authMethod )
43- }
41+ type Signer interface {
42+ GetSignedTimestamp (ctx context.Context ) (* string , []byte , error )
43+ }
44+
45+ func GetTimestamp () string {
46+ return time .Now ().UTC ().Format (time .RFC3339 )
47+ }
4448
45- if c .privateKey == "" {
46- return "" , fmt .Errorf ("%s private key (hex) is required" , c .authMethod )
49+ func NewInProcessSigner (privateKey string , algorithm CryptoAlgorithm ) (Signer , error ) {
50+ if privateKey == "" {
51+ return nil , fmt .Errorf ("%s private key (hex) is required" , algorithm )
4752 }
4853
4954 // Decode private key from hex
50- privateKeyBytes , err := hex .DecodeString (c . privateKey )
55+ privateKeyBytes , err := hex .DecodeString (privateKey )
5156 if err != nil {
52- return "" , fmt .Errorf ("invalid hex private key format: %w" , err )
57+ return nil , fmt .Errorf ("invalid hex private key format: %w" , err )
58+ }
59+
60+ return & InProcessSigner {
61+ privateKey : privateKeyBytes ,
62+ cryptoAlgorithm : algorithm ,
63+ }, nil
64+ }
65+
66+ // GetToken retrieves the registry JWT token using cryptographic authentication
67+ func (c * CryptoProvider ) GetToken (ctx context.Context ) (string , error ) {
68+ if c .domain == "" {
69+ return "" , fmt .Errorf ("%s domain is required" , c .authMethod )
5370 }
5471
5572 // Generate current timestamp
56- timestamp := time .Now ().UTC ().Format (time .RFC3339 )
57- signedTimestamp , err := c .signMessage (privateKeyBytes , []byte (timestamp ))
73+ timestamp , signedTimestamp , err := c .signer .GetSignedTimestamp (ctx )
5874 if err != nil {
5975 return "" , fmt .Errorf ("failed to sign timestamp: %w" , err )
6076 }
6177 signedTimestampHex := hex .EncodeToString (signedTimestamp )
6278
6379 // Exchange signature for registry token
64- registryToken , err := c .exchangeTokenForRegistry (ctx , c .domain , timestamp , signedTimestampHex )
80+ registryToken , err := c .exchangeTokenForRegistry (ctx , c .domain , * timestamp , signedTimestampHex )
6581 if err != nil {
6682 return "" , fmt .Errorf ("failed to exchange %s signature: %w" , c .authMethod , err )
6783 }
6884
6985 return registryToken , nil
7086}
7187
72- func (c * CryptoProvider ) signMessage (privateKeyBytes []byte , message []byte ) ([]byte , error ) {
88+ type InProcessSigner struct {
89+ privateKey []byte
90+ cryptoAlgorithm CryptoAlgorithm
91+ }
92+
93+ func (c * InProcessSigner ) GetSignedTimestamp (_ context.Context ) (* string , []byte , error ) {
94+ fmt .Fprintf (os .Stdout , "Signing in process using key algorithm %s\n " , c .cryptoAlgorithm )
95+
96+ timestamp := GetTimestamp ()
97+
7398 switch c .cryptoAlgorithm {
7499 case AlgorithmEd25519 :
75- if len (privateKeyBytes ) != ed25519 .SeedSize {
76- return nil , fmt .Errorf ("invalid seed length: expected %d bytes, got %d" , ed25519 .SeedSize , len (privateKeyBytes ))
100+ if len (c . privateKey ) != ed25519 .SeedSize {
101+ return nil , nil , fmt .Errorf ("invalid seed length: expected %d bytes, got %d" , ed25519 .SeedSize , len (c . privateKey ))
77102 }
78103
79- privateKey := ed25519 .NewKeyFromSeed (privateKeyBytes )
80- signature := ed25519 .Sign (privateKey , message )
81- return signature , nil
104+ privateKey := ed25519 .NewKeyFromSeed (c .privateKey )
105+
106+ PrintEd25519KeyInfo (privateKey .Public ().(ed25519.PublicKey ))
107+
108+ signature := ed25519 .Sign (privateKey , []byte (timestamp ))
109+ return & timestamp , signature , nil
82110 case AlgorithmECDSAP384 :
83- if len (privateKeyBytes ) != 48 {
84- return nil , fmt .Errorf ("invalid seed length for ECDSA P-384: expected 48 bytes, got %d" , len (privateKeyBytes ))
111+ if len (c . privateKey ) != 48 {
112+ return nil , nil , fmt .Errorf ("invalid seed length for ECDSA P-384: expected 48 bytes, got %d" , len (c . privateKey ))
85113 }
86114
87- digest := sha512 .Sum384 (message )
115+ digest := sha512 .Sum384 ([] byte ( timestamp ) )
88116 curve := elliptic .P384 ()
89- privateKey , err := ecdsa .ParseRawPrivateKey (curve , privateKeyBytes )
117+ privateKey , err := ecdsa .ParseRawPrivateKey (curve , c . privateKey )
90118 if err != nil {
91- return nil , fmt .Errorf ("failed to parse ECDSA private key: %w" , err )
119+ return nil , nil , fmt .Errorf ("failed to parse ECDSA private key: %w" , err )
92120 }
121+
122+ PrintEcdsaP384KeyInfo (privateKey .PublicKey )
123+
93124 r , s , err := ecdsa .Sign (rand .Reader , privateKey , digest [:])
94125 if err != nil {
95- return nil , fmt .Errorf ("failed to sign message: %w" , err )
126+ return nil , nil , fmt .Errorf ("failed to sign message: %w" , err )
96127 }
97128 signature := append (r .Bytes (), s .Bytes ()... )
98- return signature , nil
129+ return & timestamp , signature , nil
99130 default :
100- return nil , fmt .Errorf ("unsupported crypto algorithm: %s" , c .cryptoAlgorithm )
131+ return nil , nil , fmt .Errorf ("unsupported crypto algorithm: %s" , c .cryptoAlgorithm )
101132 }
102133}
103134
@@ -111,6 +142,23 @@ func (c *CryptoProvider) Login(_ context.Context) error {
111142 return nil
112143}
113144
145+ func PrintEd25519KeyInfo (pubKey ed25519.PublicKey ) {
146+ pubKeyString := base64 .StdEncoding .EncodeToString (pubKey )
147+ fmt .Fprint (os .Stdout , "Expected proof record:\n " )
148+ fmt .Fprintf (os .Stdout , "v=MCPv1; k=ed25519; p=%s\n " , pubKeyString )
149+ }
150+
151+ func PrintEcdsaP384KeyInfo (pubKey ecdsa.PublicKey ) {
152+ printEcdsaKeyInfo ("ecdsap384" , pubKey )
153+ }
154+
155+ func printEcdsaKeyInfo (k string , pubKey ecdsa.PublicKey ) {
156+ compressed := elliptic .MarshalCompressed (pubKey .Curve , pubKey .X , pubKey .Y )
157+ pubKeyString := base64 .StdEncoding .EncodeToString (compressed )
158+ fmt .Fprint (os .Stdout , "Expected proof record:\n " )
159+ fmt .Fprintf (os .Stdout , "v=MCPv1; k=%s; p=%s\n " , k , pubKeyString )
160+ }
161+
114162// exchangeTokenForRegistry exchanges signature for a registry JWT token
115163func (c * CryptoProvider ) exchangeTokenForRegistry (ctx context.Context , domain , timestamp , signedTimestamp string ) (string , error ) {
116164 if c .registryURL == "" {
@@ -130,7 +178,7 @@ func (c *CryptoProvider) exchangeTokenForRegistry(ctx context.Context, domain, t
130178 }
131179
132180 // Make the token exchange request
133- exchangeURL := fmt .Sprintf ("%s/v0/auth/%s" , c .registryURL , c .authMethod )
181+ exchangeURL := fmt .Sprintf ("%s/v0/auth/%s" , strings . TrimSuffix ( c .registryURL , "/" ) , c .authMethod )
134182 req , err := http .NewRequestWithContext (ctx , http .MethodPost , exchangeURL , bytes .NewBuffer (jsonData ))
135183 if err != nil {
136184 return "" , fmt .Errorf ("failed to create request: %w" , err )
0 commit comments