From a977e69aef5f159047c1487a33c0f33b3304cad7 Mon Sep 17 00:00:00 2001 From: greysoh Date: Sun, 20 Oct 2024 12:02:15 -0400 Subject: [PATCH 1/6] choore: Prepares support for certificate validation in client. --- client/client.go | 62 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/client/client.go b/client/client.go index 7f49ba6..f6601aa 100644 --- a/client/client.go +++ b/client/client.go @@ -41,6 +41,20 @@ func (bismuth BismuthClient) decryptMessage(aead cipher.AEAD, encMsg []byte) ([] return decryptedData, nil } +// Checks to see if a certificate is trusted in the client cache. +// +// - `host`: The host of the server. +// - `certificateFingerprint`: A fingerprint of the servers key. +// - `isSelfSigned`: If true, the certificate is either actually self-signed, or +// verification is dsabled (CheckIfCertificatesAreSigned in BismuthClient is false) +// - `isTrustworthy`: If true, the certificate is signed by 51% of peers. +type CertCheckCallback func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool + +// Connects to a server using a provided method, with host being the host: +// +// OwnConnMethodCallback("google.com:80") +type OwnConnMethodCallback func(address string) (net.Conn, error) + // Bismuth Client type BismuthClient struct { // GOpenPGP public key @@ -48,9 +62,48 @@ type BismuthClient struct { // GOpenPGP private key PrivateKey *crypto.Key + // Check if the certificates are signed if enabled. + // + // If true, "cross-verifies" the server to make sure the certificates are signed. + // + // If false, all certificates will be reported as being self signed because we can't + // really prove otherwise. + CheckIfCertificatesAreSigned bool + // Checks to see if a certificate is trusted in the client cache. + // See CertCheckCallback for more typing information. + CertificateSignChecker CertCheckCallback + + // Connects to a server (used for CheckIfCertificatesAreSigned if enabled/set to true). + ConnectToServer OwnConnMethodCallback + + // GopenPGP instance pgp *crypto.PGPHandle } +// Initializes the client. Should be done automatically if you call New() +// +// If you don't call client.New(), you *MUST* call this function before running bismuth.Conn(). +func (bismuth *BismuthClient) InitializeClient() error { + if bismuth.pgp == nil { + bismuth.pgp = crypto.PGP() + } + + if bismuth.CertificateSignChecker == nil { + bismuth.CertificateSignChecker = func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool { + fmt.Println("WARNING: Using stub CertificateSignChecker. Returing true and ignoring arguments") + return true + } + } + + if bismuth.ConnectToServer == nil { + bismuth.ConnectToServer = func(address string) (net.Conn, error) { + return net.Dial("tcp", address) + } + } + + return nil +} + // Connects to a Bismuth server. This wraps an existing net.Conn interface. // The returned net.Conn is the server, but over bismuth. func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { @@ -204,12 +257,15 @@ func New(pubKey string, privKey string) (*BismuthClient, error) { return nil, err } - pgp := crypto.PGP() - bismuth := BismuthClient{ PublicKey: publicKey, PrivateKey: privateKey, - pgp: pgp, + } + + err = bismuth.InitializeClient() + + if err != nil { + return nil, err } return &bismuth, nil From a5e479cc0c5c85420cc325c839289209819c9459 Mon Sep 17 00:00:00 2001 From: greysoh Date: Sun, 20 Oct 2024 12:13:23 -0400 Subject: [PATCH 2/6] chore: Seperate code into multiple files for client and server. --- client/client.go | 100 ----------------------------------------------- client/typing.go | 46 ++++++++++++++++++++++ client/utils.go | 68 ++++++++++++++++++++++++++++++++ commons/enum.go | 30 +++++++++----- server/server.go | 79 ------------------------------------- server/typing.go | 26 ++++++++++++ server/utils.go | 69 ++++++++++++++++++++++++++++++++ 7 files changed, 230 insertions(+), 188 deletions(-) create mode 100644 client/typing.go create mode 100644 client/utils.go create mode 100644 server/typing.go create mode 100644 server/utils.go diff --git a/client/client.go b/client/client.go index f6601aa..d3f26d3 100644 --- a/client/client.go +++ b/client/client.go @@ -1,8 +1,6 @@ package client import ( - "crypto/cipher" - "crypto/rand" "fmt" "net" @@ -12,74 +10,6 @@ import ( "github.com/ProtonMail/gopenpgp/v3/crypto" ) -func (bismuth BismuthClient) encryptMessage(aead cipher.AEAD, msg []byte) ([]byte, error) { - nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead()) - - if _, err := rand.Read(nonce); err != nil { - return []byte{}, err - } - - encryptedMsg := aead.Seal(nonce, nonce, msg, nil) - return encryptedMsg, nil -} - -func (bismuth BismuthClient) decryptMessage(aead cipher.AEAD, encMsg []byte) ([]byte, error) { - if len(encMsg) < aead.NonceSize() { - return []byte{}, fmt.Errorf("ciphertext too short") - } - - // Split nonce and ciphertext. - nonce, ciphertext := encMsg[:aead.NonceSize()], encMsg[aead.NonceSize():] - - // Decrypt the message and check it wasn't tampered with. - decryptedData, err := aead.Open(nil, nonce, ciphertext, nil) - - if err != nil { - return []byte{}, err - } - - return decryptedData, nil -} - -// Checks to see if a certificate is trusted in the client cache. -// -// - `host`: The host of the server. -// - `certificateFingerprint`: A fingerprint of the servers key. -// - `isSelfSigned`: If true, the certificate is either actually self-signed, or -// verification is dsabled (CheckIfCertificatesAreSigned in BismuthClient is false) -// - `isTrustworthy`: If true, the certificate is signed by 51% of peers. -type CertCheckCallback func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool - -// Connects to a server using a provided method, with host being the host: -// -// OwnConnMethodCallback("google.com:80") -type OwnConnMethodCallback func(address string) (net.Conn, error) - -// Bismuth Client -type BismuthClient struct { - // GOpenPGP public key - PublicKey *crypto.Key - // GOpenPGP private key - PrivateKey *crypto.Key - - // Check if the certificates are signed if enabled. - // - // If true, "cross-verifies" the server to make sure the certificates are signed. - // - // If false, all certificates will be reported as being self signed because we can't - // really prove otherwise. - CheckIfCertificatesAreSigned bool - // Checks to see if a certificate is trusted in the client cache. - // See CertCheckCallback for more typing information. - CertificateSignChecker CertCheckCallback - - // Connects to a server (used for CheckIfCertificatesAreSigned if enabled/set to true). - ConnectToServer OwnConnMethodCallback - - // GopenPGP instance - pgp *crypto.PGPHandle -} - // Initializes the client. Should be done automatically if you call New() // // If you don't call client.New(), you *MUST* call this function before running bismuth.Conn(). @@ -240,33 +170,3 @@ func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { Bismuth: &bmConn, }, nil } - -// Creates a new BismuthClient. -// -// Both `pubKey` and `privKey` are armored PGP public and private keys respectively. -func New(pubKey string, privKey string) (*BismuthClient, error) { - publicKey, err := crypto.NewKeyFromArmored(pubKey) - - if err != nil { - return nil, err - } - - privateKey, err := crypto.NewKeyFromArmored(privKey) - - if err != nil { - return nil, err - } - - bismuth := BismuthClient{ - PublicKey: publicKey, - PrivateKey: privateKey, - } - - err = bismuth.InitializeClient() - - if err != nil { - return nil, err - } - - return &bismuth, nil -} diff --git a/client/typing.go b/client/typing.go new file mode 100644 index 0000000..de537ec --- /dev/null +++ b/client/typing.go @@ -0,0 +1,46 @@ +package client + +import ( + "net" + + "github.com/ProtonMail/gopenpgp/v3/crypto" +) + +// Checks to see if a certificate is trusted in the client cache. +// +// - `host`: The host of the server. +// - `certificateFingerprint`: A fingerprint of the servers key. +// - `isSelfSigned`: If true, the certificate is either actually self-signed, or +// verification is dsabled (CheckIfCertificatesAreSigned in BismuthClient is false) +// - `isTrustworthy`: If true, the certificate is signed by 51% of peers. +type CertCheckCallback func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool + +// Connects to a server using a provided method, with host being the host: +// +// OwnConnMethodCallback("google.com:80") +type OwnConnMethodCallback func(address string) (net.Conn, error) + +// Bismuth Client +type BismuthClient struct { + // GOpenPGP public key + PublicKey *crypto.Key + // GOpenPGP private key + PrivateKey *crypto.Key + + // Check if the certificates are signed if enabled. + // + // If true, "cross-verifies" the server to make sure the certificates are signed. + // + // If false, all certificates will be reported as being self signed because we can't + // really prove otherwise. + CheckIfCertificatesAreSigned bool + // Checks to see if a certificate is trusted in the client cache. + // See CertCheckCallback for more typing information. + CertificateSignChecker CertCheckCallback + + // Connects to a server (used for CheckIfCertificatesAreSigned if enabled/set to true). + ConnectToServer OwnConnMethodCallback + + // GopenPGP instance + pgp *crypto.PGPHandle +} diff --git a/client/utils.go b/client/utils.go new file mode 100644 index 0000000..dc236f5 --- /dev/null +++ b/client/utils.go @@ -0,0 +1,68 @@ +package client + +import ( + "crypto/cipher" + "crypto/rand" + "fmt" + + "github.com/ProtonMail/gopenpgp/v3/crypto" +) + +func (bismuth BismuthClient) encryptMessage(aead cipher.AEAD, msg []byte) ([]byte, error) { + nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead()) + + if _, err := rand.Read(nonce); err != nil { + return []byte{}, err + } + + encryptedMsg := aead.Seal(nonce, nonce, msg, nil) + return encryptedMsg, nil +} + +func (bismuth BismuthClient) decryptMessage(aead cipher.AEAD, encMsg []byte) ([]byte, error) { + if len(encMsg) < aead.NonceSize() { + return []byte{}, fmt.Errorf("ciphertext too short") + } + + // Split nonce and ciphertext. + nonce, ciphertext := encMsg[:aead.NonceSize()], encMsg[aead.NonceSize():] + + // Decrypt the message and check it wasn't tampered with. + decryptedData, err := aead.Open(nil, nonce, ciphertext, nil) + + if err != nil { + return []byte{}, err + } + + return decryptedData, nil +} + +// Creates a new BismuthClient. +// +// Both `pubKey` and `privKey` are armored PGP public and private keys respectively. +func New(pubKey string, privKey string) (*BismuthClient, error) { + publicKey, err := crypto.NewKeyFromArmored(pubKey) + + if err != nil { + return nil, err + } + + privateKey, err := crypto.NewKeyFromArmored(privKey) + + if err != nil { + return nil, err + } + + bismuth := BismuthClient{ + PublicKey: publicKey, + PrivateKey: privateKey, + } + + err = bismuth.InitializeClient() + + if err != nil { + return nil, err + } + + return &bismuth, nil +} diff --git a/commons/enum.go b/commons/enum.go index be1848f..9e78c88 100644 --- a/commons/enum.go +++ b/commons/enum.go @@ -2,32 +2,44 @@ package commons -// Commands +// Core Protocol Commands const ( - // SendPublicKey: Sending public key back and forth + // Sending public key back and forth SendPublicKey = iota - // SwitchToSymmetricKey: Sent by the client along with the symmetric key that is going to be used + // Sent by the client along with the symmetric key that is going to be used SwitchToSymmetricKey - // ClientSendHost: Currently unimplemented. // Client sends what host they are connecting to. + // Currently unimplemented. ClientSendHost - // GetSigningServers: Currently unimplemented. // Gets the signing servers trusting/signing the current server. + // Currently unimplemented. GetSigningServers - // GetTrustedDomains: Currently unimplemented. // Gets the domains that are supported by this certificate (should be cross-checked) + // Currently unimplemented. GetTrustedDomains - // InitiateForwarding: Starts forwarding traffic over this protocol. + // Starts forwarding traffic over this protocol. InitiateForwarding ) -// Encryption algorithms +// Validation API Commands +const ( + // Checks if the domains are valid for a specified key + AreDomainsValidForKey = iota + // Validate a server and keys + ValidateKey + // Status codes + Success + Failure + InternalError +) + +// Encryption Algorithms const ( // Default and only encryption algorithm XChaCha20Poly1305 = iota ) -// Unsigned integer limits +// Unsigned Integer Limits const ( BitLimit24 = 16_777_215 BitLimit16 = 65535 diff --git a/server/server.go b/server/server.go index 4e1c183..b211f6e 100644 --- a/server/server.go +++ b/server/server.go @@ -1,7 +1,6 @@ package server import ( - "fmt" "net" core "git.greysoh.dev/imterah/bismuthd/commons" @@ -13,54 +12,6 @@ import ( "golang.org/x/crypto/chacha20poly1305" ) -// Bismuth Server -type BismuthServer struct { - // Public key to use for transmission - PublicKey *crypto.Key - // Private key to use for transmission - PrivateKey *crypto.Key - - pgp *crypto.PGPHandle - - // Algorithm to use for encryption (currently XChaCha20Poly1305 is the only option) - SymmetricEncryptionAlgorithm int - // Servers that are signing this server. If none, this server becomes self-signed - // in the clients eyes - SigningServers []string - - // Called after a successful handshake & connection. - HandleConnection func(conn net.Conn) error -} - -func (bismuth BismuthServer) encryptMessage(aead cipher.AEAD, msg []byte) ([]byte, error) { - nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead()) - - if _, err := rand.Read(nonce); err != nil { - return []byte{}, err - } - - encryptedMsg := aead.Seal(nonce, nonce, msg, nil) - return encryptedMsg, nil -} - -func (bismuth BismuthServer) decryptMessage(aead cipher.AEAD, encMsg []byte) ([]byte, error) { - if len(encMsg) < aead.NonceSize() { - return []byte{}, fmt.Errorf("ciphertext too short") - } - - // Split nonce and ciphertext. - nonce, ciphertext := encMsg[:aead.NonceSize()], encMsg[aead.NonceSize():] - - // Decrypt the message and check it wasn't tampered with. - decryptedData, err := aead.Open(nil, nonce, ciphertext, nil) - - if err != nil { - return []byte{}, err - } - - return decryptedData, nil -} - // Called to handle a connnection for Bismuth. The conn argument is the client you'd like to handle func (bismuth BismuthServer) HandleProxy(conn net.Conn) error { serverState := "keyHandshake" @@ -229,33 +180,3 @@ func (bismuth BismuthServer) HandleProxy(conn net.Conn) error { } } } - -// Initializes a Bismuth server. -// -// Both `pubKey` and `privKey` are armored PGP public and private keys respectively. -func NewBismuthServer(pubKey string, privKey string, signServers []string, encryptionAlgo int, connHandler func(conn net.Conn) error) (*BismuthServer, error) { - publicKey, err := crypto.NewKeyFromArmored(pubKey) - - if err != nil { - return nil, err - } - - privateKey, err := crypto.NewKeyFromArmored(privKey) - - if err != nil { - return nil, err - } - - pgp := crypto.PGP() - - bismuth := BismuthServer{ - PublicKey: publicKey, - PrivateKey: privateKey, - HandleConnection: connHandler, - SigningServers: signServers, - SymmetricEncryptionAlgorithm: encryptionAlgo, - pgp: pgp, - } - - return &bismuth, nil -} diff --git a/server/typing.go b/server/typing.go new file mode 100644 index 0000000..9f830c3 --- /dev/null +++ b/server/typing.go @@ -0,0 +1,26 @@ +package server + +import ( + "net" + + "github.com/ProtonMail/gopenpgp/v3/crypto" +) + +// Bismuth Server +type BismuthServer struct { + // Public key to use for transmission + PublicKey *crypto.Key + // Private key to use for transmission + PrivateKey *crypto.Key + + pgp *crypto.PGPHandle + + // Algorithm to use for encryption (currently XChaCha20Poly1305 is the only option) + SymmetricEncryptionAlgorithm int + // Servers that are signing this server. If none, this server becomes self-signed + // in the clients eyes + SigningServers []string + + // Called after a successful handshake & connection. + HandleConnection func(conn net.Conn) error +} diff --git a/server/utils.go b/server/utils.go new file mode 100644 index 0000000..3016844 --- /dev/null +++ b/server/utils.go @@ -0,0 +1,69 @@ +package server + +import ( + "crypto/cipher" + "crypto/rand" + "fmt" + "net" + + "github.com/ProtonMail/gopenpgp/v3/crypto" +) + +func (bismuth BismuthServer) encryptMessage(aead cipher.AEAD, msg []byte) ([]byte, error) { + nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead()) + + if _, err := rand.Read(nonce); err != nil { + return []byte{}, err + } + + encryptedMsg := aead.Seal(nonce, nonce, msg, nil) + return encryptedMsg, nil +} + +func (bismuth BismuthServer) decryptMessage(aead cipher.AEAD, encMsg []byte) ([]byte, error) { + if len(encMsg) < aead.NonceSize() { + return []byte{}, fmt.Errorf("ciphertext too short") + } + + // Split nonce and ciphertext. + nonce, ciphertext := encMsg[:aead.NonceSize()], encMsg[aead.NonceSize():] + + // Decrypt the message and check it wasn't tampered with. + decryptedData, err := aead.Open(nil, nonce, ciphertext, nil) + + if err != nil { + return []byte{}, err + } + + return decryptedData, nil +} + +// Initializes a Bismuth server. +// +// Both `pubKey` and `privKey` are armored PGP public and private keys respectively. +func NewBismuthServer(pubKey string, privKey string, signServers []string, encryptionAlgo int, connHandler func(conn net.Conn) error) (*BismuthServer, error) { + publicKey, err := crypto.NewKeyFromArmored(pubKey) + + if err != nil { + return nil, err + } + + privateKey, err := crypto.NewKeyFromArmored(privKey) + + if err != nil { + return nil, err + } + + pgp := crypto.PGP() + + bismuth := BismuthServer{ + PublicKey: publicKey, + PrivateKey: privateKey, + HandleConnection: connHandler, + SigningServers: signServers, + SymmetricEncryptionAlgorithm: encryptionAlgo, + pgp: pgp, + } + + return &bismuth, nil +} From e527413faf81c61c35a0e25f2f84f885928f63e5 Mon Sep 17 00:00:00 2001 From: imterah Date: Thu, 24 Oct 2024 11:39:46 -0400 Subject: [PATCH 3/6] feature: Gets signing server and client infrastructure working. --- README.md | 2 +- client/client.go | 133 ++++++++++++-- client/typing.go | 87 ++++++--- commons/enum.go | 3 - main.go | 312 +++++++++++++++++++++++++++++++-- server/server.go | 55 +++++- server/typing.go | 14 +- server/utils.go | 4 +- signingclient/signingclient.go | 69 ++++++++ signingserver/signingserver.go | 210 ++++++++++++++++++++++ signingserver/typing.go | 14 ++ signingserver/utils.go | 15 ++ tests/integration_test.go | 9 +- 13 files changed, 865 insertions(+), 62 deletions(-) create mode 100644 signingclient/signingclient.go create mode 100644 signingserver/signingserver.go create mode 100644 signingserver/typing.go create mode 100644 signingserver/utils.go diff --git a/README.md b/README.md index 6eeccc4..b9129f5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Bismuth Protocol +# Bismuth Protocol [![Go Reference](https://pkg.go.dev/badge/git.greysoh.dev/imterah/bismuthd.svg)](https://pkg.go.dev/git.greysoh.dev/imterah/bismuthd) The Bismuth protocol is a thin wrapper for any protocol that adds TLS-like features, without being TLS on its own. diff --git a/client/client.go b/client/client.go index d3f26d3..2fef111 100644 --- a/client/client.go +++ b/client/client.go @@ -3,6 +3,7 @@ package client import ( "fmt" "net" + "strings" core "git.greysoh.dev/imterah/bismuthd/commons" "golang.org/x/crypto/chacha20poly1305" @@ -25,7 +26,15 @@ func (bismuth *BismuthClient) InitializeClient() error { } } + if bismuth.AddCertificatesToSignCache == nil { + bismuth.AddCertificatesToSignCache = func(certificates []*BismuthCertificates) { + // do nothing + } + } + if bismuth.ConnectToServer == nil { + bismuth.CheckIfCertificatesAreSigned = true + bismuth.ConnectToServer = func(address string) (net.Conn, error) { return net.Dial("tcp", address) } @@ -36,15 +45,24 @@ func (bismuth *BismuthClient) InitializeClient() error { // Connects to a Bismuth server. This wraps an existing net.Conn interface. // The returned net.Conn is the server, but over bismuth. -func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { +func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults, error) { // Yes, I'm aware defer exists. It won't work if I use it in this context. I'll shank anyone that complains // Exchange our public keys first + hostAndIP := conn.RemoteAddr().String() + hostAndIPColonIndex := strings.Index(hostAndIP, ":") + + if hostAndIPColonIndex == -1 { + return nil, nil, fmt.Errorf("failed to get colon in remote address") + } + + host := hostAndIP[:hostAndIPColonIndex] + ownKey, err := bismuth.PublicKey.GetPublicKey() if err != nil { conn.Close() - return nil, err + return nil, nil, err } pubKeyLengthBytes := make([]byte, 3) @@ -59,17 +77,17 @@ func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { if _, err = conn.Read(messageMode); err != nil { conn.Close() - return nil, err + return nil, nil, err } if messageMode[0] != core.SendPublicKey { conn.Close() - return nil, fmt.Errorf("server failed to return its public key") + return nil, nil, fmt.Errorf("server failed to return its public key") } if _, err = conn.Read(pubKeyLengthBytes); err != nil { conn.Close() - return nil, err + return nil, nil, err } pubKeyLength = core.Int24ToInt32(pubKeyLengthBytes) @@ -77,12 +95,14 @@ func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { if _, err = conn.Read(pubKeyBytes); err != nil { conn.Close() - return nil, err + return nil, nil, err } - if _, err = crypto.NewKey(pubKeyBytes); err != nil { + serverPublicKey, err := crypto.NewKey(pubKeyBytes) + + if err != nil { conn.Close() - return nil, err + return nil, nil, err } // Then exchange the symmetric key @@ -91,19 +111,19 @@ func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { if _, err = conn.Read(messageMode); err != nil { conn.Close() - return nil, err + return nil, nil, err } if messageMode[0] != core.SwitchToSymmetricKey { conn.Close() - return nil, fmt.Errorf("server failed to return symmetric key") + return nil, nil, fmt.Errorf("server failed to return symmetric key") } encryptedSymmKeyLengthInBytes := make([]byte, 3) if _, err = conn.Read(encryptedSymmKeyLengthInBytes); err != nil { conn.Close() - return nil, err + return nil, nil, err } encryptedSymmKeyLength := core.Int24ToInt32(encryptedSymmKeyLengthInBytes) @@ -111,31 +131,110 @@ func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { if _, err = conn.Read(encryptedSymmKey); err != nil { conn.Close() - return nil, err + return nil, nil, err } decHandleForSymmKey, err := bismuth.pgp.Decryption().DecryptionKey(bismuth.PrivateKey).New() if err != nil { - return nil, err + conn.Close() + return nil, nil, err } decryptedSymmKey, err := decHandleForSymmKey.Decrypt(encryptedSymmKey, crypto.Bytes) if err != nil { - return nil, err + conn.Close() + return nil, nil, err } symmKeyInfo := decryptedSymmKey.Bytes() if symmKeyInfo[0] != core.XChaCha20Poly1305 { conn.Close() - return nil, fmt.Errorf("unsupported encryption method recieved") + return nil, nil, fmt.Errorf("unsupported encryption method recieved") } symmKey := symmKeyInfo[1 : chacha20poly1305.KeySize+1] aead, err := chacha20poly1305.NewX(symmKey) + // After that, we send what host we are connecting to (enables fronting/proxy services) + + hostInformation := make([]byte, 1+len(host)) + + hostInformation[0] = core.ClientSendHost + copy(hostInformation[1:], []byte(host)) + + encryptedHostInformationPacket, err := bismuth.encryptMessage(aead, hostInformation) + + if err != nil { + conn.Close() + return nil, nil, err + } + + hostInformationSize := make([]byte, 3) + + core.Int32ToInt24(hostInformationSize, uint32(len(encryptedHostInformationPacket))) + + conn.Write(hostInformationSize) + conn.Write(encryptedHostInformationPacket) + + // Request trusted proxies + + trustedProxyRequest := make([]byte, 1) + trustedProxyRequest[0] = core.GetSigningServers + + encryptedTrustedProxyRequest, err := bismuth.encryptMessage(aead, trustedProxyRequest) + + if err != nil { + conn.Close() + return nil, nil, err + } + + trustedProxyLength := make([]byte, 3) + core.Int32ToInt24(trustedProxyLength, uint32(len(encryptedTrustedProxyRequest))) + + conn.Write(trustedProxyLength) + conn.Write(encryptedTrustedProxyRequest) + + if _, err = conn.Read(trustedProxyLength); err != nil { + conn.Close() + return nil, nil, err + } + + encryptedTrustedProxyResponse := make([]byte, core.Int24ToInt32(trustedProxyLength)) + + if _, err = conn.Read(encryptedTrustedProxyResponse); err != nil { + conn.Close() + return nil, nil, err + } + + trustedProxyResponse, err := bismuth.decryptMessage(aead, encryptedTrustedProxyResponse) + + if err != nil { + conn.Close() + return nil, nil, err + } + + if trustedProxyResponse[0] != core.GetSigningServers { + conn.Close() + return nil, nil, fmt.Errorf("server failed to return its signing servers") + } + + signingServers := strings.Split(string(trustedProxyResponse[1:]), "\n") + isServerSelfSigned := len(trustedProxyResponse)-1 == 0 + + if !isServerSelfSigned { + fmt.Printf("acquired signing servers: '%s'\n", strings.Join(signingServers, ", ")) + } else { + fmt.Println("server is self signed, not printing (non-existent) signing servers") + } + + signResults := BismuthSignResults{ + OverallTrustScore: 100, + ServerPublicKey: serverPublicKey, + } + // Start proxying startForwardingPacket := []byte{ @@ -146,7 +245,7 @@ func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { if err != nil { conn.Close() - return nil, err + return nil, nil, err } encryptedForwardPacketPacketSize := make([]byte, 3) @@ -168,5 +267,5 @@ func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) { return core.BismuthConnWrapped{ Bismuth: &bmConn, - }, nil + }, &signResults, nil } diff --git a/client/typing.go b/client/typing.go index de537ec..ad9a532 100644 --- a/client/typing.go +++ b/client/typing.go @@ -6,25 +6,11 @@ import ( "github.com/ProtonMail/gopenpgp/v3/crypto" ) -// Checks to see if a certificate is trusted in the client cache. -// -// - `host`: The host of the server. -// - `certificateFingerprint`: A fingerprint of the servers key. -// - `isSelfSigned`: If true, the certificate is either actually self-signed, or -// verification is dsabled (CheckIfCertificatesAreSigned in BismuthClient is false) -// - `isTrustworthy`: If true, the certificate is signed by 51% of peers. -type CertCheckCallback func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool - -// Connects to a server using a provided method, with host being the host: -// -// OwnConnMethodCallback("google.com:80") -type OwnConnMethodCallback func(address string) (net.Conn, error) - // Bismuth Client type BismuthClient struct { - // GOpenPGP public key + // GOpenPGP public key for the client PublicKey *crypto.Key - // GOpenPGP private key + // GOpenPGP private key for the client PrivateKey *crypto.Key // Check if the certificates are signed if enabled. @@ -34,13 +20,72 @@ type BismuthClient struct { // If false, all certificates will be reported as being self signed because we can't // really prove otherwise. CheckIfCertificatesAreSigned bool - // Checks to see if a certificate is trusted in the client cache. - // See CertCheckCallback for more typing information. - CertificateSignChecker CertCheckCallback - // Connects to a server (used for CheckIfCertificatesAreSigned if enabled/set to true). - ConnectToServer OwnConnMethodCallback + // Checks to see if a certificate is trusted in the client cache. + // + // - `host`: The host of the server. + // - `certificateFingerprint`: A fingerprint of the servers key. + // - `isSelfSigned`: If true, the certificate is either actually self-signed, or + // verification is dsabled (CheckIfCertificatesAreSigned in BismuthClient is false) + // - `isTrustworthy`: If true, the certificate is signed by 51% or more of peers. + // + // This function will only be called if client.CheckIfCertificatesAreSigned is true. + // + // Example usage inside the Bismuth client source: + // client.CertificateSignChecker("example.com:9090", "6c5eaff6f5c65e65e6f6ce6fc", false, true) + CertificateSignChecker func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool + + // If any certificates are false in the certificate cache, and the client has determined that + // they may need to be added, this function will get called. + // + // All of the certificates that will be called by this function in arguments are ones that + // client.CertificateSignChecker has reported to be untrustworthy, but not all untrustworthy + // certificates will be reported, as they can be trusted by future nodes that you have already + // trusted. + // + // This function will only be called if client.CheckIfCertificatesAreSigned is true. + AddCertificatesToSignCache func(certificates []*BismuthCertificates) + + // Connects to a server. + // This function will only be called if client.CheckIfCertificatesAreSigned is true. + // + // client.ConnectToServer("google.com:80") + ConnectToServer func(address string) (net.Conn, error) // GopenPGP instance pgp *crypto.PGPHandle } + +// Sign result data for the node +type BismuthSignResultData struct { + // Future node pointers in the tree + ChildNodes []*BismuthSignResultData + + // If true, the server is already trusting this node + IsTrustingAlready bool + // If true, server is trusting the previous server + IsTrustingRootServer bool +} + +type BismuthSignResults struct { + // Overall trust score calculated + OverallTrustScore int + // Parent node in tree for sign results + Node *BismuthSignResultData + + // GopenPGP public key + ServerPublicKey *crypto.Key +} + +type BismuthCertificates struct { + // The host of the server + host string + // A fingerprint of the servers key + certificateFingerprint string + // Certificate UserID + certificateUsername string + certificateMail string + + // If true, the certificate is self signed + isSelfSigned bool +} diff --git a/commons/enum.go b/commons/enum.go index 9e78c88..3c7f4ff 100644 --- a/commons/enum.go +++ b/commons/enum.go @@ -9,13 +9,10 @@ const ( // Sent by the client along with the symmetric key that is going to be used SwitchToSymmetricKey // Client sends what host they are connecting to. - // Currently unimplemented. ClientSendHost // Gets the signing servers trusting/signing the current server. - // Currently unimplemented. GetSigningServers // Gets the domains that are supported by this certificate (should be cross-checked) - // Currently unimplemented. GetTrustedDomains // Starts forwarding traffic over this protocol. InitiateForwarding diff --git a/main.go b/main.go index 4ded7a1..1dd63f9 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "context" _ "embed" + "encoding/hex" "fmt" "net" "os" @@ -11,6 +12,8 @@ import ( "git.greysoh.dev/imterah/bismuthd/client" core "git.greysoh.dev/imterah/bismuthd/commons" "git.greysoh.dev/imterah/bismuthd/server" + "git.greysoh.dev/imterah/bismuthd/signingclient" + "git.greysoh.dev/imterah/bismuthd/signingserver" "github.com/charmbracelet/log" "github.com/urfave/cli/v2" "tailscale.com/net/socks5" @@ -19,7 +22,7 @@ import ( //go:embed ascii.txt var asciiArt string -func bismuthClientEntrypoint(cCtx *cli.Context) error { +func clientEntrypoint(cCtx *cli.Context) error { pubKeyFile, err := os.ReadFile(cCtx.String("pubkey")) if err != nil { @@ -37,6 +40,8 @@ func bismuthClientEntrypoint(cCtx *cli.Context) error { bismuth, err := client.New(pubKey, privKey) + log.Debugf("My key fingerprint is: %s", bismuth.PublicKey.GetFingerprint()) + if err != nil { return err } @@ -82,13 +87,15 @@ func bismuthClientEntrypoint(cCtx *cli.Context) error { return nil, err } - conn, err = bismuth.Conn(conn) + conn, returnData, err := bismuth.Conn(conn) if err != nil && err.Error() != "EOF" { log.Errorf("failed to initialize bismuth connection to '%s:%s': '%s'", ip, port, err.Error()) return nil, err } + log.Debugf("Server key fingerprint for '%s' is: %s", addr, returnData.ServerPublicKey.GetFingerprint()) + return conn, err } else { conn, err := net.Dial(network, addr) @@ -110,8 +117,8 @@ func bismuthClientEntrypoint(cCtx *cli.Context) error { return nil } -func bismuthServerEntrypoint(cCtx *cli.Context) error { - relayServers := []string{} +func serverEntrypoint(cCtx *cli.Context) error { + signingServers := []string{} pubKeyFile, err := os.ReadFile(cCtx.String("pubkey")) @@ -130,7 +137,8 @@ func bismuthServerEntrypoint(cCtx *cli.Context) error { network := fmt.Sprintf("%s:%s", cCtx.String("source-ip"), cCtx.String("source-port")) - bismuth, err := server.NewBismuthServer(pubKey, privKey, relayServers, core.XChaCha20Poly1305, func(connBismuth net.Conn) error { + bismuth, err := server.NewBismuthServer(pubKey, privKey, signingServers, core.XChaCha20Poly1305) + bismuth.HandleConnection = func(connBismuth net.Conn, _ *server.ClientMetadata) error { connDialed, err := net.Dial("tcp", network) if err != nil { @@ -183,7 +191,7 @@ func bismuthServerEntrypoint(cCtx *cli.Context) error { }() return nil - }) + } if err != nil { return err @@ -201,23 +209,197 @@ func bismuthServerEntrypoint(cCtx *cli.Context) error { for { conn, err := listener.Accept() + + if err != nil { + log.Warnf("failed to accept connection: '%s'", err.Error()) + continue + } + log.Debugf("Recieved connection from '%s'", conn.RemoteAddr().String()) + go func() { + err := bismuth.HandleProxy(conn) + + if err != nil && err.Error() != "EOF" { + log.Warnf("connection crashed/dropped during proxy handling: '%s'", err.Error()) + } + }() + } +} + +func signingServerEntrypoint(cCtx *cli.Context) error { + log.Warn("Using the built-in bismuth signing server in production is a horrible idea as it has no validation!") + log.Warn("Consider writing using a custom solution that's based on the signing server code, rather than the default implementation.") + + signServers := []string{} + + pubKeyFile, err := os.ReadFile(cCtx.String("pubkey")) + + if err != nil { + return err + } + + privKeyFile, err := os.ReadFile(cCtx.String("privkey")) + + if err != nil { + return err + } + + pubKey := string(pubKeyFile) + privKey := string(privKeyFile) + + listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", cCtx.String("ip"), cCtx.String("port"))) + + if err != nil { + return err + } + + bismuthServer, err := server.NewBismuthServer(pubKey, privKey, signServers, core.XChaCha20Poly1305) + + if err != nil { + return nil + } + + // I'd like to use the SigningServer struct, but I can't really do that + _, err = signingserver.New(bismuthServer) + + if err != nil { + return nil + } + + defer listener.Close() + + log.Info("Bismuth signing server is listening...") + + for { + conn, err := listener.Accept() + if err != nil { log.Warn(err.Error()) continue } + log.Debugf("Recieved connection from '%s'", conn.RemoteAddr().String()) + go func() { - err := bismuth.HandleProxy(conn) + err = bismuthServer.HandleProxy(conn) if err != nil && err.Error() != "EOF" { - log.Warnf("Connection crashed/dropped during proxy handling: '%s'", err.Error()) + log.Warnf("connection crashed/dropped during proxy handling: '%s'", err.Error()) + return } }() } } +func verifyCert(cCtx *cli.Context) error { + domainList := strings.Split(cCtx.String("domain-names"), ":") + pubKeyFile, err := os.ReadFile(cCtx.String("pubkey")) + + if err != nil { + return err + } + + privKeyFile, err := os.ReadFile(cCtx.String("privkey")) + + if err != nil { + return err + } + + pubKey := string(pubKeyFile) + privKey := string(privKeyFile) + + bismuthClient, err := client.New(pubKey, privKey) + + if err != nil { + return err + } + + dialedConn, err := net.Dial("tcp", cCtx.String("signing-server")) + + if err != nil { + return err + } + + conn, certResults, err := bismuthClient.Conn(dialedConn) + + if err != nil { + return err + } + + if certResults.OverallTrustScore < 50 { + return fmt.Errorf("overall trust score is below 50% for certificate") + } + + fmt.Println("Sending signing request to sign server...") + + hasBeenTrusted, err := signingclient.RequestDomainToBeTrusted(conn, domainList, "") + + if hasBeenTrusted { + fmt.Println("Server has been successfully signed.") + } else { + fmt.Println("Server has not been successfully signed.") + os.Exit(1) + } + + return nil +} + +func signCert(cCtx *cli.Context) error { + domainList := strings.Split(cCtx.String("domain-names"), ":") + keyFingerprint, err := hex.DecodeString(cCtx.String("key-fingerprint")) + + if err != nil { + return err + } + + pubKeyFile, err := os.ReadFile(cCtx.String("pubkey")) + + if err != nil { + return err + } + + privKeyFile, err := os.ReadFile(cCtx.String("privkey")) + + if err != nil { + return err + } + + pubKey := string(pubKeyFile) + privKey := string(privKeyFile) + + bismuthClient, err := client.New(pubKey, privKey) + + if err != nil { + return err + } + + dialedConn, err := net.Dial("tcp", cCtx.String("signing-server")) + + if err != nil { + return err + } + + conn, certResults, err := bismuthClient.Conn(dialedConn) + + if err != nil { + return err + } + + if certResults.OverallTrustScore < 50 { + return fmt.Errorf("overall trust score is below 50% for certificate") + } + + isTrusted, err := signingclient.IsDomainTrusted(conn, keyFingerprint, domainList) + fmt.Printf("is certificate trusted: %t\n", isTrusted) + + if !isTrusted { + os.Exit(1) + } + + return nil +} + func main() { fmt.Println(asciiArt) fmt.Print("Implementation of the Bismuth protocol\n\n") @@ -282,7 +464,7 @@ func main() { }, }, Usage: "client for the Bismuth protocol", - Action: bismuthClientEntrypoint, + Action: clientEntrypoint, }, { Name: "server", @@ -308,6 +490,14 @@ func main() { Usage: "port to connect to", Required: true, }, + &cli.StringFlag{ + Name: "signing-servers", + Usage: "servers trusting/\"signing\" the public key. seperated using colons", + }, + &cli.StringFlag{ + Name: "domain-names", + Usage: "domain names the key is authorized to use. seperated using colons", + }, &cli.StringFlag{ Name: "dest-ip", Usage: "IP to listen on", @@ -320,7 +510,109 @@ func main() { }, }, Usage: "server for the Bismuth protocol", - Action: bismuthServerEntrypoint, + Action: serverEntrypoint, + }, + { + Name: "test-sign-server", + Aliases: []string{"tss"}, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "pubkey", + Usage: "path to PGP public key", + Required: true, + }, + &cli.StringFlag{ + Name: "privkey", + Usage: "path to PGP private key", + Required: true, + }, + &cli.StringFlag{ + Name: "ip", + Usage: "IP to listen on", + Value: "0.0.0.0", + }, + &cli.StringFlag{ + Name: "port", + Usage: "port to listen on", + Value: "9090", + }, + &cli.StringFlag{ + Name: "domain-names", + Usage: "domain names the key is authorized to use. seperated using colons", + }, + &cli.StringFlag{ + Name: "signing-server", + Usage: "domain names the key is authorized to use. seperated using colons", + }, + }, + Usage: "test signing server for the Bismuth protocol", + Action: signingServerEntrypoint, + }, + { + Name: "sign-tool", + Aliases: []string{"st"}, + Subcommands: []*cli.Command{ + { + Name: "is-verified", + Aliases: []string{"i", "iv", "cv"}, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "key-fingerprint", + Usage: "fingerprint of key", + Required: true, + }, + &cli.StringFlag{ + Name: "pubkey", + Usage: "path to PGP public key", + }, + &cli.StringFlag{ + Name: "privkey", + Usage: "path to PGP private key", + }, + &cli.StringFlag{ + Name: "domain-names", + Usage: "domain names the key is authorized to use. seperated using colons", + Required: true, + }, + &cli.StringFlag{ + Name: "signing-server", + Usage: "signing server to use", + Required: true, + }, + }, + Usage: "check if a certificate is verified for Bismuth", + Action: signCert, + }, + { + Name: "verify-cert", + Aliases: []string{"v", "vc"}, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "pubkey", + Usage: "path to PGP public key", + Required: true, + }, + &cli.StringFlag{ + Name: "privkey", + Usage: "path to PGP private key", + Required: true, + }, + &cli.StringFlag{ + Name: "domain-names", + Usage: "domain names the key is authorized to use. seperated using colons", + Required: true, + }, + &cli.StringFlag{ + Name: "signing-server", + Usage: "signing server to use", + Required: true, + }, + }, + Usage: "verifies certificate for Bismuth", + Action: verifyCert, + }, + }, + Usage: "signing tool for Bismuth", }, }, } diff --git a/server/server.go b/server/server.go index b211f6e..c6f6c7f 100644 --- a/server/server.go +++ b/server/server.go @@ -162,7 +162,54 @@ func (bismuth BismuthServer) HandleProxy(conn net.Conn) error { return err } - if packet[0] == core.InitiateForwarding { + switch packet[0] { + case core.GetSigningServers: + totalPacketContents := make([]byte, 1) + totalPacketContents[0] = core.GetSigningServers + + for index, signServer := range bismuth.SigningServers { + totalPacketContents = append(totalPacketContents, []byte(signServer)...) + + if index+1 != len(bismuth.SigningServers) { + totalPacketContents = append(totalPacketContents, '\n') + } + } + + encryptedPacket, err := bismuth.encryptMessage(aead, totalPacketContents) + + if err != nil { + return err + } + + encryptedPacketLength := make([]byte, 3) + core.Int32ToInt24(encryptedPacketLength, uint32(len(encryptedPacket))) + + conn.Write(encryptedPacketLength) + conn.Write(encryptedPacket) + case core.GetTrustedDomains: + totalPacketContents := make([]byte, 1) + totalPacketContents[0] = core.GetTrustedDomains + + for index, trustedDomain := range bismuth.TrustedDomains { + totalPacketContents = append(totalPacketContents, []byte(trustedDomain)...) + + if index+1 != len(bismuth.TrustedDomains) { + totalPacketContents = append(totalPacketContents, '\n') + } + } + + encryptedPacket, err := bismuth.encryptMessage(aead, totalPacketContents) + + if err != nil { + return err + } + + encryptedPacketLength := make([]byte, 3) + core.Int32ToInt24(encryptedPacketLength, uint32(len(encryptedPacket))) + + conn.Write(encryptedPacketLength) + conn.Write(encryptedPacket) + case core.InitiateForwarding: bmConn := core.BismuthConn{ Aead: aead, PassedConn: conn, @@ -171,9 +218,13 @@ func (bismuth BismuthServer) HandleProxy(conn net.Conn) error { bmConn.DoInitSteps() + metadata := ClientMetadata{ + ClientPublicKey: clientPublicKey, + } + err := bismuth.HandleConnection(core.BismuthConnWrapped{ Bismuth: &bmConn, - }) + }, &metadata) return err } diff --git a/server/typing.go b/server/typing.go index 9f830c3..bab25c4 100644 --- a/server/typing.go +++ b/server/typing.go @@ -13,14 +13,24 @@ type BismuthServer struct { // Private key to use for transmission PrivateKey *crypto.Key + // GopenPGP instance pgp *crypto.PGPHandle // Algorithm to use for encryption (currently XChaCha20Poly1305 is the only option) SymmetricEncryptionAlgorithm int // Servers that are signing this server. If none, this server becomes self-signed - // in the clients eyes + // in the clients eyes. SigningServers []string + // Domains that the certificate is authorized to use. This will be checked by the + // signing servers. + TrustedDomains []string // Called after a successful handshake & connection. - HandleConnection func(conn net.Conn) error + HandleConnection func(conn net.Conn, metadata *ClientMetadata) error +} + +// Metadata from the client that may be helpful for the server to have. +type ClientMetadata struct { + // Client's public key + ClientPublicKey *crypto.Key } diff --git a/server/utils.go b/server/utils.go index 3016844..f57c280 100644 --- a/server/utils.go +++ b/server/utils.go @@ -4,7 +4,6 @@ import ( "crypto/cipher" "crypto/rand" "fmt" - "net" "github.com/ProtonMail/gopenpgp/v3/crypto" ) @@ -41,7 +40,7 @@ func (bismuth BismuthServer) decryptMessage(aead cipher.AEAD, encMsg []byte) ([] // Initializes a Bismuth server. // // Both `pubKey` and `privKey` are armored PGP public and private keys respectively. -func NewBismuthServer(pubKey string, privKey string, signServers []string, encryptionAlgo int, connHandler func(conn net.Conn) error) (*BismuthServer, error) { +func NewBismuthServer(pubKey string, privKey string, signServers []string, encryptionAlgo int) (*BismuthServer, error) { publicKey, err := crypto.NewKeyFromArmored(pubKey) if err != nil { @@ -59,7 +58,6 @@ func NewBismuthServer(pubKey string, privKey string, signServers []string, encry bismuth := BismuthServer{ PublicKey: publicKey, PrivateKey: privateKey, - HandleConnection: connHandler, SigningServers: signServers, SymmetricEncryptionAlgorithm: encryptionAlgo, pgp: pgp, diff --git a/signingclient/signingclient.go b/signingclient/signingclient.go new file mode 100644 index 0000000..0fe3ebb --- /dev/null +++ b/signingclient/signingclient.go @@ -0,0 +1,69 @@ +package signingclient + +import ( + "encoding/binary" + "net" + "strings" + + core "git.greysoh.dev/imterah/bismuthd/commons" +) + +func IsDomainTrusted(conn net.Conn, keyFingerprint []byte, domainList []string) (bool, error) { + domainListAsString := strings.Join(domainList, "\n") + + keyFingerprintSize := len(keyFingerprint) + domainListSize := len(domainListAsString) + + domainTrustedCommand := make([]byte, 1+2+2+keyFingerprintSize+domainListSize) + + domainTrustedCommand[0] = core.AreDomainsValidForKey + currentOffset := 1 + + binary.BigEndian.PutUint16(domainTrustedCommand[currentOffset:currentOffset+2], uint16(keyFingerprintSize)) + copy(domainTrustedCommand[2+currentOffset:2+currentOffset+keyFingerprintSize], keyFingerprint) + + currentOffset += 2 + keyFingerprintSize + + binary.BigEndian.PutUint16(domainTrustedCommand[currentOffset:currentOffset+2], uint16(domainListSize)) + copy(domainTrustedCommand[2+currentOffset:2+currentOffset+domainListSize], []byte(domainListAsString)) + + conn.Write(domainTrustedCommand) + + requestResponse := make([]byte, 1) + + if _, err := conn.Read(requestResponse); err != nil { + return false, err + } + + return requestResponse[0] == core.Success, nil +} + +func RequestDomainToBeTrusted(conn net.Conn, domainList []string, additionalInformation string) (bool, error) { + domainListAsString := strings.Join(domainList, "\n") + + domainListSize := len(domainListAsString) + additionalInfoSize := len(additionalInformation) + + requestDomainTrust := make([]byte, 1+2+2+domainListSize+additionalInfoSize) + + requestDomainTrust[0] = core.ValidateKey + currentOffset := 1 + + binary.BigEndian.PutUint16(requestDomainTrust[currentOffset:currentOffset+2], uint16(domainListSize)) + copy(requestDomainTrust[2+currentOffset:2+currentOffset+domainListSize], []byte(domainListAsString)) + + currentOffset += 2 + domainListSize + + binary.BigEndian.PutUint16(requestDomainTrust[currentOffset:currentOffset+2], uint16(additionalInfoSize)) + copy(requestDomainTrust[2:currentOffset:2+currentOffset+additionalInfoSize], []byte(additionalInformation)) + + conn.Write(requestDomainTrust) + + requestResponse := make([]byte, 1) + + if _, err := conn.Read(requestResponse); err != nil { + return false, err + } + + return requestResponse[0] == core.Success, nil +} diff --git a/signingserver/signingserver.go b/signingserver/signingserver.go new file mode 100644 index 0000000..b226537 --- /dev/null +++ b/signingserver/signingserver.go @@ -0,0 +1,210 @@ +package signingserver + +import ( + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "net" + "strings" + + core "git.greysoh.dev/imterah/bismuthd/commons" + "git.greysoh.dev/imterah/bismuthd/server" +) + +func (signServer *BismuthSigningServer) InitializeServer() error { + if signServer.AddVerifyHandler == nil { + fmt.Println("WARN: You are using the default AddVerifyHandler in SignServer! This is a bad idea. Please write your own implementation!") + + signServer.AddVerifyHandler = func(serverAddr string, serverKeyFingerprint string, serverDomainList []string, additionalClientProvidedInfo string) (bool, error) { + domainListHash := sha256.Sum256([]byte(strings.Join(serverDomainList, ":"))) + + signServer.builtInVerifyMapStore[serverAddr+".fingerprint"] = serverKeyFingerprint + signServer.builtInVerifyMapStore[serverAddr+".domainListHash"] = hex.EncodeToString(domainListHash[:]) + + return true, nil + } + } + + if signServer.VerifyServerHandler == nil { + fmt.Println("WARN: You are using the default VerifyServerHandler in SignServer! This is a bad idea. Please write your own implementation!") + + signServer.VerifyServerHandler = func(serverAddr string, serverKeyFingerprint string, serverDomainList []string) (bool, error) { + domainListHash := sha256.Sum256([]byte(strings.Join(serverDomainList, ":"))) + domainListHashHex := hex.EncodeToString(domainListHash[:]) + + if storedKeyFingerprint, ok := signServer.builtInVerifyMapStore[serverAddr+".fingerprint"]; ok { + if storedKeyFingerprint != serverKeyFingerprint { + return false, nil + } + } else { + return false, nil + } + + if storedDomainListHashHex, ok := signServer.builtInVerifyMapStore[serverAddr+".domainListHash"]; ok { + if storedDomainListHashHex != domainListHashHex { + return false, nil + } + } else { + return false, nil + } + + return true, nil + } + } + + if signServer.builtInVerifyMapStore == nil { + signServer.builtInVerifyMapStore = map[string]string{} + } + + signServer.BismuthServer.HandleConnection = signServer.connHandler + return nil +} + +func (signServer *BismuthSigningServer) connHandler(conn net.Conn, metadata *server.ClientMetadata) error { + defer conn.Close() + requestType := make([]byte, 1) + + hostAndIP := conn.RemoteAddr().String() + hostAndIPColonIndex := strings.Index(hostAndIP, ":") + + if hostAndIPColonIndex == -1 { + return fmt.Errorf("failed to get colon in remote address") + } + + host := hostAndIP[:hostAndIPColonIndex] + clientKeyFingerprint := metadata.ClientPublicKey.GetFingerprint() + + for { + if _, err := conn.Read(requestType); err != nil { + return err + } + + if requestType[0] == core.AreDomainsValidForKey { + // This is probably a bit too big, but I'd like to air on the side of caution here... + keyFingerprintLength := make([]byte, 2) + + fmt.Println("keyFingerLen") + + if _, err := conn.Read(keyFingerprintLength); err != nil { + return err + } + + keyFingerprintBytes := make([]byte, binary.BigEndian.Uint16(keyFingerprintLength)) + + fmt.Println("keyFingerBytes") + + if _, err := conn.Read(keyFingerprintBytes); err != nil { + return err + } + + keyFingerprint := hex.EncodeToString(keyFingerprintBytes) + + serverDomainListLength := make([]byte, 2) + + fmt.Println("serverDomainListLen") + + if _, err := conn.Read(serverDomainListLength); err != nil { + return err + } + + serverDomainListBytes := make([]byte, binary.BigEndian.Uint16(serverDomainListLength)) + + fmt.Println("serverDomainList") + fmt.Printf("len: %d\n", binary.BigEndian.Uint16(serverDomainListLength)) + + if _, err := conn.Read(serverDomainListBytes); err != nil { + return err + } + + fmt.Println("done") + + serverDomainList := strings.Split(string(serverDomainListBytes), "\n") + + // We can't trust anything if they aren't advertising any domains/IPs + if len(serverDomainList) == 0 { + requestResponse := make([]byte, 1) + requestResponse[0] = core.Failure + + conn.Write(requestResponse) + continue + } + + isVerified, err := signServer.VerifyServerHandler(host, keyFingerprint, serverDomainList) + + if err != nil { + requestResponse := make([]byte, 1) + requestResponse[0] = core.InternalError + + conn.Write(requestResponse) + + return err + } + + if isVerified { + requestResponse := make([]byte, 1) + requestResponse[0] = core.Success + + conn.Write(requestResponse) + } else { + requestResponse := make([]byte, 1) + requestResponse[0] = core.Failure + + conn.Write(requestResponse) + } + } else if requestType[0] == core.ValidateKey { + // This is probably a bit too big, but I'd like to air on the side of caution here... + serverDomainListLength := make([]byte, 2) + + if _, err := conn.Read(serverDomainListLength); err != nil { + return err + } + + serverDomainListBytes := make([]byte, binary.BigEndian.Uint16(serverDomainListLength)) + + if _, err := conn.Read(serverDomainListBytes); err != nil { + return err + } + + serverDomainList := strings.Split(string(serverDomainListBytes), "\n") + + additionalArgumentsLength := make([]byte, 2) + var additionalArgumentsSize uint16 + + if _, err := conn.Read(additionalArgumentsLength); err != nil { + return err + } + + additionalArgumentsSize = binary.BigEndian.Uint16(additionalArgumentsLength) + additionalArguments := "" + + if additionalArgumentsSize != 0 { + additionalArgumentsBytes := make([]byte, additionalArgumentsSize) + + if _, err := conn.Read(additionalArgumentsBytes); err != nil { + return err + } + + additionalArguments = string(additionalArgumentsBytes) + } + + isAddedToTrust, err := signServer.AddVerifyHandler(host, clientKeyFingerprint, serverDomainList, additionalArguments) + + if err != nil { + return err + } + + if isAddedToTrust { + requestResponse := make([]byte, 1) + requestResponse[0] = core.Success + + conn.Write(requestResponse) + } else { + requestResponse := make([]byte, 1) + requestResponse[0] = core.Failure + + conn.Write(requestResponse) + } + } + } +} diff --git a/signingserver/typing.go b/signingserver/typing.go new file mode 100644 index 0000000..e15f7cd --- /dev/null +++ b/signingserver/typing.go @@ -0,0 +1,14 @@ +package signingserver + +import "git.greysoh.dev/imterah/bismuthd/server" + +type AddVerifyHandlerCallback func(serverAddr string, serverKeyFingerprint string, serverAdvertisedTrustList []string, additionalClientProvidedInfo string) (bool, error) +type VerifyServerHandlerCallback func(serverAddr string, serverKeyFingerprint string, serverDomainList []string) (bool, error) + +type BismuthSigningServer struct { + BismuthServer *server.BismuthServer + + AddVerifyHandler AddVerifyHandlerCallback + VerifyServerHandler VerifyServerHandlerCallback + builtInVerifyMapStore map[string]string +} diff --git a/signingserver/utils.go b/signingserver/utils.go new file mode 100644 index 0000000..955524a --- /dev/null +++ b/signingserver/utils.go @@ -0,0 +1,15 @@ +package signingserver + +import "git.greysoh.dev/imterah/bismuthd/server" + +func New(bismuthServer *server.BismuthServer) (*BismuthSigningServer, error) { + signServer := BismuthSigningServer{ + BismuthServer: bismuthServer, + } + + if err := signServer.InitializeServer(); err != nil { + return nil, err + } + + return &signServer, nil +} diff --git a/tests/integration_test.go b/tests/integration_test.go index 9e56be7..aa657fe 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -19,6 +19,7 @@ var testProtocolTxRxBufCount = 32 // Tests protocol transmitting and receiving // This is designed to be a nightmare scenario for the protocol to push the limits on what would be possible. func TestProtocolTxRx(t *testing.T) { + t.Log("running tests") pubKeyCli, privKeyCli, err := CreateKeyring("alice", "alice@contoso.com") if err != nil { @@ -52,7 +53,9 @@ func TestProtocolTxRx(t *testing.T) { t.Fatalf("failed to listen on TCP for localhost (%s)", err.Error()) } - bismuth, err := server.NewBismuthServer(pubKeyServ, privKeyServ, []string{}, commons.XChaCha20Poly1305, func(conn net.Conn) error { + bismuth, err := server.NewBismuthServer(pubKeyServ, privKeyServ, []string{}, commons.XChaCha20Poly1305) + + bismuth.HandleConnection = func(conn net.Conn, _ *server.ClientMetadata) error { for entryCount, randomDataSlice := range randomDataSlices { _, err = conn.Write(randomDataSlice) @@ -62,7 +65,7 @@ func TestProtocolTxRx(t *testing.T) { } return nil - }) + } // TODO: fix these warnings? go func() { @@ -92,7 +95,7 @@ func TestProtocolTxRx(t *testing.T) { t.Fatalf("failed to connect to bismuth server (%s)", err.Error()) } - conn, err := bismuthClient.Conn(originalConn) + conn, _, err := bismuthClient.Conn(originalConn) if err != nil { t.Fatalf("bismuth client failed to handshake when connecting to server (%s)", err.Error()) From 86ad3e174f95607331744d4fce8e1f42d5e039b8 Mon Sep 17 00:00:00 2001 From: imterah Date: Thu, 24 Oct 2024 11:43:47 -0400 Subject: [PATCH 4/6] chore: Remove extra code. --- main.go | 2 +- signingserver/signingserver.go | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/main.go b/main.go index 1dd63f9..e2e0f18 100644 --- a/main.go +++ b/main.go @@ -391,7 +391,7 @@ func signCert(cCtx *cli.Context) error { } isTrusted, err := signingclient.IsDomainTrusted(conn, keyFingerprint, domainList) - fmt.Printf("is certificate trusted: %t\n", isTrusted) + fmt.Printf("Certificate trust status: %t\n", isTrusted) if !isTrusted { os.Exit(1) diff --git a/signingserver/signingserver.go b/signingserver/signingserver.go index b226537..0ff29e7 100644 --- a/signingserver/signingserver.go +++ b/signingserver/signingserver.go @@ -84,41 +84,29 @@ func (signServer *BismuthSigningServer) connHandler(conn net.Conn, metadata *ser // This is probably a bit too big, but I'd like to air on the side of caution here... keyFingerprintLength := make([]byte, 2) - fmt.Println("keyFingerLen") - if _, err := conn.Read(keyFingerprintLength); err != nil { return err } keyFingerprintBytes := make([]byte, binary.BigEndian.Uint16(keyFingerprintLength)) - fmt.Println("keyFingerBytes") - if _, err := conn.Read(keyFingerprintBytes); err != nil { return err } keyFingerprint := hex.EncodeToString(keyFingerprintBytes) - serverDomainListLength := make([]byte, 2) - fmt.Println("serverDomainListLen") - if _, err := conn.Read(serverDomainListLength); err != nil { return err } serverDomainListBytes := make([]byte, binary.BigEndian.Uint16(serverDomainListLength)) - fmt.Println("serverDomainList") - fmt.Printf("len: %d\n", binary.BigEndian.Uint16(serverDomainListLength)) - if _, err := conn.Read(serverDomainListBytes); err != nil { return err } - fmt.Println("done") - serverDomainList := strings.Split(string(serverDomainListBytes), "\n") // We can't trust anything if they aren't advertising any domains/IPs From 00a57443d48d4615b9ca2aa6fdf5b168482c0a87 Mon Sep 17 00:00:00 2001 From: imterah Date: Fri, 25 Oct 2024 11:59:16 -0400 Subject: [PATCH 5/6] feature: Adds signing validation support to the client. --- client/client.go | 251 +++++++++++++++++++++++++++++--------- client/typing.go | 24 ++-- main.go | 38 ++++-- server/server.go | 2 + server/utils.go | 3 +- tests/integration_test.go | 4 +- 6 files changed, 242 insertions(+), 80 deletions(-) diff --git a/client/client.go b/client/client.go index 2fef111..824b6f9 100644 --- a/client/client.go +++ b/client/client.go @@ -6,11 +6,34 @@ import ( "strings" core "git.greysoh.dev/imterah/bismuthd/commons" + "git.greysoh.dev/imterah/bismuthd/signingclient" "golang.org/x/crypto/chacha20poly1305" "github.com/ProtonMail/gopenpgp/v3/crypto" ) +func computeNodes(children []*BismuthSignResultData) (int, int) { + totalServerCount := 0 + passedServerCount := 0 + + for _, child := range children { + totalServerCount += 1 + + if child.IsTrusting { + passedServerCount += 1 + } + + if len(child.ChildNodes) != 0 { + recievedTotalCount, recievedPassedCount := computeNodes(child.ChildNodes) + + totalServerCount += recievedTotalCount + passedServerCount += recievedPassedCount + } + } + + return totalServerCount, passedServerCount +} + // Initializes the client. Should be done automatically if you call New() // // If you don't call client.New(), you *MUST* call this function before running bismuth.Conn(). @@ -20,7 +43,7 @@ func (bismuth *BismuthClient) InitializeClient() error { } if bismuth.CertificateSignChecker == nil { - bismuth.CertificateSignChecker = func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool { + bismuth.CertificateSignChecker = func(host, certificateFingerprint string, isSelfSigned bool) bool { fmt.Println("WARNING: Using stub CertificateSignChecker. Returing true and ignoring arguments") return true } @@ -40,9 +63,46 @@ func (bismuth *BismuthClient) InitializeClient() error { } } + bismuth.CheckIfCertificatesAreSigned = true + return nil } +func (bismuth *BismuthClient) checkIfDomainIsTrusted(servers, advertisedDomains []string) ([]*BismuthSignResultData, error) { + signResultData := make([]*BismuthSignResultData, len(servers)) + + for index, server := range servers { + baseConn, err := bismuth.ConnectToServer(server) + + if err != nil { + return signResultData, err + } + + defer baseConn.Close() + + conn, signResultsForConn, err := bismuth.Conn(baseConn) + + if err != nil { + return signResultData, err + } + + isTrusted, err := signingclient.IsDomainTrusted(conn, signResultsForConn.ServerPublicKey.GetFingerprintBytes(), advertisedDomains) + + if signResultsForConn.OverallTrustScore < 50 { + isTrusted = false + } + + signResultData[index] = &BismuthSignResultData{ + IsTrusting: isTrusted, + ChildNodes: []*BismuthSignResultData{ + signResultsForConn.Node, + }, + } + } + + return signResultData, nil +} + // Connects to a Bismuth server. This wraps an existing net.Conn interface. // The returned net.Conn is the server, but over bismuth. func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults, error) { @@ -158,6 +218,139 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults symmKey := symmKeyInfo[1 : chacha20poly1305.KeySize+1] aead, err := chacha20poly1305.NewX(symmKey) + // Request trusted domains + + trustedDomainsRequest := make([]byte, 1) + trustedDomainsRequest[0] = core.GetTrustedDomains + + encryptedTrustedDomainRequest, err := bismuth.encryptMessage(aead, trustedDomainsRequest) + + if err != nil { + conn.Close() + return nil, nil, err + } + + trustedDomainLength := make([]byte, 3) + core.Int32ToInt24(trustedDomainLength, uint32(len(encryptedTrustedDomainRequest))) + + conn.Write(trustedDomainLength) + conn.Write(encryptedTrustedDomainRequest) + + if _, err = conn.Read(trustedDomainLength); err != nil { + conn.Close() + return nil, nil, err + } + + encryptedTrustedDomainResponse := make([]byte, core.Int24ToInt32(trustedDomainLength)) + + if _, err = conn.Read(encryptedTrustedDomainResponse); err != nil { + conn.Close() + return nil, nil, err + } + + trustedDomainResponse, err := bismuth.decryptMessage(aead, encryptedTrustedDomainResponse) + + if err != nil { + conn.Close() + return nil, nil, err + } + + if trustedDomainResponse[0] != core.GetTrustedDomains { + conn.Close() + return nil, nil, fmt.Errorf("server failed to return its signing servers") + } + + trustedDomains := strings.Split(string(trustedDomainResponse[1:]), "\n") + + // Request signing servers + + signingServerRequest := make([]byte, 1) + signingServerRequest[0] = core.GetSigningServers + + encryptedSigningServerRequest, err := bismuth.encryptMessage(aead, signingServerRequest) + + if err != nil { + conn.Close() + return nil, nil, err + } + + signingRequestLength := make([]byte, 3) + core.Int32ToInt24(signingRequestLength, uint32(len(encryptedSigningServerRequest))) + + conn.Write(signingRequestLength) + conn.Write(encryptedSigningServerRequest) + + if _, err = conn.Read(signingRequestLength); err != nil { + conn.Close() + return nil, nil, err + } + + encryptedSigningRequestResponse := make([]byte, core.Int24ToInt32(signingRequestLength)) + + if _, err = conn.Read(encryptedSigningRequestResponse); err != nil { + conn.Close() + return nil, nil, err + } + + signingServerResponse, err := bismuth.decryptMessage(aead, encryptedSigningRequestResponse) + + if err != nil { + conn.Close() + return nil, nil, err + } + + if signingServerResponse[0] != core.GetSigningServers { + conn.Close() + return nil, nil, fmt.Errorf("server failed to return its signing servers") + } + + // Check if the server is signed + + signingServers := strings.Split(string(signingServerResponse[1:]), "\n") + isServerSelfSigned := len(signingServers)-1 == 0 || len(trustedDomains)-1 == 0 + + rootNode := &BismuthSignResultData{ + ChildNodes: []*BismuthSignResultData{}, + IsTrusting: false, + } + + signResults := BismuthSignResults{ + OverallTrustScore: 0, + ServerPublicKey: serverPublicKey, + Node: rootNode, + } + + totalServerCount, passedServerCount := 0, 0 + + if bismuth.CheckIfCertificatesAreSigned { + serverKeyFingerprint := serverPublicKey.GetFingerprint() + isCertSigned := bismuth.CertificateSignChecker(host, serverKeyFingerprint, isServerSelfSigned) + + if !isServerSelfSigned || !isCertSigned { + domainTrustResults, err := bismuth.checkIfDomainIsTrusted(signingServers, trustedDomains) + + if err == nil { + rootNode.ChildNodes = domainTrustResults + } else { + fmt.Printf("ERROR: failed to verify servers (%s).\n", err.Error()) + signResults.OverallTrustScore = 0 + } + + totalServerCount, passedServerCount = computeNodes(rootNode.ChildNodes) + } else if isCertSigned { + rootNode.IsTrusting = isCertSigned + + totalServerCount, passedServerCount = 1, 1 + rootNode.IsTrusting = true + } + } else { + totalServerCount, passedServerCount = 1, 1 + } + + if totalServerCount != 0 { + signResults.OverallTrustScore = int((float32(passedServerCount) / float32(totalServerCount)) * 100) + } + // After that, we send what host we are connecting to (enables fronting/proxy services) hostInformation := make([]byte, 1+len(host)) @@ -179,62 +372,6 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults conn.Write(hostInformationSize) conn.Write(encryptedHostInformationPacket) - // Request trusted proxies - - trustedProxyRequest := make([]byte, 1) - trustedProxyRequest[0] = core.GetSigningServers - - encryptedTrustedProxyRequest, err := bismuth.encryptMessage(aead, trustedProxyRequest) - - if err != nil { - conn.Close() - return nil, nil, err - } - - trustedProxyLength := make([]byte, 3) - core.Int32ToInt24(trustedProxyLength, uint32(len(encryptedTrustedProxyRequest))) - - conn.Write(trustedProxyLength) - conn.Write(encryptedTrustedProxyRequest) - - if _, err = conn.Read(trustedProxyLength); err != nil { - conn.Close() - return nil, nil, err - } - - encryptedTrustedProxyResponse := make([]byte, core.Int24ToInt32(trustedProxyLength)) - - if _, err = conn.Read(encryptedTrustedProxyResponse); err != nil { - conn.Close() - return nil, nil, err - } - - trustedProxyResponse, err := bismuth.decryptMessage(aead, encryptedTrustedProxyResponse) - - if err != nil { - conn.Close() - return nil, nil, err - } - - if trustedProxyResponse[0] != core.GetSigningServers { - conn.Close() - return nil, nil, fmt.Errorf("server failed to return its signing servers") - } - - signingServers := strings.Split(string(trustedProxyResponse[1:]), "\n") - isServerSelfSigned := len(trustedProxyResponse)-1 == 0 - - if !isServerSelfSigned { - fmt.Printf("acquired signing servers: '%s'\n", strings.Join(signingServers, ", ")) - } else { - fmt.Println("server is self signed, not printing (non-existent) signing servers") - } - - signResults := BismuthSignResults{ - OverallTrustScore: 100, - ServerPublicKey: serverPublicKey, - } - // Start proxying startForwardingPacket := []byte{ diff --git a/client/typing.go b/client/typing.go index ad9a532..873a94f 100644 --- a/client/typing.go +++ b/client/typing.go @@ -27,13 +27,12 @@ type BismuthClient struct { // - `certificateFingerprint`: A fingerprint of the servers key. // - `isSelfSigned`: If true, the certificate is either actually self-signed, or // verification is dsabled (CheckIfCertificatesAreSigned in BismuthClient is false) - // - `isTrustworthy`: If true, the certificate is signed by 51% or more of peers. // // This function will only be called if client.CheckIfCertificatesAreSigned is true. // // Example usage inside the Bismuth client source: // client.CertificateSignChecker("example.com:9090", "6c5eaff6f5c65e65e6f6ce6fc", false, true) - CertificateSignChecker func(host, certificateFingerprint string, isSelfSigned, isTrustworthy bool) bool + CertificateSignChecker func(host, certificateFingerprint string, isSelfSigned bool) bool // If any certificates are false in the certificate cache, and the client has determined that // they may need to be added, this function will get called. @@ -49,7 +48,8 @@ type BismuthClient struct { // Connects to a server. // This function will only be called if client.CheckIfCertificatesAreSigned is true. // - // client.ConnectToServer("google.com:80") + // Example usage in the client source: + // client.ConnectToServer("google.com:80") ConnectToServer func(address string) (net.Conn, error) // GopenPGP instance @@ -62,9 +62,7 @@ type BismuthSignResultData struct { ChildNodes []*BismuthSignResultData // If true, the server is already trusting this node - IsTrustingAlready bool - // If true, server is trusting the previous server - IsTrustingRootServer bool + IsTrusting bool } type BismuthSignResults struct { @@ -79,13 +77,17 @@ type BismuthSignResults struct { type BismuthCertificates struct { // The host of the server - host string + Host string // A fingerprint of the servers key - certificateFingerprint string + CertificateFingerprint string // Certificate UserID - certificateUsername string - certificateMail string + CertificateUsername string + CertificateMail string // If true, the certificate is self signed - isSelfSigned bool + IsSelfSigned bool + + // If true, the client should not prompt the user, and automatically + // add the certificate instead. + ShouldAutomaticallyAdd bool } diff --git a/main.go b/main.go index e2e0f18..373320d 100644 --- a/main.go +++ b/main.go @@ -118,7 +118,16 @@ func clientEntrypoint(cCtx *cli.Context) error { } func serverEntrypoint(cCtx *cli.Context) error { - signingServers := []string{} + var domainList []string + var signServers []string + + if cCtx.String("domain-names") != "" { + domainList = strings.Split(cCtx.String("domain-names"), ":") + } + + if cCtx.String("signing-servers") != "" { + signServers = strings.Split(cCtx.String("signing-servers"), ";") + } pubKeyFile, err := os.ReadFile(cCtx.String("pubkey")) @@ -137,7 +146,7 @@ func serverEntrypoint(cCtx *cli.Context) error { network := fmt.Sprintf("%s:%s", cCtx.String("source-ip"), cCtx.String("source-port")) - bismuth, err := server.NewBismuthServer(pubKey, privKey, signingServers, core.XChaCha20Poly1305) + bismuth, err := server.New(pubKey, privKey, signServers, domainList, core.XChaCha20Poly1305) bismuth.HandleConnection = func(connBismuth net.Conn, _ *server.ClientMetadata) error { connDialed, err := net.Dial("tcp", network) @@ -231,7 +240,16 @@ func signingServerEntrypoint(cCtx *cli.Context) error { log.Warn("Using the built-in bismuth signing server in production is a horrible idea as it has no validation!") log.Warn("Consider writing using a custom solution that's based on the signing server code, rather than the default implementation.") - signServers := []string{} + var domainList []string + var signServers []string + + if cCtx.String("domain-names") != "" { + domainList = strings.Split(cCtx.String("domain-names"), ":") + } + + if cCtx.String("signing-servers") != "" { + signServers = strings.Split(cCtx.String("signing-servers"), ";") + } pubKeyFile, err := os.ReadFile(cCtx.String("pubkey")) @@ -254,7 +272,7 @@ func signingServerEntrypoint(cCtx *cli.Context) error { return err } - bismuthServer, err := server.NewBismuthServer(pubKey, privKey, signServers, core.XChaCha20Poly1305) + bismuthServer, err := server.New(pubKey, privKey, signServers, domainList, core.XChaCha20Poly1305) if err != nil { return nil @@ -328,7 +346,7 @@ func verifyCert(cCtx *cli.Context) error { } if certResults.OverallTrustScore < 50 { - return fmt.Errorf("overall trust score is below 50% for certificate") + return fmt.Errorf("overall trust score is below 50 percent for certificate") } fmt.Println("Sending signing request to sign server...") @@ -387,7 +405,7 @@ func signCert(cCtx *cli.Context) error { } if certResults.OverallTrustScore < 50 { - return fmt.Errorf("overall trust score is below 50% for certificate") + return fmt.Errorf("overall trust score is below 50 percent for certificate") } isTrusted, err := signingclient.IsDomainTrusted(conn, keyFingerprint, domainList) @@ -492,7 +510,7 @@ func main() { }, &cli.StringFlag{ Name: "signing-servers", - Usage: "servers trusting/\"signing\" the public key. seperated using colons", + Usage: "servers trusting/\"signing\" the public key. seperated using semicolons", }, &cli.StringFlag{ Name: "domain-names", @@ -537,11 +555,11 @@ func main() { Value: "9090", }, &cli.StringFlag{ - Name: "domain-names", - Usage: "domain names the key is authorized to use. seperated using colons", + Name: "signing-servers", + Usage: "servers trusting/\"signing\" the public key. seperated using semicolons", }, &cli.StringFlag{ - Name: "signing-server", + Name: "domain-names", Usage: "domain names the key is authorized to use. seperated using colons", }, }, diff --git a/server/server.go b/server/server.go index c6f6c7f..e9d61bd 100644 --- a/server/server.go +++ b/server/server.go @@ -1,6 +1,7 @@ package server import ( + "fmt" "net" core "git.greysoh.dev/imterah/bismuthd/commons" @@ -191,6 +192,7 @@ func (bismuth BismuthServer) HandleProxy(conn net.Conn) error { totalPacketContents[0] = core.GetTrustedDomains for index, trustedDomain := range bismuth.TrustedDomains { + fmt.Println("building trusted domains") totalPacketContents = append(totalPacketContents, []byte(trustedDomain)...) if index+1 != len(bismuth.TrustedDomains) { diff --git a/server/utils.go b/server/utils.go index f57c280..aff3186 100644 --- a/server/utils.go +++ b/server/utils.go @@ -40,7 +40,7 @@ func (bismuth BismuthServer) decryptMessage(aead cipher.AEAD, encMsg []byte) ([] // Initializes a Bismuth server. // // Both `pubKey` and `privKey` are armored PGP public and private keys respectively. -func NewBismuthServer(pubKey string, privKey string, signServers []string, encryptionAlgo int) (*BismuthServer, error) { +func New(pubKey string, privKey string, signServers []string, trustedDomains []string, encryptionAlgo int) (*BismuthServer, error) { publicKey, err := crypto.NewKeyFromArmored(pubKey) if err != nil { @@ -59,6 +59,7 @@ func NewBismuthServer(pubKey string, privKey string, signServers []string, encry PublicKey: publicKey, PrivateKey: privateKey, SigningServers: signServers, + TrustedDomains: trustedDomains, SymmetricEncryptionAlgorithm: encryptionAlgo, pgp: pgp, } diff --git a/tests/integration_test.go b/tests/integration_test.go index aa657fe..bc2e9da 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -53,7 +53,7 @@ func TestProtocolTxRx(t *testing.T) { t.Fatalf("failed to listen on TCP for localhost (%s)", err.Error()) } - bismuth, err := server.NewBismuthServer(pubKeyServ, privKeyServ, []string{}, commons.XChaCha20Poly1305) + bismuth, err := server.New(pubKeyServ, privKeyServ, []string{}, []string{}, commons.XChaCha20Poly1305) bismuth.HandleConnection = func(conn net.Conn, _ *server.ClientMetadata) error { for entryCount, randomDataSlice := range randomDataSlices { @@ -89,6 +89,8 @@ func TestProtocolTxRx(t *testing.T) { t.Fatalf("failed to initialize bismuthClient (%s)", err.Error()) } + bismuthClient.CheckIfCertificatesAreSigned = false + originalConn, err := net.Dial("tcp", "127.0.0.1:"+strconv.Itoa(port)) if err != nil { From e062975a0b65fefb03b611f4c2fd23f5c0548a09 Mon Sep 17 00:00:00 2001 From: imterah Date: Fri, 25 Oct 2024 12:01:02 -0400 Subject: [PATCH 6/6] fix: Fixes potential crashing issues. --- client/client.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/client.go b/client/client.go index 824b6f9..bdb7cf6 100644 --- a/client/client.go +++ b/client/client.go @@ -331,12 +331,11 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults if err == nil { rootNode.ChildNodes = domainTrustResults + totalServerCount, passedServerCount = computeNodes(rootNode.ChildNodes) } else { fmt.Printf("ERROR: failed to verify servers (%s).\n", err.Error()) signResults.OverallTrustScore = 0 } - - totalServerCount, passedServerCount = computeNodes(rootNode.ChildNodes) } else if isCertSigned { rootNode.IsTrusting = isCertSigned