diff --git a/crypto/jwk/private.go b/crypto/jwk/private.go new file mode 100644 index 0000000..b4d425c --- /dev/null +++ b/crypto/jwk/private.go @@ -0,0 +1,76 @@ +package jwk + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/INFURA/go-did/crypto" + "github.com/INFURA/go-did/crypto/ed25519" + "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/secp256k1" + "github.com/INFURA/go-did/crypto/x25519" +) + +type PrivateJwk struct { + Privkey crypto.PrivateKey +} + +func (pj PrivateJwk) UnmarshalJSON(bytes []byte) error { + aux := make(map[string]string) + err := json.Unmarshal(bytes, &aux) + if err != nil { + return err + } + + switch aux["kty"] { + case "EC": // Elliptic curve + // we only use D, ignore X/Y which will be recomputed from D + d, err := base64.RawURLEncoding.DecodeString(aux["d"]) + if err != nil { + return fmt.Errorf("invalid d parameter with kty=EC: %w", err) + } + switch aux["crv"] { + case "P-256": + pj.Privkey, err = p256.PrivateKeyFromBytes(d) + return err + case "P-384": + pj.Privkey, err = p384.PrivateKeyFromBytes(d) + return err + case "P-521": + pj.Privkey, err = p521.PrivateKeyFromBytes(d) + return err + case "secp256k1": + pj.Privkey, err = secp256k1.PrivateKeyFromBytes(d) + return err + + default: + return fmt.Errorf("unsupported Curve %s", aux["crv"]) + } + + case "RSA": + return fmt.Errorf("not implemented") + + case "OKP": // Octet key pair + x, err := base64.RawURLEncoding.DecodeString(aux["x"]) + 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) + return err + case "X25519": + pj.Privkey, err = x25519.PrivateKeyFromBytes(x) + return err + + default: + return fmt.Errorf("unsupported Curve %s", aux["crv"]) + } + + default: + return fmt.Errorf("unsupported key type %s", aux["kty"]) + } +} diff --git a/verifications/jsonwebkey/jwk.go b/crypto/jwk/public.go similarity index 74% rename from verifications/jsonwebkey/jwk.go rename to crypto/jwk/public.go index 1ffd8aa..8612c15 100644 --- a/verifications/jsonwebkey/jwk.go +++ b/crypto/jwk/public.go @@ -1,16 +1,16 @@ -package jsonwebkey +package jwk import ( "encoding/base64" "encoding/json" "fmt" - "math/big" "github.com/INFURA/go-did/crypto" "github.com/INFURA/go-did/crypto/ed25519" "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/secp256k1" "github.com/INFURA/go-did/crypto/x25519" ) @@ -18,12 +18,12 @@ import ( // - https://www.rfc-editor.org/rfc/rfc7517#section-4 (JWK) // - https://www.iana.org/assignments/jose/jose.xhtml#web-key-types (key parameters) -type jwk struct { - pubkey crypto.PublicKey +type PublicJwk struct { + Pubkey crypto.PublicKey } -func (j jwk) MarshalJSON() ([]byte, error) { - switch pubkey := j.pubkey.(type) { +func (pj PublicJwk) MarshalJSON() ([]byte, error) { + switch pubkey := pj.Pubkey.(type) { case ed25519.PublicKey: return json.Marshal(struct { Kty string `json:"kty"` @@ -70,6 +70,18 @@ func (j jwk) MarshalJSON() ([]byte, error) { X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()), Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()), }) + case *secp256k1.PublicKey: + return json.Marshal(struct { + Kty string `json:"kty"` + Crv string `json:"crv"` + X string `json:"x"` + Y string `json:"y"` + }{ + Kty: "EC", + Crv: "secp256k1", + X: base64.RawURLEncoding.EncodeToString(pubkey.XBytes()), + Y: base64.RawURLEncoding.EncodeToString(pubkey.YBytes()), + }) case *x25519.PublicKey: return json.Marshal(struct { Kty string `json:"kty"` @@ -86,40 +98,35 @@ func (j jwk) MarshalJSON() ([]byte, error) { } } -func (j *jwk) UnmarshalJSON(bytes []byte) error { +func (pj *PublicJwk) UnmarshalJSON(bytes []byte) error { aux := make(map[string]string) err := json.Unmarshal(bytes, &aux) if err != nil { return err } - bigIntBase64Url := func(s string) (*big.Int, error) { - raw, err := base64.RawURLEncoding.DecodeString(s) - if err != nil { - return nil, err - } - return new(big.Int).SetBytes(raw), nil - } - switch aux["kty"] { case "EC": // Elliptic curve - x, err := bigIntBase64Url(aux["x"]) + x, err := base64.RawURLEncoding.DecodeString(aux["x"]) if err != nil { return fmt.Errorf("invalid x parameter with kty=EC: %w", err) } - y, err := bigIntBase64Url(aux["y"]) + y, err := base64.RawURLEncoding.DecodeString(aux["y"]) if err != nil { return fmt.Errorf("invalid y parameter with kty=EC: %w", err) } switch aux["crv"] { case "P-256": - j.pubkey, err = p256.PublicKeyFromXY(x, y) + pj.Pubkey, err = p256.PublicKeyFromXY(x, y) return err case "P-384": - j.pubkey, err = p384.PublicKeyFromXY(x, y) + pj.Pubkey, err = p384.PublicKeyFromXY(x, y) return err case "P-521": - j.pubkey, err = p521.PublicKeyFromXY(x, y) + pj.Pubkey, err = p521.PublicKeyFromXY(x, y) + return err + case "secp256k1": + pj.Pubkey, err = secp256k1.PublicKeyFromXY(x, y) return err default: @@ -136,10 +143,10 @@ func (j *jwk) UnmarshalJSON(bytes []byte) error { } switch aux["crv"] { case "Ed25519": - j.pubkey, err = ed25519.PublicKeyFromBytes(x) + pj.Pubkey, err = ed25519.PublicKeyFromBytes(x) return err case "X25519": - j.pubkey, err = x25519.PublicKeyFromBytes(x) + pj.Pubkey, err = x25519.PublicKeyFromBytes(x) return err default: diff --git a/verifications/jsonwebkey/JsonWebKey2020.go b/verifications/jsonwebkey/JsonWebKey2020.go index f8db1fa..87b3a93 100644 --- a/verifications/jsonwebkey/JsonWebKey2020.go +++ b/verifications/jsonwebkey/JsonWebKey2020.go @@ -6,6 +6,7 @@ import ( "github.com/INFURA/go-did" "github.com/INFURA/go-did/crypto" + "github.com/INFURA/go-did/crypto/jwk" ) // Specification: https://www.w3.org/TR/vc-jws-2020/ @@ -34,24 +35,24 @@ func NewJsonWebKey2020(id string, pubkey crypto.PublicKey, controller did.DID) * func (j JsonWebKey2020) MarshalJSON() ([]byte, error) { return json.Marshal(struct { - ID string `json:"id"` - Type string `json:"type"` - Controller string `json:"controller"` - PublicKeyJWK jwk `json:"publicKeyJwk"` + ID string `json:"id"` + Type string `json:"type"` + Controller string `json:"controller"` + PublicKeyJWK jwk.PublicJwk `json:"publicKeyJwk"` }{ ID: j.ID(), Type: j.Type(), Controller: j.Controller(), - PublicKeyJWK: jwk{pubkey: j.pubkey}, + PublicKeyJWK: jwk.PublicJwk{Pubkey: j.pubkey}, }) } func (j *JsonWebKey2020) UnmarshalJSON(bytes []byte) error { aux := struct { - ID string `json:"id"` - Type string `json:"type"` - Controller string `json:"controller"` - PublicKeyJWK jwk `json:"publicKeyJwk"` + ID string `json:"id"` + Type string `json:"type"` + Controller string `json:"controller"` + PublicKeyJWK jwk.PublicJwk `json:"publicKeyJwk"` }{} err := json.Unmarshal(bytes, &aux) if err != nil { @@ -69,7 +70,7 @@ func (j *JsonWebKey2020) UnmarshalJSON(bytes []byte) error { return errors.New("invalid controller") } - j.pubkey = aux.PublicKeyJWK.pubkey + j.pubkey = aux.PublicKeyJWK.Pubkey return nil }