Skip to content

Commit

Permalink
feat: implement support for instance signed requests
Browse files Browse the repository at this point in the history
  • Loading branch information
TheDevMinerTV committed Sep 21, 2024
1 parent b344e5b commit f8221b8
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 13 deletions.
35 changes: 28 additions & 7 deletions internal/validators/val_impls/request_validator_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import (
)

var (
ErrInvalidSignature = errors.New("invalid signature")
ErrInvalidSignature = errors.New("invalid signature")
ErrInvalidOriginHeader = errors.New("invalid origin header")

_ validators.RequestValidator = (*RequestValidatorImpl)(nil)
)
Expand All @@ -41,30 +42,50 @@ 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)
if err != nil {
return err
}

user, err := i.repositories.Users().Resolve(ctx, versiautils.URLFromStd(fedHeaders.SignedBy))
if err != nil {
return err
var key *versiacrypto.SPKIPublicKey
var signerURI *versiautils.URL
if fedHeaders.SignedBy == nil {
metadata, err := i.repositories.InstanceMetadata().GetByHost(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
Expand Down
27 changes: 21 additions & 6 deletions pkg/versia/crypto/federation_headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
}
Expand All @@ -39,9 +48,15 @@ func ExtractFederationHeaders(h http.Header) (*FederationHeaders, error) {
return nil, fmt.Errorf("missing x-signed-by header")
}

u, err := url.Parse(signedBy)
if err != nil {
return nil, err
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")
Expand Down
13 changes: 13 additions & 0 deletions pkg/versia/crypto/federation_headers_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package versiacrypto

import (
"net/http"
"net/url"
"testing"

Expand All @@ -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")
}

0 comments on commit f8221b8

Please sign in to comment.