This repository has been archived by the owner on Mar 29, 2024. It is now read-only.
/
verification.go
157 lines (137 loc) · 4.71 KB
/
verification.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
package cabin
import (
"crypto/subtle"
"errors"
"fmt"
"github.com/safing/jess"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/rng"
"github.com/safing/spn/hub"
)
var (
verificationChallengeSize = 32
verificationChallengeMinSize = 16
verificationSigningSuite = jess.SuiteSignV1
verificationRequirements = jess.NewRequirements().
Remove(jess.Confidentiality).
Remove(jess.Integrity).
Remove(jess.RecipientAuthentication)
)
// Verification is used to verify certain aspects of another Hub.
type Verification struct {
// Challenge is a random value chosen by the client.
Challenge []byte `json:"c"`
// Purpose defines the purpose of the verification. Protects against using verification for other purposes.
Purpose string `json:"p"`
// ClientReference is an optional field for exchanging metadata about the client. Protects against forwarding/relay attacks.
ClientReference string `json:"cr"`
// ServerReference is an optional field for exchanging metadata about the server. Protects against forwarding/relay attacks.
ServerReference string `json:"sr"`
}
// CreateVerificationRequest creates a new verification request with the given
// purpose and references.
func CreateVerificationRequest(purpose, clientReference, serverReference string) (v *Verification, request []byte, err error) {
// Generate random challenge.
challenge, err := rng.Bytes(verificationChallengeSize)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate challenge: %w", err)
}
// Create verification object.
v = &Verification{
Purpose: purpose,
ClientReference: clientReference,
Challenge: challenge,
}
// Serialize verification.
request, err = dsd.Dump(v, dsd.JSON)
if err != nil {
return nil, nil, fmt.Errorf("failed to serialize verification request: %w", err)
}
// The server reference is not sent to the server, but needs to be supplied
// by the server.
v.ServerReference = serverReference
return v, request, nil
}
// SignVerificationRequest sign a verification request.
// The purpose and references must match the request, else the verification
// will fail.
func (id *Identity) SignVerificationRequest(request []byte, purpose, clientReference, serverReference string) (response []byte, err error) {
// Parse request.
v := new(Verification)
_, err = dsd.Load(request, v)
if err != nil {
return nil, fmt.Errorf("failed to parse request: %w", err)
}
// Validate request.
if len(v.Challenge) < verificationChallengeMinSize {
return nil, errors.New("challenge too small")
}
if v.Purpose != purpose {
return nil, errors.New("purpose mismatch")
}
if v.ClientReference != clientReference {
return nil, errors.New("client reference mismatch")
}
// Assign server reference and serialize.
v.ServerReference = serverReference
dataToSign, err := dsd.Dump(v, dsd.JSON)
if err != nil {
return nil, fmt.Errorf("failed to serialize verification response: %w", err)
}
// Sign response.
e := jess.NewUnconfiguredEnvelope()
e.SuiteID = verificationSigningSuite
e.Senders = []*jess.Signet{id.Signet}
jession, err := e.Correspondence(nil)
if err != nil {
return nil, fmt.Errorf("failed to setup signer: %w", err)
}
letter, err := jession.Close(dataToSign)
if err != nil {
return nil, fmt.Errorf("failed to sign: %w", err)
}
// Serialize and return.
signedResponse, err := letter.ToDSD(dsd.JSON)
if err != nil {
return nil, fmt.Errorf("failed to serialize letter: %w", err)
}
return signedResponse, nil
}
// Verify verifies the verification response and checks if everything is valid.
func (v *Verification) Verify(response []byte, h *hub.Hub) error {
// Parse response.
letter, err := jess.LetterFromDSD(response)
if err != nil {
return fmt.Errorf("failed to parse response: %w", err)
}
// Verify response.
responseData, err := letter.Open(
verificationRequirements,
&hub.SingleTrustStore{
Signet: h.PublicKey,
},
)
if err != nil {
return fmt.Errorf("failed to verify response: %w", err)
}
// Parse verified response.
responseV := new(Verification)
_, err = dsd.Load(responseData, responseV)
if err != nil {
return fmt.Errorf("failed to parse verified response: %w", err)
}
// Validate request.
if subtle.ConstantTimeCompare(v.Challenge, responseV.Challenge) != 1 {
return errors.New("challenge mismatch")
}
if subtle.ConstantTimeCompare([]byte(v.Purpose), []byte(responseV.Purpose)) != 1 {
return errors.New("purpose mismatch")
}
if subtle.ConstantTimeCompare([]byte(v.ClientReference), []byte(responseV.ClientReference)) != 1 {
return errors.New("client reference mismatch")
}
if subtle.ConstantTimeCompare([]byte(v.ServerReference), []byte(responseV.ServerReference)) != 1 {
return errors.New("server reference mismatch")
}
return nil
}