-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
209 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|