Module MlFront_Signify.Signify

Introduction

Use this module to have your software update itself or its datasets securely.

Please read the short introduction written by Ted Unangst for OpenBSD: signify: Securing OpenBSD From Us To You

Release Process

You will need to:

  1. Create a keypair with generate_key_pair_exn, or on Unix download "signify" with your package manager and run "signify -G -p newkey.pub -s newkey.sec". This will be for the current release (ex. "2.3.0") and any patches or errata (ex. "2.3.1", "2.3.2", etc.) for the release. Safeguard the private (secret) key. It is only to be used for signing your artifacts, and should never be stored on your filesystem. Recommendation: Use the list at OWASP's Key Storage to find a good spot to store your private key.
  2. Create another keypair. This will be for the next release (ex. the minor version bump "2.4.0" or the major version bump "3.0.0") even though you likely haven't started that release yet! Safeguard the private key.
  3. (A3) Store the next release public key and the download URL of the next release in the source code of the current release. The download URL should fail with something like a 404 Not Found error.
  4. Build and publish the current release.
  5. Let's say your current release artifact lives at "https://example.com/a/b/c/my-artifact-2.3.5.zip". Checksum your artifact(s) with a secure hash alongside your artifact. For example, the secure hash could be saved hex or base64 encoded into the location "https://example.com/a/b/c/my-artifact-2.3.5.zip.sha256sum", or for multiple artifacts you can have the output of "sha256sum" be placed into "https://example.com/a/b/c/SHA256". Recommendation: Even though SHA-256 is a good secure hash, if you don't know better, pick Blake3 as your hash since it scales well when you need multi-threaded downloads.
  6. Use sign_message_exn with the secret key ~message as the contents of the current release checksum file, and the ~seckey as the the current release secret key. Save the signature alongside your checksum file. For example, "https://example.com/a/b/c/my-artifact-2.3.5.zip.sha256sum.sig" or "https://example.com/a/b/c/SHA256.sig".

When you are ready to do the next release:

  1. Do all the steps above.

Probing in your software

Your software will need to periodically probe for the "next release" download URL using the steps:

  1. (C1). Check if the next release signature URL can be downloaded (ex. "https://example.com/a/b/c/my-artifact-2.4.0.zip.sha256sum.sig"). If that can't be downloaded, there are no updates and you should stop the probe.
  2. (C2) Check if the next release checksum URL can be downloaded (ex. "https://example.com/a/b/c/my-artifact-2.4.0.zip.sha256sum"). If that can't be downloaded, there are no updates and you should stop the probe.
  3. Do a verify_message with the ~signature_ from the first step (C1), the ~message from the second step (C2), and ~pubkey has been embedded in your source code in the Release Process third step (A3).
  4. Use the checksum (~message) to download and verify the next release artifact URL (ex. "https://example.com/a/b/c/my-artifact-2.4.0.zip"). If that can't be downloaded or the checksum does not match, there are no updates and you should stop the probe.
  5. You have a verified next release of an artifact. Do whatever logic you need to switch to that artifact.

FAQ

Q1: How do I download verified errata/patches?

A1: In your source code not only should you embed the next release public key and next release URL (ex. "https://example.com/a/b/c/my-artifact-2.4.0.zip.sha256sum.sig"), you should also embed the next patch URL (ex. "https://example.com/a/b/c/my-artifact-2.3.6.zip.sha256sum.sig").

That way when you do the "Probing in your software" steps, you can check for the next patch URL first. The only difference between a next patch URL and the next patch URL is that a patch URL uses the current release public key, not the next release public key.

API

val fingerprint_pubkey_exn : string -> string

fingerprint_pubkey_exn pubkey returns the fingerprint of the base64-encoded public key pubkey.

The public key pubkey must not have a preceding line of comment.

Raises Invalid_argument if the public key could not be decoded.

val fingerprint_signature_exn : string -> string

fingerprint_signature_exn signature_ returns the fingerprint of the base64-encoded signature signature_.

The signature signature_ must not have a preceding line of comment.

Raises Invalid_argument if the signature could not be decoded.

val sign_message_exn : seckey:string -> message:string -> unit -> string

sign_message_exn ~seckey ~message () signs the message with the signify-formatted secret key seckey.

The signify format of the secret key seckey is:

  1. one line of comment ending with a newline
  2. the base64-encoding of the secret key

Raises Invalid_argument if the secret key is not a signify secret key.

Raises Invalid_argument if the secret key has its algorithm field (first two characters) anything other than "Ed".

val generate_key_pair_exn : ?comment:string -> (int -> string) -> [ `PublicKey of string ] * [ `SecretKey of string ]

generate_key_pair_exn ?comment gen creates a key pair from the random byte generator gen with an optional comment comment.

The function gen sz must take an integer size sz and return a string of random bytes of sz length..

The typical way to create a generator gen and one or more key pairs is with:

  Mirage_crypto_rng_unix.use_default ();
  let rng = Mirage_crypto_rng.default_generator () in
  let gen sz = Mirage_crypto_rng.generate ~g:rng sz in

  let `PublicKey pubkey, `SecretKey seckey =
    MlFront_Signify.Signify.generate_key_pair gen
  in
  ()

Raises Invalid_argument if the random byte generator gen sz does not give a random byte of length sz.

val verify_message : pubkey:string -> signature_:string -> message:string -> unit -> (unit, [ `Msg of string ]) Stdlib.result

verify_message ~pubkey ~signature_ ~message () verifies the message with the signify-formatted signature signature_ and the signify-formatted public key pubkey.

The signify format of the public key pubkey is:

  1. one line of comment ending with a newline
  2. the base64-encoding of the public key

The signify format of the signature signature_ is:

  1. one line of comment ending with a newline
  2. the base64-encoding of the signature

On success this returns Ok ().

Returns Error (`Msg "the error message") if the message cannot be verified.