Skip to content

Instantly share code, notes, and snippets.

@lionello
Created March 16, 2023 20:48
Show Gist options
  • Save lionello/5e84c6eefd6cb537f8b89784f5befbfa to your computer and use it in GitHub Desktop.
Save lionello/5e84c6eefd6cb537f8b89784f5befbfa to your computer and use it in GitHub Desktop.
JWK in Go
package pkg
import (
"crypto/ecdsa"
"crypto/elliptic"
"encoding/base64"
"errors"
"math/big"
)
type Jwk struct {
Kty Kty `json:"kty"`
Crv Crv `json:"crv"`
X *JwkInt `json:"x"`
Y *JwkInt `json:"y,omitempty"`
D *JwkInt `json:"d,omitempty"`
Use Use `json:"use,omitempty"`
}
func fromCurve(curve elliptic.Curve) (Crv, error) {
switch curve {
case elliptic.P256():
return P256, nil
default:
return "", errors.New("unsupported curve")
}
}
func FromEcdsaPrivateKey(pk *ecdsa.PrivateKey) (*Jwk, error) {
jwk, err := FromEcdsaPublicKey(pk.PublicKey)
if err != nil {
return nil, err
}
jwk.D = (*JwkInt)(pk.D)
return jwk, nil
}
func FromEcdsaPublicKey(pub ecdsa.PublicKey) (*Jwk, error) {
crv, err := fromCurve(pub.Curve)
if err != nil {
return nil, err
}
return &Jwk{
Kty: EC,
Crv: crv,
X: (*JwkInt)(pub.X),
Y: (*JwkInt)(pub.Y),
}, nil
}
type Crv string
const P256 Crv = "P-256"
type Kty string
const EC Kty = "EC"
type JwkInt big.Int
func (j JwkInt) MarshalText() ([]byte, error) {
bi := big.Int(j)
return []byte(base64.RawURLEncoding.EncodeToString(bi.Bytes())), nil
}
func (j *JwkInt) UnmarshalText(b []byte) error {
b, err := base64.RawURLEncoding.DecodeString(string(b))
if err != nil {
return err
}
bi := big.Int{}
*j = JwkInt(*bi.SetBytes(b))
return nil
}
type Use string
const (
Sig Use = "sig"
Enc = "enc"
)
package pkg
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"encoding/json"
"testing"
)
func TestFromEcdsaPrivateKey(t *testing.T) {
pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
jwk, err := FromEcdsaPrivateKey(pk)
if err != nil {
t.Fatal(err)
}
if jwk.Kty != EC {
t.Fatalf("expected kty EC, got %s", jwk.Kty)
}
if jwk.Crv != P256 {
t.Fatalf("expected crv P-256, got %s", jwk.Crv)
}
if jwk.X == nil {
t.Fatal("expected x to be non-nil")
}
if jwk.Y == nil {
t.Fatal("expected y to be non-nil")
}
if jwk.D == nil {
t.Fatal("expected d to be non-nil")
}
}
func TestJwk(t *testing.T) {
const testKey = `{"kty":"EC","crv":"P-256","x":"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ","y":"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck","d":"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"}`
jwk := Jwk{}
err := json.Unmarshal([]byte(testKey), &jwk)
if err != nil {
t.Fatal(err)
}
if jwk.Kty != EC {
t.Fatalf("expected kty EC, got %s", jwk.Kty)
}
if jwk.Crv != P256 {
t.Fatalf("expected crv P-256, got %s", jwk.Crv)
}
if jwk.X == nil {
t.Fatal("expected x to be set")
}
if jwk.Y == nil {
t.Fatal("expected y to be set")
}
if jwk.D == nil {
t.Fatal("expected d to be set")
}
b, err := json.Marshal(jwk)
if err != nil {
t.Fatal(err)
}
if string(b) != testKey {
t.Fatalf("expected value %s, got %s", testKey, b)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment