mirror of
https://git.numenor-labs.us/dsfx.git
synced 2025-04-29 08:10:34 +00:00
commit
b78eb2b71a
14
README.md
14
README.md
@ -6,7 +6,7 @@ dsfx is a robust, secure, and distributed file exchange system written in Go. De
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **End-to-End Security:** Uses modern cryptographic primitives (ECDSA keys, for example) to ensure that all file exchanges are encrypted and authenticated.
|
- **End-to-End Security:** Uses modern cryptographic primitives (ED25519 keys, for example) to ensure that all file exchanges are encrypted and authenticated.
|
||||||
- **Distributed Architecture:** Designed for secure file exchange across multiple nodes with built-in support for key-based authentication.
|
- **Distributed Architecture:** Designed for secure file exchange across multiple nodes with built-in support for key-based authentication.
|
||||||
- **High Performance:** Optimized for low latency and high throughput, with a focus on reliable and predictable behavior.
|
- **High Performance:** Optimized for low latency and high throughput, with a focus on reliable and predictable behavior.
|
||||||
- **Administrative and Test Tools:** The dsfx client can be used to test connectivity and perform preliminary administrative actions against the dsfx server.
|
- **Administrative and Test Tools:** The dsfx client can be used to test connectivity and perform preliminary administrative actions against the dsfx server.
|
||||||
@ -48,13 +48,13 @@ go install ./cmd/...
|
|||||||
|
|
||||||
### Starting the Server
|
### Starting the Server
|
||||||
|
|
||||||
The dsfxnode 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 (ED25519 private key in Base64 format). For example:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dsfxnode -host localhost -port 8000 -key /path/to/serverkey.pem
|
dsfxnode -host localhost -port 8000 -key /path/to/serverkey
|
||||||
```
|
```
|
||||||
|
|
||||||
> Note, if you need to generate a new ECDSA key, you can use the following command: `go run ./tool/genkey > path/to/masterkey.pem`
|
> Note, if you need to generate a new ED25519 key, you can use the following command: `go run ./tool/genkey > path/to/masterkey`
|
||||||
|
|
||||||
Command-line flags for dsfx-server:
|
Command-line flags for dsfx-server:
|
||||||
|
|
||||||
@ -65,18 +65,18 @@ The host interface on which the server will listen.
|
|||||||
The TCP port on which the server will accept connections.
|
The TCP port on which the server will accept connections.
|
||||||
|
|
||||||
-key (required)
|
-key (required)
|
||||||
File path to the PEM-encoded ECDSA private key that serves as the server’s master key.
|
File path to the Base64-encoded ED25519 private key that serves as the server’s master key.
|
||||||
|
|
||||||
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 Admin Client
|
### Running the Admin Client
|
||||||
|
|
||||||
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.
|
The dsfxctl uses a private key for the client (also an ED25519 key in Base64 format) and currently supports only the “test” command for checking connectivity to the server.
|
||||||
|
|
||||||
Client command usage:
|
Client command usage:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dsfxctl -key /path/to/clientkey.pem test <remote_addr>
|
dsfxctl -key /path/to/clientkey test <remote_addr>
|
||||||
```
|
```
|
||||||
|
|
||||||
Where:
|
Where:
|
||||||
|
@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ed25519"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
@ -53,7 +53,7 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
identity, err := identity.LoadSigningKeyFromFile(*flagKey)
|
id, 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)
|
||||||
@ -62,7 +62,7 @@ func main() {
|
|||||||
laddr := network.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.ToPublicKey(id),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.DebugContext(ctx, "using addr", slog.String("address", laddr.String()))
|
logger.DebugContext(ctx, "using addr", slog.String("address", laddr.String()))
|
||||||
@ -74,7 +74,7 @@ func main() {
|
|||||||
logger.ErrorContext(ctx, "no remote address provided")
|
logger.ErrorContext(ctx, "no remote address provided")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
testConnection(ctx, identity, laddr, raddrRaw)
|
testConnection(ctx, id, laddr, raddrRaw)
|
||||||
case "":
|
case "":
|
||||||
logger.InfoContext(ctx, "no command provided")
|
logger.InfoContext(ctx, "no command provided")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -84,7 +84,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testConnection(ctx context.Context, identity *ecdsa.PrivateKey, laddr *network.Addr, raddrRaw string) {
|
func testConnection(ctx context.Context, id ed25519.PrivateKey, laddr *network.Addr, raddrRaw string) {
|
||||||
logger := logging.FromContext(context.Background())
|
logger := logging.FromContext(context.Background())
|
||||||
|
|
||||||
raddr, err := network.ParseAddr(raddrRaw)
|
raddr, err := network.ParseAddr(raddrRaw)
|
||||||
@ -93,7 +93,7 @@ func testConnection(ctx context.Context, identity *ecdsa.PrivateKey, laddr *netw
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := network.Dial(ctx, identity, laddr, raddr)
|
conn, err := network.Dial(ctx, id, 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
|
||||||
|
@ -60,7 +60,7 @@ func main() {
|
|||||||
slog.ErrorContext(ctx, "invalid host or port")
|
slog.ErrorContext(ctx, "invalid host or port")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
addr := network.FromTCPAddr(tcpAddr, &masterKey.PublicKey)
|
addr := network.FromTCPAddr(tcpAddr, identity.ToPublicKey(masterKey))
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Listener
|
// Listener
|
||||||
|
57
pkg/buffer/lenprefixed.go
Normal file
57
pkg/buffer/lenprefixed.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package buffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MaxUint16 is the maximum value of a uint16. It is used to check if the
|
||||||
|
// length of the data is too large to be encoded in a uint16.
|
||||||
|
const MaxUint16 = 0xFFFF
|
||||||
|
|
||||||
|
// ErrInvalidLength is the error message returned when the length of the data
|
||||||
|
// is too large to be encoded in a uint16.
|
||||||
|
var ErrInvalidLength = errors.New("data length is too large to be encoded in a uint16")
|
||||||
|
|
||||||
|
// NewLenPrefixed returns a new buffer with a length prefix. The length prefix is
|
||||||
|
// 2 bytes long and is encoded in big-endian order. The length prefix is
|
||||||
|
// followed by the data.
|
||||||
|
func NewLenPrefixed(data []byte) ([]byte, error) {
|
||||||
|
length := len(data)
|
||||||
|
// Overflow Guard: If the length of the data is greater than the maximum
|
||||||
|
// value of a uint16, return an error.
|
||||||
|
if length > MaxUint16 {
|
||||||
|
return nil, ErrInvalidLength
|
||||||
|
}
|
||||||
|
buf := make([]byte, 2+len(data))
|
||||||
|
binary.BigEndian.PutUint16(buf, uint16(len(data)))
|
||||||
|
copy(buf[2:], data)
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadLenPrefixed(maxSize uint16, r io.Reader) ([]byte, error) {
|
||||||
|
lenBuf := make([]byte, 2)
|
||||||
|
if _, err := io.ReadFull(r, lenBuf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(lenBuf) < 2 {
|
||||||
|
return nil, errors.New("buffer is too small to contain length prefix")
|
||||||
|
}
|
||||||
|
length := binary.BigEndian.Uint16(lenBuf)
|
||||||
|
|
||||||
|
if length > maxSize {
|
||||||
|
return nil, errors.New("data length is too large to be encoded in a uint16")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(r, buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(buf) < int(length) {
|
||||||
|
return nil, errors.New("buffer is too small to contain data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
@ -12,6 +12,8 @@ import (
|
|||||||
|
|
||||||
// Encrypt uses AES-GCM to encrypt the given plaintext with the given key. The
|
// Encrypt uses AES-GCM to encrypt the given plaintext with the given key. The
|
||||||
// plaintext is sealed with a 12-byte nonce, which is prepended to the ciphertext.
|
// plaintext is sealed with a 12-byte nonce, which is prepended to the ciphertext.
|
||||||
|
// The nonce adds 28 bytes to the ciphertext, so the total length of the ciphertext
|
||||||
|
// is the length of the plaintext plus 28 bytes.
|
||||||
func Encrypt(key, plaintext []byte) ([]byte, error) {
|
func Encrypt(key, plaintext []byte) ([]byte, error) {
|
||||||
switch len(key) {
|
switch len(key) {
|
||||||
case 16, 24, 32: // AES-128, AES-192, AES-256
|
case 16, 24, 32: // AES-128, AES-192, AES-256
|
||||||
|
35
pkg/crypto/encryption/aead_test.go
Normal file
35
pkg/crypto/encryption/aead_test.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package encryption_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncryptDecrypt(t *testing.T) {
|
||||||
|
key := make([]byte, 32)
|
||||||
|
_, err := rand.Read(key)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext := []byte("Hello, Worlskljfsjflskfjlskjfjslkfjsfjslkfjsfd!")
|
||||||
|
ciphertext, err := encryption.Encrypt(key, plaintext)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted, err := encryption.Decrypt(key, ciphertext)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(decrypted) != string(plaintext) {
|
||||||
|
t.Errorf("decrypted text does not match original plaintext")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
@ -1,114 +0,0 @@
|
|||||||
package identity
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// DefaultSigningCurve is the default elliptic curve used for signing.
|
|
||||||
DefaultSigningCurve = elliptic.P384
|
|
||||||
|
|
||||||
ExportedPublicKeySize = 215
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadSigningKeyFromFile(filePath string) (*ecdsa.PrivateKey, error) {
|
|
||||||
masterKeyFile, err := os.ReadFile(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The second argument is not an error.
|
|
||||||
derEncoded, _ := pem.Decode(masterKeyFile)
|
|
||||||
if derEncoded == nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode master key file")
|
|
||||||
}
|
|
||||||
|
|
||||||
masterKey, err := x509.ParseECPrivateKey(derEncoded.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return masterKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate generates a new ECDSA private key for signing.
|
|
||||||
func Generate() (*ecdsa.PrivateKey, error) {
|
|
||||||
return ecdsa.GenerateKey(DefaultSigningCurve(), rand.Reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the data with the private key.
|
|
||||||
func Sign(priv *ecdsa.PrivateKey, data []byte) ([]byte, error) {
|
|
||||||
return ecdsa.SignASN1(rand.Reader, priv, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify verifies the signature of the data with the public key.
|
|
||||||
func Verify(pub *ecdsa.PublicKey, data, signature []byte) bool {
|
|
||||||
return ecdsa.VerifyASN1(pub, data, signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportPrivateKey exports the private key as a byte slice.
|
|
||||||
func ExportPrivateKey(key *ecdsa.PrivateKey) ([]byte, error) {
|
|
||||||
der, err := x509.MarshalECPrivateKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "PRIVATE KEY",
|
|
||||||
Bytes: der,
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportPublicKey exports the public key as a byte slice.
|
|
||||||
func ExportPublicKey(key *ecdsa.PublicKey) ([]byte, error) {
|
|
||||||
der, err := x509.MarshalPKIXPublicKey(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "PUBLIC KEY",
|
|
||||||
Bytes: der,
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportPrivateKey imports the private key from a byte slice.
|
|
||||||
func ImportPrivateKey(keyBytes []byte) (*ecdsa.PrivateKey, error) {
|
|
||||||
block, _ := pem.Decode(keyBytes)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey, err := x509.ParseECPrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return privKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportPublicKey imports the public key from a byte slice.
|
|
||||||
func ImportPublicKey(keyBytes []byte) (*ecdsa.PublicKey, error) {
|
|
||||||
block, _ := pem.Decode(keyBytes)
|
|
||||||
if block == nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKeyAny, err := x509.ParsePKIXPublicKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, ok := pubKeyAny.(*ecdsa.PublicKey)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("not an ECDSA public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pubKey, nil
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
package identity_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"log"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestImportExportPrivate(t *testing.T) {
|
|
||||||
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to generate key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
exported, err := identity.ExportPrivateKey(key)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to export key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
imported, err := identity.ImportPrivateKey(exported)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to import key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !key.Equal(imported) {
|
|
||||||
t.Fatalf("imported key does not match original")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImportExportPublic(t *testing.T) {
|
|
||||||
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to generate key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
exported, err := identity.ExportPublicKey(&key.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to export key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("keylen", len(exported))
|
|
||||||
imported, err := identity.ImportPublicKey(exported)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to import key: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !key.PublicKey.Equal(imported) {
|
|
||||||
t.Fatalf("imported key does not match original")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
91
pkg/crypto/identity/ed25519.go
Normal file
91
pkg/crypto/identity/ed25519.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package identity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ExportedPublicKeySize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadSigningKeyFromFile(filePath string) (ed25519.PrivateKey, error) {
|
||||||
|
keyBase64, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the base64-encoded master key file.
|
||||||
|
keyRaw, err := base64.StdEncoding.DecodeString(string(keyBase64))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode master key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyRaw) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("invalid master key file size: %d", len(keyRaw))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ed25519.PrivateKey(keyRaw), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate generates a new ED25519 private key for signing.
|
||||||
|
func Generate() (ed25519.PrivateKey, error) {
|
||||||
|
_, key, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the data with the private key.
|
||||||
|
func Sign(priv ed25519.PrivateKey, data []byte) ([]byte, error) {
|
||||||
|
return ed25519.Sign(priv, data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify verifies the signature of the data with the public key.
|
||||||
|
func Verify(pub ed25519.PublicKey, data, signature []byte) bool {
|
||||||
|
return ed25519.Verify(pub, data, signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPrivateKey exports the private key as a byte slice.
|
||||||
|
func ExportPrivateKey(key ed25519.PrivateKey) ([]byte, error) {
|
||||||
|
return []byte(base64.StdEncoding.EncodeToString(key)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportPublicKey exports the public key as a byte slice.
|
||||||
|
func ExportPublicKey(key ed25519.PublicKey) ([]byte, error) {
|
||||||
|
log.Println("exporting key", len(key))
|
||||||
|
encoded := []byte(base64.StdEncoding.EncodeToString(key))
|
||||||
|
log.Println("exported key", len(encoded))
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPrivateKey imports the private key from a byte slice.
|
||||||
|
func ImportPrivateKey(keyBytes []byte) (ed25519.PrivateKey, error) {
|
||||||
|
rawKey, err := base64.StdEncoding.DecodeString(string(keyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode private key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rawKey) != ed25519.PrivateKeySize {
|
||||||
|
return nil, fmt.Errorf("invalid private key size: %d", len(rawKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ed25519.PrivateKey(rawKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPublicKey imports the public key from a byte slice.
|
||||||
|
func ImportPublicKey(keyBytes []byte) (ed25519.PublicKey, error) {
|
||||||
|
log.Println("importing key", len(keyBytes))
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(string(keyBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode public key: %w", err)
|
||||||
|
}
|
||||||
|
log.Println("imported key", len(decoded))
|
||||||
|
return ed25519.PublicKey(decoded), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToPublicKey(key ed25519.PrivateKey) ed25519.PublicKey {
|
||||||
|
return key.Public().(ed25519.PublicKey)
|
||||||
|
}
|
@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultDHCurve is the default elliptic curve used for signing.
|
// DefaultDHCurve is the default elliptic curve used for signing.
|
||||||
DefaultDHCurve = ecdh.P384
|
DefaultDHCurve = ecdh.X25519
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateDHKey generates a new ECDH private key for key exchange.
|
// GenerateDHKey generates a new ECDH private key for key exchange.
|
||||||
|
@ -4,29 +4,24 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdh"
|
"crypto/ecdh"
|
||||||
"crypto/ecdsa"
|
"crypto/ed25519"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/assert"
|
"koti.casa/numenor-labs/dsfx/pkg/assert"
|
||||||
|
"koti.casa/numenor-labs/dsfx/pkg/buffer"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
|
"koti.casa/numenor-labs/dsfx/pkg/crypto/encryption"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/crypto/keyexchange"
|
"koti.casa/numenor-labs/dsfx/pkg/crypto/keyexchange"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/frame"
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
"koti.casa/numenor-labs/dsfx/pkg/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ECDHPublicKeySize is the size of an ECDH public key in bytes.
|
DHKeySize = 32
|
||||||
ECDHPublicKeySize = 97
|
IdentityKeySize = 32
|
||||||
// ECDSAPublicKeySize is the size of an ECDSA public key in bytes.
|
|
||||||
ECDSAPublicKeySize = 222
|
|
||||||
// BoxedClientAuthMessageSize is the size of a boxed client authentication message in bytes.
|
|
||||||
BoxedClientAuthMessageSize = 353
|
|
||||||
// BoxedServerAuthMessageSize is the size of a boxed server authentication message in bytes.
|
|
||||||
BoxedServerAuthMessageSize = 130
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initiate initiates the handshake process between the given actor
|
// Initiate initiates the handshake process between the given actor
|
||||||
@ -34,8 +29,8 @@ const (
|
|||||||
func Initiate(
|
func Initiate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn io.ReadWriteCloser,
|
conn io.ReadWriteCloser,
|
||||||
lPrivKey *ecdsa.PrivateKey,
|
lPrivKey ed25519.PrivateKey,
|
||||||
rPubKey *ecdsa.PublicKey,
|
rPubKey ed25519.PublicKey,
|
||||||
) ([]byte, error) {
|
) ([]byte, error) {
|
||||||
logger := logging.FromContext(ctx).WithGroup("handshake")
|
logger := logging.FromContext(ctx).WithGroup("handshake")
|
||||||
|
|
||||||
@ -56,32 +51,35 @@ func Initiate(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
assert.Assert(len(ourDHKeyRaw) == ECDHPublicKeySize, "invalid dh key size")
|
|
||||||
|
welcomeMessage, err := buffer.NewLenPrefixed(ourDHKeyRaw)
|
||||||
|
if err != nil {
|
||||||
|
return 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 = frame.New(ourDHKeyRaw).WriteTo(conn)
|
n, err := conn.Write(welcomeMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if n != len(welcomeMessage) {
|
||||||
|
return nil, errors.New("failed to write dh key")
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Step 2: Ephemeral Key Exchange From Server
|
// Step 2: Ephemeral Key Exchange From Server
|
||||||
|
|
||||||
// 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 := frame.New(nil)
|
remoteDHKeyRaw, err := buffer.ReadLenPrefixed(buffer.MaxUint16, conn)
|
||||||
_, err = remoteDHKeyFrame.ReadFrom(conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(remoteDHKeyFrame.Contents()) != ECDHPublicKeySize {
|
|
||||||
return nil, errors.New("invalid dh key size")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 := keyexchange.ImportPublicKey(remoteDHKeyFrame.Contents())
|
remoteDHKey, err := keyexchange.ImportPublicKey(remoteDHKeyRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -91,7 +89,7 @@ func Initiate(
|
|||||||
|
|
||||||
// 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 := identity.ExportPublicKey(&lPrivKey.PublicKey)
|
ourPublicKeyRaw, err := identity.ExportPublicKey(identity.ToPublicKey(lPrivKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -130,6 +128,9 @@ func Initiate(
|
|||||||
}
|
}
|
||||||
assert.Assert(len(derivedKey) == 32, "invalid shared secret size")
|
assert.Assert(len(derivedKey) == 32, "invalid shared secret size")
|
||||||
|
|
||||||
|
log.Println("raw pub key", len(ourPublicKeyRaw))
|
||||||
|
log.Println("raw sig", len(signature))
|
||||||
|
|
||||||
plaintext := make([]byte, 0, len(ourPublicKeyRaw)+len(signature))
|
plaintext := make([]byte, 0, len(ourPublicKeyRaw)+len(signature))
|
||||||
plaintext = append(plaintext, ourPublicKeyRaw...)
|
plaintext = append(plaintext, ourPublicKeyRaw...)
|
||||||
plaintext = append(plaintext, signature...)
|
plaintext = append(plaintext, signature...)
|
||||||
@ -141,20 +142,28 @@ func Initiate(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Println(string(plaintext), len(plaintext))
|
||||||
|
|
||||||
// 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 = frame.New(boxedMsg).WriteTo(conn)
|
boxedMsgPrepared, err := buffer.NewLenPrefixed(boxedMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
n, err = conn.Write(boxedMsgPrepared)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != len(boxedMsgPrepared) {
|
||||||
|
return nil, errors.New("failed to write authentication message")
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Step 4: Server Authentication
|
// Step 4: Server Authentication
|
||||||
|
|
||||||
// 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 := frame.New(nil)
|
authMessageBoxed, err := buffer.ReadLenPrefixed(buffer.MaxUint16, conn)
|
||||||
n, err := authMessageFrame.ReadFrom(conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -162,7 +171,7 @@ func Initiate(
|
|||||||
|
|
||||||
// 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 = encryption.Decrypt(derivedKey, authMessageFrame.Contents())
|
plaintext, err = encryption.Decrypt(derivedKey, authMessageBoxed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -182,11 +191,17 @@ func Initiate(
|
|||||||
|
|
||||||
// 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, err := buffer.NewLenPrefixed([]byte{0x01})
|
||||||
_, err = frame.New(handshakeCompleteMsg).WriteTo(conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
n, err = conn.Write(handshakeCompleteMsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if n != len(handshakeCompleteMsg) {
|
||||||
|
return nil, errors.New("failed to write handshake complete message")
|
||||||
|
}
|
||||||
|
|
||||||
logger.DebugContext(ctx, "handshake complete")
|
logger.DebugContext(ctx, "handshake complete")
|
||||||
return derivedKey, nil
|
return derivedKey, nil
|
||||||
@ -194,7 +209,7 @@ func Initiate(
|
|||||||
|
|
||||||
// Accept accepts a handshake from the given actor and connection. It
|
// Accept 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 Accept(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecdsa.PrivateKey) (*ecdsa.PublicKey, []byte, error) {
|
func Accept(ctx context.Context, conn io.ReadWriteCloser, lPrivKey ed25519.PrivateKey) (ed25519.PublicKey, []byte, error) {
|
||||||
logger := logging.FromContext(ctx).WithGroup("handshake")
|
logger := logging.FromContext(ctx).WithGroup("handshake")
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@ -202,15 +217,14 @@ func Accept(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecdsa.Privat
|
|||||||
|
|
||||||
// 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 := frame.New(nil)
|
remoteDHKeyRaw, err := buffer.ReadLenPrefixed(buffer.MaxUint16, conn)
|
||||||
_, err := remoteDHKeyFrame.ReadFrom(conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 := keyexchange.ImportPublicKey(remoteDHKeyFrame.Contents())
|
remoteDHKey, err := keyexchange.ImportPublicKey(remoteDHKeyRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -234,22 +248,32 @@ func Accept(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecdsa.Privat
|
|||||||
|
|
||||||
// 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 = frame.New(ourDHKeyRaw).WriteTo(conn)
|
ourPrefixedDHKey, err := buffer.NewLenPrefixed(ourDHKeyRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
n, err := conn.Write(ourPrefixedDHKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if n != len(ourPrefixedDHKey) {
|
||||||
|
return nil, nil, errors.New("failed to write dh key")
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Step 3: Server Authentication
|
// Step 3: Server Authentication
|
||||||
|
|
||||||
// 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 := frame.New(nil)
|
authMessageRaw, err := buffer.ReadLenPrefixed(buffer.MaxUint16, conn)
|
||||||
n, err := authMessageFrame.ReadFrom(conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
logger.DebugContext(ctx, "received authentication message", slog.Int("message.size", int(n)))
|
logger.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"received authentication message",
|
||||||
|
slog.Int("message.size", len(authMessageRaw)),
|
||||||
|
)
|
||||||
|
|
||||||
// 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")
|
||||||
@ -259,13 +283,14 @@ func Accept(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecdsa.Privat
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugContext(ctx, "decrypting authentication message")
|
logger.DebugContext(ctx, "decrypting authentication message")
|
||||||
plaintext, err := encryption.Decrypt(derivedKey, authMessageFrame.Contents())
|
plaintext, err := encryption.Decrypt(derivedKey, authMessageRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
log.Println(string(plaintext), len(plaintext))
|
||||||
|
|
||||||
clientPublicKeyRaw := plaintext[:identity.ExportedPublicKeySize]
|
clientPublicKeyRaw := plaintext[:44]
|
||||||
signature := plaintext[identity.ExportedPublicKeySize:]
|
signature := plaintext[44:]
|
||||||
|
|
||||||
// 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")
|
||||||
@ -309,19 +334,25 @@ func Accept(ctx context.Context, conn io.ReadWriteCloser, lPrivKey *ecdsa.Privat
|
|||||||
|
|
||||||
// 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 = frame.New(boxedMsg).WriteTo(conn)
|
prefixedAuthMessage, err := buffer.NewLenPrefixed(boxedMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
n, err = conn.Write(prefixedAuthMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if n != len(prefixedAuthMessage) {
|
||||||
|
return nil, nil, errors.New("failed to write authentication message")
|
||||||
|
}
|
||||||
|
|
||||||
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 := frame.New(nil)
|
handshakeCompleteMsg, err := buffer.ReadLenPrefixed(buffer.MaxUint16, conn)
|
||||||
_, err = handshakeCompleteFrame.ReadFrom(conn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if !bytes.Equal(handshakeCompleteFrame.Contents(), []byte{0x01}) {
|
if !bytes.Equal(handshakeCompleteMsg, []byte{0x01}) {
|
||||||
return nil, nil, errors.New("invalid handshake complete message")
|
return nil, nil, errors.New("invalid handshake complete message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package handshake_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ed25519"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -17,9 +17,9 @@ import (
|
|||||||
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 ed25519 key pair.
|
||||||
alice, _ := identity.Generate()
|
alice, _ := identity.Generate()
|
||||||
// bob, also represented by an ecdsa key pair.
|
// bob, also represented by an ed25519 key pair.
|
||||||
bob, _ := identity.Generate()
|
bob, _ := identity.Generate()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -28,7 +28,7 @@ func TestHandshake(t *testing.T) {
|
|||||||
// any errors produce by alice
|
// any errors produce by alice
|
||||||
aliceErr error
|
aliceErr error
|
||||||
// alice's public key as discovered by bob
|
// alice's public key as discovered by bob
|
||||||
discoveredAlicePublicKey *ecdsa.PublicKey
|
discoveredAlicePublicKey ed25519.PublicKey
|
||||||
|
|
||||||
// the secret that bob should arrive at on his own
|
// the secret that bob should arrive at on his own
|
||||||
bobSecret []byte
|
bobSecret []byte
|
||||||
@ -47,7 +47,7 @@ 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 = handshake.Initiate(ctx, client, alice, &bob.PublicKey)
|
aliceSecret, aliceErr = handshake.Initiate(ctx, client, alice, identity.ToPublicKey(bob))
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
@ -76,7 +76,7 @@ func TestHandshake(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Bob should have discovered alice's public key.
|
// Bob should have discovered alice's public key.
|
||||||
if !alice.PublicKey.Equal(discoveredAlicePublicKey) {
|
if !identity.ToPublicKey(alice).Equal(discoveredAlicePublicKey) {
|
||||||
t.Errorf("handshake failed: discovered public key is not equal to alice's public key")
|
t.Errorf("handshake failed: discovered public key is not equal to alice's public key")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -93,9 +93,9 @@ func BenchmarkHandshake(b *testing.B) {
|
|||||||
func runSimulation() error {
|
func runSimulation() error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// alice, represented by an ecdsa key pair.
|
// alice, represented by an ed25519 key pair.
|
||||||
alice, _ := identity.Generate()
|
alice, _ := identity.Generate()
|
||||||
// bob, also represented by an ecdsa key pair.
|
// bob, also represented by an ed25519 key pair.
|
||||||
bob, _ := identity.Generate()
|
bob, _ := identity.Generate()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -123,7 +123,7 @@ func runSimulation() error {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
go func() {
|
go func() {
|
||||||
_, aliceErr = handshake.Initiate(ctx, client, alice, &bob.PublicKey)
|
_, aliceErr = handshake.Initiate(ctx, client, alice, identity.ToPublicKey(bob))
|
||||||
wg.Done()
|
wg.Done()
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ed25519"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -22,11 +22,11 @@ type Addr struct {
|
|||||||
network string
|
network string
|
||||||
ip net.IP
|
ip net.IP
|
||||||
port int
|
port int
|
||||||
publicKey *ecdsa.PublicKey
|
publicKey ed25519.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAddr creates a new Addr.
|
// NewAddr creates a new Addr.
|
||||||
func NewAddr(ip net.IP, port int, publicKey *ecdsa.PublicKey) *Addr {
|
func NewAddr(ip net.IP, port int, publicKey ed25519.PublicKey) *Addr {
|
||||||
network := "dsfx"
|
network := "dsfx"
|
||||||
return &Addr{network, ip, port, publicKey}
|
return &Addr{network, ip, port, publicKey}
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ func ParseAddr(addrRaw string) (*Addr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FromTCPAddr creates a new Addr from a net.TCPAddr.
|
// FromTCPAddr creates a new Addr from a net.TCPAddr.
|
||||||
func FromTCPAddr(addr *net.TCPAddr, publicKey *ecdsa.PublicKey) *Addr {
|
func FromTCPAddr(addr *net.TCPAddr, publicKey ed25519.PublicKey) *Addr {
|
||||||
return &Addr{
|
return &Addr{
|
||||||
network: "dsfx",
|
network: "dsfx",
|
||||||
ip: addr.IP,
|
ip: addr.IP,
|
||||||
@ -102,7 +102,7 @@ func (a *Addr) Port() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PublicKey returns the public key of the Addr.
|
// PublicKey returns the public key of the Addr.
|
||||||
func (a *Addr) PublicKey() *ecdsa.PublicKey {
|
func (a *Addr) PublicKey() ed25519.PublicKey {
|
||||||
return a.publicKey
|
return a.publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ed25519"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -14,12 +14,12 @@ import (
|
|||||||
type Conn struct {
|
type Conn struct {
|
||||||
conn *net.TCPConn
|
conn *net.TCPConn
|
||||||
sessionKey []byte
|
sessionKey []byte
|
||||||
localIdentity *ecdsa.PublicKey
|
localIdentity ed25519.PublicKey
|
||||||
remoteIdentity *ecdsa.PublicKey
|
remoteIdentity ed25519.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConn creates a new Conn.
|
// NewConn creates a new Conn.
|
||||||
func NewConn(conn *net.TCPConn, sessionKey []byte, localIdentity, remoteIdentity *ecdsa.PublicKey) *Conn {
|
func NewConn(conn *net.TCPConn, sessionKey []byte, localIdentity, remoteIdentity ed25519.PublicKey) *Conn {
|
||||||
return &Conn{conn, sessionKey, localIdentity, remoteIdentity}
|
return &Conn{conn, sessionKey, localIdentity, remoteIdentity}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,11 @@ package network
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ed25519"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"koti.casa/numenor-labs/dsfx/pkg/crypto/identity"
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ import (
|
|||||||
type Listener struct {
|
type Listener struct {
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
tcpListener *net.TCPListener
|
tcpListener *net.TCPListener
|
||||||
identity *ecdsa.PrivateKey
|
identity ed25519.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept implements net.Listener.
|
// Accept implements net.Listener.
|
||||||
@ -30,7 +31,7 @@ func (l *Listener) Accept() (net.Conn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(conn, sessionKey, &l.identity.PublicKey, clientIdentity), nil
|
return NewConn(conn, sessionKey, identity.ToPublicKey(l.identity), clientIdentity), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close implements net.Listener.
|
// Close implements net.Listener.
|
||||||
@ -42,5 +43,5 @@ func (l *Listener) Close() error {
|
|||||||
func (l *Listener) Addr() net.Addr {
|
func (l *Listener) Addr() net.Addr {
|
||||||
laddr := l.tcpListener.Addr().(*net.TCPAddr)
|
laddr := l.tcpListener.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
return NewAddr(laddr.IP, laddr.Port, &l.identity.PublicKey)
|
return NewAddr(laddr.IP, laddr.Port, identity.ToPublicKey(l.identity))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package network
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ed25519"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
"koti.casa/numenor-labs/dsfx/pkg/handshake"
|
||||||
@ -12,7 +12,7 @@ import (
|
|||||||
// Dial ...
|
// Dial ...
|
||||||
func Dial(
|
func Dial(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
identity *ecdsa.PrivateKey,
|
identity ed25519.PrivateKey,
|
||||||
laddr *Addr,
|
laddr *Addr,
|
||||||
raddr *Addr,
|
raddr *Addr,
|
||||||
) (*Conn, error) {
|
) (*Conn, error) {
|
||||||
@ -32,7 +32,7 @@ func Dial(
|
|||||||
// Listen ...
|
// Listen ...
|
||||||
func Listen(
|
func Listen(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
identity *ecdsa.PrivateKey,
|
identity ed25519.PrivateKey,
|
||||||
laddr *Addr,
|
laddr *Addr,
|
||||||
) (net.Listener, error) {
|
) (net.Listener, error) {
|
||||||
tcpListener, err := net.ListenTCP("tcp", laddr.TCPAddr())
|
tcpListener, err := net.ListenTCP("tcp", laddr.TCPAddr())
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@ -15,17 +13,10 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the private key to der
|
exported, err := identity.ExportPrivateKey(key)
|
||||||
der, err := x509.MarshalECPrivateKey(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the private key to PEM
|
fmt.Fprint(os.Stdout, string(exported))
|
||||||
pem := pem.EncodeToMemory(&pem.Block{
|
|
||||||
Type: "PRIVATE KEY",
|
|
||||||
Bytes: der,
|
|
||||||
})
|
|
||||||
|
|
||||||
fmt.Fprint(os.Stdout, string(pem))
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user