File: authenc.ha

package info (click to toggle)
hare 0.26.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 7,352 kB
  • sloc: asm: 1,374; makefile: 123; sh: 117; lisp: 101
file content (136 lines) | stat: -rw-r--r-- 4,910 bytes parent folder | download | duplicates (2)
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.