Skip to content

Commit

Permalink
VC-JWT and VP-JWT Support (#47)
Browse files Browse the repository at this point in the history
* reorg

* signing

* jwt verification working

* imports

* sign and verify working

* refactor

* jwt cred parsing

* add vp-jwt and update docs
  • Loading branch information
decentralgabe authored Mar 14, 2022
1 parent 42f80e0 commit beacaea
Show file tree
Hide file tree
Showing 13 changed files with 555 additions and 199 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ change as the library evolves.
03 August 2021_
- [Verifiable Credentials Data Model v1.1](https://www.w3.org/TR/2021/REC-vc-data-model-20211109/) _W3C Recommendation
09 November 2021_
- Supports [Linked Data Proof](https://www.w3.org/TR/vc-data-model/#data-integrity-proofs) formats.
- Supports [VC-JWT and VP-JWT](https://www.w3.org/TR/vc-data-model/#json-web-token) formats.
- [Verifiable Credentials JSON Schema Specification](https://w3c-ccg.github.io/vc-json-schemas/v2/index.html) _Draft
Community Group Report, 21 September 2021_

Expand All @@ -46,6 +48,7 @@ change as the library evolves.
- [JSON Web Signature 2020](https://w3c-ccg.github.io/lds-jws2020) _Draft Community Group Report 09 February 2022_
- [VC Proof Formats Test Suite, VC Data Model with JSON Web Signatures](https://identity.foundation/JWS-Test-Suite/) _Unofficial Draft 09 March 2022_
This implementation's compliance with the JWS Test Suite [can be found here](https://identity.foundation/JWS-Test-Suite/#tbd).
- Supports both JWT and Linked Data proof formats with [JOSE compliance](https://jose.readthedocs.io/en/latest/).

## did methods

Expand Down
2 changes: 0 additions & 2 deletions crypto/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
"github.com/lestrrat-go/jwx/x25519"
)

type KeyType string

const (
Ed25519 KeyType = "Ed25519"
X25519 KeyType = "X25519"
Expand Down
6 changes: 6 additions & 0 deletions crypto/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package crypto

type (
Proof interface{}
KeyType string
)
45 changes: 29 additions & 16 deletions cryptosuite/cryptosuite.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//go:build jwx_es256k

package cryptosuite

import (
"crypto"
gocrypto "crypto"
"github.com/TBD54566975/did-sdk/crypto"

. "github.com/TBD54566975/did-sdk/util"
"github.com/goccy/go-json"
Expand All @@ -10,16 +13,20 @@ import (
)

type (
Proof interface{}
SignatureType string
ProofPurpose string
PayloadFormat string
)

const (
W3CSecurityContext = "https://w3id.org/security/v1"
JWS2020LinkedDataContext string = "https://w3id.org/security/suites/jws-2020/v1"
AssertionMethod ProofPurpose = "assertionMethod"
Authentication ProofPurpose = "authentication"
W3CSecurityContext string = "https://w3id.org/security/v1"
JWS2020LinkedDataContext string = "https://w3id.org/security/suites/jws-2020/v1"

AssertionMethod ProofPurpose = "assertionMethod"
Authentication ProofPurpose = "authentication"

JWTFormat PayloadFormat = "jwt"
LDPFormat PayloadFormat = "ldp"
)

var (
Expand All @@ -42,7 +49,7 @@ type CryptoSuiteInfo interface {
ID() string
Type() LDKeyType
CanonicalizationAlgorithm() string
MessageDigestAlgorithm() crypto.Hash
MessageDigestAlgorithm() gocrypto.Hash
SignatureAlgorithm() SignatureType
RequiredContexts() []string
}
Expand All @@ -54,29 +61,35 @@ type CryptoSuiteProofType interface {
Marshal(data interface{}) ([]byte, error)
Canonicalize(marshaled []byte) (*string, error)
// CreateVerifyHash https://w3c-ccg.github.io/data-integrity-spec/#create-verify-hash-algorithm
CreateVerifyHash(provable Provable, proof Proof, proofOptions *ProofOptions) ([]byte, error)
CreateVerifyHash(provable Provable, proof crypto.Proof, proofOptions *ProofOptions) ([]byte, error)
Digest(tbd []byte) ([]byte, error)
}

type Provable interface {
GetProof() *Proof
SetProof(p *Proof)
GetProof() *crypto.Proof
SetProof(p *crypto.Proof)
}

type Signer interface {
KeyID() string
KeyType() string
SignatureType() SignatureType
SigningAlgorithm() string
Sign(tbs []byte) ([]byte, error)

GetKeyID() string
GetKeyType() string
GetSignatureType() SignatureType
GetSigningAlgorithm() string

SetProofPurpose(purpose ProofPurpose)
GetProofPurpose() ProofPurpose

SetPayloadFormat(format PayloadFormat)
GetPayloadFormat() PayloadFormat
}

type Verifier interface {
KeyID() string
KeyType() string
Verify(message, signature []byte) error

GetKeyID() string
GetKeyType() string
}

type ProofOptions struct {
Expand Down
86 changes: 51 additions & 35 deletions cryptosuite/jsonwebkey2020.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,35 +341,53 @@ type JSONWebKeySigner struct {
jwa.SignatureAlgorithm
jwk.Key
purpose ProofPurpose
format PayloadFormat
}

func (j *JSONWebKeySigner) SignatureType() SignatureType {
// Sign returns a byte array signature value for a message `tbs`
func (s *JSONWebKeySigner) Sign(tbs []byte) ([]byte, error) {
b64 := "b64"
headers := jws.NewHeaders()
if err := headers.Set(b64, false); err != nil {
return nil, err
}
if err := headers.Set(jws.CriticalKey, []string{b64}); err != nil {
return nil, err
}
signOptions := []jws.SignOption{jws.WithHeaders(headers), jws.WithDetachedPayload(tbs)}
return jws.Sign(nil, s.SignatureAlgorithm, s.Key, signOptions...)
}

func (s *JSONWebKeySigner) GetKeyID() string {
return s.Key.KeyID()
}

func (s *JSONWebKeySigner) GetKeyType() string {
return string(s.Key.KeyType())
}

func (s *JSONWebKeySigner) GetSignatureType() SignatureType {
return JSONWebSignature2020
}

func (j *JSONWebKeySigner) KeyID() string {
return j.Key.KeyID()
func (s *JSONWebKeySigner) GetSigningAlgorithm() string {
return s.Algorithm()
}

func (j *JSONWebKeySigner) KeyType() string {
return string(j.Key.KeyType())
func (s *JSONWebKeySigner) SetProofPurpose(purpose ProofPurpose) {
s.purpose = purpose
}

func (j *JSONWebKeySigner) SigningAlgorithm() string {
return j.Algorithm()
func (s *JSONWebKeySigner) GetProofPurpose() ProofPurpose {
return s.purpose
}

// Sign returns a byte array signature value for a message `tbs`
func (j *JSONWebKeySigner) Sign(tbs []byte) ([]byte, error) {
headers := jws.NewHeaders()
if err := headers.Set("b64", false); err != nil {
return nil, err
}
if err := headers.Set(jws.CriticalKey, []string{"b64"}); err != nil {
return nil, err
}
signOptions := []jws.SignOption{jws.WithHeaders(headers), jws.WithDetachedPayload(tbs)}
return jws.Sign(nil, j.SignatureAlgorithm, j.Key, signOptions...)
func (s *JSONWebKeySigner) SetPayloadFormat(format PayloadFormat) {
s.format = format
}

func (s *JSONWebKeySigner) GetPayloadFormat() PayloadFormat {
return s.format
}

func NewJSONWebKeySigner(kid string, key PrivateKeyJWK, purpose ProofPurpose) (*JSONWebKeySigner, error) {
Expand All @@ -392,41 +410,36 @@ func NewJSONWebKeySigner(kid string, key PrivateKeyJWK, purpose ProofPurpose) (*
if err := privKeyJWK.Set(jwk.KeyIDKey, kid); err != nil {
return nil, fmt.Errorf("could not set kid with provided value: %s", kid)
}
if err := privKeyJWK.Set(jwk.AlgorithmKey, alg); err != nil {
return nil, fmt.Errorf("could not set alg with value: %s", alg)
}
return &JSONWebKeySigner{
SignatureAlgorithm: alg,
Key: privKeyJWK,
purpose: purpose,
}, nil
}

func (j *JSONWebKeySigner) SetProofPurpose(purpose ProofPurpose) {
j.purpose = purpose
}

func (j *JSONWebKeySigner) GetProofPurpose() ProofPurpose {
return j.purpose
}

type JSONWebKeyVerifier struct {
jwa.SignatureAlgorithm
jwk.Key
}

func (v JSONWebKeyVerifier) KeyID() string {
return v.Key.KeyID()
}

func (v JSONWebKeyVerifier) KeyType() string {
return string(v.Key.KeyType())
}

// Verify attempts to verify a `signature` against a given `message`, returning nil if the verification is successful
// and an error should it fail.
func (v JSONWebKeyVerifier) Verify(message, signature []byte) error {
func (v *JSONWebKeyVerifier) Verify(message, signature []byte) error {
_, err := jws.Verify(signature, v.SignatureAlgorithm, v.Key, jws.VerifyOption(jws.WithDetachedPayload(message)))
return err
}

func (v *JSONWebKeyVerifier) GetKeyID() string {
return v.Key.KeyID()
}

func (v *JSONWebKeyVerifier) GetKeyType() string {
return string(v.Key.KeyType())
}

func NewJSONWebKeyVerifier(kid string, key PublicKeyJWK) (*JSONWebKeyVerifier, error) {
pubKeyJWKBytes, err := json.Marshal(key)
if err != nil {
Expand All @@ -447,6 +460,9 @@ func NewJSONWebKeyVerifier(kid string, key PublicKeyJWK) (*JSONWebKeyVerifier, e
if err := pubKeyJWK.Set(jwk.KeyIDKey, kid); err != nil {
return nil, fmt.Errorf("could not set kid with provided value: %s", kid)
}
if err := pubKeyJWK.Set(jwk.AlgorithmKey, alg); err != nil {
return nil, fmt.Errorf("could not set alg with value: %s", alg)
}
return &JSONWebKeyVerifier{
SignatureAlgorithm: alg,
Key: pubKeyJWK,
Expand Down
2 changes: 2 additions & 0 deletions cryptosuite/jsonwebkey2020_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build jwx_es256k

package cryptosuite

import (
Expand Down
41 changes: 23 additions & 18 deletions cryptosuite/jwssignaturesuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
package cryptosuite

import (
"crypto"
gocrypto "crypto"
"crypto/sha256"
"encoding/base64"
"fmt"
"github.com/TBD54566975/did-sdk/crypto"
"strings"

"github.com/google/uuid"
Expand All @@ -26,7 +27,7 @@ const (
JWSSignatureSuiteType LDKeyType = JsonWebKey2020
JWSSignatureSuiteCanonicalizationAlgorithm string = "https://w3id.org/security#URDNA2015"
// JWSSignatureSuiteDigestAlgorithm uses https://www.rfc-editor.org/rfc/rfc4634
JWSSignatureSuiteDigestAlgorithm crypto.Hash = crypto.SHA256
JWSSignatureSuiteDigestAlgorithm gocrypto.Hash = gocrypto.SHA256
// JWSSignatureSuiteProofAlgorithm uses https://www.rfc-editor.org/rfc/rfc7797
JWSSignatureSuiteProofAlgorithm = JSONWebSignature2020
)
Expand All @@ -39,6 +40,8 @@ func GetJSONWebSignature2020Suite() CryptoSuite {
return &JWSSignatureSuite{}
}

// CryptoSuiteInfo interface

func (j JWSSignatureSuite) ID() string {
return JWSSignatureSuiteID
}
Expand All @@ -51,7 +54,7 @@ func (j JWSSignatureSuite) CanonicalizationAlgorithm() string {
return JWSSignatureSuiteCanonicalizationAlgorithm
}

func (j JWSSignatureSuite) MessageDigestAlgorithm() crypto.Hash {
func (j JWSSignatureSuite) MessageDigestAlgorithm() gocrypto.Hash {
return JWSSignatureSuiteDigestAlgorithm
}

Expand All @@ -65,7 +68,7 @@ func (j JWSSignatureSuite) RequiredContexts() []string {

func (j JWSSignatureSuite) Sign(s Signer, p Provable) error {
// create proof before CVH
proof := j.createProof(s.KeyID(), s.GetProofPurpose())
proof := j.createProof(s.GetKeyID(), s.GetProofPurpose())

// prepare proof options
contexts, err := GetContextsFromProvable(p)
Expand All @@ -91,7 +94,7 @@ func (j JWSSignatureSuite) Sign(s Signer, p Provable) error {

// set the signature on the proof object and return
proof.SetDetachedJWS(string(signature))
genericProof := Proof(proof)
genericProof := crypto.Proof(proof)
p.SetProof(&genericProof)
return nil
}
Expand Down Expand Up @@ -132,6 +135,8 @@ func (j JWSSignatureSuite) Verify(v Verifier, p Provable) error {
return v.Verify(tbv, jwsCopy)
}

// CryptoSuiteProofType interface

func (j JWSSignatureSuite) Marshal(data interface{}) ([]byte, error) {
// JSONify the provable object
jsonBytes, err := json.Marshal(data)
Expand All @@ -155,15 +160,7 @@ func (j JWSSignatureSuite) Canonicalize(marshaled []byte) (*string, error) {
return &canonicalString, nil
}

func (j JWSSignatureSuite) Digest(tbd []byte) ([]byte, error) {
if j.MessageDigestAlgorithm() != crypto.SHA256 {
return nil, fmt.Errorf("unexpected digest algorithm: %s", j.MessageDigestAlgorithm().String())
}
hash := sha256.Sum256(tbd)
return hash[:], nil
}

func (j JWSSignatureSuite) CreateVerifyHash(provable Provable, proof Proof, opts *ProofOptions) ([]byte, error) {
func (j JWSSignatureSuite) CreateVerifyHash(provable Provable, proof crypto.Proof, opts *ProofOptions) ([]byte, error) {
// first, make sure "created" exists in the proof and insert an LD context property for the proof vocabulary
preparedProof, err := j.prepareProof(proof, opts)
if err != nil {
Expand Down Expand Up @@ -213,7 +210,15 @@ func (j JWSSignatureSuite) CreateVerifyHash(provable Provable, proof Proof, opts
return output, nil
}

func (j JWSSignatureSuite) prepareProof(proof Proof, opts *ProofOptions) (*Proof, error) {
func (j JWSSignatureSuite) Digest(tbd []byte) ([]byte, error) {
if j.MessageDigestAlgorithm() != gocrypto.SHA256 {
return nil, fmt.Errorf("unexpected digest algorithm: %s", j.MessageDigestAlgorithm().String())
}
hash := sha256.Sum256(tbd)
return hash[:], nil
}

func (j JWSSignatureSuite) prepareProof(proof crypto.Proof, opts *ProofOptions) (*crypto.Proof, error) {
var genericProof map[string]interface{}
proofBytes, err := json.Marshal(proof)
if err != nil {
Expand All @@ -240,7 +245,7 @@ func (j JWSSignatureSuite) prepareProof(proof Proof, opts *ProofOptions) (*Proof
contexts = ArrayStrToInterface(j.RequiredContexts())
}
genericProof["@context"] = contexts
p := Proof(genericProof)
p := crypto.Proof(genericProof)
return &p, nil
}

Expand All @@ -253,7 +258,7 @@ type JsonWebSignature2020Proof struct {
VerificationMethod string `json:"verificationMethod,omitempty"`
}

func FromGenericProof(p Proof) (*JsonWebSignature2020Proof, error) {
func FromGenericProof(p crypto.Proof) (*JsonWebSignature2020Proof, error) {
proofBytes, err := json.Marshal(p)
if err != nil {
return nil, err
Expand Down Expand Up @@ -296,7 +301,7 @@ func FromGenericProof(p Proof) (*JsonWebSignature2020Proof, error) {
}, nil
}

func (j *JsonWebSignature2020Proof) ToGenericProof() Proof {
func (j *JsonWebSignature2020Proof) ToGenericProof() crypto.Proof {
return j
}

Expand Down
Loading

0 comments on commit beacaea

Please sign in to comment.