Skip to content

Commit

Permalink
Added JWS+JWE decrypt plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
balvisio authored and Bruno Alvisio committed Nov 21, 2020
1 parent 4a520e8 commit 0646731
Showing 1 changed file with 209 additions and 0 deletions.
209 changes: 209 additions & 0 deletions jws-jwe-decrypt/plugin.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
-- Name:JWS+JWE Decrypt
-- Version: 1.0
-- Description:## Short Description
-- Generates the user's payload from a **JSON Web Encryption (JWE)** .
--
-- ### ## Introduction
-- This plugin performs decrypt using JWE standards: `enc: A256CBC-HS512 alg: RSA-OAEP-256`.
--
-- It performs the following steps:
--
-- 1. It takes the JWE as input and splits it to get the parts i.e. encrypted key material, iv, ciphertext, and tag.
-- 2. Decrypts the encrypted composite transient key using the RSA private key (provided in input), and generate AES, HMAC transient keys from the decrypted key-material.
-- 3. Computes the `aad` from the header in the input and `al` to store the size of `aad`.
-- 4. Using `iv` from the input, and the generated AES key, it decrypts the cipher provided in the input in `CBC` mode. We receive the plaintext after decryption, which is correct only after the verification is successful.
-- 5. For verification, it creates an input payload for HMAC consisting of `aad, iv, cipher, al`.
-- 6. Creates a HMAC of the payload created above using HMAC key using `SHA-512` as the hashing algorithm.
-- 7. It truncates the digest generated above to half the length and compares it to the tag in the input.
-- 8. If the generated digest matches the tag, then it verifies the plaintext which is a JWS.
-- 9. It takes the JWS and splits it to get the parts i.e. header, payload, and signature.
-- 10. From the header and payload, it re-constructs the `Jws Signing input`.
-- 11. Decodes the signature from Base64URL to Base64, so as to use for verifying the signature.
-- 12. Imports the `certificate` as a transient key.
-- 13. Verifies the above constructed `Jws Signing input` and the decoded `signature` by `certificate` using `SHA-256` and mode as `PKCS1-v1_5`.
-- 14. The plugin output is `VERIFIED` and the actual `payload` (inside the JWS) in case signature is correctly verified and `VERIFICATION FAILED` otherwise.
--
-- ## Use cases
--
-- 1. Assert one’s identity, given that the recipient of the JWE trusts the asserting party.
-- 2. Transfer data securely between interested parties over a unsecured channel.
--
-- ## Setup
--
-- 1. For these plugin, we need a RSA private key already imported in SDKMS, and its corresponding public key as a certificate which the user should provide as input.
--
-- ## Input/Output JSON object format
--
-- Input parameters details:
--
-- 1. **`jwe`** corresponds to JWE generated by `Encrypt` plugin.
-- 2. **`key`** is the name of `RSA` private key which should be already imported in `SDKMS`. This is used for decrypting the payload.
-- 3. **`cert`** contains the contents of the certificate (`pem` file) in base64 encoding. This is used to verify the signature.
--
-- ## Example usages
--
-- Sample Input format: (The certificate value should be provided as a base-64 encoded string).
-- ```
-- {
-- "jwe": "...",
-- "key" : "keyname",
-- "cert" : "..."
-- }
-- ```
--
-- Sample output format:
-- ```
-- {
-- "payload": "...",
-- "output": "VERIFIED"
-- }
-- ```
--
-- ## References
--
-- 1. https://tools.ietf.org/html/rfc7515
-- 2. https://tools.ietf.org/html/rfc7516
--
-- ### Release Notes
-- - Initial release

--[[
Sample Input format:
{
"jwe": "fqww_I3raNvSU5HGERcKr0P4sHJeDOvOD5Zy39L2Ic4cDLt_oQFx-aZAVGIe2QzWiXFTmyHyb5PyeiITftsDdntVDPP8P0kWkjl8MfcMXs1ttbDvNa_pDN0oA7aCkG-X9L_5zx2ck_vPNHc4-Z5z3POmlkrJXPZXXknBY5bb2YHuIby-Mbj26FpK8wp3aZrkoGfF0gei38imk7JDV2p5xfMwzzdL4k16d-2RJxl8u-HEOA-1c9_KQcjSKLKC_Ji0J7N5MURvr0wz4DNbg1_Ivk4nQvmRQwfVEiZ3ewzuzFIFO5dX9GP_ERkoOCFw9OiBxPROym683UVgMbxot2Uwjg.YYjBKq2IgvTk1Erg5Rdiyw.xQSPnRY_xMqZB8endzsatxPuSE1o9r_9-CwaUoJK6nVfmaoj4OPdnYoVN4H-yk_g5TqYzCfZxDUGHfXqG_xZgp2EAC7GKVrNoSSPzuHAlfWoyZ_o9qtIbAz_53U4DmgpVMXUugYy6k_27IJWbcLM-CWROpZtpBTgNaP-E3674MSlUBUp1MJObyZiDexB-OFgdbb_-J1KJu20Fqh-6mIAsrSq-mDHVeUshwrrQQ91CG4Jf6CPIgOw2crY4TUJKk7PpqJ0YosooItV7pFEbPuYttLtEXlONIcyhe6XZsviCPu2R7SPSih58BY-9ybEiCEQ2Z8PLkqAZfdJvR6b_JpeaaTjw9Z128NCej9s6tQde94tnm0rkhwO38AUYZM-BR0mQfHO5lMDDE35fbLssZUwPxWvw83Y7lhVydO69FaiP5nb37wS6AnhabibvxXIu4o_w1n2wn8n-loEWLqcJmbpmPG30UEwD3mT5XJh5VrdYBJweS-mQjZpAmDjvjxmIUnanpeGfLpV404-1z5h4PSBWg.9eakMXCsoryqHozXxuKNlsjyEeYsdCTzgy_nrZJBbp4",
"key" : "rsakey",
"cert" : "MIIDRTCCAi2gAwIBAgIJANWt2yo+EcuAMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDTALBgNVBAMMBERFTU8wHhcNMTkxMDEwMjExNDQ4WhcNMTkxMTA5MjExNDQ4WjBUMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ0wCwYDVQQDDARERU1PMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsNkNXpiQfvOBSTBRvvMoJBWIL7ucyC3LBXX4kybqLQHeI/FcrbfuhQ+PCY67A7cMjhUCRVvv/NL3I1o7JeBraFnJC1R8+RZ8aD3SqweusOOxFFibMgveaQRQIh7C9Dc9sdn7wgnrBCP6kDk8vTwYylUd5v0NBXZcuAC89dHjYLMQCVGQ0s5qTFxUBhs0MXjKgIZm/0gqkbqln78NYsNuIVBt8TBEYTbpBC4CAtJzuNcOKpRfsYyGk/HAG8kiaGnQcRoa3fZZBXGZbjvn7q7qRd9l/Okvov4MqvAOjxErh4v+2smDp/RQhxG1khksYjVU1U5168of8XkveFvm/Q6dVwIDAQABoxowGDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DANBgkqhkiG9w0BAQsFAAOCAQEAm4WT2Phx+t00pfKRy3AByZyewqTwHzyuY3UCYGi+vJ4l/YmrPrKI2FUU/QQe5IYb8b07+eV54vpqGcdcif0u7eRcjYGnTz6vxQPsUnDj8SVgjDihGxJGb+f6BonBT8IHQM+JXKWjbWaPK7aWMjsbIL1X+IrY1Anx+GX99AmuYskC8Tq0JpIuRSrNnJoC1zA5+U7saateh3iSBHdSqQVCSjcKwblmxEz5e6lQ8OPQzpnf7iblMbvdDyTWTSIEZ5JO3C+byQ98OheW6mrJupw8rPXdDdQClzn2ZhoNxhWrQsWcvfIXJAo/xbeVbEzJrnqbTcDFR/fwiSR6hgH4mUUo1Q=="
}
]]--

function base64url(str)
local blob = Blob.from_bytes(str)
local base64 = blob:base64()
local url = base64:gsub("+", "-")
url = url:gsub("/", "_")
url = url:gsub("=", "")
return url
end

-- enc: A256CBC-HS512 alg: RSA-OAEP-256
function generateHeader()
return {alg = "RSA-OAEP-256", enc = "A256CBC-HS512", typ = "JWT"}
end

function generateAD(header)
local object = "\"alg\":"..header.alg..",\"typ\":"..header.typ
local base64string = base64url(object)
return Blob.from_bytes(base64string):base64()
end

function truncateDigest(digest)
local num = #digest
return digest:slice(1, num/2)
end

function transformTo64(input)
local converted = input:gsub("-", "+")
converted = converted:gsub("_", "/")
local length = converted:len() % 4
local base64string = converted
if length == 0 then
base64string = converted
elseif length == 1 then
base64string = converted .. "==="
elseif length == 2 then
base64string = converted .. "=="
else
base64string = converted .. "="
end
return base64string
end

function computeLength(aad)
local num = #aad
local bitlength = num * 8
a = {0,0,0,0,0,0,0,0}
for i = 8, 1, -1 do
a[i] = bitlength % 256
bitlength = bitlength // 256
end
local s = ""
for i = 1, 8 do
s = s.. string.char(a[i])
end
return s
end

function splits(s)
encrypted_key, iv, cipher, tag = s:match"([^.]*).([^.]*).([^.]*).(.*)"
if encrypted_key == "" or iv == "" or cipher == "" or tag == "" then
return 0
end
return {encrypted_key = transformTo64(encrypted_key), iv = transformTo64(iv), cipher = transformTo64(cipher), tag = tag}
end

function splitJws(s)
jws_header, jws_payload, jws_signature_value = s:match"([^.]*).([^.]*).(.*)"
return {header = jws_header, payload = jws_payload, sign = jws_signature_value}
end

function verifyJws(input, jws)
parts = splitJws(jws)
jws_header = parts.header
jws_payload = parts.payload
jws_signature_value = parts.sign
local signature = transformTo64(jws_signature_value)
header = "{\"typ\":\"JWT\",\"alg\":\"RS256\"}"
jws_signing_input = jws_header .. "." .. jws_payload
local sobject = createCert(input)
local verify_response = assert(sobject:verify{data = Blob.from_bytes(jws_signing_input), signature = signature, hash_alg = 'SHA256', mode = { PKCS1_V15 = {} } } )
return {result = verify_response.result, payload = jws_payload}
end

function processCert(input)
local result = input.cert
result = string.gsub(result, "\n", "")
result = string.sub(result, 28)
result = string.sub(result, 1, -26)
return result
end

function createCert(input)
local value = processCert(input)
local sobject = assert(Sobject.import { name = "my key", obj_type = "CERTIFICATE", value = value, transient = true})
return sobject
end

function run(input)
parts = splits(input.jwe)
if parts == 0 then
return "Invalid Request"
end
local header = generateHeader()
local sobject = assert(Sobject { name = input.key})
local decrypt_response = assert(sobject:decrypt { cipher = parts.encrypted_key, mode = 'OAEP_MGF1_SHA256', alg='RSA' })
local key_material = decrypt_response.plain
local num = #key_material
local exported_aes_key = key_material:slice(1, num//2)
local exported_hmac_key = key_material:slice(num//2 + 1, num)
local aes_key = assert(Sobject.import { name = "aes-key", obj_type = "AES", key_size = 256, transient = true, key_ops = {'EXPORT', 'ENCRYPT', 'DECRYPT'}, value = exported_aes_key})
local hmac_key = assert(Sobject.import { name = "hmac-key", obj_type = "HMAC", key_size = 256, transient = true, key_ops = {'EXPORT', 'MACGENERATE', 'MACVERIFY'}, value = exported_hmac_key})
local aad = generateAD(header)
local decrypt_response = assert(aes_key:decrypt { cipher = parts.cipher, mode = 'CBC', iv = parts.iv})
local plain = decrypt_response.plain
local al = computeLength(aad)
local hmac_input_blob = Blob.from_bytes(aad) .. Blob.from_base64(parts.iv) .. Blob.from_base64(parts.cipher) .. Blob.from_bytes(al)
local mac_response = assert(hmac_key:mac { data = hmac_input_blob:base64(), alg = 'SHA512' })
local digest = truncateDigest(mac_response.mac)
if digest == Blob.from_base64(parts.tag) then
local response = verifyJws(input, plain:bytes())
if response.result then
local data = Blob.from_base64(transformTo64(response.payload))
return {output = "VERIFIED", payload = data:bytes()}
else
return {output = "VERIFICATION FAILED"}
end
else
return {output = "Invalid Request"}
end
end

0 comments on commit 0646731

Please sign in to comment.