+
diff --git a/cmd/dashboard/ui/static/css/clients.css b/cmd/dashboard/ui/static/css/clients.css
index 56e8deb..1b41baf 100644
--- a/cmd/dashboard/ui/static/css/clients.css
+++ b/cmd/dashboard/ui/static/css/clients.css
@@ -51,4 +51,8 @@
.fleet-device {
margin-bottom: 50px;
+}
+
+#service-name {
+ font-size: large;
}
\ No newline at end of file
diff --git a/cmd/drawbridge/api.go b/cmd/drawbridge/api.go
index 09fede3..bcc2596 100644
--- a/cmd/drawbridge/api.go
+++ b/cmd/drawbridge/api.go
@@ -124,7 +124,7 @@ func (d *Drawbridge) SetUpCAAndDependentServices(protectedServices []services.Pr
// An Emissary TCP Mutual TLS Key is used to allow the Emissary Client to connect to Drawbridge directly.
// The user will connect to the local proxy server the Emissary Client creates and all traffic will then flow
// through Drawbridge.
-func (d *Drawbridge) CreateEmissaryClientTCPMutualTLSKey(clientId string, overrideDirectory ...string) (*string, error) {
+func (d *Drawbridge) CreateEmissaryClientTCPMutualTLSKey(clientId, platform string, overrideDirectory ...string) (*string, error) {
var directoryToSave string
if len(overrideDirectory) == 0 {
directoryToSave = "./emissary_certs_and_key_here"
@@ -189,16 +189,35 @@ func (d *Drawbridge) CreateEmissaryClientTCPMutualTLSKey(clientId string, overri
return nil, err
}
- certPrivKeyPEMBytes, err := x509.MarshalECPrivateKey(clientCertPrivKey)
- if err != nil {
- return nil, err
+ // Android is a special little platform. The Kotlin/Java stdlib seems to only have support for the
+ // PKCS8 format. We generate a key in this format for Android to avoid complicated conversion code
+ // on the Android client.
+ var certPrivKeyPEMBytes []byte
+ var certPrivKeyPEM *bytes.Buffer
+ if platform == "android" {
+ certPrivKeyPEMBytes, err = x509.MarshalPKCS8PrivateKey(clientCertPrivKey)
+ if err != nil {
+ return nil, err
+ }
+ certPrivKeyPEM = new(bytes.Buffer)
+ pem.Encode(certPrivKeyPEM, &pem.Block{
+ Type: "PRIVATE KEY",
+ Bytes: certPrivKeyPEMBytes,
+ })
+ // For non-Android platforms, use the EC Private Key format.
+ } else {
+ certPrivKeyPEMBytes, err = x509.MarshalECPrivateKey(clientCertPrivKey)
+ if err != nil {
+ return nil, err
+ }
+ certPrivKeyPEM = new(bytes.Buffer)
+ pem.Encode(certPrivKeyPEM, &pem.Block{
+ Type: "EC PRIVATE KEY",
+ Bytes: certPrivKeyPEMBytes,
+ })
+
}
- certPrivKeyPEM := new(bytes.Buffer)
- pem.Encode(certPrivKeyPEM, &pem.Block{
- Type: "EC PRIVATE KEY",
- Bytes: certPrivKeyPEMBytes,
- })
// Save the file to disk for use by an Emissary client. This should be later used and saved in the db for downloading later.
err = utils.SaveFile("emissary-mtls-tcp.key", certPrivKeyPEM.String(), directoryToSave)
if err != nil {
@@ -357,7 +376,7 @@ func (d *Drawbridge) SetUpProtectedServiceTunnel() error {
slog.Error("Failed to tcp dial to actual target service", err)
}
- slog.Debug(fmt.Sprintf("TCP Accept from Emissary client: %s\n", clientConn.RemoteAddr()))
+ slog.Debug(fmt.Sprintf("TCP Accept from Emissary client: %s", clientConn.RemoteAddr()))
// Copy data back and from client and server.
go io.Copy(resourceConn, clientConn)
io.Copy(clientConn, resourceConn)
@@ -371,7 +390,10 @@ func (d *Drawbridge) SetUpProtectedServiceTunnel() error {
// We pad the service id with zeros as we want a fixed-width id for easy parsing. This will allow support for up to 1000 Protected Services.
serviceList += fmt.Sprintf("%s%s,", utils.PadWithZeros(int(value.Service.ID)), value.Service.Name)
}
- serviceConnectCommand := fmt.Sprintf("PS_LIST: %s", serviceList)
+ // The newline character is important for other platforms, such as Android,
+ // to properly read the string from the socket without blocking.
+ serviceConnectCommand := fmt.Sprintf("PS_LIST: %s\n", serviceList)
+ slog.Debug(fmt.Sprintf("PS_LIST values: %s\n", serviceConnectCommand))
clientConn.Write([]byte(serviceConnectCommand))
default:
}
@@ -428,10 +450,15 @@ type BundleFile struct {
// To accomplish this, we pull the latest version of Emissary from GitHub Releases, verify it is signed with the
// Drawbridge & Emissary signing key, generate the mTLS key(s) and cert, zip it all up, and allow the Drawbridge admin to download it.
func (d *Drawbridge) GenerateEmissaryBundle(config EmissaryConfig) (*BundleFile, error) {
- if config.Platform != "macos" && config.Platform != "linux" && config.Platform != "windows" {
+ if config.Platform != "macos" && config.Platform != "linux" && config.Platform != "windows" && config.Platform != "android" {
return nil, fmt.Errorf("platform %s is not supported", config.Platform)
}
+ if config.Platform == "android" || config.Platform == "ios" {
+ slog.Debug("Making mobile platform Emissary Bundle")
+ return d.generateMobileEmissaryBundle(config.Platform)
+ }
+
// Get assets url
releaseResp, err := http.Get("https://api.github.com/repos/dhens/Emissary-Daemon/releases/latest")
if err != nil {
@@ -571,7 +598,7 @@ func (d *Drawbridge) GenerateEmissaryBundle(config EmissaryConfig) (*BundleFile,
return nil, fmt.Errorf("error generating uuid: %w", err)
}
certsAndKeysFolderPath := "./bundle_tmp/put_certificates_and_key_from_drawbridge_here"
- emissaryCert, err := d.CreateEmissaryClientTCPMutualTLSKey(clientId, certsAndKeysFolderPath)
+ emissaryCert, err := d.CreateEmissaryClientTCPMutualTLSKey(clientId, config.Platform, certsAndKeysFolderPath)
if err != nil {
return nil, err
}
@@ -621,9 +648,9 @@ func (d *Drawbridge) GenerateEmissaryBundle(config EmissaryConfig) (*BundleFile,
}
func (d *Drawbridge) createEmissaryDevice(id, certificate string) error {
- intOne := utils.RandInt(0, len(Adjectives))
- intTwo := utils.RandInt(0, len(Animals))
- deviceName := fmt.Sprintf("%s %s", Adjectives[intOne], Animals[intTwo])
+ adjectivesIndex := utils.RandInt(0, len(Adjectives))
+ animalsIndex := utils.RandInt(0, len(Animals))
+ deviceName := fmt.Sprintf("%s %s", Adjectives[adjectivesIndex], Animals[animalsIndex])
client := emissary.EmissaryClient{
ID: id,
@@ -637,3 +664,66 @@ func (d *Drawbridge) createEmissaryDevice(id, certificate string) error {
}
return nil
}
+
+// Generate an Emissary Bundle for a mobile device.
+// We can't fling .apk or .ipa files at mobile users, so we instead just ship the bundle with our certs, keypair, and drawbridge address.
+func (d *Drawbridge) generateMobileEmissaryBundle(platform string) (*BundleFile, error) {
+ bundleTmpFolderPath := "./bundle_tmp"
+ // Create temporary directory used for placing Emissary files to zip up for use as the downloadable Emissary Bundle.
+ os.Mkdir(utils.CreateDrawbridgeFilePath(bundleTmpFolderPath), os.ModePerm)
+
+ // Generate and save the mTLS key(s) and cert
+ clientId, err := utils.NewUUID()
+ if err != nil {
+ return nil, fmt.Errorf("error generating uuid: %w", err)
+ }
+ certsAndKeysFolderPath := "./bundle_tmp/put_certificates_and_key_from_drawbridge_here"
+ emissaryCert, err := d.CreateEmissaryClientTCPMutualTLSKey(clientId, platform, certsAndKeysFolderPath)
+ if err != nil {
+ return nil, err
+ }
+ // Copy ca.crt next to keys
+ err = utils.CopyFile("./ca/ca.crt", certsAndKeysFolderPath)
+ if err != nil {
+ slog.Error("Emissary Bundle Creation", slog.Any("Error", fmt.Errorf("unable to copy the Drawbridge ca.crt file to the Emissary Bundle put_certificates_... folder: %s", err)))
+ return nil, err
+ }
+ // Generate and save bundle using Drawbridge listening address
+ listeningAddress, err := d.DB.GetDrawbridgeConfigValueByName("listening_address")
+ if err != nil {
+ return nil, err
+ }
+ if len(*listeningAddress) > 0 {
+ // TODO
+ // Change the port hardcoding and write the listening port in the lsiteningAddress config file instead.
+ utils.SaveFile("drawbridge.txt", fmt.Sprintf("%s:3100", *listeningAddress), "./bundle_tmp/bundle")
+ } else {
+ slog.Error("Emissary Bundle Creation", slog.String("Error", "Unable to get Drawbridge listening address. Unable to finish creating bundle."))
+ return nil, fmt.Errorf("error getting Drawbridge listening address")
+ }
+ // Zip up Emissary directory to bundles output folder.
+ bundledFilename := fmt.Sprintf("./android_bundle_%s", clientId)
+ // TODO
+ // return the file contents rather than writing to disk by default.
+ // there are tons of situations where we'd prefer to just hand off the bytes to the Drawbridge admin in the
+ // form of a file.
+ utils.ZipSource(bundleTmpFolderPath, bundledFilename)
+
+ // Serve to Drawbridge admin
+ slog.Debug("reading bundled emissary output file to send back to admin...")
+ bundledEmissaryZipFile := utils.ReadFile(bundledFilename)
+ // Remove temp folders
+ defer os.RemoveAll("./bundle_tmp")
+ defer os.RemoveAll("./emissary_download_scratch")
+ bundleFile := BundleFile{
+ Contents: bundledEmissaryZipFile,
+ Name: bundledFilename,
+ }
+
+ err = d.createEmissaryDevice(clientId, *emissaryCert)
+ if err != nil {
+ return nil, err
+ }
+ return &bundleFile, nil
+
+}
diff --git a/cmd/drawbridge/persistence/emissary_client.go b/cmd/drawbridge/persistence/emissary_client.go
index 6a3f17a..f72bb54 100644
--- a/cmd/drawbridge/persistence/emissary_client.go
+++ b/cmd/drawbridge/persistence/emissary_client.go
@@ -63,7 +63,7 @@ func (r *SQLiteRepository) GetAllEmissaryClients() ([]*emissary.EmissaryClient,
}
func (r *SQLiteRepository) GetAllEmissaryClientCertificates() (map[string]emissary.DeviceCertificate, error) {
- rows, err := r.db.Query("SELECT drawbridge_certificate AND id AND revoked FROM emissary_client WHERE revoked = 1")
+ rows, err := r.db.Query("SELECT drawbridge_certificate, id, revoked FROM emissary_client")
if err != nil {
return nil, fmt.Errorf("error getting all emissary clients: %s", err)
}
diff --git a/cmd/reverse_proxy/ca/ca.go b/cmd/reverse_proxy/ca/ca.go
index 0aad160..acca76b 100644
--- a/cmd/reverse_proxy/ca/ca.go
+++ b/cmd/reverse_proxy/ca/ca.go
@@ -15,7 +15,6 @@ import (
"dhens/drawbridge/cmd/utils"
"encoding/hex"
"encoding/pem"
- "errors"
"fmt"
"log"
"log/slog"
@@ -93,6 +92,14 @@ func (c *CA) SetupCertificates() error {
MinVersion: tls.VersionTLS13,
}
+ // Populate the certificate authority's list of emissary certificates.
+ // Is used to lookup emissary client certs for revocation status to allow deny access to Drawbridge.
+ emissaryClientCertificates, err := c.DB.GetAllEmissaryClientCertificates()
+ if err != nil {
+ return err
+ }
+ c.CertificateList = emissaryClientCertificates
+
// Terminate function early as we have all of the cert and key data we need.
slog.Info("Loaded TLS Certs & Keys")
return nil
@@ -287,25 +294,29 @@ func (c *CA) SetupCertificates() error {
return nil
}
-// THIS FUNCTION NEEDS TO BE FAST TO NOT DELAY HANDSHAKE
-// Run for every Drawbridge + Emissary handshake to verify the presented cert is not revoked.
-func (c *CA) verifyEmissaryCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
- // Parse the peer certificate
- // PEM encode
+// Parse the peer certificate
+func hashEmissaryCertificate(rawCert []byte) string {
caPEM := new(bytes.Buffer)
pem.Encode(caPEM, &pem.Block{
Type: "CERTIFICATE",
- Bytes: rawCerts[0],
+ Bytes: rawCert,
})
// Calculate the SHA-256 hash of the peer certificate
hash := sha256.Sum256(caPEM.Bytes())
hexHash := hex.EncodeToString(hash[:])
+ return hexHash
+}
+// THIS FUNCTION NEEDS TO BE FAST TO NOT DELAY HANDSHAKE
+// Run for every Drawbridge + Emissary handshake to verify the presented cert is not revoked.
+func (c *CA) verifyEmissaryCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
+ // Parse the peer certificate
+ hexHash := hashEmissaryCertificate(rawCerts[0])
// Check if the certificate hash is in the revocation list
if c.CertificateList[hexHash].Revoked == 1 {
slog.Debug("peer cert is REVOKED")
- return errors.New("peer certificate is revoked")
+ return fmt.Errorf("peer certificate is revoked")
}
// Additional certificate verification checks can be added here
@@ -322,7 +333,7 @@ func (c *CA) RevokeCertInCertificateRevocationList(shaCert string) {
defer revokedCertsMutex.Unlock()
cert, ok := c.CertificateList[shaCert]
if !ok {
- slog.Error("Unable to revoke certificate", shaCert)
+ slog.Error("Unable to revoke certificate as it doesn't exist in the certificate list", shaCert)
}
certCopy := cert
certCopy.Revoked = 1