summaryrefslogtreecommitdiff
path: root/luascrypt.c
blob: 11ae79926d9a60713315554bb010d433d673985a (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
/*
 * luascrypt - Lua binding to libscrypt
 *
 * Copyright 2015 Daniel Silverstone <dsilvers@digital-scurf.org>
 *
 * Please see the file COPYING for licence details.
 */

#include <stdint.h>
#include <errno.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "lua.h"
#include "lauxlib.h"

#include "libscrypt.h"

#include "base64.h"

static void
luascrypt_salt_gen(char *salt, int saltlen)
{
	int fd;
	/* Following comment applies to libscrypt prior to 1.21:
	 *
	 * We'd go with libscrypt's implementation, but since libscrypt's salt
	 * generation is time based, we cannot fully trust it to generate
	 * unique salts so to improve our chances we assume we have urandom
	 * and fall back to libscrypt's implementation if we don't.  Since the
	 * libscrypt implementation is fast, call it, and then overwrite it
	 * if we can...
	 */
	libscrypt_salt_gen(salt, saltlen);

	fd = open("/dev/urandom", O_RDONLY);
	if (fd >= 0) {
		size_t total = 0;
		ssize_t n;

		while (total < saltlen) {
			n = read(fd, salt + total, saltlen - total);
			if (n == 0) {
				break;
			}

			if (n == -1) {
				if (errno == EINTR) {
					continue; 	/* just try again */
				}

				/* Ignore all other errors, since we have our fallback. */
				break;
			}

			total += n;
		}

		close(fd);
	}
}

static int
luascrypt_hash_password(lua_State *L)
{
	// password {N, r, p} -> crypted
	size_t passwd_len;
	const char *passwd = luaL_checklstring(L, 1, &passwd_len);
	char buffer[256];         /* This value is nasty here, but      */
	char salt[16];            /* while I am not normally into magic */
	uint8_t hashbuf[64];      /* numbers, these are taken from the  */
	char saltbuf[256];        /* libscrypt_hash() source.           */
	char outbuf[256];         /* Icky, I know, but what can I do?   */
	size_t bufused;
	uint32_t N = SCRYPT_N;
	uint32_t r = SCRYPT_r;
	uint32_t p = SCRYPT_p;
	if (lua_gettop(L) > 1) {
		N = (uint32_t)luaL_checknumber(L, 2);
		r = (uint32_t)luaL_checknumber(L, 3);
		p = (uint32_t)luaL_checknumber(L, 4);
	}
	
	/* We know that libscrypt is limited to N of 2^15 or less
	 * so raise an error if N is too large
	 */
	if (N > 32768) {
		return luaL_error(L, "Unable to generate password hash: %s",
				  "N is too large (limited to 2^15)");
	}

#ifdef TRUST_LIBSCRYPT_SALT_GEN
	/* Modern versions of libscrypt generate sufficiently random salts
	 * and take a uint8_t * instead of char *
	 */
	libscrypt_salt_gen((uint8_t *) salt, sizeof(salt));
#else
	luascrypt_salt_gen(salt, sizeof(salt));
#endif
	
	if (libscrypt_scrypt((uint8_t*)passwd, passwd_len,
			     (uint8_t*)salt, sizeof(salt),
			     N, r, p,
			     hashbuf, sizeof(hashbuf)) < 0) {
		return luaL_error(L, "Unable to generate password hash: %s",
				  (errno == EFBIG) ? "r and p are too large" :
				  (errno == EINVAL) ? "N is not a power of 2" :
				  (errno == ENOMEM) ? "Buffer sizes are bad" :
				  "Unknown error");
	}
	
	bufused = base64_encode(outbuf, (char *)hashbuf, sizeof(hashbuf));
	outbuf[bufused] = '\0';
	
	bufused = base64_encode(saltbuf, salt, sizeof(salt));
	saltbuf[bufused] = '\0';
	
	if (libscrypt_mcf(N, r, p, saltbuf, outbuf, buffer) < 1) {
		return luaL_error(L, "Unable to mcf encode password.");
	}


	/* some versions of libscrypt fail to include the final equals
	 * after mcf encoding -- check and if it's missing, add it back.
	 * known bad: jessie's libscrypt0 package
	 * known good: master of libscrypt as of 8th July 2015 (check date)
	 */
	{
		int oblen = strlen(outbuf);
		int bulen = strlen(buffer);
		if (outbuf[oblen-1] == outbuf[oblen-2] &&
		    outbuf[oblen-1] == '=' &&
		    buffer[bulen-1] == '=' && buffer[bulen-2] != '=') {
			strcat(buffer, "=");
		}
	}

	lua_pushstring(L, buffer);
	return 1;
}

static int
luascrypt_verify_password(lua_State *L)
{
	// crypted password -> okbool
	const char *crypted = luaL_checkstring(L, 1);
	const char *passwd  = luaL_checkstring(L, 2);
	
	/* libscrypt_check() mutates the provided crypted data, so copy it
	 * otherwise we damage the memory Lua holds.
	 */
	char *crypted_copy = (char *)lua_newuserdata(L, strlen(crypted)+1);
	strcpy(crypted_copy, crypted);
	
	int r = libscrypt_check(crypted_copy, (char *)passwd);
	
	if (r < 0) {
		return luaL_error(L, "Unable to verify password.  Bad crypt.");
	}
	
	lua_pushboolean(L, r);
	return 1;
}

static const struct luaL_Reg
luascrypt_functions[] = {
	{ "hash_password", luascrypt_hash_password },
	{ "verify_password", luascrypt_verify_password },
	{ NULL, NULL }
};

int
luaopen_scrypt(lua_State *L)
{
#if LUA_VERSION_NUM > 501
	lua_newtable(L);
	luaL_setfuncs(L, luascrypt_functions, 0);
#else
	luaL_register(L, "scrypt", luascrypt_functions);
#endif
	return 1;
}