With this feature the signing of the swu file
can be done in a second call after the generation,
because signing could require a different environment.
Refactor SWUFile supporting read and write
Change SWUFile close to add_trailer as it is not closing but writing
---
 README.rst                |  20 +++++--
 swugenerator/generator.py |   2 +-
 swugenerator/main.py      |  30 +++++++++--
 swugenerator/signer.py    |  43 +++++++++++++++
 swugenerator/swu_file.py  | 107 +++++++++++++++++++++++++++++++++-----
 5 files changed, 180 insertions(+), 22 deletions(-)
 create mode 100644 swugenerator/signer.py
diff --git a/README.rst b/README.rst
index 40d76f2..26b860b 100644
--- a/README.rst
+++ b/README.rst
@@ -8,28 +8,36 @@ A host tool to generate SWU update package for SWUpdate.
 SYNOPSIS
 ========
 
-usage: SWUGenerator [-h] [-K ENCRYPTION_KEY_FILE] [-k SIGN] -s SW_DESCRIPTION
+usage: SWUGenerator [-h] [-K ENCRYPTION_KEY_FILE] [-k SIGN] [-s SW_DESCRIPTION]
                     [-a ARTIFACTORY] -o SWU_FILE [-c CONFIG]
                     command
 
 Generator SWU Packages for SWUpdate
 
 positional arguments:
-  command               command to be executed, one of : create
+  command:
+    {create,sign}         command to be executed
+      create              creates a SWU file
+      sign                signs an existing SWU file provided by -i
 
 optional arguments:
   -h, --help            show this help message and exit
   -K ENCRYPTION_KEY_FILE, --encryption-key-file ENCRYPTION_KEY_FILE
                         AES Key to encrypt artifacts
   -n, --no-compress     Do not compress files
+  -e, --no-encrypt      Do not encrypt files
+  -x, --no-ivt          Do not generate IV when encrypting
+  -y, --no-hash         Do not store sha256 hash in sw-description
   -k SIGN, --sign SIGN  RSA key or certificate to sign the SWU
   -s SW_DESCRIPTION, --sw-description SW_DESCRIPTION
-                        sw-description template
+                        sw-description template for the create command
   -t, --encrypt-swdesc  Encrypt sw-description
   -a ARTIFACTORY, --artifactory ARTIFACTORY
                         list of directories where artifacts are searched
   -o SWU_FILE, --swu-file SWU_FILE
                         SWU output file
+  -i, --swu-in-file SWU_IN_FILE
+                        SWU input file to be signed for the sign command
   -c CONFIG, --config CONFIG
                         configuration file
 
@@ -52,6 +60,12 @@ The tool signs the SWU and can encrypt the artifacts. The tool parses the libcon
         - sign sw-description with one of the methods accepted by SWUpdate
         - pack all artifacts into a SWU file
 
+It maybe run in two steps to create an unsigned swu file and then sign it later in a second call::
+
+    swugenerator -o signed_output.swu -i output.swu -k CMS,key.pem,ca.crt sign
+
+
 Installation
 ============
 
diff --git a/swugenerator/generator.py b/swugenerator/generator.py
index dc0af31..6087a64 100644
--- a/swugenerator/generator.py
+++ b/swugenerator/generator.py
@@ -63,7 +63,7 @@ class SWUGenerator:
 
     def close(self):
         self.temp.cleanup()
-        self.cpiofile.close()
+        self.cpiofile.add_trailer()
         self.out.close()
 
     def process_entry(self, entry):
diff --git a/swugenerator/main.py b/swugenerator/main.py
index fbc28f4..b5ede6b 100644
--- a/swugenerator/main.py
+++ b/swugenerator/main.py
@@ -15,7 +15,8 @@ from typing import List, Optional, Tuple, Union
 
 import libconf
 
-from swugenerator import __about__, generator
+from swugenerator import __about__, generator, signer
+
 from swugenerator.swu_sign import SWUSignCMS, SWUSignCustom, SWUSignPKCS11, SWUSignRSA
 
 
@@ -204,6 +205,14 @@ def create_swu(args: argparse.Namespace) -> None:
     swu.process()
     swu.close()
 
+def sign_swu(args: argparse.Namespace) -> None:
+    swu = signer.SWUSigner(
+        args.swu_in_file,
+        args.swu_file,
+        args.sign
+    )
+    swu.process()
+    swu.close()
 
 def parse_args(args: List[str]) -> None:
     """Sets up arguments for swugenerator and parses commandline args
@@ -273,9 +282,9 @@ def parse_args(args: List[str]) -> None:
     parser.add_argument(
         "-s",
         "--sw-description",
-        required=True,
+        required=False,
         type=lambda p: Path(p).resolve(),
-        help="sw-description template",
+        help="sw-description template, required for the create command",
     )
 
     parser.add_argument(
@@ -338,10 +347,23 @@ def parse_args(args: List[str]) -> None:
     )
     create_subparser = subparsers.add_parser("create", help="creates a SWU file")
     create_subparser.set_defaults(func=create_swu)
-
+    sign_subparser = subparsers.add_parser("sign", help="signs an existing SWU file provided by -i")
+    sign_subparser.set_defaults(func=sign_swu)
+    sign_subparser.add_argument(
+        "-i",
+        "--swu-in-file",
+        required=True,
+        type=Path,
+        help="SWU input file to be signed, required for the sign command",
+    )
     args = parser.parse_args(args)
     if hasattr(args, 'sign') and args.sign:
         args.sign = parse_signing_option(args.sign, args.engine, args.keyform)
+    if args.func == create_swu and not args.sw_description:
+        parser.error(
+            "the following arguments are required: -s/--sw-description"
+        )
+
     args.func(args)
 
 
diff --git a/swugenerator/signer.py b/swugenerator/signer.py
new file mode 100644
index 0000000..2b75d3a
--- /dev/null
+++ b/swugenerator/signer.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2025 INgo Rah
+#
+# SPDX-License-Identifier: GPLv3
+import logging
+from tempfile import TemporaryDirectory
+from pathlib import Path
+
+from swugenerator.swu_file import SWUFile
+
+class SWUSigner:
+    def __init__(self, in_file, out_file, crypt):
+        self.signtool = crypt
+        self.fout = open(out_file, "wb")
+        self.cpio_out = SWUFile(self.fout)
+        self.fin = open(in_file, "rb")
+        self.cpio_in = SWUFile(self.fin)
+        self.temp = TemporaryDirectory()
+
+    def close(self):
+        self.temp.cleanup()
+        self.fin.close()
+        self.cpio_out.add_trailer()
+        self.fout.close()
+
+    def process(self):
+        # extract the swu file and get the file list
+
+        # call signing to generate the sig file
+        if self.signtool:
+            sw_desc_out = sw_desc_in.with_suffix(".sig")
+            self.signtool.prepare_cmd(sw_desc_in, sw_desc_out)
+            self.signtool.sign()
+            if artifacts[0] + ".sig" not in artifacts:
+                artifacts.insert(1, artifacts[0] + ".sig")
+            else:
+
+        for name in artifacts:
+            self.cpio_out.addartifacttoswu(path)
+
diff --git a/swugenerator/swu_file.py b/swugenerator/swu_file.py
index d05f694..058a413 100644
--- a/swugenerator/swu_file.py
+++ b/swugenerator/swu_file.py
@@ -2,8 +2,9 @@
 #
 # SPDX-License-Identifier: GPLv3
 #
-# This class manages to pack the SWU file
-# it has methods to add files to the archive
+# This class manages to pack the SWU file and
+# extracts a SWU file with returning the list.
+# It has methods to add files to the archive.
 import os
 import stat
 
@@ -11,21 +12,28 @@ import stat
 class CPIOException(Exception):
     pass
 
+class MagicException(Exception):
+    pass
+
+class FormatException(Exception):
+    pass
 
 class SWUFile:
-    def __init__(self, outfile):
-        self.position = 0
+    def __init__(self, file):
         """
-        :type outfile: FileIO of bytes
+        :type file: FileIO of bytes
         """
-        self.outfile = outfile
         self.position = 0
+        self.file = file
+        self.artifacts = []
+        # for reading
+        self.header = None
 
     def _rawwrite(self, data):
         """
         :type data: bytes
         """
-        self.outfile.write(data)
+        self.file.write(data)
         self.position += len(data)
 
     def _align(self):
@@ -76,6 +84,7 @@ class SWUFile:
                 size -= len(chunk)
         if size:
             raise CPIOException("File was changed while reading", cpio_filename)
+        self.artifacts.append(cpio_filename)
 
     def write_header(self, cpio_filename):
         if cpio_filename != "TRAILER!!!":
@@ -112,15 +121,85 @@ class SWUFile:
             if (value > 0xFFFFFFFF) or (value < 0):
                 raise CPIOException("STOP: value out of range", i, value)
             s = f"{value:08X}"
-            self.outfile.write(bytes(s, "ascii"))
-            self.position += len(s)
+            self._rawwrite(bytes(s, "ascii"))
 
         fn = bytes(base_filename, "ascii") + b"\x00"
         self._rawwrite(fn)
 
-    def close(self):
-        self.write_header("TRAILER!!!")
-        offset = -(self.position % -512)
-        if offset:
-            self._rawwrite(b"\x00" * offset)
+    def add_trailer(self):
+        try:
+            self.write_header("TRAILER!!!")
+            offset = -(self.position % -512)
+            if offset:
+                self._rawwrite(b"\x00" * offset)
+        except PermissionError:
+            # the file may have been opened for reading
+            # flush shouldn't be called
+
         self.position = 0
+
+    def _extract_file(self, dir, name):
+        c_mode = int(self.header[14:22], 16)
+        c_filesize = int(self.header[54:62], 16)
+        # Read file data
+        filedata = self.file.read(c_filesize)
+        pad = (4 - c_filesize % 4) % 4
+        self.file.read(pad)
+        # Compute output path
+        path = os.path.join(dir, name)
+        os.makedirs(os.path.dirname(path), exist_ok=True)
+        # check if its really a file
+        if c_mode & 0o170000 != 0o040000:
+            with open(path, "wb") as out:
+                out.write(filedata)
+            try:
+                # preserve mode
+                os.chmod(path, c_mode & 0o7777)
+            except Exception:
+                pass
+        # check CRC
+        outcrc = self.cpiocrc(path)
+        incrc = int(self.header[102:110], 16)
+        if outcrc !=  incrc:
+            raise FormatException(
+                f"Wrong file crc {incrc} vs. {outcrc}"
+            )
+
+    def _extract_next(self, dir):
+        # Read fixed-size header (110 bytes for "newc" format)
+        self.header = self.file.read(110)
+        if len(self.header) < 110:
+            raise FormatException(
+                f"Unknown or unsupported header format. "
+            )
+        # Parse fields: all are ASCII hex numbers
+        c_magic = self.header[0:6].decode()
+        if c_magic != "070702":
+            raise MagicException(
+                f"Unknown or unsupported CPIO format. Our magic: {c_magic}"
+            )
+        # Parse fields (hex strings)
+        c_namesize = int(self.header[94:102], 16)
+        # Read filename (padded to 4 bytes)
+        name = self.file.read(c_namesize)
+        name = name[:-1].decode()
+        if name == "TRAILER!!!":
+            # end of archive
+            return False
+        # Align to 4 bytes
+        pad = (4 - (110 + c_namesize) % 4) % 4
+        self.file.read(pad)
+        self.artifacts.append(name)
+        self._extract_file(dir, name)
+
+         # lets continue
+        return True
+
+    def extract(self, dir):
+        # extract all files from the swu file
+        next_file = True
+        while next_file:
+            next_file = self._extract_next(dir)
+
+        return self.artifacts
-- 
2.47.3