forked from NuVotifier/go-votifier
-
Notifications
You must be signed in to change notification settings - Fork 2
/
protocol_v2.go
131 lines (111 loc) · 2.92 KB
/
protocol_v2.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
package votifier
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
)
type votifier2Wrapper struct {
Payload string `json:"payload"`
Signature []byte `json:"signature"`
}
type votifier2Inner struct {
ServiceName string `json:"serviceName"`
Username string `json:"username"`
Address string `json:"address"`
Timestamp int64 `json:"timestamp"`
Challenge string `json:"challenge"`
}
const v2Magic int16 = 0x733A
func (v *Vote) DecodeV2(data []byte, tokenProvider TokenProvider, challenge string) error {
rd := bytes.NewReader(data)
// verify v2 magic
var magicRead int16
err := binary.Read(rd, binary.BigEndian, &magicRead)
if err != nil {
return err
}
if magicRead != v2Magic {
return errors.New("v2 magic mismatch")
}
// read message length
var length int16
if err = binary.Read(rd, binary.BigEndian, &length); err != nil {
return err
}
// now for the fun part
var wrapper votifier2Wrapper
if err = json.NewDecoder(rd).Decode(&wrapper); err != nil {
return err
}
var vote votifier2Inner
if err = json.NewDecoder(strings.NewReader(wrapper.Payload)).Decode(&vote); err != nil {
return err
}
// validate challenge
if vote.Challenge != challenge {
return errors.New("invalid challenge")
}
// validate HMAC
token := tokenProvider.Token(vote.ServiceName)
m := hmac.New(sha256.New, []byte(token))
m.Write([]byte(wrapper.Payload))
s := m.Sum(nil)
if !hmac.Equal(s, wrapper.Signature) {
return errors.New("invalid signature")
}
v.ServiceName = vote.ServiceName
v.Username = vote.Username
v.Address = vote.Address
v.Timestamp = time.UnixMilli(vote.Timestamp)
return nil
}
func (v *Vote) EncodeV2(token string, challenge string) ([]byte, error) {
if v.Timestamp.IsZero() {
v.Timestamp = timeNow()
}
inner := votifier2Inner{
ServiceName: v.ServiceName,
Address: v.Address,
Username: v.Username,
Timestamp: v.Timestamp.UnixMilli(),
Challenge: challenge,
}
// encode inner vote and generate outer package
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(inner); err != nil {
return nil, err
}
innerJSON := buf.String()
m := hmac.New(sha256.New, []byte(token))
_, err := buf.WriteTo(m)
if err != nil {
return nil, fmt.Errorf("failed to write to hmac: %w", err)
}
wrapper := votifier2Wrapper{
Payload: innerJSON,
Signature: m.Sum(nil),
}
// assemble full package
var wrapperBuf bytes.Buffer
if err := json.NewEncoder(&wrapperBuf).Encode(wrapper); err != nil {
return nil, fmt.Errorf("failed to encode wrapper: %w", err)
}
buf.Reset()
if err := binary.Write(buf, binary.BigEndian, v2Magic); err != nil {
return nil, err
}
if err := binary.Write(buf, binary.BigEndian, int16(wrapperBuf.Len())); err != nil {
return nil, err
}
_, err = wrapperBuf.WriteTo(buf)
if err != nil {
return nil, fmt.Errorf("failed to write to buffer: %w", err)
}
return buf.Bytes(), nil
}