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

Commit ea15c86

Browse files
SD-JWT impl (#6)
* update deps * first cut * tmp * working ish * working i think * test * sd jwt vc done * thumbprint * verifiable presentation * lints
1 parent 27a2d6d commit ea15c86

File tree

8 files changed

+853
-40
lines changed

8 files changed

+853
-40
lines changed

credential/credential.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ type VerifiableCredential struct {
4343
Evidence util.SingleOrArray[any] `json:"evidence,omitempty"`
4444
}
4545

46+
// ToMap converts the VerifiableCredential to a map[string]any
47+
func (vc *VerifiableCredential) ToMap() (map[string]any, error) {
48+
jsonBytes, err := json.Marshal(vc)
49+
if err != nil {
50+
return nil, fmt.Errorf("failed to marshal VerifiableCredential: %w", err)
51+
}
52+
53+
var result map[string]any
54+
if err = json.Unmarshal(jsonBytes, &result); err != nil {
55+
return nil, fmt.Errorf("failed to unmarshal VerifiableCredential to map: %w", err)
56+
}
57+
58+
return result, nil
59+
}
60+
4661
// IssuerHolder represents the issuer of a Verifiable Credential or holder of a Verifiable Presentation, which can be
4762
// either a URL string or an object containing an ID property
4863
type IssuerHolder struct {
@@ -134,19 +149,19 @@ type Schema struct {
134149
DigestSRI string `json:"digestSRI,omitempty"`
135150
}
136151

137-
func (v *VerifiableCredential) IsEmpty() bool {
138-
if v == nil {
152+
func (vc *VerifiableCredential) IsEmpty() bool {
153+
if vc == nil {
139154
return true
140155
}
141-
return reflect.DeepEqual(v, &VerifiableCredential{})
156+
return reflect.DeepEqual(vc, &VerifiableCredential{})
142157
}
143158

144-
func (v *VerifiableCredential) IsValid() error {
145-
return util.NewValidator().Struct(v)
159+
func (vc *VerifiableCredential) IsValid() error {
160+
return util.NewValidator().Struct(vc)
146161
}
147162

148-
func (v *VerifiableCredential) IssuerID() string {
149-
return v.Issuer.ID()
163+
func (vc *VerifiableCredential) IssuerID() string {
164+
return vc.Issuer.ID()
150165
}
151166

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

161-
func (v *VerifiablePresentation) IsEmpty() bool {
162-
if v == nil {
176+
// ToMap converts the VerifiablePresentation to a map[string]any
177+
func (vp *VerifiablePresentation) ToMap() (map[string]any, error) {
178+
jsonBytes, err := json.Marshal(vp)
179+
if err != nil {
180+
return nil, fmt.Errorf("failed to marshal VerifiablePresentation: %w", err)
181+
}
182+
183+
var result map[string]any
184+
if err = json.Unmarshal(jsonBytes, &result); err != nil {
185+
return nil, fmt.Errorf("failed to unmarshal VerifiablePresentation to map: %w", err)
186+
}
187+
188+
return result, nil
189+
}
190+
191+
func (vp *VerifiablePresentation) IsEmpty() bool {
192+
if vp == nil {
163193
return true
164194
}
165-
return reflect.DeepEqual(v, &VerifiablePresentation{})
195+
return reflect.DeepEqual(vp, &VerifiablePresentation{})
166196
}
167197

168-
func (v *VerifiablePresentation) IsValid() error {
169-
return util.NewValidator().Struct(v)
198+
func (vp *VerifiablePresentation) IsValid() error {
199+
return util.NewValidator().Struct(vp)
170200
}

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ require (
1313
)
1414

1515
require (
16+
github.com/MichaelFraser99/go-sd-jwt v1.2.1
1617
github.com/btcsuite/btcd/btcec/v2 v2.3.4
1718
github.com/davecgh/go-spew v1.1.1 // indirect
1819
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
@@ -28,9 +29,9 @@ require (
2829
github.com/lestrrat-go/option v1.0.1 // indirect
2930
github.com/pmezard/go-difflib v1.0.0 // indirect
3031
github.com/segmentio/asm v1.2.0 // indirect
31-
golang.org/x/crypto v0.26.0 // indirect
32-
golang.org/x/net v0.28.0 // indirect
32+
golang.org/x/crypto v0.28.0 // indirect
33+
golang.org/x/net v0.30.0 // indirect
3334
golang.org/x/sys v0.26.0 // indirect
34-
golang.org/x/text v0.17.0 // indirect
35+
golang.org/x/text v0.19.0 // indirect
3536
gopkg.in/yaml.v3 v3.0.1 // indirect
3637
)

go.sum

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
github.com/MichaelFraser99/go-jose v0.9.0 h1:7vUcuJs5vGP0F+AQDStv6puqMYMmx75B4/Qc2CeKQR8=
2+
github.com/MichaelFraser99/go-jose v0.9.0/go.mod h1:kdRvg7/FPcDnsEz8PyCg5hhcBlLud9F0jB4Xy/u771c=
3+
github.com/MichaelFraser99/go-sd-jwt v1.2.1 h1:1Rf+Wy4jdPnRXRI4dvhjUsH2ygERYIrZETtiBtqIPos=
4+
github.com/MichaelFraser99/go-sd-jwt v1.2.1/go.mod h1:1Kt/SQQEpexmeO0NrfPACRwn51NdhcqORikJDNDQMVA=
15
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
26
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
37
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -47,17 +51,17 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
4751
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
4852
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
4953
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
50-
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
51-
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
52-
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
53-
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
54+
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
55+
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
56+
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
57+
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
5458
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5559
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
5660
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5761
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
5862
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
59-
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
60-
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
63+
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
64+
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
6165
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
6266
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
6367
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

jose/jose.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jose
22

33
import (
4+
"errors"
45
"fmt"
56
"time"
67

@@ -16,21 +17,23 @@ import (
1617
const (
1718
VCJOSEType = "vc+jwt"
1819
VPJOSEType = "vp+jwt"
19-
VCJWTTyp = "JWT"
20-
VCJWTAlg = "alg"
21-
VCJWTKid = "kid"
2220
)
2321

2422
// SignVerifiableCredential dynamically signs a VerifiableCredential based on the key type.
25-
func SignVerifiableCredential(vc *credential.VerifiableCredential, key jwk.Key) (string, error) {
26-
// Marshal the VerifiableCredential to a map
27-
vcMap := make(map[string]any)
28-
vcBytes, err := json.Marshal(vc)
29-
if err != nil {
30-
return "", err
23+
func SignVerifiableCredential(vc credential.VerifiableCredential, key jwk.Key) (*string, error) {
24+
if vc.IsEmpty() {
25+
return nil, errors.New("VerifiableCredential is empty")
3126
}
32-
if err = json.Unmarshal(vcBytes, &vcMap); err != nil {
33-
return "", err
27+
if key.KeyID() == "" {
28+
return nil, errors.New("key ID is required")
29+
}
30+
if key.Algorithm().String() == "" {
31+
return nil, errors.New("key algorithm is required")
32+
}
33+
// Convert VC to a map
34+
vcMap, err := vc.ToMap()
35+
if err != nil {
36+
return nil, fmt.Errorf("failed to convert VC to map: %w", err)
3437
}
3538

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

5659
// Add protected header values
5760
jwsHeaders := jws.NewHeaders()
5861
headers := map[string]string{
59-
"typ": VPJOSEType,
60-
"cty": credential.VPContentType,
62+
"typ": VCJOSEType,
63+
"cty": credential.VCContentType,
6164
"alg": key.Algorithm().String(),
6265
"kid": key.KeyID(),
6366
}
6467
for k, v := range headers {
6568
if err = jwsHeaders.Set(k, v); err != nil {
66-
return "", err
69+
return nil, err
6770
}
6871
}
6972

7073
// Sign the payload
7174
signed, err := jws.Sign(payload, jws.WithKey(key.Algorithm(), key, jws.WithProtectedHeaders(jwsHeaders)))
7275
if err != nil {
73-
return "", err
76+
return nil, err
7477
}
7578

76-
return string(signed), nil
79+
result := string(signed)
80+
return &result, nil
7781
}
7882

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

100110
kty := key.KeyType()
101111
switch kty {

jose/jose_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func Test_Sign_Verify_VerifiableCredential(t *testing.T) {
2727
key, err := util.GenerateJWKWithAlgorithm(tt.curve)
2828
require.NoError(t, err)
2929

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

4545
// Verify the VC
46-
verifiedVC, err := VerifyVerifiableCredential(jwt, key)
46+
verifiedVC, err := VerifyVerifiableCredential(*jwt, key)
4747
require.NoError(t, err)
4848
assert.Equal(t, vc.ID, verifiedVC.ID)
4949
assert.Equal(t, vc.Issuer.ID(), verifiedVC.Issuer.ID())

0 commit comments

Comments
 (0)