Skip to content

WIP: Rough draft for updated generic OCI sealing#226

Draft
cgwalters wants to merge 2 commits intocomposefs:mainfrom
cgwalters:sealing-impl
Draft

WIP: Rough draft for updated generic OCI sealing#226
cgwalters wants to merge 2 commits intocomposefs:mainfrom
cgwalters:sealing-impl

Conversation

@cgwalters
Copy link
Collaborator

This is just some rough draft raw material that builds on:

The biggest goal here is support for Linux kernel-native fsverity
signatures to be attached to layers, which enables integration with
IPE.

Add support for a fully separate OCI "composefs signature" artifact
which can be attached to an image.

Drop the -impl.md doc...it's not useful to try to write this
stuff in markdown. The spec has some implementation considerations,
but it's easier to look at implementation side from a code draft.

Add standardized-erofs-meta.md as a placeholder document outlining the
goal of standardizing composefs EROFS serialization across implementations
(canonical model: tar -> dumpfile -> EROFS).

Assisted-by: OpenCode (Claude Opus 4.5)
Signed-off-by: Colin Walters <walters@verbum.org>
Implement PKCS#7 signing and verification for composefs fsverity digests,
following the OCI referrers pattern for signature artifact storage.

Key features:
- cfsctl oci sign: Sign images with X.509 cert/key pairs
- cfsctl oci verify: Verify signatures against trusted certificates
- cfsctl oci pull --require-signature: Enforce signature verification
- cfsctl keyring add-cert: Inject certs into kernel .fs-verity keyring
- Signature artifacts stored as OCI referrers with subject field

The signature artifact format uses:
- artifactType: application/vnd.composefs.signature.v1
- Layers contain PKCS#7 DER signatures
- Algorithm annotation: composefs.algorithm: fsverity-sha512-12

Assisted-by: OpenCode (Claude claude-opus-4-5@20251101)
composefs_oci::signing::FsVeritySigningKey::from_pem(&cert_pem, &key_pem)?;

// Build subject descriptor from the source image's manifest
let manifest_json = img.manifest().to_string()?;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm we actually need to operate on the raw original representation, can't rely on to_string() always giving us the same thing.

/// Image reference (tag name)
image: String,
/// Path to the OCI layout directory (must already exist)
oci_layout_path: PathBuf,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use clap(value_parser) into an ocidir directly or so

/// the container to be mounted with integrity protection.
///
/// Returns a tuple of (sha256 content hash, fs-verity hash value) for the updated configuration.
pub fn seal<ObjectID: FsVerityHashValue>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be cleaner if we do a prep commit that removes the old sealing as we know we're not going to do it anymore.

/// # Returns
///
/// The number of referrer artifacts exported.
pub fn export_referrers_to_oci_layout<ObjectID: FsVerityHashValue>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this could land as a prep commit

use std::fs;
use std::io::Write;

let blobs_dir = oci_layout_path.join("blobs").join("sha256");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ocidir

format!("{seed:02x}").repeat(32)
}

fn sample_subject() -> Descriptor {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's unify this stuff with shared infra to generate an ocidir with known content


#[test]
fn test_fs_ioc_enable_verity_with_sig_bad_fd() {
let fd = ManuallyDrop::new(unsafe { OwnedFd::from_raw_fd(123456) });
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gross, no. This unit test has no value, should just be integration testing this.

/// <base64 encoded DER>
/// -----END CERTIFICATE-----
/// ```
fn pem_to_der(pem: &[u8]) -> Result<Vec<u8>, KeyringError> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

/// Simple base64 decoder for PEM parsing.
fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, use base64 crate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant