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

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 for nethsm::UserRole
  • Implement AsRef<str> for NamespaceId to return string slice
  • Rely on serde's into and try_from attributes for KeyId
  • Rely on serde's into and try_from attributes for UserId
  • 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 and OpenPgpUserIdList for OpenPGP User IDs
  • Add function to validate SignatureType against other key data
  • Derive Deserialize and Serialize for SignatureType
  • Derive Hash, Eq and PartialEq 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 version 0.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 of i32 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 and NamespaceId 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 executing unlock
  • 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 and Passphrase 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

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

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 and nethsm-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 of Debug 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 of i32 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 and NamespaceId 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 executing unlock

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

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-wide UserId
  • Add AuthorizedKeyEntry and AuthorizedKeyEntryList for SSH keys
  • Add SystemUserId as representation of a system user name
  • Derive Copy for nethsm::UserRole
  • Derive Eq, Hash and PartialEq for Connection

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

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

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

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:

  • 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:

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 and optional,
  • providing more fields than input and output in required 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 of m 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 credentials
  • signstar-download-backup: for receiving backups of the HSM using HSM Backup credentials
  • signstar-download-key-certificate: for downloading the certificates (e.g. OpenPGP certificates) of all keys
  • signstar-download-metrics: for retrieving metrics of the device using HSM Metrics credentials
  • signstar-download-secret-share: for downloading (new) individual shares of a secret (containing administrative credentials) divided using SSS
  • signstar-download-wireguard: for downloading the public key of the WireGuard setup used for diverting logs and metrics to a dedicated host
  • signstar-upload-backup: for uploading a backup file, to be used in a system restore action
  • signstar-upload-secret-share: for providing HSM administrative credentials as shares of a secret divided using SSS
  • signstar-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.

FeatureABCDEF
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.