File: 0006-Add-a-sign-subcommand-to-sign-an-existing-swu-file.patch

package info (click to toggle)
swugenerator 0.5-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 452 kB
  • sloc: python: 1,304; sh: 107; makefile: 14
file content (362 lines) | stat: -rw-r--r-- 12,162 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
From 78f78e96102c6b7d56751a68af164e9eaf0c2dfe Mon Sep 17 00:00:00 2001
From: INgo Rah <ingo.rah@linutronix.de>
Date: Tue, 7 Oct 2025 16:39:00 +0200
Subject: [PATCH] Add a sign subcommand to sign an existing swu file

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

Signed-off-by: INgo Rah <ingo.rah@linutronix.de>
Forwarded: not-needed
Origin: upstream
---
 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 output.swu -a . -s sw-description.in create
+    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
+        artifacts = self.cpio_in.extract(self.temp.name)
+
+        # call signing to generate the sig file
+        if self.signtool:
+            sw_desc_in = Path(self.temp.name) / artifacts[0]
+            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:
+                logging.info("resigning a signed file")
+
+        for name in artifacts:
+            path = Path(self.temp.name) / name
+            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
+            logging.info("flush called on read only file")
+
         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