mirror of
https://git.numenor-labs.us/dsfx.git
synced 2025-04-29 08:10:34 +00:00
reorganize everything
This commit is contained in:
parent
2b291900b9
commit
2d3631af45
22
README.md
22
README.md
@ -33,13 +33,13 @@ cd dsfx
|
||||
Build the project:
|
||||
|
||||
```sh
|
||||
go build -o dist/ ./...
|
||||
go build -o dist/ ./cmd/...
|
||||
```
|
||||
|
||||
You can also install the executables to your $GOPATH/bin:
|
||||
|
||||
```sh
|
||||
go install ./...
|
||||
go install ./cmd/...
|
||||
```
|
||||
|
||||
---
|
||||
@ -48,13 +48,13 @@ go install ./...
|
||||
|
||||
### 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
|
||||
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:
|
||||
|
||||
@ -69,14 +69,14 @@ File path to the PEM-encoded ECDSA private key that serves as the server’s 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.
|
||||
|
||||
### 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:
|
||||
|
||||
```sh
|
||||
/dsfx-client -key /path/to/clientkey.pem test <remote_addr>
|
||||
dsfxctl -key /path/to/clientkey.pem test <remote_addr>
|
||||
```
|
||||
|
||||
Where:
|
||||
@ -96,7 +96,7 @@ For example, `dsfx://127.0.0.1:8000#<base64 pubkey>” or “dsfx://127.0.0.1:80
|
||||
Example:
|
||||
|
||||
```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.
|
||||
@ -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:
|
||||
|
||||
```sh
|
||||
dsfx-server -h
|
||||
dsfx-client -h
|
||||
dsfxctl -h
|
||||
dsfxnode -h
|
||||
```
|
||||
|
||||
This will display the usage information along with available flags.
|
||||
|
9
cmd/dsfx/main.go
Normal file
9
cmd/dsfx/main.go
Normal file
@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Println("hello from dsfx!")
|
||||
}
|
@ -9,9 +9,9 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/shared/dcrypto"
|
||||
"koti.casa/numenor-labs/dsfx/shared/dlog"
|
||||
"koti.casa/numenor-labs/dsfx/shared/dnet"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/network"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -31,13 +31,13 @@ func main() {
|
||||
// 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.
|
||||
slog.SetDefault(logger)
|
||||
ctx = dlog.WithContext(ctx, logger)
|
||||
ctx = logging.WithContext(ctx, logger)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Commands
|
||||
|
||||
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, " test <remote_addr> Test the connection to the server\n")
|
||||
fmt.Fprintf(os.Stderr, "Flags:\n")
|
||||
@ -53,13 +53,13 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
identity, err := dcrypto.LoadSigningKeyFromFile(*flagKey)
|
||||
identity, err := identity.LoadSigningKeyFromFile(*flagKey)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to load private key", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
laddr := dnet.NewAddr(
|
||||
laddr := network.NewAddr(
|
||||
net.ParseIP("0.0.0.0"),
|
||||
0, // port 0 means any available port
|
||||
&identity.PublicKey,
|
||||
@ -84,16 +84,16 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func testConnection(ctx context.Context, identity *ecdsa.PrivateKey, laddr *dnet.Addr, raddrRaw string) {
|
||||
logger := dlog.FromContext(context.Background())
|
||||
func testConnection(ctx context.Context, identity *ecdsa.PrivateKey, laddr *network.Addr, raddrRaw string) {
|
||||
logger := logging.FromContext(context.Background())
|
||||
|
||||
raddr, err := dnet.ParseAddr(raddrRaw)
|
||||
raddr, err := network.ParseAddr(raddrRaw)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to parse server address", slog.Any("error", err))
|
||||
return
|
||||
}
|
||||
|
||||
conn, err := dnet.Dial(ctx, identity, laddr, raddr)
|
||||
conn, err := network.Dial(ctx, identity, laddr, raddr)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to connect", slog.Any("error", err))
|
||||
return
|
@ -8,9 +8,9 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/shared/dcrypto"
|
||||
"koti.casa/numenor-labs/dsfx/shared/dlog"
|
||||
"koti.casa/numenor-labs/dsfx/shared/dnet"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/network"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -36,7 +36,7 @@ func main() {
|
||||
// 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.
|
||||
slog.SetDefault(logger)
|
||||
ctx = dlog.WithContext(ctx, logger)
|
||||
ctx = logging.WithContext(ctx, logger)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Flags
|
||||
@ -48,7 +48,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
masterKey, err := dcrypto.LoadSigningKeyFromFile(*flagKey)
|
||||
masterKey, err := identity.LoadSigningKeyFromFile(*flagKey)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to load master key", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
@ -60,12 +60,12 @@ func main() {
|
||||
slog.ErrorContext(ctx, "invalid host or port")
|
||||
os.Exit(1)
|
||||
}
|
||||
addr := dnet.FromTCPAddr(tcpAddr, &masterKey.PublicKey)
|
||||
addr := network.FromTCPAddr(tcpAddr, &masterKey.PublicKey)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Listener
|
||||
|
||||
listener, err := dnet.Listen(ctx, masterKey, addr)
|
||||
listener, err := network.Listen(ctx, masterKey, addr)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "listener error", slog.Any("error", err))
|
||||
os.Exit(1)
|
||||
@ -87,7 +87,7 @@ func main() {
|
||||
func handleConnection(ctx context.Context, conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
|
||||
logger := dlog.FromContext(ctx)
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
msg := make([]byte, 1024)
|
||||
n, err := conn.Read(msg)
|
@ -1,4 +1,4 @@
|
||||
package dcrypto
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
@ -1,4 +1,4 @@
|
||||
package dcrypto
|
||||
package identity
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
@ -1,8 +1,10 @@
|
||||
package dcrypto
|
||||
package keyexchange
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/hkdf"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -17,7 +19,17 @@ func GenerateDHKey() (*ecdh.PrivateKey, error) {
|
||||
|
||||
// ComputeDHSecret computes the shared secret from the private key and the public key.
|
||||
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.
|
@ -1,4 +1,4 @@
|
||||
package dnet
|
||||
package frame
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
@ -1,11 +1,11 @@
|
||||
package dnet_test
|
||||
package frame_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/shared/dnet"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/frame"
|
||||
)
|
||||
|
||||
func TestLenPrefixedWriteTo(t *testing.T) {
|
||||
@ -16,7 +16,7 @@ func TestLenPrefixedWriteTo(t *testing.T) {
|
||||
|
||||
// When ...
|
||||
|
||||
n, err := dnet.NewFrame(msg).WriteTo(buf)
|
||||
n, err := frame.NewFrame(msg).WriteTo(buf)
|
||||
|
||||
// Then ...
|
||||
|
||||
@ -49,7 +49,7 @@ func TestLenPrefixedReadFrom(t *testing.T) {
|
||||
expectedBytesRead := len(msg)
|
||||
|
||||
// When ...
|
||||
f := dnet.NewFrame(nil)
|
||||
f := frame.NewFrame(nil)
|
||||
n, err := f.ReadFrom(buf)
|
||||
|
||||
// Then ...
|
@ -1,4 +1,4 @@
|
||||
package dnet
|
||||
package handshake
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -11,8 +11,11 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/pkg/assert"
|
||||
"koti.casa/numenor-labs/dsfx/shared/dcrypto"
|
||||
"koti.casa/numenor-labs/dsfx/shared/dlog"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
|
||||
"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 (
|
||||
@ -34,14 +37,14 @@ func Handshake(
|
||||
lPrivKey *ecdsa.PrivateKey,
|
||||
rPubKey *ecdsa.PublicKey,
|
||||
) ([]byte, error) {
|
||||
logger := dlog.FromContext(ctx).WithGroup("handshake")
|
||||
logger := logging.FromContext(ctx).WithGroup("handshake")
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Step 1: Ephemeral Key Exchange To Server
|
||||
|
||||
logger.DebugContext(ctx, "creating dh key")
|
||||
// Create a new ECDH private key for the actor.
|
||||
ourDHKey, err := dcrypto.GenerateDHKey()
|
||||
ourDHKey, err := keyexchange.GenerateDHKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -49,7 +52,7 @@ func Handshake(
|
||||
|
||||
logger.DebugContext(ctx, "exporting dh 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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -57,7 +60,7 @@ func Handshake(
|
||||
|
||||
// Write the actor's public key to the connection.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -67,7 +70,7 @@ func Handshake(
|
||||
|
||||
// Read the remote actor's public key from the connection.
|
||||
logger.DebugContext(ctx, "waiting for server's dh key")
|
||||
remoteDHKeyFrame := NewFrame(nil)
|
||||
remoteDHKeyFrame := frame.NewFrame(nil)
|
||||
_, err = remoteDHKeyFrame.ReadFrom(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -78,7 +81,7 @@ func Handshake(
|
||||
|
||||
// Import the remote actor's public key.
|
||||
logger.DebugContext(ctx, "importing server's dh key")
|
||||
remoteDHKey, err := dcrypto.ImportDHPublicKey(remoteDHKeyFrame.Contents())
|
||||
remoteDHKey, err := keyexchange.ImportDHPublicKey(remoteDHKeyFrame.Contents())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -88,13 +91,13 @@ func Handshake(
|
||||
|
||||
// Export the public key of the actor's signing key.
|
||||
logger.DebugContext(ctx, "exporting public signing key")
|
||||
ourPublicKeyRaw, err := dcrypto.ExportPublicSigningKey(&lPrivKey.PublicKey)
|
||||
ourPublicKeyRaw, err := identity.ExportPublicSigningKey(&lPrivKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "exporting remote public signing key")
|
||||
remotePublicKeyRaw, err := dcrypto.ExportPublicSigningKey(rPubKey)
|
||||
remotePublicKeyRaw, err := identity.ExportPublicSigningKey(rPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -114,26 +117,18 @@ func Handshake(
|
||||
|
||||
// Sign the message with the actor's private key.
|
||||
logger.DebugContext(ctx, "signing authentication message")
|
||||
signature, err := dcrypto.Sign(lPrivKey, authMessage)
|
||||
signature, err := identity.Sign(lPrivKey, authMessage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Compute the shared secret between the actor and the remote actor.
|
||||
logger.DebugContext(ctx, "computing shared secret")
|
||||
sharedSecret, err := dcrypto.ComputeDHSecret(ourDHKey, remoteDHKey)
|
||||
derivedKey, err := keyexchange.ComputeDHSecret(ourDHKey, remoteDHKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assert.Assert(len(sharedSecret) == 48, "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")
|
||||
assert.Assert(len(derivedKey) == 32, "invalid shared secret size")
|
||||
|
||||
plaintext := make([]byte, 0, len(ourPublicKeyRaw)+len(signature))
|
||||
plaintext = append(plaintext, ourPublicKeyRaw...)
|
||||
@ -141,14 +136,14 @@ func Handshake(
|
||||
|
||||
// Encrypt the message with the derived key.
|
||||
logger.DebugContext(ctx, "encrypting authentication message")
|
||||
boxedMsg, err := dcrypto.Encrypt(derivedKey, plaintext)
|
||||
boxedMsg, err := encryption.Encrypt(derivedKey, plaintext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write the boxed message to the connection.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -158,7 +153,7 @@ func Handshake(
|
||||
|
||||
// Read the authentication message from the connection.
|
||||
logger.DebugContext(ctx, "waiting for server's authentication message")
|
||||
authMessageFrame := NewFrame(nil)
|
||||
authMessageFrame := frame.NewFrame(nil)
|
||||
n, err := authMessageFrame.ReadFrom(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -167,7 +162,7 @@ func Handshake(
|
||||
|
||||
// Decrypt the authentication message with the derived key.
|
||||
logger.DebugContext(ctx, "decrypting authentication message")
|
||||
plaintext, err = dcrypto.Decrypt(derivedKey, authMessageFrame.Contents())
|
||||
plaintext, err = encryption.Decrypt(derivedKey, authMessageFrame.Contents())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -175,20 +170,20 @@ func Handshake(
|
||||
// The server authentication is just verifying the signature it created of
|
||||
// the client authentication message.
|
||||
logger.DebugContext(ctx, "importing server's public signing key")
|
||||
remotePublicKey, err := dcrypto.ImportPublicSigningKey(remotePublicKeyRaw)
|
||||
remotePublicKey, err := identity.ImportPublicSigningKey(remotePublicKeyRaw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
// Finally, we need to let the server know that the handshake is complete.
|
||||
logger.DebugContext(ctx, "sending handshake complete message")
|
||||
handshakeCompleteMsg := []byte{0x01}
|
||||
_, err = NewFrame(handshakeCompleteMsg).WriteTo(conn)
|
||||
_, err = frame.NewFrame(handshakeCompleteMsg).WriteTo(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -200,14 +195,14 @@ func Handshake(
|
||||
// AcceptHandshake accepts a handshake from the given actor and connection. It
|
||||
// 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) {
|
||||
logger := dlog.FromContext(ctx).WithGroup("handshake")
|
||||
logger := logging.FromContext(ctx).WithGroup("handshake")
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Step 1: Ephemeral Key Exchange From Client
|
||||
|
||||
// Read the remote actor's public key from the connection.
|
||||
logger.DebugContext(ctx, "waiting for client's dh key")
|
||||
remoteDHKeyFrame := NewFrame(nil)
|
||||
remoteDHKeyFrame := frame.NewFrame(nil)
|
||||
_, err := remoteDHKeyFrame.ReadFrom(conn)
|
||||
if err != nil {
|
||||
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.
|
||||
logger.DebugContext(ctx, "importing client's dh key")
|
||||
remoteDHKey, err := dcrypto.ImportDHPublicKey(remoteDHKeyFrame.Contents())
|
||||
remoteDHKey, err := keyexchange.ImportDHPublicKey(remoteDHKeyFrame.Contents())
|
||||
if err != nil {
|
||||
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.
|
||||
logger.DebugContext(ctx, "creating dh key")
|
||||
ourDHKey, err := dcrypto.GenerateDHKey()
|
||||
ourDHKey, err := keyexchange.GenerateDHKey()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Export the public key of the actor's ECDH private key.
|
||||
logger.DebugContext(ctx, "exporting dh key")
|
||||
ourDHKeyRaw, err := dcrypto.ExportDHPublicKey(ourDHKey.PublicKey())
|
||||
ourDHKeyRaw, err := keyexchange.ExportDHPublicKey(ourDHKey.PublicKey())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Write the actor's public key to the connection.
|
||||
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 {
|
||||
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.
|
||||
logger.DebugContext(ctx, "waiting for client's authentication message")
|
||||
authMessageFrame := NewFrame(nil)
|
||||
authMessageFrame := frame.NewFrame(nil)
|
||||
n, err := authMessageFrame.ReadFrom(conn)
|
||||
if err != nil {
|
||||
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.
|
||||
logger.DebugContext(ctx, "computing shared secret")
|
||||
sharedSecret, err := dcrypto.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)
|
||||
derivedKey, err := keyexchange.ComputeDHSecret(ourDHKey, remoteDHKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "decrypting authentication message")
|
||||
plaintext, err := dcrypto.Decrypt(derivedKey, authMessageFrame.Contents())
|
||||
plaintext, err := encryption.Decrypt(derivedKey, authMessageFrame.Contents())
|
||||
if err != nil {
|
||||
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.
|
||||
logger.DebugContext(ctx, "importing client's public signing key")
|
||||
clientPublicKey, err := dcrypto.ImportPublicSigningKey(clientPublicKeyRaw)
|
||||
clientPublicKey, err := identity.ImportPublicSigningKey(clientPublicKeyRaw)
|
||||
if err != nil {
|
||||
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")
|
||||
if !dcrypto.Verify(clientPublicKey, authMessage, signature) {
|
||||
if !identity.Verify(clientPublicKey, authMessage, 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
|
||||
// the server to the client.
|
||||
logger.DebugContext(ctx, "signing authentication message")
|
||||
serverSignature, err := dcrypto.Sign(lPrivKey, authMessage)
|
||||
serverSignature, err := identity.Sign(lPrivKey, authMessage)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "encrypting server's signature")
|
||||
boxedMsg, err := dcrypto.Encrypt(derivedKey, serverSignature)
|
||||
boxedMsg, err := encryption.Encrypt(derivedKey, serverSignature)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Send the server's signature back to the client.
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "waiting for handshake complete message")
|
||||
// Read the handshake complete message from the client.
|
||||
handshakeCompleteFrame := NewFrame(nil)
|
||||
handshakeCompleteFrame := frame.NewFrame(nil)
|
||||
_, err = handshakeCompleteFrame.ReadFrom(conn)
|
||||
if err != nil {
|
||||
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) {
|
||||
clientPubKeyRaw, err := dcrypto.ExportDHPublicKey(clientPubKey)
|
||||
clientPubKeyRaw, err := keyexchange.ExportDHPublicKey(clientPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverPubKeyRaw, err := dcrypto.ExportDHPublicKey(serverPubKey)
|
||||
serverPubKeyRaw, err := keyexchange.ExportDHPublicKey(serverPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package dnet_test
|
||||
package handshake_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -10,17 +10,17 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/shared/dcrypto"
|
||||
"koti.casa/numenor-labs/dsfx/shared/dnet"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
||||
)
|
||||
|
||||
func TestHandshake(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// alice, represented by an ecdsa key pair.
|
||||
alice, _ := dcrypto.GenerateSigningKey()
|
||||
alice, _ := identity.GenerateSigningKey()
|
||||
// bob, also represented by an ecdsa key pair.
|
||||
bob, _ := dcrypto.GenerateSigningKey()
|
||||
bob, _ := identity.GenerateSigningKey()
|
||||
|
||||
var (
|
||||
// the secret that alice should arrive at on her own
|
||||
@ -47,11 +47,11 @@ func TestHandshake(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
aliceSecret, aliceErr = dnet.Handshake(ctx, client, alice, &bob.PublicKey)
|
||||
aliceSecret, aliceErr = handshake.Handshake(ctx, client, alice, &bob.PublicKey)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
discoveredAlicePublicKey, bobSecret, bobErr = dnet.AcceptHandshake(ctx, server, bob)
|
||||
discoveredAlicePublicKey, bobSecret, bobErr = handshake.AcceptHandshake(ctx, server, bob)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
@ -94,9 +94,9 @@ func runSimulation() error {
|
||||
ctx := context.Background()
|
||||
|
||||
// alice, represented by an ecdsa key pair.
|
||||
alice, _ := dcrypto.GenerateSigningKey()
|
||||
alice, _ := identity.GenerateSigningKey()
|
||||
// bob, also represented by an ecdsa key pair.
|
||||
bob, _ := dcrypto.GenerateSigningKey()
|
||||
bob, _ := identity.GenerateSigningKey()
|
||||
|
||||
var (
|
||||
// the secret that alice should arrive at on her own
|
||||
@ -123,11 +123,11 @@ func runSimulation() error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
_, aliceErr = dnet.Handshake(ctx, client, alice, &bob.PublicKey)
|
||||
_, aliceErr = handshake.Handshake(ctx, client, alice, &bob.PublicKey)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
_, _, bobErr = dnet.AcceptHandshake(ctx, server, bob)
|
||||
_, _, bobErr = handshake.AcceptHandshake(ctx, server, bob)
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
@ -1,4 +1,4 @@
|
||||
package dlog
|
||||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
@ -1,4 +1,4 @@
|
||||
package dnet
|
||||
package network
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/shared/dcrypto"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -60,7 +60,7 @@ func ParseAddr(addrRaw string) (*Addr, error) {
|
||||
return nil, ErrInvalidFormat
|
||||
}
|
||||
|
||||
publicKey, err := dcrypto.ImportPublicSigningKey(publicKeyBytes)
|
||||
publicKey, err := identity.ImportPublicSigningKey(publicKeyBytes)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidFormat
|
||||
}
|
||||
@ -86,7 +86,7 @@ func (a *Addr) Network() string {
|
||||
|
||||
// String implements net.Addr.
|
||||
func (a *Addr) String() string {
|
||||
exported, _ := dcrypto.ExportPublicSigningKey(a.publicKey)
|
||||
exported, _ := identity.ExportPublicSigningKey(a.publicKey)
|
||||
exportedBase64 := base64.StdEncoding.EncodeToString(exported)
|
||||
return fmt.Sprintf("%s://%s:%d#%s", a.network, a.ip, a.port, exportedBase64)
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package dnet
|
||||
package network
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"net"
|
||||
"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
|
||||
@ -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
|
||||
// should not rely on this number as an indication of network metrics.
|
||||
func (c *Conn) Read(b []byte) (int, error) {
|
||||
f := NewFrame(nil)
|
||||
f := frame.NewFrame(nil)
|
||||
_, err := f.ReadFrom(c.conn)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
plaintext, err := dcrypto.Decrypt(c.sessionKey, f.Contents())
|
||||
plaintext, err := encryption.Decrypt(c.sessionKey, f.Contents())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -42,11 +43,11 @@ func (c *Conn) Read(b []byte) (int, error) {
|
||||
|
||||
// Write implements io.Writer.
|
||||
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 {
|
||||
return 0, err
|
||||
}
|
||||
_, err = NewFrame(ciphertext).WriteTo(c.conn)
|
||||
_, err = frame.NewFrame(ciphertext).WriteTo(c.conn)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package dnet
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"log/slog"
|
||||
"net"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
||||
)
|
||||
|
||||
// Listener ...
|
||||
@ -23,7 +25,7 @@ func (l *Listener) Accept() (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clientIdentity, sessionKey, err := AcceptHandshake(ctx, conn, l.identity)
|
||||
clientIdentity, sessionKey, err := handshake.AcceptHandshake(ctx, conn, l.identity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
package dnet
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"net"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/shared/dlog"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
||||
)
|
||||
|
||||
// Dial ...
|
||||
@ -20,7 +21,7 @@ func Dial(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sessionKey, err := Handshake(ctx, conn, identity, raddr.PublicKey())
|
||||
sessionKey, err := handshake.Handshake(ctx, conn, identity, raddr.PublicKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -39,7 +40,7 @@ func Listen(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger := dlog.FromContext(ctx)
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
return &Listener{logger, tcpListener, identity}, nil
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -6,11 +6,11 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"koti.casa/numenor-labs/dsfx/shared/dcrypto"
|
||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||
)
|
||||
|
||||
func main() {
|
||||
key, err := dcrypto.GenerateSigningKey()
|
||||
key, err := identity.GenerateSigningKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user