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
|
|
@ -15,7 +15,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidSignature = errors.New("invalid signature")
|
ErrInvalidSignature = errors.New("invalid signature")
|
||||||
|
ErrInvalidOriginHeader = errors.New("invalid origin header")
|
||||||
|
|
||||||
_ validators.RequestValidator = (*RequestValidatorImpl)(nil)
|
_ validators.RequestValidator = (*RequestValidatorImpl)(nil)
|
||||||
)
|
)
|
||||||
|
|
@ -41,6 +42,13 @@ func (i RequestValidatorImpl) Validate(ctx context.Context, r *http.Request) err
|
||||||
defer s.End()
|
defer s.End()
|
||||||
ctx = s.Context()
|
ctx = s.Context()
|
||||||
|
|
||||||
|
origin := r.Header.Get("Origin")
|
||||||
|
if origin != "" {
|
||||||
|
return ErrInvalidOriginHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
l := i.log.WithValues("url", r.URL.Path)
|
||||||
|
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
fedHeaders, err := versiacrypto.ExtractFederationHeaders(r.Header)
|
fedHeaders, err := versiacrypto.ExtractFederationHeaders(r.Header)
|
||||||
|
|
@ -48,23 +56,36 @@ func (i RequestValidatorImpl) Validate(ctx context.Context, r *http.Request) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := i.repositories.Users().Resolve(ctx, versiautils.URLFromStd(fedHeaders.SignedBy))
|
var key *versiacrypto.SPKIPublicKey
|
||||||
if err != nil {
|
var signerURI *versiautils.URL
|
||||||
return err
|
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)
|
body, err := utils.CopyBody(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(versiacrypto.Verifier{PublicKey: user.PublicKey.Key}).Verify(r.Method, r.URL, body, fedHeaders) {
|
if !(versiacrypto.Verifier{PublicKey: key}).Verify(r.Method, r.URL, body, fedHeaders) {
|
||||||
i.log.WithCallDepth(1).Info("signature verification failed", "user", user.URI, "url", r.URL.Path)
|
l.WithCallDepth(1).Info("signature verification failed")
|
||||||
s.CaptureError(ErrInvalidSignature)
|
s.CaptureError(ErrInvalidSignature)
|
||||||
|
|
||||||
return ErrInvalidSignature
|
return ErrInvalidSignature
|
||||||
} else {
|
} 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
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
//
|
//
|
||||||
// [Spec]: https://versia.pub/signatures#signature-definition
|
// [Spec]: https://versia.pub/signatures#signature-definition
|
||||||
type FederationHeaders struct {
|
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
|
SignedBy *url.URL
|
||||||
// Nonce is a random string, used to prevent replay attacks
|
// Nonce is a random string, used to prevent replay attacks
|
||||||
Nonce string
|
Nonce string
|
||||||
|
|
@ -20,14 +20,23 @@ type FederationHeaders struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FederationHeaders) Inject(h http.Header) {
|
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-nonce", f.Nonce)
|
||||||
h.Set("x-signature", base64.StdEncoding.EncodeToString(f.Signature))
|
h.Set("x-signature", base64.StdEncoding.EncodeToString(f.Signature))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FederationHeaders) Headers() map[string]string {
|
func (f *FederationHeaders) Headers() map[string]string {
|
||||||
|
signedBy := "instance"
|
||||||
|
if f.SignedBy != nil {
|
||||||
|
signedBy = f.SignedBy.String()
|
||||||
|
}
|
||||||
|
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"x-signed-by": f.SignedBy.String(),
|
"x-signed-by": signedBy,
|
||||||
"x-nonce": f.Nonce,
|
"x-nonce": f.Nonce,
|
||||||
"x-signature": base64.StdEncoding.EncodeToString(f.Signature),
|
"x-signature": base64.StdEncoding.EncodeToString(f.Signature),
|
||||||
}
|
}
|
||||||
|
|
@ -39,9 +48,15 @@ func ExtractFederationHeaders(h http.Header) (*FederationHeaders, error) {
|
||||||
return nil, fmt.Errorf("missing x-signed-by header")
|
return nil, fmt.Errorf("missing x-signed-by header")
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(signedBy)
|
var u *url.URL
|
||||||
if err != nil {
|
if signedBy == "instance" {
|
||||||
return nil, err
|
// Signed by the instance
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
u, err = url.Parse(signedBy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := h.Get("x-nonce")
|
nonce := h.Get("x-nonce")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package versiacrypto
|
package versiacrypto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"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())
|
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