Skip to content

Commit

Permalink
feat: Application package signing (#287)
Browse files Browse the repository at this point in the history
* refactor resources mod

* refactor resources module

* wip

* rename ManifestResource to ResourceBuilder

* update comments

* add copy_package_to_package_test test

* wip

* fix

* finish application package build

* add test

* wip

* wip

* update app package test

* fix spelling

* rename `PackagePath` to `Path`

* moved all hdf5 related code to under the hdf5 module, created new `Dir` struct

* remove duplicated package::resource module

* remove duplicated package::path module

* add hdf5::File struct

* update `Package` struct

* wip

* wip

* fix spelling

* fix fmt

* wip

* update docs

* implement app author cose payload object

* update `validate` functions

* add signature test

* add app sign cli command, update docs

* wip

* wip

* rename manifest "file" field to "package"

* wip

* fix app signing, update test

* fix spelling

* update descriptions

* wip
  • Loading branch information
Mr-Leshiy authored Jul 15, 2024
1 parent 9fa4890 commit 2e1e47f
Show file tree
Hide file tree
Showing 18 changed files with 866 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,29 @@ Signatures in Hermes Applications are created by Authors of the application.

If there is an independent Publisher/s of the application they too can attach a signature to the application.

Signatures are generated on a hash of the Applications contents from the root.

HDF5 filesystem is internally hierarchical.
What this means is that it is possible to get the entire contents of a group as binary data.
This significantly simplifies hashing of the filesystem contents in a controlled way,
without needing to traverse the filesystem tree.

Recall the Package Filesystem hierarchy:

![Diagram](images/application_package_hierarchy.d2)

We can generate a complete hash of the entire Application with the following procedure:

```rust
let mut hasher = Hash::new();

// Update the hasher with each of the binary buffers
hasher.update(bytes_of(`metadata.json`));

if exists(`/srv`) {
hasher.update(bytes_of(`/srv`));
}

if exists(`/usr`) {
`hasher.update(bytes_of(`/usr`));
}
This method protects any Application from being tampered with once released by the Author,
and also allows it to be safely co-signed by a Publisher.

if exists(`/lib`) {
hasher.update(bytes_of(`/lib`));
}
## Author signature payload

let digest = hasher.finalize()
```
Application package author signature payload according to the signing
[spec](../hermes_signing_procedure/signature_format.md#signature-payload)
should follow this schema:

In addition the following checks are made:
<!-- markdownlint-disable max-one-sentence-per-line -->
??? note "Schema: `hermes_module_cose_author_payload.schema.json`"

1. Has the bare minimum to be a viable application:
* Is there a `/srv/www` group with at least 1 file in it; OR
* Is there a `/lib` group with at least 1 validly signed Module in it.

2. Does the Application ONLY contain the groups and files as described in the Hierarchy diagram.
```json
{{ include_file('includes/schemas/hermes_module_cose_author_payload.schema.json', indent=4) }}
```
<!-- markdownlint-enable max-one-sentence-per-line -->

If either of these fails, the package is Invalid and can not be signed.
These checks are also made when any Application is loaded, to ensure it has not been tampered with.
Application package author signature payload example:

Once the hash of the root groups is known, and the structure is validated,
it is a simple matter of generating or validating a signature from the Hash.
<!-- markdownlint-disable max-one-sentence-per-line -->
??? note "Example: `hermes_module_cose_author_payload.json`"

This method protects any Application from being tampered with once released by the Author,
and also allows it to be safely co-signed by a Publisher.
```json
{{ include_file('includes/schemas/example/hermes_module_cose_author_payload.json', indent=4) }}
```
<!-- markdownlint-enable max-one-sentence-per-line -->
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ Packaging a Module is controlled by a manifest file, which must conform to the H

1. Create an unsigned WASM Component Module.
2. Sign it as one or more authors.
3. *Optionally, sign it as one or more publishers.*

#### Creating the unsigned Application Package

Expand Down Expand Up @@ -149,27 +148,27 @@ This takes the X.509 Private Certificate presented, and signs or counter-signs t

*Note: A Hermes WASM Component Module is INVALID if it does not contain at least 1 Author Signature.*

##### WASM module signature payload
##### Author signature payload

WASM module package signature payload according to the signing
WASM module package author signature payload according to the signing
[spec](../hermes_signing_procedure/signature_format.md#signature-payload)
should follow this schema:

<!-- markdownlint-disable max-one-sentence-per-line -->
??? note "Schema: `hermes_module_cose_payload.schema.json`"
??? note "Schema: `hermes_module_cose_author_payload.schema.json`"

```json
{{ include_file('includes/schemas/hermes_module_cose_payload.schema.json', indent=4) }}
{{ include_file('includes/schemas/hermes_module_cose_author_payload.schema.json', indent=4) }}
```
<!-- markdownlint-enable max-one-sentence-per-line -->

WASM module package signature payload example:
WASM module package author signature payload example:

<!-- markdownlint-disable max-one-sentence-per-line -->
??? note "Example: `hermes_module_cose_payload.json`"
??? note "Example: `hermes_module_cose_author_payload.json`"

```json
{{ include_file('includes/schemas/example/hermes_module_cose_payload.json', indent=4) }}
{{ include_file('includes/schemas/example/hermes_module_cose_author_payload.json', indent=4) }}
```
<!-- markdownlint-enable max-one-sentence-per-line -->

Expand Down
8 changes: 6 additions & 2 deletions hermes/bin/src/cli/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
use clap::Subcommand;

mod package;
mod sign;

/// Hermes cli commands
/// Hermes cli app commands
#[derive(Subcommand)]
pub(crate) enum Commands {
/// package wasm module
/// package application
Package(package::PackageCommand),
/// sign application
Sign(sign::SignCommand),
}

impl Commands {
/// Execute cli module command
pub(crate) fn exec(self) -> anyhow::Result<()> {
match self {
Commands::Package(cmd) => cmd.exec(),
Commands::Sign(cmd) => cmd.exec(),
}
}
}
41 changes: 41 additions & 0 deletions hermes/bin/src/cli/app/sign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//! cli app sign command
use std::path::PathBuf;

use clap::Args;
use console::Emoji;

use crate::packaging::{
app::ApplicationPackage,
sign::{certificate::Certificate, keys::PrivateKey},
};

/// Application package signing
#[derive(Args)]
pub(crate) struct SignCommand {
/// Defines the location of the builded application package.
package: PathBuf,

/// Defines the location of the ED2559 private key associated with the signing key.
private_key: PathBuf,

/// Defines the location of the x.509 certificate associated with the signing key.
cert: PathBuf,
}

impl SignCommand {
/// Run cli command
pub(crate) fn exec(self) -> anyhow::Result<()> {
println!("{} Sign application package...", Emoji::new("📝", ""));

let private_key = PrivateKey::from_file(self.private_key)?;
let cert = Certificate::from_file(self.cert)?;
let package = ApplicationPackage::from_file(self.package)?;

package.validate(true)?;
package.author_sign(&private_key, &cert)?;

println!("{} Done", Emoji::new("✅", ""));
Ok(())
}
}
2 changes: 1 addition & 1 deletion hermes/bin/src/cli/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clap::Subcommand;
mod package;
mod sign;

/// Hermes cli commands
/// Hermes cli module commands
#[derive(Subcommand)]
pub(crate) enum Commands {
/// package wasm module
Expand Down
2 changes: 1 addition & 1 deletion hermes/bin/src/cli/module/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl SignCommand {
let cert = Certificate::from_file(self.cert)?;
let package = WasmModulePackage::from_file(self.package)?;

package.validate()?;
package.validate(true)?;
package.sign(&private_key, &cert)?;

println!("{} Done", Emoji::new("✅", ""));
Expand Down
Loading

0 comments on commit 2e1e47f

Please sign in to comment.