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

SD-JWT impl #6

Merged
merged 11 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 42 additions & 12 deletions credential/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,21 @@ type VerifiableCredential struct {
Evidence util.SingleOrArray[any] `json:"evidence,omitempty"`
}

// ToMap converts the VerifiableCredential to a map[string]any
func (vc *VerifiableCredential) ToMap() (map[string]any, error) {
jsonBytes, err := json.Marshal(vc)
if err != nil {
return nil, fmt.Errorf("failed to marshal VerifiableCredential: %w", err)
}

var result map[string]any
if err = json.Unmarshal(jsonBytes, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal VerifiableCredential to map: %w", err)
}

return result, nil
}

// IssuerHolder represents the issuer of a Verifiable Credential or holder of a Verifiable Presentation, which can be
// either a URL string or an object containing an ID property
type IssuerHolder struct {
Expand Down Expand Up @@ -134,19 +149,19 @@ type Schema struct {
DigestSRI string `json:"digestSRI,omitempty"`
}

func (v *VerifiableCredential) IsEmpty() bool {
if v == nil {
func (vc *VerifiableCredential) IsEmpty() bool {
if vc == nil {
return true
}
return reflect.DeepEqual(v, &VerifiableCredential{})
return reflect.DeepEqual(vc, &VerifiableCredential{})
}

func (v *VerifiableCredential) IsValid() error {
return util.NewValidator().Struct(v)
func (vc *VerifiableCredential) IsValid() error {
return util.NewValidator().Struct(vc)
}

func (v *VerifiableCredential) IssuerID() string {
return v.Issuer.ID()
func (vc *VerifiableCredential) IssuerID() string {
return vc.Issuer.ID()
}

// VerifiablePresentation https://www.w3.org/TR/vc-data-model-2.0/#verifiable-presentations
Expand All @@ -158,13 +173,28 @@ type VerifiablePresentation struct {
VerifiableCredential []VerifiableCredential `json:"verifiableCredential,omitempty"`
}

func (v *VerifiablePresentation) IsEmpty() bool {
if v == nil {
// ToMap converts the VerifiablePresentation to a map[string]any
func (vp *VerifiablePresentation) ToMap() (map[string]any, error) {
jsonBytes, err := json.Marshal(vp)
if err != nil {
return nil, fmt.Errorf("failed to marshal VerifiablePresentation: %w", err)
}

var result map[string]any
if err = json.Unmarshal(jsonBytes, &result); err != nil {
return nil, fmt.Errorf("failed to unmarshal VerifiablePresentation to map: %w", err)
}

return result, nil
}

func (vp *VerifiablePresentation) IsEmpty() bool {
if vp == nil {
return true
}
return reflect.DeepEqual(v, &VerifiablePresentation{})
return reflect.DeepEqual(vp, &VerifiablePresentation{})
}

func (v *VerifiablePresentation) IsValid() error {
return util.NewValidator().Struct(v)
func (vp *VerifiablePresentation) IsValid() error {
return util.NewValidator().Struct(vp)
}
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
)

require (
github.com/MichaelFraser99/go-sd-jwt v1.2.1
github.com/btcsuite/btcd/btcec/v2 v2.3.4
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
Expand All @@ -28,9 +29,9 @@ require (
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/text v0.19.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
16 changes: 10 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
github.com/MichaelFraser99/go-jose v0.9.0 h1:7vUcuJs5vGP0F+AQDStv6puqMYMmx75B4/Qc2CeKQR8=
github.com/MichaelFraser99/go-jose v0.9.0/go.mod h1:kdRvg7/FPcDnsEz8PyCg5hhcBlLud9F0jB4Xy/u771c=
github.com/MichaelFraser99/go-sd-jwt v1.2.1 h1:1Rf+Wy4jdPnRXRI4dvhjUsH2ygERYIrZETtiBtqIPos=
github.com/MichaelFraser99/go-sd-jwt v1.2.1/go.mod h1:1Kt/SQQEpexmeO0NrfPACRwn51NdhcqORikJDNDQMVA=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -47,17 +51,17 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
44 changes: 27 additions & 17 deletions jose/jose.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package jose

import (
"errors"
"fmt"
"time"

Expand All @@ -16,21 +17,23 @@ import (
const (
VCJOSEType = "vc+jwt"
VPJOSEType = "vp+jwt"
VCJWTTyp = "JWT"
VCJWTAlg = "alg"
VCJWTKid = "kid"
)

// SignVerifiableCredential dynamically signs a VerifiableCredential based on the key type.
func SignVerifiableCredential(vc *credential.VerifiableCredential, key jwk.Key) (string, error) {
// Marshal the VerifiableCredential to a map
vcMap := make(map[string]any)
vcBytes, err := json.Marshal(vc)
if err != nil {
return "", err
func SignVerifiableCredential(vc credential.VerifiableCredential, key jwk.Key) (*string, error) {
if vc.IsEmpty() {
return nil, errors.New("VerifiableCredential is empty")
}
if err = json.Unmarshal(vcBytes, &vcMap); err != nil {
return "", err
if key.KeyID() == "" {
return nil, errors.New("key ID is required")
}
if key.Algorithm().String() == "" {
return nil, errors.New("key algorithm is required")
}
// Convert VC to a map
vcMap, err := vc.ToMap()
if err != nil {
return nil, fmt.Errorf("failed to convert VC to map: %w", err)
}

// Add standard claims
Expand All @@ -50,30 +53,31 @@ func SignVerifiableCredential(vc *credential.VerifiableCredential, key jwk.Key)
// Marshal the claims to JSON
payload, err := json.Marshal(vcMap)
if err != nil {
return "", err
return nil, err
}

// Add protected header values
jwsHeaders := jws.NewHeaders()
headers := map[string]string{
"typ": VPJOSEType,
"cty": credential.VPContentType,
"typ": VCJOSEType,
"cty": credential.VCContentType,
"alg": key.Algorithm().String(),
"kid": key.KeyID(),
}
for k, v := range headers {
if err = jwsHeaders.Set(k, v); err != nil {
return "", err
return nil, err
}
}

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

return string(signed), nil
result := string(signed)
return &result, nil
}

// VerifyVerifiableCredential verifies a VerifiableCredential JWT using the provided key.
Expand All @@ -96,6 +100,12 @@ func VerifyVerifiableCredential(jwt string, key jwk.Key) (*credential.Verifiable
// SignVerifiablePresentation dynamically signs a VerifiablePresentation based on the key type.
func SignVerifiablePresentation(vp credential.VerifiablePresentation, key jwk.Key) (string, error) {
var alg jwa.SignatureAlgorithm
if key.KeyID() == "" {
return "", errors.New("key ID is required")
}
if key.Algorithm().String() == "" {
return "", errors.New("key algorithm is required")
}

kty := key.KeyType()
switch kty {
Expand Down
4 changes: 2 additions & 2 deletions jose/jose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func Test_Sign_Verify_VerifiableCredential(t *testing.T) {
key, err := util.GenerateJWKWithAlgorithm(tt.curve)
require.NoError(t, err)

vc := &credential.VerifiableCredential{
vc := credential.VerifiableCredential{
Context: []string{"https://www.w3.org/2018/credentials/v1"},
ID: "https://example.edu/credentials/1872",
Type: []string{"VerifiableCredential"},
Expand All @@ -43,7 +43,7 @@ func Test_Sign_Verify_VerifiableCredential(t *testing.T) {
assert.NotEmpty(t, jwt)

// Verify the VC
verifiedVC, err := VerifyVerifiableCredential(jwt, key)
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())
Expand Down
Loading
Loading