2 sptps.c -- Simple Peer-to-Peer Security
3 Copyright (C) 2011 Guus Sliepen <guus@tinc-vpn.org>,
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
33 static bool error(sptps_t *s, int s_errno, const char *msg) {
34 fprintf(stderr, "SPTPS error: %s\n", msg);
39 static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_t len) {
40 char plaintext[len + 23];
41 char ciphertext[len + 19];
43 // Create header with sequence number, length and record type
44 uint32_t seqno = htonl(s->outseqno++);
45 uint16_t netlen = htons(len);
47 memcpy(plaintext, &seqno, 4);
48 memcpy(plaintext + 4, &netlen, 2);
51 // Add plaintext (TODO: avoid unnecessary copy)
52 memcpy(plaintext + 7, data, len);
55 // If first handshake has finished, encrypt and HMAC
56 if(!digest_create(&s->outdigest, plaintext, len + 7, plaintext + 7 + len))
59 if(!cipher_encrypt(&s->outcipher, plaintext + 4, sizeof ciphertext, ciphertext, NULL, false))
62 return s->send_data(s->handle, ciphertext, len + 19);
64 // Otherwise send as plaintext
65 return s->send_data(s->handle, plaintext + 4, len + 3);
69 bool send_record(sptps_t *s, uint8_t type, const char *data, uint16_t len) {
70 // Sanity checks: application cannot send data before handshake is finished,
71 // and only record types 0..127 are allowed.
73 return error(s, EINVAL, "Handshake phase not finished yet");
76 return error(s, EINVAL, "Invalid application record type");
78 return send_record_priv(s, type, data, len);
81 static bool send_kex(sptps_t *s) {
82 size_t keylen = ECDH_SIZE;
83 size_t siglen = ecdsa_size(&s->mykey);
84 char data[32 + keylen + siglen];
86 // Create a random nonce.
87 s->myrandom = realloc(s->myrandom, 32);
89 return error(s, errno, strerror(errno));
91 randomize(s->myrandom, 32);
92 memcpy(data, s->myrandom, 32);
94 // Create a new ECDH public key.
95 if(!ecdh_generate_public(&s->ecdh, data + 32))
99 if(!ecdsa_sign(&s->mykey, data, 32 + keylen, data + 32 + keylen))
102 // Send the handshake record.
103 return send_record_priv(s, 128, data, sizeof data);
106 static bool generate_key_material(sptps_t *s, const char *shared, size_t len, const char *hisrandom) {
107 // Initialise cipher and digest structures if necessary
110 = cipher_open_by_name(&s->incipher, "aes-256-ofb")
111 && cipher_open_by_name(&s->outcipher, "aes-256-ofb")
112 && digest_open_by_name(&s->indigest, "sha256", 16)
113 && digest_open_by_name(&s->outdigest, "sha256", 16);
118 // Allocate memory for key material
119 size_t keylen = digest_keylength(&s->indigest) + digest_keylength(&s->outdigest) + cipher_keylength(&s->incipher) + cipher_keylength(&s->outcipher);
121 s->key = realloc(s->key, keylen);
123 return error(s, errno, strerror(errno));
125 // Create the HMAC seed, which is "key expansion" + session label + server nonce + client nonce
126 char seed[s->labellen + 64 + 13];
127 strcpy(seed, "key expansion");
129 memcpy(seed + 13, hisrandom, 32);
130 memcpy(seed + 45, s->myrandom, 32);
132 memcpy(seed + 13, s->myrandom, 32);
133 memcpy(seed + 45, hisrandom, 32);
135 memcpy(seed + 78, s->label, s->labellen);
137 // Use PRF to generate the key material
138 if(!prf(shared, len, seed, s->labellen + 64 + 13, s->key, keylen))
144 static bool send_ack(sptps_t *s) {
145 return send_record_priv(s, 128, "", 0);
148 static bool receive_ack(sptps_t *s, const char *data, uint16_t len) {
152 // TODO: set cipher/digest keys
153 return error(s, ENOSYS, "receive_ack() not completely implemented yet");
156 static bool receive_kex(sptps_t *s, const char *data, uint16_t len) {
157 size_t keylen = ECDH_SIZE;
158 size_t siglen = ecdsa_size(&s->hiskey);
160 // Verify length of KEX record.
161 if(len != 32 + keylen + siglen)
162 return error(s, EIO, "Invalid KEX record length");
165 if(!ecdsa_verify(&s->hiskey, data, 32 + keylen, data + 32 + keylen))
168 // Compute shared secret.
169 char shared[ECDH_SHARED_SIZE];
170 if(!ecdh_compute_shared(&s->ecdh, data + 32, shared))
173 // Generate key material from shared secret.
174 if(!generate_key_material(s, shared, sizeof shared, data))
177 // Send cipher change record if necessary
182 // TODO: set cipher/digest keys
185 = cipher_set_key(&s->incipher, s->key, false)
186 && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->incipher), digest_keylength(&s->indigest))
187 && cipher_set_key(&s->outcipher, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest), true)
188 && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest) + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest));
193 = cipher_set_key(&s->outcipher, s->key, true)
194 && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest))
195 && cipher_set_key(&s->incipher, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest), false)
196 && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest) + cipher_keylength(&s->incipher), digest_keylength(&s->indigest));
204 static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) {
205 // Only a few states to deal with handshaking.
208 // We have sent our public ECDH key, we expect our peer to sent one as well.
209 if(!receive_kex(s, data, len))
214 // We receive a secondary key exchange request, first respond by sending our own public ECDH key.
218 // If we already sent our secondary public ECDH key, we expect the peer to send his.
219 if(!receive_kex(s, data, len))
224 // We expect an empty handshake message to indicate transition to the new keys.
225 if(!receive_ack(s, data, len))
230 return error(s, EIO, "Invalid session state");
234 bool receive_data(sptps_t *s, const char *data, size_t len) {
236 // First read the 2 length bytes.
238 size_t toread = 6 - s->buflen;
243 if(!cipher_decrypt(&s->incipher, data, toread, s->inbuf + s->buflen, NULL, false))
246 memcpy(s->inbuf + s->buflen, data, toread);
253 // Exit early if we don't have the full length.
257 // If we have the length bytes, ensure our buffer can hold the whole request.
259 memcpy(&reclen, s->inbuf + 4, 2);
260 reclen = htons(reclen);
261 s->inbuf = realloc(s->inbuf, reclen + 23UL);
263 return error(s, errno, strerror(errno));
265 // Add sequence number.
266 uint32_t seqno = htonl(s->inseqno++);
267 memcpy(s->inbuf, &seqno, 4);
269 // Exit early if we have no more data to process.
274 // Read up to the end of the record.
276 memcpy(&reclen, s->inbuf + 4, 2);
277 reclen = htons(reclen);
278 size_t toread = reclen + (s->state ? 23UL : 7UL) - s->buflen;
283 if(!cipher_decrypt(&s->incipher, data, toread, s->inbuf + s->buflen, NULL, false))
286 memcpy(s->inbuf + s->buflen, data, toread);
293 // If we don't have a whole record, exit.
294 if(s->buflen < reclen + (s->state ? 23UL : 7UL))
299 if(!digest_verify(&s->indigest, s->inbuf, reclen + 7UL, s->inbuf + reclen + 7UL))
300 error(s, EIO, "Invalid HMAC");
302 uint8_t type = s->inbuf[6];
306 if(!s->receive_record(s->handle, type, s->inbuf + 7, reclen))
308 } else if(type == 128) {
309 if(!receive_handshake(s, s->inbuf + 7, reclen))
312 return error(s, EIO, "Invalid record type");
321 bool start_sptps(sptps_t *s, void *handle, bool initiator, ecdsa_t mykey, ecdsa_t hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) {
322 // Initialise struct sptps
323 memset(s, 0, sizeof *s);
326 s->initiator = initiator;
330 s->label = malloc(labellen);
332 return error(s, errno, strerror(errno));
334 s->inbuf = malloc(7);
336 return error(s, errno, strerror(errno));
338 memset(s->inbuf, 0, 4);
340 memcpy(s->label, label, labellen);
341 s->labellen = labellen;
343 s->send_data = send_data;
344 s->receive_record = receive_record;
346 // Do first KEX immediately
350 bool stop_sptps(sptps_t *s) {
351 // Clean up any resources.