From bb3816953b0ed728af11c1b75e02ff10d70132ec Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 30 Mar 2020 14:42:53 -0400 Subject: Define Cipher #ccm_data_len= for CCM mode ciphers Allow specifying just length to #update CCM mode ciphers need to specify the total plaintext or ciphertext length to EVP_CipherUpdate. Update the link to the tests file Define Cipher#ccm_data_len= for CCM mode ciphers Add a unit test for CCM mode Also check CCM is authenticated when testing --- CONTRIBUTING.md | 2 +- ext/openssl/ossl_cipher.c | 26 ++++++++++++++++++++++++++ test/openssl/test_cipher.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 89f7e1d2..9f246bbc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,7 +80,7 @@ $ docker-compose run debug ``` All possible values for `RUBY_VERSION` and `OPENSSL_VERSION` can be found in -[`.travis.yml`](https://github.com/ruby/openssl/tree/master/.travis.yml). +[`test.yml`](https://github.com/ruby/openssl/tree/master/.github/workflows/test.yml). **NOTE**: these commands must be run from the openssl repository root, in order to use the diff --git a/ext/openssl/ossl_cipher.c b/ext/openssl/ossl_cipher.c index 66bf0beb..2c981ee2 100644 --- a/ext/openssl/ossl_cipher.c +++ b/ext/openssl/ossl_cipher.c @@ -813,6 +813,31 @@ ossl_cipher_block_size(VALUE self) return INT2NUM(EVP_CIPHER_CTX_block_size(ctx)); } +/* + * call-seq: + * cipher.ccm_data_len = integer -> integer + * + * Sets the length of the plaintext / ciphertext message that will be + * processed in CCM mode. Make sure to call this method after #key= and + * #iv= have been set, and before #auth_data=. + * + * Only call this method after calling Cipher#encrypt or Cipher#decrypt. + */ +static VALUE +ossl_cipher_set_ccm_data_len(VALUE self, VALUE data_len) +{ + int in_len, out_len; + EVP_CIPHER_CTX *ctx; + + in_len = NUM2INT(data_len); + + GetCipher(self, ctx); + if (EVP_CipherUpdate(ctx, NULL, &out_len, NULL, in_len) != 1) + ossl_raise(eCipherError, NULL); + + return data_len; +} + /* * INIT */ @@ -1059,6 +1084,7 @@ Init_ossl_cipher(void) 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); + rb_define_method(cCipher, "ccm_data_len=", ossl_cipher_set_ccm_data_len, 1); id_auth_tag_len = rb_intern_const("auth_tag_len"); id_key_set = rb_intern_const("key_set"); diff --git a/test/openssl/test_cipher.rb b/test/openssl/test_cipher.rb index f6ec4980..4b3d9a3a 100644 --- a/test/openssl/test_cipher.rb +++ b/test/openssl/test_cipher.rb @@ -174,6 +174,48 @@ class OpenSSL::TestCipher < OpenSSL::TestCase assert_not_predicate(cipher, :authenticated?) end + def test_aes_ccm + # RFC 3610 Section 8, Test Case 1 + key = ["c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"].pack("H*") + iv = ["00000003020100a0a1a2a3a4a5"].pack("H*") + aad = ["0001020304050607"].pack("H*") + pt = ["08090a0b0c0d0e0f101112131415161718191a1b1c1d1e"].pack("H*") + ct = ["588c979a61c663d2f066d0c2c0f989806d5f6b61dac384"].pack("H*") + tag = ["17e8d12cfdf926e0"].pack("H*") + + kwargs = {auth_tag_len: 8, iv_len: 13, key: key, iv: iv} + cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, auth_data: aad) + assert_equal ct, cipher.update(pt) << cipher.final + assert_equal tag, cipher.auth_tag + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad) + assert_equal pt, cipher.update(ct) << cipher.final + + # truncated tag is accepted + cipher = new_encryptor("aes-128-ccm", **kwargs, ccm_data_len: pt.length, auth_data: aad) + assert_equal ct, cipher.update(pt) << cipher.final + assert_equal tag[0, 8], cipher.auth_tag(8) + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag[0, 8], auth_data: aad) + assert_equal pt, cipher.update(ct) << cipher.final + + # wrong tag is rejected + tag2 = tag.dup + tag2.setbyte(-1, (tag2.getbyte(-1) + 1) & 0xff) + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag2, auth_data: aad) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) } + + # wrong aad is rejected + aad2 = aad[0..-2] << aad[-1].succ + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct.length, auth_tag: tag, auth_data: aad2) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct) } + + # wrong ciphertext is rejected + ct2 = ct[0..-2] << ct[-1].succ + cipher = new_decryptor("aes-128-ccm", **kwargs, ccm_data_len: ct2.length, auth_tag: tag, auth_data: aad) + assert_raise(OpenSSL::Cipher::CipherError) { cipher.update(ct2) } + end if has_cipher?("aes-128-ccm") && + OpenSSL::Cipher.new("aes-128-ccm").authenticated? && + OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10101000 # version >= v1.1.1 + def test_aes_gcm # GCM spec Appendix B Test Case 4 key = ["feffe9928665731c6d6a8f9467308308"].pack("H*") -- cgit v1.2.3