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.c217
1 files changed, 213 insertions, 4 deletions
diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c
index 26851515cd..03e89fe420 100644
--- a/ext/openssl/ossl_cipher.c
+++ b/ext/openssl/ossl_cipher.c
@@ -329,7 +329,6 @@ ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self)
return Qnil;
}
-
/*
* call-seq:
* cipher.update(data [, buffer]) -> string or buffer
@@ -379,10 +378,15 @@ ossl_cipher_update(int argc, VALUE *argv, VALUE self)
* call-seq:
* cipher.final -> string
*
- * Returns the remaining data held in the cipher object. Further calls to
- * Cipher#update or Cipher#final will return garbage.
+ * Returns the remaining data held in the cipher object. Further calls to
+ * Cipher#update or Cipher#final will return garbage. This call should always
+ * be made as the last call of an encryption or decryption operation, after
+ * after having fed the entire plaintext or ciphertext to the Cipher instance.
*
- * See EVP_CipherFinal_ex for further information.
+ * If an authenticated cipher was used, a CipherError is raised if the tag
+ * could not be authenticated successfully. Only call this method after
+ * setting the authentication tag and passing the entire contents of the
+ * ciphertext into the cipher.
*/
static VALUE
ossl_cipher_final(VALUE self)
@@ -478,6 +482,168 @@ ossl_cipher_set_iv(VALUE self, VALUE iv)
return iv;
}
+/*
+ * call-seq:
+ * cipher.auth_data = string -> string
+ *
+ * Sets the cipher's additional authenticated data. This field must be
+ * set when using AEAD cipher modes such as GCM or CCM. If no associated
+ * data shall be used, this method must *still* be called with a value of "".
+ * 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. In encryption mode,
+ * it must be set after calling Cipher#encrypt and setting Cipher#key= and
+ * Cipher#iv=. When decrypting, the authenticated data must be set after key,
+ * iv and especially *after* the authentication tag has been set. I.e. set it
+ * only after calling Cipher#decrypt, Cipher#key=, Cipher#iv= and
+ * Cipher#auth_tag= first.
+ */
+static VALUE
+ossl_cipher_set_auth_data(VALUE self, VALUE data)
+{
+ EVP_CIPHER_CTX *ctx;
+ unsigned char *in;
+ int in_len;
+ int out_len;
+
+ 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 data;
+}
+
+#define ossl_is_gcm(nid) (nid) == NID_aes_128_gcm || \
+ (nid) == NID_aes_192_gcm || \
+ (nid) == NID_aes_256_gcm
+
+static VALUE
+ossl_get_gcm_auth_tag(EVP_CIPHER_CTX *ctx, int len)
+{
+ unsigned char *tag;
+ VALUE ret;
+
+ tag = ALLOC_N(unsigned char, len);
+
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, len, tag))
+ ossl_raise(eCipherError, "retrieving the authentication tag failed");
+
+ ret = rb_str_new((const char *) tag, len);
+ xfree(tag);
+ return ret;
+}
+
+/*
+ * call-seq:
+ * cipher.auth_tag([ tag_len ] -> string
+ *
+ * Gets the authentication tag generated by Authenticated Encryption Cipher
+ * modes (GCM for example). This tag may be stored along with the ciphertext,
+ * 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.
+ *
+ * The tag may only be retrieved after calling Cipher#final.
+ */
+static VALUE
+ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self)
+{
+ VALUE vtag_len;
+ EVP_CIPHER_CTX *ctx;
+ int nid, tag_len;
+
+ if (rb_scan_args(argc, argv, "01", &vtag_len) == 0) {
+ tag_len = 16;
+ } else {
+ tag_len = NUM2INT(vtag_len);
+ }
+
+ GetCipher(self, ctx);
+ nid = EVP_CIPHER_CTX_nid(ctx);
+
+ if (ossl_is_gcm(nid)) {
+ return ossl_get_gcm_auth_tag(ctx, tag_len);
+ } else {
+ ossl_raise(eCipherError, "authentication tag not supported by this cipher");
+ return Qnil; /* dummy */
+ }
+}
+
+static inline void
+ossl_set_gcm_auth_tag(EVP_CIPHER_CTX *ctx, unsigned char *tag, int tag_len)
+{
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag))
+ ossl_raise(eCipherError, "unable to set GCM tag");
+}
+
+/*
+ * call-seq:
+ * cipher.auth_tag = string -> string
+ *
+ * Sets the authentication tag to verify the contents of the
+ * ciphertext. The tag must be set after calling Cipher#decrypt,
+ * Cipher#key= and Cipher#iv=, but before assigning the associated
+ * authenticated data using Cipher#auth_data= and of course, before
+ * decrypting any of the ciphertext. After all decryption is
+ * performed, the tag is verified automatically in the call to
+ * Cipher#final.
+ */
+static VALUE
+ossl_cipher_set_auth_tag(VALUE self, VALUE vtag)
+{
+ EVP_CIPHER_CTX *ctx;
+ int nid;
+ unsigned char *tag;
+ int tag_len;
+
+ StringValue(vtag);
+ tag = (unsigned char *) RSTRING_PTR(vtag);
+ tag_len = RSTRING_LENINT(vtag);
+
+ GetCipher(self, ctx);
+ nid = EVP_CIPHER_CTX_nid(ctx);
+
+ if (ossl_is_gcm(nid)) {
+ ossl_set_gcm_auth_tag(ctx, tag, tag_len);
+ } else {
+ ossl_raise(eCipherError, "authentication tag not supported by this cipher");
+ }
+
+ return vtag;
+}
+
+/*
+ * call-seq:
+ * cipher.authenticated? -> boolean
+ *
+ * Indicated whether this Cipher instance uses an Authenticated Encryption
+ * mode.
+ */
+static VALUE
+ossl_cipher_is_authenticated(VALUE self)
+{
+ EVP_CIPHER_CTX *ctx;
+ int nid;
+
+ GetCipher(self, ctx);
+ nid = EVP_CIPHER_CTX_nid(ctx);
+
+ if (ossl_is_gcm(nid)) {
+ return Qtrue;
+ } else {
+ return Qfalse;
+ }
+}
/*
* call-seq:
@@ -728,6 +894,45 @@ Init_ossl_cipher(void)
*
* puts data == plain #=> true
*
+ * === Authenticated Encryption and Associated Data (AEAD)
+ *
+ * If the OpenSSL version used supports it, an Authenticated Encryption
+ * mode (such as GCM or CCM) should always be preferred over any
+ * unauthenticated mode. Currently, OpenSSL supports AE only in combination
+ * with Associated Data (AEAD) where additional associated data is included
+ * in the encryption process to compute a tag at the end of the encryption.
+ * This tag will also be used in the decryption process and by verifying
+ * its validity, the authenticity of a given ciphertext is established.
+ *
+ * This is superior to unauthenticated modes in that it allows to detect
+ * if somebody effectively changed the ciphertext after it had been
+ * encrypted. This prevents malicious modifications of the ciphertext that
+ * 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):
+ *
+ * cipher = OpenSSL::Cipher::AES.new(128, :GCM)
+ * cipher.encrypt
+ * key = cipher.random_key
+ * iv = cipher.random_iv
+ * cipher.auth_data = ""
+ *
+ * encrypted = cipher.update(data) + cipher.final
+ * tag = cipher.auth_tag
+ *
+ * decipher = OpenSSL::Cipher::AES.new(128, :GCM)
+ * decipher.decrypt
+ * decipher.key = key
+ * decipher.iv = iv
+ * decipher.auth_tag = tag
+ * decipher.auth_data = ""
+ *
+ * plain = decipher.update(encrypted) + decipher.final
+ *
+ * puts data == plain #=> true
*/
cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError);
@@ -744,6 +949,10 @@ Init_ossl_cipher(void)
rb_define_method(cCipher, "final", ossl_cipher_final, 0);
rb_define_method(cCipher, "name", ossl_cipher_name, 0);
rb_define_method(cCipher, "key=", ossl_cipher_set_key, 1);
+ 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, "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);