Signstar
This project provides tools and documentation for running a generic signing enclave with the help of one or more Nitrokey NetHSM devices.
Raw cryptographic signatures and OpenPGP data signatures are supported.
Components
Signstar consists of several loosely coupled components, some of which are used in conjunction with one another.
- nethsm: A library to provide interaction with the Nitrokey NetHSM to applications
- nethsm-cli: A dedicated commandline interface to the Nitrokey NetHSM, akin to Nitrokey's pynitrokey, useful for general purpose, interactive use of the HSM
- signstar-configure-build: A commandline interface for the configuration of Signstar system during build-time
- signstar-sign: An executable, that allows signing of messages with the help of a Nitrokey NetHSM, based on a configuration (#34)
- signstar-configure: An executable, that allows non-interactive configuration of a Nitrokey NetHSM based on a configuration (#48)
- [signstar-request-signature]: An executable, run on a client host, that prepares data to be signed and retrieves a signature for it from a Signstar setup
Requirements
A Signstar setup requires a TPM-2.0-enabled host, allowing to run SignstarOS which provides a read-only root filesystem and an encrypted /var
partition for its state.
This signing service host is connected to one or more Nitrokey NetHSM devices over an otherwise secluded network and exposes signstar-sign to clients of the signing service.
Clients use signstar-request-signature to connect to a Signstar setup and retrieve a signature for a provided payload.
--- title: Simplified overview of a Signstar setup --- sequenceDiagram participant C as Client participant S as Signstar participant N as NetHSM Note over S: pair of Signstar credentials Note over N: pair of NetHSM credentials S ->> N: HSM is configured using *signstar-configure* C ->>+ S: User "A" requests signature using *signstar-request-signature* S ->> S: Host user "A" is mapped to HSM operator user "X" by *signstar-sign* S ->> N: Signature is requested using operator user "X" by *signstar-sign* N ->> S: Raw cryptographic signature is received by *signstar-sign* S ->>- C: Signature for user "A" is returned by *signstar-sign*
Further details on the setup, as well as the threat model that the setup operates under can be found in the design documentation.
Packaging
The justfile
contains recipes for generating integration useful for packaging:
just generate shell_completions nethsm-cli
generates shell completions for nethsm-cli to$CARGO_TARGET_DIR/output/shell_completions/
(or to$PWD/output/shell_completions/
if$CARGO_TARGET_DIR
is unset)just generate manpages nethsm-cli
generates man pages for nethsm-cli to$CARGO_TARGET_DIR/output/manpages/
(or to$PWD/output/manpages/
if$CARGO_TARGET_DIR
is unset)
The target directory is created automatically.
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
NetHSM
A high-level library abstracting the use of the nethsm-sdk-rs library.
The NetHSM is a hardware appliance, that serves as secure store for cryptographic keys. With the help of a REST API it is possible to communicate with the device (as well as the official nethsm container) for setup and various cryptographic actions.
The nethsm-sdk-rs library is auto-generated using openapi-generator. This leads to a broad API surface with sparse documentation, that this crate attempts to rectify with the help of a central struct used for authentication setup and communication.
Documentation
- https://signstar.archlinux.page/rustdoc/nethsm/ for development version of the crate
- https://docs.rs/nethsm/latest/nethsm/ for released versions of the crate
Testing
This library is integration tested against Nitrokey's official nethsm container. To run these long running tests a podman installation is required. The tests handle the creation and teardown of containers as needed.
cargo test --all -- --ignored
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.7.3] - 2024-12-13
Added
- Add
NetHsm::openpgp_sign_state
[0.7.2] - 2024-12-08
Added
- Add
validate_backup
to validate NetHSM backups - Add
Passphrase::expose_borrowed
Other
- (README) Add links to latest (un)released crate documentation
- (deps) Update dependencies to fix security issues
- (cargo) Consolidate dependencies with workspace dependencies
[0.7.1] - 2024-11-27
Other
- Update libc crate as the previously used version was yanked
[0.7.0] - 2024-11-26
Added
- Introduce
nethsm-tests
for easier integration testing - Derive
Copy
fornethsm::UserRole
- Implement
AsRef<str>
forNamespaceId
to return string slice - Rely on
serde
'sinto
andtry_from
attributes forKeyId
- Rely on
serde
'sinto
andtry_from
attributes forUserId
- Add
SigningKeySetup
struct to track key setups for signing - Add
CryptographicKeyContext
to track a key's crypto context - [breaking] Provide version with
OpenPgpVersion
when creating OpenPGP certificate - [breaking] Use
OpenPgpUserId
for User ID when creating OpenPGP certificate - Add
OpenPgpVersion
to track OpenPGP version - Add
OpenPgpUserId
andOpenPgpUserIdList
for OpenPGP User IDs - Add function to validate
SignatureType
against other key data - Derive
Deserialize
andSerialize
forSignatureType
- Derive
Hash
,Eq
andPartialEq
for some types
Fixed
- (deps) update rust crate picky-asn1-x509 to 0.14.0
- Properly truncate digests for ECDSA signing schemes
- Make serde use
TryFrom<String>
for deserialization - (deps) Update dependencies removing yanked crate
- (deps) Migrate to rpgp v0.14.0
- Print more details on errors
- (deps) Update
secrecy
to version0.10.2
- (deps) update rust crate strum to 0.26.0
- Read real value of the RSA modulus instead of using a hardcoded one
Other
- Consolidate contributing and licensing information
- (deps) Update dependencies and fix license ID
- (cargo) Move same-crate, feature-incompatible crates to workspace
- (cargo) Move common dependencies to workspace dependencies
- (cargo) Move shared dependencies to workspace dependencies
- (cargo) Move package metadata to workspace
- Use correct link to upstream Error type in
nethsm_sdk::Message
- Add docs for and spacing between
nethsm::key::Error
variants - (deps) update rust crate rstest to 0.23.0
- Refactor error cases to use
Error::UnsupportedKeyFormat
- (deps) update rust crate rustainers to 0.13.0
[0.6.0] - 2024-09-11
Added
- Ensure valid bit length when generating RSA TLS keys
- Ensure valid bit length for block cipher and RSA keys
Fixed
- Adjust broken links in
KeyId
documentation
Other
- [breaking] Introduce
nethsm::KeyId
type - Improve documentation for Error variant
Error::Key
- [breaking] Remove unused Error variant
Error::KeyData
- Provide function to check KeyType - KeyMechanism compatibility
- [breaking] Use
u32
instead ofi32
for ports and lengths
[0.5.0] - 2024-09-06
Fixed
- (deps) [breaking] update rust crate rustls-native-certs to 0.8.0
- Pad secret keys with zeros before sending them to NetHSM
- Use correct function for constructing MPIs
Other
- Make
SignatureType
a copy type - Import keys of all supported types
- (Cargo.toml) Remove duplicate rand development dependency
- Replace ed25519-compact with ed25519-dalek in all tests
[0.4.0] - 2024-08-30
Added
- Validate namespace access using
UserId
method - Add
UserId
andNamespaceId
types for handling User IDs - Add facilities for namespace administration
- Add facilities for OpenPGP certificate creation and signing
- Add support for PEM-encoded private keys in
key import
Fixed
- (Cargo.toml) Have cargo-machete ignore the md-5 dependency
- Do not require
Administrator
role when executingunlock
- Adjust the test to remove credentials as they are not needed in
unlock
- Adjust functions as
update_file
does not need to be async - Reduce the number of direct dependencies
- (README.md) Remove license attribution as it is in reuse config
Other
- Remove warning from
NetHsm::restore
method - Adapt existing documentation for the use of namespaces
- Adapt and extend tests for use of namespaces
- [breaking] Move
Credentials
andPassphrase
to user module - Adjust information on output format on public key retrieval
- Adjust documentation on authentication for unlock call
- Split tests for retrieval of TLS public key
- Fix user role requirements for backup retrieval
- (deps) update rust crate rstest to 0.22.0
- Pin nethsm container image version to c16fe4ed
- (nethsm/tests/config.rs) Fix create_backup test
- Simplify license attribution setup for entire project
[0.3.0] - 2024-07-12
Added
- Add functions to return key type specific lists of key mechanisms
- Extend FromStr for ConnectionSecurity to cover case sensitivity
- Derive strum::IntoStaticStr for various types
- Derive strum::EnumIter for various types
- [breaking] Assemble connection configuration only when it is needed
- [breaking] Use secrecy for passphrase zeroing
- Publicly re-export all required nethsm-sdk-rs models
Fixed
- (nethsm/src/nethsm_sdk.rs) Fix spelling mistake in error message
Other
- Switch rustls's crypto provider to ring
- Describe output data types for signing functionality
- (Cargo.toml) Remove strum_macros as it is unused
- Assemble user agent string from crate data
- Make container setup with fixtures more robust
- Use TestResult for all doc tests
[0.2.0] - 2024-05-10
Added
- [breaking] Use PrivateKeyImport for import of private key material
- Provide own LogLevel
- Provide own EncryptMode
- Provide own DecryptMode
- Provide own KeyMechanism
- Provide own TlsKeyType
- Provide own KeyType
- Provide own UserRole
- Derive Clone for SignatureType
- Derive strum::{Display,EnumString} for BootMode
- Add handling of /config/tls/public.pem endpoint
- Add handling of /system/info endpoint
- Add handling of /health/ready endpoint
- Add handling of /health/alive endpoint
Fixed
- Simplify use of strum macros for SignatureType
Other
- Rely on global use of serde for Message
- Extend KeyType to validate a list of KeyMechanisms
- Add dedicated tests for /health/state endpoint handling
[0.1.1] - 2024-05-04
Added
- Use custom url type to validate the connection to a NetHSM
- Implement Serialize/Deserialize for ConnectionSecurity
Other
- Use re-exported facilities instead of nethsm_sdk_rs directly
- (README.md) Adjust test setup for new podman requirements
- (container) Use rustainers instead of podman-api
[0.1.0] - 2024-03-22
Added
- Add library for controlling a NetHSM
NetHSM backup
A library to parse, decrypt, validate and browse NetHSM backups.
Documentation
- https://signstar.archlinux.page/rustdoc/nethsm_backup/ for development version of the crate
- https://docs.rs/nethsm_backup/latest/nethsm_backup/ for released versions of the crate
Examples
Listing all fields in a backup file:
fn main() -> testresult::TestResult { use std::collections::HashMap; use nethsm_backup::Backup; let backup = Backup::parse(std::fs::File::open("tests/nethsm.backup-file.bkp")?)?; let decryptor = backup.decrypt(b"my-very-unsafe-backup-passphrase")?; assert_eq!(decryptor.version()?, [0]); for item in decryptor.items_iter() { let (key, value) = item?; println!("Found {key} with value: {value:X?}"); } Ok(()) }
Dumping the value of one specified field (here /config/version
):
fn main() -> testresult::TestResult { use std::collections::HashMap; use nethsm_backup::Backup; let backup = Backup::parse(std::fs::File::open("tests/nethsm.backup-file.bkp")?)?; let decryptor = backup.decrypt(b"my-very-unsafe-backup-passphrase")?; assert_eq!(decryptor.version()?, [0]); for (key, value) in decryptor .items_iter() .flat_map(|item| item.ok()) .filter(|(key, _)| key == "/config/version") { println!("Found {key} with value: {value:X?}"); } Ok(()) }
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.1.0] - 2024-12-08
Added
- Add
nethsm-backup
library
Other
- (README) Add links to latest (un)released crate documentation
- (cargo) Consolidate dependencies with workspace dependencies
NetHSM Command Line Interface
A command line interface (CLI) for the Nitrokey NetHSM based on the nethsm crate.
Documentation
- https://signstar.archlinux.page/rustdoc/nethsm_cli/ for development version of the crate
- https://docs.rs/nethsm_cli/latest/nethsm_cli/ for released versions of the crate
Installation
This crate can be installed using cargo
:
cargo install nethsm-cli
Afterwards the nethsm
executable is available.
It is recommended to refer to the extensive --help
output of the executable and its subcommands.
Usage
The following assumes a recent version of openssl
and podman
.
Start a test container
podman run --rm -ti --network=pasta:-t,auto,-u,auto,-T,auto,-U,auto docker.io/nitrokey/nethsm:testing
Configuration file
The configuration file uses the TOML format.
By default an Operating System specific, well-defined configuration file location is chosen.
Using -c
/ --config
/ the NETHSM_CONFIG
environment variable it is possible to provide a custom configuration file location.
# use a custom, temporary directory for all generated files
nethsm_tmpdir="$(mktemp --directory --suffix '.nethsm-test')"
# set a custom, temporary configuration file location
export NETHSM_CONFIG="$(mktemp --tmpdir="$nethsm_tmpdir" --suffix '-nethsm.toml' --dry-run)"
To be able to interact with a NetHSM (or the testing container), each device must be added to the configuration file.
# add the container using unsafe TLS connection handling for testing
nethsm env add device --label test https://localhost:8443/api/v1 Unsafe
If only one device environment is configured, it is used by default when issuing nethsm
commands.
If more than one environment is configured, the target device must be selected using the global -l
/ --label
option.
The handling of credentials is flexible: Credentials can be stored in the configuration file with or without passphrases or not at all. If credentials are not configured, they are prompted for interactively.
# prepare a temporary passphrase file for the initial admin user passphrase
nethsm_admin_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.admin-passphrase.txt')"
export NETHSM_PASSPHRASE_FILE="$nethsm_admin_passphrase_file"
printf 'my-very-unsafe-admin-passphrase' > "$NETHSM_PASSPHRASE_FILE"
# add the default admin user credentials
nethsm env add credentials admin Administrator
Provisioning
Before using a device for the first time, it must be provisioned. This includes setting the passphrase for the initial "admin" user, the unlock passphrase and the system time of the device.
# prepare a temporary passphrase file for the initial unlock passphrase
export NETHSM_UNLOCK_PASSPHRASE_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.unlock-passphrase.txt')"
printf 'my-very-unsafe-unlock-passphrase' > "$NETHSM_UNLOCK_PASSPHRASE_FILE"
# reuse the initial admin passphrase
export NETHSM_ADMIN_PASSPHRASE_FILE="$nethsm_admin_passphrase_file"
nethsm provision
Users
Each user may be in exactly one role ("Administrator", "Operator", "Metrics" or "Backup"). Users either exist system-wide or in a "Namespace". Users in a Namespace only have access to users and keys in their own Namespace and are not able to interact with system-wide facilities. System-wide users on the other hand are not able to access keys or manipulate users in a Namespace, but can interact with other system-wide facilities, as well as system-wide users and keys.
- "Administrator": for adjusting system configuration, managing users and keys (may exist in a Namespace or system-wide)
- R-Administrator: a system-wide Administrator which is able to interact with all system-wide facilities, as well as managing system-wide users and keys
- N-Administrator: a namespace Administrator, which is only able to operate on users and keys in their own namespace
- "Operator": for using cryptographic keys and getting random bytes (may exist in a Namespace or system-wide)
- "Metrics": for retrieving metrics of a device (may only exist system-wide)
- "Backup": for creating and downloading backups of a device (may only exist system-wide)
System-wide and namespace users are easily distinguishable: While system-wide user names consist only of characters in the set [a-z0-9]
(e.g. admin1
), namespace user names consist of characters in the set [a-z0-9~]
and start with the namespace name (e.g. namespace1~admin1
).
nethsm_admin1_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.admin1-passphrase.txt')"
printf 'my-very-unsafe-admin1-passphrase' > "$nethsm_admin1_passphrase_file"
nethsm_operator1_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.operator1-passphrase.txt')"
printf 'my-very-unsafe-operator1-passphrase' > "$nethsm_operator1_passphrase_file"
nethsm_backup1_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.backup1-passphrase.txt')"
printf 'my-very-unsafe-backup1-passphrase' > "$nethsm_backup1_passphrase_file"
nethsm_metrics1_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.metrics1-passphrase.txt')"
printf 'my-very-unsafe-metrics1-passphrase' > "$nethsm_metrics1_passphrase_file"
nethsm_namespace1_admin1_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.namespace1-admin1-passphrase.txt')"
printf 'my-very-unsafe-namespace1-admin1-passphrase' > "$nethsm_namespace1_admin1_passphrase_file"
nethsm_namespace1_operator1_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.namespace1-operator1-passphrase.txt')"
printf 'my-very-unsafe-namespace1-operator1-passphrase' > "$nethsm_namespace1_operator1_passphrase_file"
# we create a user in each role and add the credentials including passphrases to the configuration
# NOTE: this is for testing purposes! passphrases stored in configuration files can easily be retrieved!
export NETHSM_PASSPHRASE_FILE="$nethsm_admin1_passphrase_file"
nethsm user add "Some Admin1" Administrator admin1
nethsm env add credentials admin1 Administrator
export NETHSM_PASSPHRASE_FILE="$nethsm_operator1_passphrase_file"
nethsm user add "Some Operator1" Operator operator1
nethsm env add credentials operator1 Operator
export NETHSM_PASSPHRASE_FILE="$nethsm_backup1_passphrase_file"
nethsm user add "Some Backup1" Backup backup1
nethsm env add credentials backup1 Backup
export NETHSM_PASSPHRASE_FILE="$nethsm_metrics1_passphrase_file"
nethsm user add "Some Metrics1" Metrics metrics1
nethsm env add credentials metrics1 Metrics
# we also create an admin for a namespace "namespace1" and create that namespace
export NETHSM_PASSPHRASE_FILE="$nethsm_namespace1_admin1_passphrase_file"
nethsm user add "Namespace1 Admin1" Administrator namespace1~admin1
nethsm env add credentials namespace1~admin1 Administrator
nethsm --user admin namespace add namespace1
# now the N-Administrator can create further users in that namespace
export NETHSM_PASSPHRASE_FILE="$nethsm_operator1_passphrase_file"
nethsm --user namespace1~admin1 user add "Namespace1 Operator1" Operator namespace1~operator1
nethsm env add credentials namespace1~operator1 Operator
# NOTE: from now on we have to be *specific* about which Administrator and which Operator user to use for each action as we have multiple and `nethsm` opportunistically selects the first it finds!
# show the configured environments in the configuration file
nethsm env list
The user names and accompanying information can be queried:
# the R-Administrator can see all users
while read -r user; do
nethsm --user admin1 user get "$user"
done < <(nethsm --user admin1 user list)
# the N-Administrator can only see the users in its own namespace
while read -r user; do
nethsm --user namespace1~admin1 user get "$user"
done < <(nethsm --user namespace1~admin1 user list)
Tags for users can only be created once keys with those tags exists.
Keys
Keys on the device are managed using users in the "Administrator" role. Depending on restrictions (tags), the keys may then be used by users in the "Operator" role.
NOTE: Keys created by an N-Administrator are only visible within their namespace and only available to Operator users in that namespace.
Generating keys
Below, we are generating keys of all available types (Curve25519, EcP224, EcP256, EcP384, EcP521, Generic and Rsa). When generating a key, the unique ID for it may be set manually (else it is auto-generated). Tags, which later on allow users access to the keys may also be set during key generation.
Note that some keys require to set the key bit length (i.e. Generic and Rsa).
# keys created by the R-Administrator are only available to system-wide Operator users!
nethsm --user admin1 key generate --key-id signing1 --tags tag1 Curve25519 EdDsaSignature
nethsm --user admin1 key generate --key-id signing2 --tags tag2 EcP224 EcdsaSignature
nethsm --user admin1 key generate --key-id signing3 --tags tag2 EcP256 EcdsaSignature
nethsm --user admin1 key generate --key-id signing4 --tags tag2 EcP384 EcdsaSignature
nethsm --user admin1 key generate --key-id signing5 --tags tag2 EcP521 EcdsaSignature
nethsm --user admin1 key generate --key-id encdec1 --tags tag3 --length 128 Generic AesDecryptionCbc AesEncryptionCbc
nethsm --user admin1 key generate --key-id dec1 --tags tag4 --length 2048 Rsa RsaDecryptionPkcs1
nethsm --user admin1 key generate --key-id signing6 --tags tag5 --length 2048 Rsa RsaSignaturePssSha512
nethsm --user admin1 key generate --key-id signing8 --tags tag6 --length 2048 Rsa RsaSignaturePkcs1
# keys created by the N-Administrator are only available to Operator users in the same namespace!
nethsm --user namespace1~admin1 key generate --key-id signing1 --tags tag1 Curve25519 EdDsaSignature
nethsm --user namespace1~admin1 key generate --key-id signing2 --tags tag2 EcP224 EcdsaSignature
nethsm --user namespace1~admin1 key generate --key-id signing3 --tags tag2 EcP256 EcdsaSignature
nethsm --user namespace1~admin1 key generate --key-id signing4 --tags tag2 EcP384 EcdsaSignature
nethsm --user namespace1~admin1 key generate --key-id signing5 --tags tag2 EcP521 EcdsaSignature
nethsm --user namespace1~admin1 key generate --key-id encdec1 --length 128 --tags tag3 Generic AesDecryptionCbc AesEncryptionCbc
nethsm --user namespace1~admin1 key generate --key-id dec1 --length 2048 --tags tag4 Rsa RsaDecryptionPkcs1
nethsm --user namespace1~admin1 key generate --key-id signing6 --length 2048 --tags tag5 Rsa RsaSignaturePssSha512
nethsm --user namespace1~admin1 key generate --key-id signing8 --length 2048 --tags tag6 Rsa RsaSignaturePkcs1
All key IDs on the device and info about them can be listed:
# R-Administrators can only see system-wide keys
while read -r key; do
nethsm key get "$key"
done < <(nethsm --user admin1 key list)
# N-Administrators can only see keys in their own namespace
while read -r key; do
nethsm key get "$key"
done < <(nethsm --user namespace1~admin1 key list)
Importing keys
Keys can also be imported:
ed25519_cert_pem="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.ed25519_cert.pem')"
ed25519_cert_der="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.ed25519_cert.pkcs8.der')"
openssl genpkey -algorithm ed25519 -out "$ed25519_cert_pem"
openssl pkcs8 -topk8 -inform pem -in "$ed25519_cert_pem" -outform der -nocrypt -out "$ed25519_cert_der"
# import supports PKCS#8 private key in ASN.1 DER-encoded format, by default
nethsm --user admin1 key import --key-id signing7 Curve25519 "$ed25519_cert_der" EdDsaSignature
# however, importing a PKCS#8 private key in ASN.1 PEM-encoded format is supported, too
nethsm --user admin1 key import --format PEM --key-id signing9 Curve25519 "$ed25519_cert_pem" EdDsaSignature
# forgot to set a tag for key signing7 so that operator1 has access!
nethsm --user admin1 key tag signing7 tag1
# show information about the new key
nethsm --user operator1 key get signing7
# the same for namespace1
# import supports PKCS#8 private key in ASN.1 DER-encoded format, by default
nethsm --user namespace1~admin1 key import --key-id signing7 Curve25519 "$ed25519_cert_der" EdDsaSignature
# however, importing a PKCS#8 private key in ASN.1 PEM-encoded format is supported, too
nethsm --user namespace1~admin1 key import --format PEM --key-id signing9 Curve25519 "$ed25519_cert_pem" EdDsaSignature
# forgot to set a tag for key signing7 so that namespace1~operator1 has access!
nethsm --user namespace1~admin1 key tag signing7 tag1
# show information about the new key
nethsm --user namespace1~operator1 key get signing7
Access to keys
To provide access to keys for users, the users have to be tagged with the same tags as the keys.
# an R-Administrator can only modify system-wide users
nethsm --user admin1 user tag operator1 tag1
# an N-Administrator can only modify namespace users
nethsm --user namespace1~admin1 user tag namespace1~operator1 tag1
Signing messages
export NETHSM_KEY_SIGNATURE_OUTPUT_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.readme.signature.sig')"
export NETHSM_KEY_PUBKEY_OUTPUT_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.pubkey.pem')"
message_digest="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.message.dgst')"
# if we add the tag2 tag for operator1 it is able to use signing{2-5}
nethsm --user admin1 user tag operator1 tag2
# if we add the tag5 tag for operator1 it is able to use signing6
nethsm --user admin1 user tag operator1 tag5
nethsm --user admin1 user tag operator1 tag6
# we made the same tags available in namespace1, so the examples work similarly
# if we add the tag2 tag for namespace1~operator1 it is able to use signing{2-5}
nethsm --user namespace1~admin1 user tag namespace1~operator1 tag2
# if we add the tag5 tag for namespace1~operator1 it is able to use signing6
nethsm --user namespace1~admin1 user tag namespace1~operator1 tag5
nethsm --user namespace1~admin1 user tag namespace1~operator1 tag6
# create a signature with each key type
nethsm --user operator1 key sign --force signing1 EdDsa README.md
nethsm --user operator1 key public-key --force signing1
openssl pkeyutl -verify -in README.md -rawin -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user operator1 key sign --force signing2 EcdsaP224 README.md
nethsm --user operator1 key public-key --force signing2
openssl dgst -sha224 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user operator1 key sign --force signing3 EcdsaP256 README.md
nethsm --user operator1 key public-key --force signing3
openssl dgst -sha256 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user operator1 key sign --force signing4 EcdsaP384 README.md
nethsm --user operator1 key public-key --force signing4
openssl dgst -sha384 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user operator1 key sign --force signing5 EcdsaP521 README.md
nethsm --user operator1 key public-key --force signing5
openssl dgst -sha512 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user operator1 key sign --force signing6 PssSha512 README.md
nethsm --user operator1 key public-key --force signing6
openssl dgst -sha512 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin -pkeyopt rsa_padding_mode:pss -pkeyopt digest:sha512 -pkeyopt rsa_pss_saltlen:-1
nethsm --user operator1 key sign --force signing7 EdDsa README.md
nethsm --user operator1 key public-key --force signing7
openssl pkeyutl -verify -in README.md -rawin -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
# the same of course also works in a namespace!
nethsm --user namespace1~operator1 key sign --force signing1 EdDsa README.md
nethsm --user namespace1~operator1 key public-key --force signing1
openssl pkeyutl -verify -in README.md -rawin -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user namespace1~operator1 key sign --force signing2 EcdsaP224 README.md
nethsm --user namespace1~operator1 key public-key --force signing2
openssl dgst -sha224 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user namespace1~operator1 key sign --force signing3 EcdsaP256 README.md
nethsm --user namespace1~operator1 key public-key --force signing3
openssl dgst -sha256 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user namespace1~operator1 key sign --force signing4 EcdsaP384 README.md
nethsm --user namespace1~operator1 key public-key --force signing4
openssl dgst -sha384 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user namespace1~operator1 key sign --force signing5 EcdsaP521 README.md
nethsm --user namespace1~operator1 key public-key --force signing5
openssl dgst -sha512 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
nethsm --user namespace1~operator1 key sign --force signing6 PssSha512 README.md
nethsm --user namespace1~operator1 key public-key --force signing6
openssl dgst -sha512 -binary README.md > "$message_digest"
openssl pkeyutl -verify -in "$message_digest" -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin -pkeyopt rsa_padding_mode:pss -pkeyopt digest:sha512 -pkeyopt rsa_pss_saltlen:-1
nethsm --user namespace1~operator1 key sign --force signing7 EdDsa README.md
nethsm --user namespace1~operator1 key public-key --force signing7
openssl pkeyutl -verify -in README.md -rawin -sigfile "$NETHSM_KEY_SIGNATURE_OUTPUT_FILE" -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin
# if we remove the tag5 tag for operator1 it is no longer able to use signing6
nethsm --user admin1 user untag operator1 tag5
# analogous: if we remove the tag5 tag for namespace1~operator1 it is no longer able to use signing6
nethsm --user namespace1~admin1 user untag namespace1~operator1 tag5
OpenPGP
The CLI can also create OpenPGP certificates for keys stored in the HSM:
export GNUPGHOME="$(mktemp --directory --tmpdir="$nethsm_tmpdir" --suffix 'gnupghome')"
export NETHSM_KEY_CERT_OUTPUT_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.openpgp-cert.pgp')"
nethsm --user admin1 --user operator1 openpgp add --can-sign signing1 "Test signing1 key <test@example.org>"
nethsm --user operator1 key cert get --force signing1
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing1 key"
nethsm --user admin1 --user operator1 openpgp add signing3 "Test signing3 key <test@example.org>"
nethsm --user operator1 key cert get --force signing3
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing3 key"
nethsm --user admin1 --user operator1 openpgp add signing4 "Test signing4 key <test@example.org>"
nethsm --user operator1 key cert get --force signing4
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing4 key"
nethsm --user admin1 --user operator1 openpgp add signing5 "Test signing5 key <test@example.org>"
nethsm --user operator1 key cert get --force signing5
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing5 key"
nethsm --user admin1 --user operator1 openpgp add signing8 "Test signing8 key <test@example.org>"
nethsm --user operator1 key cert get --force signing8
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing8 key"
# all of this works with our namespaced keys as well of course!
nethsm --user namespace1~admin1 --user namespace1~operator1 openpgp add --can-sign signing1 "Test signing1 key <test@example.org>"
nethsm --user namespace1~operator1 key cert get --force signing1
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing1 key"
nethsm --user namespace1~admin1 --user namespace1~operator1 openpgp add signing3 "Test signing3 key <test@example.org>"
nethsm --user namespace1~operator1 key cert get --force signing3
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing3 key"
nethsm --user namespace1~admin1 --user namespace1~operator1 openpgp add signing4 "Test signing4 key <test@example.org>"
nethsm --user namespace1~operator1 key cert get --force signing4
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing4 key"
nethsm --user namespace1~admin1 --user namespace1~operator1 openpgp add signing5 "Test signing5 key <test@example.org>"
nethsm --user namespace1~operator1 key cert get --force signing5
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing5 key"
nethsm --user namespace1~admin1 --user namespace1~operator1 openpgp add signing8 "Test signing8 key <test@example.org>"
nethsm --user namespace1~operator1 key cert get --force signing8
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing8 key"
Importing new keys:
export NETHSM_OPENPGP_TSK_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.openpgp-private-key.tsk')"
rsop generate-key --no-armor --signing-only "Test signing10 key <test@example.org>" > "$NETHSM_OPENPGP_TSK_FILE"
nethsm --user admin1 openpgp import --key-id signing10 --tags tag1
# openpgp import automatically stores the certificate so it can be fetched
nethsm --user operator1 key cert get --force signing10
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing10 key"
nethsm --user namespace1~admin1 openpgp import --key-id signing10 --tags tag1
# openpgp import automatically stores the certificate so it can be fetched
nethsm --user namespace1~operator1 key cert get --force signing10
gpg --import "$NETHSM_KEY_CERT_OUTPUT_FILE"
sq inspect "$NETHSM_KEY_CERT_OUTPUT_FILE" | grep "Test signing10 key"
Signing messages:
export NETHSM_OPENPGP_SIGNATURE_OUTPUT_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.openpgp-message.txt.sig')"
export NETHSM_OPENPGP_SIGNATURE_MESSAGE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.openpgp-message.txt')"
printf "I like strawberries\n" > "$NETHSM_OPENPGP_SIGNATURE_MESSAGE"
for key in signing1 signing3 signing4 signing5 signing8 signing10; do
nethsm --user operator1 openpgp sign --force "$key"
gpg --verify "$NETHSM_OPENPGP_SIGNATURE_OUTPUT_FILE" "$NETHSM_OPENPGP_SIGNATURE_MESSAGE"
nethsm --user operator1 key cert get --force "$key"
rsop verify "$NETHSM_OPENPGP_SIGNATURE_OUTPUT_FILE" "$NETHSM_KEY_CERT_OUTPUT_FILE" < "$NETHSM_OPENPGP_SIGNATURE_MESSAGE"
nethsm --user namespace1~operator1 openpgp sign --force "$key"
gpg --verify "$NETHSM_OPENPGP_SIGNATURE_OUTPUT_FILE" "$NETHSM_OPENPGP_SIGNATURE_MESSAGE"
nethsm --user namespace1~operator1 key cert get --force "$key"
rsop verify "$NETHSM_OPENPGP_SIGNATURE_OUTPUT_FILE" "$NETHSM_KEY_CERT_OUTPUT_FILE" < "$NETHSM_OPENPGP_SIGNATURE_MESSAGE"
done
signstar-request-signature "$NETHSM_OPENPGP_SIGNATURE_MESSAGE" | tee "${NETHSM_OPENPGP_SIGNATURE_MESSAGE}.json"
nethsm openpgp sign-state --force "signing1" "${NETHSM_OPENPGP_SIGNATURE_MESSAGE}.json"
gpg --verify "$NETHSM_OPENPGP_SIGNATURE_OUTPUT_FILE" "$NETHSM_OPENPGP_SIGNATURE_MESSAGE"
sq toolbox packet dump "$NETHSM_OPENPGP_SIGNATURE_OUTPUT_FILE"
sha512sum "$NETHSM_OPENPGP_SIGNATURE_MESSAGE"
jq < "${NETHSM_OPENPGP_SIGNATURE_MESSAGE}.json"
Encrypting messages
Messages can be encrypted using keys that offer the key mechanisms for this operation.
message="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.message.txt')"
printf "Hello World! This is a message!!" > "$message"
export NETHSM_KEY_ENCRYPT_IV="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.iv.txt')"
printf "This is unsafe!!" > "$NETHSM_KEY_ENCRYPT_IV"
export NETHSM_KEY_ENCRYPT_OUTPUT="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.symmetric-encrypted-message.txt.enc')"
# the initialization vector for decryption must be the same
export NETHSM_KEY_DECRYPT_IV="$NETHSM_KEY_ENCRYPT_IV"
export NETHSM_KEY_DECRYPT_OUTPUT="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.decrypted-message.txt')"
# we need to provide access to the key by tagging the user
nethsm --user admin1 user tag operator1 tag3
# let's use our symmetric encryption key to encrypt a message
nethsm --user operator1 key encrypt --force encdec1 "$message"
# now let's decrypt the encrypted message again
nethsm --user operator1 key decrypt --force encdec1 "$NETHSM_KEY_ENCRYPT_OUTPUT" AesCbc
cat "$NETHSM_KEY_DECRYPT_OUTPUT"
[[ "$(b2sum "$NETHSM_KEY_DECRYPT_OUTPUT" | cut -d ' ' -f1)" == "$(b2sum "$message" | cut -d ' ' -f1)" ]]
# this works analogously in a namespace
# we need to provide access to the key by tagging the user
nethsm --user namespace1~admin1 user tag namespace1~operator1 tag3
# let's use our symmetric encryption key to encrypt a message
nethsm --user namespace1~operator1 key encrypt --force encdec1 "$message"
# now let's decrypt the encrypted message again
nethsm --user namespace1~operator1 key decrypt --force encdec1 "$NETHSM_KEY_ENCRYPT_OUTPUT" AesCbc
cat "$NETHSM_KEY_DECRYPT_OUTPUT"
[[ "$(b2sum "$NETHSM_KEY_DECRYPT_OUTPUT" | cut -d ' ' -f1)" == "$(b2sum "$message" | cut -d ' ' -f1)" ]]
The same works for asymmetric keys as well:
# unset the initialization vectors as we do not need them for this
unset NETHSM_KEY_DECRYPT_IV NETHSM_KEY_ENCRYPT_IV
asymmetric_enc_message="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.asymmetric-encrypted-message.txt.enc')"
# we need to provide access to the key by tagging the user
nethsm --user admin1 user tag operator1 tag4
# retrieve the public key of the key to use (and overwrite any previously existing)
nethsm --user operator1 key public-key --force dec1
# encrypt the previous message
openssl pkeyutl -encrypt -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin -in "$message" -out "$asymmetric_enc_message"
# decrypt the asymmetrically encrypted message and replace any existing output
nethsm --user operator1 key decrypt --force dec1 "$asymmetric_enc_message" Pkcs1
cat "$NETHSM_KEY_DECRYPT_OUTPUT"
[[ "$(b2sum "$NETHSM_KEY_DECRYPT_OUTPUT" | cut -d ' ' -f1)" == "$(b2sum "$message" | cut -d ' ' -f1)" ]]
# this works analogously in a namespace
# we need to provide access to the key by tagging the user
nethsm --user namespace1~admin1 user tag namespace1~operator1 tag4
# retrieve the public key of the key to use (and overwrite any previously existing)
nethsm --user namespace1~operator1 key public-key --force dec1
# encrypt the previous message
openssl pkeyutl -encrypt -inkey "$NETHSM_KEY_PUBKEY_OUTPUT_FILE" -pubin -in "$message" -out "$asymmetric_enc_message"
# decrypt the asymmetrically encrypted message and replace any existing output
nethsm --user namespace1~operator1 key decrypt --force dec1 "$asymmetric_enc_message" Pkcs1
cat "$NETHSM_KEY_DECRYPT_OUTPUT"
[[ "$(b2sum "$NETHSM_KEY_DECRYPT_OUTPUT" | cut -d ' ' -f1)" == "$(b2sum "$message" | cut -d ' ' -f1)" ]]
Public key
Administrators and operators can retrieve the public key of any key:
# when NETHSM_KEY_PUBKEY_OUTPUT_FILE is set, the public key is written to that file
# to print to stdout, we unset the environment variable
unset NETHSM_KEY_PUBKEY_OUTPUT_FILE
# keys of type "Generic" don't have a public key, so we do not request them
for key in signing{1..8} dec1; do
nethsm --user operator1 key public-key --force "$key"
# in our namespace1 we have keys of the same name, that we can get public keys for
nethsm --user namespace1~operator1 key public-key --force "$key"
done
Certificate Signing Requests for keys
Certificate Signing Requests for a particular target can be issued using the keys.
# get a CSR for example.com
nethsm --user operator1 key csr signing7 example.com
# also for the key of the same name in our namespace
nethsm --user namespace1~operator1 key csr signing7 example.com
Random bytes
The device can generate an arbitrary number of random bytes on demand. All users in the "Operator" role have access to this functionality!
export NETHSM_RANDOM_OUTPUT_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.random.txt')"
nethsm random 200
[[ -f "$NETHSM_RANDOM_OUTPUT_FILE" ]]
Metrics
The metrics for a device can only be retrieved by a system-wide user in the "Metrics" role.
nethsm metrics
Device configuration
Several aspects of the device configuration can be retrieved and modified. The device configuration is only available to "R-Administrators" (system-wide users in the "Administrator" role).
Boot mode
The boot mode defines whether the system starts into "Locked" or "Operational" state (the former requiring to supply the unlock passphrase to get to "Operational" state).
nethsm --user admin1 config get boot-mode
# let's set it to unattended
nethsm --user admin1 config set boot-mode Unattended
nethsm --user admin1 config get boot-mode
Logging
Each device may send syslog to a remote host.
nethsm --user admin1 config get logging
Network
The devices have a unique and static network configuration.
nethsm --user admin1 config get network
System Time
The device's system time can be queried and set.
nethsm --user admin1 config get time
nethsm --user admin1 config set time
nethsm --user admin1 config get time
TLS certificate
We can get and set the TLS certificate used for the device.
nethsm --user admin1 config get tls-certificate
# this generates a new RSA 4096bit certificate on the device
nethsm --user admin1 config set tls-generate Rsa 4096
nethsm --user admin1 config get tls-certificate
We can also receive only the public key for the TLS certificate:
nethsm --user admin1 config get tls-public-key
Or generate a Certificate Signing Request for the TLS certificate:
nethsm --user admin1 config get tls-csr example.com
Setting passphrases
The backup passphrase is used to decrypt a backup created for the device, when importing. By default it is the empty string (""
).
nethsm_backup_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.backup-passphrase.txt')"
printf 'my-very-unsafe-backup-passphrase' > "$nethsm_backup_passphrase_file"
export NETHSM_NEW_PASSPHRASE_FILE="$nethsm_backup_passphrase_file"
nethsm_initial_backup_passphrase_file="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.initial-backup-passphrase.txt')"
touch "$nethsm_initial_backup_passphrase_file"
export NETHSM_OLD_PASSPHRASE_FILE="$nethsm_initial_backup_passphrase_file"
nethsm --user admin1 config set backup-passphrase
The unlock passphrase is set during initial provisioning and is used to unlock the device when it is locked.
export NETHSM_OLD_PASSPHRASE_FILE="$NETHSM_UNLOCK_PASSPHRASE_FILE"
export NETHSM_NEW_PASSPHRASE_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.unlock-passphrase.txt')"
printf 'my-new-unsafe-unlock-passphrase' > "$NETHSM_NEW_PASSPHRASE_FILE"
nethsm --user admin1 config set unlock-passphrase
Locking
The device can be locked and unlocked, which puts it into state "Locked"
and "Operational"
, respectively.
nethsm --user admin1 lock
nethsm --user admin1 health state
nethsm --user admin1 health alive
# as we have changed the unlock passphrase, we need to provide the new one
export NETHSM_UNLOCK_PASSPHRASE_FILE="$NETHSM_NEW_PASSPHRASE_FILE"
nethsm unlock
nethsm --user admin1 health state
nethsm --user admin1 health ready
System modifications
The devices offer various system level actions, e.g.:
# reset device to factory settings
nethsm --user admin1 system factory-reset
# reboot device
nethsm --user admin1 system reboot
# shut down device
nethsm --user admin1 system shutdown
# get system info about the device
nethsm --user admin1 system info
Backups
The device offers backing up of keys and user data. Backup retrieval is only available to system-wide users in the "Backup" role!
export NETHSM_BACKUP_OUTPUT_FILE="$(mktemp --tmpdir="$nethsm_tmpdir" --dry-run --suffix '-nethsm.backup-file.bkp')"
nethsm system backup
A backup can later on be used to restore a device, using an "R-Administrator":
export NETHSM_BACKUP_PASSPHRASE_FILE="$nethsm_backup_passphrase_file"
nethsm --user admin1 system restore "$NETHSM_BACKUP_OUTPUT_FILE"
Backups can be validated offline:
export NETHSM_VALIDATE_BACKUP_PASSPHRASE_FILE="$nethsm_backup_passphrase_file"
nethsm system validate-backup "$NETHSM_BACKUP_OUTPUT_FILE"
Updates
Updates for the operating system/ firmware of the device are uploaded to the device and then applied or aborted.
nethsm --user admin1 system upload-update my-update-file.bin
# apply the update
nethsm --user admin1 system commit-update
# abort the update
nethsm --user admin1 system cancel-update
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.6.0] - 2024-12-13
Added
- Add
nethsm openpgp sign-state
command
[0.5.0] - 2024-12-08
Added
- Add
nethsm system validate-backup
command
Other
- (README) Add links to latest (un)released crate documentation
[0.4.1] - 2024-11-27
Other
- update Cargo.lock dependencies
[0.4.0] - 2024-11-26
Added
- [breaking] Provide version with
OpenPgpVersion
when creating OpenPGP certificate - [breaking] Use
OpenPgpUserId
for User ID when creating OpenPGP certificate
Fixed
- (deps) Update dependencies removing yanked crate
- (deps) update rust crate strum to 0.26.0
Other
- Consolidate contributing and licensing information
- (cargo) Use workspace dependencies for
clap
andnethsm-config
- (deps) Update dependencies and fix license ID
- Use easier to understand
no_run
attribute - (cargo) Move common dependencies to workspace dependencies
- (cargo) Move shared dependencies to workspace dependencies
- (cargo) Move package metadata to workspace
- Use
Display
in rendered docs instead ofDebug
representation - (deps) update rust crate rstest to 0.23.0
- Use
expression_format
for easier to read help strings
[0.3.0] - 2024-09-11
Fixed
- Allow generating Curve25519 key by default
Other
- Upgrade nethsm-config crate to 0.1.1
- Upgrade nethsm crate to 0.6.0
- Adapt subcommand documentation for the use of namespaces
- [breaking] Introduce
nethsm::KeyId
type - [breaking] Use
u32
instead ofi32
for ports and lengths
[0.2.2] - 2024-09-06
Fixed
- Distinguish restore of provisioned and unprovisioned device
Other
- Upgrade nethsm crate to 0.5.0
- Switch to nethsm-config crate from own modules
[0.2.1] - 2024-08-31
Fixed
- Name the command explicitly so that clap_allgen can use it
[0.2.0] - 2024-08-30
Added
- Allow providing global
--passphrase-file
option multiple times - Allow providing the global
--user
option multiple times - Add subcommands for managing namespaces
- Add
UserId
andNamespaceId
types for handling User IDs - Add shell completion and manpages generation
- (cli) Add
nethsm openpgp import
- (cli) Add
openpgp sign
subcommand - (cli) Add
openpgp add
command - Add support for PEM-encoded private keys in
key import
Fixed
- Adjust format option documentation for
nethsm key import
- When printing user tags, only show return value not Result
- [breaking] Rename function for creating an OpenPGP certificate
- Retrieving a key certificate may be done in the Operator role too
- Do not require
Administrator
role when executingunlock
Other
- Create release 0.4.0 for nethsm
- (README.md) Extend examples to cover the use of namespaces
- (README.md) Simplify OpenPGP examples with environment variables
- (README.md) Standardize OpenPGP User IDs used in examples
- (README.md) Sort
nethsm
options alphabetically before arguments - (README.md) Disambiguate key names from tag names
- Remove cleanup from the rendered README
- Document the formats of certificates
- Fix user role requirements for backup retrieval
- (deps) update rust crate rstest to 0.22.0
- Make integration tests more robust
- Simplify license attribution setup for entire project
[0.1.0] - 2024-07-13
Added
- Add CLI for the nethsm library
Other
- Add documentation for nethsm-cli crate
NetHSM-config
A library for working with application configuration files for Nitrokey NetHSM devices.
The Nitrokey NetHSM is a hardware appliance, that serves as secure store for cryptographic keys. With the help of a REST API it is possible to communicate with the device (as well as the official nethsm container) for setup and various cryptographic actions.
This library is meant to be used by end-user applications written against the nethsm crate.
Documentation
- https://signstar.archlinux.page/rustdoc/nethsm_config/ for development version of the crate
- https://docs.rs/nethsm_config/latest/nethsm_config/ for released versions of the crate
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.2.2] - 2024-12-08
Other
- (README) Add links to latest (un)released crate documentation
[0.2.1] - 2024-11-27
Other
- Update libc crate as the previously used version was yanked
[0.2.0] - 2024-11-26
Added
- Add
HermeticParallelConfig
as hermetic, parallel configuration - [breaking] Allow tracking inner error message in
config::Error::Load
- Add
UserMapping
, mapping system and NetHsm users and their roles - Add
NetHsmMetricsUsers
for tracking metrics and operator users - Add
SystemWideUserId
for a guaranteed to be system-wideUserId
- Add
AuthorizedKeyEntry
andAuthorizedKeyEntryList
for SSH keys - Add
SystemUserId
as representation of a system user name - Derive
Copy
fornethsm::UserRole
- Derive
Eq
,Hash
andPartialEq
forConnection
Fixed
- Provide the config name from settings when loading a configuration
- Extend documentation for
ConfigInteractivity::NonInteractive
- Return borrowed from
ConfigCredentials::get_passphrase
- (deps) Update dependencies removing yanked crate
- Adjust test names so they are isolated
Other
- Consolidate contributing and licensing information
- (deps) Update dependencies and fix license ID
- (cargo) Move common dependencies to workspace dependencies
- (cargo) Move shared dependencies to workspace dependencies
- (cargo) Move package metadata to workspace
- Move
ConfigCredentials
to credentials module - (deps) update rust crate rstest to 0.23.0
[0.1.1] - 2024-09-11
Other
- Upgrade nethsm crate to 0.6.0
[0.1.0] - 2024-09-06
Added
- Add nethsm-config crate as common configuration library
Other
- Upgrade nethsm crate to 0.5.0
NetHSM containerized tests
Containerized testing environments for NetHSM related projects.
This project contains types which start virtual NetHSM instances using Podman.
Documentation
- https://signstar.archlinux.page/rustdoc/nethsm_tests/ for development version of the crate
- https://docs.rs/nethsm_tests/latest/nethsm_tests/ for released versions of the crate
Example
The following integration test starts a NetHSM container with users to retrieve several random bytes:
#![allow(unused)] fn main() { use nethsm::Credentials; use nethsm::NetHsm; use nethsm::Passphrase; use nethsm_tests::nethsm_with_users; use nethsm_tests::NetHsmImage; use nethsm_tests::DEFAULT_OPERATOR_USER_ID; use nethsm_tests::DEFAULT_OPERATOR_USER_PASSPHRASE; use rustainers::Container; use testresult::TestResult; pub static LENGTH: u32 = 32; #[ignore = "requires Podman"] #[rstest::rstest] #[tokio::test] async fn get_random_bytes( #[future] nethsm_with_users: TestResult<(NetHsm, Container<NetHsmImage>)>, ) -> TestResult { let (nethsm, _container) = nethsm_with_users.await?; nethsm.add_credentials(Credentials::new( DEFAULT_OPERATOR_USER_ID.parse()?, Some(Passphrase::new( DEFAULT_OPERATOR_USER_PASSPHRASE.to_string(), )), )); nethsm.use_credentials(&DEFAULT_OPERATOR_USER_ID.parse()?)?; let random_message = nethsm.random(LENGTH)?; println!("A random message from the NetHSM: {:#?}", random_message); assert_eq!(usize::try_from(LENGTH)?, random_message.len(),); Ok(()) } }
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.1.2] - 2024-12-08
Other
- (README) Add links to latest (un)released crate documentation
- (cargo) Consolidate dependencies with workspace dependencies
[0.1.1] - 2024-11-27
Other
- Update libc crate as the previously used version was yanked
[0.1.0] - 2024-11-26
Added
- Introduce
nethsm-tests
for easier integration testing
Other
- Consolidate contributing and licensing information
Signstar configure build
A commandline tool to configure a Signstar system during build.
The scope of this project is to read a dedicated configuration file, derive system users and their integration from it and create them.
The signstar-configure-build
executable must be run as root.
Documentation
- https://signstar.archlinux.page/rustdoc/signstar_configure_build/ for development version of the crate
- https://docs.rs/signstar_configure_build/latest/signstar_configure_build/ for released versions of the crate
Configuration file
By default signstar-configure-build
relies on the configuration file /usr/share/signstar/config.toml
and will fail if it is not found or not valid.
One of the following configuration files in the following order are used instead, if they exist:
/usr/local/share/signstar/config.toml
/run/signstar/config.toml
/etc/signstar/config.toml
Alternatively, signstar-configure-build
can be provided with a custom configuration file location using the --config
/ -c
option.
System users
Based on configured user mappings in the configuration file, signstar-configure-build
:
- creates unlocked system users
- without passphrase
- with a home directory below
/var/lib/signstar/home/
(but without creating it)
- adds tmpfiles.d integration for each user, so that their home directory is created automatically
- adds a dedicated authorized_keys file and sshd_config drop-in configuration, which defines a ForceCommand option to enforce specific commands for each configured user with SSH access
Examples
Assuming a valid configuration file (such as example.toml) in one of the default locations, the executable is called without any options:
signstar-configure-build
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.1.2] - 2024-12-08
Fixed
- (deps) update rust crate sysinfo to 0.33.0
Other
- (README) Add links to latest (un)released crate documentation
[0.1.1] - 2024-11-27
Other
- Update libc crate as the previously used version was yanked
[0.1.0] - 2024-11-26
Added
- Add build-time configuration tool for signstar host
Other
- Consolidate contributing and licensing information
Signstar Create Signing Request
This crate offers a library and an executable for creating, reading and writing of signing requests for files.
Documentation
- https://signstar.archlinux.page/rustdoc/signstar_request_signature/ for development version of the crate
- https://docs.rs/signstar_request_signature/ for released versions of the crate
Glossary
Client - the application (here: signstar-request-signature
) which computes a file digest and creates a signing request,
Server - the application (currently: nethsm-cli
) which receives a signing request, processes it and returns a signature,
Signing request - machine and human-readable description of the data that will be signed. The exact format for the signing request is described below.
Signature - raw cryptographic signature in a technology-specific framing (e.g. in "packets" for OpenPGP).
Format specification
The signing request is a custom JSON encoded data format. It is used to represent required information on data input (the hasher state type and the hasher state) and output (the requested signature type). Additionally, arbitrary optional data can be provided.
The below sample signing request is fully expanded for illustrative purposes:
{
"version": "1.0.0",
"required": {
"input": {
"type": "sha2-0.11-SHA512-state",
"content": [8,201,188,243,103,230,9,106,59,167,202,132,133,174,103,187,43,248,148,254,114,243,110,60,241,54,29,95,58,245,79,165,209,130,230,173,127,82,14,81,31,108,62,43,140,104,5,155,107,189,65,251,171,217,131,31,121,33,126,19,25,205,224,91,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,64,20,73,32,108,105,107,101,32,115,116,114,97,119,98,101,114,114,105,101,115,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
},
"output": {
"type": "OpenPGPv4"
}
},
"optional": {
"request-time": 1728913277,
"XHy1dHj": "https://gitlab.archlinux.org/archlinux/signstar/-/merge_requests/43"
}
}
The fields are as follows:
-
version
- Semantic Versioning-compatible version string. Incompatible changes to the format will result in a major version bump. -
required
- a dictionary of fields that the client considers critical enough that the signing server should reject a signing request if any of these fields are not understood. This serves the same purpose as the "critical bit" in OpenPGP and X.509.
optional
- a dictionary of fields that are optional for the server to understand. Their presence may cause the signing server to act differently. If any field in this category is not understood by the server it must ignore it.
Required category
This specification defines two fields in the Required category:
-
input
- the input to the signing process. It is an object with two fields:type
- type of the input content. The following values are defined:sha2-0.11-SHA512-state
: Represents a hasher state, as expected by thesha2
crate. The format is stable across minor versions. Note that due to internal OpenPGP hashing mechanics this is not a digest of the data (e.g. a file) being signed.
content
- the actual bytes of the input content.
-
output
- the output of the signing process as expected by the client. This is an object with the following fields:type
- the type of the signature expected by the client. The following values are defined:OpenPGPv4
: OpenPGP v4 signatures
Optional category
There are no defined fields in this category.
Specification evolution
The specification can be extended by adding new fields or allowed values in minor version changes. If fields are removed, this constitutes a major version change to the specification. Old values are supported indefinitely (with the exception of security related changes). This allows older clients to keep using the same API and only upgrade when they want to take advantage of additional features.
If the client sends a request that is not spec-compliant (e.g. a required field has not been provided) the server MUST reject the request.
The following is a non-exhaustive list of non-compliant behavior:
- providing more or other fields than
version
,required
andoptional
, - providing more fields than
input
andoutput
inrequired
dictionary, - providing other values than the ones listed in the Required section.
Any changes in the optional
category are allowed, since this is explicitly an open-ended section.
Deprecation of values is handled in the same way: the old value is supported and the accepting party (i.e. the server) must transparently translate old, deprecated values to the new format.
Each change in the protocol must be documented in this specification.
Refer to the design document for an in-depth discussion of the technical details.
Contributing
Please refer to the contributing guidelines to learn how to contribute to this project.
License
This project may be used under the terms of the Apache-2.0 or MIT license.
Changes to this project - unless stated otherwise - automatically fall under the terms of both of the aforementioned licenses.
Design
This document describes the design decisions and technical details of the signature request mechanism used by Signstar.
Design requirements
-
the signing request must be of small size, regardless of the size of the signed file. This makes it possible to minimize network traffic between client and the signing server
-
the signing request must convey enough information to identify the file being signed in a secure way
-
the signing request must allow optional extensions and evolution of the format. Backwards and forwards compatibility will allow mixing versions of clients and servers which will make the migration to new versions smoother
-
the format of the request must be easy to inspect by a human operator if such a need arises
Transmitting the state instead of the digest
--- title: High-level Signstar signing setup --- sequenceDiagram participant C as Client participant S as Signstar participant N as NetHSM Note over C: hashes a file without finalizing the digest C ->> S: send a signing request with the hasher state S ->> S: recover signed file's digest from the hasher state S ->> S: create OpenPGP packets and append them to the hasher S ->> S: finalize the OpenPGP hasher stream S ->> N: request a raw cryptographic signature N ->> N: create a raw cryptographic signature N ->> S: return a raw cryptographic signature S ->> S: add protocol-specific framing to the raw cryptographic signature S ->> C: return signature
The signing requests of most cryptographic systems are based on providing a digest to create a signature for. The Signstar project follows a different approach, outlined in the graph above. The internal hasher state is transmitted instead of the final digest, which has several advantages:
-
it decouples the act of hashing a file from the framing that is imposed by the target cryptographic system (e.g. OpenPGP).
-
transmitting the state makes it possible for the client to do just the file hashing with no cryptographic technology specific handling at all. All further cryptographic processing (e.g. appending OpenPGP packets) is done by the server.
-
the server may append any packets that it sees fit, for example timestamping notations, capturing the identity of the signer or any constraints on the signature (such as making the signatures expire).
-
since the state is just an array of bytes the signing server may additionally finalize it early to recover the digest of the file to be signed.
-
the client cannot fake data (such as OpenPGP notations), since it is the server appending them.
-
this design is easily expandable to other systems, that require only the file digest (such as SSH signatures).
The client sends a state and a malicious Signstar host could misuse the state to claim a signature over a different file. This creates a central and exposed position for the signing service host and requires it to be trustworthy and sufficiently monitored. In this design it is considered an acceptable trade-off, as the Signstar host is considered critical infrastructure, that has no direct access to private key material.
A discussion with the RustCrypto upstream raised the point that transmitting the state is "well beyond the typical analysis", as hazmat functionality is made use of. Note that transmitting the hasher state is only needed in the context of OpenPGP signatures, as in the case of SSH and X.509 the file digest is used directly.
Comparison of signing methods
The following section discusses the details on what exactly is signed by the low-level cryptographic primitive. All reviewed cryptographic systems allow attaching additional metadata to signed files. Low-level primitives therefore make use of a different digest than that of the plain file itself.
OpenPGP
In OpenPGP the calculation of the digest-to-be-signed is described in RFC 9580: Notes on Signatures.
Currently, two types of signatures are in active use: Version 4 and version 6.
For version 4 signatures a hash of the file to be signed is created. Then the hasher is updated with OpenPGP specific metadata (signature subpackets), just as if they were appended at the end of the file.
This can be imagined as follows:
flowchart TD F[file] --> CAT M[metadata] --> CAT CAT(concatenate bytes) CAT --> DIGEST DIGEST(compute digest) --> DTS DTS[data to be signed]
RFC 9580, which is a revision of the widely implemented RFC 4880 additionally specifies Version 6 signatures which prepend the file with a randomly generated "salt" value:
flowchart TD S[salt] --> CAT F[file] --> CAT M[metadata] --> CAT CAT(concatenate bytes) CAT --> DIGEST DIGEST(compute digest) --> DTS DTS[data to be signed]
The server creates pgp-metadata
and continues the hashing, which produces the final digest-to-be-signed
.
With V4 signatures the server is able to recover the digest of the file, which may be embedded in the OpenPGP metadata as a signature notation.
Version 6 signatures prepend a randomly generated "salt" value, which precludes calculation of the original file digest by the server and addition of this value as a signature notation. However, this still allows to issue a signature over the combination of salt and file.
SSH file signatures
SSH calculates the file digest directly, then wraps that digest's value in a different structure, which includes metadata.
The documentation provides a rationale to why the file digest is computed first:
This is done to limit the amount of data presented to the signature operation, which may be of concern if the signing key is held in limited or slow hardware or on a remote ssh-agent.
Conceptually the operation looks like this:
flowchart TD P[SSH signature prefix] --> CAT F[file] --> DIGEST DIGEST(compute digest) --> CAT CAT(concatenate bytes) --> DIGEST2 DIGEST2(compute digest) --> DTS DTS[data to be signed]
X.509 signatures
X.509 signatures are similar to SSH file signatures: the digest of the file is computed first, then it is embedded in the SignedData
structure through an intermediate SignerInfo
structure. The entire SignedData
structure is digested and signed. The signature is inserted as a field of the SignerInfo
.
flowchart TD F[file] --> DIGEST DIGEST(compute digest) --> WRAP WRAP(wrap in a signerInfo) --> WRAP2 WRAP2(wrap in a signedData) --> DIGEST2 DIGEST2(compute digest) --> DTS DTS[data to be signed] --> SIG SIG(update signerInfo)
Design documentation
This document outlines the design of the Signstar project in more detail.
NOTE: Until a test system (%6) is deployed, this document will likely undergo further revisions.
See previous setup for details on the setup upon which Signstar improves.
In evaluated setups all high-level setups which have been evaluated are listed. Only one of them (option C) is considered for implementation.
Threat model
The following assumptions are made with regard to the setup.
- HSM
- The given HSM is a tamper-proof device, which fully prevents private key exfiltration.
- Credentials required for administrative actions towards the HSM can be stored securely offline until they are needed for the (re)configuration of the HSM (e.g. initial setup, or creation of new keys and users).
- Backups of the HSM are stored securely offline and allow restoring a device in case of disaster.
- A collection of administrative credentials required for the (re)configuration of the HSM are distributed as shares of a shared secret, divided using Shamir's Secret Sharing (SSS) to dedicated individuals.
- Service host
- A dedicated physical host managing all access to the HSM is used for its configuration and for granting access to signing operations.
- A custom image-based operating system with a read-only root filesystem and an encrypted
/var
partition (with key enrolled to local TPM2 chip) is used on the host. Relevant secrets such as SSH host keys and current Operator user credentials are kept in the encrypted partition, preventing offline exfiltration on harddrive theft or copy. The cryptographic key material used for secure boot and verity signing of the OS image, as well as that used for signing OS image artifacts are kept in offline backups and are only used with dedicated hardware tokens when building images of the OS image. A compromise of the holder of either or both secure boot/verity signing or artifact signing key may lead to the creation of a compromised OS image. It is therefore advised to ensure proper deployment of update artifacts using quality gates. - The host is not directly accessible by system users of the image-based OS (i.e. via login shell over SSH) during runtime, but only via rescue environment.
- Dedicated host credentials are used to collect the public key for a WireGuard connection, which is setup during first boot and is used exclusively for securely diverting system logs and metrics to a hardcoded host.
- Administrative credentials for the configuration of the HSM are provided to the host OS as shares of a shared secret, divided using SSS. They are held in tmpfs and removed once the administrative action is finished or a predefined timeout is reached.
- Passphrases for unprivileged user actions on the NetHSM are rotated on each (re)configuration of the system and are never persisted outside of the host.
- A backup is created after each successful (re)configuration of the NetHSM.
- If a shareholder of the shared secret to the administrative credentials of the HSM is compromised, loses access to the share or leaves the organization, a reconfiguration of the administrative credentials takes place, rotating all credentials and making new shares for a shared secret available for download to current shareholders.
If
n
out ofm
shareholders are compromised, an attacker needs physical access to the NetHSM or the backups to be able to either remove keys or use them for cryptographic operations. - During runtime the OS grants access to unprivileged user credentials (e.g. Operator, Metrics and Backup) of the HSM to pre-configured system users. If credentials for a pre-configured system user account are compromised, an attacker may use the account solely in the context of its role (e.g. to sign artifacts, to retrieve metrics or encrypted backups). Credentials for the host's system users can be changed transparently, reproducibly and in a fast manner by upgrading the host's OS.
- Logs and metrics of the host are aggregated securely and are accessible for continuous monitoring of the OS and its critical facilities.
- A public transparency log is appended to by the signing facilities for each requested signature, which provides insights into all signing operations done by the system.
- Transparency log monitoring is done by a system outside of the scope of the Signstar project.
- The service host has access to Operator credentials which allows using available signing keys on the NetHSM. If the service host is fully compromised, it can issue signatures using those signing keys while sidestepping additions to a transparency log. The operating system must only provide tooling for signing in the narrow scope described by the Signstar project to prevent the use of signing keys for other use-cases.
- Client
- Clients to the signing service can each be provided with credentials to the service in a dedicated scope (e.g. hardware backed and service specific) and have no access to the actual Operator (or any other) credentials of the HSM.
- Some or all signstar clients may run in untrusted environments or may run untrusted or unsafe code (e.g. build servers during build time), which may lead to them being compromised.
- Known compromised clients can either be shutdown and/ or be disconnected from the signing service by re-configuring the service with altered or removed credentials for the respective clients.
- Clients don't have direct access to signing keys nor can they directly influence the structure of signatures.
- Digital Signatures
- Digital signatures are requested by designated clients to the service host. If the client host or its credentials for the service host are compromised, either the client host is deactivated and/ or its credentials for the service host are removed or changed. All identified malicious signatures are gathered and affected artifacts are rebuilt or resigned. Depending on severity of the breach, all artifacts in direct circulation still signed by the the affected key material can be rebuilt and signed by a different key. Afterwards the affected key material in its entirety or simply its trust level can be revoked, ensuring that end-users no longer trust signatures made by this key. Finally, a public report lists all known affected artifacts and signatures and outlines the chosen mitigation steps.
- Digital signatures contain metadata about the environment in which they were created. This helps in identifying signature requester, signed payloads and information about the involved hosts.
Setup
Signstar as a signing service is meant to be run as a set of middleware applications on a physical host, fronting a Nitrokey NetHSM.
The signing service allows for assigned users to request signatures, metrics and backups. For system modification related tasks, dedicated credentials are used to provide shares of a shared secret for the (re)configuration of the HSM and backups files to restore from.
Hardware Security Module
The NetHSM offers users of different roles in segregated namespaces:
- Administrator (R-Administrator for system-wide and N-Administrator for per-namespace Administrator): for creating, removing and modifying unprivileged users (e.g. Operator, Backup, Metrics), creating, removing and modifying of keys and all other system-level privileged actions
- Operator: for cryptographic operations using accessible keys (may exist system-wide or in a namespace)
- Metrics: for pulling metrics of the HSM (may only exist system-wide)
- Backup: for downloading encrypted backups of the HSM, containing device settings, user setups and keys (may only exist system-wide)
The Administrator credentials are needed for initial setup of the appliance, as well as any ongoing maintenance tasks that involve adding, removing or modifying users or keys. Each Operator account is assigned exactly one private signing key of the HSM (using the tags system of the NetHSM).
The user setup is explained in more detail in the configuration section.
Signing service
The signing service host is a physical machine running the custom image-based operating system SignstarOS (based on Arch Linux), running from a read-only root filesystem (with verity signing support) and an encrypted /var
partition for state, such as SSH host keys and per-user configuration.
Its central configuration and user setup is provided through OS updates.
Per-user configuration files for the integration with the NetHSM are created and changed in a persistent location by a dedicated tool, once administrative credentials have been passed to the host. Administrative credentials for the HSM are provided by designated individuals as shares of a secret divided using Shamir's Secret Sharing (SSS) algorithm.
Authentication towards the signing service host is provided over SSH for credentials mapped to user roles on the HSM, that allow for unprivileged operations (i.e. Operator, Metrics and Backup users), for special system users that allow providing specific system-altering configuration (e.g. WireGuard configuration items, backup files, or shares of the administrative credentials) or for downloading purposes (e.g. key certificates, or shares of administrative credentials).
Dedicated software components provide access for authenticated users to these different functionalities:
signstar-download-signature
: for signing messages using HSM Operator credentialssignstar-download-backup
: for receiving backups of the HSM using HSM Backup credentialssignstar-download-key-certificate
: for downloading the certificates (e.g. OpenPGP certificates) of all keyssignstar-download-metrics
: for retrieving metrics of the device using HSM Metrics credentialssignstar-download-secret-share
: for downloading (new) individual shares of a secret (containing administrative credentials) divided using SSSsignstar-download-wireguard
: for downloading the public key of the WireGuard setup used for diverting logs and metrics to a dedicated hostsignstar-upload-backup
: for uploading a backup file, to be used in a system restore actionsignstar-upload-secret-share
: for providing HSM administrative credentials as shares of a secret divided using SSSsignstar-upload-update
: for uploading NetHSM firmware updates
Additionally, the signstar-configure
executable is used to (re)configure the HSM and per-user configuration files based on central configuration and provided administrative credentials.
Signing service clients
All signing service clients should rely on tamper-proof hardware (e.g. TPM2, or hardware token) to guard dedicated credentials for connecting to the signing service.
Networking
The Signstar setup is run on a dedicated host, that on one hand is exposed to the network of its clients, and on the other to the network of the NethHSM.
It is advisable to not expose the NetHSM to the network beyond that of the host running SignstarOS.
--- title: Simplified network setup --- sequenceDiagram participant C as Client participant S as Signstar participant N as NetHSM Note over S: Public network range Note over N: Private network range C ->> S: accepted S ->> N: accepted C --x N: denied
Authentication
Clients authenticate against the signing service host as individual Unix users over SSH and each user is assigned an executable as dedicated component for their use-case. Credentials to the HSM are never directly exposed to the clients of the signing service host.
--- title: User relation of Signstar and HSM credentials --- sequenceDiagram participant C as Client participant S as Signstar participant N as NetHSM Note over S: pair of Signstar credentials Note over N: pair of NetHSM credentials C ->> S: User "A" requests operation S ->> S: Middleware user "A" is mapped to HSM Operator user "X" S ->> N: Operation is requested using Operator user "X" N ->> S: Result of operation is returned S ->> C: Result of operation for user "A" is returned
The following executables rely on a per-user configuration, that provides a one-to-one mapping of signing service host credentials to HSM Operator, Backup and Metrics credentials.
Match user signer
ForceCommand /usr/bin/signstar-download-signature
Match user backup
ForceCommand /usr/bin/signstar-download-backup
Match user metrics
ForceCommand /usr/bin/signstar-download-metrics
Match user certificates
ForceCommand /usr/bin/signstar-download-key-certificates
The following executables do not rely on a NetHSM user mapping, as they are only used to provide data to the system or download data generated by the system, but not to interact with the HSM.
Match user backup-upload
ForceCommand /usr/bin/signstar-upload-backup
Match user secret-share-download
ForceCommand /usr/bin/signstar-download-secret-share
Match user secret-share-upload
ForceCommand /usr/bin/signstar-upload-secret-share
Match user update-upload
ForceCommand /usr/bin/signstar-upload-update
Match user wireguard-download
ForceCommand /usr/bin/signstar-download-wireguard
Logs and metrics
Syslogs of NetHSM devices are collected on the signing service host. Both the HSM syslogs and the host's own systemd-journald are consumed by Promtail and sent to a dedicated Grafana Loki logging host over a WireGuard tunnel.
--- title: Simplified syslog setup --- sequenceDiagram participant N as NetHSM participant S as SignstarOS participant L as Loki loop logs loop NetHSM syslog N ->> S: syslog to syslog-ng S ->> S: syslog-ng to promtail end loop SignstarOS journal S ->> S: journal to promtail end S -->> L: Promtail send<br>(WireGuard) end
Metrics for the NetHSM are collected using a custom Prometheus metrics exporter which relies on Metrics user credentials of the HSM for device metrics and a set of dedicated Operator user credentials for per-key metrics. Metrics for the signing service host itself are collected separately. All metrics are scraped using Prometheus over a WireGuard tunnel.
--- title: Simplified metrics setup --- sequenceDiagram participant N as NetHSM participant S as SignstarOS participant M as Prometheus loop metrics loop NetHSM metrics exporter S ->> N: request N ->> S: return end loop SignstarOS metrics S ->> S: Export host metrics end S -->> M: scrape metrics<br>(WireGuard) end
Configuration
The configuration of the signing service host is changed by upgrading its read-only operating system SignstarOS. The basic configuration of the NetHSM (i.e. its available users and keys) is set and altered using a central, versioned configuration file on the signing service host, which does not include any passphrases.
Passphrases for common (unprivileged) operations towards the NetHSM are kept in per-user configuration files and can only be created when (re)configuring the HSM using administrative credentials.
The administrative credentials for the NetHSM (for (re)configuration actions) are provided to the signing service host by designated shareholders of a shared secret, divided using Shamir's Secret Sharing.
The shared secret contains the unlock and backup passphrases and all Administrator credentials of the NetHSM. If one of these is changed or a new one is added, the shared secret is recreated and shareholders must download their shares anew.
Configuration actions towards the NetHSM relate to users in the Administrator role: System-wide (R-Administrator - R) or per namespaces (N-Administrator - N).
NOTE: To segregate groups of users and their access to keys, namespaces for each purpose are created (e.g. "release artifact signing", "repository sync database signing", "package signing").
- provision (R)
- configure TLS setup (R)
- reboot system (R)
- shut down system (R)
- factory reset system (R)
- restore from backup (R)
- set network configuration (R)
- set logging configuration (R)
- set boot mode (R)
- set backup passphrase (R)
- set unlock passphrase (R)
- create Administrator user (R)
- remove Administrator user (R)
- create Backup user (R)
- remove Backup user (R)
- create Metrics user (R)
- remove Metrics user (R)
- create Administrator user in namespace (R)
- create namespace (R)
- delete namespace (R)
- create Operator user in namespace (N)
- remove Operator user in namespace (N)
- set passphrase for Operator user in namespace (N)
- generate key in namespace (N)
- remove key in namespace (N)
- tag key in namespace (N)
- untag key in namespace (N)
- tag user in namespace (N)
- untag user in namespace (N)
When it comes to configuration, one can distinguish between privileges required for initial and those required for ongoing administrative operations.
Initial
An initial configuration encompasses device, user and key configuration.
- provision (R)
- configure TLS setup (R)
- reboot system (R)
- shut down system (R)
- factory reset system (R)
- restore from backup (R)
- set network configuration (R)
- set logging configuration (R)
- set boot mode (R)
- set backup passphrase (R)
- set unlock passphrase (R)
- create N-Administrator user in namespace (R)
- create namespace (R)
- create Operator user in namespace (N)
- generate key in namespace (N)
- tag key in namespace (N)
- tag user in namespace (N)
Ongoing system maintenance
When the device-level configuration changes, the system-wide Administrator credentials are required.
- configure TLS setup (R)
- reboot system (R)
- shut down system (R)
- factory reset system (R)
- restore from backup (R)
- set network configuration (R)
- set logging configuration (R)
- set boot mode (R)
- set backup passphrase (R)
- create system-wide Operator user (R)
- create system-wide Backup user (R)
- create system-wide Metrics user (R)
- create N-Administrator user in namespace (R)
- create namespace (R)
- delete namespace (R)
Ongoing key and user maintenance
Ongoing key and user configuration entails the addition, removal and modification of existing users and keys.
- create Operator user in namespace (N)
- remove Operator user in namespace (N)
- set passphrase for Operator user in namespace (N)
- generate key in namespace (N)
- remove key in namespace (N)
- tag key in namespace (N)
- untag key in namespace (N)
- tag user in namespace (N)
- untag user in namespace (N)
Deployment
The signing service host is instantiated by deploying an installation image of SignstarOS onto a host with UEFI support in "setup mode".
During first boot, relevant partitions and encrypted credentials are created.
After first boot dedicated system credentials are used to download the public key for a WireGuard tunnel to a host used for logging and metrics aggregation.
If the attached NetHSM is unprovisioned, administrative credentials are generated by the signing service host and made available as shares of a shared secret, divided using Shamir's Secret Sharing. After successful download of all shares, the system provisions the NetHSM, as well as dedicated system user configurations and removes the administrative credentials.
After successful initial configuration, dedicated credentials are used to download an encrypted backup of the NetHSM.
--- title: Initial deployment --- sequenceDiagram actor C as Admin actor H as n shareholders participant S as Signing service host participant N as NetHSM participant L as Logging server Note over S: SignstarOS C ->> S: SignstarOS installation image S --> S: Install and first boot S ->> C: Download WireGuard public key C --> L: Configure WireGuard tunnel S ->> L: WireGuard tunnel S ->> N: Unprovisioned? N ->> S: Yes! S --> S: NetHSM administrative credentials (SSS) S ->> H: Share of administrative credentials S ->> N: Configure S --> S: Delete administrative credentials
Upgrading
The signing service host is upgraded by providing relevant update artifacts on a specific webserver. SignstarOS upgrades autonomously with the help of systemd-sysupdate.
Upgrades to the NetHSM are uploaded to the signing service host using designated credentials, which make use of signstar-upload-update. Once sufficient shares of the shared administrative credentials are uploaded after that, the NetHSM is updated using signstar-configure.
Restoring
The signing service host is bound to its particular hardware. If the hardware fails, a new physical host must be connected, which has to undergo initial deployment.
To restore a NetHSM from backup, a backup file is uploaded using designated credentials, which make use of signstar-upload-backup. Once sufficient shares of the shared administrative credentials used for creating the backup are uploaded after that, the NetHSM is restored using signstar-configure.
Signing
The central feature of the signing service is to allow (otherwise unprivileged) users on that host to request cryptographic signatures backed by an HSM.
--- title: High-level Signstar signing setup --- sequenceDiagram participant C as Client participant S as Signstar participant N as NetHSM Note over S: pair of Signstar credentials Note over N: pair of NetHSM credentials C ->> S: request a signature S ->> N: request a raw cryptographic signature N ->> N: create a raw cryptographic signature N ->> S: return a raw cryptographic signature S ->> S: add protocol-specific framing to the raw cryptographic signature S ->> C: return signature
Signature request payload
Clients may request a signature for very large artifacts, which leads to congestion on the signing service host (as observed in other signing solutions such as sigul). To prevent this from happening signstar will employ a custom machine-readable payload format (tracked in #41), that contains required metadata and pre-hashed data for a signature.
Making use of this custom payload comes at the downside, that the signing service never directly interacts with the specific artifact to sign and solely relies on provided metadata (which may be fraudulent).
When transmitting actual artifacts to sign, the signing service would be able to evaluate and/ or validate an artifact. However, this comes at the downside of having to employ custom parsers and validation tools, which may slow down the signing process significantly and also increase the attack surface of the setup.
Attestation log
An attestation log is a tamper-resistent and append-only data structure to which the resulting raw cryptographic signatures from signing operations are appended to. Additional metadata in an entry should provide insight into caller of the signing operation, the alleged payload and the certificate or fingerprint of the issuer.
Writing to an attestion log happens exclusively on the signing service host in an environment only exposing signing capabilities to authenticated users.
Work on attestation log integration is tracked in #43.
Threshold signatures
To further manifest and strengthen the Arch Linux reproducible builds effort, it is possible to build a system in the future in which only reproducible artifacts are eligible for a signature.
In such a system, m
build machines create the artifact in question and each request a signature from the Signstar system.
Only if a threshold of n
(where n <= m
) identical artifacts is met, a signature is created.
OpenPGP
Private keys stored on the HSM may be used for OpenPGP signatures.
For this a dedicated certificate with a User ID specific to a single host is created (e.g. Build Host 1 <buildhost1@archlinux.org>
).
When used in a packaging context, an OpenPGP certificate should not be created with an expiration time, as its validity is determined by a PGPKI (aka. Web of Trust, as provided by archlinux-keyring).
Single component key certificates are preferred in a packaging context, because a signing key is not used for authentication, encryption or third-party certification.
Moreover, certificates with multiple component keys also require combining and using multiple private keys on the HSM, which is currently not yet supported (see #50).
When creating OpenPGP signatures, the signing service may append the transaction metadata (e.g. file metadata, requesting client) as notations in the hashed area of the signature. In the future the position of the signature in an attestation log, may additionally be appended in the unhashed area of the signature.
OpenPGP signatures consist of OpenPGP packets (see packet structure of data signatures), that contain raw cryptographic signatures (e.g. those created by an HSM). As such, Signstar is responsible for issuing cryptographic signatures in the correct "framing".
--- title: OpenPGP signature creation with attestation log --- sequenceDiagram participant C as n clients participant S as Signstar participant A as attestation log participant N as NetHSM Note over C: one set of signstar credentials each Note over S: n NetHSM Operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings critical authentication critical option C ->> C: create checksum, gather metadata C ->>+ S: authenticate,<br/>transmit hash and metadata option C ->> C: gather metadata C ->>+ S: authenticate,<br/>transmit file and metadata end S -->> S: log user access and request to syslog option login failure S -->> S: log user login failure to syslog S -x C: failure option login successful S -->> S: log user login success to syslog critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found S -->> S: log user mapping failure to syslog S -x C: failure option mapping found S -->> S: log user mapping success to syslog end end critical data preparation S ->> S: check request option request is file S ->> S: create checksum for file end critical raw cryptographic signing S ->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ raw cryptographic signature not created S -->> S: log signature failure to syslog S -x C: failure option authentication succeeds/ raw cryptographic signature created N ->>- S: receive raw cryptographic signature S -->> S: log signature success to syslog critical appending to attestation log S ->> A: send raw cryptographic signature and metadata option authentication fails/ attestation log not appended to S -->> S: log attestation log append failure to syslog option authentication succeeds/ attestation log appended to A ->> S: receive slot number S -->> S: log attestation log append success to syslog end critical OpenPGP signature creation S ->> S: create OpenPGP signature with notation for attestation log slot in unhashed area option creation of OpenPGP signature fails S -->> S: log failure to create OpenPGP signature to syslog option creation of OpenPGP signature succeeds S -->> S: log successful creation of OpenPGP signature to syslog end S -->> S: log return of OpenPGP signature to syslog S -->>- C: receive OpenPGP signature end
Packages and repository sync databases
By unifying the surface for how clients interact with the signing service, it is possible to improve over the previous setup for packages and repository sync databases by requesting signatures transparently from centrally managed build and repository server hosts.
Here the signature issuing takes place as outlined in the section on OpenPGP signing.
--- title: Signing service signing files or hashes --- sequenceDiagram participant B as build server participant S as Signstar participant R as repo server Note over B: one set of Signstar credentials Note over R: one set of Signstar credentials critical B ->>+ B: get sources,<br/>build package(s) loop get signature for each package B ->>+ S: request signature S ->>- B: receive OpenPGP signature end B ->>- B: move package file and OpenPGP signature to publicly accessible storage end critical repository update R ->>+ R: order to add package(s) from build server to repo B ->> R: download package(s) and OpenPGP signature(s) to pool R ->> R: generate temporary sync databases loop get signature for each sync database R ->>+ S: request signature S ->>- R: receive OpenPGP signature end R ->>- R: update repository end
Secure Boot Shim
A signed shim can be created by first (reproducibly) building the shim package and afterwards signing the resulting EFI binary. This requires X.509 support, which is tracked in #51.
--- title: Signing service signing shim --- sequenceDiagram participant B as client participant S as Signstar participant N as NetHSM Note over B: one set of Signstar credentials Note over S: n NetHSM Operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings critical B ->> B: install shim package critical B ->> B: gather metadata B ->> S: authenticate,<br/>send EFI file and metadata S ->> S: create checksum for file end S ->> N: authenticate for client,<br/>transmit digest and key ID N -->> S: raw cryptographic signature S ->> S: create signature S -->> B: receive signature B ->> B: move signed shim to output dir end
Evaluated setups
This document contains evaluated, but not considered setup concepts for signstar.
Scenarios
In the below subsection all evaluated scenarios are listed. The following list provides a more high-level overview of the evaluated features.
Feature | A | B | C | D | E | F |
---|---|---|---|---|---|---|
attestation log | ❌️ | ✅️ | ✅️ | ❌️ | ✅️ | ✅️ |
central signing | ❌️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
low complexity | ✅️ | ✅️ | ✅️ | ✅️ | ❌️ | ❌️ |
client crypto backend unaware 1 | ❌️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
no direct client access to hardware appliance | ❌️ | ✅️ | ✅️ | ✅️ | ✅️ | ✅️ |
no direct build server access to repo server | ❌️ | ❌️ | ✅️ | ❌️ | ✅️ | ✅️ |
no direct signing service access to repo server | ✅️ | ✅️ | ✅️ | ✅️ | ❌️ | ❌️ |
no transmission of files from build server | ✅️ | ✅️ | ✔️ | ❌️ | ✔️ | ❌️ |
no custom wire format | ✅️ | ❌️ | ❌️ | ❌️ | ❌️ | ❌️ |
no workflow complexity offloaded to signing service | ✅️ | ✅️ | ✅️ | ✅️ | ❌️ | ❌️ |
HSM directly
The clients directly interact with the hardware appliance (there is no signing service).
--- title: HSM directly --- sequenceDiagram participant B as build server participant N as NetHSM participant R as repo server participant L as logging server participant M as metrics server Note over B: 1 NetHSM operator credential,<br/>1 certificate ID,<br/>1 repo server credential Note over B: PKCS11 based tooling for signing Note over R: 1 NetHSM operator credential,<br/>1 certificate ID Note over R: PKCS11 based tooling for signing loop package build B ->> B: get sources,<br/>build package(s),<br/>get signature,<br/>send to repo server end B-->>N: authenticate,<br/>transmit checksum and cert ID N->>B: raw cryptographic signature loop repository update R ->> R: receive package(s),<br/>generate sync databases,<br/>get signature,<br/>update repository end B ->> R: package(s) and OpenPGP signature(s) R -->> N: authenticate,<br/>transmit checksum and cert ID N ->> R: raw cryptographic signature loop metrics collection N --> M: read B --> M: read R --> M: read end loop log aggregation N -->> L: send via syslog end
Signing service signing hashes
In this setup a microservice takes care of taking authenticated client requests and issuing signatures for the request via a PKCS#11 backend.
--- title: Signing service signing hashes --- sequenceDiagram participant B as build server participant S as signing server participant N as NetHSM participant R as repo server participant L as logging server participant M as metrics server Note over B: 1 signing server credential,<br/>1 repo server credential Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings Note over S: PKCS11 based tooling for signing Note over R: 1 signing server credential loop package build B ->> B: get sources,<br/>build package(s),<br/>create OpenPGP hash,<br/>get signature,<br/>send to repo server end B -->> S: authenticate,<br/>transmit hash and file metadata S -->> N: authenticate for client,<br/>transmit hash and cert ID N ->> S: raw cryptographic signature S ->> B: OpenPGP signature loop repository update R ->> R: receive package(s),<br/>generate sync databases,<br/>create OpenPGP hash,<br/>get signature,<br/>update repository end B ->> R: package(s) and OpenPGP signature(s) R -->> S: authenticate,<br/>transmit hash S -->> N: authenticate for client,<br/>transmit hash and cert ID N ->> S: raw cryptographic signature S ->> R: OpenPGP signature loop metrics collection B --> M: read S --> M: read N --> M: read R --> M: read end loop log aggregation N -->> L: send via syslog end
The signing process in more detail may look as follows:
--- title: Signing process in "Signing service signing hashes" scenario --- sequenceDiagram participant C as n clients participant S as signing server participant N as NetHSM Note over C: one signing server credential each,<br/>if build server: one repo server credential each Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings critical authentication C ->> C: create OpenPGP hash C -->>+ S: authenticate,<br/>transmit hash and file metadata S ->> S: log user access and request option login failure S -x C: failure S ->> S: log user login failure option login successful S ->> S: log user login success critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found S ->> S: log user mapping failure S -x C: failure option mapping found S ->> S: log user mapping success end end critical data preparation S ->> S: check request option request is file S ->> S: create checksum for file end critical signing S -->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ signature not created S ->> S: log signature failure S -x C: failure S ->> S: log signature return failure option authentication succeeds/ signature created N ->>- S: raw cryptographic signature S ->> S: log signature success S ->>- C: OpenPGP signature S ->> S: log signature return success end
Here, the n clients
may be build servers or the repository server, as they are functionally equal in behavior.
Signing service signing files or hashes
In this setup a microservice takes care of taking authenticated client requests and issuing signatures for the request via a PKCS#11 backend. Clients may send checksums or entire files using a custom wire format.
On a build server the signed packages are exposed via a static webserver location.
--- title: Signing service signing files or hashes --- sequenceDiagram participant B as build server participant S as signing server participant N as NetHSM participant R as repo server participant L as logging server participant M as metrics server Note over B: 1 signing server credential Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings Note over S: PKCS11 based tooling for signing Note over R: 1 signing server credential critical B ->> B: get sources,<br/>build package(s) loop get signature for each package critical option B ->> B: create checksum,<br/>gather metadata B ->> S: authenticate,<br/>send digest and metadata option B ->> B: gather metadata B ->> S: authenticate,<br/>send file and metadata S ->> S: create checksum for file end S ->> S: combine checksum and metadata to OpenPGP digest S ->> N: authenticate for client,<br/>transmit OpenPGP digest and cert ID N -->> S: raw cryptographic signature S ->> S: create OpenPGP signature S -->> B: receive OpenPGP signature B ->> B: move package file and OpenPGP signature<br/>to publicly accessible storage end end critical repository update R ->> R: order to add package(s) from build server to repo B ->> R: download package(s) and OpenPGP signature(s) to pool R ->> R: generate temporary sync databases loop get signature for each sync database critical option R ->> R: create checksum,<br/>gather metadata R ->> S: authenticate,<br/>send digest and metadata option R ->> R: gather metadata R ->> S: authenticate,<br/>send file and metadata S ->> S: create checksum for file end S ->> S: combine checksum and metadata to OpenPGP digest S ->> N: authenticate for client,<br/>transmit OpenPGP digest and cert ID N -->> S: raw cryptographic signature S ->> S: create OpenPGP signature S -->> R: receive OpenPGP signature end R ->> R: update repository end loop metrics collection B --> M: read S --> M: read N --> M: read R --> M: read end loop log aggregation N -->> L: send via syslog end
The signing process in more detail may look as follows:
--- title: Signing process in "Signing service signing files or hashes" scenario --- sequenceDiagram participant C as n clients participant S as signing server participant A as attestation log participant N as NetHSM Note over C: one signing server credential each Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings critical authentication critical option C ->> C: create checksum, gather metadata C ->>+ S: authenticate,<br/>transmit hash and metadata option C ->> C: gather metadata C ->>+ S: authenticate,<br/>transmit file and metadata end S ->> A: log user access and request option login failure S -x C: failure S ->> A: log user login failure option login successful S ->> A: log user login success critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found S ->> A: log user mapping failure S -x C: failure option mapping found S ->> A: log user mapping success end end critical data preparation S ->> S: check request option request is file S ->> S: create checksum for file end critical signing S ->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ signature not created S ->> A: log signature failure S -x C: failure S ->> A: log signature return failure option authentication succeeds/ signature created N -->>- S: receive raw cryptographic signature S ->> A: log signature success S ->> S: create OpenPGP signature S -->>- C: receive OpenPGP signature end
Here, the n clients
may be build servers or the repository server, as they are functionally equal in behavior.
Signing service signing files
In this setup a microservice takes care of taking authenticated client requests and issuing signatures for the request via a PKCS#11 backend. The client sends entire files to the service.
--- title: Signing service signing files --- sequenceDiagram participant B as build server participant S as signing server participant N as NetHSM participant R as repo server participant L as logging server participant M as metrics server Note over B: 1 signing server credential,<br/>1 repo server credential Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings Note over S: PKCS11 based tooling for signing Note over R: 1 signing server credential loop package build B ->> B: get sources,<br/>build package(s),<br/>get signature,<br/>send to repo server end B -->> S: authenticate,<br/>transmit file S -->> N: authenticate for client,<br/>transmit checksum and cert ID N ->> S: raw cryptographic signature S ->> B: OpenPGP signature loop repository update R ->> R: receive package(s),<br/>generate sync databases,<br/>get signature,<br/>update repository end B ->> R: package(s) and OpenPGP signature(s) R -->> S: authenticate,<br/>transmit file S -->> N: authenticate for client,<br/>transmit checksum and cert ID N ->> S: raw cryptographic signature S ->> R: OpenPGP signature loop metrics collection B --> M: read S --> M: read N --> M: read R --> M: read end loop log aggregation N -->> L: send via syslog end
The signing process in more detail may look as follows:
--- title: Signing process in "Signing service signing files" scenario --- sequenceDiagram participant C as n clients participant S as signing server participant N as NetHSM Note over C: one signing server credential each,<br/>if build server: one repo server credential each Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings critical authentication C -->>+ S: authenticate,<br/>transmit file S ->> S: log user access and request option login failure S -x C: failure S ->> S: log user login failure option login successful S ->> S: log user login success critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found S ->> S: log user mapping failure S -x C: failure option mapping found S ->> S: log user mapping success end end critical data preparation S ->> S: create checksum for file end critical signing S -->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ signature not created S ->> S: log signature failure S -x C: failure S ->> S: log signature return failure option authentication succeeds/ signature created N ->>- S: raw cryptographic signature S ->> S: log signature success S ->>- C: OpenPGP signature S ->> S: log signature return success end
Here, the n clients
may be build servers or the repository server, as they are functionally equal in behavior.
Signing service signing hashes and files as proxy
--- title: Signing service signing hashes and files as proxy --- sequenceDiagram participant B as build server participant S as signing server participant N as NetHSM participant R as repo server participant L as logging server participant M as metrics server Note over B: 1 signing server credential Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings,<br/>1 repo server credential Note over S: PKCS11 based tooling for signing Note over R: 1 signing server credential loop package build B ->> B: get sources,<br/>build package(s),<br/>send to signing server end B -->> S: authenticate,<br/>transmit file S -->> N: authenticate for client,<br/>transmit checksum and cert ID N ->> S: raw cryptographic signature loop repository update R ->> R: receive package(s),<br/>generate sync databases,<br/>get signature,<br/>update repository end S ->> R: package(s) and OpenPGP signature(s) R -->> S: authenticate,<br/>transmit checksum or file S -->> N: authenticate for client,<br/>transmit checksum and cert ID N ->> S: raw cryptographic signature S ->> R: OpenPGP signature loop metrics collection B --> M: read S --> M: read N --> M: read R --> M: read end loop log aggregation N -->> L: send via syslog end
The signing process in more detail may look as follows:
--- title: Signing process in "Signing service signing hashes and files as proxy" scenario --- sequenceDiagram participant B as n build clients participant R as one repo client participant S as signing server participant N as NetHSM Note over B: 1 signing server credential each Note over R: 1 signing server credential Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings,<br/>repo server credential critical data aggregation B ->> B: aggregate packages and metadata (e.g. target repo) in single file end critical authentication B -->>+ S: authenticate,<br/>transmit package(s) and target repo S ->> S: log user access and request option login failure break login failure S ->> S: log user login failure S -x B: failure end option login success S ->> S: log user login success critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found break user mapping not found S ->> S: log user mapping failure S -x B: failure end option mapping found S ->> S: log user mapping success S ->> B: success end end loop data preparation S ->> S: create checksum for package end loop get signature(s) critical signing S ->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ signature not created S ->> S: log signature failure option authentication succeeds/ signature created N ->>- S: raw cryptographic signature S ->> S: log signature success end end critical send to repo server S ->>- R: authenticate,<br/>transmit package(s), OpenPGP signature(s) and target repo option authentication failure S ->> S: log failure of transmitting package(s), OpenPGP signature(s) and target repo option authentication success S ->> S: log success of transmitting package(s), OpenPGP signature(s) and target repo end loop payload preparation R ->> R: create OpenPGP hash for database and collect file metadata end critical authentication R -->>+ S: authenticate,<br/>transmit OpenPGP hash and file metadata S ->> S: log user access and request option login failure break login failure S ->> S: log user login failure S -x R: failure end option login successful S ->> S: log user login success critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found break user mapping not found S ->> S: log user mapping failure S -x R: failure end option mapping found S ->> S: log user mapping success end end loop get signature(s) critical signing S ->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ signature not created break signature failure S ->> S: log signature failure S -x R: failure end option authentication succeeds/ signature created N ->>- S: raw cryptographic signature S ->> S: log signature success end end critical return of signature(s) S ->>- R: OpenPGP signature(s) option signature(s) not returned S ->> S: log failed return of OpenPGP signature(s) option signature(s) returned S ->> S: log successful return of OpenPGP signature(s) end
Signing service signing files as proxy
--- title: Signing service signing files as proxy --- sequenceDiagram participant B as build server participant S as signing server participant N as NetHSM participant R as repo server participant L as logging server participant M as metrics server Note over B: 1 signing server credential Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings,<br/>1 repo server credential Note over S: PKCS11 based tooling for signing Note over R: 1 signing server credential loop package build B ->> B: get sources,<br/>build package(s),<br/>send to signing server end B -->> S: authenticate,<br/>transmit file S -->> N: authenticate for client,<br/>transmit checksum and cert ID N ->> S: raw cryptographic signature loop repository update R ->> R: receive package(s),<br/>generate sync databases,<br/>get signature,<br/>update repository end S ->> R: package(s) and OpenPGP signature(s) R -->> S: authenticate,<br/>transmit checksum or file S -->> N: authenticate for client,<br/>transmit checksum and cert ID N ->> S: raw cryptographic signature S ->> R: OpenPGP signature loop metrics collection B --> M: read S --> M: read N --> M: read R --> M: read end loop log aggregation N -->> L: send via syslog end
The signing process in more detail may look as follows:
--- title: Signing process in "Signing service signing files as proxy" scenario --- sequenceDiagram participant B as n build clients participant R as one repo client participant S as signing server participant N as NetHSM Note over B: 1 signing server credential each Note over R: 1 signing server credential Note over S: n NetHSM operator credentials,<br/>n NetHSM certificate IDs,<br/>n client to NetHSM mappings,<br/>repo server credential critical data aggregation B ->> B: aggregate packages and metadata (e.g. target repo) in single file end critical authentication B -->>+ S: authenticate,<br/>transmit package(s) and target repo S ->> S: log user access and request option login failure break login failure S ->> S: log user login failure S -x B: failure end option login success S ->> S: log user login success critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found break user mapping not found S ->> S: log user mapping failure S -x B: failure end option mapping found S ->> S: log user mapping success S ->> B: success end end loop data preparation S ->> S: create checksum for package end loop get signature(s) critical signing S ->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ signature not created S ->> S: log signature failure option authentication succeeds/ signature created N ->>- S: raw cryptographic signature S ->> S: log signature success end end critical send to repo server S ->>- R: authenticate,<br/>transmit package(s), OpenPGP signature(s) and target repo option authentication failure S ->> S: log failure of transmitting package(s), OpenPGP signature(s) and target repo option authentication success S ->> S: log success of transmitting package(s), OpenPGP signature(s) and target repo end critical authentication R -->>+ S: authenticate,<br/>transmit database(s) S ->> S: log user access and request option login failure break login failure S ->> S: log user login failure S -x R: failure end option login successful S ->> S: log user login success critical user mapping S ->> S: map client user to NetHSM user and certificate ID option mapping not found break user mapping not found S ->> S: log user mapping failure S -x R: failure end option mapping found S ->> S: log user mapping success end end loop data preparation S ->> S: create checksum for database end loop get signature(s) critical signing S ->>+ N: authenticate for client,<br/>transmit checksum and cert ID option authentication fails/ signature not created break signature failure S ->> S: log signature failure S -x R: failure end option authentication succeeds/ signature created N ->>- S: raw cryptographic signature S ->> S: log signature success end end critical return of signature(s) S ->>- R: OpenPGP signature(s) option signature(s) not returned S ->> S: log failed return of OpenPGP signature(s) option signature(s) returned S ->> S: log successful return of OpenPGP signature(s) end
Previous setup
This document provides an overview of the workflows and contexts for package creation and other artifacts on Arch Linux as it has been until at least 2024.
Packages
The packaging infrastructure involves creating packages on n
machines that m
package maintainers have access to.
In many cases, the same machine is also used for cryptographically signing the resulting package file(s). There is no overview over whether package maintainers use hardware tokens for this to prevent key exfiltration and no way to enforce it either.
From n
machines that m
package maintainers have access to, package and detached signature files are copied to a central package repository server.
--- title: Per package maintainer access --- sequenceDiagram actor P as package maintainer participant B as n build machines participant R as repo server Note over B: 1 pair of build machine credentials Note over R: 1 pair of repo server credentials P ->> B: build and sign package(s) P ->> R: push built package and signature file(s) critical B ->> B: get sources,<br/>build package(s) B ->> B: sign package(s) end
Repository sync databases
The central repository server is responsible for creating the repository sync database files, which define the state of each binary package repository.
Repository sync databases are not signed as that would involve either forwarding gpg-agent to the host from n
machines that m
package maintainers have access to (security and blocking issue), or to add a software key to the host (which may be exfiltrated easily).
Release artifacts
Other artifacts such as installation media and virtual machine images are built semi-automatically or manually and are usually cryptographically signed.
Signing happens either with a software key in CI (in the case of virtual machine images) and is prone to exfiltration attacks, or manual on a single person's machine.
--- title: Building and signing of virtual machine images --- sequenceDiagram participant C as Continuous Integration Pipeline participant R as repo server Note over R: 1 pair of repo server credentials C ->> R: push built installation media and signature file(s) critical C ->> C: get sources,<br/>build installation media C ->> C: sign installation media end
--- title: Building and signing of installation media --- sequenceDiagram actor P as release manager participant B as n build machines participant R as repo server Note over B: 1 pair of build machine credentials Note over R: 1 pair of repo server credentials P ->> B: build and sign installation media P ->> R: push built installation media and signature file(s) critical B ->> B: get sources,<br/>build installation media B ->> B: sign installation media end
Secure Boot Shim
There is so far no signed shim for Secure Boot, as the location and safe-keeping of a signing key as well as its use for signature creation in packaging is so far unsolved.
Contributing
These are the contributing guidelines for the signstar project.
Development takes place at https://gitlab.archlinux.org/archlinux/signstar.
Writing code
This project is written in Rust and formatted using the nightly rustfmt
version.
All contributions are linted using clippy
and spell checked using codespell
.
The dependencies are linted with cargo-deny
.
License identifiers and copyright statements are checked using reuse
.
Various just
targets are used to run checks and tests.
To aide in development, it is encouraged to install the relevant git pre-commit and git pre-push hooks:
just add-hooks
Writing specifications
Specifications for technology of this project are written in markdown documents in the context of a component, that serves as its reference implementation.
The specifications are located in the component's resources/specification/
directory.
Specification versioning
A new specification version must be created, if fields of an existing specification are altered (e.g. a field is removed, added or otherwise changed semantically).
By default, given an example specification named topic
and given only one version of topic
exists, there would only be a document named topic.md
.
If the need for version two of topic
arises, the document is renamed to topicv1.md
, a new file named topicv2.md
is used for the new version and a symlink from the generic specification name to the most recent version (here topic.md -> topicv2.md
) is created.
Versioned specifications additionally must clearly state the specification version number they are addressing in the NAME
and DESCRIPTION
section of the document.
New (versions of) specifications must be accompanied by examples and code testing those examples.
The examples and code testing those examples must be kept around for legacy and deprecated specifications to guarantee backwards compatibility.
Writing commit messages
To ensure compatibility and automatic creation of semantic versioning compatible releases the commit message style follows conventional commits.
Creating releases
Releases are created by the developers of this project using release-plz
by running (per package in the workspace):
just prepare-release <package>
Changed files are added in a pull request towards the default branch.
Once the changes are merged to the default branch a tag is created and pushed for the respective package:
just release <package>
The crate is afterwards automatically published on https://crates.io using a pipeline job.
License
All code contributions fall under the terms of the Apache-2.0 and MIT.
Configuration file contributions fall under the terms of the CC0-1.0.
Documentation contributions fall under the terms of the CC-BY-SA-4.0.
Specific license assignments and attribution are handled using REUSE.toml
.
Individual contributors are all summarized as "Signstar Contributors".
For a full list of individual contributors, refer to git log --format="%an <%aE>" | sort -u
.