DataHoarder
b0522a8abc
All checks were successful
continuous-integration/drone/push Build is passing
136 lines
3.7 KiB
Go
136 lines
3.7 KiB
Go
package dns_api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"git.gammaspectra.live/givna.me/dns-api/ed25519"
|
|
"net/url"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var fieldSeparator = []byte{'\xff', '\xff', '\xff', '\xff'}
|
|
|
|
const (
|
|
ExpirationTimeNever = 0
|
|
KeyPublicKey = "k"
|
|
KeyExpiration = "x"
|
|
KeySignature = "s"
|
|
)
|
|
|
|
func VerifySignatureMessage(method string, host string, requestUrl *url.URL) (ed25519.PublicKey, error) {
|
|
buf, publicKey, expirationTime, err := BuildSignatureMessage(method, host, requestUrl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
signature, err := hex.DecodeString(requestUrl.Query().Get(KeySignature))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(signature) != ed25519.SignatureSize {
|
|
return nil, fmt.Errorf("invalid signature size: expected %d, got %d", ed25519.SignatureSize, len(signature))
|
|
}
|
|
|
|
if expirationTime != ExpirationTimeNever {
|
|
checkTime := time.Now().UTC().Unix()
|
|
if expirationTime < checkTime {
|
|
return nil, fmt.Errorf("expiration verification failed: now %d, expiration %d", checkTime, expirationTime)
|
|
}
|
|
}
|
|
|
|
if ed25519.Verify(publicKey, buf, signature) {
|
|
return publicKey, nil
|
|
} else {
|
|
return nil, fmt.Errorf("signature verification failed")
|
|
}
|
|
}
|
|
|
|
func CreateSignatureMessage(method string, host string, requestUrl *url.URL, privateKey ed25519.PrivateKey, expirationTime time.Time) (*url.URL, error) {
|
|
newRequestUrl, err := url.Parse(requestUrl.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
publicKey := privateKey.Public().(ed25519.PublicKey)
|
|
|
|
newRequestUrl.Query().Set(KeyPublicKey, hex.EncodeToString(publicKey))
|
|
newRequestUrl.Query().Set(KeyExpiration, strconv.FormatInt(expirationTime.UTC().Unix(), 10))
|
|
buf, publicKeyCheck, expirationTimeCheck, err := BuildSignatureMessage(method, host, newRequestUrl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if bytes.Compare(publicKey, publicKeyCheck) != 0 {
|
|
return nil, errors.New("public keys do not match")
|
|
}
|
|
|
|
if expirationTime.UTC().Unix() != expirationTimeCheck {
|
|
return nil, errors.New("expiration times do not match")
|
|
}
|
|
|
|
signature := ed25519.Sign(privateKey, buf)
|
|
newRequestUrl.Query().Set(KeySignature, hex.EncodeToString(signature))
|
|
return newRequestUrl, nil
|
|
}
|
|
|
|
func BuildSignatureMessage(method string, host string, requestUrl *url.URL) ([]byte, ed25519.PublicKey, int64, error) {
|
|
var buf []byte
|
|
|
|
buf = append(buf, []byte(strings.ToUpper(method))...)
|
|
buf = append(buf, fieldSeparator...)
|
|
|
|
publicKey, err := hex.DecodeString(requestUrl.Query().Get(KeyPublicKey))
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
if len(requestUrl.Query()[KeyPublicKey]) > 1 {
|
|
return nil, nil, 0, errors.New("more than one public key given")
|
|
}
|
|
|
|
if len(publicKey) != ed25519.PublicKeySize {
|
|
return nil, nil, 0, fmt.Errorf("invalid public key size: expected %d, got %d", ed25519.PublicKeySize, len(publicKey))
|
|
}
|
|
|
|
expiry, err := strconv.ParseInt(requestUrl.Query().Get(KeyExpiration), 10, 0)
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
if len(requestUrl.Query()[KeyExpiration]) > 1 {
|
|
return nil, nil, 0, errors.New("more than one expiration given")
|
|
}
|
|
|
|
if len(requestUrl.Query()[KeySignature]) > 1 {
|
|
return nil, nil, 0, errors.New("more than one signature given")
|
|
}
|
|
|
|
buf = append(buf, []byte(strings.Split(host, ":")[0])...)
|
|
buf = append(buf, fieldSeparator...)
|
|
buf = append(buf, []byte(requestUrl.Path)...)
|
|
buf = append(buf, fieldSeparator...)
|
|
|
|
var keys []string
|
|
for k, v := range requestUrl.Query() {
|
|
if k == KeyPublicKey || k == KeySignature {
|
|
continue
|
|
}
|
|
for _, value := range v {
|
|
keys = append(keys, url.QueryEscape(k)+"="+url.QueryEscape(value))
|
|
}
|
|
}
|
|
sort.Strings(keys)
|
|
for _, v := range keys {
|
|
buf = append(buf, []byte(v)...)
|
|
buf = append(buf, fieldSeparator...)
|
|
}
|
|
|
|
return buf, publicKey, expiry, nil
|
|
}
|