Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
jose working
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe committed Oct 8, 2024
1 parent 7f7b103 commit ece96a8
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 102 deletions.
2 changes: 1 addition & 1 deletion controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

const (
TypeJsonWebKey string = "JsonWebKey"
TypeJSONWebKey string = "JsonWebKey"
TypeMultikey string = "Multikey"
)

Expand Down
8 changes: 7 additions & 1 deletion credential/credential.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package credential

Check failure on line 1 in credential/credential.go

View workflow job for this annotation

GitHub Actions / vulnerability-scan

package requires newer Go version go1.23

import (
"encoding/json"
"fmt"
"reflect"

"github.com/goccy/go-json"

"github.com/TBD54566975/vc-jose-cose-go/util"
)

Expand Down Expand Up @@ -102,6 +103,11 @@ func (i *IssuerHolder) IsObject() bool {
return i.object != nil
}

// IsEmpty returns true if the issuer is empty
func (i *IssuerHolder) IsEmpty() bool {
return i.id == "" && i.object == nil
}

// Get returns the value of a property in the issuer/holder object, or nil if the issuer/holder is a string
// or the property doesn't exist
func (i *IssuerHolder) Get(property string) interface{} {
Expand Down
5 changes: 3 additions & 2 deletions credential/testdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package credential

import (
"embed"
"encoding/json"
"testing"

"github.com/goccy/go-json"

"github.com/stretchr/testify/assert"
)

Expand All @@ -19,7 +20,7 @@ var (
//go:embed testdata
testVectors embed.FS
vcTestVectors = []string{VCExample1}
vpTestVectors = []string{VPEnvelopedVCExample1}
vpTestVectors = []string{VPEnvelopedVCExample1, VPEnvelopedVPExample1}
)

func TestVCVectors(t *testing.T) {
Expand Down
132 changes: 48 additions & 84 deletions jose/jose.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package jose

Check failure on line 1 in jose/jose.go

View workflow job for this annotation

GitHub Actions / vulnerability-scan

package requires newer Go version go1.23

import (
"encoding/json"
"fmt"
"time"

"github.com/goccy/go-json"

"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/lestrrat-go/jwx/v2/jws"
Expand All @@ -22,68 +23,19 @@ const (

// SignVerifiableCredential dynamically signs a VerifiableCredential based on the key type.
func SignVerifiableCredential(vc *credential.VerifiableCredential, key jwk.Key) (string, error) {
var alg jwa.SignatureAlgorithm

kty := key.KeyType()
switch kty {
case jwa.EC:
crv, ok := key.Get("crv")
if !ok || crv == nil {
return "", fmt.Errorf("invalid or missing 'crv' parameter")
}
crvAlg := crv.(jwa.EllipticCurveAlgorithm)
switch crvAlg {
case jwa.P256:
alg = jwa.ES256
case jwa.P384:
alg = jwa.ES384
case jwa.P521:
alg = jwa.ES512
default:
return "", fmt.Errorf("unsupported curve: %s", crvAlg.String())
}
case jwa.OKP:
alg = jwa.EdDSA
default:
return "", fmt.Errorf("unsupported key type: %s", kty)
}

return signVerifiableCredential(vc, key, alg)
}

// VerifyVerifiableCredential verifies a VerifiableCredential JWT using the provided key.
func VerifyVerifiableCredential(jwt string, key jwk.Key) (*credential.VerifiableCredential, error) {
// Verify the JWT signature and get the payload
payload, err := jws.Verify([]byte(jwt), jws.WithKey(key.Algorithm(), key))
if err != nil {
return nil, fmt.Errorf("invalid JWT signature: %w", err)
}

// Unmarshal the payload into VerifiableCredential
var vc credential.VerifiableCredential
if err := json.Unmarshal(payload, &vc); err != nil {
return nil, fmt.Errorf("failed to unmarshal VerifiableCredential: %w", err)
}

return &vc, nil
}

// SignVerifiableCredential dynamically signs a VerifiableCredential using the specified algorithm,
// ensuring that all fields from the vc object are included at the top level in the JWT claims.
func signVerifiableCredential(vc *credential.VerifiableCredential, key jwk.Key, alg jwa.SignatureAlgorithm) (string, error) {
// Marshal the VerifiableCredential to a map
vcMap := make(map[string]any)
vcBytes, err := json.Marshal(vc)
if err != nil {
return "", err
}
if err := json.Unmarshal(vcBytes, &vcMap); err != nil {
if err = json.Unmarshal(vcBytes, &vcMap); err != nil {
return "", err
}

// Add standard claims
if vc.Issuer != nil {
vcMap["iss"] = vc.Issuer.ID
if !vc.Issuer.IsEmpty() {
vcMap["iss"] = vc.Issuer.ID()
}
if vc.ID != "" {
vcMap["jti"] = vc.ID
Expand All @@ -106,25 +58,42 @@ func signVerifiableCredential(vc *credential.VerifiableCredential, key jwk.Key,
headers := map[string]string{
"typ": VPJOSEType,
"cty": credential.VPContentType,
"alg": alg.String(),
"alg": key.Algorithm().String(),
"kid": key.KeyID(),
}
for k, v := range headers {
if err := jwsHeaders.Set(k, v); err != nil {
if err = jwsHeaders.Set(k, v); err != nil {
return "", err
}
}

// Sign the payload
signed, err := jws.Sign(payload, jws.WithKey(alg, key, jws.WithProtectedHeaders(jwsHeaders)))
signed, err := jws.Sign(payload, jws.WithKey(key.Algorithm(), key, jws.WithProtectedHeaders(jwsHeaders)))
if err != nil {
return "", err
}

return string(signed), nil
}

// SignJOSE dynamically signs a VerifiablePresentation based on the key type.
// VerifyVerifiableCredential verifies a VerifiableCredential JWT using the provided key.
func VerifyVerifiableCredential(jwt string, key jwk.Key) (*credential.VerifiableCredential, error) {
// Verify the JWT signature and get the payload
payload, err := jws.Verify([]byte(jwt), jws.WithKey(key.Algorithm(), key))
if err != nil {
return nil, fmt.Errorf("invalid JWT signature: %w", err)
}

// Unmarshal the payload into VerifiableCredential
var vc credential.VerifiableCredential
if err := json.Unmarshal(payload, &vc); err != nil {
return nil, fmt.Errorf("failed to unmarshal VerifiableCredential: %w", err)
}

return &vc, nil
}

// SignVerifiablePresentation dynamically signs a VerifiablePresentation based on the key type.
func SignVerifiablePresentation(vp credential.VerifiablePresentation, key jwk.Key) (string, error) {
var alg jwa.SignatureAlgorithm

Expand Down Expand Up @@ -152,49 +121,27 @@ func SignVerifiablePresentation(vp credential.VerifiablePresentation, key jwk.Ke
return "", fmt.Errorf("unsupported key type: %s", kty)
}

return signVerifiablePresentation(vp, key, alg)
}

// VerifyVerifiablePresentation verifies a VerifiablePresentation JWT using the provided key.
func VerifyVerifiablePresentation(jwt string, key jwk.Key) (*credential.VerifiablePresentation, error) {
// Verify the JWT signature and get the payload
payload, err := jws.Verify([]byte(jwt), jws.WithKey(key.Algorithm(), key))
if err != nil {
return nil, fmt.Errorf("invalid JWT signature: %w", err)
}

// Unmarshal the payload into VerifiablePresentation
var vp credential.VerifiablePresentation
if err := json.Unmarshal(payload, &vp); err != nil {
return nil, fmt.Errorf("failed to unmarshal VerifiablePresentation: %w", err)
}

return &vp, nil
}

// SignVerifiablePresentation dynamically signs a VerifiablePresentation using the specified algorithm.
func signVerifiablePresentation(vp credential.VerifiablePresentation, key jwk.Key, alg jwa.SignatureAlgorithm) (string, error) {
// Convert the VerifiablePresentation to a map for manipulation
vpMap := make(map[string]any)
vpBytes, err := json.Marshal(vp)
if err != nil {
return "", err
}
if err := json.Unmarshal(vpBytes, &vpMap); err != nil {
if err = json.Unmarshal(vpBytes, &vpMap); err != nil {
return "", err
}

// Add standard claims
if vp.Holder != nil {
vpMap["iss"] = vp.Holder.ID
if !vp.Holder.IsEmpty() {
vpMap["iss"] = vp.Holder.ID()
}
if vp.ID != "" {
vpMap["jti"] = vp.ID
}

vpMap["iat"] = time.Now().Unix()

// TODO(gabe): allow this to beconfigurable
// TODO(gabe): allow this to be configurable
vpMap["exp"] = time.Now().Add(time.Hour * 24).Unix()

// Marshal the claims to JSON
Expand All @@ -212,7 +159,7 @@ func signVerifiablePresentation(vp credential.VerifiablePresentation, key jwk.Ke
"kid": key.KeyID(),
}
for k, v := range headers {
if err := jwsHeaders.Set(k, v); err != nil {
if err = jwsHeaders.Set(k, v); err != nil {
return "", err
}
}
Expand All @@ -225,3 +172,20 @@ func signVerifiablePresentation(vp credential.VerifiablePresentation, key jwk.Ke

return string(signed), nil
}

// VerifyVerifiablePresentation verifies a VerifiablePresentation JWT using the provided key.
func VerifyVerifiablePresentation(jwt string, key jwk.Key) (*credential.VerifiablePresentation, error) {
// Verify the JWT signature and get the payload
payload, err := jws.Verify([]byte(jwt), jws.WithKey(key.Algorithm(), key))
if err != nil {
return nil, fmt.Errorf("invalid JWT signature: %w", err)
}

// Unmarshal the payload into VerifiablePresentation
var vp credential.VerifiablePresentation
if err := json.Unmarshal(payload, &vp); err != nil {
return nil, fmt.Errorf("failed to unmarshal VerifiablePresentation: %w", err)
}

return &vp, nil
}
17 changes: 5 additions & 12 deletions jose/jose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"testing"

"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand All @@ -25,15 +24,12 @@ func Test_Sign_Verify_VerifiableCredential(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, privKey, err := util.GenerateKeyByEllipticCurveAlgorithm(tt.curve)
require.NoError(t, err)

key, err := jwk.FromRaw(privKey)
key, err := util.GenerateJWKWithAlgorithm(tt.curve)
require.NoError(t, err)

vc := &credential.VerifiableCredential{
Context: []string{"https://www.w3.org/2018/credentials/v1"},
ID: "http://example.edu/credentials/1872",
ID: "https://example.edu/credentials/1872",
Type: []string{"VerifiableCredential"},
Issuer: credential.NewIssuerHolderFromString("did:example:issuer"),
ValidFrom: "2010-01-01T19:23:24Z",
Expand All @@ -50,7 +46,7 @@ func Test_Sign_Verify_VerifiableCredential(t *testing.T) {
verifiedVC, err := VerifyVerifiableCredential(jwt, key)
require.NoError(t, err)
assert.Equal(t, vc.ID, verifiedVC.ID)
assert.Equal(t, vc.Issuer.ID, verifiedVC.Issuer.ID)
assert.Equal(t, vc.Issuer.ID(), verifiedVC.Issuer.ID())
})
}
}
Expand All @@ -68,10 +64,7 @@ func Test_Sign_Verify_VerifiablePresentation(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, privKey, err := util.GenerateKeyByEllipticCurveAlgorithm(tt.curve)
require.NoError(t, err)

key, err := jwk.FromRaw(privKey)
key, err := util.GenerateJWKWithAlgorithm(tt.curve)
require.NoError(t, err)

vp := credential.VerifiablePresentation{
Expand All @@ -89,7 +82,7 @@ func Test_Sign_Verify_VerifiablePresentation(t *testing.T) {
verifiedVP, err := VerifyVerifiablePresentation(jwt, key)
require.NoError(t, err)
assert.Equal(t, vp.ID, verifiedVP.ID)
assert.Equal(t, vp.Holder.ID, verifiedVP.Holder.ID)
assert.Equal(t, vp.Holder.ID(), verifiedVP.Holder.ID())
})
}
}
37 changes: 37 additions & 0 deletions util/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"reflect"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/pkg/errors"

secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
Expand All @@ -38,6 +39,42 @@ const (
P521 KeyType = "P-521"
)

func GenerateJWKWithAlgorithm(eca jwa.EllipticCurveAlgorithm) (jwk.Key, error) {
// Generate the key pair
_, privKey, err := GenerateKeyByEllipticCurveAlgorithm(eca)
if err != nil {
return nil, fmt.Errorf("failed to generate key: %w", err)
}

// Convert the private key to JWK
jwkKey, err := jwk.FromRaw(privKey)
if err != nil {
return nil, fmt.Errorf("failed to convert key to JWK: %w", err)
}

// Set the algorithm based on the elliptic curve algorithm
var alg jwa.SignatureAlgorithm
switch eca {
case jwa.P256:
alg = jwa.ES256
case jwa.P384:
alg = jwa.ES384
case jwa.P521:
alg = jwa.ES512
case jwa.Ed25519:
alg = jwa.EdDSA
default:
return nil, fmt.Errorf("unsupported elliptic curve algorithm: %s", eca)
}

// Set the algorithm in the JWK
if err = jwkKey.Set(jwk.AlgorithmKey, alg); err != nil {
return nil, fmt.Errorf("failed to set algorithm in JWK: %w", err)
}

return jwkKey, nil
}

// GenerateKeyByEllipticCurveAlgorithm creates a brand-new key, returning the public and private key for the given elliptic curve algorithm
func GenerateKeyByEllipticCurveAlgorithm(eca jwa.EllipticCurveAlgorithm) (crypto.PublicKey, crypto.PrivateKey, error) {
switch eca {
Expand Down
3 changes: 2 additions & 1 deletion util/datatype.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package util

import (
"encoding/json"
"fmt"

"github.com/goccy/go-json"
)

// SingleOrArray represents a value that can be either a single item or an array of items
Expand Down
Loading

0 comments on commit ece96a8

Please sign in to comment.