You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

393 lines
10 KiB

/*
* crypto.c - Manage the global crypto
*
* Copyright (C) 2013 - 2019, Max Lv <max.c.lv@gmail.com>
*
* This file is part of the shadowsocks-libev.
*
* shadowsocks-libev 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 3 of the License, or
* (at your option) any later version.
*
* shadowsocks-libev 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.
*
* You should have received a copy of the GNU General Public License
* along with shadowsocks-libev; see the file COPYING. If not, see
* <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(__linux__) && defined(HAVE_LINUX_RANDOM_H)
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/random.h>
#endif
#include <stdint.h>
#include <sodium.h>
#include <mbedtls/version.h>
#include <mbedtls/md5.h>
#include "base64.h"
#include "crypto.h"
#include "stream.h"
#include "aead.h"
#include "utils.h"
#include "ppbloom.h"
int
balloc(buffer_t *ptr, size_t capacity)
{
sodium_memzero(ptr, sizeof(buffer_t));
ptr->data = ss_malloc(capacity);
ptr->capacity = capacity;
return capacity;
}
int
brealloc(buffer_t *ptr, size_t len, size_t capacity)
{
if (ptr == NULL)
return -1;
size_t real_capacity = max(len, capacity);
if (ptr->capacity < real_capacity) {
ptr->data = ss_realloc(ptr->data, real_capacity);
ptr->capacity = real_capacity;
}
return real_capacity;
}
void
bfree(buffer_t *ptr)
{
if (ptr == NULL)
return;
ptr->idx = 0;
ptr->len = 0;
ptr->capacity = 0;
if (ptr->data != NULL) {
ss_free(ptr->data);
}
}
int
bprepend(buffer_t *dst, buffer_t *src, size_t capacity)
{
brealloc(dst, dst->len + src->len, capacity);
memmove(dst->data + src->len, dst->data, dst->len);
memcpy(dst->data, src->data, src->len);
dst->len = dst->len + src->len;
return dst->len;
}
int
rand_bytes(void *output, int len)
{
randombytes_buf(output, len);
// always return success
return 0;
}
unsigned char *
crypto_md5(const unsigned char *d, size_t n, unsigned char *md)
{
static unsigned char m[16];
if (md == NULL) {
md = m;
}
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
if (mbedtls_md5_ret(d, n, md) != 0)
FATAL("Failed to calculate MD5");
#else
mbedtls_md5(d, n, md);
#endif
return md;
}
static void
entropy_check(void)
{
#if defined(__linux__) && defined(HAVE_LINUX_RANDOM_H) && defined(RNDGETENTCNT)
int fd;
int c;
if ((fd = open("/dev/random", O_RDONLY)) != -1) {
if (ioctl(fd, RNDGETENTCNT, &c) == 0 && c < 160) {
LOGI("This system doesn't provide enough entropy to quickly generate high-quality random numbers.\n"
"Installing the rng-utils/rng-tools, jitterentropy or haveged packages may help.\n"
"On virtualized Linux environments, also consider using virtio-rng.\n"
"The service will not start until enough entropy has been collected.\n");
}
close(fd);
}
#endif
}
crypto_t *
crypto_init(const char *password, const char *key, const char *method)
{
int i, m = -1;
entropy_check();
// Initialize sodium for random generator
if (sodium_init() == -1) {
FATAL("Failed to initialize sodium");
}
// Initialize NONCE bloom filter
#ifdef MODULE_REMOTE
ppbloom_init(BF_NUM_ENTRIES_FOR_SERVER, BF_ERROR_RATE_FOR_SERVER);
#else
ppbloom_init(BF_NUM_ENTRIES_FOR_CLIENT, BF_ERROR_RATE_FOR_CLIENT);
#endif
if (method != NULL) {
for (i = 0; i < STREAM_CIPHER_NUM; i++)
if (strcmp(method, supported_stream_ciphers[i]) == 0) {
m = i;
break;
}
if (m != -1) {
LOGI("Stream ciphers are insecure, therefore deprecated, and should be almost always avoided.");
cipher_t *cipher = stream_init(password, key, method);
if (cipher == NULL)
return NULL;
crypto_t *crypto = (crypto_t *)ss_malloc(sizeof(crypto_t));
crypto_t tmp = {
.cipher = cipher,
.encrypt_all = &stream_encrypt_all,
.decrypt_all = &stream_decrypt_all,
.encrypt = &stream_encrypt,
.decrypt = &stream_decrypt,
.ctx_init = &stream_ctx_init,
.ctx_release = &stream_ctx_release,
};
memcpy(crypto, &tmp, sizeof(crypto_t));
return crypto;
}
for (i = 0; i < AEAD_CIPHER_NUM; i++)
if (strcmp(method, supported_aead_ciphers[i]) == 0) {
m = i;
break;
}
if (m != -1) {
cipher_t *cipher = aead_init(password, key, method);
if (cipher == NULL)
return NULL;
crypto_t *crypto = (crypto_t *)ss_malloc(sizeof(crypto_t));
crypto_t tmp = {
.cipher = cipher,
.encrypt_all = &aead_encrypt_all,
.decrypt_all = &aead_decrypt_all,
.encrypt = &aead_encrypt,
.decrypt = &aead_decrypt,
.ctx_init = &aead_ctx_init,
.ctx_release = &aead_ctx_release,
};
memcpy(crypto, &tmp, sizeof(crypto_t));
return crypto;
}
}
LOGE("invalid cipher name: %s", method);
return NULL;
}
int
crypto_derive_key(const char *pass, uint8_t *key, size_t key_len)
{
size_t datal;
datal = strlen((const char *)pass);
const digest_type_t *md = mbedtls_md_info_from_string("MD5");
if (md == NULL) {
FATAL("MD5 Digest not found in crypto library");
}
mbedtls_md_context_t c;
unsigned char md_buf[MAX_MD_SIZE];
int addmd;
unsigned int i, j, mds;
mds = mbedtls_md_get_size(md);
memset(&c, 0, sizeof(mbedtls_md_context_t));
if (pass == NULL)
return key_len;
if (mbedtls_md_setup(&c, md, 0))
return 0;
for (j = 0, addmd = 0; j < key_len; addmd++) {
mbedtls_md_starts(&c);
if (addmd) {
mbedtls_md_update(&c, md_buf, mds);
}
mbedtls_md_update(&c, (uint8_t *)pass, datal);
mbedtls_md_finish(&c, &(md_buf[0]));
for (i = 0; i < mds; i++, j++) {
if (j >= key_len)
break;
key[j] = md_buf[i];
}
}
mbedtls_md_free(&c);
return key_len;
}
/* HKDF-Extract + HKDF-Expand */
int
crypto_hkdf(const mbedtls_md_info_t *md, const unsigned char *salt,
int salt_len, const unsigned char *ikm, int ikm_len,
const unsigned char *info, int info_len, unsigned char *okm,
int okm_len)
{
unsigned char prk[MBEDTLS_MD_MAX_SIZE];
return crypto_hkdf_extract(md, salt, salt_len, ikm, ikm_len, prk) ||
crypto_hkdf_expand(md, prk, mbedtls_md_get_size(md), info, info_len,
okm, okm_len);
}
/* HKDF-Extract(salt, IKM) -> PRK */
int
crypto_hkdf_extract(const mbedtls_md_info_t *md, const unsigned char *salt,
int salt_len, const unsigned char *ikm, int ikm_len,
unsigned char *prk)
{
int hash_len;
unsigned char null_salt[MBEDTLS_MD_MAX_SIZE] = { '\0' };
if (salt_len < 0) {
return CRYPTO_ERROR;
}
hash_len = mbedtls_md_get_size(md);
if (salt == NULL) {
salt = null_salt;
salt_len = hash_len;
}
return mbedtls_md_hmac(md, salt, salt_len, ikm, ikm_len, prk);
}
/* HKDF-Expand(PRK, info, L) -> OKM */
int
crypto_hkdf_expand(const mbedtls_md_info_t *md, const unsigned char *prk,
int prk_len, const unsigned char *info, int info_len,
unsigned char *okm, int okm_len)
{
int hash_len;
int N;
int T_len = 0, where = 0, i, ret;
mbedtls_md_context_t ctx;
unsigned char T[MBEDTLS_MD_MAX_SIZE];
if (info_len < 0 || okm_len < 0 || okm == NULL) {
return CRYPTO_ERROR;
}
hash_len = mbedtls_md_get_size(md);
if (prk_len < hash_len) {
return CRYPTO_ERROR;
}
if (info == NULL) {
info = (const unsigned char *)"";
}
N = okm_len / hash_len;
if ((okm_len % hash_len) != 0) {
N++;
}
if (N > 255) {
return CRYPTO_ERROR;
}
mbedtls_md_init(&ctx);
if ((ret = mbedtls_md_setup(&ctx, md, 1)) != 0) {
mbedtls_md_free(&ctx);
return ret;
}
/* Section 2.3. */
for (i = 1; i <= N; i++) {
unsigned char c = i;
ret = mbedtls_md_hmac_starts(&ctx, prk, prk_len) ||
mbedtls_md_hmac_update(&ctx, T, T_len) ||
mbedtls_md_hmac_update(&ctx, info, info_len) ||
/* The constant concatenated to the end of each T(n) is a single
* octet. */
mbedtls_md_hmac_update(&ctx, &c, 1) ||
mbedtls_md_hmac_finish(&ctx, T);
if (ret != 0) {
mbedtls_md_free(&ctx);
return ret;
}
memcpy(okm + where, T, (i != N) ? hash_len : (okm_len - where));
where += hash_len;
T_len = hash_len;
}
mbedtls_md_free(&ctx);
return 0;
}
int
crypto_parse_key(const char *base64, uint8_t *key, size_t key_len)
{
size_t base64_len = strlen(base64);
int out_len = BASE64_SIZE(base64_len);
uint8_t out[out_len];
out_len = base64_decode(out, base64, out_len);
if (out_len > 0 && out_len >= key_len) {
memcpy(key, out, key_len);
#ifdef SS_DEBUG
dump("KEY", (char *)key, key_len);
#endif
return key_len;
}
out_len = BASE64_SIZE(key_len);
char out_key[out_len];
rand_bytes(key, key_len);
base64_encode(out_key, out_len, key, key_len);
LOGE("Invalid key for your chosen cipher!");
LOGE("It requires a " SIZE_FMT "-byte key encoded with URL-safe Base64", key_len);
LOGE("Generating a new random key: %s", out_key);
FATAL("Please use the key above or input a valid key");
return key_len;
}
#ifdef SS_DEBUG
void
dump(char *tag, char *text, int len)
{
int i;
printf("%s: ", tag);
for (i = 0; i < len; i++)
printf("0x%02x ", (uint8_t)text[i]);
printf("\n");
}
#endif