From db44f3e7b6779e8238a5d801c8f35041ad9baabb Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Wed, 29 Jun 2022 13:22:18 +0100 Subject: Add support for getting/setting GCM authentication tag (fixes #115) --- doc/luaossl.pdf | Bin 182982 -> 333327 bytes doc/luaossl.tex | 8 ++++++++ src/openssl.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/doc/luaossl.pdf b/doc/luaossl.pdf index b7a09dc..b8a0e27 100644 Binary files a/doc/luaossl.pdf and b/doc/luaossl.pdf differ diff --git a/doc/luaossl.tex b/doc/luaossl.tex index efc6422..df5626a 100644 --- a/doc/luaossl.tex +++ b/doc/luaossl.tex @@ -1485,6 +1485,14 @@ Update the cipher instance with the specified string(s). Returns a string on suc Update the cipher with the specified string(s). Returns the final output string on success, or nil and an error message on failure. The returned string may be empty if all blocks have already been flushed in prior \fn{:update} calls. +\subsubsection[\fn{cipher:getTag}]{\fn{cipher:getTag($len$)}} + +Returns the authentication tag for the ciphertext (GCM ciphers only) as a binary string. This method can only be called when encrypting data, and must be called after all data has been processed (i.e. after calling \fn{:final()}). + +\subsubsection[\fn{cipher:setTag}]{\fn{cipher:setTag($tag$)}} + +Sets the provided binary string as the expected authentication tag for the forthcoming ciphertext (GCM ciphers only). This method can only be called when decrypting data, and must be called before \fn{:final()} to ensure the ciphertext integrity can be verified successfully. + \end{Module} diff --git a/src/openssl.c b/src/openssl.c index b56c78a..b30635b 100644 --- a/src/openssl.c +++ b/src/openssl.c @@ -11975,6 +11975,53 @@ sslerr: } /* cipher_final() */ +static int cipher_get_tag(lua_State *L) { + EVP_CIPHER_CTX *ctx = checksimple(L, 1, CIPHER_CLASS); + luaL_Buffer tag; + size_t tag_size = luaL_checkinteger(L, 2); + + luaL_buffinit(L, &tag); + + /* EVP_CTRL_GCM_GET_TAG is works for both GCM and CCM and across all + * supported OpenSSL versions. We can switch to the unified identifier + * 'EVP_CTRL_AEAD_GET_TAG' in OpenSSL 1.1+. + */ + if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, tag_size, (void*)luaL_prepbuffsize(&tag, tag_size))) { + goto sslerr; + } + + luaL_pushresultsize(&tag, tag_size); + + return 1; + +sslerr: + lua_pushnil(L); + auxL_pusherror(L, auxL_EOPENSSL, NULL); + + return 2; +} /* cipher_get_tag() */ + + +static int cipher_set_tag(lua_State *L) { + EVP_CIPHER_CTX *ctx = checksimple(L, 1, CIPHER_CLASS); + size_t tag_size; + const char* tag = luaL_checklstring(L, 2, &tag_size); + if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_size, (void*)tag)) { + goto sslerr; + } + + lua_pushlstring(L, tag, tag_size); + + return 1; + +sslerr: + lua_pushnil(L); + auxL_pusherror(L, auxL_EOPENSSL, NULL); + + return 2; +} /* cipher_set_tag() */ + + static int cipher__gc(lua_State *L) { EVP_CIPHER_CTX **ctx = luaL_checkudata(L, 1, CIPHER_CLASS); @@ -11990,6 +12037,8 @@ static const auxL_Reg cipher_methods[] = { { "decrypt", &cipher_decrypt }, { "update", &cipher_update }, { "final", &cipher_final }, + { "getTag", &cipher_get_tag }, + { "setTag", &cipher_set_tag }, { NULL, NULL }, }; -- cgit v1.2.3-59-g8ed1b From 2922f436c562cf4dc716fc24816784b71da27403 Mon Sep 17 00:00:00 2001 From: Matthew Wild Date: Thu, 30 Jun 2022 19:41:34 +0100 Subject: Add regression test for #115 --- regress/115-test-aead.lua | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 regress/115-test-aead.lua diff --git a/regress/115-test-aead.lua b/regress/115-test-aead.lua new file mode 100644 index 0000000..ed0f7f8 --- /dev/null +++ b/regress/115-test-aead.lua @@ -0,0 +1,39 @@ +local regress = require "regress"; +local openssl = require "openssl"; +local cipher = require "openssl.cipher" + + +-- Test AES-256-GCM +local key = "abcdefghijklmnopabcdefghijklmnop" +local iv = "123456123456" +local message = "My secret message" + +function test_aead(params) + local c = cipher.new(params.cipher):encrypt(key, iv) + + local encrypted = c:update(message) + regress.check(encrypted) + regress.check(c:final(), "fail final encrypt") + + local tag = assert(c:getTag(params.tag_length)) + regress.check(tag and #tag == params.tag_length) + + + -- Now for the decryption + local d = cipher.new(params.cipher):decrypt(key, iv) + d:setTag(tag); + + local decrypted = d:update(encrypted) + regress.check(decrypted == message, "decrypted message doesn't match") + regress.check(d:final(), "fail final decrypt") +end + +test_aead { + cipher = "aes-256-gcm"; + tag_length = 16; +} + +test_aead { + cipher = "aes-256-ccm"; + tag_length = 12; +} -- cgit v1.2.3-59-g8ed1b