Compare commits
No commits in common. "main" and "v0.1.2b" have entirely different histories.
14 changed files with 175 additions and 1223 deletions
|
@ -1,4 +1,4 @@
|
||||||
# Bismuth Protocol [](https://pkg.go.dev/git.greysoh.dev/imterah/bismuthd)
|
# Bismuth Protocol
|
||||||
|
|
||||||
The Bismuth protocol is a thin wrapper for any protocol that adds TLS-like features, without being TLS on its own.
|
The Bismuth protocol is a thin wrapper for any protocol that adds TLS-like features, without being TLS on its own.
|
||||||
|
|
||||||
|
|
333
client/client.go
333
client/client.go
|
@ -1,128 +1,67 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
|
|
||||||
core "git.greysoh.dev/imterah/bismuthd/commons"
|
core "git.greysoh.dev/imterah/bismuthd/commons"
|
||||||
"git.greysoh.dev/imterah/bismuthd/signingclient"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v3/crypto"
|
"github.com/ProtonMail/gopenpgp/v3/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
func computeNodes(children []*BismuthSignResultData) (int, int) {
|
func (bismuth BismuthClient) encryptMessage(aead cipher.AEAD, msg []byte) ([]byte, error) {
|
||||||
totalServerCount := 0
|
nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead())
|
||||||
passedServerCount := 0
|
|
||||||
|
|
||||||
for _, child := range children {
|
if _, err := rand.Read(nonce); err != nil {
|
||||||
totalServerCount += 1
|
return []byte{}, err
|
||||||
|
|
||||||
if child.IsTrusting {
|
|
||||||
passedServerCount += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(child.ChildNodes) != 0 {
|
|
||||||
recievedTotalCount, recievedPassedCount := computeNodes(child.ChildNodes)
|
|
||||||
|
|
||||||
totalServerCount += recievedTotalCount
|
|
||||||
passedServerCount += recievedPassedCount
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalServerCount, passedServerCount
|
encryptedMsg := aead.Seal(nonce, nonce, msg, nil)
|
||||||
|
return encryptedMsg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes the client. Should be done automatically if you call New()
|
func (bismuth BismuthClient) decryptMessage(aead cipher.AEAD, encMsg []byte) ([]byte, error) {
|
||||||
//
|
if len(encMsg) < aead.NonceSize() {
|
||||||
// If you don't call client.New(), you *MUST* call this function before running bismuth.Conn().
|
return []byte{}, fmt.Errorf("ciphertext too short")
|
||||||
func (bismuth *BismuthClient) InitializeClient() error {
|
|
||||||
if bismuth.pgp == nil {
|
|
||||||
bismuth.pgp = crypto.PGP()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if bismuth.CertificateSignChecker == nil {
|
// Split nonce and ciphertext.
|
||||||
bismuth.CertificateSignChecker = func(host, certificateFingerprint string, isSelfSigned bool) bool {
|
nonce, ciphertext := encMsg[:aead.NonceSize()], encMsg[aead.NonceSize():]
|
||||||
fmt.Println("WARNING: Using stub CertificateSignChecker. Returing true and ignoring arguments")
|
|
||||||
return true
|
// Decrypt the message and check it wasn't tampered with.
|
||||||
}
|
decryptedData, err := aead.Open(nil, nonce, ciphertext, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if bismuth.AddCertificatesToSignCache == nil {
|
return decryptedData, 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bismuth.CheckIfCertificatesAreSigned = true
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bismuth *BismuthClient) checkIfDomainIsTrusted(servers, advertisedDomains []string) ([]*BismuthSignResultData, error) {
|
// Bismuth Client
|
||||||
signResultData := make([]*BismuthSignResultData, len(servers))
|
type BismuthClient struct {
|
||||||
|
// GOpenPGP public key
|
||||||
|
PublicKey *crypto.Key
|
||||||
|
// GOpenPGP private key
|
||||||
|
PrivateKey *crypto.Key
|
||||||
|
|
||||||
for index, server := range servers {
|
pgp *crypto.PGPHandle
|
||||||
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.
|
// Connects to a Bismuth server. This wraps an existing net.Conn interface.
|
||||||
// The returned net.Conn is the server, but over bismuth.
|
// The returned net.Conn is the server, but over bismuth.
|
||||||
func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults, error) {
|
func (bismuth BismuthClient) Conn(conn net.Conn) (net.Conn, error) {
|
||||||
// Yes, I'm aware defer exists. It won't work if I use it in this context. I'll shank anyone that complains
|
// 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
|
// 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()
|
ownKey, err := bismuth.PublicKey.GetPublicKey()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKeyLengthBytes := make([]byte, 3)
|
pubKeyLengthBytes := make([]byte, 3)
|
||||||
|
@ -137,17 +76,17 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults
|
||||||
|
|
||||||
if _, err = conn.Read(messageMode); err != nil {
|
if _, err = conn.Read(messageMode); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if messageMode[0] != core.SendPublicKey {
|
if messageMode[0] != core.SendPublicKey {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, fmt.Errorf("server failed to return its public key")
|
return nil, fmt.Errorf("server failed to return its public key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = conn.Read(pubKeyLengthBytes); err != nil {
|
if _, err = conn.Read(pubKeyLengthBytes); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKeyLength = core.Int24ToInt32(pubKeyLengthBytes)
|
pubKeyLength = core.Int24ToInt32(pubKeyLengthBytes)
|
||||||
|
@ -155,14 +94,12 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults
|
||||||
|
|
||||||
if _, err = conn.Read(pubKeyBytes); err != nil {
|
if _, err = conn.Read(pubKeyBytes); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
serverPublicKey, err := crypto.NewKey(pubKeyBytes)
|
if _, err = crypto.NewKey(pubKeyBytes); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then exchange the symmetric key
|
// Then exchange the symmetric key
|
||||||
|
@ -171,19 +108,19 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults
|
||||||
|
|
||||||
if _, err = conn.Read(messageMode); err != nil {
|
if _, err = conn.Read(messageMode); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if messageMode[0] != core.SwitchToSymmetricKey {
|
if messageMode[0] != core.SwitchToSymmetricKey {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, fmt.Errorf("server failed to return symmetric key")
|
return nil, fmt.Errorf("server failed to return symmetric key")
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedSymmKeyLengthInBytes := make([]byte, 3)
|
encryptedSymmKeyLengthInBytes := make([]byte, 3)
|
||||||
|
|
||||||
if _, err = conn.Read(encryptedSymmKeyLengthInBytes); err != nil {
|
if _, err = conn.Read(encryptedSymmKeyLengthInBytes); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedSymmKeyLength := core.Int24ToInt32(encryptedSymmKeyLengthInBytes)
|
encryptedSymmKeyLength := core.Int24ToInt32(encryptedSymmKeyLengthInBytes)
|
||||||
|
@ -191,186 +128,31 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults
|
||||||
|
|
||||||
if _, err = conn.Read(encryptedSymmKey); err != nil {
|
if _, err = conn.Read(encryptedSymmKey); err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
decHandleForSymmKey, err := bismuth.pgp.Decryption().DecryptionKey(bismuth.PrivateKey).New()
|
decHandleForSymmKey, err := bismuth.pgp.Decryption().DecryptionKey(bismuth.PrivateKey).New()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
return nil, err
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decryptedSymmKey, err := decHandleForSymmKey.Decrypt(encryptedSymmKey, crypto.Bytes)
|
decryptedSymmKey, err := decHandleForSymmKey.Decrypt(encryptedSymmKey, crypto.Bytes)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
return nil, err
|
||||||
return nil, nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
symmKeyInfo := decryptedSymmKey.Bytes()
|
symmKeyInfo := decryptedSymmKey.Bytes()
|
||||||
|
|
||||||
if symmKeyInfo[0] != core.XChaCha20Poly1305 {
|
if symmKeyInfo[0] != core.XChaCha20Poly1305 {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, fmt.Errorf("unsupported encryption method recieved")
|
return nil, fmt.Errorf("unsupported encryption method recieved")
|
||||||
}
|
}
|
||||||
|
|
||||||
symmKey := symmKeyInfo[1 : chacha20poly1305.KeySize+1]
|
symmKey := symmKeyInfo[1 : chacha20poly1305.KeySize+1]
|
||||||
aead, err := chacha20poly1305.NewX(symmKey)
|
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
|
|
||||||
totalServerCount, passedServerCount = computeNodes(rootNode.ChildNodes)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("ERROR: failed to verify servers (%s).\n", err.Error())
|
|
||||||
signResults.OverallTrustScore = 0
|
|
||||||
}
|
|
||||||
} 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))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
// Start proxying
|
// Start proxying
|
||||||
|
|
||||||
startForwardingPacket := []byte{
|
startForwardingPacket := []byte{
|
||||||
|
@ -381,7 +163,7 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedForwardPacketPacketSize := make([]byte, 3)
|
encryptedForwardPacketPacketSize := make([]byte, 3)
|
||||||
|
@ -403,5 +185,32 @@ func (bismuth *BismuthClient) Conn(conn net.Conn) (net.Conn, *BismuthSignResults
|
||||||
|
|
||||||
return core.BismuthConnWrapped{
|
return core.BismuthConnWrapped{
|
||||||
Bismuth: &bmConn,
|
Bismuth: &bmConn,
|
||||||
}, &signResults, nil
|
}, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
pgp := crypto.PGP()
|
||||||
|
|
||||||
|
bismuth := BismuthClient{
|
||||||
|
PublicKey: publicKey,
|
||||||
|
PrivateKey: privateKey,
|
||||||
|
pgp: pgp,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bismuth, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v3/crypto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Bismuth Client
|
|
||||||
type BismuthClient struct {
|
|
||||||
// GOpenPGP public key for the client
|
|
||||||
PublicKey *crypto.Key
|
|
||||||
// GOpenPGP private key for the client
|
|
||||||
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.
|
|
||||||
//
|
|
||||||
// - `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)
|
|
||||||
//
|
|
||||||
// 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 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.
|
|
||||||
//
|
|
||||||
// Example usage in the client source:
|
|
||||||
// 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
|
|
||||||
IsTrusting 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
|
|
||||||
|
|
||||||
// If true, the client should not prompt the user, and automatically
|
|
||||||
// add the certificate instead.
|
|
||||||
ShouldAutomaticallyAdd bool
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -2,41 +2,32 @@
|
||||||
|
|
||||||
package commons
|
package commons
|
||||||
|
|
||||||
// Core Protocol Commands
|
// Commands
|
||||||
const (
|
const (
|
||||||
// Sending public key back and forth
|
// SendPublicKey: Sending public key back and forth
|
||||||
SendPublicKey = iota
|
SendPublicKey = iota
|
||||||
// Sent by the client along with the symmetric key that is going to be used
|
// SwitchToSymmetricKey: Sent by the client along with the symmetric key that is going to be used
|
||||||
SwitchToSymmetricKey
|
SwitchToSymmetricKey
|
||||||
|
// ClientSendHost: Currently unimplemented.
|
||||||
// Client sends what host they are connecting to.
|
// Client sends what host they are connecting to.
|
||||||
ClientSendHost
|
ClientSendHost
|
||||||
|
// GetSigningServers: Currently unimplemented.
|
||||||
// Gets the signing servers trusting/signing the current server.
|
// Gets the signing servers trusting/signing the current server.
|
||||||
GetSigningServers
|
GetSigningServers
|
||||||
|
// GetTrustedDomains: Currently unimplemented.
|
||||||
// Gets the domains that are supported by this certificate (should be cross-checked)
|
// Gets the domains that are supported by this certificate (should be cross-checked)
|
||||||
GetTrustedDomains
|
GetTrustedDomains
|
||||||
// Starts forwarding traffic over this protocol.
|
// InitiateForwarding: Starts forwarding traffic over this protocol.
|
||||||
InitiateForwarding
|
InitiateForwarding
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validation API Commands
|
// Encryption algorithms
|
||||||
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 (
|
const (
|
||||||
// Default and only encryption algorithm
|
// Default and only encryption algorithm
|
||||||
XChaCha20Poly1305 = iota
|
XChaCha20Poly1305 = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// Unsigned Integer Limits
|
// Unsigned integer limits
|
||||||
const (
|
const (
|
||||||
BitLimit24 = 16_777_215
|
BitLimit24 = 16_777_215
|
||||||
BitLimit16 = 65535
|
BitLimit16 = 65535
|
||||||
|
|
330
main.go
330
main.go
|
@ -3,7 +3,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
@ -12,8 +11,6 @@ import (
|
||||||
"git.greysoh.dev/imterah/bismuthd/client"
|
"git.greysoh.dev/imterah/bismuthd/client"
|
||||||
core "git.greysoh.dev/imterah/bismuthd/commons"
|
core "git.greysoh.dev/imterah/bismuthd/commons"
|
||||||
"git.greysoh.dev/imterah/bismuthd/server"
|
"git.greysoh.dev/imterah/bismuthd/server"
|
||||||
"git.greysoh.dev/imterah/bismuthd/signingclient"
|
|
||||||
"git.greysoh.dev/imterah/bismuthd/signingserver"
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"tailscale.com/net/socks5"
|
"tailscale.com/net/socks5"
|
||||||
|
@ -22,7 +19,7 @@ import (
|
||||||
//go:embed ascii.txt
|
//go:embed ascii.txt
|
||||||
var asciiArt string
|
var asciiArt string
|
||||||
|
|
||||||
func clientEntrypoint(cCtx *cli.Context) error {
|
func bismuthClientEntrypoint(cCtx *cli.Context) error {
|
||||||
pubKeyFile, err := os.ReadFile(cCtx.String("pubkey"))
|
pubKeyFile, err := os.ReadFile(cCtx.String("pubkey"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -40,8 +37,6 @@ func clientEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
bismuth, err := client.New(pubKey, privKey)
|
bismuth, err := client.New(pubKey, privKey)
|
||||||
|
|
||||||
log.Debugf("My key fingerprint is: %s", bismuth.PublicKey.GetFingerprint())
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -87,15 +82,13 @@ func clientEntrypoint(cCtx *cli.Context) error {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, returnData, err := bismuth.Conn(conn)
|
conn, err = bismuth.Conn(conn)
|
||||||
|
|
||||||
if err != nil && err.Error() != "EOF" {
|
if err != nil && err.Error() != "EOF" {
|
||||||
log.Errorf("failed to initialize bismuth connection to '%s:%s': '%s'", ip, port, err.Error())
|
log.Errorf("failed to initialize bismuth connection to '%s:%s': '%s'", ip, port, err.Error())
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Server key fingerprint for '%s' is: %s", addr, returnData.ServerPublicKey.GetFingerprint())
|
|
||||||
|
|
||||||
return conn, err
|
return conn, err
|
||||||
} else {
|
} else {
|
||||||
conn, err := net.Dial(network, addr)
|
conn, err := net.Dial(network, addr)
|
||||||
|
@ -117,17 +110,8 @@ func clientEntrypoint(cCtx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverEntrypoint(cCtx *cli.Context) error {
|
func bismuthServerEntrypoint(cCtx *cli.Context) error {
|
||||||
var domainList []string
|
relayServers := []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"))
|
pubKeyFile, err := os.ReadFile(cCtx.String("pubkey"))
|
||||||
|
|
||||||
|
@ -146,8 +130,7 @@ func serverEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
network := fmt.Sprintf("%s:%s", cCtx.String("source-ip"), cCtx.String("source-port"))
|
network := fmt.Sprintf("%s:%s", cCtx.String("source-ip"), cCtx.String("source-port"))
|
||||||
|
|
||||||
bismuth, err := server.New(pubKey, privKey, signServers, domainList, core.XChaCha20Poly1305)
|
bismuth, err := server.NewBismuthServer(pubKey, privKey, relayServers, core.XChaCha20Poly1305, func(connBismuth net.Conn) error {
|
||||||
bismuth.HandleConnection = func(connBismuth net.Conn, _ *server.ClientMetadata) error {
|
|
||||||
connDialed, err := net.Dial("tcp", network)
|
connDialed, err := net.Dial("tcp", network)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -200,7 +183,7 @@ func serverEntrypoint(cCtx *cli.Context) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -218,206 +201,23 @@ func serverEntrypoint(cCtx *cli.Context) error {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
conn, err := listener.Accept()
|
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())
|
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.")
|
|
||||||
|
|
||||||
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"))
|
|
||||||
|
|
||||||
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.New(pubKey, privKey, signServers, domainList, 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 {
|
if err != nil {
|
||||||
log.Warn(err.Error())
|
log.Warn(err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Recieved connection from '%s'", conn.RemoteAddr().String())
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err = bismuthServer.HandleProxy(conn)
|
err := bismuth.HandleProxy(conn)
|
||||||
|
|
||||||
if err != nil && err.Error() != "EOF" {
|
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 percent 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 percent for certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
isTrusted, err := signingclient.IsDomainTrusted(conn, keyFingerprint, domainList)
|
|
||||||
fmt.Printf("Certificate trust status: %t\n", isTrusted)
|
|
||||||
|
|
||||||
if !isTrusted {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println(asciiArt)
|
fmt.Println(asciiArt)
|
||||||
fmt.Print("Implementation of the Bismuth protocol\n\n")
|
fmt.Print("Implementation of the Bismuth protocol\n\n")
|
||||||
|
@ -482,7 +282,7 @@ func main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Usage: "client for the Bismuth protocol",
|
Usage: "client for the Bismuth protocol",
|
||||||
Action: clientEntrypoint,
|
Action: bismuthClientEntrypoint,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "server",
|
Name: "server",
|
||||||
|
@ -508,14 +308,6 @@ func main() {
|
||||||
Usage: "port to connect to",
|
Usage: "port to connect to",
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "signing-servers",
|
|
||||||
Usage: "servers trusting/\"signing\" the public key. seperated using semicolons",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "domain-names",
|
|
||||||
Usage: "domain names the key is authorized to use. seperated using colons",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "dest-ip",
|
Name: "dest-ip",
|
||||||
Usage: "IP to listen on",
|
Usage: "IP to listen on",
|
||||||
|
@ -528,109 +320,7 @@ func main() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Usage: "server for the Bismuth protocol",
|
Usage: "server for the Bismuth protocol",
|
||||||
Action: serverEntrypoint,
|
Action: bismuthServerEntrypoint,
|
||||||
},
|
|
||||||
{
|
|
||||||
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: "signing-servers",
|
|
||||||
Usage: "servers trusting/\"signing\" the public key. seperated using semicolons",
|
|
||||||
},
|
|
||||||
&cli.StringFlag{
|
|
||||||
Name: "domain-names",
|
|
||||||
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",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
134
server/server.go
134
server/server.go
|
@ -1,6 +1,7 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
core "git.greysoh.dev/imterah/bismuthd/commons"
|
core "git.greysoh.dev/imterah/bismuthd/commons"
|
||||||
|
@ -12,6 +13,54 @@ import (
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"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
|
// 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 {
|
func (bismuth BismuthServer) HandleProxy(conn net.Conn) error {
|
||||||
serverState := "keyHandshake"
|
serverState := "keyHandshake"
|
||||||
|
@ -162,54 +211,7 @@ func (bismuth BismuthServer) HandleProxy(conn net.Conn) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch packet[0] {
|
if packet[0] == core.InitiateForwarding {
|
||||||
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{
|
bmConn := core.BismuthConn{
|
||||||
Aead: aead,
|
Aead: aead,
|
||||||
PassedConn: conn,
|
PassedConn: conn,
|
||||||
|
@ -218,16 +220,42 @@ func (bismuth BismuthServer) HandleProxy(conn net.Conn) error {
|
||||||
|
|
||||||
bmConn.DoInitSteps()
|
bmConn.DoInitSteps()
|
||||||
|
|
||||||
metadata := ClientMetadata{
|
|
||||||
ClientPublicKey: clientPublicKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := bismuth.HandleConnection(core.BismuthConnWrapped{
|
err := bismuth.HandleConnection(core.BismuthConnWrapped{
|
||||||
Bismuth: &bmConn,
|
Bismuth: &bmConn,
|
||||||
}, &metadata)
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
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
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
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, 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
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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 New(pubKey string, privKey string, signServers []string, trustedDomains []string, encryptionAlgo int) (*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,
|
|
||||||
SigningServers: signServers,
|
|
||||||
TrustedDomains: trustedDomains,
|
|
||||||
SymmetricEncryptionAlgorithm: encryptionAlgo,
|
|
||||||
pgp: pgp,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bismuth, nil
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,198 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
||||||
if _, err := conn.Read(keyFingerprintLength); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
keyFingerprintBytes := make([]byte, binary.BigEndian.Uint16(keyFingerprintLength))
|
|
||||||
|
|
||||||
if _, err := conn.Read(keyFingerprintBytes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
keyFingerprint := hex.EncodeToString(keyFingerprintBytes)
|
|
||||||
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")
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -19,7 +19,6 @@ var testProtocolTxRxBufCount = 32
|
||||||
// Tests protocol transmitting and receiving
|
// 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.
|
// 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) {
|
func TestProtocolTxRx(t *testing.T) {
|
||||||
t.Log("running tests")
|
|
||||||
pubKeyCli, privKeyCli, err := CreateKeyring("alice", "alice@contoso.com")
|
pubKeyCli, privKeyCli, err := CreateKeyring("alice", "alice@contoso.com")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -53,9 +52,7 @@ func TestProtocolTxRx(t *testing.T) {
|
||||||
t.Fatalf("failed to listen on TCP for localhost (%s)", err.Error())
|
t.Fatalf("failed to listen on TCP for localhost (%s)", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
bismuth, err := server.New(pubKeyServ, privKeyServ, []string{}, []string{}, commons.XChaCha20Poly1305)
|
bismuth, err := server.NewBismuthServer(pubKeyServ, privKeyServ, []string{}, commons.XChaCha20Poly1305, func(conn net.Conn) error {
|
||||||
|
|
||||||
bismuth.HandleConnection = func(conn net.Conn, _ *server.ClientMetadata) error {
|
|
||||||
for entryCount, randomDataSlice := range randomDataSlices {
|
for entryCount, randomDataSlice := range randomDataSlices {
|
||||||
_, err = conn.Write(randomDataSlice)
|
_, err = conn.Write(randomDataSlice)
|
||||||
|
|
||||||
|
@ -65,7 +62,7 @@ func TestProtocolTxRx(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
})
|
||||||
|
|
||||||
// TODO: fix these warnings?
|
// TODO: fix these warnings?
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -89,15 +86,13 @@ func TestProtocolTxRx(t *testing.T) {
|
||||||
t.Fatalf("failed to initialize bismuthClient (%s)", err.Error())
|
t.Fatalf("failed to initialize bismuthClient (%s)", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
bismuthClient.CheckIfCertificatesAreSigned = false
|
|
||||||
|
|
||||||
originalConn, err := net.Dial("tcp", "127.0.0.1:"+strconv.Itoa(port))
|
originalConn, err := net.Dial("tcp", "127.0.0.1:"+strconv.Itoa(port))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to connect to bismuth server (%s)", err.Error())
|
t.Fatalf("failed to connect to bismuth server (%s)", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, _, err := bismuthClient.Conn(originalConn)
|
conn, err := bismuthClient.Conn(originalConn)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("bismuth client failed to handshake when connecting to server (%s)", err.Error())
|
t.Fatalf("bismuth client failed to handshake when connecting to server (%s)", err.Error())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue