Initial commit

This commit is contained in:
DataHoarder 2022-06-11 14:38:03 +02:00
commit 8a475d6114
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
5 changed files with 222 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/.idea

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2022 WeebDataHoarder, givna.me Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

63
api.go Normal file
View file

@ -0,0 +1,63 @@
package dns_api
import (
"crypto/ed25519"
"net/http"
"net/url"
"time"
)
type Client struct {
privateKey ed25519.PrivateKey
client *http.Client
RequestExpirationTime time.Duration
}
//NewClient Creates an API client with either a specified http Client, or nil
func NewClient(privateKey ed25519.PrivateKey, client *http.Client) *Client {
if client == nil {
client = http.DefaultClient
}
return &Client{
privateKey: privateKey,
client: client,
}
}
func (c *Client) Do(req *http.Request) (*http.Response, error) {
requestUrl, err := url.Parse(req.URL.String())
if err != nil {
return nil, err
}
requestUrl.Host = req.Host
var expirationTime time.Time
if c.RequestExpirationTime != 0 {
expirationTime = time.Now().Add(c.RequestExpirationTime)
}
newUrl, err := CreateSignatureMessage(req.Method, requestUrl, c.privateKey, expirationTime)
if err != nil {
return nil, err
}
//TODO: WithContext?
return c.client.Do(&http.Request{
Method: req.Method,
URL: newUrl,
Header: req.Header,
Body: req.Body,
GetBody: req.GetBody,
ContentLength: req.ContentLength,
TransferEncoding: req.TransferEncoding,
Close: req.Close,
Host: req.Host,
Form: req.Form,
PostForm: req.PostForm,
MultipartForm: req.MultipartForm,
Trailer: req.Trailer,
Response: req.Response,
})
}
//TODO: server call methods

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module git.gammaspectra.live/givna.me/dns-api
go 1.18

136
identity.go Normal file
View file

@ -0,0 +1,136 @@
package dns_api
import (
"bytes"
"crypto/ed25519"
"encoding/hex"
"errors"
"fmt"
"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, requestUrl *url.URL) (ed25519.PublicKey, error) {
buf, publicKey, expirationTime, err := BuildSignatureMessage(method, 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, 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 := make([]byte, ed25519.PublicKeySize)
copy(publicKey, privateKey[ed25519.PublicKeySize:])
newRequestUrl.Query().Set(KeyPublicKey, hex.EncodeToString(publicKey))
newRequestUrl.Query().Set(KeyExpiration, strconv.FormatInt(expirationTime.UTC().Unix(), 10))
buf, publicKeyCheck, expirationTimeCheck, err := BuildSignatureMessage(method, 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, 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(requestUrl.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
}