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.v8";
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 ReleaseMetadataV8 {
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: &ReleaseMetadataV8,
844) -> Result<ReleaseMetadataV8, ReleaseContractError> {
845 validate_release_metadata(document)?;
846 Ok(document.clone())
847}
848
849pub fn release_metadata_signing_preimage(
850 channel: &str,
851 document: &ReleaseMetadataV8,
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: &ReleaseMetadataV8,
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: &ReleaseMetadataV8,
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<ReleaseMetadataV8, 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: &ReleaseMetadataV8) -> Result<(), ReleaseContractError> {
1589 if !valid_release_metadata_ui_protocol_pair(
1590 &value.schema_version,
1591 &value.compatibility.ui_protocol_version,
1592 ) || !valid_new_id(&value.source_id)
1593 || !valid_legacy_id(&value.publisher_id)
1594 || !valid_legacy_id(&value.plugin_id)
1595 || !valid_semver(&value.version)
1596 || !valid_artifact_ref(&value.release_metadata_ref)
1597 {
1598 return invalid_document();
1599 }
1600 if !matches!(
1601 value.distribution_ref.distribution.as_str(),
1602 "registry_ref" | "host_artifact_ref"
1603 ) || !valid_artifact_ref(&value.distribution_ref.artifact_ref)
1604 {
1605 return invalid_document();
1606 }
1607 if !valid_legacy_sha256(&value.hashes.package_sha256)
1608 || !valid_legacy_sha256(&value.hashes.manifest_sha256)
1609 || !valid_legacy_sha256(&value.hashes.entries_sha256)
1610 {
1611 return invalid_document();
1612 }
1613 let metadata_signature = &value.release_metadata_signature;
1614 if metadata_signature.algorithm != SIGNATURE_ALGORITHM_ED25519
1615 || !valid_new_id(&metadata_signature.key_id)
1616 || !valid_artifact_ref(&metadata_signature.signature_ref)
1617 || !valid_epoch(&metadata_signature.source_policy_epoch)
1618 || !valid_epoch(&metadata_signature.revocation_epoch)
1619 {
1620 return invalid_document();
1621 }
1622 let package_signature = &value.package_signature;
1623 if package_signature.algorithm != SIGNATURE_ALGORITHM_ED25519
1624 || !valid_new_id(&package_signature.key_id)
1625 || !valid_artifact_ref(&package_signature.signature_bundle_ref)
1626 || !valid_epoch(&package_signature.source_policy_epoch)
1627 || !valid_epoch(&package_signature.revocation_epoch)
1628 {
1629 return invalid_document();
1630 }
1631 if !valid_semver(&value.compatibility.min_redevplugin_version)
1632 || !valid_semver(&value.compatibility.min_runtime_version)
1633 {
1634 return invalid_document();
1635 }
1636 if let Some(targets) = &value.compatibility.supported_targets {
1637 let mut previous = "";
1638 for target in targets {
1639 if !matches!(
1640 target.as_str(),
1641 "darwin/amd64" | "darwin/arm64" | "linux/amd64" | "linux/arm64"
1642 ) || target.as_str() <= previous
1643 {
1644 return invalid_document();
1645 }
1646 previous = target;
1647 }
1648 }
1649 validate_host_requirements(value.host_requirements.as_deref().unwrap_or(&[]))?;
1650 if let Some(evidence) = &value.release_evidence {
1651 if evidence
1652 .notices_sha256
1653 .as_deref()
1654 .is_some_and(|digest| !valid_legacy_sha256(digest))
1655 || evidence
1656 .provenance_sha256
1657 .as_deref()
1658 .is_some_and(|digest| !valid_legacy_sha256(digest))
1659 || evidence
1660 .generated_at
1661 .as_deref()
1662 .is_some_and(|generated| canonical_timestamp_seconds(generated).is_none())
1663 {
1664 return invalid_document();
1665 }
1666 }
1667 if let Some(metadata) = &value.metadata {
1668 if metadata.len() > 128
1669 || metadata
1670 .iter()
1671 .any(|(key, item)| key.is_empty() || key.len() > 128 || item.len() > 4096)
1672 {
1673 return invalid_document();
1674 }
1675 }
1676 Ok(())
1677}
1678
1679fn valid_release_metadata_ui_protocol_pair(
1680 schema_version: &str,
1681 ui_protocol_version: &str,
1682) -> bool {
1683 schema_version == RELEASE_METADATA_SCHEMA_VERSION && ui_protocol_version == "plugin-ui-v7"
1684}
1685
1686fn validate_host_requirements(
1687 values: &[ReleaseHostRequirement],
1688) -> Result<(), ReleaseContractError> {
1689 let mut previous_host = "";
1690 for value in values {
1691 if !valid_legacy_id(&value.host_id) || value.host_id.as_str() <= previous_host {
1692 return invalid_document();
1693 }
1694 if value
1695 .min_host_version
1696 .as_deref()
1697 .is_some_and(|version| !valid_semver(version))
1698 {
1699 return invalid_document();
1700 }
1701 let mut previous_capability = String::new();
1702 for capability in value
1703 .required_capability_contracts
1704 .as_deref()
1705 .unwrap_or(&[])
1706 {
1707 let identity = format!(
1708 "{}\0{}",
1709 capability.capability_id, capability.capability_version
1710 );
1711 if !valid_legacy_id(&capability.capability_id)
1712 || !valid_semver(&capability.capability_version)
1713 || identity <= previous_capability
1714 {
1715 return invalid_document();
1716 }
1717 validate_capability_contract_ref(&capability.contract)?;
1718 previous_capability = identity;
1719 }
1720 previous_host = &value.host_id;
1721 }
1722 Ok(())
1723}
1724
1725fn validate_capability_contract_ref(
1726 value: &HostCapabilityContractRef,
1727) -> Result<(), ReleaseContractError> {
1728 if !valid_legacy_id(&value.publisher_id)
1729 || !valid_legacy_id(&value.contract_id)
1730 || !valid_legacy_id(&value.signature_key_id)
1731 || !valid_semver(&value.contract_version)
1732 || !valid_epoch(&value.signature_policy_epoch)
1733 || !valid_epoch(&value.signature_revocation_epoch)
1734 {
1735 return invalid_document();
1736 }
1737 for reference in [
1738 &value.artifact_ref,
1739 &value.manifest_ref,
1740 &value.signature_ref,
1741 &value.compatibility_ref,
1742 &value.generated_client_ref,
1743 &value.notices_ref,
1744 ] {
1745 if !valid_artifact_ref(reference) {
1746 return invalid_document();
1747 }
1748 }
1749 for digest in [
1750 &value.artifact_sha256,
1751 &value.manifest_sha256,
1752 &value.signature_sha256,
1753 &value.compatibility_sha256,
1754 &value.generated_client_sha256,
1755 &value.notices_sha256,
1756 ] {
1757 if !valid_sha256(digest) {
1758 return invalid_document();
1759 }
1760 }
1761 Ok(())
1762}
1763
1764fn validate_source_policy(
1765 value: &SourcePolicyV2,
1766 require_signature: bool,
1767) -> Result<(), ReleaseContractError> {
1768 if value.schema_version != SOURCE_POLICY_SCHEMA_VERSION
1769 || !valid_new_id(&value.source_id)
1770 || !valid_new_id(&value.channel)
1771 || !valid_new_id(&value.key_id)
1772 {
1773 return invalid_document();
1774 }
1775 validate_epoch_chain(
1776 &value.epoch,
1777 &value.previous_epoch,
1778 &value.previous_document_sha256,
1779 )?;
1780 if !valid_positive_epoch(&value.root_epoch)
1781 || !valid_epoch(&value.minimum_revocation_epoch)
1782 || !matches!(value.source_type.as_str(), "registry" | "host_artifact")
1783 || !matches!(
1784 value.source_class.as_str(),
1785 "official" | "curated" | "community" | "private"
1786 )
1787 {
1788 return invalid_document();
1789 }
1790 validate_sorted_ids(&value.allowed_publishers, 1, 1024, true)?;
1791 if value.allowed_artifact_hosts.len() > 1024 {
1792 return invalid_document();
1793 }
1794 let mut previous_host = "";
1795 for host in &value.allowed_artifact_hosts {
1796 if host.len() > 253
1797 || !valid_hostname(host)
1798 || host.to_ascii_lowercase() != *host
1799 || host.as_str() <= previous_host
1800 {
1801 return invalid_document();
1802 }
1803 previous_host = host;
1804 }
1805 for keys in [
1806 &value.active_keys.package,
1807 &value.active_keys.release_metadata,
1808 &value.active_keys.source_policy_pointer,
1809 &value.active_keys.revocation_document,
1810 &value.active_keys.revocation_pointer,
1811 ] {
1812 validate_sorted_ids(keys, 1, 16, true)?;
1813 }
1814 validate_sorted_ids(&value.active_keys.host_capability_contract, 0, 16, true)?;
1815 if value.capability_publisher_scopes.len() != value.active_keys.host_capability_contract.len() {
1816 return invalid_document();
1817 }
1818 for (index, scope) in value.capability_publisher_scopes.iter().enumerate() {
1819 if scope.key_id != value.active_keys.host_capability_contract[index] {
1820 return invalid_document();
1821 }
1822 validate_sorted_ids(&scope.allowed_publishers, 1, 1024, false)?;
1823 }
1824 if !matches!(
1825 value.install_policy.as_str(),
1826 "allow" | "review_required" | "block"
1827 ) || !matches!(
1828 value.unsigned_policy.as_str(),
1829 "dev_only" | "review_required" | "block"
1830 ) || !matches!(value.downgrade_policy.as_str(), "review_required" | "block")
1831 || value.limits != SourcePolicyLimits::default()
1832 {
1833 return invalid_document();
1834 }
1835 validate_time_range(&value.generated_at, &value.expires_at, Some(24 * 60 * 60))?;
1836 validate_signature_field(&value.signature, require_signature)
1837}
1838
1839fn validate_source_policy_pointer(
1840 value: &SourcePolicyPointerV1,
1841 require_signature: bool,
1842) -> Result<(), ReleaseContractError> {
1843 validate_pointer(
1844 &value.schema_version,
1845 SOURCE_POLICY_POINTER_SCHEMA_VERSION,
1846 &value.source_id,
1847 &value.channel,
1848 &value.epoch,
1849 &value.previous_epoch,
1850 &value.previous_document_sha256,
1851 &value.r#ref,
1852 &value.document_sha256,
1853 &value.generated_at,
1854 &value.expires_at,
1855 &value.key_id,
1856 &value.signature,
1857 require_signature,
1858 )
1859}
1860
1861fn validate_revocation_pointer(
1862 value: &RevocationPointerV1,
1863 require_signature: bool,
1864) -> Result<(), ReleaseContractError> {
1865 validate_pointer(
1866 &value.schema_version,
1867 REVOCATION_POINTER_SCHEMA_VERSION,
1868 &value.source_id,
1869 &value.channel,
1870 &value.epoch,
1871 &value.previous_epoch,
1872 &value.previous_document_sha256,
1873 &value.r#ref,
1874 &value.document_sha256,
1875 &value.generated_at,
1876 &value.expires_at,
1877 &value.key_id,
1878 &value.signature,
1879 require_signature,
1880 )
1881}
1882
1883#[allow(clippy::too_many_arguments)]
1884fn validate_pointer(
1885 schema_version: &str,
1886 expected_schema_version: &str,
1887 source_id: &str,
1888 channel: &str,
1889 epoch: &str,
1890 previous_epoch: &str,
1891 previous_digest: &str,
1892 reference: &str,
1893 document_digest: &str,
1894 generated_at: &str,
1895 expires_at: &str,
1896 key_id: &str,
1897 signature: &str,
1898 require_signature: bool,
1899) -> Result<(), ReleaseContractError> {
1900 if schema_version != expected_schema_version
1901 || !valid_new_id(source_id)
1902 || !valid_new_id(channel)
1903 || !valid_new_id(key_id)
1904 {
1905 return invalid_document();
1906 }
1907 validate_epoch_chain(epoch, previous_epoch, previous_digest)?;
1908 if !valid_artifact_ref(reference)
1909 || !valid_sha256(document_digest)
1910 || document_digest == GENESIS_PREVIOUS_DOCUMENT_SHA256
1911 {
1912 return invalid_document();
1913 }
1914 validate_time_range(generated_at, expires_at, Some(24 * 60 * 60))?;
1915 validate_signature_field(signature, require_signature)
1916}
1917
1918fn validate_revocation(
1919 value: &RevocationV2,
1920 require_signature: bool,
1921) -> Result<(), ReleaseContractError> {
1922 if value.schema_version != REVOCATION_SCHEMA_VERSION
1923 || !valid_new_id(&value.source_id)
1924 || !valid_new_id(&value.channel)
1925 || !valid_new_id(&value.key_id)
1926 {
1927 return invalid_document();
1928 }
1929 validate_epoch_chain(
1930 &value.epoch,
1931 &value.previous_epoch,
1932 &value.previous_document_sha256,
1933 )?;
1934 if !valid_positive_epoch(&value.root_epoch) {
1935 return invalid_document();
1936 }
1937 let (_, expires_at) =
1938 validate_time_range(&value.generated_at, &value.expires_at, Some(24 * 60 * 60))?;
1939 validate_sorted_ids(&value.revoked_key_ids, 0, 4096, true)?;
1940 if value.revoked_releases.len() > 16_384 {
1941 return invalid_document();
1942 }
1943 let mut previous = String::new();
1944 for revoked in &value.revoked_releases {
1945 let identity = format!(
1946 "{}\0{}\0{}\0{}",
1947 revoked.publisher_id,
1948 revoked.plugin_id,
1949 revoked.version,
1950 revoked.release_metadata_sha256
1951 );
1952 let revoked_at = canonical_timestamp_seconds(&revoked.revoked_at)
1953 .ok_or(ReleaseContractError::InvalidDocument)?;
1954 if !valid_legacy_id(&revoked.publisher_id)
1955 || !valid_legacy_id(&revoked.plugin_id)
1956 || !valid_semver(&revoked.version)
1957 || !valid_sha256(&revoked.release_metadata_sha256)
1958 || identity <= previous
1959 || revoked_at > expires_at
1960 {
1961 return invalid_document();
1962 }
1963 previous = identity;
1964 }
1965 validate_signature_field(&value.signature, require_signature)
1966}
1967
1968fn validate_signing_ledger_evidence(
1969 value: &SigningLedgerEvidenceV1,
1970) -> Result<(), ReleaseContractError> {
1971 if value.schema_version != SIGNING_LEDGER_EVIDENCE_SCHEMA_VERSION
1972 || !valid_new_id(&value.source_id)
1973 || value
1974 .channel
1975 .as_deref()
1976 .is_some_and(|item| !valid_new_id(item))
1977 {
1978 return Err(ReleaseContractError::InvalidDocument);
1979 }
1980 for digest in [
1981 &value.subject_identity_sha256,
1982 &value.signing_preimage_sha256,
1983 &value.signature_envelope_sha256,
1984 &value.receipt_sha256,
1985 &value.checkpoint_sha256,
1986 &value.inclusion_proof_sha256,
1987 &value.latest_proof_sha256,
1988 ] {
1989 if !valid_sha256(digest) {
1990 return Err(ReleaseContractError::InvalidDocument);
1991 }
1992 }
1993 for reference in [
1994 &value.receipt_ref,
1995 &value.checkpoint_ref,
1996 &value.inclusion_proof_ref,
1997 &value.latest_proof_ref,
1998 ] {
1999 if !valid_artifact_ref(reference) {
2000 return Err(ReleaseContractError::InvalidDocument);
2001 }
2002 }
2003 if value.consistency_proof_ref.is_some() != value.consistency_proof_sha256.is_some() {
2004 return Err(ReleaseContractError::InvalidDocument);
2005 }
2006 if let (Some(reference), Some(digest)) = (
2007 value.consistency_proof_ref.as_deref(),
2008 value.consistency_proof_sha256.as_deref(),
2009 ) && (!valid_artifact_ref(reference) || !valid_sha256(digest))
2010 {
2011 return Err(ReleaseContractError::InvalidDocument);
2012 }
2013 Ok(())
2014}
2015
2016fn validate_signing_subject(value: &SigningSubjectV1) -> Result<(), ReleaseContractError> {
2017 if value.schema_version != SIGNING_SUBJECT_SCHEMA_VERSION || !valid_new_id(&value.source_id) {
2018 return invalid_document();
2019 }
2020 let valid = match value.usage {
2021 SigningSubjectUsage::RootDelegation => {
2022 value
2023 .root_epoch
2024 .as_deref()
2025 .is_some_and(valid_positive_epoch)
2026 && value.channel.is_none()
2027 && value.publisher_id.is_none()
2028 && value.plugin_id.is_none()
2029 && value.version.is_none()
2030 && value.artifact_or_metadata_identity_sha256.is_none()
2031 && value.epoch.is_none()
2032 }
2033 SigningSubjectUsage::Package | SigningSubjectUsage::ReleaseMetadata => {
2034 value.channel.as_deref().is_some_and(valid_new_id)
2035 && value.publisher_id.as_deref().is_some_and(valid_legacy_id)
2036 && value.plugin_id.as_deref().is_some_and(valid_legacy_id)
2037 && value.version.as_deref().is_some_and(valid_semver)
2038 && value
2039 .artifact_or_metadata_identity_sha256
2040 .as_deref()
2041 .is_some_and(valid_sha256)
2042 && value.root_epoch.is_none()
2043 && value.epoch.is_none()
2044 }
2045 SigningSubjectUsage::SourcePolicyDocument
2046 | SigningSubjectUsage::SourcePolicyPointer
2047 | SigningSubjectUsage::RevocationDocument
2048 | SigningSubjectUsage::RevocationPointer => {
2049 value.channel.as_deref().is_some_and(valid_new_id)
2050 && value.epoch.as_deref().is_some_and(valid_positive_epoch)
2051 && value.root_epoch.is_none()
2052 && value.publisher_id.is_none()
2053 && value.plugin_id.is_none()
2054 && value.version.is_none()
2055 && value.artifact_or_metadata_identity_sha256.is_none()
2056 }
2057 };
2058 if !valid {
2059 return invalid_document();
2060 }
2061 Ok(())
2062}
2063
2064fn validate_signature_envelope(value: &SignatureEnvelopeV1) -> Result<(), ReleaseContractError> {
2065 if value.schema_version != SIGNATURE_ENVELOPE_SCHEMA_VERSION
2066 || !valid_sha256(&value.subject_identity_sha256)
2067 || !valid_sha256(&value.signing_preimage_sha256)
2068 || value.algorithm != SIGNATURE_ALGORITHM_ED25519
2069 || !valid_new_id(&value.key_id)
2070 || decode_signature(&value.signature).is_err()
2071 {
2072 return invalid_document();
2073 }
2074 Ok(())
2075}
2076
2077fn validate_signing_ledger_entry(value: &SigningLedgerEntryV1) -> Result<(), ReleaseContractError> {
2078 if value.schema_version != SIGNING_LEDGER_ENTRY_SCHEMA_VERSION
2079 || !valid_sha256(&value.subject_identity_sha256)
2080 || !valid_sha256(&value.signing_preimage_sha256)
2081 || value.algorithm != SIGNATURE_ALGORITHM_ED25519
2082 || !valid_new_id(&value.key_id)
2083 || !valid_json_safe_positive(value.revision)
2084 {
2085 return invalid_document();
2086 }
2087 validate_signing_subject(&value.subject)?;
2088 if sha256_hex(&canonical_json(&value.subject)?) != value.subject_identity_sha256 {
2089 return invalid_document();
2090 }
2091 let reserved_at = canonical_timestamp_seconds(&value.reserved_at)
2092 .ok_or(ReleaseContractError::InvalidDocument)?;
2093 match value.state {
2094 SigningLedgerEntryState::Reserved => {
2095 if value.signature_envelope.is_some()
2096 || value.signature_envelope_sha256.is_some()
2097 || value.finalized_at.is_some()
2098 || value.failure_code.is_some()
2099 || value.failed_at.is_some()
2100 {
2101 return invalid_document();
2102 }
2103 }
2104 SigningLedgerEntryState::Finalized => {
2105 let envelope = value
2106 .signature_envelope
2107 .as_ref()
2108 .ok_or(ReleaseContractError::InvalidDocument)?;
2109 validate_signature_envelope(envelope)?;
2110 let envelope_digest = value
2111 .signature_envelope_sha256
2112 .as_deref()
2113 .ok_or(ReleaseContractError::InvalidDocument)?;
2114 let finalized_at = value
2115 .finalized_at
2116 .as_deref()
2117 .and_then(canonical_timestamp_seconds)
2118 .ok_or(ReleaseContractError::InvalidDocument)?;
2119 if value.failure_code.is_some()
2120 || value.failed_at.is_some()
2121 || finalized_at < reserved_at
2122 || envelope.subject_identity_sha256 != value.subject_identity_sha256
2123 || envelope.signing_preimage_sha256 != value.signing_preimage_sha256
2124 || envelope.algorithm != value.algorithm
2125 || envelope.key_id != value.key_id
2126 || !valid_sha256(envelope_digest)
2127 || sha256_hex(&canonical_json(envelope)?) != envelope_digest
2128 {
2129 return invalid_document();
2130 }
2131 }
2132 SigningLedgerEntryState::TerminalFailed => {
2133 let failed_at = value
2134 .failed_at
2135 .as_deref()
2136 .and_then(canonical_timestamp_seconds)
2137 .ok_or(ReleaseContractError::InvalidDocument)?;
2138 if value.signature_envelope.is_some()
2139 || value.signature_envelope_sha256.is_some()
2140 || value.finalized_at.is_some()
2141 || value.failure_code.is_none()
2142 || failed_at < reserved_at
2143 {
2144 return invalid_document();
2145 }
2146 }
2147 }
2148 Ok(())
2149}
2150
2151fn validate_signing_ledger_log_leaf(
2152 value: &SigningLedgerLogLeafV1,
2153) -> Result<(), ReleaseContractError> {
2154 if value.schema_version != SIGNING_LEDGER_LOG_LEAF_SCHEMA_VERSION
2155 || !valid_new_id(&value.source_id)
2156 || value
2157 .channel
2158 .as_deref()
2159 .is_some_and(|channel| !valid_new_id(channel))
2160 || !valid_sha256(&value.subject_identity_sha256)
2161 || !valid_sha256(&value.signing_preimage_sha256)
2162 || !valid_sha256(&value.signature_envelope_sha256)
2163 || !valid_json_safe_positive(value.sequence)
2164 {
2165 return invalid_document();
2166 }
2167 Ok(())
2168}
2169
2170fn validate_signing_ledger_checkpoint(
2171 value: &SigningLedgerCheckpointV1,
2172) -> Result<(), ReleaseContractError> {
2173 if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2174 || value.kind != "checkpoint"
2175 || !valid_new_id(&value.log_id)
2176 || !valid_json_safe_positive(value.tree_size)
2177 || !valid_sha256(&value.log_root_hash)
2178 || !valid_sha256(&value.latest_map_root_hash)
2179 || canonical_timestamp_seconds(&value.checkpoint_time).is_none()
2180 || !valid_new_id(&value.key_id)
2181 || decode_signature(&value.signature).is_err()
2182 {
2183 return invalid_document();
2184 }
2185 Ok(())
2186}
2187
2188fn validate_signing_ledger_receipt(
2189 value: &SigningLedgerReceiptV1,
2190) -> Result<(), ReleaseContractError> {
2191 if value.schema_version != SIGNING_LEDGER_RECEIPT_SCHEMA_VERSION
2192 || !valid_new_id(&value.log_id)
2193 || !valid_new_id(&value.source_id)
2194 || value
2195 .channel
2196 .as_deref()
2197 .is_some_and(|channel| !valid_new_id(channel))
2198 || !valid_sha256(&value.subject_identity_sha256)
2199 || !valid_sha256(&value.signing_preimage_sha256)
2200 || !valid_sha256(&value.signature_envelope_sha256)
2201 || !valid_json_safe_positive(value.sequence)
2202 || value.leaf_index != value.sequence - 1
2203 || !valid_json_safe_positive(value.tree_size)
2204 || value.tree_size < value.sequence
2205 || !valid_sha256(&value.log_root_hash)
2206 || !valid_sha256(&value.latest_map_root_hash)
2207 || !valid_sha256(&value.checkpoint_sha256)
2208 || canonical_timestamp_seconds(&value.checkpoint_time).is_none()
2209 || !valid_new_id(&value.key_id)
2210 || decode_signature(&value.signature).is_err()
2211 {
2212 return invalid_document();
2213 }
2214 Ok(())
2215}
2216
2217fn valid_ledger_nodes(values: &[String], expected: Option<usize>) -> bool {
2218 expected.map_or(values.len() <= 64, |count| values.len() == count)
2219 && values.iter().all(|node| valid_sha256(node))
2220}
2221
2222fn validate_signing_ledger_inclusion_proof(
2223 value: &SigningLedgerInclusionProofV1,
2224) -> Result<(), ReleaseContractError> {
2225 if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2226 || value.kind != "inclusion_proof"
2227 || !valid_new_id(&value.log_id)
2228 || !valid_json_safe_positive(value.tree_size)
2229 || value.leaf_index >= value.tree_size
2230 || !valid_ledger_nodes(&value.nodes, None)
2231 {
2232 return invalid_document();
2233 }
2234 Ok(())
2235}
2236
2237fn validate_signing_ledger_latest_proof(
2238 value: &SigningLedgerLatestProofV1,
2239) -> Result<(), ReleaseContractError> {
2240 if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2241 || value.kind != "latest_proof"
2242 || !valid_new_id(&value.log_id)
2243 || !valid_sha256(&value.subject_identity_sha256)
2244 || !valid_ledger_nodes(&value.siblings, Some(256))
2245 {
2246 return invalid_document();
2247 }
2248 if value.present {
2249 if !value.sequence.is_some_and(valid_json_safe_positive)
2250 || !value
2251 .signing_preimage_sha256
2252 .as_deref()
2253 .is_some_and(valid_sha256)
2254 || !value
2255 .signature_envelope_sha256
2256 .as_deref()
2257 .is_some_and(valid_sha256)
2258 {
2259 return invalid_document();
2260 }
2261 } else if value.sequence.is_some()
2262 || value.signing_preimage_sha256.is_some()
2263 || value.signature_envelope_sha256.is_some()
2264 {
2265 return invalid_document();
2266 }
2267 Ok(())
2268}
2269
2270fn validate_signing_ledger_consistency_proof(
2271 value: &SigningLedgerConsistencyProofV1,
2272) -> Result<(), ReleaseContractError> {
2273 if value.schema_version != SIGNING_LEDGER_SCHEMA_VERSION
2274 || value.kind != "consistency_proof"
2275 || !valid_new_id(&value.log_id)
2276 || !valid_json_safe_positive(value.old_tree_size)
2277 || value.new_tree_size < value.old_tree_size
2278 || value.new_tree_size > 9_007_199_254_740_991
2279 || !valid_ledger_nodes(&value.nodes, None)
2280 {
2281 return invalid_document();
2282 }
2283 Ok(())
2284}
2285
2286fn valid_json_safe_positive(value: u64) -> bool {
2287 value > 0 && value <= 9_007_199_254_740_991
2288}
2289
2290fn sha256_hex(value: &[u8]) -> String {
2291 Sha256::digest(value)
2292 .iter()
2293 .map(|byte| format!("{byte:02x}"))
2294 .collect()
2295}
2296
2297fn invalid_document<T>() -> Result<T, ReleaseContractError> {
2298 Err(ReleaseContractError::InvalidDocument)
2299}
2300
2301fn validate_signature_field(value: &str, required: bool) -> Result<(), ReleaseContractError> {
2302 if !required && value.is_empty() {
2303 return Ok(());
2304 }
2305 decode_signature(value).map(|_| ())
2306}
2307
2308fn validate_epoch_chain(
2309 epoch: &str,
2310 previous_epoch: &str,
2311 previous_digest: &str,
2312) -> Result<(), ReleaseContractError> {
2313 if !valid_positive_epoch(epoch)
2314 || !valid_epoch(previous_epoch)
2315 || !valid_sha256(previous_digest)
2316 || increment_decimal(previous_epoch).as_deref() != Some(epoch)
2317 {
2318 return invalid_document();
2319 }
2320 if previous_epoch == GENESIS_PREVIOUS_EPOCH {
2321 if previous_digest != GENESIS_PREVIOUS_DOCUMENT_SHA256 {
2322 return invalid_document();
2323 }
2324 } else if previous_digest == GENESIS_PREVIOUS_DOCUMENT_SHA256 {
2325 return invalid_document();
2326 }
2327 Ok(())
2328}
2329
2330fn increment_decimal(value: &str) -> Option<String> {
2331 if !valid_epoch(value) {
2332 return None;
2333 }
2334 let mut bytes = value.as_bytes().to_vec();
2335 let mut index = bytes.len();
2336 while index > 0 {
2337 index -= 1;
2338 if bytes[index] < b'9' {
2339 bytes[index] += 1;
2340 return String::from_utf8(bytes).ok();
2341 }
2342 bytes[index] = b'0';
2343 }
2344 bytes.insert(0, b'1');
2345 String::from_utf8(bytes).ok()
2346}
2347
2348fn validate_time_range(
2349 generated_at: &str,
2350 expires_at: &str,
2351 maximum_seconds: Option<i64>,
2352) -> Result<(i64, i64), ReleaseContractError> {
2353 let generated =
2354 canonical_timestamp_seconds(generated_at).ok_or(ReleaseContractError::InvalidDocument)?;
2355 let expires =
2356 canonical_timestamp_seconds(expires_at).ok_or(ReleaseContractError::InvalidDocument)?;
2357 if expires <= generated || maximum_seconds.is_some_and(|maximum| expires - generated > maximum)
2358 {
2359 return invalid_document();
2360 }
2361 Ok((generated, expires))
2362}
2363
2364fn canonical_timestamp_seconds(value: &str) -> Option<i64> {
2365 let bytes = value.as_bytes();
2366 if bytes.len() != 20
2367 || bytes[4] != b'-'
2368 || bytes[7] != b'-'
2369 || bytes[10] != b'T'
2370 || bytes[13] != b':'
2371 || bytes[16] != b':'
2372 || bytes[19] != b'Z'
2373 {
2374 return None;
2375 }
2376 let year = parse_digits(&bytes[0..4])? as i32;
2377 let month = parse_digits(&bytes[5..7])? as u32;
2378 let day = parse_digits(&bytes[8..10])? as u32;
2379 let hour = parse_digits(&bytes[11..13])? as i64;
2380 let minute = parse_digits(&bytes[14..16])? as i64;
2381 let second = parse_digits(&bytes[17..19])? as i64;
2382 if !(1..=12).contains(&month)
2383 || day < 1
2384 || day > days_in_month(year, month)
2385 || hour > 23
2386 || minute > 59
2387 || second > 59
2388 {
2389 return None;
2390 }
2391 Some(days_from_civil(year, month, day) * 86_400 + hour * 3_600 + minute * 60 + second)
2392}
2393
2394fn parse_digits(bytes: &[u8]) -> Option<u32> {
2395 if bytes.is_empty() || bytes.iter().any(|byte| !byte.is_ascii_digit()) {
2396 return None;
2397 }
2398 bytes.iter().try_fold(0_u32, |value, byte| {
2399 value.checked_mul(10)?.checked_add(u32::from(byte - b'0'))
2400 })
2401}
2402
2403fn days_in_month(year: i32, month: u32) -> u32 {
2404 match month {
2405 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2406 4 | 6 | 9 | 11 => 30,
2407 2 if year % 400 == 0 || (year % 4 == 0 && year % 100 != 0) => 29,
2408 2 => 28,
2409 _ => 0,
2410 }
2411}
2412
2413fn days_from_civil(year: i32, month: u32, day: u32) -> i64 {
2414 let adjusted_year = i64::from(year) - i64::from(month <= 2);
2415 let era = if adjusted_year >= 0 {
2416 adjusted_year
2417 } else {
2418 adjusted_year - 399
2419 } / 400;
2420 let year_of_era = adjusted_year - era * 400;
2421 let adjusted_month = i64::from(month) + if month > 2 { -3 } else { 9 };
2422 let day_of_year = (153 * adjusted_month + 2) / 5 + i64::from(day) - 1;
2423 let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
2424 era * 146_097 + day_of_era - 719_468
2425}
2426
2427fn validate_sorted_ids(
2428 values: &[String],
2429 minimum: usize,
2430 maximum: usize,
2431 lower: bool,
2432) -> Result<(), ReleaseContractError> {
2433 if values.len() < minimum || values.len() > maximum {
2434 return invalid_document();
2435 }
2436 let mut previous = "";
2437 for value in values {
2438 let valid = if lower {
2439 valid_new_id(value)
2440 } else {
2441 valid_legacy_id(value)
2442 };
2443 if !valid || value.as_str() <= previous {
2444 return invalid_document();
2445 }
2446 previous = value;
2447 }
2448 Ok(())
2449}
2450
2451fn valid_new_id(value: &str) -> bool {
2452 value.len() <= 128
2453 && value.as_bytes().first().is_some_and(u8::is_ascii_lowercase)
2454 && value.bytes().all(|byte| {
2455 byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'_' | b'-')
2456 })
2457}
2458
2459fn valid_legacy_id(value: &str) -> bool {
2460 value.len() <= 128
2461 && value
2462 .as_bytes()
2463 .first()
2464 .is_some_and(u8::is_ascii_alphanumeric)
2465 && value
2466 .bytes()
2467 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
2468}
2469
2470fn valid_epoch(value: &str) -> bool {
2471 value == "0" || valid_positive_epoch(value)
2472}
2473
2474fn valid_positive_epoch(value: &str) -> bool {
2475 !value.is_empty()
2476 && value.as_bytes()[0].is_ascii_digit()
2477 && value.as_bytes()[0] != b'0'
2478 && value.bytes().all(|byte| byte.is_ascii_digit())
2479}
2480
2481fn valid_sha256(value: &str) -> bool {
2482 value.len() == 64
2483 && value
2484 .bytes()
2485 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2486}
2487
2488fn valid_prefixed_sha256(value: &str) -> bool {
2489 value.strip_prefix("sha256:").is_some_and(valid_sha256)
2490}
2491
2492fn valid_legacy_sha256(value: &str) -> bool {
2493 valid_sha256(value) || valid_prefixed_sha256(value)
2494}
2495
2496fn valid_artifact_ref(value: &str) -> bool {
2497 !value.is_empty()
2498 && value.len() <= 1024
2499 && !value.starts_with('/')
2500 && !value.contains('\\')
2501 && !value.contains(['?', '#'])
2502 && value.bytes().all(|byte| {
2503 byte.is_ascii_alphanumeric()
2504 || matches!(byte, b'.' | b'_' | b'/' | b'@' | b'+' | b'~' | b'-')
2505 })
2506 && value
2507 .split('/')
2508 .all(|segment| !segment.is_empty() && segment != "." && segment != "..")
2509}
2510
2511fn valid_hostname(value: &str) -> bool {
2512 !value.is_empty()
2513 && value
2514 .as_bytes()
2515 .first()
2516 .is_some_and(u8::is_ascii_alphanumeric)
2517 && value
2518 .as_bytes()
2519 .last()
2520 .is_some_and(u8::is_ascii_alphanumeric)
2521 && value
2522 .bytes()
2523 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-'))
2524}
2525
2526fn valid_semver(value: &str) -> bool {
2527 semver::Version::parse(value)
2528 .map(|version| version.to_string() == value)
2529 .unwrap_or(false)
2530}