1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2001,2008 Oracle.  All rights reserved.
5 *
6 * Some parts of this code originally written by Adam Stubblefield,
7 * -- astubble@rice.edu.
8 *
9 * $Id: hmac.c,v 12.13 2008/01/08 20:58:34 bostic Exp $
10 */
11
12#include "db_config.h"
13
14#include "db_int.h"
15#include "dbinc/crypto.h"
16#include "dbinc/db_page.h"	/* for hash.h only */
17#include "dbinc/hash.h"
18#include "dbinc/hmac.h"
19#include "dbinc/log.h"
20
21#define	HMAC_OUTPUT_SIZE	20
22#define	HMAC_BLOCK_SIZE	64
23
24static void __db_hmac __P((u_int8_t *, u_int8_t *, size_t, u_int8_t *));
25
26/*
27 * !!!
28 * All of these functions use a ctx structure on the stack.  The __db_SHA1Init
29 * call does not initialize the 64-byte buffer portion of it.  The
30 * underlying SHA1 functions will properly pad the buffer if the data length
31 * is less than 64-bytes, so there isn't a chance of reading uninitialized
32 * memory.  Although it would be cleaner to do a memset(ctx.buffer, 0, 64)
33 * we do not want to incur that penalty if we don't have to for performance.
34 */
35
36/*
37 * __db_hmac --
38 *	Do a hashed MAC.
39 */
40static void
41__db_hmac(k, data, data_len, mac)
42	u_int8_t *k, *data, *mac;
43	size_t data_len;
44{
45	SHA1_CTX ctx;
46	u_int8_t key[HMAC_BLOCK_SIZE];
47	u_int8_t ipad[HMAC_BLOCK_SIZE];
48	u_int8_t opad[HMAC_BLOCK_SIZE];
49	u_int8_t tmp[HMAC_OUTPUT_SIZE];
50	int i;
51
52	memset(key, 0x00, HMAC_BLOCK_SIZE);
53	memset(ipad, 0x36, HMAC_BLOCK_SIZE);
54	memset(opad, 0x5C, HMAC_BLOCK_SIZE);
55
56	memcpy(key, k, HMAC_OUTPUT_SIZE);
57
58	for (i = 0; i < HMAC_BLOCK_SIZE; i++) {
59		ipad[i] ^= key[i];
60		opad[i] ^= key[i];
61	}
62
63	__db_SHA1Init(&ctx);
64	__db_SHA1Update(&ctx, ipad, HMAC_BLOCK_SIZE);
65	__db_SHA1Update(&ctx, data, data_len);
66	__db_SHA1Final(tmp, &ctx);
67	__db_SHA1Init(&ctx);
68	__db_SHA1Update(&ctx, opad, HMAC_BLOCK_SIZE);
69	__db_SHA1Update(&ctx, tmp, HMAC_OUTPUT_SIZE);
70	__db_SHA1Final(mac, &ctx);
71	return;
72}
73
74/*
75 * __db_chksum --
76 *	Create a MAC/SHA1 checksum.
77 *
78 * PUBLIC: void __db_chksum __P((void *,
79 * PUBLIC:     u_int8_t *, size_t, u_int8_t *, u_int8_t *));
80 */
81void
82__db_chksum(hdr, data, data_len, mac_key, store)
83	void *hdr;
84	u_int8_t *data;
85	size_t data_len;
86	u_int8_t *mac_key;
87	u_int8_t *store;
88{
89	int sumlen;
90	u_int32_t hash4;
91
92	/*
93	 * Since the checksum might be on a page of data we are checksumming
94	 * we might be overwriting after checksumming, we zero-out the
95	 * checksum value so that we can have a known value there when
96	 * we verify the checksum.
97	 * If we are passed a log header XOR in prev and len so we have
98	 * some redundancy on these fields.  Mostly we need to be sure that
99	 * we detect a race when doing hot backups and reading a live log
100	 * file.
101	 */
102	if (mac_key == NULL)
103		sumlen = sizeof(u_int32_t);
104	else
105		sumlen = DB_MAC_KEY;
106	if (hdr == NULL)
107		memset(store, 0, sumlen);
108	else
109		store = ((HDR*)hdr)->chksum;
110	if (mac_key == NULL) {
111		/* Just a hash, no MAC */
112		hash4 = __ham_func4(NULL, data, (u_int32_t)data_len);
113		if (hdr != NULL)
114			hash4 ^= ((HDR *)hdr)->prev ^ ((HDR *)hdr)->len;
115		memcpy(store, &hash4, sumlen);
116	} else {
117		__db_hmac(mac_key, data, data_len, store);
118		if (hdr != 0) {
119			((int *)store)[0] ^= ((HDR *)hdr)->prev;
120			((int *)store)[1] ^= ((HDR *)hdr)->len;
121		}
122	}
123	return;
124}
125/*
126 * __db_derive_mac --
127 *	Create a MAC/SHA1 key.
128 *
129 * PUBLIC: void __db_derive_mac __P((u_int8_t *, size_t, u_int8_t *));
130 */
131void
132__db_derive_mac(passwd, plen, mac_key)
133	u_int8_t *passwd;
134	size_t plen;
135	u_int8_t *mac_key;
136{
137	SHA1_CTX ctx;
138
139	/* Compute the MAC key. mac_key must be 20 bytes. */
140	__db_SHA1Init(&ctx);
141	__db_SHA1Update(&ctx, passwd, plen);
142	__db_SHA1Update(&ctx, (u_int8_t *)DB_MAC_MAGIC, strlen(DB_MAC_MAGIC));
143	__db_SHA1Update(&ctx, passwd, plen);
144	__db_SHA1Final(mac_key, &ctx);
145
146	return;
147}
148
149/*
150 * __db_check_chksum --
151 *	Verify a checksum.
152 *
153 *	Return 0 on success, >0 (errno) on error, -1 on checksum mismatch.
154 *
155 * PUBLIC: int __db_check_chksum __P((ENV *,
156 * PUBLIC:     void *, DB_CIPHER *, u_int8_t *, void *, size_t, int));
157 */
158int
159__db_check_chksum(env, hdr, db_cipher, chksum, data, data_len, is_hmac)
160	ENV *env;
161	void *hdr;
162	DB_CIPHER *db_cipher;
163	u_int8_t *chksum;
164	void *data;
165	size_t data_len;
166	int is_hmac;
167{
168	int ret;
169	size_t sum_len;
170	u_int32_t hash4;
171	u_int8_t *mac_key, old[DB_MAC_KEY], new[DB_MAC_KEY];
172
173	/*
174	 * If we are just doing checksumming and not encryption, then checksum
175	 * is 4 bytes.  Otherwise, it is DB_MAC_KEY size.  Check for illegal
176	 * combinations of crypto/non-crypto checksums.
177	 */
178	if (is_hmac == 0) {
179		if (db_cipher != NULL) {
180			__db_errx(env,
181    "Unencrypted checksum with a supplied encryption key");
182			return (EINVAL);
183		}
184		sum_len = sizeof(u_int32_t);
185		mac_key = NULL;
186	} else {
187		if (db_cipher == NULL) {
188			__db_errx(env,
189    "Encrypted checksum: no encryption key specified");
190			return (EINVAL);
191		}
192		sum_len = DB_MAC_KEY;
193		mac_key = db_cipher->mac_key;
194	}
195
196	/*
197	 * !!!
198	 * Since the checksum might be on the page, we need to have known data
199	 * there so that we can generate the same original checksum.  We zero
200	 * it out, just like we do in __db_chksum above.
201	 * If there is a log header, XOR the prev and len fields.
202	 */
203retry:
204	if (hdr == NULL) {
205		memcpy(old, chksum, sum_len);
206		memset(chksum, 0, sum_len);
207		chksum = old;
208	}
209
210	if (mac_key == NULL) {
211		/* Just a hash, no MAC */
212		hash4 = __ham_func4(NULL, data, (u_int32_t)data_len);
213		if (hdr != NULL)
214			LOG_HDR_SUM(0, hdr, &hash4);
215		ret = memcmp((u_int32_t *)chksum, &hash4, sum_len) ? -1 : 0;
216	} else {
217		__db_hmac(mac_key, data, data_len, new);
218		if (hdr != NULL)
219			LOG_HDR_SUM(1, hdr, new);
220		ret = memcmp(chksum, new, sum_len) ? -1 : 0;
221	}
222	/*
223	 * !!!
224	 * We might be looking at an old log even with the new
225	 * code.  So, if we have a hdr, and the checksum doesn't
226	 * match, try again without a hdr.
227	 */
228	if (hdr != NULL && ret != 0) {
229		hdr = NULL;
230		goto retry;
231	}
232
233	return (ret);
234}
235