Add reseal command for external signing

Pass `--external-sign` to `picotool seal` to use a blank signature block and output the hash

Then sign this hash externally, and pass signature and public key to `picotool reseal`
diff --git a/README.md b/README.md
index b22ba87..c4cb7f1 100644
--- a/README.md
+++ b/README.md
@@ -29,12 +29,15 @@
     picotool erase -r <from> <to> [device-selection]
     picotool reboot [-a] [-u] [-g <partition>] [-c <cpu>] [device-selection]
     picotool seal [--quiet] [--verbose] [--hash] [--sign] [--clear] [--pin-xip-sram]
-                [--no-squash] <infile> [-t <type>] [-o <offset>] <outfile> [-t <type>] [<key>]
-                [<otp>] [--major <major>] [--minor <minor>] [--rollback <rollback> [<rows>..]]
+                [--no-squash] [--external-sign] <infile> [-t <type>] [-o <offset>] <outfile>
+                [-t <type>] [<key>] [<otp>] [--major <major>] [--minor <minor>] [--rollback
+                <rollback> [<rows>..]]
     picotool encrypt [--quiet] [--verbose] [--embed] [--fast-rosc] [--use-mbedtls]
                 [--otp-key-page <page>] [--hash] [--sign] [--no-clear] [--pin-xip-sram]
                 <infile> [-t <type>] [-o <offset>] <outfile> [-t <type>] <aes_key> <iv_salt>
                 [<signing_key>] [<otp>]
+    picotool reseal [--quiet] [--verbose] <infile> [-t <type>] [-o <offset>] <sigfile> <pubkey>
+                [<otp>]
     picotool partition info|create
     picotool uf2 convert|combine|info
     picotool otp get|set|load|white-label|permissions|dump|list
@@ -58,6 +61,7 @@
     reboot      Reboot the device
     seal        Add final metadata to a binary, optionally including a hash and/or signature.
     encrypt     Encrypt the program.
+    reseal      Replace the signature in the final metadata of a binary.
     partition   Commands related to RP2350 Partition Tables
     uf2         Commands related to UF2 creation and status
     otp         Commands related to the RP2350 OTP (One-Time-Programmable) Memory
@@ -71,7 +75,7 @@
 Note commands that aren't acting on files require a device in BOOTSEL mode to be connected.
 
 ## Links to documentation for `picotool` commands
-[`info`](#info) [`config`](#config) [`load`](#load) [`save`](#save) [`verify`](#verify) [`erase`](#erase) [`reboot`](#reboot) [`seal`](#seal) [`encrypt`](#encrypt) [`partition`](#partition) [`uf2`](#uf2) [`otp`](#otp) [`coprodis`](#coprodis) [`link`](#link) [`bdev`](#bdev)
+[`info`](#info) [`config`](#config) [`load`](#load) [`save`](#save) [`verify`](#verify) [`erase`](#erase) [`reboot`](#reboot) [`seal`](#seal) [`encrypt`](#encrypt) [`reseal`](#reseal) [`partition`](#partition) [`uf2`](#uf2) [`otp`](#otp) [`coprodis`](#coprodis) [`link`](#link) [`bdev`](#bdev)
 
 ## Building & Installing
 
@@ -715,8 +719,9 @@
 
 SYNOPSIS:
     picotool seal [--quiet] [--verbose] [--hash] [--sign] [--clear] [--pin-xip-sram]
-                [--no-squash] <infile> [-t <type>] [-o <offset>] <outfile> [-t <type>] [<key>]
-                [<otp>] [--major <major>] [--minor <minor>] [--rollback <rollback> [<rows>..]]
+                [--no-squash] [--external-sign] <infile> [-t <type>] [-o <offset>] <outfile>
+                [-t <type>] [<key>] [<otp>] [--major <major>] [--minor <minor>] [--rollback
+                <rollback> [<rows>..]]
 
 OPTIONS:
         --quiet
@@ -744,6 +749,8 @@
             Pin XIP SRAM on load
         --no-squash
             Don't squash segments in the ELF file
+        --external-sign
+            For use with external signing and `picotool reseal`
     File to load from
         <infile>
             The file name
@@ -843,6 +850,88 @@
             Specify file type (uf2 | elf | bin) explicitly, ignoring file extension
 ```
 
+## reseal
+
+`reseal` allows you to modify the signature of a binary that has already been sealed with `seal`.
+
+This can be used for signing binaries without passing `picotool` the private key, for example with hardware security modules, or password-protected private keys. As with `seal`, your signing key must be for the _secp256k1_ curve, the public key must be in the PEM format, and the signature must be in the DER format.
+
+For an example, to generate a password-protected private key and corresponding public key, you could use the following commands:
+```text
+$ openssl ecparam -name secp256k1 -genkey -out private.pem
+$ openssl ec -aes256 -in private.pem -out private.enc.pem
+read EC key
+writing EC key
+Enter pass phrase for PEM:
+Verifying - Enter pass phrase for PEM:
+$ mv private.enc.pem private.pem
+$ openssl ec -in private.pem -out public.pem -pubout
+read EC key
+Enter pass phrase for private.pem:
+writing EC key
+```
+
+Then seal the binary and sign the hash (and verify the signature):
+```text
+$ picotool seal --external-sign hello_usb.uf2 hello_usb.signed.uf2 --quiet > hash.txt
+$ xxd -r -p hash.txt > hash.bin
+$ openssl pkeyutl -in hash.bin -inkey private.pem -out signature.der -pkeyopt digest:sha256
+Enter pass phrase for private.pem:
+$ openssl pkeyutl -in hash.bin -inkey public.pem -pubin -verify -sigfile signature.der -pkeyopt digest:sha256
+Signature Verified Successfully
+```
+
+Finally, reseal the binary with the new signature:
+```text
+$ picotool reseal hello_usb.signed.uf2 signature.der public.pem otp.json
+Resealed File hello_usb.signed.uf2:
+
+Program Information
+ name:          hello_usb
+ web site:      https://github.com/raspberrypi/pico-examples/tree/HEAD/hello_world/usb
+ features:      USB stdin / stdout
+ binary start:  0x10000000
+ binary end:    0x10005038
+ target chip:   RP2350
+ image type:    ARM Secure
+ hash:          verified
+ signature:     verified
+```
+
+This will produce the same output binary as `picotool seal --sign --hash ...` would for a non-password-protected key.
+
+```text
+$ picotool help reseal
+RESEAL:
+    Replace the signature in the final metadata of a binary.
+
+SYNOPSIS:
+    picotool reseal [--quiet] [--verbose] <infile> [-t <type>] [-o <offset>] <sigfile> <pubkey>
+                [<otp>]
+
+OPTIONS:
+        --quiet
+            Don't print any output
+        --verbose
+            Print verbose output
+        <sigfile>
+            Signature file (.der)
+        <pubkey>
+            Public key file (.pem)
+        <otp>
+            JSON file to save OTP to (will edit existing file if it exists)
+    File to re-seal
+        <infile>
+            The file name
+        -t <type>
+            Specify file type (uf2 | elf | bin) explicitly, ignoring file extension
+    BIN file options
+        -o, --offset
+            Specify the load address for a BIN file
+        <offset>
+            Load offset (memory address; default 0x10000000)
+```
+
 ## partition
 
 The `partition` commands allow you to interact with the partition tables on RP2350 devices, and also create them.
diff --git a/bintool/bintool.cpp b/bintool/bintool.cpp
index 43a23a0..e0a2986 100644
--- a/bintool/bintool.cpp
+++ b/bintool/bintool.cpp
@@ -40,15 +40,20 @@
     int rc;
 
     mbedtls_pk_init(&pk_ctx);
-#if MBEDTLS_VERSION_MAJOR >= 3
-    // This rng is only used for blinding when reading the key file
-    // As this should only be done on a secure computer, blinding is not required, so it's fine to not actually seed it with any entropy
-    mbedtls_ctr_drbg_context ctr_drbg;
-    mbedtls_ctr_drbg_init(&ctr_drbg);
-    rc = mbedtls_pk_parse_keyfile(&pk_ctx, filename.c_str(), NULL, mbedtls_ctr_drbg_random, &ctr_drbg);
-#else
-    rc = mbedtls_pk_parse_keyfile(&pk_ctx, filename.c_str(), NULL);
-#endif
+    if (private_key == nullptr) {
+        // Only read public key
+        rc = mbedtls_pk_parse_public_keyfile(&pk_ctx, filename.c_str());
+    } else {
+    #if MBEDTLS_VERSION_MAJOR >= 3
+        // This rng is only used for blinding when reading the key file
+        // As this should only be done on a secure computer, blinding is not required, so it's fine to not actually seed it with any entropy
+        mbedtls_ctr_drbg_context ctr_drbg;
+        mbedtls_ctr_drbg_init(&ctr_drbg);
+        rc = mbedtls_pk_parse_keyfile(&pk_ctx, filename.c_str(), NULL, mbedtls_ctr_drbg_random, &ctr_drbg);
+    #else
+        rc = mbedtls_pk_parse_keyfile(&pk_ctx, filename.c_str(), NULL);
+    #endif
+    }
     if (rc != 0) {
         char error_string[128];
         mbedtls_strerror(rc, error_string, sizeof(error_string));
@@ -60,7 +65,9 @@
     if (!keypair) {
         fail(ERROR_FORMAT, "Failed to parse key file %s", filename.c_str());
     }
-    mbedtls_mpi_write_binary(&keypair->d, reinterpret_cast<unsigned char *>(private_key), 32);
+    if (private_key != nullptr) {
+        mbedtls_mpi_write_binary(&keypair->d, reinterpret_cast<unsigned char *>(private_key), 32);
+    }
     mbedtls_mpi_write_binary(&keypair->Q.X, reinterpret_cast<unsigned char *>(public_key), 32);
     mbedtls_mpi_write_binary(&keypair->Q.Y, reinterpret_cast<unsigned char *>(public_key) + 32, 32);
     return 0;
@@ -690,13 +697,21 @@
             e = rand();
         }
 
-        signature_t sig;
-        sign_sha256(entropy, sizeof(entropy), &sha256, &public_key, &private_key, &sig);
-        dumper("SIG", sig);
+        signature_t sig = {0};
 
-        uint32_t err = verify_signature_secp256k1(&sig, &public_key, &sha256);
-        if (err) {
-            fail(ERROR_VERIFICATION_FAILED, "Signature verification failed");
+        // Skip signing if keys are zero, as that is a binary for external signing
+        int public_zero = memcmp(&(public_key.bytes[0]), &(public_key.bytes[1]), sizeof(public_key.bytes) - 1);
+        int private_zero = memcmp(&(private_key.bytes[0]), &(private_key.bytes[1]), sizeof(private_key.bytes) - 1);
+        bool empty_keys = (public_zero == 0 && public_key.bytes[0] == 0 && private_zero == 0 && private_key.bytes[0] == 0);
+
+        if (!empty_keys) {
+            sign_sha256(entropy, sizeof(entropy), &sha256, &public_key, &private_key, &sig);
+            dumper("SIG", sig);
+
+            uint32_t err = verify_signature_secp256k1(&sig, &public_key, &sha256);
+            if (err) {
+                fail(ERROR_VERIFICATION_FAILED, "Signature verification failed");
+            }
         }
 
         std::shared_ptr<signature_item> signature = std::make_shared<signature_item>(PICOBIN_SIGNATURE_SECP256K1);
diff --git a/bintool/mbedtls_wrapper.c b/bintool/mbedtls_wrapper.c
index c623ebd..89568ac 100644
--- a/bintool/mbedtls_wrapper.c
+++ b/bintool/mbedtls_wrapper.c
@@ -36,6 +36,8 @@
 #define dump_pubkey(...) ((void)0)
 #endif
 
+#define assert_or_return(assertion, retval) assert(assertion); if (!(assertion)) return retval;
+
 void mb_sha256_buffer(const uint8_t *data, size_t len, message_digest_t *digest_out) {
     mbedtls_sha256(data, len, digest_out->bytes, 0);
 }
@@ -150,14 +152,14 @@
 }
 
 
-void der_to_raw(signature_t *sig) {
-    assert(sig->der[0] == 0x30);
-    assert(sig->der[2] == 0x02);
+bool der_to_raw(signature_t *sig) {
+    assert_or_return(sig->der[0] == 0x30, false);
+    assert_or_return(sig->der[2] == 0x02, false);
     uint8_t b2 = sig->der[3];
-    assert(sig->der[4 + b2] == 0x02);
+    assert_or_return(sig->der[4 + b2] == 0x02, false);
     uint8_t b3 = sig->der[5 + b2];
 
-    assert(sig->der_len == 6u + b2 + b3);
+    assert_or_return(sig->der_len == 6u + b2 + b3, false);
 
     unsigned char r[32];
     if (b2 == 33) {
@@ -182,6 +184,8 @@
     memset(sig->bytes, 0, sizeof(sig->bytes));
     memcpy(sig->bytes, r, sizeof(r));
     memcpy(sig->bytes + 32, s, sizeof(s));
+
+    return true;
 }
 
 
diff --git a/bintool/mbedtls_wrapper.h b/bintool/mbedtls_wrapper.h
index 48c06f7..9e2526d 100644
--- a/bintool/mbedtls_wrapper.h
+++ b/bintool/mbedtls_wrapper.h
@@ -9,6 +9,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 #include <assert.h>
+#include <stdbool.h>
 
 #include <mbedtls/sha256.h>
 #include <mbedtls/ecdsa.h>
@@ -78,6 +79,8 @@
         const public_t public_key[1],
         const message_digest_t digest[1]);
 
+bool der_to_raw(signature_t *sig);
+
 #define sha256_buffer mb_sha256_buffer
 #define aes256_buffer mb_aes256_buffer
 #define sign_sha256 mb_sign_sha256
diff --git a/main.cpp b/main.cpp
index 38c467b..9dc7fcb 100644
--- a/main.cpp
+++ b/main.cpp
@@ -236,7 +236,7 @@
 };
 #endif
 
-enum class filetype {bin, elf, uf2, pem, json};
+enum class filetype {bin, elf, uf2, pem, json, der};
 const string getFiletypeName(enum filetype type) 
 {
    switch (type) 
@@ -246,6 +246,7 @@
       case filetype::uf2: return "UF2";
       case filetype::pem: return "PEM";
       case filetype::json: return "JSON";
+      case filetype::der: return "DER";
       default: assert(false); return "ERROR_TYPE";
    }
 }
@@ -633,6 +634,7 @@
         bool pin_xip_sram = false;
         bool set_tbyb = false;
         bool no_squash = false;
+        bool external_sign = false;
         uint16_t major_version = 0;
         uint16_t minor_version = 0;
         uint16_t rollback_version = 0;
@@ -1203,7 +1205,8 @@
                 option("--sign").set(settings.seal.sign) % "Sign the file" +
                 option("--clear").set(settings.seal.clear_sram) % "Clear all of main SRAM on load" +
                 option("--pin-xip-sram").set(settings.seal.pin_xip_sram) % "Pin XIP SRAM on load" +
-                option("--no-squash").set(settings.seal.no_squash) % "Don't squash segments in the ELF file"
+                option("--no-squash").set(settings.seal.no_squash) % "Don't squash segments in the ELF file" +
+                option("--external-sign").set(settings.seal.external_sign) % "For use with external signing and `picotool reseal`"
             ).min(0).doc_non_optional(true) % "Configuration" +
             named_file_selection_x("infile", 0) % "File to load from" +
             (
@@ -1233,6 +1236,31 @@
         return "Add final metadata to a binary, optionally including a hash and/or signature.";
     }
 };
+
+struct reseal_command : public cmd {
+    reseal_command() : cmd("reseal") {}
+    bool execute(device_map &devices) override;
+    virtual device_support get_device_support() override { return none; }
+
+    group get_cli() override {
+        return (
+            option("--quiet").set(settings.quiet) % "Don't print any output" +
+            option("--verbose").set(settings.verbose) % "Print verbose output" +
+            named_file_selection_x("infile", 0) % "File to re-seal" +
+            (
+                option('o', "--offset").set(settings.offset_set) % "Specify the load address for a BIN file" &
+                     hex("offset").set(settings.offset) % "Load offset (memory address; default 0x10000000)"
+            ).force_expand_help(true) % "BIN file options" +
+            named_untyped_file_selection_x("sigfile", 1) % "Signature file (.der)" +
+            named_untyped_file_selection_x("pubkey", 2) % "Public key file (.pem)" +
+            optional_untyped_file_selection_x("otp", 3) % "JSON file to save OTP to (will edit existing file if it exists)"
+        );
+    }
+
+    string get_doc() const override {
+        return "Replace the signature in the final metadata of a binary.";
+    }
+};
 #endif
 
 struct link_command : public cmd {
@@ -1806,6 +1834,7 @@
     #if HAS_MBEDTLS
         std::shared_ptr<cmd>(new seal_command()),
         std::shared_ptr<cmd>(new encrypt_command()),
+        std::shared_ptr<cmd>(new reseal_command()),
     #endif
         std::shared_ptr<cmd>(new partition_command()),
         std::shared_ptr<cmd>(new uf2_command()),
@@ -3242,6 +3271,8 @@
             return filetype::pem;
         } else if (low.rfind(".json") == low.size() - 5) {
             return filetype::json;
+        } else if (low.rfind(".der") == low.size() - 4) {
+            return filetype::der;
         }
     } else if (!file_type.empty()) {
         low = lowercase(file_type);
@@ -3260,6 +3291,9 @@
         if (low == "json") {
             return filetype::json;
         }
+        if (low == "der") {
+            return filetype::der;
+        }
         throw cli::parse_error("unsupported file type '" + low + "'");
     }
     throw cli::parse_error("filename '" + filename+ "' does not have a recognized file type (extension)");
@@ -6119,6 +6153,115 @@
     return false;
 }
 
+void output_otp_secure_boot(uint8_t idx, public_t public_key) {
+    message_digest_t pub_sha256;
+    sha256_buffer(public_key.bytes, sizeof(public_key.bytes), &pub_sha256);
+    DEBUG_LOG("PUBLIC KEY SHA256 ");
+    for(uint8_t i : pub_sha256.bytes) {
+        DEBUG_LOG("%02x", i);
+    }
+    DEBUG_LOG("\n");
+
+    if (get_file_type_idx(idx) != filetype::json) {
+        fail(ERROR_ARGS, "Can only output OTP json");
+    }
+    auto check_json_file = std::ifstream(settings.filenames[idx]);
+    json otp_json;
+    if (check_json_file.good()) {
+        otp_json = json::parse(check_json_file);
+        DEBUG_LOG("Appending to existing otp json\n");
+        check_json_file.close();
+    }
+    auto json_out = get_file_idx(ios::out, idx);
+
+    // Add otp bootkey rows
+    for (int i = 0; i < 32; ++i) {
+        otp_json["bootkey0"][i] = pub_sha256.bytes[i];
+    }
+
+    // Add otp fields to enable secure boot
+    otp_json["crit1"]["secure_boot_enable"] = 1;
+    otp_json["boot_flags1"]["key_valid"] = 1;
+
+    *json_out << std::setw(4) << otp_json << std::endl;
+    json_out->close();
+}
+
+bool reseal_command::execute(device_map &devices) {
+
+    if (get_file_type_idx(1) != filetype::der) {
+        fail(ERROR_ARGS, "Can only read der signatures");
+    }
+
+    if (get_file_type_idx(2) != filetype::pem) {
+        fail(ERROR_ARGS, "Can only read pem keys");
+    }
+
+    public_t public_key = {0};
+
+    read_keys(settings.filenames[2], &public_key, nullptr);
+
+    signature_t signature = {0};
+
+    auto sigfile = get_file_idx(ios::in|ios::binary, 1);
+    sigfile->exceptions(std::iostream::failbit | std::iostream::badbit);
+    sigfile->seekg(0, std::ios::end);
+    signature.der_len = sigfile->tellg();
+    sigfile->seekg(0, std::ios::beg);
+    sigfile->read((char*)signature.der, signature.der_len);
+    sigfile->close();
+    
+    if (!der_to_raw(&signature)) {
+        fail(ERROR_ARGS, "Signature was not in DER format");
+    }
+
+    auto file_access = get_file_memory_access(0, true);
+    set_model_from_metadata(file_access);
+    vector<uint8_t> bin;
+    std::unique_ptr<block> last_block = find_last_block(file_access, bin);
+    if (last_block == nullptr) {
+        fail(ERROR_NOT_POSSIBLE, "Last block not found");
+    }
+
+    // Replace signature in block
+    std::shared_ptr<signature_item> sig_item = last_block->get_item<signature_item>();
+    if (sig_item == nullptr) {
+        fail(ERROR_NOT_POSSIBLE, "Last block does not contain a signature item");
+    }
+    sig_item->public_key_bytes = std::vector<uint8_t>(public_key.bytes, public_key.bytes + sizeof(public_key.bytes));
+    sig_item->signature_bytes = std::vector<uint8_t>(signature.bytes, signature.bytes + sizeof(signature.bytes));
+
+    // Verify the signature
+    std::shared_ptr<hash_value_item> hash_item = last_block->get_item<hash_value_item>();
+    if (hash_item == nullptr) {
+        fail(ERROR_NOT_POSSIBLE, "Last block does not contain a hash value item");
+    }
+    message_digest_t sha256;
+    memcpy(sha256.bytes, hash_item->hash_bytes.data(), sizeof(sha256.bytes));
+    uint32_t err = verify_signature_secp256k1(&signature, &public_key, &sha256);
+    if (err) {
+        fail(ERROR_VERIFICATION_FAILED, "Signature verification failed");
+    }
+
+    // Write out modified block
+    std::vector<uint32_t> words = last_block->to_words();
+    file_access.write_vector(last_block->physical_addr, words);
+
+    // Write OTP JSON if requested
+    if (!settings.filenames[3].empty()) {
+        output_otp_secure_boot(3, public_key);
+    }
+
+    if (!settings.quiet) {
+        set_model_from_metadata(file_access);
+        fos << "Resealed File " << settings.filenames[0] << ":\n\n";
+        settings.info.show_basic = true;
+        info_guts(file_access, nullptr);
+    }
+
+    return false;
+}
+
 bool seal_command::execute(device_map &devices) {
     bool isElf = false;
     bool isBin = false;
@@ -6133,11 +6276,16 @@
         fail(ERROR_ARGS, "Can only sign ELFs, BINs or UF2s");
     }
 
+    if (settings.seal.external_sign) {
+        settings.seal.sign = true;
+        settings.seal.hash = true;
+    }
+
     if (get_file_type_idx(1) != get_file_type()) {
         fail(ERROR_ARGS, "Can only sign to same file type");
     }
 
-    if (settings.seal.sign && settings.filenames[2].empty()) {
+    if (settings.seal.sign && settings.filenames[2].empty() && !settings.seal.external_sign) {
         fail(ERROR_ARGS, "missing key file for signing");
     }
 
@@ -6172,10 +6320,10 @@
     }
 
 
-    private_t private_key = {};
-    public_t public_key = {};
+    private_t private_key = {0};
+    public_t public_key = {0};
 
-    if (settings.seal.sign) read_keys(settings.filenames[2], &public_key, &private_key);
+    if (settings.seal.sign && !settings.seal.external_sign) read_keys(settings.filenames[2], &public_key, &private_key);
 
     model_t model = get_model(0);
 
@@ -6236,40 +6384,8 @@
         fail(ERROR_ARGS, "Must be ELF or BIN");
     }
 
-    if (settings.seal.sign) {
-        message_digest_t pub_sha256;
-        sha256_buffer(public_key.bytes, sizeof(public_key.bytes), &pub_sha256);
-        DEBUG_LOG("PUBLIC KEY SHA256 ");
-        for(uint8_t i : pub_sha256.bytes) {
-            DEBUG_LOG("%02x", i);
-        }
-        DEBUG_LOG("\n");
-
-        if (!settings.filenames[3].empty()) {
-            if (get_file_type_idx(3) != filetype::json) {
-                fail(ERROR_ARGS, "Can only output OTP json");
-            }
-            auto check_json_file = std::ifstream(settings.filenames[3]);
-            json otp_json;
-            if (check_json_file.good()) {
-                otp_json = json::parse(check_json_file);
-                DEBUG_LOG("Appending to existing otp json\n");
-                check_json_file.close();
-            }
-            auto json_out = get_file_idx(ios::out, 3);
-
-            // Add otp bootkey rows
-            for (int i = 0; i < 32; ++i) {
-                otp_json["bootkey0"][i] = pub_sha256.bytes[i];
-            }
-
-            // Add otp fields to enable secure boot
-            otp_json["crit1"]["secure_boot_enable"] = 1;
-            otp_json["boot_flags1"]["key_valid"] = 1;
-
-            *json_out << std::setw(4) << otp_json << std::endl;
-            json_out->close();
-        }
+    if (settings.seal.sign && !settings.filenames[3].empty()) {
+        output_otp_secure_boot(3, public_key);
     }
 
     if (!settings.quiet) {
@@ -6280,6 +6396,28 @@
         info_guts(access, nullptr);
     }
 
+    if (settings.seal.external_sign) {
+        auto access = get_file_memory_access(1);
+        set_model_from_metadata(access);
+        vector<uint8_t> bin;
+        std::unique_ptr<block> last_block = find_last_block(access, bin);
+        std::shared_ptr<hash_value_item> hash_value = last_block->get_item<hash_value_item>();
+        if(hash_value != nullptr) {
+            std::stringstream val;
+            for(uint8_t i : hash_value->hash_bytes) {
+                val << hex_string(i, 2, false, true);
+            }
+            if (settings.quiet) {
+                // Just print hash value
+                printf("%s\n", val.str().c_str());
+            } else {
+                fos.first_column(0);
+                fos.hanging_indent(0);
+                fos << "\nHash value for external signing: " << val.str() << "\n";
+            }
+        }
+    }
+
     return false;
 }
 #endif