// The integrity of a package.
//
// A package carries a manifest that says what the entries are, where they
// are and which one of them is the program to start. The manifest is
// therefore the part of a package that an attacker wants to change: a
// manifest that names another program, or an install script, turns a package
// into something else.
//
// Three mechanisms protect the manifest, and each of them answers a different
// attack.
//
//   * The digest of the integrity block says that the manifest is the one the
//     packer wrote. It finds corruption, a write that was cut short and a
//     manifest that was changed in place. It is not a secret, so it does not
//     stop an attacker who rewrites the whole file: such an attacker simply
//     computes a digest that matches the manifest they wrote.
//
//   * The MAC of the same block is keyed with the key of the package. It is
//     what stops that attacker for an encrypted package, because the key is
//     not in the package.
//
//   * The signature that the host publishes next to the package is what stops
//     them for an unencrypted package, because the verification material is
//     with the host and not with the package.
//
// The block sits between the last entry and the manifest, so a package that
// carries one is still a package that an older LunaPack can read: the older
// reader steps over the block and finds the manifest where it expects it.
//
// The block has two shapes, and the shape says whether the package is
// encrypted:
//
//   [marker 8][digest 32]              40 bytes, an unencrypted package
//   [marker 8][digest 32][mac 32]      72 bytes, an encrypted package
//
// The two shapes have different lengths on purpose. The reader finds the
// block by looking for its marker at the two places it can be, so an
// attacker cannot turn an encrypted package into an unencrypted one by
// rewriting a marker: the block would no longer be where the reader looks
// for it, and the reader would find no block at all.

///|
/// The marker of the integrity block of a package that is not encrypted.
pub const INTEGRITY_MARKER : String = "LPMANIFU"

///|
/// The marker of the integrity block of an encrypted package.
pub const INTEGRITY_MARKER_ENCRYPTED : String = "LPMANIFE"

///|
/// The number of bytes of the marker of the integrity block.
pub const INTEGRITY_MARKER_BYTES : Int32 = 8

///|
/// The number of bytes of the digest of the manifest.
pub const INTEGRITY_DIGEST_BYTES : Int32 = 32

///|
/// The number of bytes of the MAC of the manifest.
pub const INTEGRITY_MAC_BYTES : Int32 = 32

///|
/// The number of bytes of the integrity block of a package that is not
/// encrypted: the marker and the digest.
pub const INTEGRITY_BLOCK_BYTES : Int32 = INTEGRITY_MARKER_BYTES +
  INTEGRITY_DIGEST_BYTES

///|
/// The number of bytes of the integrity block of an encrypted package: the
/// marker, the digest and the MAC.
pub const INTEGRITY_BLOCK_BYTES_ENCRYPTED : Int32 = INTEGRITY_MARKER_BYTES +
  INTEGRITY_DIGEST_BYTES +
  INTEGRITY_MAC_BYTES

///|
/// The label of the derivation of the key of the MAC of the manifest.
///
/// It is a label of its own, so that the key of the MAC is neither the key of
/// the package nor the key of an entry.
const LABEL_MANIFEST_MAC : String = "lunapack-v6|manifest-mac"

///|
/// The key of the MAC of the manifest, derived from the key of the package.
///
/// The derivation uses the salt of the package and a label of its own, so
/// that the MAC key cannot be used for anything else.
pub fn manifest_mac_key(key : PackageKey) -> Bytes {
  hkdf_sha512(
    key.master,
    key.salt,
    string_to_utf8_bytes(LABEL_MANIFEST_MAC),
    KEY_BYTES,
  )
}

///|
/// The bytes that the digest and the MAC of the manifest are computed over.
///
/// The two numbers that place the manifest are part of them, so that a footer
/// that points somewhere else does not pass the check.
fn manifest_input(moff : Int64, mlen : Int64, mbytes : Bytes) -> Bytes {
  bytes_concat(bytes_concat(i64_to_le8(moff), i64_to_le8(mlen)), mbytes)
}

///|
/// The digest of the manifest: the SHA-256 of the manifest and of the two
/// numbers that place it.
pub fn manifest_digest(
  moff : Int64,
  mlen : Int64,
  mbytes : Bytes,
) -> Bytes raise {
  match hex_to_bytes(sha256_hex(manifest_input(moff, mlen, mbytes))) {
    Some(b) => b
    None => fail("the digest of the manifest could not be read")
  }
}

///|
/// The MAC of the manifest, keyed with the key of the package.
///
/// It is the truncated HMAC-SHA512 of the manifest and of the two numbers
/// that place it, under a key that is derived from the key of the package.
pub fn manifest_mac(
  key : PackageKey,
  moff : Int64,
  mlen : Int64,
  mbytes : Bytes,
) -> Bytes {
  truncate_bytes(
    hmac_sha512(manifest_mac_key(key), manifest_input(moff, mlen, mbytes)),
    INTEGRITY_MAC_BYTES,
  )
}

///|
/// The integrity block of a package that is not encrypted: the marker and the
/// digest of the manifest.
pub fn integrity_block(
  moff : Int64,
  mlen : Int64,
  mbytes : Bytes,
) -> Bytes raise {
  bytes_concat(
    string_to_utf8_bytes(INTEGRITY_MARKER),
    manifest_digest(moff, mlen, mbytes),
  )
}

///|
/// The integrity block of an encrypted package: the marker, the digest of the
/// manifest and the MAC of the manifest.
pub fn integrity_block_encrypted(
  key : PackageKey,
  moff : Int64,
  mlen : Int64,
  mbytes : Bytes,
) -> Bytes raise {
  bytes_concat(
    bytes_concat(
      string_to_utf8_bytes(INTEGRITY_MARKER_ENCRYPTED),
      manifest_digest(moff, mlen, mbytes),
    ),
    manifest_mac(key, moff, mlen, mbytes),
  )
}

///|
/// Do the first `count` bytes of `block` hold `marker`?
fn block_starts_with(block : Bytes, marker : String, count : Int32) -> Bool {
  if block.length() < count {
    return false
  }
  let want = string_to_utf8_bytes(marker)
  let mut i = 0
  while i < count {
    if block.at(i) != want.at(i) {
      return false
    }
    i = i + 1
  }
  true
}

///|
/// Is the block the integrity block of a package that is not encrypted?
pub fn is_integrity_block(block : Bytes) -> Bool {
  block_starts_with(block, INTEGRITY_MARKER, INTEGRITY_MARKER_BYTES)
}

///|
/// Is the block the integrity block of an encrypted package?
pub fn is_integrity_block_encrypted(block : Bytes) -> Bool {
  block_starts_with(block, INTEGRITY_MARKER_ENCRYPTED, INTEGRITY_MARKER_BYTES)
}

///|
/// A checksum that is computed twice, compared with itself.
///
/// The computation is passed in, so that the comparison itself can be
/// tested with a machine that computes a different answer the second time.
///
/// A checksum that is computed once can be wrong in two ways at once: the
/// data can be wrong, and the computation can be wrong. Comparing the
/// checksum against a stored value catches the first kind and says nothing
/// about the second, so the checksum is computed a second time and the two
/// answers are compared. A machine that computes a different answer the
/// second time is not to be trusted with the answer at all.
fn redundant_with(data : Bytes, compute : (Bytes) -> String) -> String raise {
  let first = compute(data)
  let second = compute(data)
  if first != second {
    fail(
      "the checksum of the data was not the same twice: the machine is not reliable",
    )
  }
  first
}

///|
/// The SHA-256 of `data`, computed twice.
pub fn redundant_sha256(data : Bytes) -> String raise {
  redundant_with(data, fn(d : Bytes) -> String { sha256_hex(d) })
}

///|
/// The MD5 of `data`, computed twice.
///
/// The MD5 is not a security checksum, but it is what the platform tooling
/// shows, and it is an algorithm that is independent of SHA-256. Two
/// independent algorithms that agree are stronger evidence than one of them
/// twice.
pub fn redundant_md5(data : Bytes) -> String raise {
  redundant_with(data, fn(d : Bytes) -> String { md5_hex(d) })
}