package auth import ( "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "encoding/base64" "time" "github.com/golang-jwt/jwt/v5" ) type DPoPKeyPair struct { PrivateKey *ecdsa.PrivateKey PublicKey *ecdsa.PublicKey JWK map[string]string } func GenerateDPoPKeyPair() (*DPoPKeyPair, error) { privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } jwk := map[string]string{ "kty": "EC", "crv": "P-256", "x": base64.RawURLEncoding.EncodeToString(privateKey.PublicKey.X.Bytes()), "y": base64.RawURLEncoding.EncodeToString(privateKey.PublicKey.Y.Bytes()), } return &DPoPKeyPair{ PrivateKey: privateKey, PublicKey: &privateKey.PublicKey, JWK: jwk, }, nil } func (kp *DPoPKeyPair) GenerateDPoPProof(method, url, nonce string) (string, error) { // generate unique ID for this proof jti, err := GenerateRandomToken() if err != nil { return "", err } // create JWT header with JWK header := map[string]interface{}{ "typ": "dpop+jwt", "alg": "ES256", "jwk": kp.JWK, } // create JWT claims claims := jwt.MapClaims{ "jti": jti, "htm": method, // HTTP method (GET, POST, etc) "htu": url, // HTTP URL (the endpoint you're calling) "iat": time.Now().Unix(), } // add nonce if provided if nonce != "" { claims["nonce"] = nonce } // create token with custom header token := jwt.NewWithClaims(&jwt.SigningMethodECDSA{ Name: "ES256", Hash: crypto.SHA256, KeySize: 32, CurveBits: 256, }, claims) token.Header = header // sign with private key tokenString, err := token.SignedString(kp.PrivateKey) if err != nil { return "", err } return tokenString, nil }