From 18ad261595b81bad781ff700f672e29164e7763c Mon Sep 17 00:00:00 2001 From: sug Date: Thu, 17 Oct 2024 18:24:46 +0200 Subject: [PATCH] Sdk should be able to Encrypt/Decrypt CompressedString Co-authored-by: nig Co-authored-by: jhm --- .../CompressionCompatibilityTestData.json | 66 ++- test/tests/api/worker/CompressionTest.ts | 2 + tuta-sdk/rust/Cargo.lock | 7 + tuta-sdk/rust/sdk/Cargo.toml | 1 + .../rust/sdk/src/entities/entity_facade.rs | 466 ++++++++++++------ 5 files changed, 368 insertions(+), 174 deletions(-) diff --git a/test/tests/api/worker/CompressionCompatibilityTestData.json b/test/tests/api/worker/CompressionCompatibilityTestData.json index f8b8cc050b8..7f11f7641b0 100644 --- a/test/tests/api/worker/CompressionCompatibilityTestData.json +++ b/test/tests/api/worker/CompressionCompatibilityTestData.json @@ -2,111 +2,133 @@ { "uncompressedText": "", "compressedBase64TextJava": "", - "compressedBase64TextJavaScript": "" + "compressedBase64TextJavaScript": "", + "compressedBase64TextRust": "" }, { "uncompressedText": "a", "compressedBase64TextJava": "EGE=", - "compressedBase64TextJavaScript": "EGE=" + "compressedBase64TextJavaScript": "EGE=", + "compressedBase64TextRust": "EGE=" }, { "uncompressedText": "ab", "compressedBase64TextJava": "IGFi", - "compressedBase64TextJavaScript": "IGFi" + "compressedBase64TextJavaScript": "IGFi", + "compressedBase64TextRust": "IGFi" }, { "uncompressedText": "aba", "compressedBase64TextJava": "MGFiYQ==", - "compressedBase64TextJavaScript": "MGFiYQ==" + "compressedBase64TextJavaScript": "MGFiYQ==", + "compressedBase64TextRust": "MGFiYQ==" }, { "uncompressedText": "abab", "compressedBase64TextJava": "QGFiYWI=", - "compressedBase64TextJavaScript": "QGFiYWI=" + "compressedBase64TextJavaScript": "QGFiYWI=", + "compressedBase64TextRust": "QGFiYWI=" }, { "uncompressedText": "ababa", "compressedBase64TextJava": "UGFiYWJh", - "compressedBase64TextJavaScript": "UGFiYWJh" + "compressedBase64TextJavaScript": "UGFiYWJh", + "compressedBase64TextRust": "UGFiYWJh" }, { "uncompressedText": "ababab", "compressedBase64TextJava": "YGFiYWJhYg==", - "compressedBase64TextJavaScript": "YGFiYWJhYg==" + "compressedBase64TextJavaScript": "YGFiYWJhYg==", + "compressedBase64TextRust": "YGFiYWJhYg==" }, { "uncompressedText": "abababa", "compressedBase64TextJava": "cGFiYWJhYmE=", - "compressedBase64TextJavaScript": "cGFiYWJhYmE=" + "compressedBase64TextJavaScript": "cGFiYWJhYmE=", + "compressedBase64TextRust": "cGFiYWJhYmE=" }, { "uncompressedText": "abababab", "compressedBase64TextJava": "gGFiYWJhYmFi", - "compressedBase64TextJavaScript": "gGFiYWJhYmFi" + "compressedBase64TextJavaScript": "gGFiYWJhYmFi", + "compressedBase64TextRust": "gGFiYWJhYmFi" }, { "uncompressedText": "ababababa", "compressedBase64TextJava": "kGFiYWJhYmFiYQ==", - "compressedBase64TextJavaScript": "kGFiYWJhYmFiYQ==" + "compressedBase64TextJavaScript": "kGFiYWJhYmFiYQ==", + "compressedBase64TextRust": "kGFiYWJhYmFiYQ==" }, { "uncompressedText": "ababababab", "compressedBase64TextJava": "oGFiYWJhYmFiYWI=", - "compressedBase64TextJavaScript": "oGFiYWJhYmFiYWI=" + "compressedBase64TextJavaScript": "oGFiYWJhYmFiYWI=", + "compressedBase64TextRust": "oGFiYWJhYmFiYWI=" }, { "uncompressedText": "abababababa", "compressedBase64TextJava": "sGFiYWJhYmFiYWJh", - "compressedBase64TextJavaScript": "sGFiYWJhYmFiYWJh" + "compressedBase64TextJavaScript": "sGFiYWJhYmFiYWJh", + "compressedBase64TextRust": "sGFiYWJhYmFiYWJh" }, { "uncompressedText": "abababababab", "compressedBase64TextJava": "wGFiYWJhYmFiYWJhYg==", - "compressedBase64TextJavaScript": "wGFiYWJhYmFiYWJhYg==" + "compressedBase64TextJavaScript": "wGFiYWJhYmFiYWJhYg==", + "compressedBase64TextRust": "wGFiYWJhYmFiYWJhYg==" }, { "uncompressedText": "ababababababa", "compressedBase64TextJava": "0GFiYWJhYmFiYWJhYmE=", - "compressedBase64TextJavaScript": "0GFiYWJhYmFiYWJhYmE=" + "compressedBase64TextJavaScript": "0GFiYWJhYmFiYWJhYmE=", + "compressedBase64TextRust": "0GFiYWJhYmFiYWJhYmE=" }, { "uncompressedText": "ababababababab", "compressedBase64TextJava": "4GFiYWJhYmFiYWJhYmFi", - "compressedBase64TextJavaScript": "4GFiYWJhYmFiYWJhYmFi" + "compressedBase64TextJavaScript": "4GFiYWJhYmFiYWJhYmFi", + "compressedBase64TextRust": "ImFiAgBgYWJhYmFi" }, { "uncompressedText": "abababababababa", "compressedBase64TextJava": "JGFiAgBQYWJhYmE=", - "compressedBase64TextJavaScript": "8ABhYmFiYWJhYmFiYWJhYmE=" + "compressedBase64TextJavaScript": "8ABhYmFiYWJhYmFiYWJhYmE=", + "compressedBase64TextRust": "I2FiAgBgYmFiYWJh" }, { "uncompressedText": "abababababababab", "compressedBase64TextJava": "JWFiAgBQYmFiYWI=", - "compressedBase64TextJavaScript": "8AFhYmFiYWJhYmFiYWJhYmFi" + "compressedBase64TextJavaScript": "8AFhYmFiYWJhYmFiYWJhYmFi", + "compressedBase64TextRust": "JGFiAgBgYWJhYmFi" }, { "uncompressedText": "ababababababababa", "compressedBase64TextJava": "JmFiAgBQYWJhYmE=", - "compressedBase64TextJavaScript": "8AJhYmFiYWJhYmFiYWJhYmFiYQ==" + "compressedBase64TextJavaScript": "8AJhYmFiYWJhYmFiYWJhYmFiYQ==", + "compressedBase64TextRust": "JWFiAgBgYmFiYWJh" }, { "uncompressedText": "ababababababababab", "compressedBase64TextJava": "J2FiAgBQYmFiYWI=", - "compressedBase64TextJavaScript": "8ANhYmFiYWJhYmFiYWJhYmFiYWI=" + "compressedBase64TextJavaScript": "8ANhYmFiYWJhYmFiYWJhYmFiYWI=", + "compressedBase64TextRust": "JmFiAgBgYWJhYmFi" }, { "uncompressedText": "abababababababababa", "compressedBase64TextJava": "KGFiAgBQYWJhYmE=", - "compressedBase64TextJavaScript": "IWFiAgDAYmFiYWJhYmFiYWJh" + "compressedBase64TextJavaScript": "IWFiAgDAYmFiYWJhYmFiYWJh", + "compressedBase64TextRust": "J2FiAgBgYmFiYWJh" }, { "uncompressedText": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tempor orci eu lobortis elementum nibh. Nibh tellus molestie nunc non blandit. Varius quam quisque id diam vel quam. Sit amet aliquam id diam maecenas ultricies mi eget. Erat pellentesque adipiscing commodo elit at imperdiet dui accumsan. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla. Et malesuada fames ac turpis egestas integer eget aliquet nibh. Vitae purus faucibus ornare suspendisse. Ullamcorper eget nulla facilisi etiam dignissim diam quis enim. Volutpat maecenas volutpat blandit aliquam. Cursus turpis massa tincidunt dui ut ornare. A diam maecenas sed enim ut sem viverra.\n\nSit amet nisl suscipit adipiscing bibendum est ultricies integer. Pretium vulputate sapien nec sagittis aliquam malesuada. Convallis aenean et tortor at risus viverra adipiscing at in. Euismod lacinia at quis risus sed. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies. Varius duis at consectetur lorem donec. Urna nunc id cursus metus. Sed faucibus turpis in eu mi bibendum neque egestas congue. Gravida in fermentum et sollicitudin ac orci. Sed sed risus pretium quam. Nunc scelerisque viverra mauris in aliquam sem fringilla. Lectus vestibulum mattis ullamcorper velit sed. Amet commodo nulla facilisi nullam vehicula ipsum. Iaculis eu non diam phasellus vestibulum lorem. Felis bibendum ut tristique et egestas. Lobortis mattis aliquam faucibus purus in massa. Nisi vitae suscipit tellus mauris a diam maecenas sed. Velit sed ullamcorper morbi tincidunt ornare massa.\n\nUt pharetra sit amet aliquam id diam maecenas ultricies mi. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique. Consectetur adipiscing elit pellentesque habitant. Vel orci porta non pulvinar. Gravida cum sociis natoque penatibus et magnis. Eget egestas purus viverra accumsan in nisl nisi scelerisque. Erat nam at lectus urna duis convallis. Bibendum est ultricies integer quis auctor. Enim ut tellus elementum sagittis vitae et leo duis. Tellus elementum sagittis vitae et leo duis. Sem fringilla ut morbi tincidunt.\n\nEgestas diam in arcu cursus euismod quis viverra. Amet luctus venenatis lectus magna fringilla urna porttitor. Egestas sed sed risus pretium quam. Turpis massa tincidunt dui ut ornare. Convallis tellus id interdum velit laoreet id donec ultrices. Egestas sed sed risus pretium quam vulputate dignissim suspendisse. Rhoncus urna neque viverra justo nec ultrices. Sapien pellentesque habitant morbi tristique senectus et. Phasellus vestibulum lorem sed risus ultricies tristique nulla aliquet. Odio ut enim blandit volutpat maecenas volutpat blandit. Vulputate eu scelerisque felis imperdiet proin fermentum leo vel. Vitae ultricies leo integer malesuada nunc vel risus. Auctor elit sed vulputate mi sit amet mauris commodo quis.\n\nTurpis in eu mi bibendum neque egestas congue quisque egestas. Tincidunt praesent semper feugiat nibh. Ante in nibh mauris cursus mattis molestie a. Urna porttitor rhoncus dolor purus. Feugiat in fermentum posuere urna nec tincidunt. Pellentesque massa placerat duis ultricies lacus sed turpis tincidunt. Amet dictum sit amet justo donec enim diam vulputate ut. Egestas purus viverra accumsan in. Elementum sagittis vitae et leo. Euismod quis viverra nibh cras pulvinar mattis nunc. Ultricies mi eget mauris pharetra et ultrices. Mauris vitae ultricies leo integer malesuada nunc vel. Justo laoreet sit amet cursus sit. Vestibulum lectus mauris ultrices eros in cursus. Nunc congue nisi vitae suscipit tellus mauris a.", "compressedBase64TextJava": "8ldMb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LCBzZWQgZG8gZWl1c21vZCB0ZW1wb3IgaW5jaWRpZHVudCB1dCBsYWJvcmUgZXRbAPICZSBtYWduYSBhbGlxdWEuIFQ0APNgb3JjaSBldSBsb2JvcnRpcyBlbGVtZW50dW0gbmliaC4gTmliaCB0ZWxsdXMgbW9sZXN0aWUgbnVuYyBub24gYmxhbmRpdC4gVmFyaXVzIHF1YW0gcXVpc3F1ZSBpZCBkaWFtIHZlbCBxdWFtLiBT3wADhgAVbSMA8RptYWVjZW5hcyB1bHRyaWNpZXMgbWkgZWdldC4gRXJhdCBwZWxsZW50ZVkABxABUGNvbW1vCwEQbDoBkHQgaW1wZXJkafYA8wl1aSBhY2N1bXNhbi4gU3VzcGVuZGlzc2VkAOBlcyBncmF2aWRhIGRpY/EA4GZ1c2NlIHV0IHBsYWNldgABHgHwEm51bGxhLiBFdCBtYWxlc3VhZGEgZmFtZXMgYWMgdHVycDYB4Gdlc3RhcyBpbnRlZ2VyEAAD5QAjZXRJAfYOVml0YWUgcHVydXMgZmF1Y2lidXMgb3JuYXJlIHOeALQuIFVsbGFtY29ycEgAAYEAwCBmYWNpbGlzaSBldFUBgGRpZ25pc3NpCgADdAHgIGVuaW0uIFZvbHV0cGGvAANVARR2EgADrwECmgCkYW0uIEN1cnN1c8YAcW1hc3NhIHRIAgBGAjBkdWkPAQKlADIuIEHPAQVXAACGAgBzAAAkAOVzZW0gdml2ZXJyYS4KCugBQG5pc2zeADBjaXB3AAbIAsNiaWJlbmR1bSBlc3SRAQD1AQNAAfAVLiBQcmV0aXVtIHZ1bHB1dGF0ZSBzYXBpZW4gbmVjIHNhZ2l0rQICzQIAkwADmQHwDS4gQ29udmFsbGlzIGFlbmVhbiBldCB0b3J0b3IfAiByae8AA6kACJAAoWF0IGluLiBFdWlSA3BsYWNpbmlhMwABWgECOADwB3NlZC4gRGlzIHBhcnR1cmllbnQgbW+WAlEgbmFzY6wDYXJpZGljdS4DADIDQGF1cmlxAAD8AQXcAgUvAyBkdagAGXTtAxFsFQSyZG9uZWMuIFVybmFuA0JpZCBjrQGmbWV0dXMuIFNlZEgCA4cCIGluzAMmbWlfASBuZTEDBJ8Ck2Nvbmd1ZS4gR/kCY2luIGZlcvAD8ABldCBzb2xsaWNpdHVkaW7eAgAAAwJqAADsAQIFARNwmgECQQIQTgoEYnNjZWxlcvUDBFwBA/oAAEgAAXIEEW0jAmFmcmluZ2lQA4BMZWN0dXMgdlIEQGJ1bHXGAQHVAQBvAwQAAxB28AQA7wRQLiBBbWUpAQLvAwsTAwEPAFBtIHZlaHwBEmFTBWAuIElhY3UKAiFlda0EASkDYHBoYXNlbKABB3oAAXQBQC4gRmUuAAU8AZF1dCB0cmlzdGlDARB01QMA5QM0LiBMLQUDsgAEhwIF1wMC5gMiaW5SAzAuIE64AwIFAgUNAwRUBQIoAQCFBBBhAwEDswMBawIUVv4ACRQBV21vcmJppgMCnwMDawDVCgpVdCBwaGFyZXRyYU8GBYQBD3AFCjkuIESFBgmXAgd0BQGWAAmRBWFoYWJpdGEOAwGcAAQ2AQGPAw/HBgUK1AUEQwAB9gABeQJhIHBvcnRhugGGcHVsdmluYXK6AvAAY3VtIHNvY2lpcyBuYXRvogFAcGVuYUsCEHOsAQDtBlBpcy4gRYkFBP8CApcBBKICBB0GMiBpbqgEEG6mAQfMAgN7BjBuYW2kAxJssAIQdZIDAbgDFGNqBD8uIELOBAoCnQWTYXVjdG9yLiBFMgUDAAIGcAcF1gQCKQIQZXMAEG/HBhBzrgcBswIPLQATGFNpAwCdBQOWAQTYBUIuCgpFywIC0QUApAM0cmN1YAQTZQQFAfkEAy0BA38DI2x1tAMRbmwBFHMVAQBuARZh3QMCJQEAvwEidGn0AANvAABGBAAEAA9KBAESVEoHATIDBqwAAAABAMMAAuUCAVACA98FA0ABIWlkdQcQckwGAi4EcWxhb3JlZXTsAgBGBQNfBgB3BQT3AACEAAAEAA6IAAdxBgZiBwmfB1RSaG9uYwkCAl8FAyUBYSBqdXN0b6IGAusFAHMAElO3Bg8vAxJCIHNlbmYCX2V0LiBQqgQGABEFA7wAAmkAADsHBqsEAQ4FAvMDAEgAQE9kaW9DARBleQIEAggFEwgFcgQFEgADIwA1LiBWfAcoZXUYAyEgZigFBmsJOXByb0oGALgCNHZlbPQIBqQAABkAA+MHB00JEG5EBgAlCgHTADEuIEEqAwLTCQCmAQYECCZtadMEA2EGBBIGAK4CMy4KCkYCDwkHEwGZAwA7BAMhBDQuIFQkA2AgcHJhZXPhByBzZVMKcCBmZXVnaWHSCAAWC0JBbnRlOAQkYmjDBQSfBwLGBgUsCxNhxgcFDwMjIHJCAgLzCwGVBAB8BgJgAAlJAXJwb3N1ZXJlUAMA+ggFnAk4LiBQEgsCNgMFuAoBoAQHdwEQYbECAEsBAygIB0cAAVcHAwELBH4MA8kCAYQIAVUKAigEBogBEHWXCwPUAwHAAAT8CQVyCwJOCQVMDA/cBAIGbwkAvQEFQwABVAEhY3JbAAL3BQRUAQApAgAgCwsjDASBAQX3BidldPQDEk1hBwJVBQcUAQ+LAgoxLiBKzAMETAQFGgED5QERcyADBrsIBQgFAe8JAmYAYWVzIGVyb4wCAjYAAy4JAoUJAHMGDxQICVBpcyBhLg==", - "compressedBase64TextJavaScript": "8VhMb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LCBzZWQgZG8gZWl1c21vZCB0ZW1wb3IgaW5jaWRpZHVudCB1dCBsYWJvcmUgZXQgWwDyAmUgbWFnbmEgYWxpcXVhLiBUNADzYG9yY2kgZXUgbG9ib3J0aXMgZWxlbWVudHVtIG5pYmguIE5pYmggdGVsbHVzIG1vbGVzdGllIG51bmMgbm9uIGJsYW5kaXQuIFZhcml1cyBxdWFtIHF1aXNxdWUgaWQgZGlhbSB2ZWwgcXVhbS4gU98AA4YAFW0jAPEabWFlY2VuYXMgdWx0cmljaWVzIG1pIGVnZXQuIEVyYXQgcGVsbGVudGVZAAcQAVBjb21tbwsBEGxbAPMWdCBpbXBlcmRpZXQgZHVpIGFjY3Vtc2FuLiBTdXNwZW5kaXNzZWQA4GVzIGdyYXZpZGEgZGlj8QBQZnVzY2U7AVBwbGFjZXYAAR4B8BJudWxsYS4gRXQgbWFsZXN1YWRhIGZhbWVzIGFjIHR1cnA2AeFnZXN0YXMgaW50ZWdlcroAAuUAI2V0SQH2DlZpdGFlIHB1cnVzIGZhdWNpYnVzIG9ybmFyZSBzngC0LiBVbGxhbWNvcnBIAAGBAPAJIGZhY2lsaXNpIGV0aWFtIGRpZ25pc3NpCgADdAHgIGVuaW0uIFZvbHV0cGGvAANVARR2EgADrwECmgCkYW0uIEN1cnN1c8YAcW1hc3NhIHRIAiJ1bkMBI3V0pQBBLiBBIM8BBawBAIYCAHMAADMB8ABzZW0gdml2ZXJyYS4KClONARFt+wAgc2zeADBjaXATAAbIAsNiaWJlbmR1bSBlc3SRAQD1AQNAAfAVLiBQcmV0aXVtIHZ1bHB1dGF0ZSBzYXBpZW4gbmVjIHNhZ2l0rQICzQIWbZkB8AMuIENvbnZhbGxpcyBhZW5lYW4BA8B0b3J0b3IgYXQgcmnvAAOpAAhYA6FhdCBpbi4gRXVpUgNwbGFjaW5pYTMAQ3F1aXM4APAHc2VkLiBEaXMgcGFydHVyaWVudCBtb5YCUSBuYXNjrANhcmlkaWN1LgMAMgNxYXVyaXMgdvwBBdwCFC4vAxBkXAApYXTtAxFsFQSyZG9uZWMuIFVybmFuA0JpZCBjrQGmbWV0dXMuIFNlZEgCA4cCIGluzAM1bWkgXwFQbmVxdWWPAgGfApNjb25ndWUuIEf5AmNpbiBmZXLwA/AAZXQgc29sbGljaXR1ZGlu3gIAAAMCagAA7AECPQETcJoBAOsDMC4gTgoEcXNjZWxlcmmcAwRcARJt+gAASAABcgQgbSAjAmFmcmluZ2lQA4BMZWN0dXMgdlIEQGJ1bHXGAQHVAQBvAwQAAxB28AQA7wQwLiBBUAIE7wMLEwMBDwBQbSB2ZWh8ARJhUwVgLiBJYWN1CgIwZXUgrQQBvgJCcGhhc84EB3oAAXQBQC4gRmUuAAU8AZF1dCB0cmlzdGlDARR0RgE0LiBMLQUhbWGyAASHAgXXAwLmAzFpbiBSAwBFBSNzaQUCMnN1cw0DBFQFAigBAIUEEGEDAQOzAwFrAhRW/gAYIBQBV21vcmJppgMDRAQCawDVCgpVdCBwaGFyZXRyYU8GBPEDIyBpkwUFxAMFlAJpIG1pLiBEhQYJlwIHdAUBlgAJkQVhaGFiaXRhDgMBnAAENgEBjwMPxwYFCtQFBEMAYC4gVmVsIHkCYSBwb3J0YboBhnB1bHZpbmFyugLwAGN1bSBzb2NpaXMgbmF0b6IBUXBlbmF0VgUQZfwEAB8FVS4gRWdluQEDfQUEogIEHQaAIGluIG5pc2wFACdpIMwCA3sGMG5hbQgEEmywAhB1kgMBuAMUY2oEPy4gQs4ECgERB9AgYXVjdG9yLiBFbmltMgUDAAIGcAcF1gQCLgRgZXQgbGVvxwYQc64HAYEHDy0AE0VTZW0gaQMAawALMgJDLgoKRbAGARMDAKQDNHJjdWAEE2UEBQH5BAMtAQN/AyNsdbQDEW5sASNzIBUBAlsIB3QAACUBAL8BInRp9AADbwAARgQABAAPSgQBElRKBwHHAgfeAgDHBweEBgbfBQNAASFpZHUHEHJMBgIuBHFsYW9yZWV07AIARgUDXwYhZXMOAgEIBQCEAAAEAA6IAAdxBgZiBwmfB4FSaG9uY3VzIAkCAl8FAyUBYSBqdXN0b6IGAlcDAHMAElO3Bg8vAxJCIHNlbmYCAPgIH1CqBAYAEQUDkwYCaQAAOwcGqwQBDgUC8wMASABAT2RpbwYCAasHBAIIBRMIBQoEBRIAAyMANS4gVnwHKGV1GAMhIGYoBQZrCTlwcm9KBgC4AlJ2ZWwuIPQIBqQAABkAA+MHBrQHAuAGMnZlbNMAMS4gQSoDAesKAeoABgQIJm1p0wQDYQYEEgYArgIzLgoKRgIPCQcTAZkDADsEEmWgAjUuIFT8CFBwcmFlc+EHIHNlUwp0IGZldWdpYc0JQkFudGU4BDRiaCCKAAOfBwIUBgUsCxNhxgcFDwMjIHJCAgGYCwKVBAB8BgJgAAlJAXRwb3N1ZXJlbAIWYxgDOC4gUBILAjYDBbgKAaAEB3cBEGGxAgDxAgMoCAXjCQPYAwMBCwR+DAPJAgGECAFVCgL5CQaIARB1lwsCNAECwAAE/AkFcgsCTgkFTAwP3AQCBm8JAL0BBUMAAHgMZCBjcmFzIPcFEyBoBwB3DAAgCwsjDASBAQX3BhRlUwoAgQMSTWEHAlUFBxQBIWVvOQQArgsG2AsAYgAAsAwxLiBKzAMETAQFGgEDhAkgc2n0DAa7CAUIBQHvCQJmAGBlcyBlcm8UDANaBQMuCQKFCQJuBgKWAAkUCMB1cyBtYXVyaXMgYS4=" + "compressedBase64TextJavaScript": "8VhMb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LCBzZWQgZG8gZWl1c21vZCB0ZW1wb3IgaW5jaWRpZHVudCB1dCBsYWJvcmUgZXQgWwDyAmUgbWFnbmEgYWxpcXVhLiBUNADzYG9yY2kgZXUgbG9ib3J0aXMgZWxlbWVudHVtIG5pYmguIE5pYmggdGVsbHVzIG1vbGVzdGllIG51bmMgbm9uIGJsYW5kaXQuIFZhcml1cyBxdWFtIHF1aXNxdWUgaWQgZGlhbSB2ZWwgcXVhbS4gU98AA4YAFW0jAPEabWFlY2VuYXMgdWx0cmljaWVzIG1pIGVnZXQuIEVyYXQgcGVsbGVudGVZAAcQAVBjb21tbwsBEGxbAPMWdCBpbXBlcmRpZXQgZHVpIGFjY3Vtc2FuLiBTdXNwZW5kaXNzZWQA4GVzIGdyYXZpZGEgZGlj8QBQZnVzY2U7AVBwbGFjZXYAAR4B8BJudWxsYS4gRXQgbWFsZXN1YWRhIGZhbWVzIGFjIHR1cnA2AeFnZXN0YXMgaW50ZWdlcroAAuUAI2V0SQH2DlZpdGFlIHB1cnVzIGZhdWNpYnVzIG9ybmFyZSBzngC0LiBVbGxhbWNvcnBIAAGBAPAJIGZhY2lsaXNpIGV0aWFtIGRpZ25pc3NpCgADdAHgIGVuaW0uIFZvbHV0cGGvAANVARR2EgADrwECmgCkYW0uIEN1cnN1c8YAcW1hc3NhIHRIAiJ1bkMBI3V0pQBBLiBBIM8BBawBAIYCAHMAADMB8ABzZW0gdml2ZXJyYS4KClONARFt+wAgc2zeADBjaXATAAbIAsNiaWJlbmR1bSBlc3SRAQD1AQNAAfAVLiBQcmV0aXVtIHZ1bHB1dGF0ZSBzYXBpZW4gbmVjIHNhZ2l0rQICzQIWbZkB8AMuIENvbnZhbGxpcyBhZW5lYW4BA8B0b3J0b3IgYXQgcmnvAAOpAAhYA6FhdCBpbi4gRXVpUgNwbGFjaW5pYTMAQ3F1aXM4APAHc2VkLiBEaXMgcGFydHVyaWVudCBtb5YCUSBuYXNjrANhcmlkaWN1LgMAMgNxYXVyaXMgdvwBBdwCFC4vAxBkXAApYXTtAxFsFQSyZG9uZWMuIFVybmFuA0JpZCBjrQGmbWV0dXMuIFNlZEgCA4cCIGluzAM1bWkgXwFQbmVxdWWPAgGfApNjb25ndWUuIEf5AmNpbiBmZXLwA/AAZXQgc29sbGljaXR1ZGlu3gIAAAMCagAA7AECPQETcJoBAOsDMC4gTgoEcXNjZWxlcmmcAwRcARJt+gAASAABcgQgbSAjAmFmcmluZ2lQA4BMZWN0dXMgdlIEQGJ1bHXGAQHVAQBvAwQAAxB28AQA7wQwLiBBUAIE7wMLEwMBDwBQbSB2ZWh8ARJhUwVgLiBJYWN1CgIwZXUgrQQBvgJCcGhhc84EB3oAAXQBQC4gRmUuAAU8AZF1dCB0cmlzdGlDARR0RgE0LiBMLQUhbWGyAASHAgXXAwLmAzFpbiBSAwBFBSNzaQUCMnN1cw0DBFQFAigBAIUEEGEDAQOzAwFrAhRW/gAYIBQBV21vcmJppgMDRAQCawDVCgpVdCBwaGFyZXRyYU8GBPEDIyBpkwUFxAMFlAJpIG1pLiBEhQYJlwIHdAUBlgAJkQVhaGFiaXRhDgMBnAAENgEBjwMPxwYFCtQFBEMAYC4gVmVsIHkCYSBwb3J0YboBhnB1bHZpbmFyugLwAGN1bSBzb2NpaXMgbmF0b6IBUXBlbmF0VgUQZfwEAB8FVS4gRWdluQEDfQUEogIEHQaAIGluIG5pc2wFACdpIMwCA3sGMG5hbQgEEmywAhB1kgMBuAMUY2oEPy4gQs4ECgERB9AgYXVjdG9yLiBFbmltMgUDAAIGcAcF1gQCLgRgZXQgbGVvxwYQc64HAYEHDy0AE0VTZW0gaQMAawALMgJDLgoKRbAGARMDAKQDNHJjdWAEE2UEBQH5BAMtAQN/AyNsdbQDEW5sASNzIBUBAlsIB3QAACUBAL8BInRp9AADbwAARgQABAAPSgQBElRKBwHHAgfeAgDHBweEBgbfBQNAASFpZHUHEHJMBgIuBHFsYW9yZWV07AIARgUDXwYhZXMOAgEIBQCEAAAEAA6IAAdxBgZiBwmfB4FSaG9uY3VzIAkCAl8FAyUBYSBqdXN0b6IGAlcDAHMAElO3Bg8vAxJCIHNlbmYCAPgIH1CqBAYAEQUDkwYCaQAAOwcGqwQBDgUC8wMASABAT2RpbwYCAasHBAIIBRMIBQoEBRIAAyMANS4gVnwHKGV1GAMhIGYoBQZrCTlwcm9KBgC4AlJ2ZWwuIPQIBqQAABkAA+MHBrQHAuAGMnZlbNMAMS4gQSoDAesKAeoABgQIJm1p0wQDYQYEEgYArgIzLgoKRgIPCQcTAZkDADsEEmWgAjUuIFT8CFBwcmFlc+EHIHNlUwp0IGZldWdpYc0JQkFudGU4BDRiaCCKAAOfBwIUBgUsCxNhxgcFDwMjIHJCAgGYCwKVBAB8BgJgAAlJAXRwb3N1ZXJlbAIWYxgDOC4gUBILAjYDBbgKAaAEB3cBEGGxAgDxAgMoCAXjCQPYAwMBCwR+DAPJAgGECAFVCgL5CQaIARB1lwsCNAECwAAE/AkFcgsCTgkFTAwP3AQCBm8JAL0BBUMAAHgMZCBjcmFzIPcFEyBoBwB3DAAgCwsjDASBAQX3BhRlUwoAgQMSTWEHAlUFBxQBIWVvOQQArgsG2AsAYgAAsAwxLiBKzAMETAQFGgEDhAkgc2n0DAa7CAUIBQHvCQJmAGBlcyBlcm8UDANaBQMuCQKFCQJuBgKWAAkUCMB1cyBtYXVyaXMgYS4=", + "compressedBase64TextRust": "8ldMb3JlbSBpcHN1bSBkb2xvciBzaXQgYW1ldCwgY29uc2VjdGV0dXIgYWRpcGlzY2luZyBlbGl0LCBzZWQgZG8gZWl1c21vZCB0ZW1wb3IgaW5jaWRpZHVudCB1dCBsYWJvcmUgZXRbAPICZSBtYWduYSBhbGlxdWEuIFQ0APNgb3JjaSBldSBsb2JvcnRpcyBlbGVtZW50dW0gbmliaC4gTmliaCB0ZWxsdXMgbW9sZXN0aWUgbnVuYyBub24gYmxhbmRpdC4gVmFyaXVzIHF1YW0gcXVpc3F1ZSBpZCBkaWFtIHZlbCBxdWFtLiBT3wADhgAVbSMA8RptYWVjZW5hcyB1bHRyaWNpZXMgbWkgZWdldC4gRXJhdCBwZWxsZW50ZVkABxABUGNvbW1vCwEQbFsAkHQgaW1wZXJkafYA8wl1aSBhY2N1bXNhbi4gU3VzcGVuZGlzc2VkAOBlcyBncmF2aWRhIGRpY/EAUGZ1c2NlOwFQcGxhY2V2AAEeAfEkbnVsbGEuIEV0IG1hbGVzdWFkYSBmYW1lcyBhYyB0dXJwaXMgZWdlc3RhcyBpbnRlZ2VyugAC5QAjZXRJAfYOVml0YWUgcHVydXMgZmF1Y2lidXMgb3JuYXJlIHOeALQuIFVsbGFtY29ycEgAAYEAwCBmYWNpbGlzaSBldFUBgGRpZ25pc3NpCgBQYW0gcXW8AcBuaW0uIFZvbHV0cGGvAANVARR2EgADrwECmgCkYW0uIEN1cnN1c8YAcW1hc3NhIHRIAiJ1bkMBEHUGAQClADIuIEHPAQVXAACGAkBlbmltMwHwAHNlbSB2aXZlcnJhLgoKU2QAEW37ACBzbN4AMGNpcBMABrgBw2JpYmVuZHVtIGVzdJEBAPUBA0AB8BUuIFByZXRpdW0gdnVscHV0YXRlIHNhcGllbiBuZWMgc2FnaXStAgLNAgCTAAOZAfANLiBDb252YWxsaXMgYWVuZWFuIGV0IHRvcnRvch8CIHJp7wADqQAISAKhYXQgaW4uIEV1aVIDcGxhY2luaWEzAAFaAQI4APAHc2VkLiBEaXMgcGFydHVyaWVudCBtb5YC8QEgbmFzY2V0dXIgcmlkaWN1LgMABABAYXVyaXEAAPwBBdwCBS8DEGS2ASlhdO0DEWwVBPIGZG9uZWMuIFVybmEgbnVuYyBpZCBjrQGmbWV0dXMuIFNlZEgCA4cCIGluzAMmbWlfASBuZTEDBJ8Ck2Nvbmd1ZS4gR/kCY2luIGZlcvAD8ABldCBzb2xsaWNpdHVkaW7eAgAAAwJqAADsAQIFARNwmgECQQIQTpwAYnNjZWxlcvUDBFwBA/oAAEgAAXIEEW0jAmFmcmluZ2lQA4BMZWN0dXMgdlIEQGJ1bHXGAQHVAQBvAwQAAxB28AQCeAEQQVACBO8DCxMDAQ8AUG0gdmVofAESYVMFYC4gSWFjdQoCYmV1IG5vbr4CYHBoYXNlbKABB3oAAXQBQC4gRmUuAAU8AZF1dCB0cmlzdGlDARB0jwR0c3Rhcy4gTC0FA7IABIcCBdcDAuYDImluUgMwLiBOuAMCBQIFDQMEVAUCKAEAhQQQYQMBA7MDAfMAFFb+AAkUAVdtb3JiaaYDAp8DA2sA1QoKVXQgcGhhcmV0cmFPBgWEAQ9wBQo5LiBEhQYJlwIHLAMBlgAJkQVhaGFiaXRhDgMBnAAENgEBjwMPxwYFCtQFBEMAAfYAAXkCYSBwb3J0YboBhnB1bHZpbmFyugLwAGN1bSBzb2NpaXMgbmF0b6IBQHBlbmFLAhBzrAEA7QZmaXMuIEVnuQEDfQUEogIEHQYyIGluqAQQbqYBB8wCA3sGMG5hbQgEEmywAhB1kgMBuAMUY2oEPy4gQs4ECgKdBZNhdWN0b3IuIEUyBQMAAgZwBwXWBAIpAhBlcwAQb4QFEHOuBwGzAg8tABMYU2kDAJ0FA5YBBNgFQi4KCkXLAgITAwCkAzRyY3VgBBNlBAUB+QQDLQEDfwMjbHW0AxFubAEUcxUBAG4BFmHdAwIlAQC/ASJ0afQAA28AAEYEAAQAD0oEARJUSgcBMgMGrAAAAAEAwwAC5QIBUAID3wUDQAEhaWQ1BhByTAYCLgRwbGFvcmVldDIFAUYFA18GAHcFBPcAAIQAAAQADogAB3EGBmIHCZ8HVFJob25jCQICXwUDJQFhIGp1c3RvogYC6wUAcwASU7cGDy8DEkIgc2VuZgJfZXQuIFCqBAYAEQUDvAACaQAAOwcGqwQBDgUC8wMASABAT2Rpb0MBEGV5AgQCCAUTCAVyBAUSAAMjADUuIFZ8ByhldRgDISBmKAUGawk5cHJvSgYAuAI0dmVs9AgGpAAAGQAArgEAIwkFtAcC4AYydmVs0wAxLiBBKgMC0wkApgEGBAgmbWnTBANhBgQSBgCuAjMuCgpGAg8JBxMBmQMAOwQDIAc0LiBUJANgIHByYWVz4Qcgc2VTCnQgZmV1Z2lhzQlCQW50ZTgEJGJowwUEnwcCxgYFLAsTYcYHBQ8DIyByQgIRZG4FASwGAHwGAmAACUkBcnBvc3VlcmVQAwD6CAWcCTguIFCBBQI2AwW4CgGgBAd3ARBhsQIASwEDKAgHRwABVwcDAQsEfgwDyQICPgMBNwIBnQYGiAEQdZcLA9QDAcAABPwJBXILAk4JBUwMD9wEAgZvCQC9AQVDAAFUASFjcrAFAvcFBFQBACkCACALCyMMBIEBBfcGJ2V09AMfTYMJAg+LAgwxLiBKzAMETAQFGgED5QERcyADBrsIBQgFAWwAAnoBYWVzIGVyb4wCAjYAAy4JAoUJAHMGDxQICGByaXMgYS4=" }, { "uncompressedText": "ö()€ Τούτανοτα Тутасота 图塔诺塔 ツタノタ نخاعö()€ Τούτανοτα Тутасота 图塔诺塔 ツタノタ ö()€ Τούτανοτα Тутасота 图塔诺塔 ツタノタ نخاعنخاع", "compressedBase64TextJava": "8AfDtigp4oKsIM6kzr/Pjc+EzrHOvc6/CADQINCi0YPRgtCw0YHQvggA/xQg5Zu+5aGU6K+65aGUIOODhOOCv+ODjuOCvyDZhtiu2KfYuU4AMw9GADMElACA2YbYrtin2Lk=", - "compressedBase64TextJavaScript": "8AfDtigp4oKsIM6kzr/Pjc+EzrHOvc6/CADQINCi0YPRgtCw0YHQvggA/xQg5Zu+5aGU6K+65aGUIOODhOOCv+ODjuOCvyDZhtiu2KfYuU4AMw9GADPwAdmG2K7Yp9i52YbYrtin2Lk=" + "compressedBase64TextJavaScript": "8AfDtigp4oKsIM6kzr/Pjc+EzrHOvc6/CADQINCi0YPRgtCw0YHQvggA/xQg5Zu+5aGU6K+65aGUIOODhOOCv+ODjuOCvyDZhtiu2KfYuU4AMw9GADPwAdmG2K7Yp9i52YbYrtin2Lk=", + "compressedBase64TextRust": "8AfDtigp4oKsIM6kzr/Pjc+EzrHOvc6/CADQINCi0YPRgtCw0YHQvggA/xQg5Zu+5aGU6K+65aGUIOODhOOCv+ODjuOCvyDZhtiu2KfYuU4AMw+UADuA2YbYrtin2Lk=" } ] diff --git a/test/tests/api/worker/CompressionTest.ts b/test/tests/api/worker/CompressionTest.ts index 05dc7033fab..17fb7f34b3f 100644 --- a/test/tests/api/worker/CompressionTest.ts +++ b/test/tests/api/worker/CompressionTest.ts @@ -45,6 +45,8 @@ o.spec("Compression/Decompression", function () { o("decompression", function () { for (const testCase of testData) { o(utf8Uint8ArrayToString(uncompress(base64ToUint8Array(testCase.compressedBase64TextJavaScript)))).equals(testCase.uncompressedText) + o(utf8Uint8ArrayToString(uncompress(base64ToUint8Array(testCase.compressedBase64TextJava)))).equals(testCase.uncompressedText) + o(utf8Uint8ArrayToString(uncompress(base64ToUint8Array(testCase.compressedBase64TextRust)))).equals(testCase.uncompressedText) } }) }) diff --git a/tuta-sdk/rust/Cargo.lock b/tuta-sdk/rust/Cargo.lock index ea28413acf5..e56dcb02f74 100644 --- a/tuta-sdk/rust/Cargo.lock +++ b/tuta-sdk/rust/Cargo.lock @@ -1244,6 +1244,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" + [[package]] name = "memchr" version = "2.7.4" @@ -2589,6 +2595,7 @@ dependencies = [ "hyper-rustls", "hyper-util", "log 0.4.22", + "lz4_flex", "minicbor", "mockall", "mockall_double", diff --git a/tuta-sdk/rust/sdk/Cargo.toml b/tuta-sdk/rust/sdk/Cargo.toml index 05b6c24f32d..d49e90c31ea 100644 --- a/tuta-sdk/rust/sdk/Cargo.toml +++ b/tuta-sdk/rust/sdk/Cargo.toml @@ -31,6 +31,7 @@ log = "0.4.22" simple_logger = "5.0.0" uniffi = { git = "https://github.com/mozilla/uniffi-rs.git", rev = "13a1c559cb3708eeca40dcf95dc8b3ccccf3b88c" } num_enum = "0.7.3" +lz4_flex = { version = "0.11.3", default-features = false, features = ["safe-encode", "safe-decode", "std"] } # only used for the native rest client hyper = { version = "1.4.1", features = ["client"], optional = true } diff --git a/tuta-sdk/rust/sdk/src/entities/entity_facade.rs b/tuta-sdk/rust/sdk/src/entities/entity_facade.rs index 5a9546da6aa..7dbc1651aec 100644 --- a/tuta-sdk/rust/sdk/src/entities/entity_facade.rs +++ b/tuta-sdk/rust/sdk/src/entities/entity_facade.rs @@ -13,11 +13,14 @@ use crate::util::array_cast_slice; use crate::ApiCallError; use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD}; use base64::Engine; +use lz4_flex::block::DecompressError; use minicbor::Encode; use std::borrow::Borrow; use std::collections::HashMap; use std::sync::Arc; +pub const MAX_UNCOMPRESSED_INPUT_LZ4: usize = 0x7e000000; + /// Provides high level functions to handle encryption/decryption of entities #[derive(uniffi::Object)] pub struct EntityFacadeImpl { @@ -26,6 +29,7 @@ pub struct EntityFacadeImpl { } /// Value after it has been processed +#[cfg_attr(test, derive(Debug, PartialEq))] struct MappedValue { /// The actual decrypted value that will be written to the field value: ElementValue, @@ -87,14 +91,18 @@ impl EntityFacadeImpl { ) -> Result { let value_type = &model_value.value_type; + let element_is_nil = >::is_nil(instance_value); if !model_value.encrypted - || (>::is_nil(instance_value) - && model_value.cardinality == Cardinality::ZeroOrOne) + || (element_is_nil && model_value.cardinality == Cardinality::ZeroOrOne) { Ok(instance_value.clone()) + } else if element_is_nil { + Err(ApiCallError::internal(format!( + "Nil encrypted value is not accepted. ModelValue Id: {}", + model_value.id + ))) } else { - let bytes = Self::map_value_to_binary(value_type, instance_value) - .unwrap_or_else(|| panic!("invalid encrypted value {:?}", instance_value)); + let bytes = Self::map_value_to_binary(value_type, instance_value); let encrypted_data = session_key .encrypt_data(bytes.as_slice(), iv) .expect("Cannot encrypt data"); @@ -111,26 +119,27 @@ impl EntityFacadeImpl { None } - fn map_value_to_binary(value_type: &ValueType, value: &ElementValue) -> Option> { - if >::is_nil(value) { - return None; - } + fn map_value_to_binary(value_type: &ValueType, value: &ElementValue) -> Vec { match value_type { - ValueType::Bytes => Some(value.assert_bytes()), - ValueType::String => Some(value.assert_string().as_bytes().to_vec()), - ValueType::Number => Some(value.assert_number().to_string().as_bytes().to_vec()), - ValueType::Date => Some( - value - .assert_date() - .as_millis() - .to_string() - .as_bytes() - .to_vec(), - ), - ValueType::Boolean => Some(if value.assert_bool() { b"1" } else { b"0" }.to_vec()), - ValueType::GeneratedId => Some(value.assert_generated_id().0.as_bytes().to_vec()), - ValueType::CustomId => Some(value.assert_custom_id().0.as_bytes().to_vec()), - ValueType::CompressedString => unimplemented!("compressed string"), + ValueType::Bytes => value.assert_bytes(), + ValueType::String => value.assert_string().as_bytes().to_vec(), + ValueType::Number => value.assert_number().to_string().as_bytes().to_vec(), + ValueType::Date => value + .assert_date() + .as_millis() + .to_string() + .as_bytes() + .to_vec(), + + ValueType::Boolean => if value.assert_bool() { b"1" } else { b"0" }.to_vec(), + ValueType::GeneratedId => value.assert_generated_id().0.as_bytes().to_vec(), + ValueType::CustomId => value.assert_custom_id().0.as_bytes().to_vec(), + ValueType::CompressedString => { + Self::lz4_compress_plain_bytes(value.assert_string().as_bytes()) + // this limit should be checked before creating the instance/entity itself + // eg: client should not accept more input in mail editor once limit is reached + .expect("Unchecked input data? received too large byte for lz4_compression") + }, } } @@ -276,7 +285,7 @@ impl EntityFacadeImpl { for (&key, model_value) in &type_model.values { let stored_element = entity.remove(key).unwrap_or(ElementValue::Null); let MappedValue { value, iv, error } = - self.map_value(stored_element, session_key, key, model_value)?; + Self::decrypt_and_parse_value(stored_element, session_key, key, model_value)?; mapped_decrypted.insert(key.to_string(), value); if let Some(error) = error { @@ -414,8 +423,7 @@ impl EntityFacadeImpl { } } } - fn map_value( - &self, + fn decrypt_and_parse_value( value: ElementValue, session_key: &GenericAesKey, key: &str, @@ -455,7 +463,7 @@ impl EntityFacadeImpl { error_message: e.to_string(), })?; - match self.parse_decrypted_value(model_value.value_type.clone(), plaintext) { + match Self::parse_decrypted_value(model_value.value_type.clone(), plaintext) { Ok(value) => { // We want to ensure we use the same IV for final encrypted values, as this // will guarantee we get the same value back when we encrypt it. @@ -488,7 +496,6 @@ impl EntityFacadeImpl { } } fn parse_decrypted_value( - &self, value_type: ValueType, bytes: Vec, ) -> Result { @@ -504,7 +511,7 @@ impl EntityFacadeImpl { } else { // Encrypted numbers are encrypted strings. let string = String::from_utf8(bytes) - .map_err(|e| ApiCallError::internal_with_err(e, "Invalid string"))?; + .map_err(|e| ApiCallError::internal_with_err(e, "Invalid number string"))?; let number = string .parse() .map_err(|e| ApiCallError::internal_with_err(e, "Invalid number"))?; @@ -520,21 +527,65 @@ impl EntityFacadeImpl { u64::from_be_bytes(bytes), ))) }, - ValueType::Boolean => { - let value = match bytes.as_slice() { - b"0" => false, - b"1" => true, - _ => { - return Err(ApiCallError::InternalSdkError { - error_message: "Failed to parse boolean bytes".to_owned(), - }) - }, - }; - Ok(ElementValue::Bool(value)) + ValueType::Boolean => match bytes.as_slice() { + b"1" => Ok(ElementValue::Bool(true)), + b"0" => Ok(ElementValue::Bool(false)), + _ => Err(ApiCallError::InternalSdkError { + error_message: "Failed to parse boolean bytes".to_owned(), + }), }, - ValueType::CompressedString => unimplemented!("compressed string"), - v => unreachable!("Can't parse {v:?} into ElementValue"), + ValueType::CompressedString => { + let uncompressed_bytes = Self::lz4_decompress_decrypted_bytes(bytes.as_slice()) + .map_err(|e| { + ApiCallError::internal_with_err(e, "Cannot decompress compressed string") + })?; + + let uncompressed_string = String::from_utf8(uncompressed_bytes).map_err(|e| { + ApiCallError::internal_with_err( + e, + "Invalid utf-8 character in uncompressed string", + ) + })?; + + Ok(ElementValue::String(uncompressed_string)) + }, + + ValueType::GeneratedId | ValueType::CustomId => { + unreachable!("Cannot convert {value_type:?} to ElementValue"); + }, + } + } + + #[must_use] + pub fn lz4_compress_plain_bytes(bytes: &[u8]) -> Option> { + if bytes.is_empty() { + Some(Vec::new()) + } else if bytes.len() <= MAX_UNCOMPRESSED_INPUT_LZ4 { + Some(lz4_flex::compress(bytes)) + } else { + None + } + } + + pub fn lz4_decompress_decrypted_bytes( + compressed_bytes: &[u8], + ) -> Result, DecompressError> { + if compressed_bytes.is_empty() { + return Ok(Vec::new()); } + + // since we don't store the uncompressed size we have to guess how much memory we might + // need. + // 12 times the compressed size should work for almost all cases. + let mut uncompressed_bytes = lz4_flex::decompress(compressed_bytes, compressed_bytes.len()); + while let Err(DecompressError::OutputTooSmall { + actual, + expected: _, + }) = uncompressed_bytes + { + uncompressed_bytes = lz4_flex::decompress(compressed_bytes, actual * 2); + } + uncompressed_bytes } } @@ -564,6 +615,81 @@ impl EntityFacade for EntityFacadeImpl { } } +#[cfg(test)] +mod lz4_compressed_string_compatibility_tests { + use crate::entities::entity_facade::EntityFacadeImpl; + use base64::prelude::BASE64_STANDARD; + use base64::Engine; + use serde::Deserialize; + use std::io::BufReader; + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct TestData { + uncompressed_text: String, + compressed_base64_text_java: String, + compressed_base64_text_java_script: String, + compressed_base64_text_rust: String, + } + + #[test] + fn compatibility_with_js_and_java() { + const TEST_FILE: &str = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../../test/tests/api/worker/CompressionCompatibilityTestData.json" + ); + let test_data_sets: Vec = + serde_json::from_reader(BufReader::new(std::fs::File::open(TEST_FILE).unwrap())) + .unwrap(); + for test_data in test_data_sets.into_iter() { + let TestData { + uncompressed_text, + compressed_base64_text_java, + compressed_base64_text_java_script, + compressed_base64_text_rust, + } = test_data; + + let new_compressed_bas64_text_rust = BASE64_STANDARD.encode( + EntityFacadeImpl::lz4_compress_plain_bytes(uncompressed_text.as_bytes()).unwrap(), + ); + + assert_eq!(new_compressed_bas64_text_rust, compressed_base64_text_rust); + + let decompressed_bytes_java = EntityFacadeImpl::lz4_decompress_decrypted_bytes( + BASE64_STANDARD + .decode(compressed_base64_text_java.as_bytes()) + .unwrap() + .as_slice(), + ) + .unwrap(); + + let decompressed_bytes_javascript = EntityFacadeImpl::lz4_decompress_decrypted_bytes( + BASE64_STANDARD + .decode(compressed_base64_text_java_script.as_bytes()) + .unwrap() + .as_slice(), + ) + .unwrap(); + + let decompressed_bytes_rust = EntityFacadeImpl::lz4_decompress_decrypted_bytes( + BASE64_STANDARD + .decode(compressed_base64_text_rust.as_bytes()) + .unwrap() + .as_slice(), + ) + .unwrap(); + + assert_eq!(decompressed_bytes_java, uncompressed_text.as_bytes()); + assert_eq!(decompressed_bytes_javascript, uncompressed_text.as_bytes()); + assert_eq!(decompressed_bytes_rust, uncompressed_text.as_bytes()); + assert_eq!( + uncompressed_text.is_empty(), + decompressed_bytes_java.is_empty() + ); + } + } +} + #[cfg(test)] mod tests { use crate::crypto::crypto_facade::ResolvedSessionKey; @@ -573,7 +699,9 @@ mod tests { use crate::crypto::{aes::Iv, Aes256Key}; use crate::date::DateTime; use crate::element_value::{ElementValue, ParsedEntity}; - use crate::entities::entity_facade::{EntityFacade, EntityFacadeImpl}; + use crate::entities::entity_facade::{ + EntityFacade, EntityFacadeImpl, MappedValue, MAX_UNCOMPRESSED_INPUT_LZ4, + }; use crate::entities::sys::CustomerAccountTerminationRequest; use crate::entities::tutanota::Mail; use crate::entities::Entity; @@ -593,6 +721,17 @@ mod tests { 134, 182, 175, 15, 152, 117, 216, 81, 1, 120, 134, 116, 143, ]; + const ALL_VALUE_TYPES: &[ValueType] = &[ + ValueType::String, + ValueType::Number, + ValueType::Bytes, + ValueType::Date, + ValueType::Boolean, + ValueType::GeneratedId, + ValueType::CustomId, + ValueType::CompressedString, + ]; + #[test] fn test_decrypt_mail() { let sk = GenericAesKey::Aes256(Aes256Key::from_bytes(KNOWN_SK.as_slice()).unwrap()); @@ -688,6 +827,102 @@ mod tests { ); } + #[test] + fn decrypt_compressed_string() { + let model_value = create_model_value(ValueType::CompressedString, true, Cardinality::One); + let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); + let iv = Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)); + let value = ElementValue::String("this is a string value".to_string()); + + let encrypted_value = + EntityFacadeImpl::encrypt_value(&model_value, &value, &sk, iv.clone()).unwrap(); + + let decrypted_value = + EntityFacadeImpl::decrypt_and_parse_value(encrypted_value, &sk, "test", &model_value); + + assert_eq!( + Ok(MappedValue { + value: ElementValue::String("this is a string value".to_string()), + iv: Some(iv.get_inner().to_vec()), + error: None, + }), + decrypted_value + ); + } + + #[test] + fn decrypt_empty_compressed_string() { + let decrypted_value = EntityFacadeImpl::decrypt_and_parse_value( + ElementValue::String(String::default()), + &GenericAesKey::from_bytes(&KNOWN_SK).unwrap(), + "test", + &create_model_value(ValueType::CompressedString, true, Cardinality::One), + ) + .map(|a| a.value); + + assert_eq!(Ok(ElementValue::String(String::default())), decrypted_value); + } + + #[test] + fn compress_empty_compressed_string() { + let session_key = GenericAesKey::from_bytes(&KNOWN_SK).unwrap(); + let iv = Iv::from_bytes(&rand::random::<[u8; 16]>()).unwrap(); + let encrypted_value = EntityFacadeImpl::encrypt_value( + &create_model_value(ValueType::CompressedString, true, Cardinality::One), + &ElementValue::String(String::default()), + &session_key, + iv.clone(), + ); + + let encrypted_string = session_key + .encrypt_data(String::default().as_bytes(), iv) + .unwrap(); + assert_eq!(Ok(ElementValue::Bytes(encrypted_string)), encrypted_value); + } + + #[test] + fn should_not_compress_too_large_bytes() { + assert_eq!(MAX_UNCOMPRESSED_INPUT_LZ4, 0x7e000000); + let compressed_max_limit = EntityFacadeImpl::lz4_compress_plain_bytes( + b"a".repeat(MAX_UNCOMPRESSED_INPUT_LZ4).as_slice(), + ); + let compressed_over_limit = EntityFacadeImpl::lz4_compress_plain_bytes( + b"a".repeat(MAX_UNCOMPRESSED_INPUT_LZ4 + 1).as_slice(), + ); + + assert!(compressed_max_limit.is_some()); + assert_eq!(None, compressed_over_limit); + } + + #[test] + fn should_decompress_with_no_limit() { + // Construct some compressed text by ourselves instead of calling lz4_flex::compress + // to save ~10seconds ( in dev machine ) of test runtime + // compressed_over_limit is the output of: lz4_flex::compress(b"a".repeat(MAX_UNCOMPRESSED_INPUT).as_slice()); + let mut compressed_over_limit = vec![31, 97, 1, 0]; + let mut compressed_rest = vec![255; 8289918]; + let mut compressed_tail = vec![110, 96, 97, 97, 97, 97, 97, 97]; + compressed_over_limit.append(&mut compressed_rest); + compressed_over_limit.append(&mut compressed_tail); + + let decompressed = + EntityFacadeImpl::lz4_decompress_decrypted_bytes(compressed_over_limit.as_slice()) + .map(|a| a.len()) + .map_err(|_| ()); + + assert_eq!(Ok(MAX_UNCOMPRESSED_INPUT_LZ4 + 10), decompressed); + } + + #[test] + fn decrypt_compressed_string_with_resize() { + let input_bytes = b"test ".repeat(124); + let compressed = + EntityFacadeImpl::lz4_compress_plain_bytes(input_bytes.as_slice()).unwrap(); + let decompressed = + EntityFacadeImpl::lz4_decompress_decrypted_bytes(compressed.as_slice()).unwrap(); + assert_eq!(input_bytes.as_slice(), decompressed.as_slice()); + } + #[test] fn encrypt_value_string() { let model_value = create_model_value(ValueType::String, true, Cardinality::One); @@ -705,6 +940,28 @@ mod tests { assert_eq!(expected, encrypted_value.unwrap().assert_bytes()) } + #[test] + fn encrypt_value_compressed_string() { + let model_value = create_model_value(ValueType::CompressedString, true, Cardinality::One); + let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); + let iv = Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)); + + let value = ElementValue::String("Hello, world".to_string()); + + let encrypted_value = + EntityFacadeImpl::encrypt_value(&model_value, &value, &sk, iv.clone()) + .map(|a| a.assert_bytes()); + + let expected = sk + .clone() + .encrypt_data( + lz4_flex::compress(value.assert_string().as_bytes()).as_slice(), + iv.clone(), + ) + .unwrap(); + assert_eq!(Ok(expected), encrypted_value) + } + #[test] fn encrypt_value_bool() { let model_value = create_model_value(ValueType::Boolean, true, Cardinality::One); @@ -769,17 +1026,6 @@ mod tests { #[test] fn encrypt_value_null() { let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); - - const ALL_VALUE_TYPES: &[ValueType] = &[ - ValueType::String, - ValueType::Number, - ValueType::Bytes, - ValueType::Date, - ValueType::Boolean, - ValueType::GeneratedId, - ValueType::CustomId, - ValueType::CompressedString, - ]; for value_type in ALL_VALUE_TYPES { assert_eq!( ElementValue::Null, @@ -795,105 +1041,21 @@ mod tests { } #[test] - #[should_panic = "invalid encrypted value Null"] - fn encrypt_bytes_do_not_accept_null() { - let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); - assert_eq!( - Err(ApiCallError::internal( - "Value test with cardinality ONE can not be null".to_string() - )), - EntityFacadeImpl::encrypt_value( - &create_model_value(ValueType::Bytes, true, Cardinality::One), - &ElementValue::Null, - &sk, - Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)), - ) - ); - } - - #[test] - #[should_panic = "invalid encrypted value Null"] - fn encrypt_number_do_not_accept_null() { - let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); - assert_eq!( - Err(ApiCallError::internal( - "Value test with cardinality ONE can not be null".to_string() - )), - EntityFacadeImpl::encrypt_value( - &create_model_value(ValueType::Number, true, Cardinality::One), - &ElementValue::Null, - &sk, - Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)), - ) - ); - } - - #[test] - #[should_panic = "invalid encrypted value Null"] - fn encrypt_string_do_not_accept_null() { + fn encrypt_value_do_not_accept_null() { let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); - assert_eq!( - Err(ApiCallError::internal( - "Value test with cardinality ONE can not be null".to_string() - )), - EntityFacadeImpl::encrypt_value( - &create_model_value(ValueType::String, true, Cardinality::One), - &ElementValue::Null, - &sk, - Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)), - ) - ); - } - - #[test] - #[should_panic = "invalid encrypted value Null"] - fn encrypt_date_do_not_accept_null() { - let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); - assert_eq!( - Err(ApiCallError::internal( - "Value test with cardinality ONE can not be null".to_string() - )), - EntityFacadeImpl::encrypt_value( - &create_model_value(ValueType::Date, true, Cardinality::One), - &ElementValue::Null, - &sk, - Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)), - ) - ); - } - - #[test] - #[should_panic = "invalid encrypted value Null"] - fn encrypt_compressed_string_do_not_accept_null() { - let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); - assert_eq!( - Err(ApiCallError::internal( - "Value test with cardinality ONE can not be null".to_string() - )), - EntityFacadeImpl::encrypt_value( - &create_model_value(ValueType::CompressedString, true, Cardinality::One), - &ElementValue::Null, - &sk, - Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)), - ) - ); - } - - #[test] - #[should_panic = "invalid encrypted value Null"] - fn encrypt_boolean_do_not_accept_null() { - let sk = GenericAesKey::from_bytes(&[rand::random(); 32]).unwrap(); - assert_eq!( - Err(ApiCallError::internal( - "Value test with cardinality ONE can not be null".to_string() - )), - EntityFacadeImpl::encrypt_value( - &create_model_value(ValueType::Boolean, true, Cardinality::One), - &ElementValue::Null, - &sk, - Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)), - ) - ); + for value_type in ALL_VALUE_TYPES { + assert_eq!( + Err(ApiCallError::internal( + "Nil encrypted value is not accepted. ModelValue Id: 426".to_string() + )), + EntityFacadeImpl::encrypt_value( + &create_model_value(value_type.clone(), true, Cardinality::One), + &ElementValue::Null, + &sk, + Iv::generate(&RandomizerFacade::from_core(rand_core::OsRng)), + ) + ); + } } #[test]