Skip to main content

redevplugin_contracts/
release_signing.rs

1use base64::Engine;
2use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use sha2::{Digest, Sha256};
6use std::collections::BTreeMap;
7use std::error::Error;
8use std::fmt;
9
10pub const ROOT_DELEGATION_SCHEMA_VERSION: &str = "redevplugin.release_root_delegation.v1";
11pub const PACKAGE_SIGNATURE_SCHEMA_VERSION: &str = "redevplugin.package_signature.v1";
12pub const RELEASE_METADATA_SCHEMA_VERSION: &str = "redevplugin.release_metadata.v5";
13pub const SOURCE_POLICY_SCHEMA_VERSION: &str = "redevplugin.release_source_policy.v2";
14pub const SOURCE_POLICY_POINTER_SCHEMA_VERSION: &str =
15    "redevplugin.release_source_policy_pointer.v1";
16pub const REVOCATION_SCHEMA_VERSION: &str = "redevplugin.release_revocation.v2";
17pub const REVOCATION_POINTER_SCHEMA_VERSION: &str = "redevplugin.release_revocation_pointer.v1";
18pub const SIGNING_LEDGER_EVIDENCE_SCHEMA_VERSION: &str =
19    "redevplugin.release_signing_ledger_evidence.v1";
20pub const SIGNING_SUBJECT_SCHEMA_VERSION: &str = "redevplugin.release_signing_subject.v1";
21pub const SIGNATURE_ENVELOPE_SCHEMA_VERSION: &str = "redevplugin.release_signature_envelope.v1";
22pub const SIGNING_LEDGER_SCHEMA_VERSION: &str = "redevplugin.release_signing_ledger.v1";
23pub const SIGNING_LEDGER_ENTRY_SCHEMA_VERSION: &str = "redevplugin.release_signing_ledger_entry.v1";
24pub const SIGNING_LEDGER_LOG_LEAF_SCHEMA_VERSION: &str =
25    "redevplugin.release_signing_ledger_log_leaf.v1";
26pub const SIGNING_LEDGER_RECEIPT_SCHEMA_VERSION: &str =
27    "redevplugin.release_signing_ledger_receipt.v1";
28pub const SIGNATURE_ALGORITHM_ED25519: &str = "ed25519";
29pub const GENESIS_PREVIOUS_EPOCH: &str = "0";
30pub const GENESIS_PREVIOUS_DOCUMENT_SHA256: &str =
31    "0000000000000000000000000000000000000000000000000000000000000000";
32
33const MAX_DOCUMENT_BYTES: usize = 1024 * 1024;
34const SIGNING_PREFIX: &[u8] = b"REDEVPLUGIN-SIGNING-V1\0";
35
36#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
37pub enum SigningUsage {
38    RootDelegation,
39    Package,
40    ReleaseMetadata,
41    SourcePolicy,
42    SourcePolicyPointer,
43    Revocation,
44    RevocationPointer,
45}
46
47impl SigningUsage {
48    pub const fn as_str(self) -> &'static str {
49        match self {
50            Self::RootDelegation => "redevplugin.release-signing.root-delegation.v1",
51            Self::Package => "redevplugin.release-signing.package.v1",
52            Self::ReleaseMetadata => "redevplugin.release-signing.release-metadata.v1",
53            Self::SourcePolicy => "redevplugin.release-signing.source-policy-document.v1",
54            Self::SourcePolicyPointer => "redevplugin.release-signing.source-policy-pointer.v1",
55            Self::Revocation => "redevplugin.release-signing.revocation-document.v1",
56            Self::RevocationPointer => "redevplugin.release-signing.revocation-pointer.v1",
57        }
58    }
59}
60
61#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
62pub enum DelegatedKeyUsage {
63    #[serde(rename = "package")]
64    Package,
65    #[serde(rename = "release_metadata")]
66    ReleaseMetadata,
67    #[serde(rename = "host_capability_contract")]
68    HostCapabilityContract,
69    #[serde(rename = "source_policy_document")]
70    SourcePolicy,
71    #[serde(rename = "source_policy_pointer")]
72    SourcePolicyPointer,
73    #[serde(rename = "revocation_document")]
74    Revocation,
75    #[serde(rename = "revocation_pointer")]
76    RevocationPointer,
77    #[serde(rename = "signing_ledger")]
78    SigningLedger,
79    #[serde(rename = "trusted_time")]
80    TrustedTime,
81}
82
83impl DelegatedKeyUsage {
84    const fn rank(self) -> u8 {
85        match self {
86            Self::Package => 0,
87            Self::ReleaseMetadata => 1,
88            Self::HostCapabilityContract => 2,
89            Self::Revocation => 3,
90            Self::RevocationPointer => 4,
91            Self::SourcePolicy => 5,
92            Self::SourcePolicyPointer => 6,
93            Self::SigningLedger => 7,
94            Self::TrustedTime => 8,
95        }
96    }
97}
98
99#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
100#[serde(deny_unknown_fields)]
101pub struct RootDelegatedKey {
102    pub algorithm: String,
103    pub key_id: String,
104    pub public_key: String,
105    pub usages: Vec<DelegatedKeyUsage>,
106    pub channels: Vec<String>,
107    pub valid_from: String,
108    pub valid_until: String,
109}
110
111#[derive(Clone, Debug, Eq, PartialEq)]
112pub struct RootDelegationInput {
113    pub source_id: String,
114    pub root_epoch: String,
115    pub previous_root_epoch: String,
116    pub previous_delegation_sha256: String,
117    pub generated_at: String,
118    pub expires_at: String,
119    pub delegated_keys: Vec<RootDelegatedKey>,
120    pub key_id: String,
121}
122
123#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
124#[serde(deny_unknown_fields)]
125pub struct RootDelegationV1 {
126    pub schema_version: String,
127    pub source_id: String,
128    pub root_epoch: String,
129    pub previous_root_epoch: String,
130    pub previous_delegation_sha256: String,
131    pub generated_at: String,
132    pub expires_at: String,
133    pub delegated_keys: Vec<RootDelegatedKey>,
134    pub key_id: String,
135    pub signature: String,
136}
137
138#[derive(Clone, Debug, Eq, PartialEq)]
139pub struct PackageSigningInput {
140    pub source_id: String,
141    pub channel: String,
142    pub version: String,
143    pub algorithm: String,
144    pub key_id: String,
145    pub publisher_id: String,
146    pub plugin_id: String,
147    pub package_hash: String,
148    pub manifest_hash: String,
149    pub entries_hash: String,
150    pub signed_at: String,
151}
152
153#[derive(Clone, Debug, Eq, PartialEq)]
154pub struct PackageVerificationContext {
155    pub source_id: String,
156    pub channel: String,
157    pub version: String,
158}
159
160#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
161#[serde(deny_unknown_fields)]
162pub struct PackageSignatureV1 {
163    pub schema_version: String,
164    pub algorithm: String,
165    pub key_id: String,
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub publisher_id: Option<String>,
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub plugin_id: Option<String>,
170    pub package_hash: String,
171    pub manifest_hash: String,
172    pub entries_hash: String,
173    pub signature: String,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub signed_at: Option<String>,
176}
177
178#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
179#[serde(deny_unknown_fields)]
180pub struct ReleaseDistributionRef {
181    pub distribution: String,
182    pub artifact_ref: String,
183}
184
185#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
186#[serde(deny_unknown_fields)]
187pub struct ReleasePackageHashSet {
188    pub package_sha256: String,
189    pub manifest_sha256: String,
190    pub entries_sha256: String,
191}
192
193#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
194#[serde(deny_unknown_fields)]
195pub struct ReleaseMetadataSignatureRef {
196    pub algorithm: String,
197    pub key_id: String,
198    pub signature_ref: String,
199    pub source_policy_epoch: String,
200    pub revocation_epoch: String,
201}
202
203#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
204#[serde(deny_unknown_fields)]
205pub struct PackageReleaseSignatureRef {
206    pub algorithm: String,
207    pub key_id: String,
208    pub signature_bundle_ref: String,
209    pub source_policy_epoch: String,
210    pub revocation_epoch: String,
211}
212
213#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
214#[serde(deny_unknown_fields)]
215pub struct ReleaseCompatibility {
216    pub min_redevplugin_version: String,
217    pub min_runtime_version: String,
218    pub ui_protocol_version: String,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub supported_targets: Option<Vec<String>>,
221}
222
223#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
224#[serde(deny_unknown_fields)]
225pub struct HostCapabilityContractRef {
226    pub publisher_id: String,
227    pub contract_id: String,
228    pub contract_version: String,
229    pub artifact_ref: String,
230    pub artifact_sha256: String,
231    pub manifest_ref: String,
232    pub manifest_sha256: String,
233    pub signature_ref: String,
234    pub signature_sha256: String,
235    pub signature_key_id: String,
236    pub signature_policy_epoch: String,
237    pub signature_revocation_epoch: String,
238    pub compatibility_ref: String,
239    pub compatibility_sha256: String,
240    pub generated_client_ref: String,
241    pub generated_client_sha256: String,
242    pub notices_ref: String,
243    pub notices_sha256: String,
244}
245
246#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
247#[serde(deny_unknown_fields)]
248pub struct HostCapabilityRequirementRef {
249    pub capability_id: String,
250    pub capability_version: String,
251    pub contract: HostCapabilityContractRef,
252}
253
254#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
255#[serde(deny_unknown_fields)]
256pub struct ReleaseHostRequirement {
257    pub host_id: String,
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub min_host_version: Option<String>,
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub required_capability_contracts: Option<Vec<HostCapabilityRequirementRef>>,
262}
263
264#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
265#[serde(deny_unknown_fields)]
266pub struct ReleaseEvidence {
267    #[serde(skip_serializing_if = "Option::is_none")]
268    pub notices_sha256: Option<String>,
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub provenance_sha256: Option<String>,
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub generated_at: Option<String>,
273}
274
275#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
276#[serde(deny_unknown_fields)]
277pub struct ReleaseMetadataV5 {
278    pub schema_version: String,
279    pub source_id: String,
280    pub release_metadata_ref: String,
281    pub publisher_id: String,
282    pub plugin_id: String,
283    pub version: String,
284    pub distribution_ref: ReleaseDistributionRef,
285    pub hashes: ReleasePackageHashSet,
286    pub release_metadata_signature: ReleaseMetadataSignatureRef,
287    pub package_signature: PackageReleaseSignatureRef,
288    pub compatibility: ReleaseCompatibility,
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub host_requirements: Option<Vec<ReleaseHostRequirement>>,
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub release_evidence: Option<ReleaseEvidence>,
293    #[serde(skip_serializing_if = "Option::is_none")]
294    pub metadata: Option<BTreeMap<String, String>>,
295}
296
297#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
298#[serde(deny_unknown_fields)]
299pub struct SourcePolicyLimits {
300    pub document_max_lifetime_seconds: u32,
301    pub future_skew_seconds: u32,
302    pub activation_lease_max_seconds: u32,
303    pub refresh_interval_max_seconds: u32,
304    pub failure_teardown_deadline_seconds: u32,
305}
306
307impl Default for SourcePolicyLimits {
308    fn default() -> Self {
309        Self {
310            document_max_lifetime_seconds: 86_400,
311            future_skew_seconds: 300,
312            activation_lease_max_seconds: 300,
313            refresh_interval_max_seconds: 60,
314            failure_teardown_deadline_seconds: 30,
315        }
316    }
317}
318
319#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
320#[serde(deny_unknown_fields)]
321pub struct SourcePolicyActiveKeys {
322    pub package: Vec<String>,
323    pub release_metadata: Vec<String>,
324    pub host_capability_contract: Vec<String>,
325    pub source_policy_pointer: Vec<String>,
326    pub revocation_document: Vec<String>,
327    pub revocation_pointer: Vec<String>,
328}
329
330#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
331#[serde(deny_unknown_fields)]
332pub struct SourcePolicyCapabilityPublisherScope {
333    pub key_id: String,
334    pub allowed_publishers: Vec<String>,
335}
336
337#[derive(Clone, Debug, Eq, PartialEq)]
338pub struct SourcePolicyInput {
339    pub source_id: String,
340    pub channel: String,
341    pub epoch: String,
342    pub previous_epoch: String,
343    pub previous_document_sha256: String,
344    pub root_epoch: String,
345    pub source_type: String,
346    pub source_class: String,
347    pub allowed_publishers: Vec<String>,
348    pub allowed_artifact_hosts: Vec<String>,
349    pub active_keys: SourcePolicyActiveKeys,
350    pub capability_publisher_scopes: Vec<SourcePolicyCapabilityPublisherScope>,
351    pub require_signature: bool,
352    pub install_policy: String,
353    pub unsigned_policy: String,
354    pub downgrade_policy: String,
355    pub minimum_revocation_epoch: String,
356    pub limits: SourcePolicyLimits,
357    pub generated_at: String,
358    pub expires_at: String,
359    pub key_id: String,
360}
361
362#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
363#[serde(deny_unknown_fields)]
364pub struct SourcePolicyV2 {
365    pub schema_version: String,
366    pub source_id: String,
367    pub channel: String,
368    pub epoch: String,
369    pub previous_epoch: String,
370    pub previous_document_sha256: String,
371    pub root_epoch: String,
372    pub source_type: String,
373    pub source_class: String,
374    pub allowed_publishers: Vec<String>,
375    pub allowed_artifact_hosts: Vec<String>,
376    pub active_keys: SourcePolicyActiveKeys,
377    pub capability_publisher_scopes: Vec<SourcePolicyCapabilityPublisherScope>,
378    pub require_signature: bool,
379    pub install_policy: String,
380    pub unsigned_policy: String,
381    pub downgrade_policy: String,
382    pub minimum_revocation_epoch: String,
383    pub limits: SourcePolicyLimits,
384    pub generated_at: String,
385    pub expires_at: String,
386    pub key_id: String,
387    pub signature: String,
388}
389
390#[derive(Clone, Debug, Eq, PartialEq)]
391pub struct ReleasePointerInput {
392    pub source_id: String,
393    pub channel: String,
394    pub epoch: String,
395    pub previous_epoch: String,
396    pub previous_document_sha256: String,
397    pub r#ref: String,
398    pub document_sha256: String,
399    pub generated_at: String,
400    pub expires_at: String,
401    pub key_id: String,
402}
403
404#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
405#[serde(deny_unknown_fields)]
406pub struct SourcePolicyPointerV1 {
407    pub schema_version: String,
408    pub source_id: String,
409    pub channel: String,
410    pub epoch: String,
411    pub previous_epoch: String,
412    pub previous_document_sha256: String,
413    #[serde(rename = "ref")]
414    pub r#ref: String,
415    pub document_sha256: String,
416    pub generated_at: String,
417    pub expires_at: String,
418    pub key_id: String,
419    pub signature: String,
420}
421
422#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
423#[serde(deny_unknown_fields)]
424pub struct RevokedRelease {
425    pub publisher_id: String,
426    pub plugin_id: String,
427    pub version: String,
428    pub release_metadata_sha256: String,
429    pub revoked_at: String,
430}
431
432#[derive(Clone, Debug, Eq, PartialEq)]
433pub struct RevocationInput {
434    pub source_id: String,
435    pub channel: String,
436    pub epoch: String,
437    pub previous_epoch: String,
438    pub previous_document_sha256: String,
439    pub root_epoch: String,
440    pub generated_at: String,
441    pub expires_at: String,
442    pub revoked_key_ids: Vec<String>,
443    pub revoked_releases: Vec<RevokedRelease>,
444    pub key_id: String,
445}
446
447#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
448#[serde(deny_unknown_fields)]
449pub struct RevocationV2 {
450    pub schema_version: String,
451    pub source_id: String,
452    pub channel: String,
453    pub epoch: String,
454    pub previous_epoch: String,
455    pub previous_document_sha256: String,
456    pub root_epoch: String,
457    pub generated_at: String,
458    pub expires_at: String,
459    pub revoked_key_ids: Vec<String>,
460    pub revoked_releases: Vec<RevokedRelease>,
461    pub key_id: String,
462    pub signature: String,
463}
464
465#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
466#[serde(deny_unknown_fields)]
467pub struct RevocationPointerV1 {
468    pub schema_version: String,
469    pub source_id: String,
470    pub channel: String,
471    pub epoch: String,
472    pub previous_epoch: String,
473    pub previous_document_sha256: String,
474    #[serde(rename = "ref")]
475    pub r#ref: String,
476    pub document_sha256: String,
477    pub generated_at: String,
478    pub expires_at: String,
479    pub key_id: String,
480    pub signature: String,
481}
482
483#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
484#[serde(deny_unknown_fields)]
485pub struct SigningLedgerEvidenceV1 {
486    pub schema_version: String,
487    pub source_id: String,
488    #[serde(default, skip_serializing_if = "Option::is_none")]
489    pub channel: Option<String>,
490    pub subject_identity_sha256: String,
491    pub signing_preimage_sha256: String,
492    pub signature_envelope_sha256: String,
493    pub receipt_ref: String,
494    pub receipt_sha256: String,
495    pub checkpoint_ref: String,
496    pub checkpoint_sha256: String,
497    pub inclusion_proof_ref: String,
498    pub inclusion_proof_sha256: String,
499    pub latest_proof_ref: String,
500    pub latest_proof_sha256: String,
501    #[serde(default, skip_serializing_if = "Option::is_none")]
502    pub consistency_proof_ref: Option<String>,
503    #[serde(default, skip_serializing_if = "Option::is_none")]
504    pub consistency_proof_sha256: Option<String>,
505}
506
507#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
508#[serde(rename_all = "snake_case")]
509pub enum SigningSubjectUsage {
510    RootDelegation,
511    Package,
512    ReleaseMetadata,
513    SourcePolicyDocument,
514    SourcePolicyPointer,
515    RevocationDocument,
516    RevocationPointer,
517}
518
519#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
520#[serde(deny_unknown_fields)]
521pub struct SigningSubjectV1 {
522    pub schema_version: String,
523    pub usage: SigningSubjectUsage,
524    pub source_id: String,
525    #[serde(default, skip_serializing_if = "Option::is_none")]
526    pub channel: Option<String>,
527    #[serde(default, skip_serializing_if = "Option::is_none")]
528    pub root_epoch: Option<String>,
529    #[serde(default, skip_serializing_if = "Option::is_none")]
530    pub publisher_id: Option<String>,
531    #[serde(default, skip_serializing_if = "Option::is_none")]
532    pub plugin_id: Option<String>,
533    #[serde(default, skip_serializing_if = "Option::is_none")]
534    pub version: Option<String>,
535    #[serde(default, skip_serializing_if = "Option::is_none")]
536    pub artifact_or_metadata_identity_sha256: Option<String>,
537    #[serde(default, skip_serializing_if = "Option::is_none")]
538    pub epoch: Option<String>,
539}
540
541#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
542#[serde(deny_unknown_fields)]
543pub struct SignatureEnvelopeV1 {
544    pub schema_version: String,
545    pub subject_identity_sha256: String,
546    pub signing_preimage_sha256: String,
547    pub algorithm: String,
548    pub key_id: String,
549    pub signature: String,
550}
551
552#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
553#[serde(rename_all = "snake_case")]
554pub enum SigningLedgerEntryState {
555    Reserved,
556    Finalized,
557    TerminalFailed,
558}
559
560#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
561#[serde(rename_all = "snake_case")]
562pub enum SigningLedgerFailureCode {
563    SignerRejected,
564    SubjectConflict,
565    LedgerRejected,
566}
567
568#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
569#[serde(deny_unknown_fields)]
570pub struct SigningLedgerEntryV1 {
571    pub schema_version: String,
572    pub state: SigningLedgerEntryState,
573    pub subject: SigningSubjectV1,
574    pub subject_identity_sha256: String,
575    pub signing_preimage_sha256: String,
576    pub algorithm: String,
577    pub key_id: String,
578    pub revision: u64,
579    pub reserved_at: String,
580    #[serde(default, skip_serializing_if = "Option::is_none")]
581    pub signature_envelope: Option<SignatureEnvelopeV1>,
582    #[serde(default, skip_serializing_if = "Option::is_none")]
583    pub signature_envelope_sha256: Option<String>,
584    #[serde(default, skip_serializing_if = "Option::is_none")]
585    pub finalized_at: Option<String>,
586    #[serde(default, skip_serializing_if = "Option::is_none")]
587    pub failure_code: Option<SigningLedgerFailureCode>,
588    #[serde(default, skip_serializing_if = "Option::is_none")]
589    pub failed_at: Option<String>,
590}
591
592#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
593#[serde(deny_unknown_fields)]
594pub struct SigningLedgerLogLeafV1 {
595    pub schema_version: String,
596    pub source_id: String,
597    #[serde(default, skip_serializing_if = "Option::is_none")]
598    pub channel: Option<String>,
599    pub subject_identity_sha256: String,
600    pub signing_preimage_sha256: String,
601    pub signature_envelope_sha256: String,
602    pub sequence: u64,
603}
604
605#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
606#[serde(deny_unknown_fields)]
607pub struct SigningLedgerCheckpointV1 {
608    pub schema_version: String,
609    pub kind: String,
610    pub log_id: String,
611    pub tree_size: u64,
612    pub log_root_hash: String,
613    pub latest_map_root_hash: String,
614    pub checkpoint_time: String,
615    pub key_id: String,
616    pub signature: String,
617}
618
619#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
620#[serde(deny_unknown_fields)]
621pub struct SigningLedgerReceiptV1 {
622    pub schema_version: String,
623    pub log_id: String,
624    pub source_id: String,
625    #[serde(default, skip_serializing_if = "Option::is_none")]
626    pub channel: Option<String>,
627    pub subject_identity_sha256: String,
628    pub signing_preimage_sha256: String,
629    pub signature_envelope_sha256: String,
630    pub sequence: u64,
631    pub leaf_index: u64,
632    pub tree_size: u64,
633    pub log_root_hash: String,
634    pub latest_map_root_hash: String,
635    pub checkpoint_sha256: String,
636    pub checkpoint_time: String,
637    pub key_id: String,
638    pub signature: String,
639}
640
641#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
642#[serde(deny_unknown_fields)]
643pub struct SigningLedgerInclusionProofV1 {
644    pub schema_version: String,
645    pub kind: String,
646    pub log_id: String,
647    pub leaf_index: u64,
648    pub tree_size: u64,
649    pub nodes: Vec<String>,
650}
651
652#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
653#[serde(deny_unknown_fields)]
654pub struct SigningLedgerLatestProofV1 {
655    pub schema_version: String,
656    pub kind: String,
657    pub log_id: String,
658    pub subject_identity_sha256: String,
659    pub present: bool,
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub sequence: Option<u64>,
662    #[serde(default, skip_serializing_if = "Option::is_none")]
663    pub signing_preimage_sha256: Option<String>,
664    #[serde(default, skip_serializing_if = "Option::is_none")]
665    pub signature_envelope_sha256: Option<String>,
666    pub siblings: Vec<String>,
667}
668
669#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
670#[serde(deny_unknown_fields)]
671pub struct SigningLedgerConsistencyProofV1 {
672    pub schema_version: String,
673    pub kind: String,
674    pub log_id: String,
675    pub old_tree_size: u64,
676    pub new_tree_size: u64,
677    pub nodes: Vec<String>,
678}
679
680#[derive(Clone, Copy, Debug, Eq, PartialEq)]
681pub enum ReleaseContractError {
682    InvalidDocument,
683    InvalidSignature,
684}
685
686impl fmt::Display for ReleaseContractError {
687    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
688        match self {
689            Self::InvalidDocument => formatter.write_str("release contract document is invalid"),
690            Self::InvalidSignature => formatter.write_str("release contract signature is invalid"),
691        }
692    }
693}
694
695impl Error for ReleaseContractError {}
696
697#[derive(Clone, Copy, Debug)]
698pub struct SignatureVerificationRequest<'a> {
699    pub usage: SigningUsage,
700    pub key_id: &'a str,
701    pub signing_preimage_sha256: [u8; 32],
702    pub signature: &'a [u8],
703}
704
705pub trait SignatureVerifier {
706    fn verify_signature(&self, request: SignatureVerificationRequest<'_>) -> bool;
707}
708
709impl<F> SignatureVerifier for F
710where
711    F: for<'a> Fn(SignatureVerificationRequest<'a>) -> bool,
712{
713    fn verify_signature(&self, request: SignatureVerificationRequest<'_>) -> bool {
714        self(request)
715    }
716}
717
718pub fn build_root_delegation(
719    input: &RootDelegationInput,
720    signature: &[u8],
721) -> Result<RootDelegationV1, ReleaseContractError> {
722    require_signature_bytes(signature)?;
723    let document = root_delegation_from_input(input, encode_signature(signature));
724    validate_root_delegation(&document, true)?;
725    Ok(document)
726}
727
728pub fn root_delegation_signing_preimage(
729    input: &RootDelegationInput,
730) -> Result<Vec<u8>, ReleaseContractError> {
731    let document = root_delegation_from_input(input, String::new());
732    validate_root_delegation(&document, false)?;
733    preimage_without_top_level_signature(SigningUsage::RootDelegation, &document)
734}
735
736pub fn canonical_root_delegation(
737    document: &RootDelegationV1,
738) -> Result<Vec<u8>, ReleaseContractError> {
739    validate_root_delegation(document, true)?;
740    canonical_json(document)
741}
742
743pub fn verify_root_delegation(
744    document: &RootDelegationV1,
745    verifier: &impl SignatureVerifier,
746) -> Result<(), ReleaseContractError> {
747    let input = RootDelegationInput {
748        source_id: document.source_id.clone(),
749        root_epoch: document.root_epoch.clone(),
750        previous_root_epoch: document.previous_root_epoch.clone(),
751        previous_delegation_sha256: document.previous_delegation_sha256.clone(),
752        generated_at: document.generated_at.clone(),
753        expires_at: document.expires_at.clone(),
754        delegated_keys: document.delegated_keys.clone(),
755        key_id: document.key_id.clone(),
756    };
757    verify_encoded_signature(
758        SigningUsage::RootDelegation,
759        &document.key_id,
760        &root_delegation_signing_preimage(&input)?,
761        &document.signature,
762        verifier,
763    )
764}
765
766pub fn build_package_signature(
767    input: &PackageSigningInput,
768    signature: &[u8],
769) -> Result<PackageSignatureV1, ReleaseContractError> {
770    require_signature_bytes(signature)?;
771    let document = PackageSignatureV1 {
772        schema_version: PACKAGE_SIGNATURE_SCHEMA_VERSION.to_owned(),
773        algorithm: input.algorithm.clone(),
774        key_id: input.key_id.clone(),
775        publisher_id: Some(input.publisher_id.clone()),
776        plugin_id: Some(input.plugin_id.clone()),
777        package_hash: input.package_hash.clone(),
778        manifest_hash: input.manifest_hash.clone(),
779        entries_hash: input.entries_hash.clone(),
780        signature: encode_signature(signature),
781        signed_at: Some(input.signed_at.clone()),
782    };
783    validate_package_signature(
784        &PackageVerificationContext {
785            source_id: input.source_id.clone(),
786            channel: input.channel.clone(),
787            version: input.version.clone(),
788        },
789        &document,
790        true,
791    )?;
792    Ok(document)
793}
794
795pub fn package_signing_preimage(
796    input: &PackageSigningInput,
797) -> Result<Vec<u8>, ReleaseContractError> {
798    validate_package_input(input)?;
799    let payload = serde_json::json!({
800        "channel": input.channel,
801        "package_signature": {
802            "algorithm": input.algorithm,
803            "entries_hash": input.entries_hash,
804            "key_id": input.key_id,
805            "manifest_hash": input.manifest_hash,
806            "package_hash": input.package_hash,
807            "plugin_id": input.plugin_id,
808            "publisher_id": input.publisher_id,
809            "schema_version": PACKAGE_SIGNATURE_SCHEMA_VERSION,
810            "signed_at": input.signed_at,
811        },
812        "source_id": input.source_id,
813        "version": input.version,
814    });
815    signing_preimage(SigningUsage::Package, &payload)
816}
817
818pub fn canonical_package_signature(
819    context: &PackageVerificationContext,
820    document: &PackageSignatureV1,
821) -> Result<Vec<u8>, ReleaseContractError> {
822    validate_package_signature(context, document, true)?;
823    canonical_json(document)
824}
825
826pub fn verify_package_signature(
827    context: &PackageVerificationContext,
828    document: &PackageSignatureV1,
829    verifier: &impl SignatureVerifier,
830) -> Result<(), ReleaseContractError> {
831    validate_package_signature(context, document, true)?;
832    let input = package_input_from_document(context, document)?;
833    verify_encoded_signature(
834        SigningUsage::Package,
835        &document.key_id,
836        &package_signing_preimage(&input)?,
837        &document.signature,
838        verifier,
839    )
840}
841
842pub fn build_release_metadata(
843    document: &ReleaseMetadataV5,
844) -> Result<ReleaseMetadataV5, ReleaseContractError> {
845    validate_release_metadata(document)?;
846    Ok(document.clone())
847}
848
849pub fn release_metadata_signing_preimage(
850    channel: &str,
851    document: &ReleaseMetadataV5,
852) -> Result<Vec<u8>, ReleaseContractError> {
853    if !valid_new_id(channel) {
854        return Err(ReleaseContractError::InvalidDocument);
855    }
856    let built = build_release_metadata(document)?;
857    let payload = serde_json::json!({"channel": channel, "release_metadata": built});
858    signing_preimage(SigningUsage::ReleaseMetadata, &payload)
859}
860
861pub fn canonical_release_metadata(
862    document: &ReleaseMetadataV5,
863) -> Result<Vec<u8>, ReleaseContractError> {
864    validate_release_metadata(document)?;
865    canonical_json(document)
866}
867
868pub fn verify_release_metadata(
869    channel: &str,
870    document: &ReleaseMetadataV5,
871    signature: &[u8],
872    verifier: &impl SignatureVerifier,
873) -> Result<(), ReleaseContractError> {
874    require_signature_bytes(signature).map_err(|_| ReleaseContractError::InvalidSignature)?;
875    verify_raw_signature(
876        SigningUsage::ReleaseMetadata,
877        &document.release_metadata_signature.key_id,
878        &release_metadata_signing_preimage(channel, document)?,
879        signature,
880        verifier,
881    )
882}
883
884pub fn build_source_policy(
885    input: &SourcePolicyInput,
886    signature: &[u8],
887) -> Result<SourcePolicyV2, ReleaseContractError> {
888    require_signature_bytes(signature)?;
889    let document = source_policy_from_input(input, encode_signature(signature));
890    validate_source_policy(&document, true)?;
891    Ok(document)
892}
893
894pub fn source_policy_signing_preimage(
895    input: &SourcePolicyInput,
896) -> Result<Vec<u8>, ReleaseContractError> {
897    let document = source_policy_from_input(input, String::new());
898    validate_source_policy(&document, false)?;
899    preimage_without_top_level_signature(SigningUsage::SourcePolicy, &document)
900}
901
902pub fn canonical_source_policy(document: &SourcePolicyV2) -> Result<Vec<u8>, ReleaseContractError> {
903    validate_source_policy(document, true)?;
904    canonical_json(document)
905}
906
907pub fn verify_source_policy(
908    document: &SourcePolicyV2,
909    verifier: &impl SignatureVerifier,
910) -> Result<(), ReleaseContractError> {
911    let input = source_policy_input_from_document(document);
912    verify_encoded_signature(
913        SigningUsage::SourcePolicy,
914        &document.key_id,
915        &source_policy_signing_preimage(&input)?,
916        &document.signature,
917        verifier,
918    )
919}
920
921pub fn build_source_policy_pointer(
922    input: &ReleasePointerInput,
923    signature: &[u8],
924) -> Result<SourcePolicyPointerV1, ReleaseContractError> {
925    require_signature_bytes(signature)?;
926    let document = SourcePolicyPointerV1 {
927        schema_version: SOURCE_POLICY_POINTER_SCHEMA_VERSION.to_owned(),
928        source_id: input.source_id.clone(),
929        channel: input.channel.clone(),
930        epoch: input.epoch.clone(),
931        previous_epoch: input.previous_epoch.clone(),
932        previous_document_sha256: input.previous_document_sha256.clone(),
933        r#ref: input.r#ref.clone(),
934        document_sha256: input.document_sha256.clone(),
935        generated_at: input.generated_at.clone(),
936        expires_at: input.expires_at.clone(),
937        key_id: input.key_id.clone(),
938        signature: encode_signature(signature),
939    };
940    validate_source_policy_pointer(&document, true)?;
941    Ok(document)
942}
943
944pub fn source_policy_pointer_signing_preimage(
945    input: &ReleasePointerInput,
946) -> Result<Vec<u8>, ReleaseContractError> {
947    let document = SourcePolicyPointerV1 {
948        schema_version: SOURCE_POLICY_POINTER_SCHEMA_VERSION.to_owned(),
949        source_id: input.source_id.clone(),
950        channel: input.channel.clone(),
951        epoch: input.epoch.clone(),
952        previous_epoch: input.previous_epoch.clone(),
953        previous_document_sha256: input.previous_document_sha256.clone(),
954        r#ref: input.r#ref.clone(),
955        document_sha256: input.document_sha256.clone(),
956        generated_at: input.generated_at.clone(),
957        expires_at: input.expires_at.clone(),
958        key_id: input.key_id.clone(),
959        signature: String::new(),
960    };
961    validate_source_policy_pointer(&document, false)?;
962    preimage_without_top_level_signature(SigningUsage::SourcePolicyPointer, &document)
963}
964
965pub fn canonical_source_policy_pointer(
966    document: &SourcePolicyPointerV1,
967) -> Result<Vec<u8>, ReleaseContractError> {
968    validate_source_policy_pointer(document, true)?;
969    canonical_json(document)
970}
971
972pub fn verify_source_policy_pointer(
973    document: &SourcePolicyPointerV1,
974    verifier: &impl SignatureVerifier,
975) -> Result<(), ReleaseContractError> {
976    let input = pointer_input_from_source_policy(document);
977    verify_encoded_signature(
978        SigningUsage::SourcePolicyPointer,
979        &document.key_id,
980        &source_policy_pointer_signing_preimage(&input)?,
981        &document.signature,
982        verifier,
983    )
984}
985
986pub fn build_revocation(
987    input: &RevocationInput,
988    signature: &[u8],
989) -> Result<RevocationV2, ReleaseContractError> {
990    require_signature_bytes(signature)?;
991    let document = revocation_from_input(input, encode_signature(signature));
992    validate_revocation(&document, true)?;
993    Ok(document)
994}
995
996pub fn revocation_signing_preimage(
997    input: &RevocationInput,
998) -> Result<Vec<u8>, ReleaseContractError> {
999    let document = revocation_from_input(input, String::new());
1000    validate_revocation(&document, false)?;
1001    preimage_without_top_level_signature(SigningUsage::Revocation, &document)
1002}
1003
1004pub fn canonical_revocation(document: &RevocationV2) -> Result<Vec<u8>, ReleaseContractError> {
1005    validate_revocation(document, true)?;
1006    canonical_json(document)
1007}
1008
1009pub fn verify_revocation(
1010    document: &RevocationV2,
1011    verifier: &impl SignatureVerifier,
1012) -> Result<(), ReleaseContractError> {
1013    let input = revocation_input_from_document(document);
1014    verify_encoded_signature(
1015        SigningUsage::Revocation,
1016        &document.key_id,
1017        &revocation_signing_preimage(&input)?,
1018        &document.signature,
1019        verifier,
1020    )
1021}
1022
1023pub fn build_revocation_pointer(
1024    input: &ReleasePointerInput,
1025    signature: &[u8],
1026) -> Result<RevocationPointerV1, ReleaseContractError> {
1027    require_signature_bytes(signature)?;
1028    let document = RevocationPointerV1 {
1029        schema_version: REVOCATION_POINTER_SCHEMA_VERSION.to_owned(),
1030        source_id: input.source_id.clone(),
1031        channel: input.channel.clone(),
1032        epoch: input.epoch.clone(),
1033        previous_epoch: input.previous_epoch.clone(),
1034        previous_document_sha256: input.previous_document_sha256.clone(),
1035        r#ref: input.r#ref.clone(),
1036        document_sha256: input.document_sha256.clone(),
1037        generated_at: input.generated_at.clone(),
1038        expires_at: input.expires_at.clone(),
1039        key_id: input.key_id.clone(),
1040        signature: encode_signature(signature),
1041    };
1042    validate_revocation_pointer(&document, true)?;
1043    Ok(document)
1044}
1045
1046pub fn revocation_pointer_signing_preimage(
1047    input: &ReleasePointerInput,
1048) -> Result<Vec<u8>, ReleaseContractError> {
1049    let document = RevocationPointerV1 {
1050        schema_version: REVOCATION_POINTER_SCHEMA_VERSION.to_owned(),
1051        source_id: input.source_id.clone(),
1052        channel: input.channel.clone(),
1053        epoch: input.epoch.clone(),
1054        previous_epoch: input.previous_epoch.clone(),
1055        previous_document_sha256: input.previous_document_sha256.clone(),
1056        r#ref: input.r#ref.clone(),
1057        document_sha256: input.document_sha256.clone(),
1058        generated_at: input.generated_at.clone(),
1059        expires_at: input.expires_at.clone(),
1060        key_id: input.key_id.clone(),
1061        signature: String::new(),
1062    };
1063    validate_revocation_pointer(&document, false)?;
1064    preimage_without_top_level_signature(SigningUsage::RevocationPointer, &document)
1065}
1066
1067pub fn canonical_revocation_pointer(
1068    document: &RevocationPointerV1,
1069) -> Result<Vec<u8>, ReleaseContractError> {
1070    validate_revocation_pointer(document, true)?;
1071    canonical_json(document)
1072}
1073
1074pub fn verify_revocation_pointer(
1075    document: &RevocationPointerV1,
1076    verifier: &impl SignatureVerifier,
1077) -> Result<(), ReleaseContractError> {
1078    let input = pointer_input_from_revocation(document);
1079    verify_encoded_signature(
1080        SigningUsage::RevocationPointer,
1081        &document.key_id,
1082        &revocation_pointer_signing_preimage(&input)?,
1083        &document.signature,
1084        verifier,
1085    )
1086}
1087
1088pub fn decode_root_delegation(raw: &[u8]) -> Result<RootDelegationV1, ReleaseContractError> {
1089    decode_canonical_document(raw, |value| validate_root_delegation(value, true))
1090}
1091
1092pub fn decode_package_signature(
1093    raw: &[u8],
1094    context: &PackageVerificationContext,
1095) -> Result<PackageSignatureV1, ReleaseContractError> {
1096    decode_canonical_document(raw, |value| {
1097        validate_package_signature(context, value, true)
1098    })
1099}
1100
1101pub fn decode_release_metadata(raw: &[u8]) -> Result<ReleaseMetadataV5, ReleaseContractError> {
1102    decode_canonical_document(raw, validate_release_metadata)
1103}
1104
1105pub fn decode_source_policy(raw: &[u8]) -> Result<SourcePolicyV2, ReleaseContractError> {
1106    decode_canonical_document(raw, |value| validate_source_policy(value, true))
1107}
1108
1109pub fn decode_source_policy_pointer(
1110    raw: &[u8],
1111) -> Result<SourcePolicyPointerV1, ReleaseContractError> {
1112    decode_canonical_document(raw, |value| validate_source_policy_pointer(value, true))
1113}
1114
1115pub fn decode_revocation(raw: &[u8]) -> Result<RevocationV2, ReleaseContractError> {
1116    decode_canonical_document(raw, |value| validate_revocation(value, true))
1117}
1118
1119pub fn decode_revocation_pointer(raw: &[u8]) -> Result<RevocationPointerV1, ReleaseContractError> {
1120    decode_canonical_document(raw, |value| validate_revocation_pointer(value, true))
1121}
1122
1123pub fn decode_signing_ledger_evidence(
1124    raw: &[u8],
1125) -> Result<SigningLedgerEvidenceV1, ReleaseContractError> {
1126    if raw.len() > 64 * 1024 {
1127        return Err(ReleaseContractError::InvalidDocument);
1128    }
1129    decode_canonical_document(raw, validate_signing_ledger_evidence)
1130}
1131
1132pub fn canonical_signing_subject(
1133    value: &SigningSubjectV1,
1134) -> Result<Vec<u8>, ReleaseContractError> {
1135    validate_signing_subject(value)?;
1136    canonical_json(value)
1137}
1138
1139pub fn decode_signing_subject(raw: &[u8]) -> Result<SigningSubjectV1, ReleaseContractError> {
1140    decode_canonical_document(raw, validate_signing_subject)
1141}
1142
1143pub fn canonical_signature_envelope(
1144    value: &SignatureEnvelopeV1,
1145) -> Result<Vec<u8>, ReleaseContractError> {
1146    validate_signature_envelope(value)?;
1147    canonical_json(value)
1148}
1149
1150pub fn decode_signature_envelope(raw: &[u8]) -> Result<SignatureEnvelopeV1, ReleaseContractError> {
1151    decode_canonical_document(raw, validate_signature_envelope)
1152}
1153
1154pub fn canonical_signing_ledger_entry(
1155    value: &SigningLedgerEntryV1,
1156) -> Result<Vec<u8>, ReleaseContractError> {
1157    validate_signing_ledger_entry(value)?;
1158    canonical_json(value)
1159}
1160
1161pub fn decode_signing_ledger_entry(
1162    raw: &[u8],
1163) -> Result<SigningLedgerEntryV1, ReleaseContractError> {
1164    decode_canonical_document(raw, validate_signing_ledger_entry)
1165}
1166
1167pub fn decode_signing_ledger_log_leaf(
1168    raw: &[u8],
1169) -> Result<SigningLedgerLogLeafV1, ReleaseContractError> {
1170    decode_canonical_document(raw, validate_signing_ledger_log_leaf)
1171}
1172
1173pub fn decode_signing_ledger_checkpoint(
1174    raw: &[u8],
1175) -> Result<SigningLedgerCheckpointV1, ReleaseContractError> {
1176    decode_canonical_document(raw, validate_signing_ledger_checkpoint)
1177}
1178
1179pub fn decode_signing_ledger_receipt(
1180    raw: &[u8],
1181) -> Result<SigningLedgerReceiptV1, ReleaseContractError> {
1182    decode_canonical_document(raw, validate_signing_ledger_receipt)
1183}
1184
1185pub fn decode_signing_ledger_inclusion_proof(
1186    raw: &[u8],
1187) -> Result<SigningLedgerInclusionProofV1, ReleaseContractError> {
1188    decode_canonical_document(raw, validate_signing_ledger_inclusion_proof)
1189}
1190
1191pub fn decode_signing_ledger_latest_proof(
1192    raw: &[u8],
1193) -> Result<SigningLedgerLatestProofV1, ReleaseContractError> {
1194    decode_canonical_document(raw, validate_signing_ledger_latest_proof)
1195}
1196
1197pub fn decode_signing_ledger_consistency_proof(
1198    raw: &[u8],
1199) -> Result<SigningLedgerConsistencyProofV1, ReleaseContractError> {
1200    decode_canonical_document(raw, validate_signing_ledger_consistency_proof)
1201}
1202
1203fn root_delegation_from_input(input: &RootDelegationInput, signature: String) -> RootDelegationV1 {
1204    RootDelegationV1 {
1205        schema_version: ROOT_DELEGATION_SCHEMA_VERSION.to_owned(),
1206        source_id: input.source_id.clone(),
1207        root_epoch: input.root_epoch.clone(),
1208        previous_root_epoch: input.previous_root_epoch.clone(),
1209        previous_delegation_sha256: input.previous_delegation_sha256.clone(),
1210        generated_at: input.generated_at.clone(),
1211        expires_at: input.expires_at.clone(),
1212        delegated_keys: input.delegated_keys.clone(),
1213        key_id: input.key_id.clone(),
1214        signature,
1215    }
1216}
1217
1218fn source_policy_from_input(input: &SourcePolicyInput, signature: String) -> SourcePolicyV2 {
1219    SourcePolicyV2 {
1220        schema_version: SOURCE_POLICY_SCHEMA_VERSION.to_owned(),
1221        source_id: input.source_id.clone(),
1222        channel: input.channel.clone(),
1223        epoch: input.epoch.clone(),
1224        previous_epoch: input.previous_epoch.clone(),
1225        previous_document_sha256: input.previous_document_sha256.clone(),
1226        root_epoch: input.root_epoch.clone(),
1227        source_type: input.source_type.clone(),
1228        source_class: input.source_class.clone(),
1229        allowed_publishers: input.allowed_publishers.clone(),
1230        allowed_artifact_hosts: input.allowed_artifact_hosts.clone(),
1231        active_keys: input.active_keys.clone(),
1232        capability_publisher_scopes: input.capability_publisher_scopes.clone(),
1233        require_signature: input.require_signature,
1234        install_policy: input.install_policy.clone(),
1235        unsigned_policy: input.unsigned_policy.clone(),
1236        downgrade_policy: input.downgrade_policy.clone(),
1237        minimum_revocation_epoch: input.minimum_revocation_epoch.clone(),
1238        limits: input.limits,
1239        generated_at: input.generated_at.clone(),
1240        expires_at: input.expires_at.clone(),
1241        key_id: input.key_id.clone(),
1242        signature,
1243    }
1244}
1245
1246fn revocation_from_input(input: &RevocationInput, signature: String) -> RevocationV2 {
1247    RevocationV2 {
1248        schema_version: REVOCATION_SCHEMA_VERSION.to_owned(),
1249        source_id: input.source_id.clone(),
1250        channel: input.channel.clone(),
1251        epoch: input.epoch.clone(),
1252        previous_epoch: input.previous_epoch.clone(),
1253        previous_document_sha256: input.previous_document_sha256.clone(),
1254        root_epoch: input.root_epoch.clone(),
1255        generated_at: input.generated_at.clone(),
1256        expires_at: input.expires_at.clone(),
1257        revoked_key_ids: input.revoked_key_ids.clone(),
1258        revoked_releases: input.revoked_releases.clone(),
1259        key_id: input.key_id.clone(),
1260        signature,
1261    }
1262}
1263
1264fn package_input_from_document(
1265    context: &PackageVerificationContext,
1266    document: &PackageSignatureV1,
1267) -> Result<PackageSigningInput, ReleaseContractError> {
1268    Ok(PackageSigningInput {
1269        source_id: context.source_id.clone(),
1270        channel: context.channel.clone(),
1271        version: context.version.clone(),
1272        algorithm: document.algorithm.clone(),
1273        key_id: document.key_id.clone(),
1274        publisher_id: document
1275            .publisher_id
1276            .clone()
1277            .ok_or(ReleaseContractError::InvalidDocument)?,
1278        plugin_id: document
1279            .plugin_id
1280            .clone()
1281            .ok_or(ReleaseContractError::InvalidDocument)?,
1282        package_hash: document.package_hash.clone(),
1283        manifest_hash: document.manifest_hash.clone(),
1284        entries_hash: document.entries_hash.clone(),
1285        signed_at: document
1286            .signed_at
1287            .clone()
1288            .ok_or(ReleaseContractError::InvalidDocument)?,
1289    })
1290}
1291
1292fn source_policy_input_from_document(document: &SourcePolicyV2) -> SourcePolicyInput {
1293    SourcePolicyInput {
1294        source_id: document.source_id.clone(),
1295        channel: document.channel.clone(),
1296        epoch: document.epoch.clone(),
1297        previous_epoch: document.previous_epoch.clone(),
1298        previous_document_sha256: document.previous_document_sha256.clone(),
1299        root_epoch: document.root_epoch.clone(),
1300        source_type: document.source_type.clone(),
1301        source_class: document.source_class.clone(),
1302        allowed_publishers: document.allowed_publishers.clone(),
1303        allowed_artifact_hosts: document.allowed_artifact_hosts.clone(),
1304        active_keys: document.active_keys.clone(),
1305        capability_publisher_scopes: document.capability_publisher_scopes.clone(),
1306        require_signature: document.require_signature,
1307        install_policy: document.install_policy.clone(),
1308        unsigned_policy: document.unsigned_policy.clone(),
1309        downgrade_policy: document.downgrade_policy.clone(),
1310        minimum_revocation_epoch: document.minimum_revocation_epoch.clone(),
1311        limits: document.limits,
1312        generated_at: document.generated_at.clone(),
1313        expires_at: document.expires_at.clone(),
1314        key_id: document.key_id.clone(),
1315    }
1316}
1317
1318fn revocation_input_from_document(document: &RevocationV2) -> RevocationInput {
1319    RevocationInput {
1320        source_id: document.source_id.clone(),
1321        channel: document.channel.clone(),
1322        epoch: document.epoch.clone(),
1323        previous_epoch: document.previous_epoch.clone(),
1324        previous_document_sha256: document.previous_document_sha256.clone(),
1325        root_epoch: document.root_epoch.clone(),
1326        generated_at: document.generated_at.clone(),
1327        expires_at: document.expires_at.clone(),
1328        revoked_key_ids: document.revoked_key_ids.clone(),
1329        revoked_releases: document.revoked_releases.clone(),
1330        key_id: document.key_id.clone(),
1331    }
1332}
1333
1334fn pointer_input_from_source_policy(document: &SourcePolicyPointerV1) -> ReleasePointerInput {
1335    ReleasePointerInput {
1336        source_id: document.source_id.clone(),
1337        channel: document.channel.clone(),
1338        epoch: document.epoch.clone(),
1339        previous_epoch: document.previous_epoch.clone(),
1340        previous_document_sha256: document.previous_document_sha256.clone(),
1341        r#ref: document.r#ref.clone(),
1342        document_sha256: document.document_sha256.clone(),
1343        generated_at: document.generated_at.clone(),
1344        expires_at: document.expires_at.clone(),
1345        key_id: document.key_id.clone(),
1346    }
1347}
1348
1349fn pointer_input_from_revocation(document: &RevocationPointerV1) -> ReleasePointerInput {
1350    ReleasePointerInput {
1351        source_id: document.source_id.clone(),
1352        channel: document.channel.clone(),
1353        epoch: document.epoch.clone(),
1354        previous_epoch: document.previous_epoch.clone(),
1355        previous_document_sha256: document.previous_document_sha256.clone(),
1356        r#ref: document.r#ref.clone(),
1357        document_sha256: document.document_sha256.clone(),
1358        generated_at: document.generated_at.clone(),
1359        expires_at: document.expires_at.clone(),
1360        key_id: document.key_id.clone(),
1361    }
1362}
1363
1364fn canonical_json(value: &impl Serialize) -> Result<Vec<u8>, ReleaseContractError> {
1365    let value = serde_json::to_value(value).map_err(|_| ReleaseContractError::InvalidDocument)?;
1366    validate_canonical_value(&value)?;
1367    serde_json::to_vec(&value).map_err(|_| ReleaseContractError::InvalidDocument)
1368}
1369
1370fn validate_canonical_value(value: &Value) -> Result<(), ReleaseContractError> {
1371    match value {
1372        Value::Null | Value::Bool(_) | Value::String(_) => Ok(()),
1373        Value::Number(number) if number.as_u64().is_some() => Ok(()),
1374        Value::Array(values) => values.iter().try_for_each(validate_canonical_value),
1375        Value::Object(values) => values.values().try_for_each(validate_canonical_value),
1376        _ => Err(ReleaseContractError::InvalidDocument),
1377    }
1378}
1379
1380fn signing_preimage(
1381    usage: SigningUsage,
1382    value: &impl Serialize,
1383) -> Result<Vec<u8>, ReleaseContractError> {
1384    let payload = canonical_json(value)?;
1385    let mut preimage =
1386        Vec::with_capacity(SIGNING_PREFIX.len() + usage.as_str().len() + 1 + payload.len());
1387    preimage.extend_from_slice(SIGNING_PREFIX);
1388    preimage.extend_from_slice(usage.as_str().as_bytes());
1389    preimage.push(0);
1390    preimage.extend_from_slice(&payload);
1391    Ok(preimage)
1392}
1393
1394fn preimage_without_top_level_signature(
1395    usage: SigningUsage,
1396    document: &impl Serialize,
1397) -> Result<Vec<u8>, ReleaseContractError> {
1398    let mut value =
1399        serde_json::to_value(document).map_err(|_| ReleaseContractError::InvalidDocument)?;
1400    let object = value
1401        .as_object_mut()
1402        .ok_or(ReleaseContractError::InvalidDocument)?;
1403    object.remove("signature");
1404    signing_preimage(usage, &value)
1405}
1406
1407fn decode_canonical_document<T>(
1408    raw: &[u8],
1409    validate: impl FnOnce(&T) -> Result<(), ReleaseContractError>,
1410) -> Result<T, ReleaseContractError>
1411where
1412    T: for<'de> Deserialize<'de> + Serialize,
1413{
1414    if raw.is_empty() || raw.len() > MAX_DOCUMENT_BYTES || std::str::from_utf8(raw).is_err() {
1415        return Err(ReleaseContractError::InvalidDocument);
1416    }
1417    let document: T =
1418        serde_json::from_slice(raw).map_err(|_| ReleaseContractError::InvalidDocument)?;
1419    validate(&document)?;
1420    if canonical_json(&document)? != raw {
1421        return Err(ReleaseContractError::InvalidDocument);
1422    }
1423    Ok(document)
1424}
1425
1426fn verify_encoded_signature(
1427    usage: SigningUsage,
1428    key_id: &str,
1429    preimage: &[u8],
1430    encoded_signature: &str,
1431    verifier: &impl SignatureVerifier,
1432) -> Result<(), ReleaseContractError> {
1433    let signature =
1434        decode_signature(encoded_signature).map_err(|_| ReleaseContractError::InvalidSignature)?;
1435    verify_raw_signature(usage, key_id, preimage, &signature, verifier)
1436}
1437
1438fn verify_raw_signature(
1439    usage: SigningUsage,
1440    key_id: &str,
1441    preimage: &[u8],
1442    signature: &[u8],
1443    verifier: &impl SignatureVerifier,
1444) -> Result<(), ReleaseContractError> {
1445    let signing_preimage_sha256: [u8; 32] = Sha256::digest(preimage).into();
1446    if signature.len() != 64
1447        || !verifier.verify_signature(SignatureVerificationRequest {
1448            usage,
1449            key_id,
1450            signing_preimage_sha256,
1451            signature,
1452        })
1453    {
1454        return Err(ReleaseContractError::InvalidSignature);
1455    }
1456    Ok(())
1457}
1458
1459fn require_signature_bytes(signature: &[u8]) -> Result<(), ReleaseContractError> {
1460    if signature.len() != 64 {
1461        return Err(ReleaseContractError::InvalidDocument);
1462    }
1463    Ok(())
1464}
1465
1466fn encode_signature(signature: &[u8]) -> String {
1467    BASE64_STANDARD.encode(signature)
1468}
1469
1470fn decode_signature(value: &str) -> Result<Vec<u8>, ReleaseContractError> {
1471    if value.len() != 88 || !value.ends_with("==") {
1472        return Err(ReleaseContractError::InvalidDocument);
1473    }
1474    let decoded = BASE64_STANDARD
1475        .decode(value)
1476        .map_err(|_| ReleaseContractError::InvalidDocument)?;
1477    if decoded.len() != 64 || BASE64_STANDARD.encode(&decoded) != value {
1478        return Err(ReleaseContractError::InvalidDocument);
1479    }
1480    Ok(decoded)
1481}
1482
1483fn validate_root_delegation(
1484    value: &RootDelegationV1,
1485    require_signature: bool,
1486) -> Result<(), ReleaseContractError> {
1487    if value.schema_version != ROOT_DELEGATION_SCHEMA_VERSION
1488        || !valid_new_id(&value.source_id)
1489        || !valid_new_id(&value.key_id)
1490    {
1491        return invalid_document();
1492    }
1493    validate_epoch_chain(
1494        &value.root_epoch,
1495        &value.previous_root_epoch,
1496        &value.previous_delegation_sha256,
1497    )?;
1498    let (_, expires_at) = validate_time_range(&value.generated_at, &value.expires_at, None)?;
1499    if value.delegated_keys.is_empty() || value.delegated_keys.len() > 32 {
1500        return invalid_document();
1501    }
1502    let mut previous_key_id = "";
1503    for key in &value.delegated_keys {
1504        if key.algorithm != SIGNATURE_ALGORITHM_ED25519
1505            || !valid_new_id(&key.key_id)
1506            || key.key_id.as_str() <= previous_key_id
1507        {
1508            return invalid_document();
1509        }
1510        let public_key = BASE64_STANDARD
1511            .decode(&key.public_key)
1512            .map_err(|_| ReleaseContractError::InvalidDocument)?;
1513        if public_key.len() != 32 || BASE64_STANDARD.encode(&public_key) != key.public_key {
1514            return invalid_document();
1515        }
1516        if key.usages.is_empty() || key.usages.len() > 9 {
1517            return invalid_document();
1518        }
1519        let mut previous_usage = None;
1520        for usage in &key.usages {
1521            let rank = usage.rank();
1522            if previous_usage.is_some_and(|previous| rank <= previous) {
1523                return invalid_document();
1524            }
1525            previous_usage = Some(rank);
1526        }
1527        let source_wide = key.usages.iter().all(|usage| {
1528            matches!(
1529                usage,
1530                DelegatedKeyUsage::SigningLedger | DelegatedKeyUsage::TrustedTime
1531            )
1532        });
1533        let has_source_wide = key.usages.iter().any(|usage| {
1534            matches!(
1535                usage,
1536                DelegatedKeyUsage::SigningLedger | DelegatedKeyUsage::TrustedTime
1537            )
1538        });
1539        if has_source_wide != source_wide {
1540            return invalid_document();
1541        }
1542        validate_sorted_ids(
1543            &key.channels,
1544            if source_wide { 0 } else { 1 },
1545            if source_wide { 0 } else { 16 },
1546            true,
1547        )?;
1548        let (_, valid_until) = validate_time_range(&key.valid_from, &key.valid_until, None)?;
1549        if valid_until > expires_at {
1550            return invalid_document();
1551        }
1552        previous_key_id = &key.key_id;
1553    }
1554    validate_signature_field(&value.signature, require_signature)
1555}
1556
1557fn validate_package_input(value: &PackageSigningInput) -> Result<(), ReleaseContractError> {
1558    if !valid_new_id(&value.source_id)
1559        || !valid_new_id(&value.channel)
1560        || value.algorithm != SIGNATURE_ALGORITHM_ED25519
1561        || !valid_new_id(&value.key_id)
1562        || !valid_legacy_id(&value.publisher_id)
1563        || !valid_legacy_id(&value.plugin_id)
1564        || !valid_semver(&value.version)
1565        || !valid_prefixed_sha256(&value.package_hash)
1566        || !valid_prefixed_sha256(&value.manifest_hash)
1567        || !valid_prefixed_sha256(&value.entries_hash)
1568        || canonical_timestamp_seconds(&value.signed_at).is_none()
1569    {
1570        return invalid_document();
1571    }
1572    Ok(())
1573}
1574
1575fn validate_package_signature(
1576    context: &PackageVerificationContext,
1577    value: &PackageSignatureV1,
1578    require_signature: bool,
1579) -> Result<(), ReleaseContractError> {
1580    if value.schema_version != PACKAGE_SIGNATURE_SCHEMA_VERSION {
1581        return invalid_document();
1582    }
1583    let input = package_input_from_document(context, value)?;
1584    validate_package_input(&input)?;
1585    validate_signature_field(&value.signature, require_signature)
1586}
1587
1588fn validate_release_metadata(value: &ReleaseMetadataV5) -> Result<(), ReleaseContractError> {
1589    if value.schema_version != RELEASE_METADATA_SCHEMA_VERSION
1590        || !valid_new_id(&value.source_id)
1591        || !valid_legacy_id(&value.publisher_id)
1592        || !valid_legacy_id(&value.plugin_id)
1593        || !valid_semver(&value.version)
1594        || !valid_artifact_ref(&value.release_metadata_ref)
1595    {
1596        return invalid_document();
1597    }
1598    if !matches!(
1599        value.distribution_ref.distribution.as_str(),
1600        "registry_ref" | "host_artifact_ref"
1601    ) || !valid_artifact_ref(&value.distribution_ref.artifact_ref)
1602    {
1603        return invalid_document();
1604    }
1605    if !valid_legacy_sha256(&value.hashes.package_sha256)
1606        || !valid_legacy_sha256(&value.hashes.manifest_sha256)
1607        || !valid_legacy_sha256(&value.hashes.entries_sha256)
1608    {
1609        return invalid_document();
1610    }
1611    let metadata_signature = &value.release_metadata_signature;
1612    if metadata_signature.algorithm != SIGNATURE_ALGORITHM_ED25519
1613        || !valid_new_id(&metadata_signature.key_id)
1614        || !valid_artifact_ref(&metadata_signature.signature_ref)
1615        || !valid_epoch(&metadata_signature.source_policy_epoch)
1616        || !valid_epoch(&metadata_signature.revocation_epoch)
1617    {
1618        return invalid_document();
1619    }
1620    let package_signature = &value.package_signature;
1621    if package_signature.algorithm != SIGNATURE_ALGORITHM_ED25519
1622        || !valid_new_id(&package_signature.key_id)
1623        || !valid_artifact_ref(&package_signature.signature_bundle_ref)
1624        || !valid_epoch(&package_signature.source_policy_epoch)
1625        || !valid_epoch(&package_signature.revocation_epoch)
1626    {
1627        return invalid_document();
1628    }
1629    if !valid_semver(&value.compatibility.min_redevplugin_version)
1630        || !valid_semver(&value.compatibility.min_runtime_version)
1631        || value.compatibility.ui_protocol_version != "plugin-ui-v5"
1632    {
1633        return invalid_document();
1634    }
1635    if let Some(targets) = &value.compatibility.supported_targets {
1636        let mut previous = "";
1637        for target in targets {
1638            if !matches!(
1639                target.as_str(),
1640                "darwin/amd64" | "darwin/arm64" | "linux/amd64" | "linux/arm64"
1641            ) || target.as_str() <= previous
1642            {
1643                return invalid_document();
1644            }
1645            previous = target;
1646        }
1647    }
1648    validate_host_requirements(value.host_requirements.as_deref().unwrap_or(&[]))?;
1649    if let Some(evidence) = &value.release_evidence {
1650        if evidence
1651            .notices_sha256
1652            .as_deref()
1653            .is_some_and(|digest| !valid_legacy_sha256(digest))
1654            || evidence
1655                .provenance_sha256
1656                .as_deref()
1657                .is_some_and(|digest| !valid_legacy_sha256(digest))
1658            || evidence
1659                .generated_at
1660                .as_deref()
1661                .is_some_and(|generated| canonical_timestamp_seconds(generated).is_none())
1662        {
1663            return invalid_document();
1664        }
1665    }
1666    if let Some(metadata) = &value.metadata {
1667        if metadata.len() > 128
1668            || metadata
1669                .iter()
1670                .any(|(key, item)| key.is_empty() || key.len() > 128 || item.len() > 4096)
1671        {
1672            return invalid_document();
1673        }
1674    }
1675    Ok(())
1676}
1677
1678fn validate_host_requirements(
1679    values: &[ReleaseHostRequirement],
1680) -> Result<(), ReleaseContractError> {
1681    let mut previous_host = "";
1682    for value in values {
1683        if !valid_legacy_id(&value.host_id) || value.host_id.as_str() <= previous_host {
1684            return invalid_document();
1685        }
1686        if value
1687            .min_host_version
1688            .as_deref()
1689            .is_some_and(|version| !valid_semver(version))
1690        {
1691            return invalid_document();
1692        }
1693        let mut previous_capability = String::new();
1694        for capability in value
1695            .required_capability_contracts
1696            .as_deref()
1697            .unwrap_or(&[])
1698        {
1699            let identity = format!(
1700                "{}\0{}",
1701                capability.capability_id, capability.capability_version
1702            );
1703            if !valid_legacy_id(&capability.capability_id)
1704                || !valid_semver(&capability.capability_version)
1705                || identity <= previous_capability
1706            {
1707                return invalid_document();
1708            }
1709            validate_capability_contract_ref(&capability.contract)?;
1710            previous_capability = identity;
1711        }
1712        previous_host = &value.host_id;
1713    }
1714    Ok(())
1715}
1716
1717fn validate_capability_contract_ref(
1718    value: &HostCapabilityContractRef,
1719) -> Result<(), ReleaseContractError> {
1720    if !valid_legacy_id(&value.publisher_id)
1721        || !valid_legacy_id(&value.contract_id)
1722        || !valid_legacy_id(&value.signature_key_id)
1723        || !valid_semver(&value.contract_version)
1724        || !valid_epoch(&value.signature_policy_epoch)
1725        || !valid_epoch(&value.signature_revocation_epoch)
1726    {
1727        return invalid_document();
1728    }
1729    for reference in [
1730        &value.artifact_ref,
1731        &value.manifest_ref,
1732        &value.signature_ref,
1733        &value.compatibility_ref,
1734        &value.generated_client_ref,
1735        &value.notices_ref,
1736    ] {
1737        if !valid_artifact_ref(reference) {
1738            return invalid_document();
1739        }
1740    }
1741    for digest in [
1742        &value.artifact_sha256,
1743        &value.manifest_sha256,
1744        &value.signature_sha256,
1745        &value.compatibility_sha256,
1746        &value.generated_client_sha256,
1747        &value.notices_sha256,
1748    ] {
1749        if !valid_sha256(digest) {
1750            return invalid_document();
1751        }
1752    }
1753    Ok(())
1754}
1755
1756fn validate_source_policy(
1757    value: &SourcePolicyV2,
1758    require_signature: bool,
1759) -> Result<(), ReleaseContractError> {
1760    if value.schema_version != SOURCE_POLICY_SCHEMA_VERSION
1761        || !valid_new_id(&value.source_id)
1762        || !valid_new_id(&value.channel)
1763        || !valid_new_id(&value.key_id)
1764    {
1765        return invalid_document();
1766    }
1767    validate_epoch_chain(
1768        &value.epoch,
1769        &value.previous_epoch,
1770        &value.previous_document_sha256,
1771    )?;
1772    if !valid_positive_epoch(&value.root_epoch)
1773        || !valid_epoch(&value.minimum_revocation_epoch)
1774        || !matches!(value.source_type.as_str(), "registry" | "host_artifact")
1775        || !matches!(
1776            value.source_class.as_str(),
1777            "official" | "curated" | "community" | "private"
1778        )
1779    {
1780        return invalid_document();
1781    }
1782    validate_sorted_ids(&value.allowed_publishers, 1, 1024, true)?;
1783    if value.allowed_artifact_hosts.len() > 1024 {
1784        return invalid_document();
1785    }
1786    let mut previous_host = "";
1787    for host in &value.allowed_artifact_hosts {
1788        if host.len() > 253
1789            || !valid_hostname(host)
1790            || host.to_ascii_lowercase() != *host
1791            || host.as_str() <= previous_host
1792        {
1793            return invalid_document();
1794        }
1795        previous_host = host;
1796    }
1797    for keys in [
1798        &value.active_keys.package,
1799        &value.active_keys.release_metadata,
1800        &value.active_keys.source_policy_pointer,
1801        &value.active_keys.revocation_document,
1802        &value.active_keys.revocation_pointer,
1803    ] {
1804        validate_sorted_ids(keys, 1, 16, true)?;
1805    }
1806    validate_sorted_ids(&value.active_keys.host_capability_contract, 0, 16, true)?;
1807    if value.capability_publisher_scopes.len() != value.active_keys.host_capability_contract.len() {
1808        return invalid_document();
1809    }
1810    for (index, scope) in value.capability_publisher_scopes.iter().enumerate() {
1811        if scope.key_id != value.active_keys.host_capability_contract[index] {
1812            return invalid_document();
1813        }
1814        validate_sorted_ids(&scope.allowed_publishers, 1, 1024, false)?;
1815    }
1816    if !matches!(
1817        value.install_policy.as_str(),
1818        "allow" | "review_required" | "block"
1819    ) || !matches!(
1820        value.unsigned_policy.as_str(),
1821        "dev_only" | "review_required" | "block"
1822    ) || !matches!(value.downgrade_policy.as_str(), "review_required" | "block")
1823        || value.limits != SourcePolicyLimits::default()
1824    {
1825        return invalid_document();
1826    }
1827    validate_time_range(&value.generated_at, &value.expires_at, Some(24 * 60 * 60))?;
1828    validate_signature_field(&value.signature, require_signature)
1829}
1830
1831fn validate_source_policy_pointer(
1832    value: &SourcePolicyPointerV1,
1833    require_signature: bool,
1834) -> Result<(), ReleaseContractError> {
1835    validate_pointer(
1836        &value.schema_version,
1837        SOURCE_POLICY_POINTER_SCHEMA_VERSION,
1838        &value.source_id,
1839        &value.channel,
1840        &value.epoch,
1841        &value.previous_epoch,
1842        &value.previous_document_sha256,
1843        &value.r#ref,
1844        &value.document_sha256,
1845        &value.generated_at,
1846        &value.expires_at,
1847        &value.key_id,
1848        &value.signature,
1849        require_signature,
1850    )
1851}
1852
1853fn validate_revocation_pointer(
1854    value: &RevocationPointerV1,
1855    require_signature: bool,
1856) -> Result<(), ReleaseContractError> {
1857    validate_pointer(
1858        &value.schema_version,
1859        REVOCATION_POINTER_SCHEMA_VERSION,
1860        &value.source_id,
1861        &value.channel,
1862        &value.epoch,
1863        &value.previous_epoch,
1864        &value.previous_document_sha256,
1865        &value.r#ref,
1866        &value.document_sha256,
1867        &value.generated_at,
1868        &value.expires_at,
1869        &value.key_id,
1870        &value.signature,
1871        require_signature,
1872    )
1873}
1874
1875#[allow(clippy::too_many_arguments)]
1876fn validate_pointer(
1877    schema_version: &str,
1878    expected_schema_version: &str,
1879    source_id: &str,
1880    channel: &str,
1881    epoch: &str,
1882    previous_epoch: &str,
1883    previous_digest: &str,
1884    reference: &str,
1885    document_digest: &str,
1886    generated_at: &str,
1887    expires_at: &str,
1888    key_id: &str,
1889    signature: &str,
1890    require_signature: bool,
1891) -> Result<(), ReleaseContractError> {
1892    if schema_version != expected_schema_version
1893        || !valid_new_id(source_id)
1894        || !valid_new_id(channel)
1895        || !valid_new_id(key_id)
1896    {
1897        return invalid_document();
1898    }
1899    validate_epoch_chain(epoch, previous_epoch, previous_digest)?;
1900    if !valid_artifact_ref(reference)
1901        || !valid_sha256(document_digest)
1902        || document_digest == GENESIS_PREVIOUS_DOCUMENT_SHA256
1903    {
1904        return invalid_document();
1905    }
1906    validate_time_range(generated_at, expires_at, Some(24 * 60 * 60))?;
1907    validate_signature_field(signature, require_signature)
1908}
1909
1910fn validate_revocation(
1911    value: &RevocationV2,
1912    require_signature: bool,
1913) -> Result<(), ReleaseContractError> {
1914    if value.schema_version != REVOCATION_SCHEMA_VERSION
1915        || !valid_new_id(&value.source_id)
1916        || !valid_new_id(&value.channel)
1917        || !valid_new_id(&value.key_id)
1918    {
1919        return invalid_document();
1920    }
1921    validate_epoch_chain(
1922        &value.epoch,
1923        &value.previous_epoch,
1924        &value.previous_document_sha256,
1925    )?;
1926    if !valid_positive_epoch(&value.root_epoch) {
1927        return invalid_document();
1928    }
1929    let (_, expires_at) =
1930        validate_time_range(&value.generated_at, &value.expires_at, Some(24 * 60 * 60))?;
1931    validate_sorted_ids(&value.revoked_key_ids, 0, 4096, true)?;
1932    if value.revoked_releases.len() > 16_384 {
1933        return invalid_document();
1934    }
1935    let mut previous = String::new();
1936    for revoked in &value.revoked_releases {
1937        let identity = format!(
1938            "{}\0{}\0{}\0{}",
1939            revoked.publisher_id,
1940            revoked.plugin_id,
1941            revoked.version,
1942            revoked.release_metadata_sha256
1943        );
1944        let revoked_at = canonical_timestamp_seconds(&revoked.revoked_at)
1945            .ok_or(ReleaseContractError::InvalidDocument)?;
1946        if !valid_legacy_id(&revoked.publisher_id)
1947            || !valid_legacy_id(&revoked.plugin_id)
1948            || !valid_semver(&revoked.version)
1949            || !valid_sha256(&revoked.release_metadata_sha256)
1950            || identity <= previous
1951            || revoked_at > expires_at
1952        {
1953            return invalid_document();
1954        }
1955        previous = identity;
1956    }
1957    validate_signature_field(&value.signature, require_signature)
1958}
1959
1960fn validate_signing_ledger_evidence(
1961    value: &SigningLedgerEvidenceV1,
1962) -> Result<(), ReleaseContractError> {
1963    if value.schema_version != SIGNING_LEDGER_EVIDENCE_SCHEMA_VERSION
1964        || !valid_new_id(&value.source_id)
1965        || value
1966            .channel
1967            .as_deref()
1968            .is_some_and(|item| !valid_new_id(item))
1969    {
1970        return Err(ReleaseContractError::InvalidDocument);
1971    }
1972    for digest in [
1973        &value.subject_identity_sha256,
1974        &value.signing_preimage_sha256,
1975        &value.signature_envelope_sha256,
1976        &value.receipt_sha256,
1977        &value.checkpoint_sha256,
1978        &value.inclusion_proof_sha256,
1979        &value.latest_proof_sha256,
1980    ] {
1981        if !valid_sha256(digest) {
1982            return Err(ReleaseContractError::InvalidDocument);
1983        }
1984    }
1985    for reference in [
1986        &value.receipt_ref,
1987        &value.checkpoint_ref,
1988        &value.inclusion_proof_ref,
1989        &value.latest_proof_ref,
1990    ] {
1991        if !valid_artifact_ref(reference) {
1992            return Err(ReleaseContractError::InvalidDocument);
1993        }
1994    }
1995    if value.consistency_proof_ref.is_some() != value.consistency_proof_sha256.is_some() {
1996        return Err(ReleaseContractError::InvalidDocument);
1997    }
1998    if let (Some(reference), Some(digest)) = (
1999        value.consistency_proof_ref.as_deref(),
2000        value.consistency_proof_sha256.as_deref(),
2001    ) && (!valid_artifact_ref(reference) || !valid_sha256(digest))
2002    {
2003        return Err(ReleaseContractError::InvalidDocument);
2004    }
2005    Ok(())
2006}
2007
2008fn validate_signing_subject(value: &SigningSubjectV1) -> Result<(), ReleaseContractError> {
2009    if value.schema_version != SIGNING_SUBJECT_SCHEMA_VERSION || !valid_new_id(&value.source_id) {
2010        return invalid_document();
2011    }
2012    let valid = match value.usage {
2013        SigningSubjectUsage::RootDelegation => {
2014            value
2015                .root_epoch
2016                .as_deref()
2017                .is_some_and(valid_positive_epoch)
2018                && value.channel.is_none()
2019                && value.publisher_id.is_none()
2020                && value.plugin_id.is_none()
2021                && value.version.is_none()
2022                && value.artifact_or_metadata_identity_sha256.is_none()
2023                && value.epoch.is_none()
2024        }
2025        SigningSubjectUsage::Package | SigningSubjectUsage::ReleaseMetadata => {
2026            value.channel.as_deref().is_some_and(valid_new_id)
2027                && value.publisher_id.as_deref().is_some_and(valid_legacy_id)
2028                && value.plugin_id.as_deref().is_some_and(valid_legacy_id)
2029                && value.version.as_deref().is_some_and(valid_semver)
2030                && value
2031                    .artifact_or_metadata_identity_sha256
2032                    .as_deref()
2033                    .is_some_and(valid_sha256)
2034                && value.root_epoch.is_none()
2035                && value.epoch.is_none()
2036        }
2037        SigningSubjectUsage::SourcePolicyDocument
2038        | SigningSubjectUsage::SourcePolicyPointer
2039        | SigningSubjectUsage::RevocationDocument
2040        | SigningSubjectUsage::RevocationPointer => {
2041            value.channel.as_deref().is_some_and(valid_new_id)
2042                && value.epoch.as_deref().is_some_and(valid_positive_epoch)
2043                && value.root_epoch.is_none()
2044                && value.publisher_id.is_none()
2045                && value.plugin_id.is_none()
2046                && value.version.is_none()
2047                && value.artifact_or_metadata_identity_sha256.is_none()
2048        }
2049    };
2050    if !valid {
2051        return invalid_document();
2052    }
2053    Ok(())
2054}
2055
2056fn validate_signature_envelope(value: &SignatureEnvelopeV1) -> Result<(), ReleaseContractError> {
2057    if value.schema_version != SIGNATURE_ENVELOPE_SCHEMA_VERSION
2058        || !valid_sha256(&value.subject_identity_sha256)
2059        || !valid_sha256(&value.signing_preimage_sha256)
2060        || value.algorithm != SIGNATURE_ALGORITHM_ED25519
2061        || !valid_new_id(&value.key_id)
2062        || decode_signature(&value.signature).is_err()
2063    {
2064        return invalid_document();
2065    }
2066    Ok(())
2067}
2068
2069fn validate_signing_ledger_entry(value: &SigningLedgerEntryV1) -> Result<(), ReleaseContractError> {
2070    if value.schema_version != SIGNING_LEDGER_ENTRY_SCHEMA_VERSION
2071        || !valid_sha256(&value.subject_identity_sha256)
2072        || !valid_sha256(&value.signing_preimage_sha256)
2073        || value.algorithm != SIGNATURE_ALGORITHM_ED25519
2074        || !valid_new_id(&value.key_id)
2075        || !valid_json_safe_positive(value.revision)
2076    {
2077        return invalid_document();
2078    }
2079    validate_signing_subject(&value.subject)?;
2080    if sha256_hex(&canonical_json(&value.subject)?) != value.subject_identity_sha256 {
2081        return invalid_document();
2082    }
2083    let reserved_at = canonical_timestamp_seconds(&value.reserved_at)
2084        .ok_or(ReleaseContractError::InvalidDocument)?;
2085    match value.state {
2086        SigningLedgerEntryState::Reserved => {
2087            if value.signature_envelope.is_some()
2088                || value.signature_envelope_sha256.is_some()
2089                || value.finalized_at.is_some()
2090                || value.failure_code.is_some()
2091                || value.failed_at.is_some()
2092            {
2093                return invalid_document();
2094            }
2095        }
2096        SigningLedgerEntryState::Finalized => {
2097            let envelope = value
2098                .signature_envelope
2099                .as_ref()
2100                .ok_or(ReleaseContractError::InvalidDocument)?;
2101            validate_signature_envelope(envelope)?;
2102            let envelope_digest = value
2103                .signature_envelope_sha256
2104                .as_deref()
2105                .ok_or(ReleaseContractError::InvalidDocument)?;
2106            let finalized_at = value
2107                .finalized_at
2108                .as_deref()
2109                .and_then(canonical_timestamp_seconds)
2110                .ok_or(ReleaseContractError::InvalidDocument)?;
2111            if value.failure_code.is_some()
2112                || value.failed_at.is_some()
2113                || finalized_at < reserved_at
2114                || envelope.subject_identity_sha256 != value.subject_identity_sha256
2115                || envelope.signing_preimage_sha256 != value.signing_preimage_sha256
2116                || envelope.algorithm != value.algorithm
2117                || envelope.key_id != value.key_id
2118                || !valid_sha256(envelope_digest)
2119                || sha256_hex(&canonical_json(envelope)?) != envelope_digest
2120            {
2121                return invalid_document();
2122            }
2123        }
2124        SigningLedgerEntryState::TerminalFailed => {
2125            let failed_at = value
2126                .failed_at
2127                .as_deref()
2128                .and_then(canonical_timestamp_seconds)
2129                .ok_or(ReleaseContractError::InvalidDocument)?;
2130            if value.signature_envelope.is_some()
2131                || value.signature_envelope_sha256.is_some()
2132                || value.finalized_at.is_some()
2133                || value.failure_code.is_none()
2134                || failed_at < reserved_at
2135            {
2136                return invalid_document();
2137            }
2138        }
2139    }
2140    Ok(())
2141}
2142
2143fn validate_signing_ledger_log_leaf(
2144    value: &SigningLedgerLogLeafV1,
2145) -> Result<(), ReleaseContractError> {
2146    if value.schema_version != SIGNING_LEDGER_LOG_LEAF_SCHEMA_VERSION
2147        || !valid_new_id(&value.source_id)
2148        || value
2149            .channel
2150            .as_deref()
2151            .is_some_and(|channel| !valid_new_id(channel))
2152        || !valid_sha256(&value.subject_identity_sha256)
2153        || !valid_sha256(&value.signing_preimage_sha256)
2154        || !valid_sha256(&value.signature_envelope_sha256)
2155        || !valid_json_safe_positive(value.sequence)
2156    {
2157        return invalid_document();
2158    }
2159    Ok(())
2160}
2161
2162fn validate_signing_ledger_checkpoint(
2163    value: &SigningLedgerCheckpointV1,
2164) -> Result<(), ReleaseContractError> {
2165    if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2166        || value.kind != "checkpoint"
2167        || !valid_new_id(&value.log_id)
2168        || !valid_json_safe_positive(value.tree_size)
2169        || !valid_sha256(&value.log_root_hash)
2170        || !valid_sha256(&value.latest_map_root_hash)
2171        || canonical_timestamp_seconds(&value.checkpoint_time).is_none()
2172        || !valid_new_id(&value.key_id)
2173        || decode_signature(&value.signature).is_err()
2174    {
2175        return invalid_document();
2176    }
2177    Ok(())
2178}
2179
2180fn validate_signing_ledger_receipt(
2181    value: &SigningLedgerReceiptV1,
2182) -> Result<(), ReleaseContractError> {
2183    if value.schema_version != SIGNING_LEDGER_RECEIPT_SCHEMA_VERSION
2184        || !valid_new_id(&value.log_id)
2185        || !valid_new_id(&value.source_id)
2186        || value
2187            .channel
2188            .as_deref()
2189            .is_some_and(|channel| !valid_new_id(channel))
2190        || !valid_sha256(&value.subject_identity_sha256)
2191        || !valid_sha256(&value.signing_preimage_sha256)
2192        || !valid_sha256(&value.signature_envelope_sha256)
2193        || !valid_json_safe_positive(value.sequence)
2194        || value.leaf_index != value.sequence - 1
2195        || !valid_json_safe_positive(value.tree_size)
2196        || value.tree_size < value.sequence
2197        || !valid_sha256(&value.log_root_hash)
2198        || !valid_sha256(&value.latest_map_root_hash)
2199        || !valid_sha256(&value.checkpoint_sha256)
2200        || canonical_timestamp_seconds(&value.checkpoint_time).is_none()
2201        || !valid_new_id(&value.key_id)
2202        || decode_signature(&value.signature).is_err()
2203    {
2204        return invalid_document();
2205    }
2206    Ok(())
2207}
2208
2209fn valid_ledger_nodes(values: &[String], expected: Option<usize>) -> bool {
2210    expected.map_or(values.len() <= 64, |count| values.len() == count)
2211        && values.iter().all(|node| valid_sha256(node))
2212}
2213
2214fn validate_signing_ledger_inclusion_proof(
2215    value: &SigningLedgerInclusionProofV1,
2216) -> Result<(), ReleaseContractError> {
2217    if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2218        || value.kind != "inclusion_proof"
2219        || !valid_new_id(&value.log_id)
2220        || !valid_json_safe_positive(value.tree_size)
2221        || value.leaf_index >= value.tree_size
2222        || !valid_ledger_nodes(&value.nodes, None)
2223    {
2224        return invalid_document();
2225    }
2226    Ok(())
2227}
2228
2229fn validate_signing_ledger_latest_proof(
2230    value: &SigningLedgerLatestProofV1,
2231) -> Result<(), ReleaseContractError> {
2232    if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2233        || value.kind != "latest_proof"
2234        || !valid_new_id(&value.log_id)
2235        || !valid_sha256(&value.subject_identity_sha256)
2236        || !valid_ledger_nodes(&value.siblings, Some(256))
2237    {
2238        return invalid_document();
2239    }
2240    if value.present {
2241        if !value.sequence.is_some_and(valid_json_safe_positive)
2242            || !value
2243                .signing_preimage_sha256
2244                .as_deref()
2245                .is_some_and(valid_sha256)
2246            || !value
2247                .signature_envelope_sha256
2248                .as_deref()
2249                .is_some_and(valid_sha256)
2250        {
2251            return invalid_document();
2252        }
2253    } else if value.sequence.is_some()
2254        || value.signing_preimage_sha256.is_some()
2255        || value.signature_envelope_sha256.is_some()
2256    {
2257        return invalid_document();
2258    }
2259    Ok(())
2260}
2261
2262fn validate_signing_ledger_consistency_proof(
2263    value: &SigningLedgerConsistencyProofV1,
2264) -> Result<(), ReleaseContractError> {
2265    if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2266        || value.kind != "consistency_proof"
2267        || !valid_new_id(&value.log_id)
2268        || !valid_json_safe_positive(value.old_tree_size)
2269        || value.new_tree_size < value.old_tree_size
2270        || value.new_tree_size > 9_007_199_254_740_991
2271        || !valid_ledger_nodes(&value.nodes, None)
2272    {
2273        return invalid_document();
2274    }
2275    Ok(())
2276}
2277
2278fn valid_json_safe_positive(value: u64) -> bool {
2279    value > 0 && value <= 9_007_199_254_740_991
2280}
2281
2282fn sha256_hex(value: &[u8]) -> String {
2283    Sha256::digest(value)
2284        .iter()
2285        .map(|byte| format!("{byte:02x}"))
2286        .collect()
2287}
2288
2289fn invalid_document<T>() -> Result<T, ReleaseContractError> {
2290    Err(ReleaseContractError::InvalidDocument)
2291}
2292
2293fn validate_signature_field(value: &str, required: bool) -> Result<(), ReleaseContractError> {
2294    if !required && value.is_empty() {
2295        return Ok(());
2296    }
2297    decode_signature(value).map(|_| ())
2298}
2299
2300fn validate_epoch_chain(
2301    epoch: &str,
2302    previous_epoch: &str,
2303    previous_digest: &str,
2304) -> Result<(), ReleaseContractError> {
2305    if !valid_positive_epoch(epoch)
2306        || !valid_epoch(previous_epoch)
2307        || !valid_sha256(previous_digest)
2308        || increment_decimal(previous_epoch).as_deref() != Some(epoch)
2309    {
2310        return invalid_document();
2311    }
2312    if previous_epoch == GENESIS_PREVIOUS_EPOCH {
2313        if previous_digest != GENESIS_PREVIOUS_DOCUMENT_SHA256 {
2314            return invalid_document();
2315        }
2316    } else if previous_digest == GENESIS_PREVIOUS_DOCUMENT_SHA256 {
2317        return invalid_document();
2318    }
2319    Ok(())
2320}
2321
2322fn increment_decimal(value: &str) -> Option<String> {
2323    if !valid_epoch(value) {
2324        return None;
2325    }
2326    let mut bytes = value.as_bytes().to_vec();
2327    let mut index = bytes.len();
2328    while index > 0 {
2329        index -= 1;
2330        if bytes[index] < b'9' {
2331            bytes[index] += 1;
2332            return String::from_utf8(bytes).ok();
2333        }
2334        bytes[index] = b'0';
2335    }
2336    bytes.insert(0, b'1');
2337    String::from_utf8(bytes).ok()
2338}
2339
2340fn validate_time_range(
2341    generated_at: &str,
2342    expires_at: &str,
2343    maximum_seconds: Option<i64>,
2344) -> Result<(i64, i64), ReleaseContractError> {
2345    let generated =
2346        canonical_timestamp_seconds(generated_at).ok_or(ReleaseContractError::InvalidDocument)?;
2347    let expires =
2348        canonical_timestamp_seconds(expires_at).ok_or(ReleaseContractError::InvalidDocument)?;
2349    if expires <= generated || maximum_seconds.is_some_and(|maximum| expires - generated > maximum)
2350    {
2351        return invalid_document();
2352    }
2353    Ok((generated, expires))
2354}
2355
2356fn canonical_timestamp_seconds(value: &str) -> Option<i64> {
2357    let bytes = value.as_bytes();
2358    if bytes.len() != 20
2359        || bytes[4] != b'-'
2360        || bytes[7] != b'-'
2361        || bytes[10] != b'T'
2362        || bytes[13] != b':'
2363        || bytes[16] != b':'
2364        || bytes[19] != b'Z'
2365    {
2366        return None;
2367    }
2368    let year = parse_digits(&bytes[0..4])? as i32;
2369    let month = parse_digits(&bytes[5..7])? as u32;
2370    let day = parse_digits(&bytes[8..10])? as u32;
2371    let hour = parse_digits(&bytes[11..13])? as i64;
2372    let minute = parse_digits(&bytes[14..16])? as i64;
2373    let second = parse_digits(&bytes[17..19])? as i64;
2374    if !(1..=12).contains(&month)
2375        || day < 1
2376        || day > days_in_month(year, month)
2377        || hour > 23
2378        || minute > 59
2379        || second > 59
2380    {
2381        return None;
2382    }
2383    Some(days_from_civil(year, month, day) * 86_400 + hour * 3_600 + minute * 60 + second)
2384}
2385
2386fn parse_digits(bytes: &[u8]) -> Option<u32> {
2387    if bytes.is_empty() || bytes.iter().any(|byte| !byte.is_ascii_digit()) {
2388        return None;
2389    }
2390    bytes.iter().try_fold(0_u32, |value, byte| {
2391        value.checked_mul(10)?.checked_add(u32::from(byte - b'0'))
2392    })
2393}
2394
2395fn days_in_month(year: i32, month: u32) -> u32 {
2396    match month {
2397        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2398        4 | 6 | 9 | 11 => 30,
2399        2 if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) => 29,
2400        2 => 28,
2401        _ => 0,
2402    }
2403}
2404
2405fn days_from_civil(year: i32, month: u32, day: u32) -> i64 {
2406    let adjusted_year = i64::from(year) - i64::from(month <= 2);
2407    let era = if adjusted_year >= 0 {
2408        adjusted_year
2409    } else {
2410        adjusted_year - 399
2411    } / 400;
2412    let year_of_era = adjusted_year - era * 400;
2413    let adjusted_month = i64::from(month) + if month > 2 { -3 } else { 9 };
2414    let day_of_year = (153 * adjusted_month + 2) / 5 + i64::from(day) - 1;
2415    let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
2416    era * 146_097 + day_of_era - 719_468
2417}
2418
2419fn validate_sorted_ids(
2420    values: &[String],
2421    minimum: usize,
2422    maximum: usize,
2423    lower: bool,
2424) -> Result<(), ReleaseContractError> {
2425    if values.len() < minimum || values.len() > maximum {
2426        return invalid_document();
2427    }
2428    let mut previous = "";
2429    for value in values {
2430        let valid = if lower {
2431            valid_new_id(value)
2432        } else {
2433            valid_legacy_id(value)
2434        };
2435        if !valid || value.as_str() <= previous {
2436            return invalid_document();
2437        }
2438        previous = value;
2439    }
2440    Ok(())
2441}
2442
2443fn valid_new_id(value: &str) -> bool {
2444    value.len() <= 128
2445        && value.as_bytes().first().is_some_and(u8::is_ascii_lowercase)
2446        && value.bytes().all(|byte| {
2447            byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'_' | b'-')
2448        })
2449}
2450
2451fn valid_legacy_id(value: &str) -> bool {
2452    value.len() <= 128
2453        && value
2454            .as_bytes()
2455            .first()
2456            .is_some_and(u8::is_ascii_alphanumeric)
2457        && value
2458            .bytes()
2459            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
2460}
2461
2462fn valid_epoch(value: &str) -> bool {
2463    value == "0" || valid_positive_epoch(value)
2464}
2465
2466fn valid_positive_epoch(value: &str) -> bool {
2467    !value.is_empty()
2468        && value.as_bytes()[0].is_ascii_digit()
2469        && value.as_bytes()[0] != b'0'
2470        && value.bytes().all(|byte| byte.is_ascii_digit())
2471}
2472
2473fn valid_sha256(value: &str) -> bool {
2474    value.len() == 64
2475        && value
2476            .bytes()
2477            .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2478}
2479
2480fn valid_prefixed_sha256(value: &str) -> bool {
2481    value.strip_prefix("sha256:").is_some_and(valid_sha256)
2482}
2483
2484fn valid_legacy_sha256(value: &str) -> bool {
2485    valid_sha256(value) || valid_prefixed_sha256(value)
2486}
2487
2488fn valid_artifact_ref(value: &str) -> bool {
2489    !value.is_empty()
2490        && value.len() <= 1024
2491        && !value.starts_with('/')
2492        && !value.contains('\\')
2493        && !value.contains(['?', '#'])
2494        && value.bytes().all(|byte| {
2495            byte.is_ascii_alphanumeric()
2496                || matches!(byte, b'.' | b'_' | b'/' | b'@' | b'+' | b'~' | b'-')
2497        })
2498        && value
2499            .split('/')
2500            .all(|segment| !segment.is_empty() && segment != "." && segment != "..")
2501}
2502
2503fn valid_hostname(value: &str) -> bool {
2504    !value.is_empty()
2505        && value
2506            .as_bytes()
2507            .first()
2508            .is_some_and(u8::is_ascii_alphanumeric)
2509        && value
2510            .as_bytes()
2511            .last()
2512            .is_some_and(u8::is_ascii_alphanumeric)
2513        && value
2514            .bytes()
2515            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-'))
2516}
2517
2518fn valid_semver(value: &str) -> bool {
2519    semver::Version::parse(value)
2520        .map(|version| version.to_string() == value)
2521        .unwrap_or(false)
2522}