Feature #6980 » openssl_aead_ciphers.patch
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
|