Tobias Boege d0e24c483f gb.openssl: add linking exception for OpenSSL
[GB.OPENSSL]
* NEW: Add linking exception for OpenSSL (Thanks, Bastian Germann).
2019-05-03 23:35:26 +02:00

501 lines
12 KiB
C

/*
* c_cipher.c - Cipher, .Cipher.Method and CipherText classes
*
* Copyright (C) 2013-2019 Tobias Boege <tobias@gambas-buch.de>
*
* This program 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 2, or (at your option)
* any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give
* permission to link the code of portions of this program with the
* OpenSSL library under certain conditions as described in each
* individual source file, and distribute linked combinations
* including the two.
* You must obey the GNU General Public License in all respects
* for all of the code used other than OpenSSL. If you modify
* file(s) with this exception, you may extend this exception to
* your version of the file(s), but you are not obligated to do so.
* If you do not wish to do so, delete this exception statement
* from your version. If you delete this exception statement from
* all source files in the program, then also delete it here.
*/
#define __C_CIPHER_C
#include <assert.h>
#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include "main.h"
#include "c_cipher.h"
/*
* Cipher
*/
static GB_ARRAY _clist = NULL;
static void clist_func(const EVP_CIPHER *ciph, const char *from,
const char *to, void *arg)
{
const char *src;
if (!ciph)
return;
src = EVP_CIPHER_name(ciph);
*((const char **) GB.Array.Add(_clist)) = GB.NewZeroString(src);
}
static void get_clist(void)
{
GB.Array.New(&_clist, GB_T_STRING, 0);
/* XXX: There is EVP_CIPHER_do_all_sorted() but that still returns
* entries twice (NOT next to each other), so we sort manually */
EVP_CIPHER_do_all(clist_func, NULL);
sort_and_dedupe(_clist);
}
/**G
* Return a list of all ciphers present in the local OpenSSL crypto library.
**/
BEGIN_PROPERTY(Cipher_List)
GB_FUNCTION copyfn;
GB_VALUE *copy;
if (!_clist)
get_clist();
if (GB.GetFunction(&copyfn, _clist, "Copy", NULL, NULL)) {
GB.Error("Can't copy array");
return;
}
copy = GB.Call(&copyfn, 0, 0);
GB.ReturnObject(copy->_object.value);
END_PROPERTY
BEGIN_METHOD_VOID(Cipher_init)
OpenSSL_add_all_ciphers();
END_METHOD
BEGIN_METHOD_VOID(Cipher_exit)
if (!_clist)
return;
GB.Unref((void **) &_clist);
_clist = NULL;
END_METHOD
const static EVP_CIPHER *_method;
/**G
* Return a virtual object representing a cipher algorithm by giving its
* name. Valid names can be looked up from the Cipher.List property.
**/
BEGIN_METHOD(Cipher_get, GB_STRING method)
_method = EVP_get_cipherbyname(GB.ToZeroString(ARG(method)));
if (!_method) {
GB.Error("Unknown cipher method");
return;
}
RETURN_SELF();
END_METHOD
/**G
* Check if the named cipher algorithm is supported.
**/
BEGIN_METHOD(Cipher_IsSupported, GB_STRING method)
_method = EVP_get_cipherbyname(STRING(method));
GB.ReturnBoolean(!!_method);
END_METHOD
GB_DESC CCipher[] = {
GB_DECLARE("Cipher", 0),
GB_NOT_CREATABLE(),
GB_STATIC_PROPERTY_READ("List", "String[]", Cipher_List),
GB_STATIC_METHOD("_init", NULL, Cipher_init, NULL),
GB_STATIC_METHOD("_exit", NULL, Cipher_exit, NULL),
GB_STATIC_METHOD("_get", ".Cipher.Method", Cipher_get, "(Method)s"),
GB_STATIC_METHOD("IsSupported", "b", Cipher_IsSupported, "(Method)s"),
GB_END_DECLARE
};
/*
* .Cipher.Method
*/
/**G
* Return the key length that this cipher algorithm expects.
*
* See also: Cipher.Encrypt()
**/
BEGIN_PROPERTY(CipherMethod_KeyLength)
if (READ_PROPERTY) {
GB.ReturnInteger(EVP_CIPHER_key_length(_method));
return;
}
END_PROPERTY
/**G
* Return the initialisation vector length that this cipher algorithm
* expects.
*
* See also: Cipher.Encrypt()
**/
BEGIN_PROPERTY(CipherMethod_IvLength)
if (READ_PROPERTY) {
GB.ReturnInteger(EVP_CIPHER_iv_length(_method));
return;
}
END_PROPERTY
static char *do_cipher(const unsigned char *data, unsigned int dlen,
const unsigned char *key, const unsigned char *iv,
unsigned int *length, int enc, char **errmsg)
{
EVP_CIPHER_CTX *ctx;
unsigned char block[1024 + EVP_MAX_BLOCK_LENGTH];
char *out;
unsigned int ilen;
int blen;
*errmsg = NULL;
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
*errmsg = "Could not allocate cipher context";
return NULL;
}
EVP_CIPHER_CTX_init(ctx);
if (!EVP_CipherInit_ex(ctx, _method, NULL, key, iv, enc))
return NULL;
out = NULL;
*length = 0;
memset(block, 0, sizeof(block));
while (dlen) {
ilen = MIN(dlen, 1024);
if (!EVP_CipherUpdate(ctx, block, &blen, data, ilen))
goto __ERROR;
out = GB.AddString(out, (char *) block, blen);
*length += blen;
data += ilen;
dlen -= ilen;
}
if (!EVP_CipherFinal_ex(ctx, block, &blen))
goto __ERROR;
if (!EVP_CIPHER_CTX_cleanup(ctx))
goto __ERROR;
if (blen) {
out = GB.AddString(out, (char *) block, blen);
*length += blen;
}
EVP_CIPHER_CTX_free(ctx);
return out;
__ERROR:
GB.FreeString(&out);
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
typedef struct {
GB_BASE ob;
char *cipher, *key, *iv;
} CCIPHERTEXT;
/**G
* Encrypt a plaintext using the given key and initialisation vector.
**/
BEGIN_METHOD(CipherMethod_Encrypt, GB_STRING plain; GB_STRING key;
GB_STRING iv)
unsigned char key[EVP_CIPHER_key_length(_method)];
unsigned char iv[EVP_CIPHER_iv_length(_method)];
unsigned int length;
char *cipher, *errmsg;
CCIPHERTEXT *res;
bzero(key, sizeof(key));
bzero(iv, sizeof(iv));
if (MISSING(key)) {
assert(RAND_bytes(key, sizeof(key)));
} else {
if (LENGTH(key) != sizeof(key)) {
GB.Error("Key length does not match method");
return;
}
memcpy(key, STRING(key), sizeof(key));
}
if (MISSING(iv)) {
assert(RAND_bytes(iv, sizeof(iv)));
} else {
if (LENGTH(iv) != sizeof(iv)) {
GB.Error("InitVector length does not match method");
return;
}
memcpy(iv, STRING(iv), sizeof(iv));
}
cipher = do_cipher((uchar *) STRING(plain), LENGTH(plain), key, iv,
&length, 1, &errmsg);
if (!cipher) {
GB.Error(errmsg ? errmsg : "Encryption failed");
return;
}
GB.Push(3, GB_T_STRING, cipher, length,
GB_T_STRING, key, sizeof(key),
GB_T_STRING, iv, sizeof(iv));
res = GB.New(GB.FindClass("CipherText"), NULL, (void*)(intptr_t) 3);
GB.FreeString(&cipher);
GB.ReturnObject(res);
END_METHOD
/**G
* Decrypt a ciphertext generated by Encrypt().
**/
BEGIN_METHOD(CipherMethod_Decrypt, GB_OBJECT ciph)
CCIPHERTEXT *ciph = VARG(ciph);
unsigned int length;
char *plain, *errmsg;
plain = do_cipher((uchar *) ciph->cipher,
GB.StringLength(ciph->cipher),
(uchar *) ciph->key, (uchar *) ciph->iv,
&length, 0, &errmsg);
if (!plain) {
GB.Error(errmsg ? errmsg : "Decryption failed");
return;
}
GB.ReturnNewString(plain, length);
GB.FreeString(&plain);
END_METHOD
/*
* Retain compatibility with the 'openssl' program for now.
*/
#define ITER 1
/**G#
* Encrypt a plaintext with a password to a standalone string.
*
* If 'salt' is given, it should be an 8-byte string. If it is not, it is
* padded with zeros or truncated to a size of 8. If the parameter is not
* given, a pseudo-random salt is generated.
*
* This uses a less up-to-date method (PKCS#5 v1.5) to generate an
* encryption key from the password. This is for compatibility with the
* openssl program when invoked like:
*
* `echo -n Plain | openssl CipherMethod -k Password -S HexSalt`
**/
BEGIN_METHOD(CipherMethod_EncryptSalted, GB_STRING plain; GB_STRING passwd;
GB_STRING salt)
unsigned char salt[8];
unsigned char key[EVP_CIPHER_key_length(_method)];
unsigned char iv[EVP_CIPHER_iv_length(_method)];
unsigned int length;
char *cipher, *res, *errmsg;
bzero(salt, sizeof(salt));
if (MISSING(salt)) {
assert(RAND_bytes(salt, sizeof(salt)));
} else {
bzero(salt, sizeof(salt));
memcpy(salt, STRING(salt), MIN(sizeof(salt), LENGTH(salt)));
}
// "openssl enc" >= 1.1.0 uses SHA256 by default instead of MD5
// EVP_BytesToKey(_method, EVP_sha256(), salt, (uchar *) STRING(passwd),
// LENGTH(passwd), ITER, key, iv);
EVP_BytesToKey(_method, EVP_md5(), salt, (uchar *) STRING(passwd),
LENGTH(passwd), ITER, key, iv);
cipher = do_cipher((uchar *) STRING(plain), LENGTH(plain), key, iv,
&length, 1, &errmsg);
if (!cipher) {
GB.Error(errmsg ? errmsg : "Encryption failed");
return;
}
res = GB.NewZeroString("Salted__");
res = GB.AddString(res, (char *) salt, sizeof(salt));
res = GB.AddString(res, cipher, length);
GB.FreeString(&cipher);
GB.ReturnString(res);
GB.ReturnBorrow();
GB.FreeString(&res);
GB.ReturnRelease();
END_METHOD
/**G#
* Decrypt a ciphertext obtained from EncryptSalted().
**/
BEGIN_METHOD(CipherMethod_DecryptSalted, GB_STRING cipher; GB_STRING passwd)
unsigned char salt[8], *cipher;
unsigned char key[EVP_CIPHER_key_length(_method)];
unsigned char iv[EVP_CIPHER_iv_length(_method)];
unsigned int clen, length;
char *plain, *errmsg;
if (!strstr(STRING(cipher), "Salted__")) {
GB.Error("Unrecognised cipher string format");
return;
}
/* salt begins at STRING(cipher) + strlen("Salted__") */
memcpy(salt, STRING(cipher) + 8, sizeof(salt));
// "openssl enc" >= 1.1.0 uses SHA256 by default instead of MD5
// EVP_BytesToKey(_method, EVP_sha256(), salt, (uchar *) STRING(passwd),
// LENGTH(passwd), ITER, key, iv);
EVP_BytesToKey(_method, EVP_md5(), salt, (uchar *) STRING(passwd),
LENGTH(passwd), ITER, key, iv);
cipher = (uchar *) STRING(cipher) + 8 + sizeof(salt);
clen = LENGTH(cipher) - (uint) (cipher - (uchar *) STRING(cipher));
plain = do_cipher((uchar *) cipher, clen, key, iv, &length, 0, &errmsg);
if (!plain) {
GB.Error(errmsg ? errmsg : "Decryption failed");
return;
}
GB.ReturnNewString(plain, length);
GB.FreeString(&plain);
END_METHOD
#if 0
/**G
* Encrypt a plaintext with a password to a standalone string.
*
* We use the PKCS#5-v2.0-conforming PBKDF2 HMAC-SHA1 to generate an
* encryption key from a password.
**/
BEGIN_METHOD(CipherMethod_EncryptSalted, GB_STRING plain; GB_STRING passwd)
char salt[8]; /* 8 is recommended as minimum */
char key[EVP_CIPHER_key_length(_method)];
bzero(key, sizeof(key));
RAND_bytes(salt, sizeof(salt));
PKCS5_PBKDF_HMAC_SHA1(STRING(passwd), LENGTH(passwd), salt,
sizeof(salt), ITER, sizeof(key), key);
END_METHOD
/**G
* Decrypt a ciphertext obtained from EncryptSalted().
**/
BEGIN_METHOD(CipherMethod_DecryptSalted, GB_STRING cipher; GB_STRING passwd)
END_METHOD
#endif
GB_DESC CCipherMethod[] = {
/*
* TODO: Maybe add a stream interface?
*/
GB_DECLARE(".Cipher.Method", 0),
GB_VIRTUAL_CLASS(),
GB_STATIC_PROPERTY_READ("KeyLength", "i", CipherMethod_KeyLength),
GB_STATIC_PROPERTY_READ("IvLength", "i", CipherMethod_IvLength),
GB_STATIC_METHOD("Encrypt", "CipherText", CipherMethod_Encrypt, "(Plain)s[(Key)s(InitVector)s]"),
GB_STATIC_METHOD("Decrypt", "s", CipherMethod_Decrypt, "(Cipher)CipherText"),
GB_STATIC_METHOD("EncryptSalted", "s", CipherMethod_EncryptSalted, "(Plain)s(Password)s[(Salt)s]"),
GB_STATIC_METHOD("DecryptSalted", "s", CipherMethod_DecryptSalted, "(Cipher)s(Password)s"),
GB_END_DECLARE
};
/*
* CipherText
*/
#define THIS ((CCIPHERTEXT *) _object)
BEGIN_PROPERTY(CipherText_Cipher)
GB.ReturnString(THIS->cipher);
END_PROPERTY
BEGIN_PROPERTY(CipherText_Key)
GB.ReturnString(THIS->key);
END_PROPERTY
BEGIN_PROPERTY(CipherText_InitVector)
GB.ReturnString(THIS->iv);
END_PROPERTY
BEGIN_METHOD(CipherText_new, GB_STRING ciph; GB_STRING key; GB_STRING iv)
THIS->cipher = GB.NewString(STRING(ciph), LENGTH(ciph));
THIS->key = GB.NewString(STRING(key), LENGTH(key));
THIS->iv = GB.NewString(STRING(iv), LENGTH(iv));
END_METHOD
BEGIN_METHOD_VOID(CipherText_free)
GB.FreeString(&THIS->cipher);
GB.FreeString(&THIS->key);
GB.FreeString(&THIS->iv);
END_METHOD
GB_DESC CCipherText[] = {
GB_DECLARE("CipherText", sizeof(CCIPHERTEXT)),
GB_PROPERTY_READ("Cipher", "s", CipherText_Cipher),
GB_PROPERTY_READ("Key", "s", CipherText_Key),
GB_PROPERTY_READ("InitVector", "s", CipherText_InitVector),
GB_METHOD("_new", NULL, CipherText_new, "(Cipher)s(Key)s(InitVector)s"),
GB_METHOD("_free", NULL, CipherText_free, NULL),
GB_END_DECLARE
};