Initial commit
This commit is contained in:
commit
8a475d6114
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/.idea
|
19
LICENSE
Normal file
19
LICENSE
Normal 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
63
api.go
Normal 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
|
136
identity.go
Normal file
136
identity.go
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue