mirror of
https://github.com/versia-pub/versia-go.git
synced 2025-12-06 06:28:18 +01:00
feat: implement support for instance signed requests
This commit is contained in:
parent
95cff10def
commit
bb8cace462
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
var (
|
||||
ErrInvalidSignature = errors.New("invalid signature")
|
||||
ErrInvalidOriginHeader = errors.New("invalid origin header")
|
||||
|
||||
_ validators.RequestValidator = (*RequestValidatorImpl)(nil)
|
||||
)
|
||||
|
|
@ -41,6 +42,13 @@ func (i RequestValidatorImpl) Validate(ctx context.Context, r *http.Request) err
|
|||
defer s.End()
|
||||
ctx = s.Context()
|
||||
|
||||
origin := r.Header.Get("Origin")
|
||||
if origin != "" {
|
||||
return ErrInvalidOriginHeader
|
||||
}
|
||||
|
||||
l := i.log.WithValues("url", r.URL.Path)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
fedHeaders, err := versiacrypto.ExtractFederationHeaders(r.Header)
|
||||
|
|
@ -48,23 +56,36 @@ func (i RequestValidatorImpl) Validate(ctx context.Context, r *http.Request) err
|
|||
return err
|
||||
}
|
||||
|
||||
var key *versiacrypto.SPKIPublicKey
|
||||
var signerURI *versiautils.URL
|
||||
if fedHeaders.SignedBy == nil {
|
||||
metadata, err := i.repositories.InstanceMetadata().Resolve(ctx, origin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signerURI = metadata.URI
|
||||
} else {
|
||||
user, err := i.repositories.Users().Resolve(ctx, versiautils.URLFromStd(fedHeaders.SignedBy))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signerURI = user.URI
|
||||
}
|
||||
|
||||
l = l.WithValues("signer", signerURI)
|
||||
|
||||
body, err := utils.CopyBody(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !(versiacrypto.Verifier{PublicKey: user.PublicKey.Key}).Verify(r.Method, r.URL, body, fedHeaders) {
|
||||
i.log.WithCallDepth(1).Info("signature verification failed", "user", user.URI, "url", r.URL.Path)
|
||||
if !(versiacrypto.Verifier{PublicKey: key}).Verify(r.Method, r.URL, body, fedHeaders) {
|
||||
l.WithCallDepth(1).Info("signature verification failed")
|
||||
s.CaptureError(ErrInvalidSignature)
|
||||
|
||||
return ErrInvalidSignature
|
||||
} else {
|
||||
i.log.V(2).Info("signature verification succeeded", "user", user.URI, "url", r.URL.Path)
|
||||
l.V(2).Info("signature verification succeeded")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
//
|
||||
// [Spec]: https://versia.pub/signatures#signature-definition
|
||||
type FederationHeaders struct {
|
||||
// SignedBy is the URL to a user
|
||||
// SignedBy is the URL to a user, or `nil` when the signature was created by the instance's privatekey
|
||||
SignedBy *url.URL
|
||||
// Nonce is a random string, used to prevent replay attacks
|
||||
Nonce string
|
||||
|
|
@ -20,14 +20,23 @@ type FederationHeaders struct {
|
|||
}
|
||||
|
||||
func (f *FederationHeaders) Inject(h http.Header) {
|
||||
h.Set("x-signed-by", f.SignedBy.String())
|
||||
signedBy := "instance"
|
||||
if f.SignedBy != nil {
|
||||
signedBy = f.SignedBy.String()
|
||||
}
|
||||
h.Set("x-signed-by", signedBy)
|
||||
h.Set("x-nonce", f.Nonce)
|
||||
h.Set("x-signature", base64.StdEncoding.EncodeToString(f.Signature))
|
||||
}
|
||||
|
||||
func (f *FederationHeaders) Headers() map[string]string {
|
||||
signedBy := "instance"
|
||||
if f.SignedBy != nil {
|
||||
signedBy = f.SignedBy.String()
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"x-signed-by": f.SignedBy.String(),
|
||||
"x-signed-by": signedBy,
|
||||
"x-nonce": f.Nonce,
|
||||
"x-signature": base64.StdEncoding.EncodeToString(f.Signature),
|
||||
}
|
||||
|
|
@ -39,10 +48,16 @@ func ExtractFederationHeaders(h http.Header) (*FederationHeaders, error) {
|
|||
return nil, fmt.Errorf("missing x-signed-by header")
|
||||
}
|
||||
|
||||
u, err := url.Parse(signedBy)
|
||||
var u *url.URL
|
||||
if signedBy == "instance" {
|
||||
// Signed by the instance
|
||||
} else {
|
||||
var err error
|
||||
u, err = url.Parse(signedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
nonce := h.Get("x-nonce")
|
||||
if nonce == "" {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package versiacrypto
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
|
|
@ -17,3 +18,15 @@ func TestFederationHeaders_String(t *testing.T) {
|
|||
|
||||
assert.Equal(t, "post /users/bob?z=foo&a=bar 1234567890 LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=", one.String())
|
||||
}
|
||||
|
||||
func TestFederationHeaders_Headers(t *testing.T) {
|
||||
headers, err := ExtractFederationHeaders(http.Header{
|
||||
"X-Signed-By": []string{"instance"},
|
||||
"X-Nonce": []string{"11"},
|
||||
"X-Signature": []string{"Cg=="},
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Nil(t, headers.SignedBy, "the SignedBy field should be nil when the signer is the instance")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue