diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 2685151..88b380a 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -22,7 +22,7 @@ #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 { \ @@ -61,7 +61,7 @@ ossl_cipher_new(const EVP_CIPHER *cipher) 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; } @@ -73,8 +73,8 @@ static void 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); } } @@ -107,12 +107,12 @@ ossl_cipher_initialize(VALUE self, VALUE str) 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 @@ -122,7 +122,7 @@ ossl_cipher_initialize(VALUE self, VALUE str) */ 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; } @@ -137,11 +137,11 @@ ossl_cipher_copy(VALUE self, VALUE other) 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; } @@ -194,7 +194,7 @@ ossl_cipher_reset(VALUE 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; } @@ -208,36 +208,36 @@ ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode) 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; @@ -311,18 +311,18 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE 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); @@ -368,7 +368,7 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self) } 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); @@ -394,7 +394,7 @@ ossl_cipher_final(VALUE self) 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); @@ -403,6 +403,35 @@ ossl_cipher_final(VALUE self) /* * 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 @@ -473,11 +502,102 @@ ossl_cipher_set_iv(VALUE self, VALUE iv) 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: @@ -523,7 +643,7 @@ ossl_cipher_set_padding(VALUE self, VALUE padding) GetCipher(self, ctx); if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1) - ossl_raise(eCipherError, NULL); + ossl_raise(eCipherError, NULL); return padding; } #else @@ -534,9 +654,9 @@ ossl_cipher_set_padding(VALUE self, VALUE padding) 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))); \ } /* @@ -742,8 +862,12 @@ Init_ossl_cipher(void) 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); @@ -751,4 +875,3 @@ Init_ossl_cipher(void) rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0); rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1); } - diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index 0e4a775..8620be3 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -101,6 +101,38 @@ class OpenSSL::TestCipher < Test::Unit::TestCase 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