Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CLAUDE.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ members = [
]

[workspace.package]
version = "14.2.0"
version = "15.0.0"
edition = "2021"
rust-version = "1.86"
license = "AGPL-3.0-or-later"
Expand Down
23 changes: 20 additions & 3 deletions FSD/FSD-003_FEDERATION_IDENTITY_CODES.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,26 @@ ml_dsa_65_pubkey_sha256 = sha256(raw ML-DSA-65 public key)
implementation MUST NOT treat a commitment match as authentication of the
identity.

Verify exposes `fedcode::verify_pulled_ml_dsa_65_pubkey(&code, pulled)` as the
one blessed check. A code with **no** commitment fails closed there: there is
nothing to bind the pulled key to.
Verify exposes two surfaces, and the second is the one a host should use:

- `fedcode::verify_pulled_ml_dsa_65_pubkey(&code, pulled)` — the check itself.
- **`fedcode::AdmittedHybridKey::admit(&code, pulled)`** — the same check in an
enforcing shape. Its fields are private and this is its only constructor, so
a value exists **only** if the pull matched. A host that takes an
`AdmittedHybridKey` as its registration input cannot express the unchecked
path; with the free function alone, nothing structurally stops a host from
registering a pulled body it never checked, and that failure is silent
(CIRISVerify#274).

A code with **no** commitment fails closed in both: there is nothing to bind
the pulled key to, so a v1/v2 code is not a hybrid registration path at all.

**What the commitment proves, precisely.** It proves the ML-DSA half is the one
the code's minter committed to — **not** that whoever holds the Ed25519 key
also holds this ML-DSA key. Nobody cross-signs the two halves at registration,
and they need not: persist admits `algorithm: "hybrid"` only and every row must
verify under **both** signatures, so a holder of one half can never produce an
admitting row. Joint control is proven at **first use**, not at registration.

### 3A.4 Compatibility

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
**Decide whether evidence about a machine, a build, or a key is worth
believing — and say exactly how much.**

v14.2.0 · Rust + Python · AGPL-3.0 · Post-quantum from day one
v15.0.0 · Rust + Python · AGPL-3.0 · Post-quantum from day one

CIRISVerify is an embeddable Rust library (with C FFI and a Python wheel) for
**hardware attestation verification, trust-anchor management, and artifact
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/ciris_verify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def get_library_version() -> str:
return __version__


__version__ = "14.2.0"
__version__ = "15.0.0"
__all__ = [
"CIRISVerify",
"MockCIRISVerify",
Expand Down
2 changes: 1 addition & 1 deletion bindings/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "ciris-verify"
version = "14.2.0"
version = "15.0.0"
description = "Python bindings for CIRISVerify hardware-rooted license verification"
readme = "README.md"
license = "AGPL-3.0-or-later"
Expand Down
14 changes: 7 additions & 7 deletions evidence/cc_impl.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,11 @@ decimal_id claim_id repo path#symbol crate@version
3.4.5 CLM-binding-disposition-split CIRISVerify src/ciris-verify-core/src/classification.rs#Gating ciris-verify-core@v13.0.0
2.6.1.1 CLM-redactable-commitment CIRISVerify src/ciris-verify-core/src/redactable.rs#RedactableCommitment ciris-verify-core@v13.0.0
5.1 CLM-presentation-identifier-scope CIRISVerify src/ciris-verify-core/src/presentation.rs#IdentifierScope ciris-verify-core@v13.0.0
UNASSIGNED CLM-subject-binding CIRISVerify src/ciris-verify-core/src/subject_binding.rs#SubjectBinding ciris-verify-core@v13.1.0
UNASSIGNED CLM-subject-binding-keyrecord CIRISVerify src/ciris-verify-core/src/federation_self_record.rs#check_subject_binding ciris-verify-core@v13.1.0
UNASSIGNED CLM-scope-destination CIRISVerify src/ciris-crypto/src/scope_privacy.rs#derive_destination ciris-crypto@v13.4.0
2.3.2.1 CLM-subject-binding CIRISVerify src/ciris-verify-core/src/subject_binding.rs#SubjectBinding ciris-verify-core@v13.1.0
3.3.6 CLM-subject-binding-keyrecord CIRISVerify src/ciris-verify-core/src/federation_self_record.rs#check_subject_binding ciris-verify-core@v13.1.0
5.4.6 CLM-scope-destination CIRISVerify src/ciris-crypto/src/scope_privacy.rs#derive_destination ciris-crypto@v13.4.0
5.4.6 CLM-announce-suppress CIRISVerify src/ciris-verify-core/src/announce_policy.rs#may_announce ciris-verify-core@v13.6.0
UNASSIGNED CLM-key-validity-window CIRISVerify src/ciris-verify-core/src/federation_self_record.rs#valid_until_in_envelope ciris-verify-core@v14.0.0
UNASSIGNED CLM-fedcode-owned-nodes CIRISVerify src/ciris-verify-core/src/fedcode.rs#OwnedNode ciris-verify-core@v14.1.0
UNASSIGNED CLM-keyring-rng-latch CIRISVerify src/ciris-keyring/src/lib.rs#mint_p256_signing_key ciris-keyring@v14.1.0
UNASSIGNED CLM-fedcode-pqc-commitment CIRISVerify src/ciris-verify-core/src/fedcode.rs#verify_pulled_ml_dsa_65_pubkey ciris-verify-core@v14.2.0
2.1 CLM-key-validity-window CIRISVerify src/ciris-verify-core/src/federation_self_record.rs#valid_until_in_envelope ciris-verify-core@v14.0.0
2.6.8 CLM-fedcode-owned-nodes CIRISVerify src/ciris-verify-core/src/fedcode.rs#OwnedNode ciris-verify-core@v14.1.0
4.2.2 CLM-keyring-rng-latch CIRISVerify src/ciris-keyring/src/lib.rs#mint_p256_signing_key ciris-keyring@v14.1.0
2.6.8 CLM-fedcode-pqc-commitment CIRISVerify src/ciris-verify-core/src/fedcode.rs#verify_pulled_ml_dsa_65_pubkey ciris-verify-core@v14.2.0
26 changes: 26 additions & 0 deletions scripts/bump-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,32 @@ else
echo -e "${RED}✗${NC} Cargo.toml not found"
fi

# 1b. Update intra-workspace path-dependency version pins.
#
# A member crate pins a sibling by MAJOR (`version = "14"`) alongside its
# `path`. Cargo enforces that pin, so a major bump leaves the workspace
# unbuildable until it is updated — and this was missed by hand at 13.0.0,
# 14.0.0 and 15.0.0 before being automated here. Minor/patch bumps are a
# no-op, since the pin carries only the major.
NEW_MAJOR="${NEW_VERSION%%.*}"
PIN_CHANGED=0
for member_toml in "$REPO_ROOT"/src/*/Cargo.toml; do
[ -f "$member_toml" ] || continue
# Only rewrite pins on deps that also carry a `path = "../…"` — a real
# sibling — never a same-named crates.io dependency.
if grep -qE '^ciris-[a-z-]+ = \{ path = "\.\./[^"]+", version = "[0-9]+"' "$member_toml"; then
sed -i.bak -E "s|^(ciris-[a-z-]+ = \{ path = \"\.\./[^\"]+\", version = \")[0-9]+(\")|\1${NEW_MAJOR}\2|" "$member_toml" \
&& rm -f "$member_toml.bak"
if ! git -C "$REPO_ROOT" diff --quiet -- "$member_toml" 2>/dev/null; then
PIN_CHANGED=1
echo -e "${GREEN}✓${NC} $(basename "$(dirname "$member_toml")")/Cargo.toml: sibling pin -> \"$NEW_MAJOR\""
fi
fi
done
if [ "$PIN_CHANGED" = "1" ]; then
CHANGES+=("intra-workspace sibling pins -> \"$NEW_MAJOR\"")
fi

# 2. Update Python pyproject.toml
PYPROJECT="$REPO_ROOT/bindings/python/pyproject.toml"
if [ -f "$PYPROJECT" ]; then
Expand Down
2 changes: 1 addition & 1 deletion src/ciris-keyring/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ keyring = { version = "3", optional = true }
# The base dependency is the `random` feature only, which is `[]` plus the
# `rand_core` this crate already pulls — so the light default stays light. The
# heavier PQC surface is still feature-gated below.
ciris-crypto = { path = "../ciris-crypto", version = "14", default-features = false, features = ["random"] }
ciris-crypto = { path = "../ciris-crypto", version = "15", default-features = false, features = ["random"] }

# Platform-specific
[target.'cfg(target_os = "android")'.dependencies]
Expand Down
20 changes: 11 additions & 9 deletions src/ciris-verify-core/src/bin/ciris_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2537,16 +2537,18 @@ fn run_fedcode_new(a: FedcodeNew) {
};

let key_id = fedcode::derive_key_id(&a.label, &ed_pub);
let fc = FedCode {
owned_nodes: Vec::new(),
ml_dsa_65_pubkey_sha256: None,
let mut fc = FedCode::new(
kind,
key_id: key_id.clone(),
pubkey_ed25519_base64: base64::engine::general_purpose::STANDARD.encode(&ed_pub),
transport_hint: a.transport_hint.clone(),
alias_hint: Some(a.label.clone()),
group_key_id: a.group_key_id.clone(),
};
key_id.clone(),
base64::engine::general_purpose::STANDARD.encode(&ed_pub),
)
.with_alias_hint(a.label.clone());
if let Some(h) = a.transport_hint.clone() {
fc = fc.with_transport_hint(h);
}
if let Some(g) = a.group_key_id.clone() {
fc = fc.with_group_key_id(g);
}
let code = match fedcode::encode(&fc) {
Ok(c) => c,
Err(e) => {
Expand Down
Loading
Loading