213 lines
5.1 KiB
Go
213 lines
5.1 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/pem"
|
|
"testing"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
func generateTestECKey() string {
|
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
der, err := x509.MarshalECPrivateKey(privateKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
block := &pem.Block{
|
|
Type: "EC PRIVATE KEY",
|
|
Bytes: der,
|
|
}
|
|
return string(pem.EncodeToMemory(block))
|
|
}
|
|
|
|
func TestNewCDPAuth_ECKey(t *testing.T) {
|
|
ecKey := generateTestECKey()
|
|
|
|
auth, err := NewCDPAuth("test-key-id", ecKey)
|
|
if err != nil {
|
|
t.Fatalf("NewCDPAuth failed: %v", err)
|
|
}
|
|
|
|
if auth.signingMethod != jwt.SigningMethodES256 {
|
|
t.Errorf("expected ES256, got %v", auth.signingMethod)
|
|
}
|
|
|
|
if auth.keyName != "test-key-id" {
|
|
t.Errorf("expected keyName 'test-key-id', got %v", auth.keyName)
|
|
}
|
|
|
|
if auth.privateKey == nil {
|
|
t.Error("privateKey should not be nil")
|
|
}
|
|
|
|
_, ok := auth.privateKey.(*ecdsa.PrivateKey)
|
|
if !ok {
|
|
t.Error("privateKey should be *ecdsa.PrivateKey")
|
|
}
|
|
}
|
|
|
|
func TestNewCDPAuth_Ed25519Key(t *testing.T) {
|
|
// Generate a valid Ed25519 key
|
|
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatalf("failed to generate Ed25519 key: %v", err)
|
|
}
|
|
edKey := base64.StdEncoding.EncodeToString(privateKey)
|
|
|
|
auth, err := NewCDPAuth("test-key-id", edKey)
|
|
if err != nil {
|
|
t.Fatalf("NewCDPAuth failed: %v", err)
|
|
}
|
|
|
|
if auth.signingMethod != jwt.SigningMethodEdDSA {
|
|
t.Errorf("expected EdDSA, got %v", auth.signingMethod)
|
|
}
|
|
|
|
if auth.privateKey == nil {
|
|
t.Error("privateKey should not be nil")
|
|
}
|
|
|
|
_, ok := auth.privateKey.(ed25519.PrivateKey)
|
|
if !ok {
|
|
t.Error("privateKey should be ed25519.PrivateKey")
|
|
}
|
|
}
|
|
|
|
func TestNewCDPAuth_InvalidKey(t *testing.T) {
|
|
_, err := NewCDPAuth("test-key-id", "invalid-key")
|
|
if err == nil {
|
|
t.Error("expected error for invalid key")
|
|
}
|
|
}
|
|
|
|
func TestNewCDPAuth_EmptyKeyName(t *testing.T) {
|
|
_, err := NewCDPAuth("", "some-key")
|
|
if err == nil {
|
|
t.Error("expected error for empty key name")
|
|
}
|
|
}
|
|
|
|
func TestNewCDPAuth_EmptyKey(t *testing.T) {
|
|
_, err := NewCDPAuth("test-key-id", "")
|
|
if err == nil {
|
|
t.Error("expected error for empty key")
|
|
}
|
|
}
|
|
|
|
func TestGenerateBearerToken_ECKey(t *testing.T) {
|
|
ecKey := generateTestECKey()
|
|
|
|
auth, err := NewCDPAuth("test-key-id", ecKey)
|
|
if err != nil {
|
|
t.Fatalf("NewCDPAuth failed: %v", err)
|
|
}
|
|
|
|
token, err := auth.generateBearerToken("POST", "/platform/v2/x402/verify")
|
|
if err != nil {
|
|
t.Fatalf("generateBearerToken failed: %v", err)
|
|
}
|
|
|
|
if token == "" {
|
|
t.Error("token should not be empty")
|
|
}
|
|
|
|
// Parse and verify claims
|
|
parsed, err := jwt.Parse(token, func(token *jwt.Token) (any, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
|
|
return nil, jwt.ErrSignatureInvalid
|
|
}
|
|
return auth.privateKey.(*ecdsa.PrivateKey).Public(), nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("failed to parse token: %v", err)
|
|
}
|
|
|
|
if !parsed.Valid {
|
|
t.Error("token should be valid")
|
|
}
|
|
|
|
claims, ok := parsed.Claims.(jwt.MapClaims)
|
|
if !ok {
|
|
t.Fatal("claims should be MapClaims")
|
|
}
|
|
|
|
if claims["sub"] != "test-key-id" {
|
|
t.Errorf("expected sub 'test-key-id', got %v", claims["sub"])
|
|
}
|
|
if claims["iss"] != "cdp" {
|
|
t.Errorf("expected iss 'cdp', got %v", claims["iss"])
|
|
}
|
|
if claims["iat"] == nil {
|
|
t.Error("iat claim should be present")
|
|
}
|
|
if claims["nbf"] == nil {
|
|
t.Error("nbf claim should be present")
|
|
}
|
|
if claims["exp"] == nil {
|
|
t.Error("exp claim should be present")
|
|
}
|
|
if claims["uris"] == nil {
|
|
t.Error("uris claim should be present")
|
|
}
|
|
|
|
uris, ok := claims["uris"].([]any)
|
|
if !ok || len(uris) != 1 {
|
|
t.Errorf("expected uris array with 1 element, got %v", claims["uris"])
|
|
}
|
|
if uris[0] != "POST api.cdp.coinbase.com/platform/v2/x402/verify" {
|
|
t.Errorf("expected uri 'POST api.cdp.coinbase.com/platform/v2/x402/verify', got %v", uris[0])
|
|
}
|
|
|
|
// Check header
|
|
if parsed.Header["kid"] != "test-key-id" {
|
|
t.Errorf("expected kid 'test-key-id', got %v", parsed.Header["kid"])
|
|
}
|
|
if parsed.Header["nonce"] == nil {
|
|
t.Error("nonce header should be present")
|
|
}
|
|
}
|
|
|
|
func TestGetAuthHeaders(t *testing.T) {
|
|
ecKey := generateTestECKey()
|
|
|
|
auth, err := NewCDPAuth("test-key-id", ecKey)
|
|
if err != nil {
|
|
t.Fatalf("NewCDPAuth failed: %v", err)
|
|
}
|
|
|
|
headers, err := auth.GetAuthHeaders(context.TODO())
|
|
if err != nil {
|
|
t.Fatalf("GetAuthHeaders failed: %v", err)
|
|
}
|
|
|
|
if headers.Verify == nil || headers.Verify["Authorization"] == "" {
|
|
t.Error("Verify header should be present")
|
|
}
|
|
if headers.Settle == nil || headers.Settle["Authorization"] == "" {
|
|
t.Error("Settle header should be present")
|
|
}
|
|
if headers.Supported == nil || headers.Supported["Authorization"] == "" {
|
|
t.Error("Supported header should be present")
|
|
}
|
|
|
|
// All should start with "Bearer "
|
|
for name, header := range map[string]string{
|
|
"Verify": headers.Verify["Authorization"],
|
|
"Settle": headers.Settle["Authorization"],
|
|
"Supported": headers.Supported["Authorization"],
|
|
} {
|
|
if len(header) < 7 || header[:7] != "Bearer " {
|
|
t.Errorf("%s header should start with 'Bearer ', got %s", name, header)
|
|
}
|
|
}
|
|
}
|