mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-15 10:05:42 +00:00
102 lines
3.4 KiB
Go
102 lines
3.4 KiB
Go
package syncapi
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
v1 "github.com/garethgeorge/backrest/gen/go/v1"
|
|
"github.com/garethgeorge/backrest/internal/cryptoutil"
|
|
)
|
|
|
|
func tryReceiveWithinDuration(ctx context.Context, receiveChan chan *v1.SyncStreamItem, receiveErrChan chan error, timeout time.Duration) (*v1.SyncStreamItem, error) {
|
|
if timeout > 0 {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
}
|
|
|
|
select {
|
|
case item := <-receiveChan:
|
|
return item, nil
|
|
case err := <-receiveErrChan:
|
|
return nil, err
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
}
|
|
}
|
|
|
|
func createHandshakePacket(instanceID string, identity *cryptoutil.PrivateKey) (*v1.SyncStreamItem, error) {
|
|
instanceIDBytes := []byte(instanceID)
|
|
instanceIDBytesSignature, err := identity.Sign(instanceIDBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("signing instance ID: %w", err)
|
|
}
|
|
|
|
return &v1.SyncStreamItem{
|
|
Action: &v1.SyncStreamItem_Handshake{
|
|
Handshake: &v1.SyncStreamItem_SyncActionHandshake{
|
|
ProtocolVersion: SyncProtocolVersion,
|
|
InstanceId: &v1.SignedMessage{
|
|
Payload: instanceIDBytes,
|
|
Signature: instanceIDBytesSignature,
|
|
Keyid: identity.KeyID(),
|
|
},
|
|
PublicKey: identity.PublicKeyProto(),
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// verifyHandshakePacket verifies that
|
|
// - the signature on the instance ID is valid against the public key provided in the handshake
|
|
// - that the public key's ID is as attested in the handshake packet e.g. matches handshake.PublicKey.Keyid
|
|
//
|
|
// To authenticate, the caller must then check that the public key is trusted by checking the key ID against a local list.
|
|
func verifyHandshakePacket(item *v1.SyncStreamItem) (*cryptoutil.PublicKey, error) {
|
|
handshake := item.GetHandshake()
|
|
if handshake == nil {
|
|
return nil, fmt.Errorf("empty or nil handshake, handshake packet must be sent first")
|
|
}
|
|
|
|
if handshake.ProtocolVersion != SyncProtocolVersion {
|
|
return nil, fmt.Errorf("protocol version mismatch: expected %d, got %d", SyncProtocolVersion, handshake.ProtocolVersion)
|
|
}
|
|
|
|
if len(handshake.InstanceId.GetPayload()) == 0 || len(handshake.InstanceId.GetSignature()) == 0 {
|
|
return nil, errors.New("instance ID payload and signature must not be empty")
|
|
}
|
|
|
|
if len(handshake.PublicKey.Keyid) == 0 {
|
|
return nil, errors.New("public key ID must not be empty")
|
|
}
|
|
|
|
peerKey, err := cryptoutil.NewPublicKey(handshake.PublicKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("loading peer public key: %w", err)
|
|
}
|
|
|
|
if err := peerKey.Verify(handshake.InstanceId.GetPayload(), handshake.InstanceId.GetSignature()); err != nil {
|
|
return nil, fmt.Errorf("verifying instance ID: %w", err)
|
|
}
|
|
|
|
return peerKey, nil
|
|
}
|
|
|
|
// authorizeHandshakeAsPeer checks that the handshake packet has the expected key ID and instance ID.
|
|
// If this succeeds and the handshake is verified, then it is safe to assume the identity we are talking to.
|
|
func authorizeHandshakeAsPeer(item *v1.SyncStreamItem, peer *v1.Multihost_Peer) error {
|
|
handshake := item.GetHandshake()
|
|
if handshake == nil {
|
|
return fmt.Errorf("empty or nil handshake, handshake packet must be sent first")
|
|
}
|
|
if handshake.GetPublicKey().GetKeyid() != peer.Keyid {
|
|
return fmt.Errorf("public key ID mismatch: expected %s, got %s", peer.Keyid, handshake.PublicKey.Keyid)
|
|
}
|
|
if string(handshake.GetInstanceId().GetPayload()) != peer.InstanceId {
|
|
return fmt.Errorf("instance ID mismatch: expected %s, got %s", peer.InstanceId, string(handshake.InstanceId.GetPayload()))
|
|
}
|
|
return nil
|
|
}
|