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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
//! The [Manifest] specification
//! 

use core::ops::Deref;

use bitflags::bitflags;
use encdec::{Encode, Decode, DecodeOwned};
use rand_core::{RngCore, CryptoRng};
use sha2::{Sha512, Digest};
use strum::{Display, EnumString, EnumVariantNames};


use crate::{
    error::ManifestError,
    types::{PublicKey, Checksum, Signature, PrivateKey, Stringish},
    VerifyError,
};

/// Manifest version identifier, MUST be 0x0001, MAY be extended in following versions
pub const MANIFEST_VERSION: u16 = 0x0001;

/// Encoded manifest length, constant to simplify parsing when included in binary form
pub const MANIFEST_LEN: usize = 2 + 2 
    + 16 + 24
    + 4 + 32 
    + 2 + 2 + 32 
    + ed25519_dalek::PUBLIC_KEY_LENGTH
    + ed25519_dalek::SIGNATURE_LENGTH;

/// Metadata format enumeration
#[derive(Copy, Clone, Debug, PartialEq, Display, EnumString, EnumVariantNames)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[repr(u16)]
pub enum MetadataFormat {
    Binary = 0x0000,
    Json = 0x0001,
    Cbor = 0x0002,
    Other = 0xFFFF,
}

bitflags! {
    /// Manifest information flags
    pub struct Flags: u16 {
        /// Indicate the signing key is transient / should not prompt for TOFU if available
        const TRANSIENT_KEY = 1 << 0;
    }
}

/// Applet manifest, links app and metadata checksums with overall applet signature
/// 
/// Encoding:
/// 
/// ```text
/// 0                   1                   2                   3
/// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |       MANIFEST_VERSION        |         MANIFEST_FLAGS        |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                                                               |
/// |                            APP_NAME                           |
/// |                   (16-byte zero padded utf8)                  |
/// |                                                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                                                               |
/// /                           APP_VERSION                         /
/// /                   (24-byte zero padded utf8)                  /
/// |                                                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                        APP_LENGTH (u32)                       |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                                                               |
/// /                         APP_CHECKSUM                          /
/// /                   (256-bit truncated SHA512)                  /
/// |                                                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |           META_KIND           |       META_LENGTH (u16)       |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                                                               |
/// /                        META_CHECKSUM                          /
/// /                   (256-bit truncated SHA512)                  /
/// |                                                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                                                               |
/// /                         SIGNING KEY                           /
/// /                      (ED25519 Public Key)                     /
/// |                                                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                                                               |
/// /                          SIGNATURE                            /
/// /                      (ED25519 Signature)                      /
/// |                                                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// ```
/// 
#[derive(Clone, Debug, PartialEq, Encode, DecodeOwned)]
pub struct Manifest {
    /// Manifest version (must be 1)
    pub version: u16,
    /// Manifest flags
    pub flags: u16,

    /// Application name (utf8, zero-padded)
    pub app_name: Stringish<16>,

    /// Application Version (utf8, zero-padded)
    pub app_version: Stringish<24>,

    /// Application binary length
    pub app_len: u32,
    /// Application binary checksum (sha512)
    pub app_csum: Checksum,

    /// Metadata encoding kind
    pub meta_kind: u16,
    /// Metadata binary length
    pub meta_len: u16,
    /// Metadata binary checksum
    pub meta_csum: Checksum,

    /// Public key used to sign manifest
    /// 
    /// For released firmware allowed public keys should be pinned via bootloader,
    /// where release keys are not available transient keys will be used
    /// to construct a valid manifest object.
    pub key: PublicKey,

    /// Signature over manifest data, against the specified public key
    pub sig: Signature,
}

impl Manifest {
    
    /// Fetch manifest flags
    pub fn flags(&self) -> Flags {
        Flags::from_bits_truncate(self.flags)
    }

    /// Fetch app name
    pub fn app_name(&self) -> &str {
        self.app_name.as_ref()
    }

    /// Fetch app version
    pub fn app_version(&self) -> &str {
        self.app_version.as_ref()
    }

    /// Fetch app length
    pub fn app_len(&self) -> usize {
        self.app_len as usize
    }

    /// Fetch metadata length
    pub fn meta_len(&self) -> usize {
        self.meta_len as usize
    }

    /// Sign manifest using provided key
    pub fn sign<RNG: RngCore + CryptoRng + Default>(&mut self, signing_key: PrivateKey) -> Result<(), ManifestError> {        
        // Generate manifest digest for signing
        let digest = self.digest();

        // Load keys
        let public_key = ed25519_dalek::PublicKey::from(signing_key.deref());
        let keys = ed25519_dalek::Keypair{ public: public_key, secret: signing_key.inner() };

        // Generate signature
        let sig = keys.sign_prehashed(digest, None)
            .map_err(|_e| ManifestError::SigningFailed)?;

        // Write signature to manifest
        self.sig = Signature::from(sig);

        Ok(())
    }

    /// Verify manifest signature against allowed keys
    pub fn verify(&self, allowed_keys: &[PublicKey]) -> Result<(), ManifestError> {
        // Lookup signing key in allowed key listing
        let signing_key = match allowed_keys.iter().find(|k| *k == &self.key) {
            Some(k) => k,
            None => return Err(ManifestError::NoMatchingKey),
        };

        // Generate manifest digest for verification
        let digest = self.digest();

        // Verify signature
        let sig = ed25519_dalek::Signature::try_from(&self.sig)?;

        signing_key.verify_prehashed(digest, None, &sig)
            .map_err(|e| ManifestError::VerificationFailed)?;

        Ok(())
    }


    /// Check application and metadata against manifest
    pub fn check(&self, app: &[u8], meta: &[u8]) -> Result<(), VerifyError> {
        // Ensure signature is valid / object is well formed
        self.check_sig()?;

        // Check app length and checksum
        let app_csum = Checksum::compute(app);
        self.check_app(app.len(), &app_csum)?;

        // Check meta length and checksum
        let meta_csum = Checksum::compute(meta);
        self.check_meta(meta.len(), &meta_csum)?;

        Ok(())
    }

    /// Check application and metadata against manifest using pre-computed values
    /// 
    /// This is useful where the app is not entirely in memory for checksum computations
    pub fn check_precomputed(&self, app_csum: &Checksum, app_len: usize, meta_csum: &Checksum, meta_len: usize) -> Result<(), VerifyError> {
        // Ensure signature is valid / object is well formed
        self.check_sig()?;

        // Check app length and checksum
        self.check_app(app_len, app_csum)?;

        // Check meta length and checksum
        self.check_meta(meta_len, meta_csum)?;
        
        // TODO: check signing key against allowed
        
        Ok(())
    }

    /// Internal helper to check the manifest signature is valid
    fn check_sig(&self) -> Result<(), VerifyError> {
        // Generate manifest digest for verification
        let digest = self.digest();

        // Check manifest signature (should _always_ be valid)
        let sig = ed25519_dalek::Signature::try_from(&self.sig)
            .map_err(|_| VerifyError::InvalidSignature)?;
        self.key.deref().verify_prehashed(digest, None, &sig)
            .map_err(|e| VerifyError::InvalidSignature)?;

        Ok(())
    }

    /// Internal helper to check app app length and checksum match
    fn check_app(&self, app_len: usize, app_csum: &Checksum) -> Result<(), VerifyError> {
        if app_len != self.app_len() {
            return Err(VerifyError::AppLengthMismatch)
        }
        if app_csum != &self.app_csum {
            return Err(VerifyError::AppChecksumMismatch)
        }
        Ok(())
    }

    /// Internal helper to check app app length and checksum match
    fn check_meta(&self, meta_len: usize, meta_csum: &Checksum) -> Result<(), VerifyError> {
        if meta_len != self.meta_len() {
            return Err(VerifyError::MetaLengthMismatch)
        }
        if meta_csum != &self.meta_csum {
            return Err(VerifyError::MetaChecksumMismatch)
        }
        Ok(())
    }

    /// Compute digest of manifest for signing
    /// 
    /// (this is equivalent to computing the digest over the encoded object,
    /// while avoiding the need to encode prior to signing)
    fn digest(&self) -> Sha512 {
        let mut h = Sha512::new();

        h.update(&self.version.to_le_bytes());
        h.update(&self.flags.to_le_bytes());

        h.update(&self.app_name.deref());
        h.update(&self.app_version.deref());

        h.update(&self.app_len.to_le_bytes());
        h.update(&self.app_csum.deref());

        h.update(&self.meta_kind.to_le_bytes());
        h.update(&self.meta_len.to_le_bytes());
        h.update(&self.meta_csum.deref());

        h.update(&self.key.deref());

        h
    }

}

#[cfg(test)]
mod tests {
    use ed25519_dalek::SIGNATURE_LENGTH;
    use rand::rngs::OsRng;

    use super::*;

    #[test]
    fn sign_verify() {
        // Setup keys
        let private_key = PrivateKey::generate(&mut OsRng{});
        let public_key = PublicKey::from(&private_key);

        let mut m = Manifest {
            version: MANIFEST_VERSION,
            flags: Flags::TRANSIENT_KEY.bits(),
            app_name: "test_app".into(),
            app_version: "1.2.7".into(),
            app_len: 64 * 1024,
            app_csum: Checksum::compute(&[0xab; 32]),
            meta_len: 1024,
            meta_csum: Checksum::compute(&[0xbc; 32]),
            meta_kind: MetadataFormat::Binary as u16,
            key: public_key.clone(),
            sig: Signature::empty(),
        };

      
        // Perform signing
        m.sign::<OsRng>(private_key).expect("Signing failed");

        // Check manifest fields are updated
        assert_eq!(&m.key, &public_key);
        assert_ne!(m.sig.deref(), &[0u8; 64]);

        // Encode and decode manifest object
        let mut b = [0u8; 256];

        let n = m.encode(&mut b).unwrap();

        assert_eq!(n, MANIFEST_LEN);
        assert_eq!(m.encode_len().unwrap(), MANIFEST_LEN);


        let (m1, n1) = Manifest::decode(&b[..n]).unwrap();
        assert_eq!(n, n1);

        // Verify decoded manifest
        m1.verify(&[public_key]).expect("Verification failed");
    }

    #[test]
    fn digests_match() {
        // Setup keys
        let private_key = PrivateKey::generate(&mut OsRng{});
        let public_key = PublicKey::from(&private_key);

        // Build manifest object
        let m = Manifest {
            version: MANIFEST_VERSION,
            flags: Flags::TRANSIENT_KEY.bits(),
            app_name: "test_app".into(),
            app_version: "1.2.7".into(),
            app_len: 64 * 1024,
            app_csum: Checksum::compute(&[0xab; 32]),
            meta_len: 1024,
            meta_csum: Checksum::compute(&[0xbc; 32]),
            meta_kind: MetadataFormat::Binary as u16,
            key: public_key.clone(),
            sig: Signature::empty(),
        };

        // Compute piecewise (pre-encode) digest
        let d = m.digest().finalize();

        // Encode manifest object
        let mut b = [0u8; 256];
        let n = m.encode(&mut b).unwrap();

        // Compute complete (post-encode) digest
        let mut h = Sha512::new();
        h.update(&b[..MANIFEST_LEN - SIGNATURE_LENGTH]);
        let d1 = h.finalize();

        // Check pre- and post-encode digest methods match
        assert_eq!(d, d1);
        
    }
}