Project

General

Profile

Feature #6980 » openssl_aead_ciphers.patch

OpenSSL AEAD mode support - stouset (Stephen Touset), 09/05/2012 04:11 AM

View differences:

ext/openssl/ossl_cipher.c
#define GetCipher(obj, ctx) do { \
GetCipherInit((obj), (ctx)); \
if (!(ctx)) { \
ossl_raise(rb_eRuntimeError, "Cipher not inititalized!"); \
ossl_raise(rb_eRuntimeError, "Cipher not inititalized!"); \
} \
} while (0)
#define SafeGetCipher(obj, ctx) do { \
......
AllocCipher(ret, ctx);
EVP_CIPHER_CTX_init(ctx);
if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
return ret;
}
......
ossl_cipher_free(EVP_CIPHER_CTX *ctx)
{
if (ctx) {
EVP_CIPHER_CTX_cleanup(ctx);
ruby_xfree(ctx);
EVP_CIPHER_CTX_cleanup(ctx);
ruby_xfree(ctx);
}
}
......
name = StringValuePtr(str);
GetCipherInit(self, ctx);
if (ctx) {
ossl_raise(rb_eRuntimeError, "Cipher already inititalized!");
ossl_raise(rb_eRuntimeError, "Cipher already inititalized!");
}
AllocCipher(self, ctx);
EVP_CIPHER_CTX_init(ctx);
if (!(cipher = EVP_get_cipherbyname(name))) {
ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%s)", name);
ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%s)", name);
}
/*
* The EVP which has EVP_CIPH_RAND_KEY flag (such as DES3) allows
......
*/
memset(key, 0, EVP_MAX_KEY_LENGTH);
if (EVP_CipherInit_ex(ctx, cipher, NULL, key, NULL, -1) != 1)
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
return self;
}
......
GetCipherInit(self, ctx1);
if (!ctx1) {
AllocCipher(self, ctx1);
AllocCipher(self, ctx1);
}
SafeGetCipher(other, ctx2);
if (EVP_CIPHER_CTX_copy(ctx1, ctx2) != 1)
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
return self;
}
......
GetCipher(self, ctx);
if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1) != 1)
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
return self;
}
......
VALUE pass, init_v;
if(rb_scan_args(argc, argv, "02", &pass, &init_v) > 0){
/*
* oops. this code mistakes salt for IV.
* We deprecated the arguments for this method, but we decided
* keeping this behaviour for backward compatibility.
*/
const char *cname = rb_class2name(rb_obj_class(self));
rb_warn("arguments for %s#encrypt and %s#decrypt were deprecated; "
/*
* oops. this code mistakes salt for IV.
* We deprecated the arguments for this method, but we decided
* keeping this behaviour for backward compatibility.
*/
const char *cname = rb_class2name(rb_obj_class(self));
rb_warn("arguments for %s#encrypt and %s#decrypt were deprecated; "
"use %s#pkcs5_keyivgen to derive key and IV",
cname, cname, cname);
StringValue(pass);
GetCipher(self, ctx);
if (NIL_P(init_v)) memcpy(iv, "OpenSSL for Ruby rulez!", sizeof(iv));
else{
StringValue(init_v);
if (EVP_MAX_IV_LENGTH > RSTRING_LEN(init_v)) {
memset(iv, 0, EVP_MAX_IV_LENGTH);
memcpy(iv, RSTRING_PTR(init_v), RSTRING_LEN(init_v));
}
else memcpy(iv, RSTRING_PTR(init_v), sizeof(iv));
}
EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), EVP_md5(), iv,
(unsigned char *)RSTRING_PTR(pass), RSTRING_LENINT(pass), 1, key, NULL);
p_key = key;
p_iv = iv;
StringValue(pass);
GetCipher(self, ctx);
if (NIL_P(init_v)) memcpy(iv, "OpenSSL for Ruby rulez!", sizeof(iv));
else{
StringValue(init_v);
if (EVP_MAX_IV_LENGTH > RSTRING_LEN(init_v)) {
memset(iv, 0, EVP_MAX_IV_LENGTH);
memcpy(iv, RSTRING_PTR(init_v), RSTRING_LEN(init_v));
}
else memcpy(iv, RSTRING_PTR(init_v), sizeof(iv));
}
EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), EVP_md5(), iv,
(unsigned char *)RSTRING_PTR(pass), RSTRING_LENINT(pass), 1, key, NULL);
p_key = key;
p_iv = iv;
}
else {
GetCipher(self, ctx);
GetCipher(self, ctx);
}
if (EVP_CipherInit_ex(ctx, NULL, NULL, p_key, p_iv, mode) != 1) {
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
}
return self;
......
rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest);
StringValue(vpass);
if(!NIL_P(vsalt)){
StringValue(vsalt);
if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN)
ossl_raise(eCipherError, "salt must be an 8-octet string");
salt = (unsigned char *)RSTRING_PTR(vsalt);
StringValue(vsalt);
if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN)
ossl_raise(eCipherError, "salt must be an 8-octet string");
salt = (unsigned char *)RSTRING_PTR(vsalt);
}
iter = NIL_P(viter) ? 2048 : NUM2INT(viter);
digest = NIL_P(vdigest) ? EVP_md5() : GetDigestPtr(vdigest);
GetCipher(self, ctx);
EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt,
(unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv);
(unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv);
if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1)
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
OPENSSL_cleanse(key, sizeof key);
OPENSSL_cleanse(iv, sizeof iv);
......
}
if (!EVP_CipherUpdate(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len))
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
assert(out_len < RSTRING_LEN(str));
rb_str_set_len(str, out_len);
......
GetCipher(self, ctx);
str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx));
if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len))
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
assert(out_len <= RSTRING_LEN(str));
rb_str_set_len(str, out_len);
......
/*
* call-seq:
* cipher.verify -> string
*
* Verifies the decrypted ciphertext against the tag set prior to
* decryption using the ciphertext and optional associated
* authentication data. Returns an empty string if the ciphertext was
* authenticated successfully, otherwise raises an
* OpenSSL::Cipher::CipherError.
*
* Only call this method after setting the authentication tag
* appropriate for your cipher mode and passing the entire contents
* of the ciphertext into the cipher, and before calling
* Cipher#final.
*/
static VALUE
ossl_cipher_verify(VALUE self)
{
EVP_CIPHER_CTX *ctx;
int out_len = 0;
GetCipher(self, ctx);
if (!EVP_CipherUpdate(ctx, NULL, &out_len, NULL, 0))
ossl_raise(eCipherError, "ciphertext failed to authenticate");
return rb_str_new(0, 0);
}
/*
* call-seq:
* cipher.name -> string
*
* Returns the name of the cipher which may differ slightly from the original
......
ossl_raise(eCipherError, "iv length too short");
if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, (unsigned char *)RSTRING_PTR(iv), -1) != 1)
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
return iv;
}
/*
* call-seq:
* cipher.aad = string -> string
*
* Sets the cipher's additional authenticated data. This field may be
* set optionally before using AEAD cipher modes such as GCM or
* CCM. The contents of this field should be non-sensitive
* data which will be added to the ciphertext to generate the
* authentication tag which validates the contents of the ciphertext.
*
* The AAD must be set prior to encryption or decryption. Only call
* this method after calling Cipher#encrypt when encrypting, and
* after Cipher#decrypt and Cipher#gcm_tag= when decrypting.
*/
static VALUE
ossl_cipher_set_aad(VALUE self, VALUE data)
{
EVP_CIPHER_CTX *ctx;
unsigned char *in = NULL;
int in_len = 0;
int out_len = 0;
StringValue(data);
in = (unsigned char *) RSTRING_PTR(data);
in_len = RSTRING_LENINT(data);
GetCipher(self, ctx);
if (!EVP_CipherUpdate(ctx, NULL, &out_len, in, in_len))
ossl_raise(eCipherError, "couldn't set additional authenticated data");
return self;
}
/*
* call-seq:
* cipher.gcm_tag -> string
*
* Gets the authentication tag generated by GCM cipher modes. This
* tag may be stored along with the ciphertext, then set on the
* decryption cipher to authenticate the contents of the ciphertext
* against changes.
*
* The tag may only be retrieved after calling Cipher#final.
*/
static VALUE
ossl_cipher_get_gcm_tag(VALUE self)
{
EVP_CIPHER_CTX *ctx;
VALUE tag;
// GCM tags are 16 bytes in OpenSSL
tag = rb_str_new(NULL, 16);
GetCipher(self, ctx);
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, (unsigned char *)RSTRING_PTR(tag)))
ossl_raise(eCipherError, "Cipher#finish must be called before getting the tag");
return tag;
}
/*
* call-seq:
* cipher.gcm_tag = string -> string
*
* Sets the authentication tag to verify the contents of the
* ciphertext. The GCM tag must be set after calling Cipher#decrypt
* but before decrypting any of the ciphertext. After all decryption
* is performed, the tag can be verified by calling Cipher#verify.
*/
static VALUE
ossl_cipher_set_gcm_tag(VALUE self, VALUE data)
{
EVP_CIPHER_CTX *ctx;
unsigned char *in = NULL;
int in_len = 0;
StringValue(data);
in = (unsigned char *) RSTRING_PTR(data);
in_len = RSTRING_LENINT(data);
GetCipher(self, ctx);
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, in_len, in))
ossl_raise(eCipherError, "unable to set GCM tag");
return data;
}
/*
* call-seq:
......
GetCipher(self, ctx);
if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1)
ossl_raise(eCipherError, NULL);
ossl_raise(eCipherError, NULL);
return padding;
}
#else
......
static VALUE \
ossl_cipher_##func(VALUE self) \
{ \
EVP_CIPHER_CTX *ctx; \
GetCipher(self, ctx); \
return INT2NUM(EVP_CIPHER_##func(EVP_CIPHER_CTX_cipher(ctx))); \
EVP_CIPHER_CTX *ctx; \
GetCipher(self, ctx); \
return INT2NUM(EVP_CIPHER_##func(EVP_CIPHER_CTX_cipher(ctx))); \
}
/*
......
rb_define_method(cCipher, "pkcs5_keyivgen", ossl_cipher_pkcs5_keyivgen, -1);
rb_define_method(cCipher, "update", ossl_cipher_update, -1);
rb_define_method(cCipher, "final", ossl_cipher_final, 0);
rb_define_method(cCipher, "verify", ossl_cipher_verify, 0);
rb_define_method(cCipher, "name", ossl_cipher_name, 0);
rb_define_method(cCipher, "key=", ossl_cipher_set_key, 1);
rb_define_method(cCipher, "aad=", ossl_cipher_set_aad, 1);
rb_define_method(cCipher, "gcm_tag=", ossl_cipher_set_gcm_tag, 1);
rb_define_method(cCipher, "gcm_tag", ossl_cipher_get_gcm_tag, 0);
rb_define_method(cCipher, "key_len=", ossl_cipher_set_key_length, 1);
rb_define_method(cCipher, "key_len", ossl_cipher_key_length, 0);
rb_define_method(cCipher, "iv=", ossl_cipher_set_iv, 1);
......
rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0);
rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1);
}
test/openssl/test_cipher.rb
end
end
end
if OpenSSL::OPENSSL_VERSION_NUMBER > 0x1000103f
def test_aes_gcm
pt = File.read(__FILE__)
c1 = OpenSSL::Cipher.new('aes-256-gcm')
c2 = OpenSSL::Cipher.new('aes-256-gcm')
c3 = OpenSSL::Cipher.new('aes-256-gcm')
c1.encrypt
c1.pkcs5_keyivgen('passwd')
c1.aad = 'aad'
ct = c1.update(pt) + c1.final
tag = c1.gcm_tag
c2.decrypt
c2.pkcs5_keyivgen('passwd')
c2.gcm_tag = tag
c2.aad = 'aad'
assert_equal(pt, c2.update(ct) + c2.verify + c2.final)
c3.decrypt
c3.pkcs5_keyivgen('passwd')
c3.gcm_tag = tag[0..-2] << tag[-1].succ
c3.aad = 'aad'
assert_raise OpenSSL::Cipher::CipherError do
c3.update(ct) + c3.verify + c3.final
end
end
end
end
end
(1-1/2)