versia-go/pkg/versia/crypto/signature_data.go

95 lines
2.7 KiB
Go
Raw Normal View History

2024-08-22 23:03:38 +02:00
package versiacrypto
2024-08-15 19:22:17 +02:00
import (
2024-08-20 22:43:26 +02:00
"crypto"
2024-08-15 19:22:17 +02:00
"crypto/ed25519"
"encoding/base64"
"fmt"
"net/url"
2024-08-20 22:43:26 +02:00
"os"
2024-08-15 19:22:17 +02:00
"strings"
)
2024-08-22 23:03:38 +02:00
// SignatureData is a combination of HTTP method, URL (only url.URL#Path and url.URL#RawQuery are required),
// a nonce and the Base64 encoded SHA256 hash of the request body.
// For more information, see the [Spec].
//
// [Spec]: https://versia.pub/signatures
2024-08-15 19:22:17 +02:00
type SignatureData struct {
// RequestMethod is the *lowercase* HTTP method of the request
RequestMethod string
2024-08-22 23:03:38 +02:00
2024-08-15 19:22:17 +02:00
// Nonce is a random byte array, used to prevent replay attacks
Nonce string
2024-08-22 23:03:38 +02:00
2024-08-15 19:22:17 +02:00
// RawPath is the path of the request, without the query string
URL *url.URL
2024-08-22 23:03:38 +02:00
2024-08-15 19:22:17 +02:00
// Digest is the SHA-256 hash of the request body
Digest []byte
}
func NewSignatureData(method, nonce string, u *url.URL, digest []byte) *SignatureData {
return &SignatureData{
RequestMethod: method,
Nonce: nonce,
URL: u,
Digest: digest,
}
}
2024-08-22 23:03:38 +02:00
// String returns the payload to sign
2024-08-15 19:22:17 +02:00
func (s *SignatureData) String() string {
return fmt.Sprintf("%s %s?%s %s %s", strings.ToLower(s.RequestMethod), s.URL.Path, s.URL.RawQuery, s.Nonce, base64.StdEncoding.EncodeToString(s.Digest))
}
2024-08-22 23:03:38 +02:00
// Validate validate that the SignatureData belongs to the provided public key and matches the provided signature.
2024-08-20 22:43:26 +02:00
func (s *SignatureData) Validate(pubKey crypto.PublicKey, signature []byte) bool {
data := []byte(s.String())
2024-08-22 23:03:38 +02:00
verify, err := NewVerify(pubKey)
2024-08-20 22:43:26 +02:00
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
return false
}
return verify(data, signature)
2024-08-15 19:22:17 +02:00
}
2024-08-22 23:03:38 +02:00
// Sign signs the SignatureData with the provided private key.
2024-08-15 19:22:17 +02:00
func (s *SignatureData) Sign(privKey ed25519.PrivateKey) []byte {
return ed25519.Sign(privKey, []byte(s.String()))
}
2024-08-22 23:03:38 +02:00
// Signer is an object, with which requests can be signed with the user's private key.
// For more information, see the [Spec].
//
// [Spec]: https://versia.pub/signatures
2024-08-15 19:22:17 +02:00
type Signer struct {
PrivateKey ed25519.PrivateKey
UserURL *url.URL
}
2024-08-22 23:03:38 +02:00
// Sign signs a signature data and returns the headers to inject into the response.
2024-08-15 19:22:17 +02:00
func (s Signer) Sign(signatureData SignatureData) *FederationHeaders {
return &FederationHeaders{
SignedBy: s.UserURL,
Nonce: signatureData.Nonce,
Signature: signatureData.Sign(s.PrivateKey),
}
}
2024-08-22 23:03:38 +02:00
// Verifier is an object, with which requests can be verified against a user's public key.
// For more information, see the [Spec].
//
// [Spec]: https://versia.pub/signatures
2024-08-15 19:22:17 +02:00
type Verifier struct {
2024-08-20 22:43:26 +02:00
PublicKey crypto.PublicKey
2024-08-15 19:22:17 +02:00
}
2024-08-22 23:03:38 +02:00
// Verify verifies a request against the public key provided to it duration object creation.
2024-08-15 19:22:17 +02:00
func (v Verifier) Verify(method string, u *url.URL, body []byte, fedHeaders *FederationHeaders) bool {
2024-08-22 23:03:38 +02:00
return NewSignatureData(method, fedHeaders.Nonce, u, SHA256(body)).
2024-08-20 22:43:26 +02:00
Validate(v.PublicKey, fedHeaders.Signature)
2024-08-15 19:22:17 +02:00
}