reorganize everything

This commit is contained in:
Dustin Stiles 2025-03-09 15:52:33 -04:00
parent 2b291900b9
commit 2d3631af45
Signed by: duwstiles
GPG Key ID: BCD9912EC231FC87
20 changed files with 135 additions and 145 deletions

View File

@ -33,13 +33,13 @@ cd dsfx
Build the project: Build the project:
```sh ```sh
go build -o dist/ ./... go build -o dist/ ./cmd/...
``` ```
You can also install the executables to your $GOPATH/bin: You can also install the executables to your $GOPATH/bin:
```sh ```sh
go install ./... go install ./cmd/...
``` ```
--- ---
@ -48,13 +48,13 @@ go install ./...
### Starting the Server ### Starting the Server
The dsfx-server requires a listening host, port, and an identity key (ECDSA private key in PEM format). For example: The dsfxnode requires a listening host, port, and an identity key (ECDSA private key in PEM format). For example:
```sh ```sh
dsfx-server -host localhost -port 8000 -key /path/to/serverkey.pem dsfxnode -host localhost -port 8000 -key /path/to/serverkey.pem
``` ```
> Note, if you need to generate a new ECDSA key, you can use the following command: `go run ./cmd/genkey > path/to/masterkey.pem` > Note, if you need to generate a new ECDSA key, you can use the following command: `go run ./tool/genkey > path/to/masterkey.pem`
Command-line flags for dsfx-server: Command-line flags for dsfx-server:
@ -69,14 +69,14 @@ File path to the PEM-encoded ECDSA private key that serves as the servers mas
Once started, the server will bind to the specified host and port and wait for incoming secure file exchange (or other test) connections. When a client connects, the initial payload (up to 1024 bytes) from the client is read and logged. Once started, the server will bind to the specified host and port and wait for incoming secure file exchange (or other test) connections. When a client connects, the initial payload (up to 1024 bytes) from the client is read and logged.
### Running the Client ### Running the Admin Client
The dsfx-client uses a private key for the client (also an ECDSA key in PEM format) and currently supports only the “test” command for checking connectivity to the server. The dsfxctl uses a private key for the client (also an ECDSA key in PEM format) and currently supports only the “test” command for checking connectivity to the server.
Client command usage: Client command usage:
```sh ```sh
/dsfx-client -key /path/to/clientkey.pem test <remote_addr> dsfxctl -key /path/to/clientkey.pem test <remote_addr>
``` ```
Where: Where:
@ -96,7 +96,7 @@ For example, `dsfx://127.0.0.1:8000#<base64 pubkey>” or “dsfx://127.0.0.1:80
Example: Example:
```sh ```sh
dsfx-client -key ./dsfx-client/masterkey test dsfx://127.0.0.1:8000#eyJuIjoiLy8v.. dsfxctl -key ./dsfx-client/masterkey test dsfx://127.0.0.1:8000#eyJuIjoiLy8v..
``` ```
If no command or an unrecognized command is provided, the client will print a brief usage message and exit. If no command or an unrecognized command is provided, the client will print a brief usage message and exit.
@ -106,8 +106,8 @@ If no command or an unrecognized command is provided, the client will print a br
For quick help, simply pass the -h flag: For quick help, simply pass the -h flag:
```sh ```sh
dsfx-server -h dsfxctl -h
dsfx-client -h dsfxnode -h
``` ```
This will display the usage information along with available flags. This will display the usage information along with available flags.

9
cmd/dsfx/main.go Normal file
View File

@ -0,0 +1,9 @@
package main
import (
"log"
)
func main() {
log.Println("hello from dsfx!")
}

View File

@ -9,9 +9,9 @@ import (
"net" "net"
"os" "os"
"koti.casa/numenor-labs/dsfx/shared/dcrypto" "koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
"koti.casa/numenor-labs/dsfx/shared/dlog" "koti.casa/numenor-labs/dsfx/pkg/logging"
"koti.casa/numenor-labs/dsfx/shared/dnet" "koti.casa/numenor-labs/dsfx/pkg/network"
) )
func main() { func main() {
@ -31,13 +31,13 @@ func main() {
// where the context is not available, or the context is not a child of the // where the context is not available, or the context is not a child of the
// context with the logger, the default logger will be used. // context with the logger, the default logger will be used.
slog.SetDefault(logger) slog.SetDefault(logger)
ctx = dlog.WithContext(ctx, logger) ctx = logging.WithContext(ctx, logger)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Commands // Commands
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [command] [args]\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Usage: dsfxctl [command] [args]\n")
fmt.Fprintf(os.Stderr, "Commands:\n") fmt.Fprintf(os.Stderr, "Commands:\n")
fmt.Fprintf(os.Stderr, " test <remote_addr> Test the connection to the server\n") fmt.Fprintf(os.Stderr, " test <remote_addr> Test the connection to the server\n")
fmt.Fprintf(os.Stderr, "Flags:\n") fmt.Fprintf(os.Stderr, "Flags:\n")
@ -53,13 +53,13 @@ func main() {
os.Exit(1) os.Exit(1)
} }
identity, err := dcrypto.LoadSigningKeyFromFile(*flagKey) identity, err := identity.LoadSigningKeyFromFile(*flagKey)
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to load private key", slog.Any("error", err)) logger.ErrorContext(ctx, "failed to load private key", slog.Any("error", err))
os.Exit(1) os.Exit(1)
} }
laddr := dnet.NewAddr( laddr := network.NewAddr(
net.ParseIP("0.0.0.0"), net.ParseIP("0.0.0.0"),
0, // port 0 means any available port 0, // port 0 means any available port
&identity.PublicKey, &identity.PublicKey,
@ -84,16 +84,16 @@ func main() {
} }
} }
func testConnection(ctx context.Context, identity *ecdsa.PrivateKey, laddr *dnet.Addr, raddrRaw string) { func testConnection(ctx context.Context, identity *ecdsa.PrivateKey, laddr *network.Addr, raddrRaw string) {
logger := dlog.FromContext(context.Background()) logger := logging.FromContext(context.Background())
raddr, err := dnet.ParseAddr(raddrRaw) raddr, err := network.ParseAddr(raddrRaw)
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to parse server address", slog.Any("error", err)) logger.ErrorContext(ctx, "failed to parse server address", slog.Any("error", err))
return return
} }
conn, err := dnet.Dial(ctx, identity, laddr, raddr) conn, err := network.Dial(ctx, identity, laddr, raddr)
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to connect", slog.Any("error", err)) logger.ErrorContext(ctx, "failed to connect", slog.Any("error", err))
return return

View File

@ -8,9 +8,9 @@ import (
"net" "net"
"os" "os"
"koti.casa/numenor-labs/dsfx/shared/dcrypto" "koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
"koti.casa/numenor-labs/dsfx/shared/dlog" "koti.casa/numenor-labs/dsfx/pkg/logging"
"koti.casa/numenor-labs/dsfx/shared/dnet" "koti.casa/numenor-labs/dsfx/pkg/network"
) )
var ( var (
@ -36,7 +36,7 @@ func main() {
// where the context is not available, or the context is not a child of the // where the context is not available, or the context is not a child of the
// context with the logger, the default logger will be used. // context with the logger, the default logger will be used.
slog.SetDefault(logger) slog.SetDefault(logger)
ctx = dlog.WithContext(ctx, logger) ctx = logging.WithContext(ctx, logger)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Flags // Flags
@ -48,7 +48,7 @@ func main() {
os.Exit(1) os.Exit(1)
} }
masterKey, err := dcrypto.LoadSigningKeyFromFile(*flagKey) masterKey, err := identity.LoadSigningKeyFromFile(*flagKey)
if err != nil { if err != nil {
logger.ErrorContext(ctx, "failed to load master key", slog.Any("error", err)) logger.ErrorContext(ctx, "failed to load master key", slog.Any("error", err))
os.Exit(1) os.Exit(1)
@ -60,12 +60,12 @@ func main() {
slog.ErrorContext(ctx, "invalid host or port") slog.ErrorContext(ctx, "invalid host or port")
os.Exit(1) os.Exit(1)
} }
addr := dnet.FromTCPAddr(tcpAddr, &masterKey.PublicKey) addr := network.FromTCPAddr(tcpAddr, &masterKey.PublicKey)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Listener // Listener
listener, err := dnet.Listen(ctx, masterKey, addr) listener, err := network.Listen(ctx, masterKey, addr)
if err != nil { if err != nil {
logger.ErrorContext(ctx, "listener error", slog.Any("error", err)) logger.ErrorContext(ctx, "listener error", slog.Any("error", err))
os.Exit(1) os.Exit(1)
@ -87,7 +87,7 @@ func main() {
func handleConnection(ctx context.Context, conn net.Conn) error { func handleConnection(ctx context.Context, conn net.Conn) error {
defer conn.Close() defer conn.Close()
logger := dlog.FromContext(ctx) logger := logging.FromContext(ctx)
msg := make([]byte, 1024) msg := make([]byte, 1024)
n, err := conn.Read(msg) n, err := conn.Read(msg)

View File

@ -1,4 +1,4 @@
package dcrypto package encryption
import ( import (
"crypto/aes" "crypto/aes"

View File

@ -1,4 +1,4 @@
package dcrypto package identity
import ( import (
"crypto/ecdsa" "crypto/ecdsa"

View File

@ -1,8 +1,10 @@
package dcrypto package keyexchange
import ( import (
"crypto/ecdh" "crypto/ecdh"
"crypto/hkdf"
"crypto/rand" "crypto/rand"
"crypto/sha256"
) )
var ( var (
@ -17,7 +19,17 @@ func GenerateDHKey() (*ecdh.PrivateKey, error) {
// ComputeDHSecret computes the shared secret from the private key and the public key. // ComputeDHSecret computes the shared secret from the private key and the public key.
func ComputeDHSecret(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) ([]byte, error) { func ComputeDHSecret(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) ([]byte, error) {
return priv.ECDH(pub) secret, err := priv.ECDH(pub)
if err != nil {
return nil, err
}
key, err := hkdf.Key(sha256.New, secret, nil, "", 32)
if err != nil {
return nil, err
}
return key, nil
} }
// ExportDHPublicKey exports the public key as a byte slice. // ExportDHPublicKey exports the public key as a byte slice.

View File

@ -1,4 +1,4 @@
package dnet package frame
import ( import (
"encoding/binary" "encoding/binary"

View File

@ -1,11 +1,11 @@
package dnet_test package frame_test
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"testing" "testing"
"koti.casa/numenor-labs/dsfx/shared/dnet" "koti.casa/numenor-labs/dsfx/pkg/frame"
) )
func TestLenPrefixedWriteTo(t *testing.T) { func TestLenPrefixedWriteTo(t *testing.T) {
@ -16,7 +16,7 @@ func TestLenPrefixedWriteTo(t *testing.T) {
// When ... // When ...
n, err := dnet.NewFrame(msg).WriteTo(buf) n, err := frame.NewFrame(msg).WriteTo(buf)
// Then ... // Then ...
@ -49,7 +49,7 @@ func TestLenPrefixedReadFrom(t *testing.T) {
expectedBytesRead := len(msg) expectedBytesRead := len(msg)
// When ... // When ...
f := dnet.NewFrame(nil) f := frame.NewFrame(nil)
n, err := f.ReadFrom(buf) n, err := f.ReadFrom(buf)
// Then ... // Then ...

View File

@ -1,4 +1,4 @@
package dnet package handshake
import ( import (
"bytes" "bytes"
@ -11,8 +11,11 @@ import (
"log/slog" "log/slog"
"koti.casa/numenor-labs/dsfx/pkg/assert" "koti.casa/numenor-labs/dsfx/pkg/assert"
"koti.casa/numenor-labs/dsfx/shared/dcrypto" "koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
"koti.casa/numenor-labs/dsfx/shared/dlog" "koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
"koti.casa/numenor-labs/dsfx/pkg/crypto/keyexchange"
"koti.casa/numenor-labs/dsfx/pkg/frame"
"koti.casa/numenor-labs/dsfx/pkg/logging"
) )
const ( const (
@ -34,14 +37,14 @@ func Handshake(
lPrivKey *ecdsa.PrivateKey, lPrivKey *ecdsa.PrivateKey,
rPubKey *ecdsa.PublicKey, rPubKey *ecdsa.PublicKey,
) ([]byte, error) { ) ([]byte, error) {
logger := dlog.FromContext(ctx).WithGroup("handshake") logger := logging.FromContext(ctx).WithGroup("handshake")
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Step 1: Ephemeral Key Exchange To Server // Step 1: Ephemeral Key Exchange To Server
logger.DebugContext(ctx, "creating dh key") logger.DebugContext(ctx, "creating dh key")
// Create a new ECDH private key for the actor. // Create a new ECDH private key for the actor.
ourDHKey, err := dcrypto.GenerateDHKey() ourDHKey, err := keyexchange.GenerateDHKey()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -49,7 +52,7 @@ func Handshake(
logger.DebugContext(ctx, "exporting dh key") logger.DebugContext(ctx, "exporting dh key")
// Export the public key of the actor's ECDH private key. // Export the public key of the actor's ECDH private key.
ourDHKeyRaw, err := dcrypto.ExportDHPublicKey(ourDHKey.PublicKey()) ourDHKeyRaw, err := keyexchange.ExportDHPublicKey(ourDHKey.PublicKey())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -57,7 +60,7 @@ func Handshake(
// Write the actor's public key to the connection. // Write the actor's public key to the connection.
logger.DebugContext(ctx, "sending dh key", slog.Int("key.size", len(ourDHKeyRaw))) logger.DebugContext(ctx, "sending dh key", slog.Int("key.size", len(ourDHKeyRaw)))
_, err = NewFrame(ourDHKeyRaw).WriteTo(conn) _, err = frame.NewFrame(ourDHKeyRaw).WriteTo(conn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,7 +70,7 @@ func Handshake(
// Read the remote actor's public key from the connection. // Read the remote actor's public key from the connection.
logger.DebugContext(ctx, "waiting for server's dh key") logger.DebugContext(ctx, "waiting for server's dh key")
remoteDHKeyFrame := NewFrame(nil) remoteDHKeyFrame := frame.NewFrame(nil)
_, err = remoteDHKeyFrame.ReadFrom(conn) _, err = remoteDHKeyFrame.ReadFrom(conn)
if err != nil { if err != nil {
return nil, err return nil, err
@ -78,7 +81,7 @@ func Handshake(
// Import the remote actor's public key. // Import the remote actor's public key.
logger.DebugContext(ctx, "importing server's dh key") logger.DebugContext(ctx, "importing server's dh key")
remoteDHKey, err := dcrypto.ImportDHPublicKey(remoteDHKeyFrame.Contents()) remoteDHKey, err := keyexchange.ImportDHPublicKey(remoteDHKeyFrame.Contents())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -88,13 +91,13 @@ func Handshake(
// Export the public key of the actor's signing key. // Export the public key of the actor's signing key.
logger.DebugContext(ctx, "exporting public signing key") logger.DebugContext(ctx, "exporting public signing key")
ourPublicKeyRaw, err := dcrypto.ExportPublicSigningKey(&lPrivKey.PublicKey) ourPublicKeyRaw, err := identity.ExportPublicSigningKey(&lPrivKey.PublicKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
logger.DebugContext(ctx, "exporting remote public signing key") logger.DebugContext(ctx, "exporting remote public signing key")
remotePublicKeyRaw, err := dcrypto.ExportPublicSigningKey(rPubKey) remotePublicKeyRaw, err := identity.ExportPublicSigningKey(rPubKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -114,26 +117,18 @@ func Handshake(
// Sign the message with the actor's private key. // Sign the message with the actor's private key.
logger.DebugContext(ctx, "signing authentication message") logger.DebugContext(ctx, "signing authentication message")
signature, err := dcrypto.Sign(lPrivKey, authMessage) signature, err := identity.Sign(lPrivKey, authMessage)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Compute the shared secret between the actor and the remote actor. // Compute the shared secret between the actor and the remote actor.
logger.DebugContext(ctx, "computing shared secret") logger.DebugContext(ctx, "computing shared secret")
sharedSecret, err := dcrypto.ComputeDHSecret(ourDHKey, remoteDHKey) derivedKey, err := keyexchange.ComputeDHSecret(ourDHKey, remoteDHKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
assert.Assert(len(sharedSecret) == 48, "invalid shared secret size") assert.Assert(len(derivedKey) == 32, "invalid shared secret size")
// Derive a key from the shared secret using HKDF.
logger.DebugContext(ctx, "deriving key from shared secret")
derivedKey, err := dcrypto.Key(sharedSecret, nil)
if err != nil {
return nil, err
}
assert.Assert(len(derivedKey) == 32, "invalid derived key size")
plaintext := make([]byte, 0, len(ourPublicKeyRaw)+len(signature)) plaintext := make([]byte, 0, len(ourPublicKeyRaw)+len(signature))
plaintext = append(plaintext, ourPublicKeyRaw...) plaintext = append(plaintext, ourPublicKeyRaw...)
@ -141,14 +136,14 @@ func Handshake(
// Encrypt the message with the derived key. // Encrypt the message with the derived key.
logger.DebugContext(ctx, "encrypting authentication message") logger.DebugContext(ctx, "encrypting authentication message")
boxedMsg, err := dcrypto.Encrypt(derivedKey, plaintext) boxedMsg, err := encryption.Encrypt(derivedKey, plaintext)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Write the boxed message to the connection. // Write the boxed message to the connection.
logger.DebugContext(ctx, "sending authentication message", slog.Int("message.size", len(boxedMsg))) logger.DebugContext(ctx, "sending authentication message", slog.Int("message.size", len(boxedMsg)))
_, err = NewFrame(boxedMsg).WriteTo(conn) _, err = frame.NewFrame(boxedMsg).WriteTo(conn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -158,7 +153,7 @@ func Handshake(
// Read the authentication message from the connection. // Read the authentication message from the connection.
logger.DebugContext(ctx, "waiting for server's authentication message") logger.DebugContext(ctx, "waiting for server's authentication message")
authMessageFrame := NewFrame(nil) authMessageFrame := frame.NewFrame(nil)
n, err := authMessageFrame.ReadFrom(conn) n, err := authMessageFrame.ReadFrom(conn)
if err != nil { if err != nil {
return nil, err return nil, err
@ -167,7 +162,7 @@ func Handshake(
// Decrypt the authentication message with the derived key. // Decrypt the authentication message with the derived key.
logger.DebugContext(ctx, "decrypting authentication message") logger.DebugContext(ctx, "decrypting authentication message")
plaintext, err = dcrypto.Decrypt(derivedKey, authMessageFrame.Contents()) plaintext, err = encryption.Decrypt(derivedKey, authMessageFrame.Contents())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -175,20 +170,20 @@ func Handshake(
// The server authentication is just verifying the signature it created of // The server authentication is just verifying the signature it created of
// the client authentication message. // the client authentication message.
logger.DebugContext(ctx, "importing server's public signing key") logger.DebugContext(ctx, "importing server's public signing key")
remotePublicKey, err := dcrypto.ImportPublicSigningKey(remotePublicKeyRaw) remotePublicKey, err := identity.ImportPublicSigningKey(remotePublicKeyRaw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
logger.DebugContext(ctx, "verifying server's signature") logger.DebugContext(ctx, "verifying server's signature")
if !dcrypto.Verify(remotePublicKey, authMessage, plaintext) { if !identity.Verify(remotePublicKey, authMessage, plaintext) {
return nil, errors.New("failed to verify server's signature") return nil, errors.New("failed to verify server's signature")
} }
// Finally, we need to let the server know that the handshake is complete. // Finally, we need to let the server know that the handshake is complete.
logger.DebugContext(ctx, "sending handshake complete message") logger.DebugContext(ctx, "sending handshake complete message")
handshakeCompleteMsg := []byte{0x01} handshakeCompleteMsg := []byte{0x01}
_, err = NewFrame(handshakeCompleteMsg).WriteTo(conn) _, err = frame.NewFrame(handshakeCompleteMsg).WriteTo(conn)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -200,14 +195,14 @@ func Handshake(
// AcceptHandshake accepts a handshake from the given actor and connection. It // AcceptHandshake accepts a handshake from the given actor and connection. It
// returns the shared secret between the actor and the remote actor. // returns the shared secret between the actor and the remote actor.
func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecdsa.PrivateKey) (*ecdsa.PublicKey, []byte, error) { func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecdsa.PrivateKey) (*ecdsa.PublicKey, []byte, error) {
logger := dlog.FromContext(ctx).WithGroup("handshake") logger := logging.FromContext(ctx).WithGroup("handshake")
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// Step 1: Ephemeral Key Exchange From Client // Step 1: Ephemeral Key Exchange From Client
// Read the remote actor's public key from the connection. // Read the remote actor's public key from the connection.
logger.DebugContext(ctx, "waiting for client's dh key") logger.DebugContext(ctx, "waiting for client's dh key")
remoteDHKeyFrame := NewFrame(nil) remoteDHKeyFrame := frame.NewFrame(nil)
_, err := remoteDHKeyFrame.ReadFrom(conn) _, err := remoteDHKeyFrame.ReadFrom(conn)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -215,7 +210,7 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
// Import the remote actor's public key. // Import the remote actor's public key.
logger.DebugContext(ctx, "importing client's dh key") logger.DebugContext(ctx, "importing client's dh key")
remoteDHKey, err := dcrypto.ImportDHPublicKey(remoteDHKeyFrame.Contents()) remoteDHKey, err := keyexchange.ImportDHPublicKey(remoteDHKeyFrame.Contents())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -225,21 +220,21 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
// Create a new ECDH private key for the actor. // Create a new ECDH private key for the actor.
logger.DebugContext(ctx, "creating dh key") logger.DebugContext(ctx, "creating dh key")
ourDHKey, err := dcrypto.GenerateDHKey() ourDHKey, err := keyexchange.GenerateDHKey()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// Export the public key of the actor's ECDH private key. // Export the public key of the actor's ECDH private key.
logger.DebugContext(ctx, "exporting dh key") logger.DebugContext(ctx, "exporting dh key")
ourDHKeyRaw, err := dcrypto.ExportDHPublicKey(ourDHKey.PublicKey()) ourDHKeyRaw, err := keyexchange.ExportDHPublicKey(ourDHKey.PublicKey())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// Write the actor's public key to the connection. // Write the actor's public key to the connection.
logger.DebugContext(ctx, "sending dh key", slog.Int("key.size", len(ourDHKeyRaw))) logger.DebugContext(ctx, "sending dh key", slog.Int("key.size", len(ourDHKeyRaw)))
_, err = NewFrame(ourDHKeyRaw).WriteTo(conn) _, err = frame.NewFrame(ourDHKeyRaw).WriteTo(conn)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -249,7 +244,7 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
// Read the authentication message from the connection. // Read the authentication message from the connection.
logger.DebugContext(ctx, "waiting for client's authentication message") logger.DebugContext(ctx, "waiting for client's authentication message")
authMessageFrame := NewFrame(nil) authMessageFrame := frame.NewFrame(nil)
n, err := authMessageFrame.ReadFrom(conn) n, err := authMessageFrame.ReadFrom(conn)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -258,20 +253,13 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
// Decrypt the authentication message with the derived key. // Decrypt the authentication message with the derived key.
logger.DebugContext(ctx, "computing shared secret") logger.DebugContext(ctx, "computing shared secret")
sharedSecret, err := dcrypto.ComputeDHSecret(ourDHKey, remoteDHKey) derivedKey, err := keyexchange.ComputeDHSecret(ourDHKey, remoteDHKey)
if err != nil {
return nil, nil, err
}
// Derive a key from the shared secret using HKDF.
logger.DebugContext(ctx, "deriving key from shared secret")
derivedKey, err := dcrypto.Key(sharedSecret, nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
logger.DebugContext(ctx, "decrypting authentication message") logger.DebugContext(ctx, "decrypting authentication message")
plaintext, err := dcrypto.Decrypt(derivedKey, authMessageFrame.Contents()) plaintext, err := encryption.Decrypt(derivedKey, authMessageFrame.Contents())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -281,7 +269,7 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
// Verify the client's public key and signature. // Verify the client's public key and signature.
logger.DebugContext(ctx, "importing client's public signing key") logger.DebugContext(ctx, "importing client's public signing key")
clientPublicKey, err := dcrypto.ImportPublicSigningKey(clientPublicKeyRaw) clientPublicKey, err := identity.ImportPublicSigningKey(clientPublicKeyRaw)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -300,7 +288,7 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
} }
logger.DebugContext(ctx, "verifying client's signature") logger.DebugContext(ctx, "verifying client's signature")
if !dcrypto.Verify(clientPublicKey, authMessage, signature) { if !identity.Verify(clientPublicKey, authMessage, signature) {
return nil, nil, errors.New("failed to verify client's signature") return nil, nil, errors.New("failed to verify client's signature")
} }
@ -308,27 +296,27 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
// key. This will be sent back to the client in the next step to authenticate // key. This will be sent back to the client in the next step to authenticate
// the server to the client. // the server to the client.
logger.DebugContext(ctx, "signing authentication message") logger.DebugContext(ctx, "signing authentication message")
serverSignature, err := dcrypto.Sign(lPrivKey, authMessage) serverSignature, err := identity.Sign(lPrivKey, authMessage)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
logger.DebugContext(ctx, "encrypting server's signature") logger.DebugContext(ctx, "encrypting server's signature")
boxedMsg, err := dcrypto.Encrypt(derivedKey, serverSignature) boxedMsg, err := encryption.Encrypt(derivedKey, serverSignature)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
// Send the server's signature back to the client. // Send the server's signature back to the client.
logger.DebugContext(ctx, "sending authentication message", slog.Int("message.size", len(boxedMsg))) logger.DebugContext(ctx, "sending authentication message", slog.Int("message.size", len(boxedMsg)))
_, err = NewFrame(boxedMsg).WriteTo(conn) _, err = frame.NewFrame(boxedMsg).WriteTo(conn)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
logger.DebugContext(ctx, "waiting for handshake complete message") logger.DebugContext(ctx, "waiting for handshake complete message")
// Read the handshake complete message from the client. // Read the handshake complete message from the client.
handshakeCompleteFrame := NewFrame(nil) handshakeCompleteFrame := frame.NewFrame(nil)
_, err = handshakeCompleteFrame.ReadFrom(conn) _, err = handshakeCompleteFrame.ReadFrom(conn)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -345,11 +333,11 @@ func AcceptHandshake(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecd
} }
func buildMessage(clientPubKey *ecdh.PublicKey, serverPubKey *ecdh.PublicKey) ([]byte, error) { func buildMessage(clientPubKey *ecdh.PublicKey, serverPubKey *ecdh.PublicKey) ([]byte, error) {
clientPubKeyRaw, err := dcrypto.ExportDHPublicKey(clientPubKey) clientPubKeyRaw, err := keyexchange.ExportDHPublicKey(clientPubKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
serverPubKeyRaw, err := dcrypto.ExportDHPublicKey(serverPubKey) serverPubKeyRaw, err := keyexchange.ExportDHPublicKey(serverPubKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,4 +1,4 @@
package dnet_test package handshake_test
import ( import (
"bytes" "bytes"
@ -10,17 +10,17 @@ import (
"sync" "sync"
"testing" "testing"
"koti.casa/numenor-labs/dsfx/shared/dcrypto" "koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
"koti.casa/numenor-labs/dsfx/shared/dnet" "koti.casa/numenor-labs/dsfx/pkg/handshake"
) )
func TestHandshake(t *testing.T) { func TestHandshake(t *testing.T) {
ctx := context.Background() ctx := context.Background()
// alice, represented by an ecdsa key pair. // alice, represented by an ecdsa key pair.
alice, _ := dcrypto.GenerateSigningKey() alice, _ := identity.GenerateSigningKey()
// bob, also represented by an ecdsa key pair. // bob, also represented by an ecdsa key pair.
bob, _ := dcrypto.GenerateSigningKey() bob, _ := identity.GenerateSigningKey()
var ( var (
// the secret that alice should arrive at on her own // the secret that alice should arrive at on her own
@ -47,11 +47,11 @@ func TestHandshake(t *testing.T) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
go func() { go func() {
aliceSecret, aliceErr = dnet.Handshake(ctx, client, alice, &bob.PublicKey) aliceSecret, aliceErr = handshake.Handshake(ctx, client, alice, &bob.PublicKey)
wg.Done() wg.Done()
}() }()
go func() { go func() {
discoveredAlicePublicKey, bobSecret, bobErr = dnet.AcceptHandshake(ctx, server, bob) discoveredAlicePublicKey, bobSecret, bobErr = handshake.AcceptHandshake(ctx, server, bob)
wg.Done() wg.Done()
}() }()
wg.Wait() wg.Wait()
@ -94,9 +94,9 @@ func runSimulation() error {
ctx := context.Background() ctx := context.Background()
// alice, represented by an ecdsa key pair. // alice, represented by an ecdsa key pair.
alice, _ := dcrypto.GenerateSigningKey() alice, _ := identity.GenerateSigningKey()
// bob, also represented by an ecdsa key pair. // bob, also represented by an ecdsa key pair.
bob, _ := dcrypto.GenerateSigningKey() bob, _ := identity.GenerateSigningKey()
var ( var (
// the secret that alice should arrive at on her own // the secret that alice should arrive at on her own
@ -123,11 +123,11 @@ func runSimulation() error {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
go func() { go func() {
_, aliceErr = dnet.Handshake(ctx, client, alice, &bob.PublicKey) _, aliceErr = handshake.Handshake(ctx, client, alice, &bob.PublicKey)
wg.Done() wg.Done()
}() }()
go func() { go func() {
_, _, bobErr = dnet.AcceptHandshake(ctx, server, bob) _, _, bobErr = handshake.AcceptHandshake(ctx, server, bob)
wg.Done() wg.Done()
}() }()
wg.Wait() wg.Wait()

View File

@ -1,4 +1,4 @@
package dlog package logging
import ( import (
"context" "context"

View File

@ -1,4 +1,4 @@
package dnet package network
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
@ -8,7 +8,7 @@ import (
"net" "net"
"strings" "strings"
"koti.casa/numenor-labs/dsfx/shared/dcrypto" "koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
) )
var ( var (
@ -60,7 +60,7 @@ func ParseAddr(addrRaw string) (*Addr, error) {
return nil, ErrInvalidFormat return nil, ErrInvalidFormat
} }
publicKey, err := dcrypto.ImportPublicSigningKey(publicKeyBytes) publicKey, err := identity.ImportPublicSigningKey(publicKeyBytes)
if err != nil { if err != nil {
return nil, ErrInvalidFormat return nil, ErrInvalidFormat
} }
@ -86,7 +86,7 @@ func (a *Addr) Network() string {
// String implements net.Addr. // String implements net.Addr.
func (a *Addr) String() string { func (a *Addr) String() string {
exported, _ := dcrypto.ExportPublicSigningKey(a.publicKey) exported, _ := identity.ExportPublicSigningKey(a.publicKey)
exportedBase64 := base64.StdEncoding.EncodeToString(exported) exportedBase64 := base64.StdEncoding.EncodeToString(exported)
return fmt.Sprintf("%s://%s:%d#%s", a.network, a.ip, a.port, exportedBase64) return fmt.Sprintf("%s://%s:%d#%s", a.network, a.ip, a.port, exportedBase64)
} }

View File

@ -1,11 +1,12 @@
package dnet package network
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"net" "net"
"time" "time"
"koti.casa/numenor-labs/dsfx/shared/dcrypto" "koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
"koti.casa/numenor-labs/dsfx/pkg/frame"
) )
// Conn is a wrapper around net.TCPConn that encrypts and decrypts data as it is // Conn is a wrapper around net.TCPConn that encrypts and decrypts data as it is
@ -27,12 +28,12 @@ func NewConn(conn *net.TCPConn, sessionKey []byte, localIdentity, remoteIdentity
// The ciphertext that is actually transferred over the network is larger, so you // The ciphertext that is actually transferred over the network is larger, so you
// should not rely on this number as an indication of network metrics. // should not rely on this number as an indication of network metrics.
func (c *Conn) Read(b []byte) (int, error) { func (c *Conn) Read(b []byte) (int, error) {
f := NewFrame(nil) f := frame.NewFrame(nil)
_, err := f.ReadFrom(c.conn) _, err := f.ReadFrom(c.conn)
if err != nil { if err != nil {
return 0, err return 0, err
} }
plaintext, err := dcrypto.Decrypt(c.sessionKey, f.Contents()) plaintext, err := encryption.Decrypt(c.sessionKey, f.Contents())
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -42,11 +43,11 @@ func (c *Conn) Read(b []byte) (int, error) {
// Write implements io.Writer. // Write implements io.Writer.
func (c *Conn) Write(b []byte) (int, error) { func (c *Conn) Write(b []byte) (int, error) {
ciphertext, err := dcrypto.Encrypt(c.sessionKey, b) ciphertext, err := encryption.Encrypt(c.sessionKey, b)
if err != nil { if err != nil {
return 0, err return 0, err
} }
_, err = NewFrame(ciphertext).WriteTo(c.conn) _, err = frame.NewFrame(ciphertext).WriteTo(c.conn)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -1,10 +1,12 @@
package dnet package network
import ( import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"log/slog" "log/slog"
"net" "net"
"koti.casa/numenor-labs/dsfx/pkg/handshake"
) )
// Listener ... // Listener ...
@ -23,7 +25,7 @@ func (l *Listener) Accept() (net.Conn, error) {
return nil, err return nil, err
} }
clientIdentity, sessionKey, err := AcceptHandshake(ctx, conn, l.identity) clientIdentity, sessionKey, err := handshake.AcceptHandshake(ctx, conn, l.identity)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,11 +1,12 @@
package dnet package network
import ( import (
"context" "context"
"crypto/ecdsa" "crypto/ecdsa"
"net" "net"
"koti.casa/numenor-labs/dsfx/shared/dlog" "koti.casa/numenor-labs/dsfx/pkg/handshake"
"koti.casa/numenor-labs/dsfx/pkg/logging"
) )
// Dial ... // Dial ...
@ -20,7 +21,7 @@ func Dial(
return nil, err return nil, err
} }
sessionKey, err := Handshake(ctx, conn, identity, raddr.PublicKey()) sessionKey, err := handshake.Handshake(ctx, conn, identity, raddr.PublicKey())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -39,7 +40,7 @@ func Listen(
return nil, err return nil, err
} }
logger := dlog.FromContext(ctx) logger := logging.FromContext(ctx)
return &Listener{logger, tcpListener, identity}, nil return &Listener{logger, tcpListener, identity}, nil
} }

View File

@ -1,11 +0,0 @@
package dcrypto
import (
"crypto/hkdf"
"crypto/sha256"
)
// Key creates a key from the given secret and salt using HKDF.
func Key(secret, salt []byte) ([]byte, error) {
return hkdf.Key(sha256.New, secret, salt, "", 32)
}

View File

@ -1,12 +0,0 @@
package dcrypto
import "crypto/rand"
// Challenge generates a random challenge of n bytes.
func Challenge(n int) []byte {
challenge := make([]byte, n)
if _, err := rand.Read(challenge); err != nil || len(challenge) != n {
return nil
}
return challenge
}

View File

@ -6,11 +6,11 @@ import (
"fmt" "fmt"
"os" "os"
"koti.casa/numenor-labs/dsfx/shared/dcrypto" "koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
) )
func main() { func main() {
key, err := dcrypto.GenerateSigningKey() key, err := identity.GenerateSigningKey()
if err != nil { if err != nil {
panic(err) panic(err)
} }