gbl 0.3.1

GBL firmware file handling library
Documentation
use crate::Error;

use hex::FromHex;
use openssl::{
    ec::EcKey,
    pkey::{PKey, Private, Public},
};

/// A symmetric AES-128 encryption/decryption key.
///
/// Can be obtained from a variety of formats and is used by [`Gbl::encrypt`]
/// and [`Gbl::decrypt`].
///
/// [`Gbl::encrypt`]: struct.Gbl.html#method.encrypt
/// [`Gbl::decrypt`]: struct.Gbl.html#method.decrypt
pub struct AesKey([u8; 16]);

impl AesKey {
    /// Creates an AES key from 16 raw bytes.
    ///
    /// # Examples
    ///
    /// ```
    /// # use gbl::*;
    /// # fn run() -> Result<(), Error> {
    /// let key = AesKey::from_raw([
    ///		0xE7, 0xE5, 0x56, 0xB6, 0x35, 0xA3, 0x52, 0x06, // 8
    /// 	0x59, 0xA2, 0xE1, 0x61, 0xCB, 0xDF, 0x4B, 0xC2, // 16
    ///	]);
    /// # Ok(()) }    fn main() { run().unwrap() }
    /// ```
    pub fn from_raw(raw: [u8; 16]) -> Self {
        AesKey(raw)
    }

    /// Creates an AES key from a byte slice containing 16 bytes.
    ///
    /// If the slice does not contain exactly 16 bytes, returns `None`.
    ///
    /// # Examples
    ///
    /// ```
    /// # use gbl::*;
    /// # fn run() -> Result<(), Error> {
    /// assert!(AesKey::from_slice(&[
    ///		0xE7, 0xE5, 0x56, 0xB6, 0x35, 0xA3, 0x52, 0x06, // 8
    /// 	0x59, 0xA2, 0xE1, 0x61, 0xCB, 0xDF, 0x4B, 0xC2, // 16
    ///	]).is_some());
    /// # Ok(()) }    fn main() { run().unwrap() }
    /// ```
    ///
    /// Note that it is preferrable to use [`AesKey::from_raw`] when you already
    /// have a fixed-size array of 16 Bytes.
    ///
    /// When the slice does not contain 16 Bytes, this returns `None`:
    ///
    /// ```
    /// # use gbl::*;
    /// # fn run() -> Result<(), Error> {
    /// assert!(AesKey::from_slice(&[
    ///		0xE7, 0xE5, 0x56, 0xB6, 0x35, 0xA3, 0x52, 0x06, // 8
    /// 	0x59, 0xA2, 0xE1, 0x61, 0xCB, 0xDF, 0x4B, 0xC2, // 16
    /// 	0xFF,  // 17
    ///	]).is_none());
    /// # Ok(()) }    fn main() { run().unwrap() }
    /// ```
    ///
    /// [`AesKey::from_raw`]: #method.from_raw
    pub fn from_slice(slice: &[u8]) -> Option<Self> {
        if slice.len() == 16 {
            let mut raw = [0; 16];
            raw.copy_from_slice(slice);
            Some(Self::from_raw(raw))
        } else {
            None
        }
    }

    /// Parses an AES key from a hexadecimal string.
    ///
    /// # Examples
    ///
    /// ```
    /// # use gbl::*;
    /// # fn run() -> Result<(), Error> {
    /// let key = AesKey::from_hex_str("E7E556B635A3520659A2E161CBDF4BC2")?;
    /// # Ok(()) }    fn main() { run().unwrap() }
    /// ```
    pub fn from_hex_str<S: AsRef<str>>(s: S) -> Result<Self, Error> {
        let raw = <[u8; 16]>::from_hex(s.as_ref()).map_err(|e| {
            Error::parse_err(format!("couldn't parse AES key from hex string: {}", e))
        })?;
        Ok(Self::from_raw(raw))
    }

    /// Parses an AES key from a bootloader token file.
    ///
    /// # Parameters
    ///
    /// * `contents`: Contents of the bootloader token file generated by (eg.)
    ///   `commander gbl keygen --type aes-ccm`. Must contain the key
    ///   `TOKEN_MFG_SECURE_BOOTLOADER_KEY`.
    ///
    /// # Examples
    ///
    /// Load a key from an example token file:
    ///
    /// ```
    /// # use gbl::*;
    /// # fn run() -> Result<(), Error> {
    /// let contents = r"
    /// 	## Key randomly generated
    /// 	TOKEN_MFG_SECURE_BOOTLOADER_KEY: E7E556B635A3520659A2E161CBDF4BC2
    /// ";
    ///
    /// let key = AesKey::from_token_file(contents)?;
    /// # Ok(()) }    fn main() { run().unwrap() }
    /// ```
    pub fn from_token_file<S: AsRef<str>>(contents: S) -> Result<Self, Error> {
        const BL_KEY: &str = "TOKEN_MFG_SECURE_BOOTLOADER_KEY";

        let mut parsed_aes_key = None;
        for line in contents.as_ref().lines() {
            let line = line.trim();
            if line.starts_with('#') || line.is_empty() {
                continue;
            }

            let mut split = line.splitn(2, ':');
            let key = split.next().unwrap();
            let value = split
                .next()
                .ok_or_else(|| Error::parse_err(format!("malformed line: {}", line)))?;
            let key = key.trim();
            let value = value.trim();

            if key != BL_KEY {
                return Err(Error::parse_err(format!(
                    "invalid key in token file: got '{}', expected '{}'",
                    key, BL_KEY
                )));
            }

            // parse the associated 128-bit AES key (in hexadecimal notation)
            let aes_key = AesKey::from_hex_str(value)?;

            if parsed_aes_key.is_some() {
                return Err(Error::parse_err("duplicate AES key entry"));
            }

            parsed_aes_key = Some(aes_key);
        }

        parsed_aes_key.ok_or_else(|| Error::parse_err("no AES key found in token file"))
    }

    /// Returns a reference to the raw 16-Byte AES-128 key.
    pub fn as_raw(&self) -> &[u8; 16] {
        &self.0
    }

    /// Consumes `self` and returns the raw bytes making up the key.
    pub fn into_raw(self) -> [u8; 16] {
        self.0
    }
}

impl AsRef<[u8]> for AesKey {
    fn as_ref(&self) -> &[u8] {
        &self.0
    }
}

impl AsRef<[u8; 16]> for AesKey {
    fn as_ref(&self) -> &[u8; 16] {
        &self.0
    }
}

impl Into<[u8; 16]> for AesKey {
    fn into(self) -> [u8; 16] {
        self.0
    }
}

impl From<[u8; 16]> for AesKey {
    fn from(raw: [u8; 16]) -> Self {
        AesKey(raw)
    }
}

/// An elliptic curve key pair (on P-256 / secp256r1 / prime256v1).
///
/// This struct contains the private key and the corresponding public key.
pub struct P256KeyPair {
    pub(crate) inner: EcKey<Private>,
}

impl P256KeyPair {
    /// Decodes a P-256 key pair from a DER-encoded `ECPrivateKey` structure.
    ///
    /// The `ECPrivateKey` ASN.1 structure is specified in [RFC 5915].
    ///
    /// [RFC 5915]: https://tools.ietf.org/html/rfc5915
    /// # Examples
    ///
    /// ```
    /// # use gbl::{P256KeyPair, Error};
    /// # fn run() -> Result<(), Box<Error>> {
    /// let der = [
    ///     0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0x2b, 0xef, 0xab, 0x60, 0x58,
    ///     0x50, 0xdb, 0x0b, 0x3b, 0x8e, 0xf7, 0xe0, 0x54, 0xd5, 0xc5, 0xfe, 0x63,
    ///     0x95, 0x68, 0xb8, 0xcd, 0xfb, 0x86, 0x9b, 0x45, 0xd0, 0xb0, 0xb3, 0x50,
    ///     0x2c, 0xa3, 0xf5, 0xa0, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
    ///     0x03, 0x01, 0x07, 0xa1, 0x44, 0x03, 0x42, 0x00, 0x04, 0xdc, 0xc7, 0xaf,
    ///     0xdd, 0x92, 0xe7, 0xc2, 0x0b, 0xfe, 0xbb, 0xd7, 0x08, 0x45, 0xb3, 0x4e,
    ///     0x92, 0xea, 0x2d, 0x52, 0xc3, 0x38, 0xaa, 0x9b, 0x68, 0xe8, 0x2b, 0x6c,
    ///     0x82, 0x37, 0x77, 0x29, 0x8f, 0x23, 0x69, 0x39, 0xef, 0x32, 0x72, 0x4c,
    ///     0x43, 0x44, 0xc8, 0x5f, 0x06, 0x6a, 0x6f, 0x37, 0xb1, 0x3e, 0x35, 0x8f,
    ///     0x8a, 0xe5, 0x99, 0x61, 0x99, 0x3d, 0x1e, 0x63, 0x6d, 0x68, 0x5c, 0xc1,
    ///     0xe2
    /// ];
    /// let keypair = P256KeyPair::from_der(&der[..])?;
    /// # Ok(()) }   fn main() { run().unwrap(); }
    /// ```
    pub fn from_der<D: AsRef<[u8]>>(der: D) -> Result<Self, Error> {
        let inner = EcKey::private_key_from_der(der.as_ref())
            .map_err(|e| Error::parse_err(e.to_string()))?;

        Ok(Self { inner })
    }

    /// Decodes a P-256 key pair from a PEM-encoded `ECPrivateKey` structure.
    ///
    /// The `ECPrivateKey` ASN.1 structure is specified in [RFC 5915].
    ///
    /// [RFC 5915]: https://tools.ietf.org/html/rfc5915
    ///
    /// # Examples
    ///
    /// ```
    /// # use gbl::{P256KeyPair, Error};
    /// # fn run() -> Result<(), Box<Error>> {
    /// let pem = r#"
    /// -----BEGIN EC PRIVATE KEY-----
    /// MHcCAQEEICvvq2BYUNsLO4734FTVxf5jlWi4zfuGm0XQsLNQLKP1oAoGCCqGSM49
    /// AwEHoUQDQgAE3Mev3ZLnwgv+u9cIRbNOkuotUsM4qpto6Ctsgjd3KY8jaTnvMnJM
    /// Q0TIXwZqbzexPjWPiuWZYZk9HmNtaFzB4g==
    /// -----END EC PRIVATE KEY-----
    /// "#;
    /// let keypair = P256KeyPair::from_pem(pem)?;
    /// # Ok(()) }   fn main() { run().unwrap(); }
    /// ```
    pub fn from_pem<P: AsRef<str>>(pem: P) -> Result<Self, Error> {
        let inner = EcKey::private_key_from_pem(pem.as_ref().as_bytes())
            .map_err(|e| Error::parse_err(e.to_string()))?;

        Ok(Self { inner })
    }

    /// Returns the public component of this key pair.
    pub fn to_public(&self) -> P256PublicKey {
        let inner = EcKey::from_public_key(self.inner.group(), self.inner.public_key())
            .expect("couldn't turn good key pair into public key");

        P256PublicKey { inner }
    }
}

/// A public P-256 key (aka secp256r1 / prime256v1).
pub struct P256PublicKey {
    pub(crate) inner: EcKey<Public>,
}

impl P256PublicKey {
    /// Decodes a P-256 public key from a DER-encoded `SubjectPublicKeyInfo` structure.
    ///
    /// The `SubjectPublicKeyInfo` structure is described in [RFC 5280].
    ///
    /// # Examples
    ///
    /// ```
    /// # use gbl::{P256PublicKey, Error};
    /// # fn run() -> Result<(), Box<Error>> {
    /// let der = [
    ///     0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
    ///     0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
    ///     0x42, 0x00, 0x04, 0xdc, 0xc7, 0xaf, 0xdd, 0x92, 0xe7, 0xc2, 0x0b, 0xfe,
    ///     0xbb, 0xd7, 0x08, 0x45, 0xb3, 0x4e, 0x92, 0xea, 0x2d, 0x52, 0xc3, 0x38,
    ///     0xaa, 0x9b, 0x68, 0xe8, 0x2b, 0x6c, 0x82, 0x37, 0x77, 0x29, 0x8f, 0x23,
    ///     0x69, 0x39, 0xef, 0x32, 0x72, 0x4c, 0x43, 0x44, 0xc8, 0x5f, 0x06, 0x6a,
    ///     0x6f, 0x37, 0xb1, 0x3e, 0x35, 0x8f, 0x8a, 0xe5, 0x99, 0x61, 0x99, 0x3d,
    ///     0x1e, 0x63, 0x6d, 0x68, 0x5c, 0xc1, 0xe2
    /// ];
    /// let pubkey = P256PublicKey::from_der(&der[..])?;
    /// # Ok(()) }   fn main() { run().unwrap(); }
    /// ```
    ///
    /// [RFC 5280]: https://tools.ietf.org/html/rfc5280
    pub fn from_der<D: AsRef<[u8]>>(der: D) -> Result<Self, Error> {
        let pkey =
            PKey::public_key_from_der(der.as_ref()).map_err(|e| Error::parse_err(e.to_string()))?;
        let inner = pkey.ec_key().map_err(|e| Error::parse_err(e.to_string()))?;

        Ok(Self { inner })
    }

    /// Decodes a P-256 public key from a PEM-encoded `SubjectPublicKeyInfo` structure.
    ///
    /// The PEM data should have a header of `-----BEGIN PUBLIC KEY-----`.
    ///
    /// The `SubjectPublicKeyInfo` structure is described in [RFC 5280].
    ///
    /// # Examples
    ///
    /// ```
    /// # use gbl::{P256PublicKey, Error};
    /// # fn run() -> Result<(), Box<Error>> {
    /// let pem = r#"
    /// -----BEGIN PUBLIC KEY-----
    /// MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3Mev3ZLnwgv+u9cIRbNOkuotUsM4
    /// qpto6Ctsgjd3KY8jaTnvMnJMQ0TIXwZqbzexPjWPiuWZYZk9HmNtaFzB4g==
    /// -----END PUBLIC KEY-----
    /// "#;
    /// let pubkey = P256PublicKey::from_pem(pem)?;
    /// # Ok(()) }   fn main() { run().unwrap(); }
    /// ```
    ///
    /// [RFC 5280]: https://tools.ietf.org/html/rfc5280
    pub fn from_pem<P: AsRef<str>>(pem: P) -> Result<Self, Error> {
        let pkey = PKey::public_key_from_pem(pem.as_ref().as_bytes())
            .map_err(|e| Error::parse_err(e.to_string()))?;
        let inner = pkey.ec_key().map_err(|e| Error::parse_err(e.to_string()))?;

        Ok(Self { inner })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn wrong_number_of_hex_digits() {
        AesKey::from_hex_str("E7E556B635A3520659A2E161CBDF4BC")
            .err()
            .unwrap();
        AesKey::from_hex_str("E7E556B635A3520659A2E161CBDF4BC2").unwrap();
        AesKey::from_hex_str("E7E556B635A3520659A2E161CBDF4BC2F")
            .err()
            .unwrap();
    }

    #[test]
    fn non_hex() {
        AesKey::from_hex_str("E7E556B635A3520659A2E161CBDF4BCX")
            .err()
            .unwrap();
        AesKey::from_hex_str("X7E556B635A3520659A2E161CBDF4BC2")
            .err()
            .unwrap();
        AesKey::from_hex_str("").err().unwrap();
        AesKey::from_hex_str("\0").err().unwrap();
    }

    #[test]
    fn from_slice() {
        let array = [
            0xE7, 0xE5, 0x56, 0xB6, 0x35, 0xA3, 0x52, 0x06, 0x59, 0xA2, 0xE1, 0x61, 0xCB, 0xDF,
            0x4B, 0xC2,
        ];
        let slice: &[u8] = &array;

        let key = AesKey::from_raw(array);
        assert!(AesKey::from_slice(&[]).is_none());
        assert!(AesKey::from_slice(&slice[1..]).is_none());
        assert_eq!(AesKey::from_slice(slice).unwrap().as_raw(), key.as_raw());
    }
}