1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! Sign and verify `lys/attestation/v2` `COSE_Sign1` attestations over
//! arbitrary payload bytes.
//!
//! [`sign_attestation`] hashes the payload with SHA-256, captures the
//! current unix-millisecond timestamp, and signs the COSE `Sig_structure`
//! (RFC 9052 §4.4, empty `external_aad`) with the supplied
//! [`Ed25519Identity`]:
//!
//! ```text
//! Sig_structure = ["Signature1", protected, h'', claims]
//! protected = {1: -8 (EdDSA), 3: <v2 content type>, 4: <signer key>}
//! claims = {1: <SHA-256 payload hash>, 2: <unix-ms timestamp>}
//! ```
//!
//! [`verify_attestation`] recomputes the digest of the candidate payload,
//! compares it against `attestation.payload_hash`, rebuilds the
//! `Sig_structure` from the attestation's own fields, and verifies the
//! Ed25519 signature against `attestation.signer_public_key` (strict
//! verification). Any mismatch — wrong payload, tampered signature,
//! tampered timestamp, or wrong signer key — collapses to
//! [`TrustError::InvalidSignature`].
//!
//! Verification is v2-only: the COSE `Sig_structure` above is the sole
//! accepted signing scheme. A signature over anything else — the deleted v1
//! preimage (`lys/attestation/v1 ‖ timestamp_le ‖ hash`), a bare payload
//! hash, raw payload bytes — is rejected. There is no fallback path.
//!
//! Two properties fall out of the construction:
//!
//! - **Every field is authenticated.** The signer key (protected `kid`),
//! the payload hash, and the timestamp all ride inside the signed bytes;
//! none can be altered after signing without invalidating the signature.
//! - **Domain separation.** The signed message always begins
//! `0x84 0x6A "Signature1"`, which is byte-0 disjoint from every other
//! signing context in this crate: the old v1 preimage began `0x6C`
//! (deleted), signed-note bodies are valid UTF-8 (`0x84` is a UTF-8
//! continuation byte, impossible at position 0), and X.509 TBS bytes
//! begin `0x30` (DER SEQUENCE). Among future lys COSE artifacts the
//! protected content type — itself signature-covered and pinned by the
//! verifier — separates the v2 attestation. The raw
//! [`Ed25519Identity::sign`] primitive itself stays unprefixed by
//! necessity: the CA path signs exact X.509 TBS bytes through it, and
//! those bytes must not be altered. Any raw-sign caller whose message
//! could start with a valid `Sig_structure` encoding would need its own
//! separation; within this crate no such caller exists.
//!
//! The signed claims embed the 32-byte hash, not the raw payload. This
//! keeps the signing input fixed-size and uniform regardless of payload
//! length. Consumers that need to attest to large payloads pass the bytes
//! once and let `sign_attestation` produce the canonical hash.
use Utc;
use ;
use crateAttestation;
use crateencoding;
use cratecbor;
use crate;
use crateEd25519Identity;
/// Hash `payload` with SHA-256, capture the current timestamp, sign the
/// COSE `Sig_structure` with `signing_key`, and package the result as an
/// [`Attestation`].
///
/// The signature covers `["Signature1", protected, h'', claims]` — the
/// protected bucket carries the `EdDSA` algorithm, the v2 content type, and
/// the signer's public key; the claims carry the payload digest and the
/// unix-millisecond timestamp. The original payload bytes are not stored
/// on the attestation.
///
/// `sign_attestation` is infallible: `Utc::now().timestamp_millis()` is
/// total over the representable date range, the hand encoder is total over
/// the field types, and Ed25519 deterministic signing has no failure mode
/// in dalek 2.
/// Verify that `attestation` is a valid signature over `payload` and the
/// attestation's own timestamp by `attestation.signer_public_key`.
///
/// The check is two-step: the SHA-256 digest of `payload` must equal
/// `attestation.payload_hash`, and the Ed25519 signature must strictly
/// verify against the embedded public key over the `Sig_structure` rebuilt
/// from the attestation's own fields. Because the signed bytes are rebuilt
/// from the struct, a tampered timestamp, hash, or signer key fails
/// signature verification. All failures collapse to
/// [`TrustError::InvalidSignature`] so callers cannot distinguish them by
/// error variant — a tampered payload, a tampered timestamp, and a forged
/// signature all look the same to the verifier, which is the desired
/// property.
///
/// Verification is v2-only: only signatures over the COSE `Sig_structure`
/// are accepted. A signature over the deleted v1 preimage or the bare
/// payload hash is rejected like any other invalid signature.
///
/// # Errors
///
/// Returns [`TrustError::InvalidSignature`] if the recomputed payload hash
/// does not match `attestation.payload_hash`, if the public key is not a
/// valid Ed25519 point, or if the signature does not strictly verify over
/// the rebuilt `Sig_structure` (covering tampered signature bytes and
/// tampered timestamps alike).
/// Parse a tagged `COSE_Sign1` artifact and verify it against `payload` in
/// one step, returning the parsed [`Attestation`] on success — the
/// bytes-in convenience mirroring `verify_note`.
///
/// Equivalent to [`Attestation::from_cose_bytes`] followed by
/// [`verify_attestation`]; used by consumers (such as the CLI) that hold
/// the raw `.cose` file bytes.
///
/// # Errors
///
/// Returns [`TrustError::InvalidSignature`] for every failure — malformed
/// or non-canonical artifact, payload mismatch, and invalid signature are
/// deliberately indistinguishable (non-oracle).
/// SHA-256 digest of `bytes` as a fixed-size 32-byte array.