/
verifier.go
179 lines (141 loc) · 4.75 KB
/
verifier.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/*
Copyright Gen Digital Inc. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package dataintegrity
import (
"encoding/json"
"errors"
"time"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/trustbloc/vc-go/dataintegrity/models"
"github.com/trustbloc/vc-go/dataintegrity/suite"
)
const (
proofPath = "proof"
)
// Verifier implements the CheckJWTProof Proof algorithm of the verifiable credential
// data integrity specification, using a set of provided cryptographic suites.
type Verifier struct {
suites map[string]suite.Verifier
resolver didResolver
}
// NewVerifier initializes a Verifier that supports using the provided
// cryptographic suites to perform data integrity verification.
func NewVerifier(opts *Options, suites ...suite.VerifierInitializer) (*Verifier, error) {
if opts == nil {
opts = &Options{}
}
verifier := &Verifier{
suites: map[string]suite.Verifier{},
resolver: opts.DIDResolver,
}
for _, initializer := range suites {
suiteType := initializer.Type()
if _, ok := verifier.suites[suiteType]; ok {
continue
}
verifierSuite, err := initializer.Verifier()
if err != nil {
return nil, err
}
verifier.suites[suiteType] = verifierSuite
}
return verifier, nil
}
var (
// ErrMissingProof is returned when Verifier.VerifyProof() is given a document
// without a data integrity proof field.
ErrMissingProof = errors.New("missing data integrity proof")
// ErrMalformedProof is returned when Verifier.VerifyProof() is given a document
// with a proof that isn't a JSON object or is missing necessary standard
// fields.
ErrMalformedProof = errors.New("malformed data integrity proof")
// ErrWrongProofType is returned when Verifier.VerifyProof() is given a document
// with a proof that isn't a Data Integrity proof.
ErrWrongProofType = errors.New("proof provided is not a data integrity proof")
// ErrMismatchedPurpose is returned when Verifier.VerifyProof() is given a
// document with a proof whose Purpose does not match the expected purpose
// provided in the proof options.
ErrMismatchedPurpose = errors.New("data integrity proof does not match expected purpose")
// ErrOutOfDate is returned when Verifier.VerifyProof() is given a document with
// a proof that was created more than models.ProofOptions.MaxAge seconds ago.
ErrOutOfDate = errors.New("data integrity proof out of date")
// ErrInvalidDomain is returned when Verifier.VerifyProof() is given a document
// with a proof without the expected domain.
ErrInvalidDomain = errors.New("data integrity proof has invalid domain")
// ErrInvalidChallenge is returned when Verifier.VerifyProof() is given a
// document with a proof without the expected challenge.
ErrInvalidChallenge = errors.New("data integrity proof has invalid challenge")
)
// VerifyProof verifies the data integrity proof on the given JSON document,
// returning an error if proof verification fails, and nil if verification
// succeeds.
func (v *Verifier) VerifyProof(doc []byte, opts *models.ProofOptions) error { // nolint:funlen,gocyclo
proofRaw := gjson.GetBytes(doc, proofPath)
if !proofRaw.Exists() {
return ErrMissingProof
}
proof := &models.Proof{}
err := json.Unmarshal([]byte(proofRaw.Raw), proof)
if err != nil {
return ErrMalformedProof
}
if proof.Type == "" || proof.VerificationMethod == "" || proof.ProofPurpose == "" {
return ErrMalformedProof
}
if proof.Type != models.DataIntegrityProof {
return ErrWrongProofType
}
verifierSuite, ok := v.suites[proof.CryptoSuite]
if !ok {
return ErrUnsupportedSuite
}
if opts.SuiteType == "" {
opts.SuiteType = proof.CryptoSuite
}
if verifierSuite.RequiresCreated() && proof.Created == "" {
return ErrMalformedProof
}
if opts.Created.IsZero() {
var parsedCreatedTime time.Time
parsedCreatedTime, err = time.Parse(models.DateTimeFormat, proof.Created)
if err != nil {
return ErrMalformedProof
}
opts.Created = parsedCreatedTime
}
if proof.ProofPurpose != opts.Purpose {
return ErrMismatchedPurpose
}
unsecuredDoc, err := sjson.DeleteBytes(doc, proofPath)
if err != nil {
return ErrMalformedProof
}
err = resolveVM(opts, v.resolver, proof.VerificationMethod)
if err != nil {
return err
}
verifyResult := verifierSuite.VerifyProof(unsecuredDoc, proof, opts)
if proof.Created != "" {
createdTime, err := time.Parse(models.DateTimeFormat, proof.Created)
if err != nil {
return ErrMalformedProof
}
if opts.MaxAge > 0 {
now := time.Now()
diff := now.Sub(createdTime)
if diff > time.Second*time.Duration(opts.MaxAge) {
return ErrOutOfDate
}
}
}
if opts.Domain != "" && opts.Domain != proof.Domain {
return ErrInvalidDomain
}
if opts.Challenge != "" && opts.Challenge != proof.Challenge {
return ErrInvalidChallenge
}
return verifyResult
}