Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions darepod/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,8 @@ type SwapConfig struct {
// instead of system roots or insecure local credentials.
ServerTLSCertPath string `mapstructure:"servertlscertpath"`

// ServerInsecure disables TLS for the swapdk-server connection. Local
// loopback endpoints are also treated as insecure by default for
// regtest and integration-test ergonomics.
// ServerInsecure disables TLS for the swapdk-server connection. This
// should only be used for explicit regtest/dev deployments.
ServerInsecure bool `mapstructure:"serverinsecure"`

// DatabaseFileName is the daemon-owned swap SQLite database path. When
Expand Down
47 changes: 47 additions & 0 deletions darepod/rpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package darepod
import (
"bytes"
"context"
"crypto/tls"
"database/sql"
"encoding/hex"
"errors"
Expand Down Expand Up @@ -33,6 +34,7 @@ import (
"github.com/lightninglabs/darepo-client/lwwallet"
"github.com/lightninglabs/darepo-client/oor"
"github.com/lightninglabs/darepo-client/round"
"github.com/lightninglabs/darepo-client/serverconn"
"github.com/lightninglabs/darepo-client/unroll"
"github.com/lightninglabs/darepo-client/vtxo"
"github.com/lightninglabs/darepo-client/wallet"
Expand Down Expand Up @@ -100,6 +102,51 @@ func (r *RPCServer) SubLogger(tag string) btclog.Logger {
return r.server.subLogger(tag)
}

// SignMailboxAuth returns a hex-encoded Schnorr mailbox auth signature for
// the daemon identity key bound to the recipient mailbox ID. Optional
// subservers use this to authenticate mailbox RPCs without learning how the
// daemon wallet backend stores or signs with the identity key.
func (r *RPCServer) SignMailboxAuth(ctx context.Context,
recipientMailboxID string) (string, error) {

if r == nil || r.server == nil {
return "", fmt.Errorf("daemon server unavailable")
}
if r.server.clientKeyDesc.PubKey == nil {
return "", fmt.Errorf("identity key not yet derived; wallet " +
"not ready")
}

sig, err := r.server.signMailboxAuth(ctx, recipientMailboxID)
if err != nil {
return "", err
}

return hex.EncodeToString(sig.Serialize()), nil
}

// ClientTLSCerts returns the daemon identity client certificate used when
// optional subservers dial mTLS-protected mailbox edges.
func (r *RPCServer) ClientTLSCerts() ([]tls.Certificate, error) {
if r == nil || r.server == nil {
return nil, fmt.Errorf("daemon server unavailable")
}

if r.server.clientKeyDesc.PubKey == nil {
return nil, fmt.Errorf("identity key not yet derived; wallet " +
"not ready")
}

clientCert, err := serverconn.GenerateClientTLSCert(
r.server.clientKeyDesc.PubKey,
)
if err != nil {
return nil, fmt.Errorf("generate client TLS cert: %w", err)
}

return []tls.Certificate{clientCert}, nil
}

// reserveCustomInputs atomically claims every outpoint in the supplied
// slice. If any outpoint is already reserved by another in-flight call,
// no claims are taken and an error is returned. The returned release
Expand Down
116 changes: 116 additions & 0 deletions serverconn/mailbox_auth_rpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package serverconn

import (
"context"
"fmt"

mailboxpb "github.com/lightninglabs/darepo-client/mailbox/pb"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)

// MailboxAuthSigner returns the hex-encoded x-mailbox-auth-sig value for a
// recipient mailbox ID.
type MailboxAuthSigner func(context.Context, string) (string, error)

// authenticatedMailboxClient adds mailbox auth metadata before forwarding to
// the configured mailbox transport.
type authenticatedMailboxClient struct {
next mailboxpb.MailboxServiceClient
sign MailboxAuthSigner
}

// NewAuthenticatedMailboxClient wraps a mailbox edge with per-mailbox auth
// metadata. Nil signers return next unchanged for unauthenticated test
// transports.
func NewAuthenticatedMailboxClient(next mailboxpb.MailboxServiceClient,
sign MailboxAuthSigner) mailboxpb.MailboxServiceClient {

if sign == nil {
return next
}

return &authenticatedMailboxClient{
next: next,
sign: sign,
}
}

// Send authenticates the sender for the envelope's recipient mailbox.
func (c *authenticatedMailboxClient) Send(ctx context.Context,
req *mailboxpb.SendRequest, opts ...grpc.CallOption) (
*mailboxpb.SendResponse, error) {

recipient := ""
if req != nil && req.Envelope != nil {
recipient = req.Envelope.Recipient
}

ctx, err := c.authContext(ctx, recipient)
if err != nil {
return nil, err
}

return c.next.Send(ctx, req, opts...)
}

// Pull authenticates access to the requested mailbox.
func (c *authenticatedMailboxClient) Pull(ctx context.Context,
req *mailboxpb.PullRequest, opts ...grpc.CallOption) (
*mailboxpb.PullResponse, error) {

recipient := ""
if req != nil {
recipient = req.MailboxId
}

ctx, err := c.authContext(ctx, recipient)
if err != nil {
return nil, err
}

return c.next.Pull(ctx, req, opts...)
}

// AckUpTo authenticates access to the requested mailbox cursor.
func (c *authenticatedMailboxClient) AckUpTo(ctx context.Context,
req *mailboxpb.AckUpToRequest, opts ...grpc.CallOption) (
*mailboxpb.AckUpToResponse, error) {

recipient := ""
if req != nil {
recipient = req.MailboxId
}

ctx, err := c.authContext(ctx, recipient)
if err != nil {
return nil, err
}

return c.next.AckUpTo(ctx, req, opts...)
}

// authContext adds the mailbox auth metadata understood by both gRPC and the
// REST client's outgoing metadata bridge.
func (c *authenticatedMailboxClient) authContext(ctx context.Context,
recipient string) (context.Context, error) {

if recipient == "" {
return nil, fmt.Errorf("mailbox recipient is required")
}

sig, err := c.sign(ctx, recipient)
if err != nil {
return nil, fmt.Errorf("sign mailbox auth: %w", err)
}

md, ok := metadata.FromOutgoingContext(ctx)
if ok {
md = md.Copy()
} else {
md = metadata.New(nil)
}
md.Set(AuthHeaderKey, sig)

return metadata.NewOutgoingContext(ctx, md), nil
}
86 changes: 86 additions & 0 deletions serverconn/mailbox_auth_rpc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package serverconn

import (
"context"
"testing"

mailboxpb "github.com/lightninglabs/darepo-client/mailbox/pb"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)

func TestAuthenticatedMailboxClientAddsMetadata(t *testing.T) {
t.Parallel()

next := &capturingMailboxClient{}
sign := func(_ context.Context, recipient string) (string, error) {
return "auth-" + recipient, nil
}
client := NewAuthenticatedMailboxClient(
next, sign,
)

_, err := client.Pull(t.Context(), &mailboxpb.PullRequest{
MailboxId: "mailbox",
})
require.NoError(t, err)
require.Equal(t, []string{"auth-mailbox"}, next.authHeader)
}

func TestAuthenticatedMailboxClientReplacesMetadata(t *testing.T) {
t.Parallel()

next := &capturingMailboxClient{}
sign := func(_ context.Context, recipient string) (string, error) {
return "auth-" + recipient, nil
}
client := NewAuthenticatedMailboxClient(
next, sign,
)

ctx := metadata.AppendToOutgoingContext(
t.Context(), AuthHeaderKey, "caller-auth",
)
_, err := client.Pull(ctx, &mailboxpb.PullRequest{
MailboxId: "mailbox",
})
require.NoError(t, err)
require.Equal(t, []string{"auth-mailbox"}, next.authHeader)
}

type capturingMailboxClient struct {
authHeader []string
}

func (c *capturingMailboxClient) Send(ctx context.Context,
_ *mailboxpb.SendRequest, _ ...grpc.CallOption) (
*mailboxpb.SendResponse, error) {

c.capture(ctx)

return &mailboxpb.SendResponse{}, nil
}

func (c *capturingMailboxClient) Pull(ctx context.Context,
_ *mailboxpb.PullRequest, _ ...grpc.CallOption) (
*mailboxpb.PullResponse, error) {

c.capture(ctx)

return &mailboxpb.PullResponse{}, nil
}

func (c *capturingMailboxClient) AckUpTo(ctx context.Context,
_ *mailboxpb.AckUpToRequest, _ ...grpc.CallOption) (
*mailboxpb.AckUpToResponse, error) {

c.capture(ctx)

return &mailboxpb.AckUpToResponse{}, nil
}

func (c *capturingMailboxClient) capture(ctx context.Context) {
md, _ := metadata.FromOutgoingContext(ctx)
c.authHeader = md.Get(AuthHeaderKey)
}
Loading
Loading