feature: Adds signing validation support to the client.
This commit is contained in:
parent
86ad3e174f
commit
00a57443d4
6 changed files with 242 additions and 80 deletions
251
client/client.go
251
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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
38
main.go
38
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",
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue