dns-api/identity.go
DataHoarder b0522a8abc
All checks were successful
continuous-integration/drone/push Build is passing
Fixed message ed25519 usage
2022-06-12 02:21:00 +02:00

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
}