-
Notifications
You must be signed in to change notification settings - Fork 26
/
signature.go
180 lines (143 loc) · 5.1 KB
/
signature.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
180
// SPDX-FileCopyrightText: 2021 The Go-SSB Authors
//
// SPDX-License-Identifier: MIT
package legacy
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"regexp"
refs "github.com/ssbc/go-ssb-refs"
"golang.org/x/crypto/ed25519"
)
var signatureRegexp = regexp.MustCompile(",\n \"signature\": \"([A-Za-z0-9/+=.]+)\"")
// ExtractSignature expects a pretty printed message and uses a regexp to strip it from the msg for signature verification
func ExtractSignature(b []byte) ([]byte, Signature, error) {
// BUG(cryptix): this expects signature on the root of the object.
// some functions (like createHistoryStream with keys:true) nest the message on level deeper and this fails
matches := signatureRegexp.FindSubmatch(b)
if n := len(matches); n != 2 {
return nil, nil, fmt.Errorf("ssb/ExtractSignature: expected signature in formatted bytes. Only %d matches", n)
}
msg := signatureRegexp.ReplaceAll(b, []byte{})
sig, err := NewSignatureFromBase64(matches[1])
if err != nil {
fmt.Printf("\t\tbase64? %s\n", matches[1])
return nil, nil, fmt.Errorf("ssb/ExtractSignature: invalid base64 data: %w", err)
}
return msg, sig, nil
}
type Signature []byte
func NewSignatureFromBase64(input []byte) (Signature, error) {
// check for and split off the suffix
if !bytes.HasSuffix(input, signatureSuffix) {
return nil, errors.New("ssb/signature: unexpected suffix")
}
b64 := bytes.TrimSuffix(input, signatureSuffix)
// check we have at least 64 bytes of signature data
gotLen := base64.StdEncoding.DecodedLen(len(b64))
if gotLen < ed25519.SignatureSize {
return nil, fmt.Errorf("ssb/signature: expected more signature data but only got %d", gotLen)
}
// allocate space for the signature and copy data into it
decoded := make([]byte, ed25519.SignatureSize)
_, err := base64.StdEncoding.Decode(decoded, b64)
if err != nil {
return nil, fmt.Errorf("ssb/signature: invalid base64 data: %w", err)
}
return Signature(decoded), err
}
var signatureSuffix = []byte(".sig.ed25519")
// UnmarshalJSON turns a std base64 encoded string with the suffix into JSON data
func (s *Signature) UnmarshalJSON(input []byte) error {
if !(input[0] == '"' && input[len(input)-1] == '"') {
return errors.New("ssb/signature: not a string")
}
// strip of the string markers
input = input[1 : len(input)-1]
newSig, err := NewSignatureFromBase64(input)
if err != nil {
return err
}
*s = newSig
return nil
}
// MarshalJSON turns the binary signature data into a base64 string with the SSB suffix
func (s Signature) MarshalJSON() ([]byte, error) {
// 2 for the string markers
dataLen := base64.StdEncoding.EncodedLen(len(s))
totalLen := 2 + dataLen + len(signatureSuffix)
enc := make([]byte, totalLen)
// make it a string
enc[0] = '"'
enc[totalLen-1] = '"'
// copy in the base64 data
base64.StdEncoding.Encode(enc[1:1+dataLen], s)
// copy the suffix to the end
copy(enc[1+dataLen:], signatureSuffix)
return enc, nil
}
func (s Signature) Verify(content []byte, r refs.FeedRef) error {
algo := r.Algo()
if algo != refs.RefAlgoFeedSSB1 && algo != refs.RefAlgoFeedBendyButt {
return fmt.Errorf("invalid feed algorithm")
}
if ed25519.Verify(r.PubKey(), content, s) {
return nil
}
return fmt.Errorf("invalid signature")
}
type LegacyMessage struct {
Previous *refs.MessageRef `json:"previous"`
Author string `json:"author"`
Sequence int64 `json:"sequence"`
Timestamp int64 `json:"timestamp"`
Hash string `json:"hash"`
Content interface{} `json:"content"`
}
type SignedLegacyMessage struct {
LegacyMessage
Signature Signature `json:"signature"`
}
// Sign preserves the filed order (up to content)
func (msg LegacyMessage) Sign(priv ed25519.PrivateKey, hmacSecret *[32]byte) (refs.MessageRef, []byte, error) {
// flatten interface{} content value
pp, err := jsonAndPreserve(msg)
if err != nil {
return refs.MessageRef{}, nil, fmt.Errorf("legacySign: error during sign prepare: %w", err)
}
pp = maybeHMAC(pp, hmacSecret)
sig := ed25519.Sign(priv, pp)
var signedMsg SignedLegacyMessage
signedMsg.LegacyMessage = msg
signedMsg.Signature = sig
// encode again, now with the signature to get the hash of the message
ppWithSig, err := jsonAndPreserve(signedMsg)
if err != nil {
return refs.MessageRef{}, nil, fmt.Errorf("legacySign: error re-encoding signed message: %w", err)
}
v8warp, err := InternalV8Binary(ppWithSig)
if err != nil {
return refs.MessageRef{}, nil, fmt.Errorf("legacySign: could not v8 escape message: %w", err)
}
h := sha256.New()
io.Copy(h, bytes.NewReader(v8warp))
mr, err := refs.NewMessageRefFromBytes(h.Sum(nil), refs.RefAlgoMessageSSB1)
return mr, ppWithSig, err
}
func jsonAndPreserve(msg interface{}) ([]byte, error) {
var buf bytes.Buffer // might want to pass a bufpool around here
if err := json.NewEncoder(&buf).Encode(msg); err != nil {
return nil, fmt.Errorf("jsonAndPreserve: 1st-pass json flattning failed: %w", err)
}
// pretty-print v8-like
pp, err := PrettyPrint(buf.Bytes())
if err != nil {
return nil, fmt.Errorf("jsonAndPreserve: preserver order failed: %w", err)
}
return pp, nil
}