summaryrefslogtreecommitdiff
path: root/hmac/hmac.c
blob: 7c2bafe725e710705734075a00caec9009c44982 (plain)
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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2001-2009 Oracle.  All rights reserved.
 *
 * Some parts of this code originally written by Adam Stubblefield,
 * -- astubble@rice.edu.
 *
 * $Id$
 */

#include "db_config.h"

#include "db_int.h"
#include "dbinc/crypto.h"
#include "dbinc/db_page.h"	/* for hash.h only */
#include "dbinc/hash.h"
#include "dbinc/hmac.h"
#include "dbinc/log.h"

#define	HMAC_OUTPUT_SIZE	20
#define	HMAC_BLOCK_SIZE	64

static void __db_hmac __P((u_int8_t *, u_int8_t *, size_t, u_int8_t *));

/*
 * !!!
 * All of these functions use a ctx structure on the stack.  The __db_SHA1Init
 * call does not initialize the 64-byte buffer portion of it.  The
 * underlying SHA1 functions will properly pad the buffer if the data length
 * is less than 64-bytes, so there isn't a chance of reading uninitialized
 * memory.  Although it would be cleaner to do a memset(ctx.buffer, 0, 64)
 * we do not want to incur that penalty if we don't have to for performance.
 */

/*
 * __db_hmac --
 *	Do a hashed MAC.
 */
static void
__db_hmac(k, data, data_len, mac)
	u_int8_t *k, *data, *mac;
	size_t data_len;
{
	SHA1_CTX ctx;
	u_int8_t key[HMAC_BLOCK_SIZE];
	u_int8_t ipad[HMAC_BLOCK_SIZE];
	u_int8_t opad[HMAC_BLOCK_SIZE];
	u_int8_t tmp[HMAC_OUTPUT_SIZE];
	int i;

	memset(key, 0x00, HMAC_BLOCK_SIZE);
	memset(ipad, 0x36, HMAC_BLOCK_SIZE);
	memset(opad, 0x5C, HMAC_BLOCK_SIZE);

	memcpy(key, k, HMAC_OUTPUT_SIZE);

	for (i = 0; i < HMAC_BLOCK_SIZE; i++) {
		ipad[i] ^= key[i];
		opad[i] ^= key[i];
	}

	__db_SHA1Init(&ctx);
	__db_SHA1Update(&ctx, ipad, HMAC_BLOCK_SIZE);
	__db_SHA1Update(&ctx, data, data_len);
	__db_SHA1Final(tmp, &ctx);
	__db_SHA1Init(&ctx);
	__db_SHA1Update(&ctx, opad, HMAC_BLOCK_SIZE);
	__db_SHA1Update(&ctx, tmp, HMAC_OUTPUT_SIZE);
	__db_SHA1Final(mac, &ctx);
	return;
}

/*
 * __db_chksum --
 *	Create a MAC/SHA1 checksum.
 *
 * PUBLIC: void __db_chksum __P((void *,
 * PUBLIC:     u_int8_t *, size_t, u_int8_t *, u_int8_t *));
 */
void
__db_chksum(hdr, data, data_len, mac_key, store)
	void *hdr;
	u_int8_t *data;
	size_t data_len;
	u_int8_t *mac_key;
	u_int8_t *store;
{
	int sumlen;
	u_int32_t hash4;

	/*
	 * Since the checksum might be on a page of data we are checksumming
	 * we might be overwriting after checksumming, we zero-out the
	 * checksum value so that we can have a known value there when
	 * we verify the checksum.
	 * If we are passed a log header XOR in prev and len so we have
	 * some redundancy on these fields.  Mostly we need to be sure that
	 * we detect a race when doing hot backups and reading a live log
	 * file.
	 */
	if (mac_key == NULL)
		sumlen = sizeof(u_int32_t);
	else
		sumlen = DB_MAC_KEY;
	if (hdr == NULL)
		memset(store, 0, sumlen);
	else
		store = ((HDR*)hdr)->chksum;
	if (mac_key == NULL) {
		/* Just a hash, no MAC */
		hash4 = __ham_func4(NULL, data, (u_int32_t)data_len);
		if (hdr != NULL)
			hash4 ^= ((HDR *)hdr)->prev ^ ((HDR *)hdr)->len;
		memcpy(store, &hash4, sumlen);
	} else {
		__db_hmac(mac_key, data, data_len, store);
		if (hdr != 0) {
			((int *)store)[0] ^= ((HDR *)hdr)->prev;
			((int *)store)[1] ^= ((HDR *)hdr)->len;
		}
	}
	return;
}
/*
 * __db_derive_mac --
 *	Create a MAC/SHA1 key.
 *
 * PUBLIC: void __db_derive_mac __P((u_int8_t *, size_t, u_int8_t *));
 */
void
__db_derive_mac(passwd, plen, mac_key)
	u_int8_t *passwd;
	size_t plen;
	u_int8_t *mac_key;
{
	SHA1_CTX ctx;

	/* Compute the MAC key. mac_key must be 20 bytes. */
	__db_SHA1Init(&ctx);
	__db_SHA1Update(&ctx, passwd, plen);
	__db_SHA1Update(&ctx, (u_int8_t *)DB_MAC_MAGIC, strlen(DB_MAC_MAGIC));
	__db_SHA1Update(&ctx, passwd, plen);
	__db_SHA1Final(mac_key, &ctx);

	return;
}

/*
 * __db_check_chksum --
 *	Verify a checksum.
 *
 *	Return 0 on success, >0 (errno) on error, -1 on checksum mismatch.
 *
 * PUBLIC: int __db_check_chksum __P((ENV *,
 * PUBLIC:     void *, DB_CIPHER *, u_int8_t *, void *, size_t, int));
 */
int
__db_check_chksum(env, hdr, db_cipher, chksum, data, data_len, is_hmac)
	ENV *env;
	void *hdr;
	DB_CIPHER *db_cipher;
	u_int8_t *chksum;
	void *data;
	size_t data_len;
	int is_hmac;
{
	int ret;
	size_t sum_len;
	u_int32_t hash4;
	u_int8_t *mac_key, old[DB_MAC_KEY], new[DB_MAC_KEY];

	/*
	 * If we are just doing checksumming and not encryption, then checksum
	 * is 4 bytes.  Otherwise, it is DB_MAC_KEY size.  Check for illegal
	 * combinations of crypto/non-crypto checksums.
	 */
	if (is_hmac == 0) {
		if (db_cipher != NULL) {
			__db_errx(env,
    "Unencrypted checksum with a supplied encryption key");
			return (EINVAL);
		}
		sum_len = sizeof(u_int32_t);
		mac_key = NULL;
	} else {
		if (db_cipher == NULL) {
			__db_errx(env,
    "Encrypted checksum: no encryption key specified");
			return (EINVAL);
		}
		sum_len = DB_MAC_KEY;
		mac_key = db_cipher->mac_key;
	}

	/*
	 * !!!
	 * Since the checksum might be on the page, we need to have known data
	 * there so that we can generate the same original checksum.  We zero
	 * it out, just like we do in __db_chksum above.
	 * If there is a log header, XOR the prev and len fields.
	 */
retry:
	if (hdr == NULL) {
		memcpy(old, chksum, sum_len);
		memset(chksum, 0, sum_len);
		chksum = old;
	}

	if (mac_key == NULL) {
		/* Just a hash, no MAC */
		hash4 = __ham_func4(NULL, data, (u_int32_t)data_len);
		if (hdr != NULL)
			LOG_HDR_SUM(0, hdr, &hash4);
		ret = memcmp((u_int32_t *)chksum, &hash4, sum_len) ? -1 : 0;
	} else {
		__db_hmac(mac_key, data, data_len, new);
		if (hdr != NULL)
			LOG_HDR_SUM(1, hdr, new);
		ret = memcmp(chksum, new, sum_len) ? -1 : 0;
	}
	/*
	 * !!!
	 * We might be looking at an old log even with the new
	 * code.  So, if we have a hdr, and the checksum doesn't
	 * match, try again without a hdr.
	 */
	if (hdr != NULL && ret != 0) {
		hdr = NULL;
		goto retry;
	}

	return (ret);
}