From ee695fc86d50722206415e1a4bff0f148d597e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Mur=C3=A9?= Date: Tue, 8 Jul 2025 12:58:48 +0200 Subject: [PATCH] jwk: complete support of everything --- crypto/jwk/private.go | 150 ++++++++++++++++++++++++++++++++++++++++-- crypto/jwk/public.go | 22 ++++++- 2 files changed, 166 insertions(+), 6 deletions(-) diff --git a/crypto/jwk/private.go b/crypto/jwk/private.go index b4d425c..227412e 100644 --- a/crypto/jwk/private.go +++ b/crypto/jwk/private.go @@ -10,6 +10,7 @@ import ( "github.com/INFURA/go-did/crypto/p256" "github.com/INFURA/go-did/crypto/p384" "github.com/INFURA/go-did/crypto/p521" + "github.com/INFURA/go-did/crypto/rsa" "github.com/INFURA/go-did/crypto/secp256k1" "github.com/INFURA/go-did/crypto/x25519" ) @@ -18,7 +19,124 @@ type PrivateJwk struct { Privkey crypto.PrivateKey } -func (pj PrivateJwk) UnmarshalJSON(bytes []byte) error { +func (pj PrivateJwk) MarshalJSON() ([]byte, error) { + switch privkey := pj.Privkey.(type) { + case ed25519.PrivateKey: + pubkey := privkey.Public().(ed25519.PublicKey) + return json.Marshal(struct { + Kty string `json:"kty"` + Crv string `json:"crv"` + X string `json:"x"` + D string `json:"d"` + }{ + Kty: "OKP", + Crv: "Ed25519", + X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()), + D: base64.RawURLEncoding.EncodeToString(privkey.Seed()), + }) + case *p256.PrivateKey: + pubkey := privkey.Public().(*p256.PublicKey) + return json.Marshal(struct { + Kty string `json:"kty"` + Crv string `json:"crv"` + X string `json:"x"` + Y string `json:"y"` + D string `json:"d"` + }{ + Kty: "EC", + Crv: "P-256", + X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()), + Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()), + D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()), + }) + case *p384.PrivateKey: + pubkey := privkey.Public().(*p384.PublicKey) + return json.Marshal(struct { + Kty string `json:"kty"` + Crv string `json:"crv"` + X string `json:"x"` + Y string `json:"y"` + D string `json:"d"` + }{ + Kty: "EC", + Crv: "P-384", + X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()), + Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()), + D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()), + }) + case *p521.PrivateKey: + pubkey := privkey.Public().(*p521.PublicKey) + return json.Marshal(struct { + Kty string `json:"kty"` + Crv string `json:"crv"` + X string `json:"x"` + Y string `json:"y"` + D string `json:"d"` + }{ + Kty: "EC", + Crv: "P-521", + X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()), + Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()), + D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()), + }) + case *rsa.PrivateKey: + pubkey := privkey.Public().(*rsa.PublicKey) + return json.Marshal(struct { + Kty string `json:"kty"` + N string `json:"n"` + E string `json:"e"` + D string `json:"d"` + P string `json:"p"` + Q string `json:"q"` + Dp string `json:"dp"` + Dq string `json:"dq"` + Qi string `json:"qi"` + }{ + Kty: "RSA", + N: base64.RawURLEncoding.EncodeToString(pubkey.NBytes()), + E: base64.RawURLEncoding.EncodeToString(pubkey.EBytes()), + D: base64.RawURLEncoding.EncodeToString(privkey.DBytes()), + P: base64.RawURLEncoding.EncodeToString(privkey.PBytes()), + Q: base64.RawURLEncoding.EncodeToString(privkey.QBytes()), + Dp: base64.RawURLEncoding.EncodeToString(privkey.DpBytes()), + Dq: base64.RawURLEncoding.EncodeToString(privkey.DqBytes()), + Qi: base64.RawURLEncoding.EncodeToString(privkey.QiBytes()), + }) + case *secp256k1.PrivateKey: + pubkey := privkey.Public().(*secp256k1.PublicKey) + return json.Marshal(struct { + Kty string `json:"kty"` + Crv string `json:"crv"` + X string `json:"x"` + Y string `json:"y"` + D string `json:"d"` + }{ + Kty: "EC", + Crv: "secp256k1", + X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()), + Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()), + D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()), + }) + case *x25519.PrivateKey: + pubkey := privkey.Public().(*x25519.PublicKey) + return json.Marshal(struct { + Kty string `json:"kty"` + Crv string `json:"crv"` + X string `json:"x"` + D string `json:"d"` + }{ + Kty: "OKP", + Crv: "X25519", + X: base64.RawURLEncoding.EncodeToString(pubkey.ToBytes()), + D: base64.RawURLEncoding.EncodeToString(privkey.ToBytes()), + }) + + default: + return nil, fmt.Errorf("unsupported key type %T", privkey) + } +} + +func (pj *PrivateJwk) UnmarshalJSON(bytes []byte) error { aux := make(map[string]string) err := json.Unmarshal(bytes, &aux) if err != nil { @@ -51,19 +169,41 @@ func (pj PrivateJwk) UnmarshalJSON(bytes []byte) error { } case "RSA": - return fmt.Errorf("not implemented") + // we only use N,E,D,P,Q ignore Dp/Dq/Qi which will be recomputed from other parameters + n, err := base64.RawURLEncoding.DecodeString(aux["n"]) + if err != nil { + return fmt.Errorf("invalid n parameter with kty=RSA: %w", err) + } + e, err := base64.RawURLEncoding.DecodeString(aux["e"]) + if err != nil { + return fmt.Errorf("invalid e parameter with kty=RSA: %w", err) + } + d, err := base64.RawURLEncoding.DecodeString(aux["d"]) + if err != nil { + return fmt.Errorf("invalid d parameter with kty=RSA: %w", err) + } + p, err := base64.RawURLEncoding.DecodeString(aux["p"]) + if err != nil { + return fmt.Errorf("invalid p parameter with kty=RSA: %w", err) + } + q, err := base64.RawURLEncoding.DecodeString(aux["q"]) + if err != nil { + return fmt.Errorf("invalid q parameter with kty=RSA: %w", err) + } + pj.Privkey, err = rsa.PrivateKeyFromNEDPQ(n, e, d, p, q) + return err case "OKP": // Octet key pair - x, err := base64.RawURLEncoding.DecodeString(aux["x"]) + d, err := base64.RawURLEncoding.DecodeString(aux["d"]) if err != nil { return fmt.Errorf("invalid x parameter with kty=OKP: %w", err) } switch aux["crv"] { case "Ed25519": - pj.Privkey, err = ed25519.PrivateKeyFromBytes(x) + pj.Privkey, err = ed25519.PrivateKeyFromSeed(d) return err case "X25519": - pj.Privkey, err = x25519.PrivateKeyFromBytes(x) + pj.Privkey, err = x25519.PrivateKeyFromBytes(d) return err default: diff --git a/crypto/jwk/public.go b/crypto/jwk/public.go index 8612c15..da76d7b 100644 --- a/crypto/jwk/public.go +++ b/crypto/jwk/public.go @@ -10,6 +10,7 @@ import ( "github.com/INFURA/go-did/crypto/p256" "github.com/INFURA/go-did/crypto/p384" "github.com/INFURA/go-did/crypto/p521" + "github.com/INFURA/go-did/crypto/rsa" "github.com/INFURA/go-did/crypto/secp256k1" "github.com/INFURA/go-did/crypto/x25519" ) @@ -70,6 +71,16 @@ func (pj PublicJwk) MarshalJSON() ([]byte, error) { X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()), Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()), }) + case *rsa.PublicKey: + return json.Marshal(struct { + Kty string `json:"kty"` + N string `json:"n"` + E string `json:"e"` + }{ + Kty: "RSA", + N: base64.RawURLEncoding.EncodeToString(pubkey.NBytes()), + E: base64.RawURLEncoding.EncodeToString(pubkey.EBytes()), + }) case *secp256k1.PublicKey: return json.Marshal(struct { Kty string `json:"kty"` @@ -134,7 +145,16 @@ func (pj *PublicJwk) UnmarshalJSON(bytes []byte) error { } case "RSA": - return fmt.Errorf("not implemented") + n, err := base64.RawURLEncoding.DecodeString(aux["n"]) + if err != nil { + return fmt.Errorf("invalid n parameter with kty=RSA: %w", err) + } + e, err := base64.RawURLEncoding.DecodeString(aux["e"]) + if err != nil { + return fmt.Errorf("invalid e parameter with kty=RSA: %w", err) + } + pj.Pubkey, err = rsa.PublicKeyFromNE(n, e) + return err case "OKP": // Octet key pair x, err := base64.RawURLEncoding.DecodeString(aux["x"])