File: ikev1_hash.c

package info (click to toggle)
libreswan 5.2-2.3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 81,644 kB
  • sloc: ansic: 129,988; sh: 32,018; xml: 20,646; python: 10,303; makefile: 3,022; javascript: 1,506; sed: 574; yacc: 511; perl: 264; awk: 52
file content (180 lines) | stat: -rw-r--r-- 5,956 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/* IKEv1 HASH payload weirdness, for Libreswan
 *
 * Copyright (C) 2019  Andrew Cagney
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <https://www.gnu.org/licenses/gpl2.txt>.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 */

#include "ike_alg.h"
#include "crypt_prf.h"

#include "defs.h"		/* for so_serial_t */
#include "log.h"
#include "state.h"
#include "demux.h"
#include "impair.h"
#include "ikev1_hash.h"
#include "ikev1_message.h"

bool emit_v1_HASH(enum v1_hash_type hash_type, const char *what,
		  enum impair_v1_exchange exchange,
		  struct state *st, struct v1_hash_fixup *fixup,
		  struct pbs_out *rbody)
{
	zero(fixup);
	fixup->what = what;
	fixup->hash_type = hash_type;
	fixup->logger = rbody->logger; /* not ST; might be parent SA */
	fixup->impair = (impair.v1_hash_exchange == exchange
			 ? impair.v1_hash_payload : IMPAIR_EMIT_NO);
	if (fixup->impair == IMPAIR_EMIT_OMIT) {
		llog(RC_LOG, fixup->logger,
		     "IMPAIR: omitting HASH payload for %s", what);
		return true;
	}
	struct pbs_out hash_pbs;
	if (!ikev1_out_generic(&isakmp_hash_desc, rbody, &hash_pbs)) {
		return false;
	}
	if (fixup->impair == IMPAIR_EMIT_EMPTY) {
		llog(RC_LOG, fixup->logger,
		     "IMPAIR: sending HASH payload with no data for %s", what);
	} else {
		/* reserve space for HASH data */
		fixup->hash_data = chunk2(hash_pbs.cur, st->st_oakley.ta_prf->prf_output_size);
		if (!pbs_out_zero(&hash_pbs, fixup->hash_data.len, "HASH DATA")) {
			/* already logged */
			return false; /*fatal*/
		}
	}
	close_output_pbs(&hash_pbs);
	/* save start of rest of message for later */
	fixup->body = rbody->cur;
	return true;
}

void fixup_v1_HASH(struct state *st, const struct v1_hash_fixup *fixup,
		   msgid_t msgid, const uint8_t *roof)
{
	if (fixup->impair >= IMPAIR_EMIT_ROOF) {
		unsigned byte = fixup->impair - IMPAIR_EMIT_ROOF;
		llog(RC_LOG, fixup->logger,
		     "IMPAIR: setting HASH payload bytes to %02x", byte);
		/* chunk_fill()? */
		memset(fixup->hash_data.ptr, byte, fixup->hash_data.len);
		return;
	}

	if (fixup->impair != IMPAIR_EMIT_NO) {
		/* already logged above? */
		return;
	}

	struct crypt_prf *hash;
	/* msgid */
	passert(sizeof(msgid_t) == sizeof(uint32_t));
	msgid_t raw_msgid = htonl(msgid);
	switch (fixup->hash_type) {
	case V1_HASH_1:
		/* HASH(1) = prf(SKEYID_a, M-ID | payload ) */
		hash = crypt_prf_init_symkey("HASH(1)", st->st_oakley.ta_prf,
					     "SKEYID_a", st->st_skeyid_a_nss,
					     fixup->logger);
		crypt_prf_update_thing(hash, "M-ID", raw_msgid);
		crypt_prf_update_bytes(hash, "payload",
				       fixup->body, roof - fixup->body);
		break;
	case V1_HASH_2:
		/* HASH(2) = prf(SKEYID_a, M-ID | Ni_b | payload ) */
		hash = crypt_prf_init_symkey("HASH(2)", st->st_oakley.ta_prf,
					     "SKEYID_a", st->st_skeyid_a_nss,
					     fixup->logger);
		crypt_prf_update_thing(hash, "M-ID", raw_msgid);
		crypt_prf_update_hunk(hash, "Ni_b", st->st_ni);
		crypt_prf_update_bytes(hash, "payload",
				       fixup->body, roof - fixup->body);
		break;
	case V1_HASH_3:
		/* HASH(3) = prf(SKEYID_a, 0 | M-ID | Ni_b | Nr_b) */
		hash = crypt_prf_init_symkey("HASH(3)", st->st_oakley.ta_prf,
					     "SKEYID_a", st->st_skeyid_a_nss,
					     fixup->logger);
		crypt_prf_update_byte(hash, "0", 0);
		crypt_prf_update_thing(hash, "M-ID", raw_msgid);
		crypt_prf_update_hunk(hash, "Ni_b", st->st_ni);
		crypt_prf_update_hunk(hash, "Nr_b", st->st_nr);
		break;
	default:
		bad_case(fixup->hash_type);
	}
	/* stuff result into hash_data */
	passert(fixup->hash_data.len == st->st_oakley.ta_prf->prf_output_size);
	crypt_prf_final_bytes(&hash, fixup->hash_data.ptr, fixup->hash_data.len);
	if (DBGP(DBG_BASE)) {
		DBG_log("%s HASH(%u):", fixup->what, fixup->hash_type);
		DBG_dump_hunk(NULL, fixup->hash_data);
	}
}

bool check_v1_HASH(enum v1_hash_type type, const char *what,
		   struct state *st, struct msg_digest *md)
{
	if (type == V1_HASH_NONE) {
		dbg("message '%s' HASH payload not checked early", what);
		return true;
	}
	if (impair.v1_hash_check) {
		log_state(RC_LOG, st, "IMPAIR: skipping check of '%s' HASH payload", what);
		return true;
	}
	if (md->hdr.isa_np != ISAKMP_NEXT_HASH) {
		log_state(RC_LOG, st, "received '%s' message is missing a HASH(%u) payload",
		       what, type);
		return false;
	}
	struct pbs_in *hash_pbs = &md->chain[ISAKMP_NEXT_HASH]->pbs;
	shunk_t received_hash = pbs_in_left(hash_pbs);
	if (received_hash.len != st->st_oakley.ta_prf->prf_output_size) {
		log_state(RC_LOG, st,
		       "received '%s' message HASH(%u) data is the wrong length (received %zd bytes but expected %zd)",
		       what, type, received_hash.len, st->st_oakley.ta_prf->prf_output_size);
		return false;
	}
	/*
	 * Create a fixup pointing at COMPUTED_HASH so that
	 * fixup_v1_HASH() will fill it in.
	 */
	struct crypt_mac computed_hash = {
		.len = st->st_oakley.ta_prf->prf_output_size,
	};
	struct v1_hash_fixup expected = {
		.hash_data = chunk2(computed_hash.ptr, computed_hash.len),
		.body = received_hash.ptr + received_hash.len,
		.what = what,
		.hash_type = type,
		.logger = st->logger,
	};
	fixup_v1_HASH(st, &expected, md->hdr.isa_msgid, md->message_pbs.roof);
	/* does it match? */
	if (!hunk_eq(received_hash, computed_hash)) {
		if (DBGP(DBG_BASE)) {
			DBG_log("received %s HASH_DATA:", what);
			DBG_dump_hunk(NULL, received_hash);
		}
		log_state(RC_LOG, st,
		       "received '%s' message HASH(%u) data does not match computed value",
		       what, type);
		return false;
	}
	dbg("received '%s' message HASH(%u) data ok", what, type);
	return true;
}