161 lines
4.4 KiB
Go
161 lines
4.4 KiB
Go
|
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)
|
||
|
}
|