view src/auth/password-scheme.c @ 5757:f71e234d72e4 HEAD

SMD5 scheme was broken, it was using SHA1 constants.
author Timo Sirainen <tss@iki.fi>
date Sat, 16 Jun 2007 03:45:09 +0300
parents 121af23cfc65
children 2cba4c0f3b4f
line wrap: on
line source

/* Copyright (C) 2003-2007 Timo Sirainen */

#include "lib.h"
#include "array.h"
#include "base64.h"
#include "hex-binary.h"
#include "md4.h"
#include "md5.h"
#include "hmac-md5.h"
#include "ntlm.h"
#include "module-dir.h"
#include "mycrypt.h"
#include "randgen.h"
#include "sha1.h"
#include "otp.h"
#include "str.h"
#include "password-scheme.h"

static const char salt_chars[] =
	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

static ARRAY_DEFINE(schemes_arr, struct password_scheme);
static const struct password_scheme *schemes;
#ifdef HAVE_MODULES
static struct module *scheme_modules;
#endif

/* Lookup scheme and encoding by given name. The encoding is taken from
   ".base64", ".b64" or ".hex" suffix if it exists, otherwise the default
   encoding is used. */
static const struct password_scheme *
password_scheme_lookup(const char *scheme, enum password_encoding *encoding_r)
{
	const struct password_scheme *s;
	const char *encoding = NULL;
	unsigned int scheme_len;

	*encoding_r = PW_ENCODING_NONE;

	for (scheme_len = 0; scheme[scheme_len] != '\0'; scheme_len++) {
		if (scheme[scheme_len] == '.') {
			encoding = scheme + scheme_len + 1;
			break;
		}
	}

	for (s = schemes; s->name != NULL; s++) {
		if (strncasecmp(s->name, scheme, scheme_len) == 0 &&
		    s->name[scheme_len] == '\0') {
			if (encoding == NULL)
				*encoding_r = s->default_encoding;
			else if (strcasecmp(encoding, "b64") == 0 ||
				 strcasecmp(encoding, "base64") == 0)
				*encoding_r = PW_ENCODING_BASE64;
			else if (strcasecmp(encoding, "hex") == 0)
				*encoding_r = PW_ENCODING_HEX;
			else {
				/* unknown encoding. treat as invalid scheme. */
				return NULL;
			}
			return s;
		}
	}
	return NULL;
}

int password_verify(const char *plaintext, const char *user, const char *scheme,
		    const unsigned char *raw_password, size_t size)
{
	const struct password_scheme *s;
	enum password_encoding encoding;
	const unsigned char *generated;
	size_t generated_size;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL)
		return -1;

	if (s->password_verify != NULL)
		return s->password_verify(plaintext, user, raw_password, size);

	/* generic verification handler: generate the password and compare it
	   to the one in database */
	s->password_generate(plaintext, user, &generated, &generated_size);
	return size != generated_size ? 0 :
		memcmp(generated, raw_password, size) == 0;
}

const char *password_list_schemes(const struct password_scheme **listptr)
{
	if (*listptr == NULL)
		*listptr = schemes;

	if ((*listptr)->name == NULL) {
		*listptr = NULL;
		return NULL;
	}

	return (*listptr)++->name;
}

const char *password_get_scheme(const char **password)
{
	const char *p, *scheme;

	if (*password == NULL)
		return NULL;

	if (strncmp(*password, "$1$", 3) == 0) {
		/* $1$<salt>$<password>[$<ignored>] */
		p = strchr(*password + 3, '$');
		if (p != NULL) {
			/* stop at next '$' after password */
			p = strchr(p+1, '$');
			if (p != NULL)
				*password = t_strdup_until(*password, p);
			return "MD5-CRYPT";
		}
	}

	if (**password != '{')
		return NULL;

	p = strchr(*password, '}');
	if (p == NULL)
		return NULL;

	scheme = t_strdup_until(*password + 1, p);
	*password = p + 1;
	return scheme;
}

int password_decode(const char *password, const char *scheme,
		    const unsigned char **raw_password_r, size_t *size_r)
{
	const struct password_scheme *s;
	enum password_encoding encoding;
	buffer_t *buf;
	unsigned int len;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL)
		return 0;

	len = strlen(password);
	if (encoding != PW_ENCODING_NONE && s->raw_password_len != 0 &&
	    strchr(scheme, '.') == NULL) {
		/* encoding not specified. we can autodetect between
		   base64 and hex encodings. */
		encoding = len == s->raw_password_len * 2 ? PW_ENCODING_HEX :
			PW_ENCODING_BASE64;
	}

	switch (encoding) {
	case PW_ENCODING_NONE:
		*raw_password_r = (const unsigned char *)password;
		*size_r = len;
		break;
	case PW_ENCODING_BASE64:
		buf = buffer_create_static_hard(pool_datastack_create(),
						MAX_BASE64_DECODED_SIZE(len));
		if (base64_decode(password, len, NULL, buf) < 0)
			return -1;

		*raw_password_r = buf->data;
		*size_r = buf->used;
		break;
	case PW_ENCODING_HEX:
		buf = buffer_create_static_hard(pool_datastack_create(),
						len / 2 + 1);
		if (hex_to_binary(password, buf) < 0)
			return -1;

		*raw_password_r = buf->data;
		*size_r = buf->used;
		break;
	}
	if (s->raw_password_len != *size_r && s->raw_password_len != 0) {
		/* password has invalid length */
		return -1;
	}
	return 1;
}

bool password_generate(const char *plaintext, const char *user,
		       const char *scheme,
		       const unsigned char **raw_password_r, size_t *size_r)
{
	const struct password_scheme *s;
	enum password_encoding encoding;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL)
		return FALSE;

	s->password_generate(plaintext, user, raw_password_r, size_r);
	return TRUE;
}

bool password_generate_encoded(const char *plaintext, const char *user,
			       const char *scheme, const char **password_r)
{
	const struct password_scheme *s;
	const unsigned char *raw_password;
	enum password_encoding encoding;
	string_t *str;
	size_t size;

	s = password_scheme_lookup(scheme, &encoding);
	if (s == NULL)
		return FALSE;

	s->password_generate(plaintext, user, &raw_password, &size);
	switch (encoding) {
	case PW_ENCODING_NONE:
		*password_r = t_strndup(raw_password, size);
		break;
	case PW_ENCODING_BASE64:
		str = t_str_new(MAX_BASE64_ENCODED_SIZE(size) + 1);
		base64_encode(raw_password, size, str);
		*password_r = str_c(str);
		break;
	case PW_ENCODING_HEX:
		*password_r = binary_to_hex(raw_password, size);
		break;
	}
	return TRUE;
}

bool password_scheme_is_alias(const char *scheme1, const char *scheme2)
{
	const struct password_scheme *s, *s1 = NULL, *s2 = NULL;

	scheme1 = t_strcut(scheme1, '.');
	scheme2 = t_strcut(scheme2, '.');

	if (strcasecmp(scheme1, scheme2) == 0)
		return TRUE;

	for (s = schemes; s->name != NULL; s++) {
		if (strcasecmp(s->name, scheme1) == 0)
			s1 = s;
		else if (strcasecmp(s->name, scheme2) == 0)
			s2 = s;
	}

	/* if they've the same generate function, they're equivalent */
	return s1 != NULL && s2 != NULL &&
		s1->password_generate == s2->password_generate;
}

static bool
crypt_verify(const char *plaintext, const char *user __attr_unused__,
	     const unsigned char *raw_password, size_t size)
{
	const char *password;

	if (size == 0) {
		/* the default mycrypt() handler would return match */
		return FALSE;
	}

	password = t_strndup(raw_password, size);
	return strcmp(mycrypt(plaintext, password), password) == 0;
}

static void
crypt_generate(const char *plaintext, const char *user __attr_unused__,
	       const unsigned char **raw_password_r, size_t *size_r)
{
	char salt[3];
	const char *password;

	random_fill(salt, sizeof(salt)-1);
	salt[0] = salt_chars[salt[0] % (sizeof(salt_chars)-1)];
	salt[1] = salt_chars[salt[1] % (sizeof(salt_chars)-1)];
	salt[2] = '\0';

	password = t_strdup(mycrypt(plaintext, salt));
	*raw_password_r = (const unsigned char *)password;
	*size_r = strlen(password);
}

static bool
md5_crypt_verify(const char *plaintext, const char *user __attr_unused__,
		 const unsigned char *raw_password, size_t size)
{
	const char *password, *str;

	password = t_strndup(raw_password, size);
	str = password_generate_md5_crypt(plaintext, password);
	return strcmp(str, password) == 0;
}

static void
md5_crypt_generate(const char *plaintext, const char *user __attr_unused__,
		   const unsigned char **raw_password_r, size_t *size_r)
{
	const char *password;
	char salt[9];
	unsigned int i;

	random_fill(salt, sizeof(salt)-1);
	for (i = 0; i < sizeof(salt)-1; i++)
		salt[i] = salt_chars[salt[i] % (sizeof(salt_chars)-1)];
	salt[sizeof(salt)-1] = '\0';

	password = password_generate_md5_crypt(plaintext, salt);
	*raw_password_r = (const unsigned char *)password;
	*size_r = strlen(password);
}

static void
sha1_generate(const char *plaintext, const char *user __attr_unused__,
	      const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(SHA1_RESULTLEN);
	sha1_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = SHA1_RESULTLEN;
}

static void
ssha_generate(const char *plaintext, const char *user __attr_unused__,
	      const unsigned char **raw_password_r, size_t *size_r)
{
#define SSHA_SALT_LEN 4
	unsigned char *digest, *salt;
	struct sha1_ctxt ctx;

	digest = t_malloc(SHA1_RESULTLEN + SSHA_SALT_LEN);
	salt = digest + SHA1_RESULTLEN;
	random_fill(salt, SSHA_SALT_LEN);

	sha1_init(&ctx);
	sha1_loop(&ctx, plaintext, strlen(plaintext));
	sha1_loop(&ctx, salt, SSHA_SALT_LEN);
	sha1_result(&ctx, digest);

	*raw_password_r = digest;
	*size_r = SHA1_RESULTLEN + SSHA_SALT_LEN;
}

static bool ssha_verify(const char *plaintext, const char *user,
			const unsigned char *raw_password, size_t size)
{
	unsigned char sha1_digest[SHA1_RESULTLEN];
	struct sha1_ctxt ctx;

	/* format: <SHA1 hash><salt> */
	if (size <= SHA1_RESULTLEN) {
		i_error("ssha_verify(%s): SSHA password too short", user);
		return FALSE;
	}

	sha1_init(&ctx);
	sha1_loop(&ctx, plaintext, strlen(plaintext));
	sha1_loop(&ctx, raw_password + SHA1_RESULTLEN, size - SHA1_RESULTLEN);
	sha1_result(&ctx, sha1_digest);
	return memcmp(sha1_digest, raw_password, SHA1_RESULTLEN) == 0;
}

static void
smd5_generate(const char *plaintext, const char *user __attr_unused__,
	      const unsigned char **raw_password_r, size_t *size_r)
{
#define SMD5_SALT_LEN 4
	unsigned char *digest, *salt;
	struct md5_context ctx;

	digest = t_malloc(MD5_RESULTLEN + SMD5_SALT_LEN);
	salt = digest + MD5_RESULTLEN;
	random_fill(salt, SMD5_SALT_LEN);

	md5_init(&ctx);
	md5_update(&ctx, plaintext, strlen(plaintext));
	md5_update(&ctx, salt, SMD5_SALT_LEN);
	md5_final(&ctx, digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN + SMD5_SALT_LEN;
}

static bool smd5_verify(const char *plaintext, const char *user,
			const unsigned char *raw_password, size_t size)
{
	unsigned char md5_digest[MD5_RESULTLEN];
	struct md5_context ctx;

	/* format: <MD5 hash><salt> */
	if (size <= MD5_RESULTLEN) {
		i_error("smd5_verify(%s): SMD5 password too short", user);
		return FALSE;
	}

	md5_init(&ctx);
	md5_update(&ctx, plaintext, strlen(plaintext));
	md5_update(&ctx, raw_password + MD5_RESULTLEN, size - MD5_RESULTLEN);
	md5_final(&ctx, md5_digest);
	return memcmp(md5_digest, raw_password, MD5_RESULTLEN) == 0;
}

static void
plain_generate(const char *plaintext, const char *user __attr_unused__,
	       const unsigned char **raw_password_r, size_t *size_r)
{
	*raw_password_r = (const unsigned char *)plaintext,
	*size_r = strlen(plaintext);
}

static void
cram_md5_generate(const char *plaintext, const char *user __attr_unused__,
		  const unsigned char **raw_password_r, size_t *size_r)
{
	struct hmac_md5_context ctx;
	unsigned char *context_digest;

	context_digest = t_malloc(CRAM_MD5_CONTEXTLEN);
	hmac_md5_init(&ctx, (const unsigned char *)plaintext,
		      strlen(plaintext));
	hmac_md5_get_cram_context(&ctx, context_digest);

	*raw_password_r = context_digest;
	*size_r = CRAM_MD5_CONTEXTLEN;
}

static void
digest_md5_generate(const char *plaintext, const char *user,
		    const unsigned char **raw_password_r, size_t *size_r)
{
	const char *realm, *str;
	unsigned char *digest;

	if (user == NULL)
		i_panic("digest_md5_generate(): username not given");

	/* user:realm:passwd */
	realm = strchr(user, '@');
	if (realm != NULL) realm++; else realm = "";

	digest = t_malloc(MD5_RESULTLEN);
	str = t_strdup_printf("%s:%s:%s", t_strcut(user, '@'),
			      realm, plaintext);
	md5_get_digest(str, strlen(str), digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN;
}

static void
plain_md4_generate(const char *plaintext, const char *user __attr_unused__,
		   const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(MD4_RESULTLEN);
	md4_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = MD4_RESULTLEN;
}

static void
plain_md5_generate(const char *plaintext, const char *user __attr_unused__,
		   const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(MD5_RESULTLEN);
	md5_get_digest(plaintext, strlen(plaintext), digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN;
}

static void
lm_generate(const char *plaintext, const char *user __attr_unused__,
	    const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(LM_HASH_SIZE);
	lm_hash(plaintext, digest);

	*raw_password_r = digest;
	*size_r = LM_HASH_SIZE;
}

static void
ntlm_generate(const char *plaintext, const char *user __attr_unused__,
	      const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(NTLMSSP_HASH_SIZE);
	ntlm_v1_hash(plaintext, digest);

	*raw_password_r = digest;
	*size_r = NTLMSSP_HASH_SIZE;
}

static bool otp_verify(const char *plaintext, const char *user __attr_unused__,
		       const unsigned char *raw_password, size_t size)
{
	const char *password;

	password = t_strndup(raw_password, size);
	return strcasecmp(password,
		password_generate_otp(plaintext, password, -1)) == 0;
}

static void
otp_generate(const char *plaintext, const char *user __attr_unused__,
	     const unsigned char **raw_password_r, size_t *size_r)
{
	const char *password;

	password = password_generate_otp(plaintext, NULL, OTP_HASH_SHA1);
	*raw_password_r = (const unsigned char *)password;
	*size_r = strlen(password);
}

static void
skey_generate(const char *plaintext, const char *user __attr_unused__,
	      const unsigned char **raw_password_r, size_t *size_r)
{
	const char *password;

	password = password_generate_otp(plaintext, NULL, OTP_HASH_MD4);
	*raw_password_r = (const unsigned char *)password;
	*size_r = strlen(password);
}

static void
rpa_generate(const char *plaintext, const char *user __attr_unused__,
	     const unsigned char **raw_password_r, size_t *size_r)
{
	unsigned char *digest;

	digest = t_malloc(MD5_RESULTLEN);
	password_generate_rpa(plaintext, digest);

	*raw_password_r = digest;
	*size_r = MD5_RESULTLEN;
}

static const struct password_scheme default_schemes[] = {
	{ "CRYPT", PW_ENCODING_NONE, 0, crypt_verify, crypt_generate },
	{ "MD5", PW_ENCODING_NONE, 0, md5_crypt_verify, md5_crypt_generate },
	{ "MD5-CRYPT", PW_ENCODING_NONE, 0,
	  md5_crypt_verify, md5_crypt_generate },
 	{ "SHA", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate },
 	{ "SHA1", PW_ENCODING_BASE64, SHA1_RESULTLEN, NULL, sha1_generate },
	{ "SMD5", PW_ENCODING_BASE64, 0, smd5_verify, smd5_generate },
	{ "SSHA", PW_ENCODING_BASE64, 0, ssha_verify, ssha_generate },
	{ "PLAIN", PW_ENCODING_NONE, 0, NULL, plain_generate },
	{ "CLEARTEXT", PW_ENCODING_NONE, 0, NULL, plain_generate },
	{ "CRAM-MD5", PW_ENCODING_HEX, 0, NULL, cram_md5_generate },
	{ "HMAC-MD5", PW_ENCODING_HEX, CRAM_MD5_CONTEXTLEN,
	  NULL, cram_md5_generate },
	{ "DIGEST-MD5", PW_ENCODING_HEX, MD5_RESULTLEN,
	  NULL, digest_md5_generate },
	{ "PLAIN-MD4", PW_ENCODING_HEX, MD4_RESULTLEN,
	  NULL, plain_md4_generate },
	{ "PLAIN-MD5", PW_ENCODING_HEX, MD5_RESULTLEN,
	  NULL, plain_md5_generate },
	{ "LDAP-MD5", PW_ENCODING_BASE64, MD5_RESULTLEN,
	  NULL, plain_md5_generate },
	{ "LANMAN", PW_ENCODING_HEX, LM_HASH_SIZE, NULL, lm_generate },
	{ "NTLM", PW_ENCODING_HEX, NTLMSSP_HASH_SIZE, NULL, ntlm_generate },
	{ "OTP", PW_ENCODING_NONE, 0, otp_verify, otp_generate },
	{ "SKEY", PW_ENCODING_NONE, 0, otp_verify, skey_generate },
	{ "RPA", PW_ENCODING_HEX, MD5_RESULTLEN, NULL, rpa_generate },

	{ NULL, 0, 0, NULL, NULL }
};

void password_schemes_init(void)
{
	const struct password_scheme *s;
#ifdef HAVE_MODULES
	struct module *mod;
	const char *symbol;
#endif

	i_array_init(&schemes_arr, sizeof(default_schemes) /
		     sizeof(default_schemes[0]) + 4);
	for (s = default_schemes; s->name != NULL; s++)
		array_append(&schemes_arr, s, 1);

#ifdef HAVE_MODULES
	scheme_modules = module_dir_load(AUTH_MODULE_DIR"/password",
					 NULL, FALSE, PACKAGE_VERSION);
	module_dir_init(scheme_modules);
	for (mod = scheme_modules; mod != NULL; mod = mod->next) {
		t_push();
		symbol = t_strconcat(mod->name, "_scheme", NULL);
		s = module_get_symbol(mod, symbol);
		if (s != NULL)
			array_append(&schemes_arr, s, 1);
		t_pop();
	}
#endif

	(void)array_append_space(&schemes_arr);
	schemes = array_idx(&schemes_arr, 0);
}

void password_schemes_deinit(void)
{
#ifdef HAVE_MODULES
	module_dir_unload(&scheme_modules);
#endif

	array_free(&schemes_arr);
	schemes = NULL;
}