summaryrefslogtreecommitdiff
path: root/ext/openssl/ossl_cipher.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/openssl/ossl_cipher.c')
-rw-r--r--ext/openssl/ossl_cipher.c273
1 files changed, 200 insertions, 73 deletions
diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c
index be7a0321ae..f1f3459ef2 100644
--- a/ext/openssl/ossl_cipher.c
+++ b/ext/openssl/ossl_cipher.c
@@ -36,6 +36,7 @@
*/
VALUE cCipher;
VALUE eCipherError;
+static ID id_auth_tag_len;
static VALUE ossl_cipher_alloc(VALUE klass);
static void ossl_cipher_free(void *ptr);
@@ -54,11 +55,24 @@ static const rb_data_type_t ossl_cipher_type = {
const EVP_CIPHER *
GetCipherPtr(VALUE obj)
{
- EVP_CIPHER_CTX *ctx;
+ if (rb_obj_is_kind_of(obj, cCipher)) {
+ EVP_CIPHER_CTX *ctx;
+
+ GetCipher(obj, ctx);
+
+ return EVP_CIPHER_CTX_cipher(ctx);
+ }
+ else {
+ const EVP_CIPHER *cipher;
- SafeGetCipher(obj, ctx);
+ StringValueCStr(obj);
+ cipher = EVP_get_cipherbyname(RSTRING_PTR(obj));
+ if (!cipher)
+ ossl_raise(rb_eArgError,
+ "unsupported cipher algorithm: %"PRIsVALUE, obj);
- return EVP_CIPHER_CTX_cipher(ctx);
+ return cipher;
+ }
}
VALUE
@@ -248,7 +262,7 @@ ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode)
*
* Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the
* following methods:
- * * [key=, iv=, random_key, random_iv, pkcs5_keyivgen]
+ * * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen]
*
* Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 1).
*/
@@ -266,7 +280,7 @@ ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self)
*
* Make sure to call Cipher#encrypt or Cipher#decrypt before using any of the
* following methods:
- * * [key=, iv=, random_key, random_iv, pkcs5_keyivgen]
+ * * [#key=, #iv=, #random_key, #random_iv, #pkcs5_keyivgen]
*
* Internally calls EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, 0).
*/
@@ -278,20 +292,20 @@ ossl_cipher_decrypt(int argc, VALUE *argv, VALUE self)
/*
* call-seq:
- * cipher.pkcs5_keyivgen(pass [, salt [, iterations [, digest]]] ) -> nil
+ * cipher.pkcs5_keyivgen(pass, salt = nil, iterations = 2048, digest = "MD5") -> nil
*
* Generates and sets the key/IV based on a password.
*
- * WARNING: This method is only PKCS5 v1.5 compliant when using RC2, RC4-40,
+ * *WARNING*: This method is only PKCS5 v1.5 compliant when using RC2, RC4-40,
* or DES with MD5 or SHA1. Using anything else (like AES) will generate the
* key/iv using an OpenSSL specific method. This method is deprecated and
* should no longer be used. Use a PKCS5 v2 key generation method from
* OpenSSL::PKCS5 instead.
*
* === Parameters
- * +salt+ must be an 8 byte string if provided.
- * +iterations+ is a integer with a default of 2048.
- * +digest+ is a Digest object that defaults to 'MD5'
+ * * +salt+ must be an 8 byte string if provided.
+ * * +iterations+ is a integer with a default of 2048.
+ * * +digest+ is a Digest object that defaults to 'MD5'
*
* A minimum of 1000 iterations is recommended.
*
@@ -331,25 +345,23 @@ ossl_cipher_update_long(EVP_CIPHER_CTX *ctx, unsigned char *out, long *out_len_p
const unsigned char *in, long in_len)
{
int out_part_len;
+ int limit = INT_MAX / 2 + 1;
long out_len = 0;
-#define UPDATE_LENGTH_LIMIT INT_MAX
-
-#if SIZEOF_LONG > UPDATE_LENGTH_LIMIT
- if (in_len > UPDATE_LENGTH_LIMIT) {
- const int in_part_len = (UPDATE_LENGTH_LIMIT / 2 + 1) & ~1;
- do {
- if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0,
- &out_part_len, in, in_part_len))
- return 0;
- out_len += out_part_len;
- in += in_part_len;
- } while ((in_len -= in_part_len) > UPDATE_LENGTH_LIMIT);
- }
-#endif
- if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0,
- &out_part_len, in, (int)in_len))
- return 0;
- if (out_len_ptr) *out_len_ptr = out_len += out_part_len;
+
+ do {
+ int in_part_len = in_len > limit ? limit : (int)in_len;
+
+ if (!EVP_CipherUpdate(ctx, out ? (out + out_len) : 0,
+ &out_part_len, in, in_part_len))
+ return 0;
+
+ out_len += out_part_len;
+ in += in_part_len;
+ } while ((in_len -= limit) > 0);
+
+ if (out_len_ptr)
+ *out_len_ptr = out_len;
+
return 1;
}
@@ -362,9 +374,8 @@ ossl_cipher_update_long(EVP_CIPHER_CTX *ctx, unsigned char *out, long *out_len_p
* data chunk. When done, the output of Cipher#final should be additionally
* added to the result.
*
- * === Parameters
- * +data+ is a nonempty string.
- * +buffer+ is an optional string to store the result.
+ * If +buffer+ is given, the encryption/decryption result will be written to
+ * it. +buffer+ will be resized automatically.
*/
static VALUE
ossl_cipher_update(int argc, VALUE *argv, VALUE self)
@@ -499,12 +510,17 @@ static VALUE
ossl_cipher_set_iv(VALUE self, VALUE iv)
{
EVP_CIPHER_CTX *ctx;
- int iv_len;
+ int iv_len = 0;
StringValue(iv);
GetCipher(self, ctx);
- iv_len = EVP_CIPHER_CTX_iv_length(ctx);
+#if defined(HAVE_AUTHENTICATED_ENCRYPTION)
+ if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)
+ iv_len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
+#endif
+ if (!iv_len)
+ iv_len = EVP_CIPHER_CTX_iv_length(ctx);
if (RSTRING_LEN(iv) != iv_len)
ossl_raise(rb_eArgError, "iv must be %d bytes", iv_len);
@@ -562,8 +578,9 @@ ossl_cipher_set_auth_data(VALUE self, VALUE data)
* then set on the decryption cipher to authenticate the contents of the
* ciphertext against changes. If the optional integer parameter +tag_len+ is
* given, the returned tag will be +tag_len+ bytes long. If the parameter is
- * omitted, the maximum length of 16 bytes will be returned. For maximum
- * security, the default of 16 bytes should be chosen.
+ * omitted, the default length of 16 bytes or the length previously set by
+ * #auth_tag_len= will be used. For maximum security, the longest possible
+ * should be chosen.
*
* The tag may only be retrieved after calling Cipher#final.
*/
@@ -574,7 +591,10 @@ ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self)
EVP_CIPHER_CTX *ctx;
int tag_len = 16;
- if (rb_scan_args(argc, argv, "01", &vtag_len) == 1)
+ rb_scan_args(argc, argv, "01", &vtag_len);
+ if (NIL_P(vtag_len))
+ vtag_len = rb_attr_get(self, id_auth_tag_len);
+ if (!NIL_P(vtag_len))
tag_len = NUM2INT(vtag_len);
GetCipher(self, ctx);
@@ -600,6 +620,9 @@ ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self)
* decrypting any of the ciphertext. After all decryption is
* performed, the tag is verified automatically in the call to
* Cipher#final.
+ *
+ * For OCB mode, the tag length must be supplied with #auth_tag_len=
+ * beforehand.
*/
static VALUE
ossl_cipher_set_auth_tag(VALUE self, VALUE vtag)
@@ -624,6 +647,36 @@ ossl_cipher_set_auth_tag(VALUE self, VALUE vtag)
/*
* call-seq:
+ * cipher.auth_tag_len = Integer -> Integer
+ *
+ * Sets the length of the authentication tag to be generated or to be given for
+ * AEAD ciphers that requires it as in input parameter. Note that not all AEAD
+ * ciphers support this method.
+ *
+ * In OCB mode, the length must be supplied both when encrypting and when
+ * decrypting, and must be before specifying an IV.
+ */
+static VALUE
+ossl_cipher_set_auth_tag_len(VALUE self, VALUE vlen)
+{
+ int tag_len = NUM2INT(vlen);
+ EVP_CIPHER_CTX *ctx;
+
+ GetCipher(self, ctx);
+ if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER))
+ ossl_raise(eCipherError, "AEAD not supported by this cipher");
+
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_len, NULL))
+ ossl_raise(eCipherError, "unable to set authentication tag length");
+
+ /* for #auth_tag */
+ rb_ivar_set(self, id_auth_tag_len, INT2NUM(tag_len));
+
+ return vlen;
+}
+
+/*
+ * call-seq:
* cipher.authenticated? -> boolean
*
* Indicated whether this Cipher instance uses an Authenticated Encryption
@@ -638,11 +691,43 @@ ossl_cipher_is_authenticated(VALUE self)
return (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER) ? Qtrue : Qfalse;
}
+
+/*
+ * call-seq:
+ * cipher.iv_len = integer -> integer
+ *
+ * Sets the IV/nonce length of the Cipher. Normally block ciphers don't allow
+ * changing the IV length, but some make use of IV for 'nonce'. You may need
+ * this for interoperability with other applications.
+ */
+static VALUE
+ossl_cipher_set_iv_length(VALUE self, VALUE iv_length)
+{
+ int len = NUM2INT(iv_length);
+ EVP_CIPHER_CTX *ctx;
+
+ GetCipher(self, ctx);
+ if (!(EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER))
+ ossl_raise(eCipherError, "cipher does not support AEAD");
+
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, len, NULL))
+ ossl_raise(eCipherError, "unable to set IV length");
+
+ /*
+ * EVP_CIPHER_CTX_iv_length() returns the default length. So we need to save
+ * the length somewhere. Luckily currently we aren't using app_data.
+ */
+ EVP_CIPHER_CTX_set_app_data(ctx, (void *)(VALUE)len);
+
+ return iv_length;
+}
#else
#define ossl_cipher_set_auth_data rb_f_notimplement
#define ossl_cipher_get_auth_tag rb_f_notimplement
#define ossl_cipher_set_auth_tag rb_f_notimplement
+#define ossl_cipher_set_auth_tag_len rb_f_notimplement
#define ossl_cipher_is_authenticated rb_f_notimplement
+#define ossl_cipher_set_iv_length rb_f_notimplement
#endif
/*
@@ -692,36 +777,60 @@ ossl_cipher_set_padding(VALUE self, VALUE padding)
return padding;
}
-#define CIPHER_0ARG_INT(func) \
- static VALUE \
- ossl_cipher_##func(VALUE self) \
- { \
- EVP_CIPHER_CTX *ctx; \
- GetCipher(self, ctx); \
- return INT2NUM(EVP_CIPHER_##func(EVP_CIPHER_CTX_cipher(ctx))); \
- }
-
/*
* call-seq:
* cipher.key_len -> integer
*
* Returns the key length in bytes of the Cipher.
*/
-CIPHER_0ARG_INT(key_length)
+static VALUE
+ossl_cipher_key_length(VALUE self)
+{
+ EVP_CIPHER_CTX *ctx;
+
+ GetCipher(self, ctx);
+
+ return INT2NUM(EVP_CIPHER_CTX_key_length(ctx));
+}
+
/*
* call-seq:
* cipher.iv_len -> integer
*
* Returns the expected length in bytes for an IV for this Cipher.
*/
-CIPHER_0ARG_INT(iv_length)
+static VALUE
+ossl_cipher_iv_length(VALUE self)
+{
+ EVP_CIPHER_CTX *ctx;
+ int len = 0;
+
+ GetCipher(self, ctx);
+#if defined(HAVE_AUTHENTICATED_ENCRYPTION)
+ if (EVP_CIPHER_CTX_flags(ctx) & EVP_CIPH_FLAG_AEAD_CIPHER)
+ len = (int)(VALUE)EVP_CIPHER_CTX_get_app_data(ctx);
+#endif
+ if (!len)
+ len = EVP_CIPHER_CTX_iv_length(ctx);
+
+ return INT2NUM(len);
+}
+
/*
* call-seq:
* cipher.block_size -> integer
*
* Returns the size in bytes of the blocks on which this Cipher operates on.
*/
-CIPHER_0ARG_INT(block_size)
+static VALUE
+ossl_cipher_block_size(VALUE self)
+{
+ EVP_CIPHER_CTX *ctx;
+
+ GetCipher(self, ctx);
+
+ return INT2NUM(EVP_CIPHER_CTX_block_size(ctx));
+}
/*
* INIT
@@ -730,7 +839,8 @@ void
Init_ossl_cipher(void)
{
#if 0
- mOSSL = rb_define_module("OpenSSL"); /* let rdoc know about mOSSL */
+ mOSSL = rb_define_module("OpenSSL");
+ eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
#endif
/* Document-class: OpenSSL::Cipher
@@ -843,16 +953,16 @@ Init_ossl_cipher(void)
* of the IV as a nonce (number used once) - it's public but random and
* unpredictable. A secure random IV can be created as follows
*
- * cipher = ...
- * cipher.encrypt
- * key = cipher.random_key
- * iv = cipher.random_iv # also sets the generated IV on the Cipher
+ * cipher = ...
+ * cipher.encrypt
+ * key = cipher.random_key
+ * iv = cipher.random_iv # also sets the generated IV on the Cipher
*
- * Although the key is generally a random value, too, it is a bad choice
- * as an IV. There are elaborate ways how an attacker can take advantage
- * of such an IV. As a general rule of thumb, exposing the key directly
- * or indirectly should be avoided at all cost and exceptions only be
- * made with good reason.
+ * Although the key is generally a random value, too, it is a bad choice
+ * as an IV. There are elaborate ways how an attacker can take advantage
+ * of such an IV. As a general rule of thumb, exposing the key directly
+ * or indirectly should be avoided at all cost and exceptions only be
+ * made with good reason.
*
* === Calling Cipher#final
*
@@ -906,29 +1016,42 @@ Init_ossl_cipher(void)
* could otherwise be exploited to modify ciphertexts in ways beneficial to
* potential attackers.
*
- * If no associated data is needed for encryption and later decryption,
- * the OpenSSL library still requires a value to be set - "" may be used in
- * case none is available. An example using the GCM (Galois Counter Mode):
+ * An associated data is used where there is additional information, such as
+ * headers or some metadata, that must be also authenticated but not
+ * necessarily need to be encrypted. If no associated data is needed for
+ * encryption and later decryption, the OpenSSL library still requires a
+ * value to be set - "" may be used in case none is available.
*
- * cipher = OpenSSL::Cipher.new("aes-128-gcm")
- * cipher.encrypt
- * key = cipher.random_key
- * iv = cipher.random_iv
- * cipher.auth_data = ""
+ * An example using the GCM (Galois/Counter Mode). You have 16 bytes +key+,
+ * 12 bytes (96 bits) +nonce+ and the associated data +auth_data+. Be sure
+ * not to reuse the +key+ and +nonce+ pair. Reusing an nonce ruins the
+ * security gurantees of GCM mode.
+ *
+ * cipher = OpenSSL::Cipher::AES.new(128, :GCM).encrypt
+ * cipher.key = key
+ * cipher.iv = nonce
+ * cipher.auth_data = auth_data
*
* encrypted = cipher.update(data) + cipher.final
- * tag = cipher.auth_tag
+ * tag = cipher.auth_tag # produces 16 bytes tag by default
*
- * decipher = OpenSSL::Cipher.new("aes-128-gcm")
- * decipher.decrypt
+ * Now you are the receiver. You know the +key+ and have received +nonce+,
+ * +auth_data+, +encrypted+ and +tag+ through an untrusted network. Note
+ * that GCM accepts an arbitrary length tag between 1 and 16 bytes. You may
+ * additionally need to check that the received tag has the correct length,
+ * or you allow attackers to forge a valid single byte tag for the tampered
+ * ciphertext with a probability of 1/256.
+ *
+ * raise "tag is truncated!" unless tag.bytesize == 16
+ * decipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
* decipher.key = key
- * decipher.iv = iv
+ * decipher.iv = nonce
* decipher.auth_tag = tag
- * decipher.auth_data = ""
+ * decipher.auth_data = auth_data
*
- * plain = decipher.update(encrypted) + decipher.final
+ * decrypted = decipher.update(encrypted) + decipher.final
*
- * puts data == plain #=> true
+ * puts data == decrypted #=> true
*/
cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError);
@@ -948,11 +1071,15 @@ Init_ossl_cipher(void)
rb_define_method(cCipher, "auth_data=", ossl_cipher_set_auth_data, 1);
rb_define_method(cCipher, "auth_tag=", ossl_cipher_set_auth_tag, 1);
rb_define_method(cCipher, "auth_tag", ossl_cipher_get_auth_tag, -1);
+ rb_define_method(cCipher, "auth_tag_len=", ossl_cipher_set_auth_tag_len, 1);
rb_define_method(cCipher, "authenticated?", ossl_cipher_is_authenticated, 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, "iv_len=", ossl_cipher_set_iv_length, 1);
rb_define_method(cCipher, "iv_len", ossl_cipher_iv_length, 0);
rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0);
rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1);
+
+ id_auth_tag_len = rb_intern_const("auth_tag_len");
}