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
|
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>
use bytes;
use crypto::chachapoly;
use crypto::math;
use errors;
use io;
use memio;
// A secret session key.
export type sessionkey = [32]u8;
// A value which is only used once.
export type nonce = [24]u8;
// A message authentication code.
export type mac = [16]u8;
// An encrypted, authenticated message.
export type box = (mac, nonce, []u8);
// Performs authenticated encryption on a message. The result may be
// authenticated and decrypted with [[decrypt]].
//
// To use this function, you must first establish a session key which is shared
// with both parties. This key must be random and secret. You may derive this
// key with a key exchange (such as [[exchange]] or [[dh]]), or with a key
// derivation function (such as [[derivekey]]), or by sharing it in person, or
// some other, similar means which preserves the traits of randomness and
// secrecy.
//
// You must also establish a unique nonce for each message, which you must not
// reuse for any future messages using the same session key. It is recommended
// to generate this randomly with [[crypto::random::]].
//
// The plaintext parameter provides the message to encrypt. It will be
// overwritten with the ciphertext. The buffer provided in the return value is
// borrowed from this parameter.
//
// The optional 'additional' parameters provide additional data to be
// authenticated with the same MAC. This data is not encrypted, but [[decrypt]]
// will fail if it has been tampered with. The order of these arguments must be
// consistent between [[encrypt]] and [[decrypt]].
//
// The return value contains all of the information which should be transmitted
// to the other party, including the computed MAC, a copy of the nonce, and the
// ciphertext. It is safe to transmit these values over an unsecured connection,
// or to encode them with something like [[encoding::base64::]].
//
// Any 'additional' data, if provided, is not included in the [[box]] type. The
// user must provide for this data to be transmitted to the other party
// themselves.
//
// let key: crypto::sessionkey = [0...];
// let nonce: crypto::nonce = [0...];
// random::buffer(key); // Or some other means of establishing the key
// random::buffer(nonce);
//
// let buf: [64]u8 = [0...]; // Populate with your message
// let box = crypto::encrypt(&key, &nonce, buf[..], buf[..]);
//
// // To decrypt this message:
// let plaintext = match (crypto::decrypt(&key, &box, buf[..])) {
// case let buf: []u8 =>
// yield buf;
// case errors::invalid =>
// abort("Message authentication or decryption failed");
// };
//
// The current implementation of this algorithm is based on RFC 8439, but uses
// XChaCha20 instead of ChaCha20.
export fn encrypt(
key: *sessionkey,
nonce: *nonce,
plaintext: []u8,
additional: []u8...
) box = {
let s = chachapoly::chachapoly();
defer io::close(&s)!;
let h = memio::fixed(plaintext);
chachapoly::xinit(&s, &h, key, nonce, additional...);
io::writeall(&s, plaintext)!;
let m: mac = [0...];
chachapoly::seal(&s, m);
return (m, *nonce, memio::buffer(&h));
};
// Authenticates and decrypts a message encrypted with [[encrypt]]. If the
// decryption is successful, the plaintext slice is returned, and if not,
// [[errors::invalid]] is returned.
//
// The 'sessionkey' parameter is the shared key. The 'box' parameter is the
// output of [[encrypt]]. If additional data should be authenticated, it may be
// provided in the variadic 'additional' parameters.
//
// Note that the data is decrypted in-place, such that the box's ciphertext
// becomes overwritten with the plaintext. The return value is borrowed from
// this buffer. If decryption fails, this buffer will be zeroed, causing the
// ciphertext to be destroyed as well. It is advised to zero the plaintext
// buffer yourself after you are done using it, see [[bytes::zero]].
//
// See [[decrypt]] for the full details and a usage example.
export fn decrypt(
key: *sessionkey,
box: *box,
additional: []u8...
) ([]u8 | errors::invalid) = {
let s = chachapoly::chachapoly();
defer io::close(&s)!;
let ciphertext = box.2;
let h = memio::fixed(ciphertext);
chachapoly::xinit(&s, &h, key, box.1, additional...);
let plaintext = ciphertext;
io::readall(&s, plaintext)!;
if (chachapoly::verify(&s, box.0) is errors::invalid) {
bytes::zero(plaintext);
return errors::invalid;
};
return plaintext;
};
// Compares two slices and returns true if their contents are the same.
// Comparison is done in constant time, meaning that, if the slices have equal
// length, the time it takes depends only on the length of the slices and not
// their content.
export fn compare(a: []u8, b: []u8) bool = {
return len(a) == len(b) && math::eqslice(a, b) == 1;
};
// TODO: Add additional entry-points which provide a finer degree of control
// over buffer usage.
|