1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Sneakernet (offline) license activation support
//!
//! This module provides file formats for offline "sneakernet" license activation,
//! where users can:
//!
//! 1. Generate a `.req` file on an air-gapped machine
//! 2. Transfer it to an internet-connected machine
//! 3. Get a `.resp` file from the licensing server
//! 4. Transfer the `.resp` back and install the license
//!
//! ## File Formats
//!
//! Both `.req` and `.resp` files support two formats:
//!
//! - **Binary format**: Compact, efficient for file transfer
//! - **Base64 text format**: Human-readable, suitable for email/copy-paste
//!
//! ## Security
//!
//! - Request/response records include an **integrity digest** (SHA-256 of canonical fields) to catch accidental corruption; it is not a keyed MAC.
//! - Responses embed the **cryptographically signed license** (`SignedLicense`); always verify that signature.
//! - Binary payloads enforce [`MAX_SNEAKERNET_JSON_PAYLOAD`] before JSON decode (DoS bound).
//! - Both formats include version fields for future evolution.
//!
//! ## Example Usage
//!
//! ### Client-side (air-gapped machine)
//!
//! ```rust,ignore
//! use licenz_core::sneakernet::{ActivationRequest, ActivationRequestBuilder};
//! use licenz_core::anti_tamper::HardwareFingerprint;
//!
//! // Generate activation request
//! let request = ActivationRequestBuilder::new()
//! .product_id("MY-APP")
//! .fingerprint(HardwareFingerprint::generate())
//! .feature("pro")
//! .feature("enterprise")
//! .build()
//! .unwrap();
//!
//! // Save as binary .req file
//! request.save_binary("activation.req".as_ref()).unwrap();
//!
//! // Or get as base64 text for email
//! let text = request.to_base64().unwrap();
//! ```
//!
//! ### Server-side (licensing server)
//!
//! ```rust,ignore
//! use licenz_core::sneakernet::{ActivationRequest, ActivationResponse};
//!
//! // Load the request
//! let request = ActivationRequest::load("activation.req".as_ref()).unwrap();
//!
//! // Validate and create license...
//! // let signed_license = create_license_for_request(&request);
//!
//! // Create response
//! let response = ActivationResponse::new(
//! request.request_id,
//! signed_license,
//! chrono::Utc::now() + chrono::Duration::days(365),
//! );
//!
//! // Save as .resp file
//! response.save_binary("activation.resp".as_ref()).unwrap();
//! ```
//!
//! ### Client-side (installing the license)
//!
//! ```rust,ignore
//! use licenz_core::sneakernet::ActivationResponse;
//!
//! // Load response
//! let response = ActivationResponse::load("activation.resp".as_ref()).unwrap();
//!
//! // Extract and save the license
//! let license = response.extract_license().unwrap();
//! // Save license to appropriate location...
//! ```
pub use ;
pub use ActivationResponse;
/// Magic header for activation request files (.req)
pub const REQUEST_MAGIC: & = b"LREQ";
/// Magic header for activation response files (.resp)
pub const RESPONSE_MAGIC: & = b"LRSP";
/// Current format version for request files
pub const REQUEST_VERSION: u8 = 1;
/// Current format version for response files
pub const RESPONSE_VERSION: u8 = 1;
/// Maximum JSON payload size (bytes) inside binary `.req` / `.resp` wrappers.
pub const MAX_SNEAKERNET_JSON_PAYLOAD: usize = 2 * 1024 * 1024;
/// Base64 prefix for text-format request files
pub const REQUEST_TEXT_PREFIX: &str = "-----BEGIN LICENZ ACTIVATION REQUEST-----";
/// Base64 suffix for text-format request files
pub const REQUEST_TEXT_SUFFIX: &str = "-----END LICENZ ACTIVATION REQUEST-----";
/// Base64 prefix for text-format response files
pub const RESPONSE_TEXT_PREFIX: &str = "-----BEGIN LICENZ ACTIVATION RESPONSE-----";
/// Base64 suffix for text-format response files
pub const RESPONSE_TEXT_SUFFIX: &str = "-----END LICENZ ACTIVATION RESPONSE-----";
/// File format type
/// Detect the format of a sneakernet file