Wiederverwendung öffentlicher Schlüssel zum Signieren von Debug- und Produktionscode

Beschreibung

Die Wiederverwendung öffentlicher Schlüssel zum Signieren von Debug- und Produktionscode tritt auf, wenn ein einzelner öffentlicher Schlüssel zur Authentifizierung sowohl von Debug- als auch von Produktions-Firmware verwendet wird, was eine kritische Sicherheitsschwachstelle darstellt. Public-Key-Kryptographie verifiziert die Firmware-Integrität, indem ein Hash mit dem öffentlichen Schlüssel entschlüsselt und mit dem berechneten Firmware-Hash verglichen wird. Während der Entwicklung enthält Debug-Firmware umfangreiche Debug-Hooks, -Modi und -Nachrichten zum Testen. Wenn Debug-Firmware-Images durchsickern -- und das geschieht häufig -- können Angreifer sie auf Produktionsgeräten verwenden, wenn derselbe Schlüssel verwendet wird, und erhalten so erhebliche Systemkontrollfähigkeiten durch Debug-Funktionalität.

Risiko

Die Wiederverwendung von Schlüsseln für Debug und Produktion hat schwerwiegende Auswirkungen. Durchgesickerte Debug-Firmware ermöglicht Produktionsangriffe. Debug-Hooks sind auf Produktionssystemen ausnutzbar. Privilegieneskalation möglich. Systemkompromittierung erreichbar. Geistiges Eigentum offengelegt. Unbefugte Codeausführung ermöglicht. Speicher-Lese-/Schreibzugriff möglich. Identitätsübernahme-Angriffe ermöglicht. Die Vertrauensbasis ist grundlegend kompromittiert.

Lösung

Verwenden Sie verschiedene Schlüssel für Produktions- und Debug-Firmware. Reservieren Sie ausreichend Speicher für mehrere kryptographische Schlüssel. Wenn der Speicher begrenzt ist, erwägen Sie hardware-unterstütztes Schlüsselmanagement. Implementieren Sie Schlüsselwiderrufsmechanismen. Verwenden Sie separate Signierinfrastruktur für Debug und Produktion. Stellen Sie sicher, dass Debug-Firmware nicht auf Produktionsgeräten geladen werden kann. Erwägen Sie die Verwendung verschiedener Schlüsselgrößen oder Algorithmen zur zusätzlichen Trennung. Implementieren Sie Hardware-Fuses, um Debug-Fähigkeiten in der Produktion dauerhaft zu deaktivieren.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Debug-Modi legen sensible Daten und Speicherinhalte offen.
IntegritätBereich: Integrität

Debug-Firmware kann Speicher und Systemzustand modifizieren.
VerfügbarkeitBereich: Verfügbarkeit

Debug-Fähigkeiten können den normalen Systembetrieb storen.
ZugriffskontrolleBereich: Zugriffskontrolle, Authentifizierung

Privilegieneskalation und Identitätsübernahme durch Debug-Schnittstellen.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Einzelner Schlüssel für Debug und Produktion

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

// VERWUNDBAR: Einzelner öffentlicher Schlüssel für alle Firmware
static const uint8_t FIRMWARE_PUBLIC_KEY[256] = {
    0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01,
    // ... 2048-Bit-RSA-Öffentlicher-Schlüssel (gleich für Debug und Produktion)
    // Dieser Schlüssel ist in Silizium/OTP eingebettet
};

typedef struct {
    uint32_t magic;
    uint32_t version;
    uint32_t flags;  // Bit 0: Debug-Modus
    uint32_t size;
    uint8_t  signature[256];
    uint8_t  payload[];
} firmware_header_t;

// VERWUNDBAR: Keine Unterscheidung zwischen Debug und Produktion
bool vulnerable_verify_firmware(const firmware_header_t* header) {
    uint8_t computed_hash[32];
    uint8_t decrypted_hash[32];

    // Hash der Firmware-Payload berechnen
    sha256_hash(header->payload, header->size, computed_hash);

    // VERWUNDBAR: Verwendet denselben Schlüssel für Debug und Produktion
    rsa_decrypt(FIRMWARE_PUBLIC_KEY, header->signature, decrypted_hash);

    // VERWUNDBAR: Prüft nur Signatur, nicht Firmware-Typ
    return memcmp(computed_hash, decrypted_hash, 32) == 0;
}

bool vulnerable_boot_firmware(const firmware_header_t* header) {
    // VERWUNDBAR: Debug-Firmware auf Produktionsgerät akzeptiert
    if (!vulnerable_verify_firmware(header)) {
        return false;
    }

    // VERWUNDBAR: Debug-Modus-Flag kann in durchgesickerter Debug-Firmware gesetzt sein
    if (header->flags & 0x01) {
        enable_debug_mode();  // Angreifer erhält Debug-Zugriff!
    }

    execute_firmware(header->payload, header->size);
    return true;
}

// Angriffsszenario:
// 1. Debug-Firmware-Image sickert aus der Entwicklung durch
// 2. Angreifer extrahiert Debug-Firmware mit gesetztem Debug-Flag
// 3. Debug-Firmware ist mit demselben Schlüssel wie Produktion signiert
// 4. Angreifer lädt Debug-Firmware auf Produktionsgerät
// 5. Debug-Firmware bootet erfolgreich (gleicher Schlüssel!)
// 6. Angreifer hat vollen Debug-Zugriff auf Produktionshardware
# VERWUNDBAR: Firmware-Signierinfrastruktur mit gemeinsamen Schlüsseln

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding

class VulnerableSigningInfrastructure:
    def __init__(self):
        # VERWUNDBAR: Einzelnes Schlüsselpaar für alles
        self.private_key = self._load_master_key()
        self.public_key = self.private_key.public_key()

    def _load_master_key(self):
        # VERWUNDBAR: Gleicher Schlüssel für alle Zwecke verwendet
        return load_key_from_hsm("master_signing_key")

    def sign_debug_firmware(self, firmware_data):
        """Debug-Firmware signieren - VERWUNDBAR: Verwendet Produktionsschlüssel."""
        # VERWUNDBAR: Gleicher Schlüssel wie Produktion
        return self._sign_data(firmware_data)

    def sign_production_firmware(self, firmware_data):
        """Produktions-Firmware signieren - VERWUNDBAR: Gleicher Schlüssel wie Debug."""
        # VERWUNDBAR: Gleicher Schlüssel wie Debug
        return self._sign_data(firmware_data)

    def _sign_data(self, data):
        return self.private_key.sign(
            data,
            padding.PKCS1v15(),
            hashes.SHA256()
        )

# Build-System mit verwundbarer Infrastruktur
class VulnerableBuildSystem:
    def __init__(self):
        self.signing = VulnerableSigningInfrastructure()

    def build_debug_firmware(self, source_code):
        """Debug-Firmware mit umfangreichen Hooks erstellen."""
        firmware = compile_with_debug_flags(source_code)

        # Debug-Fähigkeiten hinzufügen
        firmware = add_debug_shell(firmware)
        firmware = add_memory_dump_function(firmware)
        firmware = add_jtag_enable(firmware)

        # VERWUNDBAR: Mit demselben Schlüssel wie Produktion signiert
        signature = self.signing.sign_debug_firmware(firmware)

        return package_firmware(firmware, signature, debug=True)

    def build_production_firmware(self, source_code):
        """Produktions-Firmware erstellen."""
        firmware = compile_with_release_flags(source_code)

        # VERWUNDBAR: Gleicher Schlüssel wie Debug, Debug-Firmware funktioniert hier auch
        signature = self.signing.sign_production_firmware(firmware)

        return package_firmware(firmware, signature, debug=False)

Sichere Lösung

// SICHER: Separate Schlüssel für Debug und Produktion

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

// SICHER: Separate öffentliche Schlüssel für verschiedene Firmware-Typen
static const uint8_t PRODUCTION_PUBLIC_KEY[384] = {
    0x30, 0x82, 0x01, 0x8A, 0x02, 0x82, 0x01, 0x81,
    // ... 3072-Bit-RSA-Öffentlicher-Schlüssel für Produktion
};

static const uint8_t DEBUG_PUBLIC_KEY[384] = {
    0x30, 0x82, 0x01, 0x8A, 0x02, 0x82, 0x01, 0x81,
    // ... Anderer 3072-Bit-RSA-Öffentlicher-Schlüssel für Debug
    // Nur auf Debug-/Entwicklungseinheiten installiert
};

// SICHER: Gerätemodus in Hardware-Fuse gespeichert
typedef enum {
    DEVICE_MODE_PRODUCTION = 0,
    DEVICE_MODE_DEVELOPMENT = 1
} device_mode_t;

// SICHER: Gerätemodus aus OTP-Fuses lesen (kann nicht geändert werden)
device_mode_t get_device_mode(void) {
    return read_otp_fuse(DEVICE_MODE_FUSE_ADDR);
}

typedef struct {
    uint32_t magic;
    uint32_t version;
    uint32_t firmware_type;  // 0 = Produktion, 1 = Debug
    uint32_t size;
    uint8_t  signature[384];
    uint8_t  payload[];
} firmware_header_t;

// SICHER: Mit passendem Schlüssel basierend auf Firmware-Typ verifizieren
bool secure_verify_firmware(const firmware_header_t* header) {
    const uint8_t* public_key;
    device_mode_t device_mode = get_device_mode();

    // SICHER: Schlüssel basierend auf Firmware-Typ auswählen
    if (header->firmware_type == 0) {
        // Produktions-Firmware - immer erlaubt
        public_key = PRODUCTION_PUBLIC_KEY;
    } else if (header->firmware_type == 1) {
        // SICHER: Debug-Firmware nur auf Entwicklungsgeräten
        if (device_mode != DEVICE_MODE_DEVELOPMENT) {
            log_security_event("Debug-Firmware auf Produktionsgerät abgelehnt");
            return false;
        }
        public_key = DEBUG_PUBLIC_KEY;
    } else {
        log_security_event("Unbekannter Firmware-Typ");
        return false;
    }

    uint8_t computed_hash[32];
    uint8_t decrypted_hash[32];

    sha256_hash(header->payload, header->size, computed_hash);
    rsa_decrypt(public_key, header->signature, decrypted_hash);

    return secure_compare(computed_hash, decrypted_hash, 32);
}

bool secure_boot_firmware(const firmware_header_t* header) {
    if (!secure_verify_firmware(header)) {
        return false;
    }

    // SICHER: Debug-Funktionen nur auf Entwicklungsgeräten verfügbar
    if (header->firmware_type == 1) {
        // Bereits verifiziert, dass Gerät im Entwicklungsmodus ist
        enable_debug_mode();
    }

    execute_firmware(header->payload, header->size);
    return true;
}

// SICHER: Produktionsgeräte haben Debug-Schlüssel-Fuse gebrannt
void secure_production_provisioning(void) {
    // Fuse brennen, um Produktionsmodus zu setzen (irreversibel)
    blow_otp_fuse(DEVICE_MODE_FUSE_ADDR, DEVICE_MODE_PRODUCTION);

    // SICHER: Optional Debug-Schlüsselspeicher löschen
    secure_erase_key_slot(DEBUG_KEY_SLOT);
}
# SICHER: Firmware-Signierinfrastruktur mit separaten Schlüsseln

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
from enum import Enum

class FirmwareType(Enum):
    PRODUCTION = "production"
    DEBUG = "debug"
    FACTORY_TEST = "factory_test"

class SecureSigningInfrastructure:
    def __init__(self):
        # SICHER: Separate Schlüssel für jeden Firmware-Typ
        self._keys = {
            FirmwareType.PRODUCTION: self._load_production_key(),
            FirmwareType.DEBUG: self._load_debug_key(),
            FirmwareType.FACTORY_TEST: self._load_factory_key(),
        }

    def _load_production_key(self):
        """Produktions-Signierschlüssel aus sicherem HSM laden."""
        # SICHER: Separater Schlüssel mit strikten Zugriffskontrollen
        return load_key_from_hsm(
            key_id="production_signing_key",
            require_quorum=True,  # Mehrere Bediener erforderlich
            audit_log=True
        )

    def _load_debug_key(self):
        """Debug-Signierschlüssel aus Entwicklungs-HSM laden."""
        # SICHER: Anderer Schlüssel, anderes HSM, anderer Zugriff
        return load_key_from_hsm(
            key_id="debug_signing_key",
            hsm="development_hsm",
            require_quorum=False  # Weniger strikt für Entwicklung
        )

    def _load_factory_key(self):
        """Werktest-Signierschlüssel laden."""
        return load_key_from_hsm(
            key_id="factory_test_key",
            hsm="factory_hsm"
        )

    def sign_firmware(self, firmware_data: bytes, firmware_type: FirmwareType) -> bytes:
        """Firmware mit dem passenden Schlüssel signieren."""
        if firmware_type not in self._keys:
            raise ValueError(f"Unbekannter Firmware-Typ: {firmware_type}")

        private_key = self._keys[firmware_type]

        # SICHER: Firmware-Typ in signierte Daten einbetten
        type_prefix = firmware_type.value.encode() + b'\x00'
        data_to_sign = type_prefix + firmware_data

        signature = private_key.sign(
            data_to_sign,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA384()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA384()
        )

        # SICHER: Signiervorgang protokollieren
        audit_log(
            action="firmware_signed",
            firmware_type=firmware_type.value,
            firmware_hash=sha384(firmware_data).hexdigest()
        )

        return signature

class SecureBuildSystem:
    def __init__(self):
        self.signing = SecureSigningInfrastructure()

    def build_debug_firmware(self, source_code):
        """Debug-Firmware erstellen - funktioniert nur auf Debug-Geräten."""
        firmware = compile_with_debug_flags(source_code)
        firmware = add_debug_capabilities(firmware)

        # SICHER: Verwendet Debug-spezifischen Schlüssel
        signature = self.signing.sign_firmware(
            firmware,
            FirmwareType.DEBUG
        )

        return package_firmware(
            firmware,
            signature,
            firmware_type=FirmwareType.DEBUG
        )

    def build_production_firmware(self, source_code):
        """Produktions-Firmware erstellen - funktioniert nur auf Produktionsgeräten."""
        firmware = compile_with_release_flags(source_code)

        # SICHER: Debug-Firmware mit anderem Schlüssel signiert funktioniert nicht
        signature = self.signing.sign_firmware(
            firmware,
            FirmwareType.PRODUCTION
        )

        return package_firmware(
            firmware,
            signature,
            firmware_type=FirmwareType.PRODUCTION
        )
// SICHER: Hardware-Boot-ROM mit separater Schlüsselverifizierung

module secure_boot_rom (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        device_mode_fuse,  // 0=Produktion, 1=Entwicklung
    input  wire [31:0] firmware_type,
    input  wire [3071:0] signature,
    input  wire [255:0] firmware_hash,
    output reg         boot_authorized,
    output reg  [7:0]  boot_status
);

    // SICHER: Separater Schlüsselspeicher
    reg [3071:0] production_public_key;  // Aus OTP
    reg [3071:0] debug_public_key;       // Aus OTP (in Produktion genullt)

    // Boot-Statuscodes
    localparam STATUS_OK = 8'h00;
    localparam STATUS_INVALID_TYPE = 8'h01;
    localparam STATUS_DEBUG_ON_PROD = 8'h02;
    localparam STATUS_SIGNATURE_FAIL = 8'h03;

    reg [3071:0] selected_key;
    reg key_selected;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            boot_authorized <= 1'b0;
            boot_status <= STATUS_OK;
            key_selected <= 1'b0;
        end else begin
            // SICHER: Schlüsselauswahl basierend auf Firmware-Typ und Gerätemodus
            case (firmware_type)
                32'h0000_0000: begin  // Produktions-Firmware
                    selected_key <= production_public_key;
                    key_selected <= 1'b1;
                end

                32'h0000_0001: begin  // Debug-Firmware
                    // SICHER: Debug nur auf Entwicklungsgeräten erlauben
                    if (device_mode_fuse == 1'b1) begin
                        selected_key <= debug_public_key;
                        key_selected <= 1'b1;
                    end else begin
                        // SICHER: Debug-Firmware auf Produktion ablehnen
                        boot_authorized <= 1'b0;
                        boot_status <= STATUS_DEBUG_ON_PROD;
                        key_selected <= 1'b0;
                    end
                end

                default: begin
                    boot_authorized <= 1'b0;
                    boot_status <= STATUS_INVALID_TYPE;
                    key_selected <= 1'b0;
                end
            endcase
        end
    end

endmodule

CVE-Beispiele

  • CVE-2020-10713: BootHole-Schwachstelle, bei der signierte Bootloader ersetzt werden könnten, was die Risiken des Schlüsselmanagements in Boot-Ketten verdeutlicht.
  • CVE-2018-3665: Debug-Funktionen wurden durch Firmware-Schlüsselkompromittierung in bestimmten Prozessoren aktiviert.

Verwandte CWEs

  • CWE-693: Versagen des Schutzmechanismus (übergeordnet)
  • CWE-321: Verwendung eines fest codierten kryptographischen Schlüssels (gleichrangig)
  • CWE-1207: Debug- und Testprobleme (Kategorie)
  • CWE-522: Unzureichend geschützte Anmeldeinformationen (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1291: Public Key Re-Use for Signing both Debug and Production Code." https://cwe.mitre.org/data/definitions/1291.html
  2. NIST. "Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations"
  3. ARM. "Platform Security Architecture"