/
utility.go
160 lines (144 loc) · 4.37 KB
/
utility.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
package olm
// #cgo LDFLAGS: -lolm -lstdc++
// #include <olm/olm.h>
import "C"
import (
"encoding/json"
"fmt"
"strings"
"unsafe"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"maunium.net/go/mautrix/crypto/canonicaljson"
"maunium.net/go/mautrix/id"
)
// Utility stores the necessary state to perform hash and signature
// verification operations.
type Utility struct {
int *C.OlmUtility
mem []byte
}
// utilitySize returns the size of a utility object in bytes.
func utilitySize() uint {
return uint(C.olm_utility_size())
}
// sha256Len returns the length of the buffer needed to hold the SHA-256 hash.
func (u *Utility) sha256Len() uint {
return uint(C.olm_sha256_length((*C.OlmUtility)(u.int)))
}
// lastError returns an error describing the most recent error to happen to a
// utility.
func (u *Utility) lastError() error {
return convertError(C.GoString(C.olm_utility_last_error((*C.OlmUtility)(u.int))))
}
// Clear clears the memory used to back this utility.
func (u *Utility) Clear() error {
r := C.olm_clear_utility((*C.OlmUtility)(u.int))
if r == errorVal() {
return u.lastError()
}
return nil
}
// NewUtility creates a new utility.
func NewUtility() *Utility {
memory := make([]byte, utilitySize())
return &Utility{
int: C.olm_utility(unsafe.Pointer(&memory[0])),
mem: memory,
}
}
// Sha256 calculates the SHA-256 hash of the input and encodes it as base64.
func (u *Utility) Sha256(input string) string {
if len(input) == 0 {
panic(EmptyInput)
}
output := make([]byte, u.sha256Len())
r := C.olm_sha256(
(*C.OlmUtility)(u.int),
unsafe.Pointer(&([]byte(input)[0])),
C.size_t(len(input)),
unsafe.Pointer(&(output[0])),
C.size_t(len(output)))
if r == errorVal() {
panic(u.lastError())
}
return string(output)
}
// VerifySignature verifies an ed25519 signature. Returns true if the verification
// suceeds or false otherwise. Returns error on failure. If the key was too
// small then the error will be "INVALID_BASE64".
func (u *Utility) VerifySignature(message string, key id.Ed25519, signature string) (ok bool, err error) {
if len(message) == 0 || len(key) == 0 || len(signature) == 0 {
return false, EmptyInput
}
r := C.olm_ed25519_verify(
(*C.OlmUtility)(u.int),
unsafe.Pointer(&([]byte(key)[0])),
C.size_t(len(key)),
unsafe.Pointer(&([]byte(message)[0])),
C.size_t(len(message)),
unsafe.Pointer(&([]byte(signature)[0])),
C.size_t(len(signature)))
if r == errorVal() {
err = u.lastError()
if err == BadMessageMAC {
err = nil
}
} else {
ok = true
}
return ok, err
}
var gjsonEscaper = strings.NewReplacer(
`\`, `\\`,
".", `\.`,
"|", `\|`,
"#", `\#`,
"@", `\@`,
"*", `\*`,
"?", `\?`)
func gjsonPath(path ...string) string {
var result strings.Builder
for i, part := range path {
_, _ = gjsonEscaper.WriteString(&result, part)
if i < len(path)-1 {
result.WriteRune('.')
}
}
return result.String()
}
// VerifySignatureJSON verifies the signature in the JSON object _obj following
// the Matrix specification:
// https://matrix.org/speculator/spec/drafts%2Fe2e/appendices.html#signing-json
// If the _obj is a struct, the `json` tags will be honored.
func (u *Utility) VerifySignatureJSON(obj interface{}, userID id.UserID, keyName string, key id.Ed25519) (bool, error) {
objJSON, err := json.Marshal(obj)
if err != nil {
return false, err
}
sig := gjson.GetBytes(objJSON, gjsonPath("signatures", string(userID), fmt.Sprintf("ed25519:%s", keyName)))
if !sig.Exists() || sig.Type != gjson.String {
return false, SignatureNotFound
}
objJSON, err = sjson.DeleteBytes(objJSON, "unsigned")
if err != nil {
return false, err
}
objJSON, err = sjson.DeleteBytes(objJSON, "signatures")
if err != nil {
return false, err
}
objJSONString := string(canonicaljson.CanonicalJSONAssumeValid(objJSON))
return u.VerifySignature(objJSONString, key, sig.Str)
}
// VerifySignatureJSON verifies the signature in the JSON object _obj following
// the Matrix specification:
// https://matrix.org/speculator/spec/drafts%2Fe2e/appendices.html#signing-json
// This function is a wrapper over Utility.VerifySignatureJSON that creates and
// destroys the Utility object transparently.
// If the _obj is a struct, the `json` tags will be honored.
func VerifySignatureJSON(obj interface{}, userID id.UserID, keyName string, key id.Ed25519) (bool, error) {
u := NewUtility()
defer u.Clear()
return u.VerifySignatureJSON(obj, userID, keyName, key)
}