Skip to content

Commit

Permalink
refactor: move jwk from kms-go, and refactor implementation to be les…
Browse files Browse the repository at this point in the history
…s dependant on cryto libs.

Signed-off-by: Volodymyr Kubiv <[email protected]>
  • Loading branch information
vkubiv committed Feb 7, 2024
1 parent e95287d commit 9254073
Show file tree
Hide file tree
Showing 25 changed files with 1,290 additions and 1,118 deletions.
142 changes: 142 additions & 0 deletions crypto-ext/jwksupport/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package jwksupport

import (
"crypto/ed25519"
"crypto/elliptic"
"errors"
"fmt"
"math/big"

"github.com/btcsuite/btcutil/base58"
"github.com/trustbloc/bbs-signature-go/bbs12381g2pub"
"github.com/trustbloc/kms-go/util/cryptoutil"

"github.com/trustbloc/did-go/doc/jose/jwk"
)

const (
ecKty = "EC"
okpKty = "OKP"
x25519Crv = "X25519"
ed25519Crv = "Ed25519"
bls12381G2Crv = "BLS12381_G2"
bls12381G2Size = 96
)

func FromEdPublicKey(pub ed25519.PublicKey) *jwk.JWK {

Check failure on line 26 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

exported function `FromEdPublicKey` should have comment or be unexported (golint)
return &jwk.JWK{
Kty: "OKP",
Crv: ed25519Crv,
X: jwk.NewBuffer(pub),
}
}

func FromEdPrivateKey(ed ed25519.PrivateKey) *jwk.JWK {

Check failure on line 34 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

exported function `FromEdPrivateKey` should have comment or be unexported (golint)
raw := FromEdPublicKey(ed25519.PublicKey(ed[32:]))

raw.D = jwk.NewBuffer(ed[0:32])
return raw
}

func JWKFromX25519Key(pubKey []byte) (*jwk.JWK, error) {

Check failure on line 41 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

exported function `JWKFromX25519Key` should have comment or be unexported (golint)
if len(pubKey) != cryptoutil.Curve25519KeySize {
return nil, errors.New("JWKFromX25519Key: invalid key")
}

return &jwk.JWK{
Crv: x25519Crv,
Kty: okpKty,
X: jwk.NewFixedSizeBuffer(pubKey, cryptoutil.Curve25519KeySize),
}, nil
}

func FromEcdsaPubKeyBytes(curve elliptic.Curve, pubKeyBytes []byte) (*jwk.JWK, error) {

Check failure on line 53 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

exported function `FromEcdsaPubKeyBytes` should have comment or be unexported (golint)
println(base58.Encode(pubKeyBytes))
x, y := elliptic.UnmarshalCompressed(curve, pubKeyBytes)
if x == nil {
return nil, fmt.Errorf("error unmarshalling key bytes")
}

return FromEcdsaContent(EcdsaContent{
Curve: curve,
X: x,
Y: y,
})
}

func FromBLS12381G2(key *bbs12381g2pub.PublicKey) (*jwk.JWK, error) {

Check failure on line 67 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

exported function `FromBLS12381G2` should have comment or be unexported (golint)
var raw *jwk.JWK

mKey, err := key.Marshal()
if err != nil {
return nil, err
}

raw = &jwk.JWK{
Kty: ecKty,
Crv: bls12381G2Crv,
X: jwk.NewFixedSizeBuffer(mKey, bls12381G2Size),
}

return raw, nil
}

type EcdsaContent struct {

Check failure on line 84 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

exported type `EcdsaContent` should have comment or be unexported (golint)
Curve elliptic.Curve

X *big.Int
Y *big.Int
}

func FromEcdsaContent(content EcdsaContent) (*jwk.JWK, error) {

Check failure on line 91 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

exported function `FromEcdsaContent` should have comment or be unexported (golint)
name, err := curveName(content.Curve)
if err != nil {
return nil, err
}

size := curveSize(content.Curve)

xBytes := content.X.Bytes()
yBytes := content.Y.Bytes()

if len(xBytes) > size || len(yBytes) > size {
return nil, fmt.Errorf("go-jose/go-jose: invalid EC key (X/Y too large)")
}

key := &jwk.JWK{
Kty: "EC",
Crv: name,
X: jwk.NewFixedSizeBuffer(xBytes, size),
Y: jwk.NewFixedSizeBuffer(yBytes, size),
}

return key, nil
}

// Get JOSE name of curve

Check failure on line 116 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

Comment should end in a period (godot)
func curveName(crv elliptic.Curve) (string, error) {
switch crv {
case elliptic.P256():
return "P-256", nil
case elliptic.P384():
return "P-384", nil
case elliptic.P521():
return "P-521", nil
default:
return "", fmt.Errorf("unsupported/unknown elliptic curve")
}
}

// Get size of curve in bytes

Check failure on line 130 in crypto-ext/jwksupport/create.go

View workflow job for this annotation

GitHub Actions / Checks

Comment should end in a period (godot)
func curveSize(crv elliptic.Curve) int {
bits := crv.Params().BitSize

div := bits / 8
mod := bits % 8

if mod == 0 {
return div
}

return div + 1
}
96 changes: 96 additions & 0 deletions crypto-ext/jwksupport/fingerprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/

package jwksupport

import (
"crypto/elliptic"
"fmt"

"github.com/trustbloc/kms-go/util/cryptoutil"

"github.com/trustbloc/did-go/doc/jose/jwk"

"github.com/trustbloc/did-go/doc/fingerprint"
)

// CreateDIDKeyByJwk creates a did:key ID using the multicodec key fingerprint as per the did:key format spec found at:
// https://w3c-ccg.github.io/did-method-key/#format.
func CreateDIDKeyByJwk(jsonWebKey *jwk.JWK) (string, string, error) {
if jsonWebKey == nil {
return "", "", fmt.Errorf("jsonWebKey is required")
}

switch jsonWebKey.Kty {
case "EC":
code, curve, err := ecCodeAndCurve(jsonWebKey.Crv)
if err != nil {
return "", "", err
}

bytes := elliptic.MarshalCompressed(curve, jsonWebKey.X.BigInt(), jsonWebKey.Y.BigInt())
didKey, keyID := fingerprint.CreateDIDKeyByCode(code, bytes)

return didKey, keyID, nil

case "OKP":
var code uint64

switch jsonWebKey.Crv {
case "X25519":
var keyData = jsonWebKey.X.Bytes()

if len(keyData) != cryptoutil.Curve25519KeySize {
return "", "", jwk.ErrInvalidKey
}

code = fingerprint.X25519PubKeyMultiCodec
didKey, keyID := fingerprint.CreateDIDKeyByCode(code, keyData)

return didKey, keyID, nil
case "Ed25519":
keyData, err := ToED25519PublicKeyBytes(jsonWebKey)
if err != nil {
return "", "", err
}

code = fingerprint.ED25519PubKeyMultiCodec
didKey, keyID := fingerprint.CreateED25519DIDKey(keyData)

return didKey, keyID, nil

default:
return "", "", fmt.Errorf(
"unsupported kty %q and crv %q combination", jsonWebKey.Kty, jsonWebKey.Crv)
}

default:
return "", "", fmt.Errorf("unsupported kty %q", jsonWebKey.Kty)
}
}

func ecCodeAndCurve(ecCurve string) (uint64, elliptic.Curve, error) {
var (
curve elliptic.Curve
code uint64
)

switch ecCurve {
case elliptic.P256().Params().Name, "NIST_P256":
curve = elliptic.P256()
code = fingerprint.P256PubKeyMultiCodec
case elliptic.P384().Params().Name, "NIST_P384":
curve = elliptic.P384()
code = fingerprint.P384PubKeyMultiCodec
case elliptic.P521().Params().Name, "NIST_P521":
curve = elliptic.P521()
code = fingerprint.P521PubKeyMultiCodec
default:
return 0, nil, fmt.Errorf("unsupported crv %s", ecCurve)
}

return code, curve, nil
}
134 changes: 134 additions & 0 deletions crypto-ext/jwksupport/jwksupport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package jwksupport_test

import (
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"testing"

"github.com/btcsuite/btcutil/base58"
"github.com/stretchr/testify/require"
"github.com/trustbloc/bbs-signature-go/bbs12381g2pub"

"github.com/trustbloc/did-go/crypto-ext/jwksupport"
"github.com/trustbloc/did-go/doc/jose/jwk"
)

const (
ecP256PubKeyBase58 = "23youFZZdHMVdpv28DRSWP2zJbTJ8KHBeSKUX3qVqqnmp"
ecP384PubKeyBase58 = "ad1jjx1hRrEkMWSsFsXpLULAbmUq67ii1jRsBNtYEKhCwLXTo4wcjY7C2K4cuGZ859"
ecP521PubKeyBase58 = "4SsRN7NAk3175KrnPVQn5XTZE49MKdFKiq4XhWdhfx3QEUb2e96A3YLonFC6B21sa4uU776QMxEnxAQP6GWko8f3aNV"
x25519KeyBase64 = "egRLO+ygwW/VNHjQhZiHw1vhHwVj4KmzeRnKIEDz6gE="
)

func TestFromEdPublicKey(t *testing.T) {
pubKey, _, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)

jwk := jwksupport.FromEdPublicKey(pubKey)

_, _, err = jwksupport.CreateDIDKeyByJwk(jwk)
require.NoError(t, err)
}

func TestFromEdPrivateKey(t *testing.T) {
_, privKey, err := ed25519.GenerateKey(rand.Reader)
require.NoError(t, err)

jwk := jwksupport.FromEdPrivateKey(privKey)
require.NotEmpty(t, jwk.X.Bytes())
}

func TestJWKFromX25519Key(t *testing.T) {
keyBytes, err := base64.StdEncoding.DecodeString(x25519KeyBase64)
require.NoError(t, err)

jwk, err := jwksupport.JWKFromX25519Key(keyBytes)
require.NoError(t, err)

_, _, err = jwksupport.CreateDIDKeyByJwk(jwk)
require.NoError(t, err)

t.Run("Failure", func(t *testing.T) {
_, err = jwksupport.JWKFromX25519Key([]byte{})
require.ErrorContains(t, err, "invalid key")
})
}

func TestFromEcdsaPubKeyBytes(t *testing.T) {

t.Run("P256", func(t *testing.T) {
jwk, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P256(), base58.Decode(ecP256PubKeyBase58))
require.NoError(t, err)

_, _, err = jwksupport.CreateDIDKeyByJwk(jwk)
require.NoError(t, err)
})

t.Run("P384", func(t *testing.T) {
jwk, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P384(), base58.Decode(ecP384PubKeyBase58))
require.NoError(t, err)

_, _, err = jwksupport.CreateDIDKeyByJwk(jwk)
require.NoError(t, err)
})

t.Run("P521", func(t *testing.T) {
jwk, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P521(), base58.Decode(ecP521PubKeyBase58))
require.NoError(t, err)

_, _, err = jwksupport.CreateDIDKeyByJwk(jwk)
require.NoError(t, err)
})

t.Run("Failure", func(t *testing.T) {
_, err := jwksupport.FromEcdsaPubKeyBytes(elliptic.P256(), []byte{})
require.ErrorContains(t, err, "error unmarshalling key bytes")

_, err = jwksupport.FromEcdsaContent(jwksupport.EcdsaContent{Curve: elliptic.P224()})
require.ErrorContains(t, err, "unsupported/unknown elliptic curve")
})
}

func TestFromBLS12381G2(t *testing.T) {
bbsPubKey, _, err := bbs12381g2pub.GenerateKeyPair(sha256.New, nil)
require.NoError(t, err)

jwk, err := jwksupport.FromBLS12381G2(bbsPubKey)
require.NoError(t, err)
require.NotEmpty(t, jwk.X.Bytes())
}

func TestCreateDIDKeyByJwk(t *testing.T) {
t.Run("Failure", func(t *testing.T) {
_, _, err := jwksupport.CreateDIDKeyByJwk(&jwk.JWK{
Kty: "OKP",
})
require.ErrorContains(t, err, "unsupported kty \"OKP\" and crv \"\" combination")

_, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{})

require.ErrorContains(t, err, "unsupported kty \"\"")

_, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{
Kty: "OKP",
Crv: "X25519",
X: jwk.NewBuffer([]byte{}),
})
require.ErrorContains(t, err, "invalid JWK")

_, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{
Kty: "OKP",
Crv: "Ed25519",
})
require.ErrorContains(t, err, "invalid Ed key")

_, _, err = jwksupport.CreateDIDKeyByJwk(&jwk.JWK{
Kty: "EC",
Crv: "Ed25519",
})
require.ErrorContains(t, err, "unsupported crv Ed25519")
})
}
20 changes: 20 additions & 0 deletions crypto-ext/jwksupport/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package jwksupport

import (
"fmt"

"github.com/trustbloc/did-go/doc/jose/jwk"
)

const (
ed25519PublicKeySize = 32
)

func ToED25519PublicKeyBytes(key *jwk.JWK) ([]byte, error) {

Check failure on line 13 in crypto-ext/jwksupport/parse.go

View workflow job for this annotation

GitHub Actions / Checks

exported function `ToED25519PublicKeyBytes` should have comment or be unexported (golint)
if key.X == nil {
return nil, fmt.Errorf("invalid Ed key, missing x value")
}
publicKey := make([]byte, ed25519PublicKeySize)
copy(publicKey[0:ed25519PublicKeySize], key.X.Bytes())
return publicKey, nil
}
Loading

0 comments on commit 9254073

Please sign in to comment.