Metadata-Version: 2.4
Name: signing-tool
Version: 3.9.3
Summary: LAAVAT signing-tool: CLI and Python client for HSM-backed firmware, secure-boot image and artifact signing via the LAAVAT PKI & Signing Platform
Author-email: Laavat Oy <contact@laavat.com>
License: Copyright (c) 2026 Laavat Oy
        
        All rights reserved.
        
        This software is the proprietary work of Laavat Oy. Use, reproduction, or distribution of this software, in whole or in part, is prohibited without the express prior written permission of Laavat Oy.
        
        Laavat Oy makes this software available on the Python Package Index (PyPI) so that Laavat customers and evaluators may download, install, and use it with the LAAVAT PKI & Signing Platform. Installing it from PyPI grants only the right to that use; it does not grant any right to modify, redistribute, sublicense, or use the software for any other purpose. All other rights are reserved.
        
        THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL LAAVAT OY BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THIS SOFTWARE OR ITS USE.
        
Project-URL: Homepage, https://www.laavat.io/
Project-URL: Documentation, https://docs.laavat.io/
Project-URL: Reference client, https://docs.laavat.io/democlient/usage/
Project-URL: CRA compliance guide, https://docs.laavat.io/cra-compliance/
Project-URL: Solution guides, https://docs.laavat.io/solutions/
Project-URL: Support, https://www.laavat.io/contact
Keywords: code signing,firmware signing,secure boot,pki,hsm,cloudhsm,device identity,idevid,ldevid,est,mcuboot,rauc,mender,u-boot fit,nxp hab,ahab,xilinx,cyber resilience act,cra,iot security,embedded,supply chain security,signing-tool,laavat
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSES/certifi.txt
License-File: LICENSES/configparser.txt
License-File: LICENSES/cryptography.txt
License-File: LICENSES/jwcrypto.txt
License-File: LICENSES/python-dateutil.txt
License-File: LICENSES/requests.txt
License-File: LICENSES/six.txt
License-File: LICENSES/urllib3.txt
Requires-Dist: certifi>=2017.4.17
Requires-Dist: python-dateutil>=2.1
Requires-Dist: six>=1.10
Requires-Dist: urllib3>=1.23
Requires-Dist: requests>=2.25.1
Requires-Dist: jwcrypto>=1.0
Requires-Dist: configparser>=5.0.2
Requires-Dist: cryptography>=3.4
Provides-Extra: dev
Requires-Dist: autopep8>=1.5.7; extra == "dev"
Requires-Dist: pycodestyle>=2.7.0; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

# LAAVAT signing-tool

> ℹ️ **Verified releases** — used in production by LAAVAT customers. Every release is published with verifiable provenance — see [Verifying the download](#verifying-the-download).

Provided under the [License](#license) below.

LAAVAT is an EU-hosted PKI and code-signing platform for embedded and IoT device manufacturers: HSM-backed secure-boot and firmware signing, device identities (IDevID/LDevID) and CA hierarchies, delivered as a service.

`signing-tool` is the official command-line and Python client for the LAAVAT REST API. It runs in build systems and CI/CD pipelines — not on devices — and signs firmware, boot images and other artifacts with keys that are generated in, and never leave, FIPS-mode AWS CloudHSM. Use it directly in a pipeline or as the basis of your own integration.

## What it signs

The client drives the platform's signing and encryption operations, each covered in the documentation:

- **NXP i.MX secure boot** — HAB (via the NXP CST) and AHAB (via SPSDK). See [CST/HAB signing](https://docs.laavat.io/usage/signing-encryption/cstsigning/).
- **U-Boot FIT images** — [FIT signing](https://docs.laavat.io/usage/signing-encryption/fitsigning/).
- **Xilinx Zynq UltraScale+ boot images** (Bootgen) — [Xilinx signing](https://docs.laavat.io/usage/signing-encryption/xilinxsigning/).
- **MCUboot images** — [MCUboot signing](https://docs.laavat.io/usage/signing-encryption/mcuboot-signing/).
- **RAUC update bundles** — [RAUC signing](https://docs.laavat.io/usage/signing-encryption/raucsigning/).
- **Detached digest signatures** and **OCI/container images** — [signing overview](https://docs.laavat.io/usage/signing-encryption/signing-encryption/).
- **AES image encryption** — [AES image encryption](https://docs.laavat.io/usage/signing-encryption/aes-encryption/).
- **Device identity certificates** (IDevID/LDevID) and **PKI/CA management** — [documentation](https://docs.laavat.io/).

Keys are held in FIPS-mode AWS CloudHSM. The platform supports RSA (2048/3072/4096-bit) and EC (secp256r1/secp384r1/secp521r1) keys with SHA-256 and SHA-512; see [Supported keys and algorithms](https://docs.laavat.io/appendixes/supported-keys/) for the authoritative list.

RSA and EC are fully supported today. ML-DSA (post-quantum) support is designed and ready to build on AWS CloudHSM. If ML-DSA is on your roadmap, [talk to us](https://www.laavat.io/contact).

## Installation

Install from PyPI when released:

```bash
pip install signing-tool
```

## Verifying the download

Releases on PyPI are published with [PEP 740 digital attestations](https://docs.pypi.org/attestations/), signed via [Sigstore](https://www.sigstore.dev/). The attestation provides a publicly verifiable, transparency-logged link proving that a given `signing-tool` artifact was built and published by Laavat's official GitLab CI pipeline for this project — it lets you detect a tampered or substituted artifact before you run it.

You can see the attestation on the release's PyPI page (under the file's "Provenance" / "Verified details"). To verify a downloaded artifact yourself:

```bash
python3 -m pip install pypi-attestations
# Verify a published file directly against PyPI (downloads the file and its
# attestation for you); pass the exact released filename after "pypi:":
python3 -m pypi_attestations verify pypi \
    --repository https://gitlab.com/laavat/laavat-product/architecture/dist \
    pypi:signing_tool-<version>-py3-none-any.whl
```

A successful verification confirms the artifact's provenance (who built it, from which repository). What it does **not** do: it is not a guarantee about the *contents* of the code (it is provenance, not a security audit of the source).

> The `verify` command above works against production PyPI. (Attestation verification of TestPyPI artifacts is not supported by the current `pypi-attestations` release.)

A [CycloneDX](https://cyclonedx.org/) Software Bill of Materials (SBOM) listing the package's dependencies is produced for each release: it is bundled inside the source distribution (`.tar.gz`) as `sbom.json`, and is also published as a per-version build artifact (`signing-tool-sbom.json`) by the release pipeline.

## Quick Start

Display help:

```bash
signing-tool --help
```

### Secure token input

**Do not pass the token literally** (`-t "$TOKEN"`) and **do not store it inline in a config file** — in both cases the secret is exposed (shell history, process listings such as `ps` / `/proc/<pid>/cmdline`, or plaintext on disk). The tool accepts curl-style references that keep the secret out of the command line; in order of preference:

- **`-t @/path/to/tokenfile`** *(recommended)* — read the token from a **file** (the tool warns if the file is group/world-readable; use `chmod 600`):

  ```bash
  signing-tool -c -t @/run/secrets/jwt -a https://app.laavat.io/<CustomerName>/api/v1 product getall
  ```

- **`-t @-`** — read the token from **stdin**:

  ```bash
  printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<CustomerName>/api/v1 product getall
  ```

In a configuration file, reference the token by file instead of inlining it (an inline `token =` still works but is deprecated and warns on use):

```ini
[service]
url = https://app.laavat.io/<CustomerName>/api/v1
token_file = /run/secrets/jwt
```

These forms integrate cleanly with CI secret managers, which expose secrets as files or on stdin. TLS verification is on by default; `--skipssl` disables it and is intended for local development only.

> A leading `@` is interpreted as a stdin/file reference (the curl convention). OAuth2/JWT tokens never start with `@`, but if you ever need a literal token that does, escape it as `\@token`.

### Recommended usage

Supply the token without exposing it on the command line (see [Secure token input](#secure-token-input)), and keep the rest of the settings in a configuration file referenced with `-n`.

A configuration file that references the token by file (the token itself is never stored in the config):

```ini
# test.ini
[service]
url = https://app.laavat.io/<CustomerName>/api/v1
token_file = /run/secrets/jwt
```

```bash
signing-tool -n test.ini product getall
```

Or pass everything on the command line, reading the token from stdin:

```bash
printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<CustomerName>/api/v1 product getall
```

> TLS verification is on by default. `--skipssl` disables it and is for local development only.

### Configuration file

The config file has a single `[service]` section:

| Key | Description |
|-----|-------------|
| `url` | API address, e.g. `https://app.laavat.io/<CustomerName>/api/v1` |
| `token_file` | Path to a file containing the token (preferred — keeps the secret out of the config) |
| `token` | The token. `token = @/path/to/file` reads it from a file; an inline literal token is **deprecated** and warns on use |
| `skipssl` | `True` disables TLS verification (local development only); defaults to `False` |

You can generate a config file with the `config-init` helper. Prefer `-t @/path/to/tokenfile` so it writes a `token_file =` reference (the secret stays out of the config); `-t @-` reads the token from stdin; a literal `-t <token>` is written inline and warns. The file is created owner-only (`0600`) on POSIX.

```bash
config-init -n test.ini -t @/run/secrets/jwt -a https://app.laavat.io/<CustomerName>/api/v1
signing-tool -n test.ini product getall
```

### Quick test

For a quick one-off command without setting up a config file, pipe the token in via stdin (`-t @-`) so it stays out of your shell history and process listings:

```bash
printf '%s' "$TOKEN" | signing-tool -c -t @- -a https://app.laavat.io/<CustomerName>/api/v1 product getall
```

> **Note:** Some commands are interactive and prompt for input: `escrow add`, `group add`, and the `escrow`, `group` and `product` approvals. They cannot read the token from stdin, so do **not** use `-t @-` / `token = @-` with them; supply the token via `-t @/path/to/file` or a `token`/`token_file` entry in a `-n` config file instead. They have no machine-readable form either, and refuse `--json`.

> **Output files:** exports of secret or verification material (`secrets get`/`getkeys`, certificates, CSRs, `product getpubkey`, fusemaps) never overwrite an existing file and fail instead. Re-fetchable outputs — the JSON from `product get -O`/`group get -O` and the signed artifact from `imagesigning get -O` — are refreshed in place on every run. Signed artifacts follow your umask (`0644` under the usual `022`); only exported key material is `0600`.

### What `imagesigning get` verifies

Two different questions, answered by two different digests:

| Check | Answers | Default |
|---|---|---|
| `modifiedSHA256` (automatic) | Did the artifact arrive intact? | Always on; refuses to install on mismatch or when the service reports no digest |
| `originalSHA256` (`--expect-input FILE`) | Was it signed over the bytes I still hold? | Off — you have to name the file |

The first cannot answer the second. Approval is a human step that can take days, and over that gap the build tree moves — a rebuild, another branch, a stale request id in a resume file — so a perfectly intact artifact can carry a signature over an input you no longer have.

```bash
signing-tool -n test.ini imagesigning get -I "$RID" --wait \
    --expect-input u-boot.bin -O u-boot.bin.signed
```

The input is checked before the download, so a mismatch costs nothing. `--expect-input` requires `-I`, and a request reporting no input digest — usually one that is not Ready yet — is an error rather than a silent pass. `--allow-unverified` downgrades both checks to warnings.

## Exit codes and `--json`

The tool is meant to be driven from a script, so an error means a non-zero exit and error text goes to stderr:

| Code | Meaning |
|---|---|
| `0` | Success |
| `1` | Error - API, HTTP, transport, anything unclassified |
| `2` | Usage - a missing or malformed option, or a token that cannot be resolved |
| `3` | Not ready - `--wait` ran out of time with the request still pending. The request is alive; approve it and re-run |
| `4` | The request is `Rejected` or `Failed`, so it can never produce a payload and retrying is pointless |
| `130` | Interrupted with Ctrl-C |

`--json` prints exactly one JSON document on stdout and nothing else, across every command family, so stdout stays parseable:

```bash
# The request id, without scraping it out of prose
ID=$(signing-tool --json -n test.ini imagesigning add SignUBoot \
        -N build-42 -D "u-boot" -P "$PRODUCT" --operid "$OPER" \
        -F u-boot.imx | jq -r .id)
```

## The whole round trip in one command

`imagesigning sign` submits, waits, verifies both digests and installs the result, and records the pending request in `--state-dir` so an interrupted run resumes it instead of submitting again. That last part matters on a four-eyes operation: a resubmitted request spends a second human approval on bytes that did not change.

```bash
signing-tool -n test.ini imagesigning sign \
    --operation SignUBoot -P "$PRODUCT" --operid "$OPER" \
    -F u-boot.imx -O u-boot.imx.signed \
    --state-dir "$HOME/.cache/laavat-requests"
```

Omit `--approver-token` and the command waits for a human to approve; pass it and the same run approves its own request, which is what an unattended pipeline on a single-approver operation wants.

### Operations that sign a set of files

Three operations take a tar.gz rather than a single artifact - OP-TEE trusted applications, RAUC bundles and Xilinx `bootgen` - and multi-stage i.MX boot images take a rendered request alongside the payload. Three verbs cover those, and none of them talks to the service, so they need no credentials:

| Verb | What it does |
|---|---|
| `imagesigning pack` | Packs files into a deterministic tar.gz - same inputs, same bytes, so an unchanged build does not open a new request |
| `imagesigning hab-request` | Renders a `hab-archive-v1` `request.json` for a multi-stage i.MX boot image, reading the stage layout out of the image itself |
| `imagesigning unpack` | Installs the signed members of a result archive over their unsigned originals, bounded and staged so a rejected member cannot leave the tree half replaced |

`imagesigning sign --payload hab-archive-v1` packs the `request.json` named by `-F` together with the binaries it names, and installs the patched member named by `--member` - required when the request patches more than one.

## Requirements

- Python 3.9 or newer
- No separate SDK install. The generated **SigningService** API client is vendored inside the package (`signingtool.SigningService`), so `pip install signing-tool` is the whole installation

## Why it matters for the EU Cyber Resilience Act

The Cyber Resilience Act's Annex I expects connected products to ship with secure boot, a secure update mechanism and strong device identity. LAAVAT provides the key management and signing behind all three; it does not by itself make a product compliant. Start with the [CRA compliance guide](https://docs.laavat.io/cra-compliance/) and the [solution guides](https://docs.laavat.io/solutions/).

## Support and links

- [Documentation](https://docs.laavat.io/)
- [CRA compliance guide](https://docs.laavat.io/cra-compliance/)
- [Solution guides](https://docs.laavat.io/solutions/)
- [FAQ](https://docs.laavat.io/faq/)
- Website: [www.laavat.io](https://www.laavat.io/)
- Contact: [www.laavat.io/contact](https://www.laavat.io/contact) · contact@laavat.com

To report a security issue, email contact@laavat.com.

## License

Proprietary — © Laavat Oy, all rights reserved. Published on PyPI so that Laavat customers and evaluators may install and use it with the LAAVAT PKI & Signing Platform; installing it grants no other rights, and the software is provided "as is", without warranty. See [LICENSE](LICENSE) for the full terms.
