“請(qǐng)問我怎么才能保證 Java 程序內(nèi)存中密碼的安全呢?”如果你也過有類似的問題,并且在網(wǎng)上搜到一些并不十分完善卻又無可奈何的答案,說明你就是 Java 程序安全性問題的 stakeholder。
這個(gè)問題的標(biāo)準(zhǔn)答案是 Java 機(jī)密計(jì)算技術(shù),它將機(jī)密計(jì)算技術(shù)引入 Java 的世界,為 Java 程序的安全性帶來了重大的提升。基于此,龍蜥社區(qū)云原生機(jī)密計(jì)算 SIG 推出了 Java 機(jī)密計(jì)算的具體實(shí)現(xiàn)技術(shù)——Teaclave Java TEE SDK, 以下簡(jiǎn)稱 Teaclave Java。該技術(shù)具有以下顯著優(yōu)點(diǎn):
全場(chǎng)景安全性。當(dāng)用戶有機(jī)密計(jì)算硬件支持時(shí),Teaclave Java 可以實(shí)現(xiàn)最高安全等級(jí)的 Java 可信計(jì)算;當(dāng)用戶沒有相關(guān)硬件時(shí),退化為 安全沙箱隔離 級(jí)別的可信計(jì)算,亦可有效保護(hù)用戶的敏感數(shù)據(jù)和計(jì)算過程的安全性。
開發(fā)和構(gòu)建簡(jiǎn)單。基于 SPI 的純 Java 編程模型,一鍵式構(gòu)建,將 Java 機(jī)密計(jì)算開發(fā)構(gòu)建門檻一降到底。
Teaclave Java 已經(jīng)過企業(yè)級(jí)內(nèi)部場(chǎng)景的驗(yàn)證,在 Apache 社區(qū)開源。描述本技術(shù)的論文由龍蜥社區(qū)云原生機(jī)密計(jì)算 SIG 與上海交通大學(xué)、大連理工大學(xué)合作發(fā)表在軟件工程頂會(huì) ICSE 2023(https://conf.researchr.org/home/icse-2023)上,并且獲得了本屆會(huì)議的 ACM SIGSOFT 杰出論文獎(jiǎng)。這是 2020 年以來,龍蜥社區(qū)云原生機(jī)密計(jì)算 SIG、上海交通大學(xué)、大連理工大學(xué)首次獲此殊榮。
問題的本質(zhì)
這個(gè)問題的本質(zhì)是如何在具有風(fēng)險(xiǎn)的運(yùn)行時(shí)環(huán)境中安全地使用敏感數(shù)據(jù)。當(dāng)我們?cè)谶\(yùn)行時(shí)將密碼解密后,密碼就會(huì)以明文的形式存在于內(nèi)存中,也就是 Java 的堆上,如圖 1 的左半部分所示。如果系統(tǒng)遭受攻擊,比如 2021 年聲名大噪的 log4j 漏洞攻擊,Java 堆上的內(nèi)容就會(huì)被竊取。即便沒有被攻擊,在為了性能診斷而做 heap dump 時(shí)也有可能主動(dòng)將敏感信息泄漏出去。所以將諸如密碼這樣的安全敏感信息暴露在普通運(yùn)行環(huán)境具有高度的風(fēng)險(xiǎn)。
(解圖 1 樸素的 Java 密碼保護(hù)示意圖)
一種保護(hù)思路是盡可能地縮短明文密碼在內(nèi)存中的存放時(shí)間,以縮短敏感信息暴露的時(shí)間窗口。
如圖 1 右半部分所示,在使用完密碼后,及時(shí)將其從內(nèi)存中銷毀,這樣會(huì)比先前更加安全一些。因?yàn)槊艽a是文本信息,會(huì)用字符串類 java.lang.String 保存。Java 的 String 是一種 immutable 類型,創(chuàng)建后不能更改內(nèi)容,所以沒有可以重置內(nèi)容的 API。要銷毀密碼只能通過反射將 String 類內(nèi)部保存字符內(nèi)容的數(shù)組內(nèi)容置空,從而將密碼內(nèi)容從 Java 堆上抹去。直接將密碼字符串設(shè)置為 null 是沒有用的,這樣只是把 String 變量的指針設(shè)為空,對(duì)于 Java 堆上的密碼數(shù)據(jù)沒有任何影響,只有等到下次垃圾回收時(shí)才有可能將密碼數(shù)據(jù)從堆中清除。
另一種方法是用 char 數(shù)組保存密碼,而不是 String 類,這樣就不必調(diào)用反射,讓銷毀更加便利。還有一種方法是用 byte 數(shù)組保存密碼,因?yàn)槠涿魑氖亲址幋a而非人可讀的字符,所以會(huì)更難被人看懂。
這些就是目前可以從網(wǎng)絡(luò)上搜到的解決方法,本文將它們稱為“樸素”的 Java 密碼保護(hù)方案。因?yàn)檫@些方案只是縮短了明文密碼在 Java 堆上的生存時(shí)間,并沒有真正將明文密碼保護(hù)起來。而且“及時(shí)”一詞有很大的彈性,開發(fā)人員未必能準(zhǔn)確地判斷出何時(shí)才是及時(shí)。
更具有典型性的案例就是著名的 log4j 漏洞問題(https://nvd.nist.gov/vuln/detail/CVE-2021-44228)。攻擊者可以利用 log4j 2.14 的漏洞將惡意 class 文件上傳到服務(wù)器并通過 Java 的動(dòng)態(tài)類加載機(jī)制運(yùn)行,從而竊取 Java 堆中保存的服務(wù)器私鑰。有了私鑰,服務(wù)器與客戶端之間的所有通信內(nèi)容對(duì)于攻擊者都如同明文了。
在以上兩個(gè)例子中,需要在運(yùn)行時(shí)保護(hù)的密碼和密鑰都是安全敏感的數(shù)據(jù)。而在實(shí)際場(chǎng)景中,保護(hù)范圍并不僅限于敏感數(shù)據(jù),還有可能擴(kuò)大到運(yùn)算過程。比如鑒權(quán)認(rèn)證場(chǎng)景中,需要保證認(rèn)證的過程可信,不能被攻擊者篡改。再比如云服務(wù)的用戶將自己的算法部署上云時(shí),雖然部署的制品可以加密,以保護(hù)傳輸和存儲(chǔ)時(shí)的安全,云廠商提供了固若金湯的安全防護(hù)以免受外部攻擊,但是用戶依然會(huì)擔(dān)心云廠商有沒有在運(yùn)行時(shí)窺探用戶的計(jì)算過程,是否存在監(jiān)守自盜的可能。
由此可見,保護(hù) Java 應(yīng)用中的安全敏感數(shù)據(jù)和運(yùn)算并不是一件遙遠(yuǎn)的需求,而是具有迫切的現(xiàn)實(shí)意義的需求。對(duì)于云計(jì)算的供應(yīng)商,讓用戶相信其敏感數(shù)據(jù)和運(yùn)算對(duì)于云廠商自己也是不可見的黑盒亦具有重大的商業(yè)價(jià)值。
Java 機(jī)密計(jì)算現(xiàn)狀
保護(hù)運(yùn)行時(shí)的敏感數(shù)據(jù)并不是一個(gè)新鮮話題,而屬于迄今已發(fā)展了 20 多年的技術(shù)——機(jī)密計(jì)算的一部分。機(jī)密計(jì)算是一種提供硬件級(jí)的系統(tǒng)隔離,以保障數(shù)據(jù)安全和程序運(yùn)行安全的技術(shù)。機(jī)密計(jì)算將執(zhí)行環(huán)境劃分為富執(zhí)行環(huán)境(Rich Execution Environment,REE)和可信執(zhí)行環(huán)境(Trusted Execution Environment, TEE),認(rèn)為 REE 和 TEE 應(yīng)該相互隔離,TEE 需要通過硬件加密以保證外界無法知曉其中的內(nèi)容。安全敏感的內(nèi)容應(yīng)該放在 TEE 中運(yùn)行,其他內(nèi)容則在 REE 中執(zhí)行。
這套機(jī)制早在 1999 年就已提出,不過早期的硬件加密技術(shù)能力有限,僅有支持執(zhí)行加解密程序的 TPM(可信任的平臺(tái)模塊,Trusted Platform Module)硬件。2008 年 Arm 發(fā)布 TrustZone 技術(shù)白皮書,以支持 Arm 平臺(tái)的通用型機(jī)密計(jì)算任務(wù)。2015 年 Intel 也推出了帶有支持通用應(yīng)用加密的 SGX(軟件保護(hù)擴(kuò)展,Software Guard Extension)芯片的硬件設(shè)備,2021 年 SGX 升級(jí)為可支持 1T 內(nèi)存、具有更高性能的 SGX2。
機(jī)密計(jì)算的核心理念是在具有被攻擊風(fēng)險(xiǎn)的運(yùn)行時(shí)環(huán)境中提供一塊安全區(qū)域供安全敏感程序運(yùn)行,實(shí)現(xiàn)了安全敏感數(shù)據(jù)和程序在傳輸、存儲(chǔ)和計(jì)算全流程的安全可信。目前機(jī)密計(jì)算在隱私安全、區(qū)塊鏈、多方計(jì)算、IoT 和邊緣設(shè)備,以及個(gè)人計(jì)算設(shè)備上均有廣泛的應(yīng)用和廣闊的前景。
看起來機(jī)密計(jì)算技術(shù)正是解決 Java 程序安全性問題的標(biāo)準(zhǔn)答案,那么我們是否能夠在 Java 應(yīng)用中應(yīng)用機(jī)密計(jì)算技術(shù)呢?
Occlum – 在 TEE 中放入 JVM 和應(yīng)用整體
SGX、TrustZone 等為通用型機(jī)密計(jì)算提供了硬件基礎(chǔ),Intel、微軟等開源的驅(qū)動(dòng)和 SDK 則為通用型機(jī)密計(jì)算提供了軟件基礎(chǔ)。基于這些軟硬件基礎(chǔ),開發(fā)者已經(jīng)可以在軟件應(yīng)用中使用機(jī)密計(jì)算。但是機(jī)密計(jì)算對(duì)于 Java 應(yīng)用并不友好,因?yàn)?TEE 中只能運(yùn)行 native 程序,所以 Java 程序并不能直接運(yùn)行于 TEE 中。要在 TEE 中運(yùn)行 Java 程序,就必須先在 TEE 中啟動(dòng)一個(gè) JVM,然后在 JVM 上執(zhí)行 Java 程序。 那么是否能在 TEE 中運(yùn)行 JVM 呢?答案是肯定的,那就是 Occlum,其原理如圖 2 所示。
Occlum 是介于 TEE 底層 SDK 與 JVM 之間的一層 LibOS,作為操作系統(tǒng)支持普通 JVM 在 TEE 中的運(yùn)行。 用戶將包含了機(jī)密代碼在內(nèi)的整個(gè) Java 程序部署在 TEE 中,由 Occlum 支持 JVM 執(zhí)行。圖 2 右半部分給出了部署的結(jié)構(gòu),其中黃色的 APP 代表整個(gè) Java 應(yīng)用及其所需三方庫(kù),紅色圓圈代表可信代碼。應(yīng)用通過 REE 中的啟動(dòng)器——通常只是一個(gè)很小的命令行工具,啟動(dòng)執(zhí)行。這種方案的兼容性好,用戶基本不需要修改原有代碼即可獲得機(jī)密計(jì)算支持。但是缺點(diǎn)也很明顯——放入 TEE 的代碼太多,會(huì)導(dǎo)致兩個(gè)問題:
安全性下降。 原本需要在 TEE 中執(zhí)行的可信程序可能并不多,但是此方案需要將所有的 Java 程序、三方庫(kù)、JVM 和 LibOS 全部放入 TEE,導(dǎo)致 TCB(可信計(jì)算基,Trusted Computing Base)太大,安全性并不理想。TCB 是安全領(lǐng)域衡量安全性的重要指標(biāo),指信任的代碼量。TCB 越大,其中可能存在安全隱患的代碼就越多,程序的安全性就越差,所以 TCB 越小越好。以 log4j 攻擊為例,Occlum 仍然無法對(duì)其免疫。因?yàn)?log4j 庫(kù)與機(jī)密代碼并沒有被分隔在不同的執(zhí)行環(huán)境中,而都部署在 TEE 中,所以攻擊者上傳的惡意類文件也會(huì)位于 TEE 中,仍然可以從內(nèi)存中訪問到私鑰。
性能下降。TEE 的硬件不是通用硬件,與 REE 相比存在性能退化,所以將應(yīng)用整體放入 TEE 中會(huì)導(dǎo)致整個(gè)應(yīng)用的性能下降。但用戶原本的需求只是局部加密,為了局部加密而導(dǎo)致整體性能下降會(huì)增大應(yīng)用機(jī)密計(jì)算的成本。雖然一般用戶可以接受為了安全而產(chǎn)生的部分性能退化,但是對(duì)于過度加密產(chǎn)生的額外性能退化會(huì)感到難以接受。
圖片(圖 2 Occlum 原理示意圖)
綜上可見,Occlum 方案雖然具有簡(jiǎn)單易行的優(yōu)勢(shì),但是其在安全性和性能方面的缺點(diǎn)卻是其投入實(shí)際應(yīng)用的主要障礙。
Teaclave Java TEE SDK –? 在 TEE 中僅放入可信代碼
因?yàn)樵?TEE 中整體支持 JVM 和全部應(yīng)用程序的方案會(huì)在 TEE 中執(zhí)行過多的代碼,導(dǎo)致安全性和性能下降而難以投入實(shí)用,能不能換種思路,僅將可信代碼放入 TEE 呢?考慮到 TEE 中只能執(zhí)行 native code,那么是不是可以將可信代碼從 Java 代碼直接編譯為 native code 放入 TEE 運(yùn)行呢?答案是肯定的,這就是本文的主角 Teaclave Java TEE SDK,以下簡(jiǎn)稱 Teaclave Java。
Teaclave Java 是由 JVM 團(tuán)隊(duì)開發(fā)的 Java 機(jī)密計(jì)算開發(fā)框架和構(gòu)建工具鏈,可以一站式快速實(shí)現(xiàn) Java 機(jī)密計(jì)算應(yīng)用的開發(fā)和構(gòu)建。退一步考慮,即使用戶沒有支持機(jī)密計(jì)算的硬件環(huán)境,Teaclave Java 也可以實(shí)現(xiàn)安全沙箱隔離,有效保障敏感數(shù)據(jù)和程序的運(yùn)行時(shí)安全。
Teaclave Java 的關(guān)鍵技術(shù)特性有:
模塊分隔、機(jī)密計(jì)算服務(wù)化,如圖 3 所示。
簡(jiǎn)潔完善的機(jī)密計(jì)算服務(wù)生命周期管理 API。
Java 靜態(tài)編譯機(jī)密內(nèi)容。
隱藏實(shí)現(xiàn)細(xì)節(jié)、自動(dòng)生成所有輔助代碼。
在這些技術(shù)的支持下,Teaclave Java 能夠?qū)钠胀K到機(jī)密模塊的 Java 模塊間服務(wù)調(diào)用轉(zhuǎn)為從普通模塊到機(jī)密 native 庫(kù)的函數(shù)調(diào)用,如圖 4 所示。
模塊分隔、機(jī)密計(jì)算服務(wù)化
Teaclave Java 將應(yīng)用代碼分為三個(gè)模塊,Host、Enclave 和 Common。Host 中是普通的安全非敏感程序,Enclave 中是安全敏感程序,Common 中則是前兩者都會(huì)用到的公共代碼。這種模塊劃分方式一是為了讓開發(fā)者感知到代碼的安全性區(qū)分,二是為了構(gòu)建時(shí)針對(duì)不同模塊使用不同工具鏈的便利性。
Host 和 Enclave 是解耦合的,它們之間只能通過 Java 的 SPI(Service Provider Interface)機(jī)制交互,而不能直接調(diào)用。機(jī)密計(jì)算的實(shí)現(xiàn)在 Enclave 模塊中被封裝成為了服務(wù),其接口聲明定義在 Common 模塊中,并用 @EnclaveService 注解標(biāo)識(shí)。當(dāng) Host 中的程序需要用到某一機(jī)密計(jì)算任務(wù)時(shí),就可以先加載服務(wù)實(shí)例,再調(diào)用相應(yīng)的函數(shù)。這一結(jié)構(gòu)組織關(guān)系如圖 3 所示。
(圖 3 Teaclave Java 的開發(fā)視圖)
例如我們可以在 Common 中聲明一個(gè)如代碼塊 1 所示的機(jī)密計(jì)算服務(wù)接口,其中提供了用于認(rèn)證加密的密碼是否有效的 API,authenticate 函數(shù)。該函數(shù)接受一個(gè)用戶傳入的加密的密碼,返回該密碼的認(rèn)證結(jié)果。
代碼塊 1:在 Common 模塊定義機(jī)密計(jì)算服務(wù)接口聲明示例
@EnclaveService public interface AuthenticationService { /** * Given an encrypted input password, check if it is the correct password. * @param inputPwd the encrypted password to be authenticated * @return true if the given password is correct. */ boolean authenticate(String inputPwd); }
AuthenticationService 接口的具體實(shí)現(xiàn)則在 Enclave 模塊的 AuthenticationServiceImpl 類中定義,如代碼塊 2 所示。該類的 authenticate 函數(shù)先使用私鑰對(duì)輸入的加密字符串解密,獲得明文結(jié)果,然后將其和內(nèi)存中保存的正確的密碼比對(duì),再返回是否一致的檢查結(jié)果。該類中保存的正確密碼值和私鑰都是安全敏感數(shù)據(jù),authenticate 函數(shù)的實(shí)現(xiàn)也是安全敏感運(yùn)算。它們都將在 TEE 中運(yùn)行,以黑盒的形式提供給外部使用。從外部只能看到加密的輸入數(shù)據(jù)和返回的判定結(jié)果,而無法窺探到實(shí)際的運(yùn)行過程和數(shù)據(jù)。
代碼塊 2 在 Enclave 模塊定義機(jī)密計(jì)算服務(wù)接口實(shí)現(xiàn)示例
public class AuthenticationServiceImpl implements AuthenticationService { private String pwd = "somePwd"; // assume it's got at runtime. @Override public boolean authenticate(String inputPwd) { String decryptedInputPwd = decrypt(inputPwd); return pwd.equals(decryptedInputPwd); } private static String decrypt(String inputPwd) { return inputPwd; // assume it's decrypted with private key } }
Host 模塊使用機(jī)密計(jì)算服務(wù)的代碼示例如代碼塊 3 所示,從中可以看到對(duì)機(jī)密計(jì)算服務(wù) AuthenticationService 接口的使用和普通的 SPI 接口別無二致,依然是加載服務(wù)、調(diào)用函數(shù)、根據(jù)結(jié)果執(zhí)行不同的動(dòng)作等過程。稍有區(qū)別的地方在于先要?jiǎng)?chuàng)建出機(jī)密計(jì)算環(huán)境 Enclave 的實(shí)例,然后從中加載機(jī)密計(jì)算服務(wù)實(shí)例,由此將機(jī)密計(jì)算的服務(wù)實(shí)例和環(huán)境實(shí)例綁定,最后再銷毀環(huán)境。這些機(jī)密計(jì)算環(huán)境生命周期管理的 API 由 Teaclave Java 提供。從代碼塊 3 中可見,在 Host 模塊中無需感知密碼和私鑰究竟是什么,也不用了解認(rèn)證的過程,只是將認(rèn)證函數(shù)當(dāng)作黑盒服務(wù)調(diào)用。
代碼塊 3 從 Host 模塊使用機(jī)密計(jì)算服務(wù)示例
public class Main { public static void main(String[] args) throws Exception { Enclave enclave = EnclaveFactory.create(); Iteratorservices = enclave.load(AuthenticationService.class); String pwd = "encryptedPwd"; // assume this is an encrypted password while (services.hasNext()) { AuthenticationService authenticationService = services.next(); if (authenticationService.authenticate(pwd)) { System.out.println("Passed"); } else { System.out.println("Rejected"); } } enclave.destroy(); } }
以上三部分代碼就構(gòu)成了一個(gè)完整的 Java 機(jī)密計(jì)算應(yīng)用。從開發(fā)的角度看起來與編寫一個(gè)普通的 SPI 服務(wù)調(diào)用的應(yīng)用基本一樣,只需要專注于業(yè)務(wù)邏輯的開發(fā)即可,并不需要學(xué)習(xí)機(jī)密計(jì)算底層的內(nèi)容。因此 Teaclave Java 將 Java 機(jī)密計(jì)算的開發(fā)門檻降低到了 0。
構(gòu)建機(jī)密計(jì)算應(yīng)用
Teaclave Java 提供了一套完整的構(gòu)建工具鏈以支持上文所述的編程模型,用戶只需輸入幾個(gè)簡(jiǎn)單的 maven 命令即可完成全部構(gòu)建任務(wù)。構(gòu)建工具鏈將非機(jī)密代碼和機(jī)密代碼分別編譯為 Java bytecode 產(chǎn)物和可部署于 SGX 中的 native 庫(kù),以及自動(dòng)生成完成機(jī)密計(jì)算服務(wù)調(diào)用所需的所有的輔助代碼。
圖 4 展示了 Teaclave Java 的構(gòu)建部署視圖,主要包括三方面內(nèi)容:
1)Host 和 Common 模塊被編譯為普通的 Java bytecode,部署在普通環(huán)境中執(zhí)行。
2)Enclave 和它所使用到的 Common 模塊中的內(nèi)容被編譯為 native 機(jī)密庫(kù)文件,部署在 SGX 硬件中執(zhí)行。
3)從 Java bytecode 到 native code 之間并不能直接調(diào)用,而需要一些適配轉(zhuǎn)換工作,包括:
服務(wù)代理:通過 J ava 的動(dòng)態(tài)代理機(jī)制將 Host 模塊中的機(jī)密計(jì)算服務(wù)調(diào)用代理到實(shí)際的 native 函數(shù)上,并完成上下文環(huán)境的同步、服務(wù)參數(shù)和返回值的序列化反序列化等工作。
JNI 層:Java 側(cè)的 native 函數(shù)聲明、native 側(cè)的 JNI 函數(shù)聲明和到機(jī)密庫(kù)函數(shù)的調(diào)用等輔助代碼。
(圖 4 Teaclave Java 部署視圖)
這些適配轉(zhuǎn)換調(diào)用的代碼在構(gòu)建中被自動(dòng)生成,分別部署在普通環(huán)境和 SGX 中,在圖 4 中它們被用藍(lán)色標(biāo)出。
構(gòu)建過程中的重要一步是將機(jī)密部分的 Java 代碼編譯為 native 代碼的 Java 靜態(tài)編譯。
Java 靜態(tài)編譯
Java 程序原本需要在 JVM 上才能運(yùn)行,但是 Java 靜態(tài)編譯技術(shù)可以將 Java 程序(包括 JDK 庫(kù)依賴和三方庫(kù)依賴)加上必要的運(yùn)行時(shí)支持代碼一起編譯為 native 代碼,然后直接運(yùn)行。以此實(shí)現(xiàn)了 Java 程序無需 JVM 的輕量級(jí)運(yùn)行。
Teaclave Java 采用了目前最成熟的 Java 靜態(tài)編譯技術(shù)——Oracle 主導(dǎo)的開源項(xiàng)目 GraalVM 進(jìn)行 Java 靜態(tài)編譯。GraalVM 首先對(duì) Java 程序做可達(dá)性分析,找到從程序入口開始的所有可能執(zhí)行到的代碼范圍,然后僅編譯這些可達(dá)的代碼,得到一個(gè) native 制品(被稱為 native image)。程序入口對(duì)于可執(zhí)行程序來說是 main 函數(shù),對(duì)于庫(kù)文件來說是暴露的公共 API。具體到 Teaclave Java 場(chǎng)景,入口就是開發(fā)者定義的機(jī)密計(jì)算服務(wù)函數(shù),也就是 Enclave 模塊中定義的機(jī)密計(jì)算服務(wù)接口的實(shí)現(xiàn)函數(shù)。這些接口實(shí)現(xiàn)會(huì)用到三種依賴,Common 模塊中的代碼、某些 JDK 庫(kù)以及其他 Java 三方庫(kù),但是只會(huì)用到這些依賴的部分代碼,而非全部代碼。
GraalVM 就會(huì)將實(shí)際用到的代碼分析出來,與機(jī)密計(jì)算服務(wù)的實(shí)現(xiàn)代碼和 GraalVM 提供的運(yùn)行時(shí)支持(被稱為 Substrate VM)一起編譯為 native image。但是 GraalVM 是面向通用場(chǎng)景和硬件平臺(tái)的,所以 Teaclave Java 為其額外提供了針對(duì) SGX 硬件平臺(tái)的適配和機(jī)密計(jì)算需求的優(yōu)化。當(dāng)我們編譯出 native image 后,會(huì)發(fā)現(xiàn)其具有了一些特別的性質(zhì):
TCB 下降。GraalVM 僅編譯從機(jī)密計(jì)算服務(wù)入口可達(dá)的代碼,因此與 Occlum 將 LibOS、JVM 和 Java 應(yīng)用全部放入 TEE 的方案相比,TCB 大幅降低了。
安全性提升。Native image 在運(yùn)行時(shí)有自己的 native 內(nèi)存堆,它與 Java 堆是相互隔離的,從 Java 應(yīng)用中很難被訪問到(Java 通過 Unsafe 接口依然可以訪問 native 內(nèi)存,但是難度提升很多)。而且 Java 靜態(tài)編譯去掉了 Java 的動(dòng)態(tài)特性,只有在編譯時(shí)經(jīng)過顯式配置的反射和動(dòng)態(tài)類加載才會(huì)生效,其他運(yùn)行時(shí)的動(dòng)態(tài)行為是無效的。Log4j 漏洞攻擊在 native image 上本身就是無效的。因此 native image 可以被視作一個(gè)安全沙箱,即使沒有 SGX 硬件環(huán)境,native image 相比 Java 程序也提升了安全性。部署在 SGX 里之后,TEE 的安全性會(huì)更高,因?yàn)橄?Java 動(dòng)態(tài)特性對(duì) TEE 安全性的威脅。
性能提升。GraalVM 的 Java 靜態(tài)編譯對(duì)代碼有相當(dāng)程度的編譯優(yōu)化,其運(yùn)行時(shí)性能大致可以達(dá)到 JVM 的 C1 優(yōu)化水平,再加上無需啟動(dòng) JVM、沒有類加載過程、沒有解釋執(zhí)行、沒有 JIT 消耗資源等等,在執(zhí)行短小的任務(wù)時(shí)與 Java 程序相比能有 1 個(gè)數(shù)量級(jí)的性能提升,內(nèi)存也有大幅削減。
這些性質(zhì)可以有效地提升機(jī)密程序的安全性,提升了 Teaclave Java 的實(shí)用性。
Teaclave Java 技術(shù)評(píng)估
以上介紹了 Teaclave Java 提供的 Java 機(jī)密計(jì)算編程模型和采用的構(gòu)建方式等技術(shù)問題,那么最終實(shí)現(xiàn)的效果如何呢?本文以 log4j 漏洞攻擊為例分析 Teaclave Java 的功能有效性。
在 TCB 改進(jìn)和運(yùn)行時(shí)性能分析方面,我們準(zhǔn)備了如表格 1 所示的 10 個(gè)測(cè)試。前 4 個(gè)“app-”前綴的是我們自己寫的簡(jiǎn)單應(yīng)用,將它們當(dāng)作機(jī)密程序,以各自的 main 入口當(dāng)作普通程序。后 6 個(gè)“ct-”前綴的用例則采用了著名開源加密框架 BouncyCastle(https://www.bouncycastle.org/java.html)的單元測(cè)試,我們提供了單一入口調(diào)用這些測(cè)試,將測(cè)試入口當(dāng)作普通程序,單元測(cè)試當(dāng)作機(jī)密程序。
(表 1 / 測(cè)試用例描述)
Java 機(jī)密計(jì)算框架則采用了 OcclumJ 和 Teaclave Java 進(jìn)行對(duì)比。OcclumJ 是我們實(shí)現(xiàn)的一種介于 Occlum 和 Teaclave Java 之間的機(jī)密計(jì)算模型,采用 Teaclave Java 的模塊化和機(jī)密計(jì)算服務(wù)化的模型,但是不做 Java 靜態(tài)編譯,而是在 TEE 中以 Occlum 方式運(yùn)行機(jī)密計(jì)算服務(wù)。
功能有效性評(píng)估
圖 5 給出了 log4j 漏洞攻擊的原理示意(a 子圖)和 Teaclave Java 防范 log4j 漏洞攻擊的原理(b 子圖)。對(duì)于一個(gè)普通的 Java 應(yīng)用服務(wù),它和客戶端通過三個(gè)步驟交互。
1)客戶端從服務(wù)端獲取公鑰。
2)客戶端用公鑰對(duì)消息加密,然后將密文發(fā)送給服務(wù)端。
3)服務(wù)端從運(yùn)行時(shí)內(nèi)存中拿出私鑰解密消息,然后再處理消息內(nèi)容。假設(shè)服務(wù)端使用了 log4j-2.14.x 版本做日志記錄,其中的漏洞允許攻擊者誘導(dǎo) log4j 從遠(yuǎn)程服務(wù)器下載指定的惡意 class 文件(圖 5-a 中的步驟 4、5、6),然后動(dòng)態(tài)加載惡意類,從 Java 堆內(nèi)存上獲取到私鑰(步驟 7)傳給攻擊者。
有了服務(wù)器的私鑰,服務(wù)器和客戶端之間的所有通信對(duì)于攻擊者而言都如同明文了。
a. log4j 漏洞攻擊示意
b. 通過機(jī)密計(jì)算免疫 log4j 漏洞攻擊示意
(圖 5 / Java 機(jī)密計(jì)算保護(hù)應(yīng)用免 受 Log4j 漏洞攻擊示意圖)
圖 5-b 展示了 Teaclave Java 如何保護(hù)應(yīng)用服務(wù)端免受 log4j 漏洞攻擊的威脅。Teaclave Java 將應(yīng)用的普通代碼放在 REE 中,安全敏感的解密和私鑰放在 TEE 中,客戶端送來加密消息會(huì)被 REE 中的代理服務(wù)轉(zhuǎn)到 TEE 中進(jìn)行解密。此時(shí)當(dāng)攻擊者發(fā)起 log4j 攻擊時(shí),因?yàn)?Log4j 部署在 REE 中,惡意代碼也只能在 REE 中運(yùn)行,而無法拿到 TEE 內(nèi)存上的私鑰,攻擊失效。假如機(jī)密代碼也使用了 log4j 記日志,導(dǎo)致 log4j 運(yùn)行在 TEE 中運(yùn)行會(huì)發(fā)生什么呢?
此時(shí) log4j 將攻擊者惡意代碼下載到了 TEE 中,但是因?yàn)?Teaclave Java 采用了 Java 靜態(tài)編譯技術(shù),惡意代碼在編譯時(shí)是未知的,不會(huì)被編譯到 native image 中。而 Java 靜態(tài)編譯技術(shù)并不支持對(duì) native image 中不存在的代碼進(jìn)行動(dòng)態(tài)加載執(zhí)行,所以即便惡意類被下載到了 TEE,也不會(huì)被執(zhí)行。因此在這種場(chǎng)景下 Teaclave Java 支持的機(jī)密計(jì)算依然是安全的。但是如果采用了 Occlum 方案,因?yàn)?TEE 中有了 JVM,就可以動(dòng)態(tài)加載惡意代碼并運(yùn)行,攻擊就會(huì)成功。
再退一步,當(dāng)在沒有 SGX 硬件為 TEE 加密時(shí),native image 依然是一個(gè) native 沙箱,惡意 Java 代碼無法輕易從 native 內(nèi)存中拿到安全敏感內(nèi)容。
TCB 評(píng)估
因?yàn)?Teaclave Java 不再需要 LibOS 和 JVM,機(jī)密代碼部分也是按需編譯部署。OcclumJ 方案雖然采用了分模塊模型,但是并沒有做靜態(tài)分析,因此只是模塊級(jí)別的代碼劃分,雖然較 Occlum 完全不劃分有所改進(jìn),但是與 Teaclave Java 函數(shù)級(jí)的劃分相比仍有相當(dāng)大的差距。圖 6 展示了 OcclumJ 和 Teaclave Java 放入 TEE 的二進(jìn)制編譯產(chǎn)物的大小對(duì)比。藍(lán)條是 OcclumJ 的結(jié)果,橙條是 Teaclave Java 的結(jié)果,圖中的 Lejacon 是 Teaclave Java 在論文中的代號(hào)。
(圖 6 Occlum 和 T eaclave Java 方案的 TCB 比較。Lejacon 是 Teaclave Java 在論文中的代號(hào))
由圖 6 中的對(duì)比數(shù)據(jù)可見,Teaclave Java 的編譯后 TCB 大小僅為 Occlum 的大約 1/20 到 1/10。考慮到編譯時(shí) native 代碼的膨脹問題,兩者實(shí)際的函數(shù)數(shù)量差距更大,所以 Teaclave Java 的 TCB 低于 Occlum 一個(gè)數(shù)量級(jí),從而具有了更高的安全性。
運(yùn)行時(shí)性能評(píng)估
因?yàn)?native image 會(huì)直接以 native 代碼的形式運(yùn)行,省去了包括 JVM 啟動(dòng)、類加載、解釋執(zhí)行等步驟的 Java 程序的冷啟動(dòng)過程,所以啟動(dòng)速度會(huì)非常快。如果要執(zhí)行的機(jī)密代碼較少,會(huì)很快執(zhí)行完畢。但是 native image 的代碼編譯質(zhì)量不如 JVM 的 C2,所以當(dāng)程序執(zhí)行的時(shí)間足夠長(zhǎng),Java 代碼被 JIT 充分編譯后,native image 的運(yùn)行時(shí)性能就會(huì)隨著時(shí)間的增長(zhǎng)而與 Java 程序越來越接近,然后被超越。所以 Teaclave Java 在小型應(yīng)用的性能遠(yuǎn)優(yōu)于 OcclumJ,但是在長(zhǎng)時(shí)間執(zhí)行的應(yīng)用方面該優(yōu)勢(shì)就會(huì)縮小。
圖 7 就展現(xiàn)出了這一特點(diǎn)。圖中的藍(lán)線是機(jī)密代碼部分采用 OcclumJ 模型的執(zhí)行時(shí)間,黃線是采用 Teaclave Java 模型的執(zhí)行時(shí)間,綠線是在普通環(huán)境中在普通 JVM 上直接運(yùn)行的時(shí)間。程序在 TEE 中運(yùn)行的時(shí)間要大于普通環(huán)境,主要因?yàn)樵黾恿藱C(jī)密環(huán)境的創(chuàng)建、機(jī)密內(nèi)存的分配等開銷,我們將其統(tǒng)稱為機(jī)密環(huán)境開銷。黃線在執(zhí)行時(shí)間較短的場(chǎng)景中保持了與綠線接近的性能,說明 Java 程序冷啟動(dòng)的開銷與 native ?image 的機(jī)密環(huán)境開銷差不多可以相抵。當(dāng)程序執(zhí)行時(shí)間較長(zhǎng)時(shí),冷啟動(dòng)開銷被攤薄,但是機(jī)密環(huán)境開銷與 TEE 內(nèi)存使用量成正比,所以黃線較綠線在最后三個(gè)測(cè)試用例上的上升線條更陡峭。
圖 8 給出了 OcclumJ 和 Teaclave Java 的運(yùn)行時(shí)內(nèi)存使用量對(duì)比。OcclumJ 的內(nèi)存消耗包括 LibOS、JVM 和應(yīng)用三部分,而 Teaclave Java 模型的內(nèi)存消耗只有應(yīng)用和 native image 中的輕量級(jí)運(yùn)行時(shí)。更簡(jiǎn)化的結(jié)構(gòu)為 Teaclave Java 模式的機(jī)密計(jì)算帶來了更少的內(nèi)存消耗。
(圖 7 OcclumJ 和 Teaclave Java 運(yùn)行時(shí)性能對(duì)比圖。Lejacon 是 Teaclave Java 在論文中的代號(hào))
(圖 8 / OcclumJ 與 Teaclave Java 的運(yùn)行時(shí)內(nèi)存消耗對(duì)比圖。Lejacon 是 Teaclave Java 在論文中的代號(hào))
總? 結(jié)
Teaclave Java 是一個(gè)使用簡(jiǎn)單、效果顯著、性能良好的 Java 機(jī)密計(jì)算解決方案,能夠幫助用戶徹底解決保護(hù) Java 應(yīng)用中的安全敏感內(nèi)容和運(yùn)算的問題。Teaclave Java 具有硬件寬容性,當(dāng)具備 SGX 硬件環(huán)境時(shí),可以使 Java 用戶也能像其他 native 語言用戶一樣享受到機(jī)密計(jì)算帶來的 最高等級(jí)運(yùn)行時(shí)安全保護(hù);在缺少機(jī)密計(jì)算的硬件環(huán)境時(shí),仍然可以提供 安全沙箱 對(duì)機(jī)密代碼實(shí)施內(nèi)存隔離,以避免安全敏感內(nèi)容直接暴露。可以說,Teaclave Java 就是保護(hù) Java 應(yīng)用中敏感數(shù)據(jù)和運(yùn)算安全的標(biāo)準(zhǔn)答案。
Oracle 已經(jīng)把 GraalVM 的 Java 靜態(tài)編譯技術(shù)貢獻(xiàn)給了 OpenJDK,預(yù)計(jì)在 JDK 21 會(huì)合入 OpenJDK 主干。因此在未來 Teaclave Java 方案就可以獲得 JDK 的原生支持。我們也計(jì)劃向 Java 社區(qū)提交關(guān)于增加機(jī)密計(jì)算規(guī)范的文件,希望可以將 Teaclave Java 的機(jī)密計(jì)算模型上升為 Java 原生的機(jī)密計(jì)算方案。
編輯:黃飛
?
評(píng)論
查看更多