DRM(Digital Rights Management)是一個成熟的操作系統(tǒng)中必須實現(xiàn)的功能。DRM提供的功能正如其字面的意思,可以幫助保護(hù)數(shù)字版權(quán);目前最直接的一個應(yīng)用就是對在線播放的媒體流進(jìn)行保護(hù)。在Android下DRM相關(guān)的代碼被放置在了多媒體的架構(gòu)當(dāng)中。
安卓的DRM架構(gòu)目前常見的實現(xiàn)有兩種。
經(jīng)典的Android DRM Framework架構(gòu);
現(xiàn)在用的比較多的mediaDRM實現(xiàn)。
DRM Framework架構(gòu)圖
MediaDrm流程及其工作流程圖
這兩者的區(qū)別是DRM Framework考慮的是通用DRM實現(xiàn);舉例來說,當(dāng)播放一個媒體源的時候,會有一些初始的與服務(wù)器交互得到的數(shù)據(jù)被DRM Manager所解析,來判斷是否含有DRM信息;如果包含相關(guān)信息,則對應(yīng)已注冊的DRM Plugin會被選中用來處理DRM流程;并且在流程完畢以后負(fù)責(zé)媒體流的解密。
而mediaDRM則在簡化了流程。mediaDRM的API設(shè)計主要是為了對接ISO/IEC 23001-7: Common Encryption(縮寫CENC)標(biāo)準(zhǔn);CENC定義了如何獲得一個媒體流解密所需要的密鑰的流程和數(shù)據(jù)格式。這個標(biāo)準(zhǔn)相對簡潔,不過這個標(biāo)準(zhǔn)是收費的,筆者也沒有能閱讀詳細(xì)的內(nèi)容,只能從代碼上略知一二。舉例說明,當(dāng)播放一個媒體流的時候,這個媒體流事先就定義好是哪種符合CENC標(biāo)準(zhǔn)的DRM場景(前面的DRM Framework中有一個嗅探的過程);對于此種DRM場景,Media Framework會直接去查找相應(yīng)的mediaDRM插件來處理與服務(wù)器的交互,并且流程和信息都遵照CENC標(biāo)準(zhǔn)(DRM Framework中考慮的是通用實現(xiàn),比如一種全新的DRM場景);最后得到密鑰,來進(jìn)行媒體流解密。
mediaDRM對于Player應(yīng)用來說使用起來相對簡單。很多常見的DRM實現(xiàn)基本使用這種方法。比如Widevine; Playready等。而且谷歌的開源播放器Exoplayer可以直接用來測試mediaDRM實現(xiàn)。
Android下實現(xiàn)了一個簡單的開源mediaDRM 插件: ClearKey;讀者可以通過研究這個插件而對mediaDRM的接口有所了解。ClearKey的路徑在:frameworks/av/drm/mediadrm/plugins/clearkey/
由于需要比較好的實現(xiàn)DRM功能;并且現(xiàn)在的操作系統(tǒng)大多為開放式操作系統(tǒng),被破解或者root的概率是相當(dāng)?shù)母撸凰訢RM對設(shè)備上從解密到播放的這一條通路都做了要求;要求媒體流數(shù)據(jù)從解密,被解碼到顯示的過程中一概不能被泄露;WidevineL1之類對此都有嚴(yán)格的要求。這種從解密到顯示的通路稱為Video Path;而保證安全的通路則稱為Secure Video Path。
實現(xiàn)過程
對于通用的mediaDRM架構(gòu),比如上文提到的ClearKey;或者商用的DRM場景比如Widevine或者Playready;DRM交互協(xié)議部分基本已經(jīng)實現(xiàn),留下的與設(shè)備的密鑰相關(guān)的操作一般需要被放置在一個安全的環(huán)境里進(jìn)行。OEM一般需要閱讀DRM場景的文檔,配合DRM場景的要求實現(xiàn)OEM必須要實現(xiàn)的模塊。實現(xiàn)這些模塊是為了達(dá)到以下兩個目的:
1. 將安全系統(tǒng)與DRM框架對接,以實現(xiàn)DRM框架所必須的安全功能;比如保護(hù)設(shè)備私鑰等。常見的做法有使用硬件安全環(huán)境;或者運行在可信執(zhí)行環(huán)境(TEE)的安全操作系統(tǒng)(Secure OS)。
保護(hù)密鑰是最基本最重要的DRM要求。Widevine L2就是要求保護(hù)密鑰;L1則是保護(hù)密鑰+Secure Video Path;而L3基本只是為了測試Widevine協(xié)議而存在,既不保護(hù)密鑰也不保護(hù)Video Path;
密鑰的產(chǎn)生和維護(hù)過程,又是另一個安全相關(guān)的主題;在這篇文章里不做贅述。
2.實現(xiàn)一個安全通路使得從解密開始直到被顯示都是安全的。
為了達(dá)到這兩個目的,以下組件需要進(jìn)行必要的增加或者修改。
安全內(nèi)存
要點:
實現(xiàn)安全內(nèi)存分配器(比如ION Heap)
實現(xiàn)安全內(nèi)存所需的配套設(shè)施(Secure Boot, TEE, Bootloader)
為了保存解密后的媒體流,為解碼和顯示做好準(zhǔn)備,安全內(nèi)存必須被提供。安全內(nèi)存有許多實現(xiàn)方式。使用防火墻或者內(nèi)存保護(hù)單元(MPU – Memory Protection Unit)是比較常見的方法。而對這些安全內(nèi)存進(jìn)行分配和使用的操作,Android提供了ION這個組件。
ION是一個安卓下統(tǒng)一的堆(Heap)管理接口。使用ION可以靈活的實現(xiàn)一些特定的內(nèi)存管理器;正適合作為管理安全內(nèi)存的接口。ION的實現(xiàn)基于DmaBuf;后者是一套內(nèi)核API,可以實現(xiàn)在進(jìn)程間的Dma內(nèi)存共享;ION在內(nèi)核API的基礎(chǔ)上提供了接口供應(yīng)用程序調(diào)用(/dev/ion);使得用戶程序也能夠分配在進(jìn)程間共享的Dma內(nèi)存。
最簡單的安全內(nèi)存實現(xiàn)則是在內(nèi)存中預(yù)留一塊區(qū)域為安全內(nèi)存;使用MPU對此地址范圍的內(nèi)存進(jìn)行保護(hù),將不合格的存取請求拒絕。這一塊預(yù)留的內(nèi)存可以使用ION Heap管理起來;讓用戶程序可以在這個Heap里分配和釋放內(nèi)存;然而,僅僅是分配釋放;想Memory Map以后再進(jìn)行存取,是不可以的(MPU會拒絕非安全存取)。
MPU的規(guī)則只能在安全模式下定義;一般可以放在更早的啟動組件里進(jìn)行(Bootloader);如果具有動態(tài)內(nèi)存權(quán)限設(shè)置功能的MPU,對MPU規(guī)則的設(shè)置可以放在Secure OS里完成。為了保證系統(tǒng)的完整性,安全啟動(Secure Boot)必須被打開,驗證Bootloader和Secure OS的完整性;防止非法篡改。
Linux中預(yù)留內(nèi)存有多種方法。使用顯式的內(nèi)存預(yù)留是一種方法,參見dts代碼:
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* reserve memory for secure heap */
carveout: carveout@60000000 {
compatible = "ion,heap_secure";
reg = <0x0 0x60000000 0x0 0x02000000>;
};
}
在上面的例子中,使用了carveout類型;carveout類型總體和安全內(nèi)存的需求接近;但是Carveout Heap在分配的時候會負(fù)責(zé)清零;而非安全CPU訪問內(nèi)存是被MPU禁止的。所以需要一些改動,去除這些直接訪問內(nèi)存的地方。
經(jīng)過以上一些列的設(shè)置,系統(tǒng)中的安全內(nèi)存就被管理了起來。
目前常見的Android內(nèi)核中,都為經(jīng)典的ION接口API(alloc, free, map),這種方式有一個問題就是所有的Heap ID都是Hard Code。當(dāng)用戶在ION中添加了一個新Heap,則一個新的Heap ID需要被添加到ion.h中;然后復(fù)制到Android的bionic內(nèi)核頭文件的目錄中;再運行腳本,將這個更新的頭文件被復(fù)制到其他的lib頭文件中(比如libion)。這樣帶來一些問題,一是因為在ion.h中,經(jīng)典的代碼把Heap Id和Heap Type給關(guān)聯(lián)了起來;實際上這二者是獨立的意義;二是Android使用repo管理很多的git倉庫;假如使用前面修改ion.h的方法,一個簡單的添加Heap Id的改動起碼會影響三個左右的git倉庫。所以在比較新的內(nèi)核中ION添加了一個方法enumerate;使用這個方法可以得到當(dāng)前所有的ION Heap的描述,根據(jù)描述得到目標(biāo)Heap的ID,避免了頻繁修改ion.h的問題。條件允許的話,建議大家盡量更新到后面的版本。
安全解密系統(tǒng)
要點:
實現(xiàn)在安全環(huán)境里解密并且將結(jié)果放入安全內(nèi)存的操作
嚴(yán)格檢查目標(biāo)地址是否為安全地址
加密的媒體流是放在非安全內(nèi)存里的。這部分的內(nèi)容被解密以后結(jié)果會被放置到一個安全的環(huán)境里;同時這個解密的過程,也需要在一個安全的環(huán)境里。這里就涉及到安全解密系統(tǒng)。安全解密系統(tǒng)往往都是DRM實現(xiàn)的一部分。因為:
DRM流程中需要用到與設(shè)備有關(guān)的密鑰來進(jìn)行加解密行為。
解密媒體流所用的密鑰最后也是在安全環(huán)境里被算出,并且解密過程需要在安全環(huán)境中進(jìn)行。
目前通用的做法是將安全解密系統(tǒng)實現(xiàn)在安全操作系統(tǒng)中(Secure OS);在支持Arm Trustzone的芯片架構(gòu)下,Secure OS可以訪問系統(tǒng)的所有資源;在Secure OS中對加密的媒體流進(jìn)行解密是比較適合的。另外還有其他類似的解決方案,比如硬件的安全加解密環(huán)境等。安全解密系統(tǒng)的職責(zé)就是解密,并且把數(shù)據(jù)放在安全內(nèi)存中。這里比較重要的地方是,由于解密系統(tǒng)實際上是第一道檢查安全內(nèi)存的關(guān)卡,它有一個重要的責(zé)任就是,確認(rèn)解密的目的地,必須是安全的。它需要檢查目的地的范圍和屬性。
有一點需要說明的是,在Android中,解密系統(tǒng)是第一個處理媒體流的模塊;但是它所使用的安全內(nèi)存,是由視頻解碼器調(diào)用安全內(nèi)存的接口(ION Heap)來分配的。
視頻解碼器
要點:
修改Codec組件函數(shù)enumerateComponents宣告支持Secure Codec類型
修改Codec組件函數(shù)makeComponentInstance支持創(chuàng)建Secure Codec實例
修改media_codecs.xml使得secure codec能夠被Player枚舉
修改內(nèi)存分配函數(shù),使得為Secure Codec實例分配安全內(nèi)存成為可能
視頻解碼器需要支持安全解碼;安全解碼器能夠存取安全內(nèi)存。另一個重要的特點是,安全解碼器,不能夠存取普通內(nèi)存。這是一個重要的原則,否則安全解碼器就有可能將媒體流泄露到非安全內(nèi)存中。在Android播放器一般的初始化流程中,初始化mediaCodec的時候,會為這個mediaCodec對象設(shè)置一個輸出Surface:
codec.setOutputSurface(surface);
在上面一小節(jié)的介紹中,安全解密系統(tǒng)已經(jīng)將解密后的媒體流放在了安全內(nèi)存中等待解碼。這個安全內(nèi)存是由Codec組件分配,并且在調(diào)用解密函數(shù)的時候,傳給安全解密系統(tǒng)的。這個存放待解碼的媒體流的Buffer稱為Input Buffer;在這里,由于需要使用安全內(nèi)存,這里的Input Buffer是分配至安全內(nèi)存的(通過調(diào)用ION接口);解碼完成后放置幀數(shù)據(jù)的內(nèi)存則來自Surface.
Android下為Secure Video Path所預(yù)留的設(shè)計是:當(dāng)一個安全解碼器被需要并且成功加載的時候,Android會激活整個Secure Video Path所需要的flag。安全解碼器是否被需要,一般在mediaDrm Plugin的代碼里會指定:
class CryptoPlugin : public android::CryptoPlugin{
...
virtual bool requiresSecureDecoderComponent(const char* mime) const {
/* TODO: check mime type */
return true;
}
...
}
如果DRM插件返回true的話,Player的一個職責(zé)就是需要初始化必要的安全解碼器。安全解碼器的名稱,則是在普通的解碼器名稱后加上了一個后綴”.secure”。系統(tǒng)中所支持的解碼器,都列在了media_codecs.xml中。下面的例子展示了如何添加一個安全解碼器:
其次在Codec的enumerateComponents中,需要在Media Framework中注冊自己所支持的Codec類型。除了通常的decoder和encoder,decoder.secure是需要添加支持的。
Player在根據(jù)所需要的解碼器的mimeType,找到可用的Secure Codec以后,會去進(jìn)行初始化。在初始化函數(shù)makeComponentInstance中,需要能夠分配Secure Codec實例。一般來說,這個函數(shù)可以和普通的Codec的makeComponentInstance復(fù)用;只是發(fā)現(xiàn)Codec名稱為”.secure”結(jié)尾的時候,在Codec Component內(nèi)部的數(shù)據(jù)結(jié)構(gòu)中置上一個Secure標(biāo)志;以便后面分配內(nèi)存的時候,能夠知道當(dāng)前的Codec Component是不是安全解碼器:
解碼器組件在初始化實例的時候,需要提供實例所支持的接口給Media Framework,這里使用SoftOMXComponent的代碼作為例子;在硬件解碼器的代碼里也有類似的代碼:
各種必要的函數(shù)需要被提供。這里需要關(guān)注的就是AllocateBuffer函數(shù)。這個函數(shù)在一些情況之下會被調(diào)用用來分配Buffer。
Codec Component初始化完成的時候的時候,Media Framework就會發(fā)現(xiàn)Player剛初始化了安全解碼器,于是它就會將Secure Video Path上所要用到的組件置上相應(yīng)的Flag:
在這里幾個標(biāo)志的作用:
kFlagIsSecure標(biāo)志決定了Input Buffer需要來自安全內(nèi)存。由于Media Framework并不知道安全內(nèi)存的具體實現(xiàn);在遇到需要分配安全內(nèi)存的情況下,F(xiàn)ramework則會去調(diào)用Codec Component提供的AllocateBuffer函數(shù)。
所有的Surface內(nèi)存都是由Gralloc來進(jìn)行分配。kFlagIsGrallocUsageProtected標(biāo)志決定了當(dāng)使用Gralloc來分配Surface內(nèi)存的時候,Gralloc需要支持從安全內(nèi)存分配器分配內(nèi)存。使用安全內(nèi)存的Surface一般稱呼為Protected Surface.
kFlagPushBlankBuffersToNativeWindowOnShutdown表示在Surface無效的時候,顯示空白的畫面;而不是之前尚存在于Surface中的數(shù)據(jù)。
最終在AllocateBufferWrapper中,Component通過檢查secure標(biāo)志來決定是否要從安全內(nèi)存中分配一塊區(qū)域并返回:
安全內(nèi)存被分配以后,其handle將被在安全解密系統(tǒng)(DRM進(jìn)程)和多媒體(Media進(jìn)程)之間傳遞。安全解密系統(tǒng)通過ION的API可以獲得安全內(nèi)存的地址,來進(jìn)行解密操作。而Codec的驅(qū)動也可以獲得安全內(nèi)存的地址,將其作為DMA地址來進(jìn)行解碼。
圖形和顯示系統(tǒng)和Gralloc
要點:
實現(xiàn)支持安全復(fù)合的硬件顯示設(shè)備(HwComposor)
在Gralloc()分配安全內(nèi)存給具有GRALLOC_USAGE_PROTECTED標(biāo)志的分配請求
如果不能實現(xiàn)安全的GPU,則將GPU隔離在Secure Video Path之外
解碼后用于顯示的Surface由SurfaceFlinger進(jìn)程創(chuàng)建而來。在解碼器組件被實例化以后,所需要分配的Surface被放置上了保護(hù)flag:
這個保護(hù)flag最后在分配Surface所需要使用的內(nèi)存的時候,會被傳遞到Gralloc模塊里。Gralloc模塊負(fù)責(zé)分配所有與顯示相關(guān)的內(nèi)存。在Gralloc模塊的代碼里,會根據(jù)傳入的flag選擇適當(dāng)?shù)膬?nèi)存分配器。檢查到 GRALLOC_USAGE_PROTECTED標(biāo)志,在本文的例子中,則會去使用ION申請一塊安全內(nèi)存。
硬件復(fù)合器負(fù)責(zé)對硬件的Layer進(jìn)行復(fù)合,并且顯示最終結(jié)果;其組件名稱為HwCompsor;一般存在于系統(tǒng)分區(qū)(/vendor/lib/hw/hwcomposer.xxxx.so).GPU則是負(fù)責(zé)圖形繪制和渲染的引擎。使用硬件復(fù)合器可以減輕GPU負(fù)擔(dān)。
含有解碼后內(nèi)容的Surface一般直接就會被復(fù)合后輸出。在以下情況下,GPU會操作這個Surface:
Player對輸出的Surface進(jìn)行了特效或者貼圖等后期處理;
Surface所在的Layer (這里為Protected Layer)的特性不符合硬件復(fù)合器的要求;復(fù)合操作被Reject,GPU將負(fù)責(zé)這個Layer的復(fù)合操作。
在Secure Video Path中硬件復(fù)合最好能夠被滿足;因為軟件復(fù)合意味著CPU將可以存取Protected Surface的內(nèi)容。MPU也會拒絕CPU對保護(hù)內(nèi)存的訪問。如果不能夠被滿足,那么使用Secure state CPU來進(jìn)行復(fù)合操作,則會導(dǎo)致整個多媒體框架實現(xiàn)的復(fù)雜度。
在Android的Surfaceflinger中,不會對Protected Layer進(jìn)行復(fù)合操作;遇到Protected Layer就會顯示黑屏。這也是Surfaceflinger知道自己可能無法訪問安全內(nèi)存而做出的一個保險的行為。
所以想要改動最少的實現(xiàn)Secure Video Path,則這點需要被滿足:
確保Protected Layer的特性不會被硬件復(fù)合器拒絕。可以使用dumpsys SurfaceFlinger查看原因;如果復(fù)合器在dump函數(shù)中記錄了Reject Reason的話。通常被拒絕的原因是顏色格式不支持;或者要做Downscale。Upscale一般沒限制。所以播放的媒體流的分辨率,最好不要超過屏幕的分辨率。
安全內(nèi)存File Descriptor在進(jìn)程間的傳遞
要點:
使用native_handle作為安全內(nèi)存的Handle類型
除了Codec,DRM安全解密系統(tǒng)也需要在用戶端操作安全內(nèi)存句柄。在Android 7.0 (Android N)開始,DRM Server (mediaDRM所在的進(jìn)程)和Media Service不在一個進(jìn)程里;Codec組件無論是自己調(diào)用ION接口分配的函數(shù);還是調(diào)用一個管理安全內(nèi)存的動態(tài)庫分配的函數(shù),安全內(nèi)存所對應(yīng)的File Descriptor(以下簡稱FD)都只在被分配的進(jìn)程里有效;同樣的FD數(shù)值被傳遞到另一個進(jìn)程會導(dǎo)致得不到安全內(nèi)存的信息而不能操作。
在Android中,Binder服務(wù)可以幫助傳遞FD去別的進(jìn)程;它可以在目標(biāo)進(jìn)程里映射一個新的FD。在新建一個Parcel的時候,如果類型是BINDER_TYPE_FD,則Binder驅(qū)動會映射一個目標(biāo)FD。
在Codec的內(nèi)存分配函數(shù)AllocateBufferWrapper中,由于它可接受的句柄類型,并不接受FD,只有如下所示的三種類型;所以無法直接返回一個FD給AllocateBufferWrapper的調(diào)用者(Media Framework)。
其中Secure Codec所使用的安全內(nèi)存句柄只能為后面兩種。其中,kSecureBufferTypeNativeHandle就是為FD的傳遞而包裹的一個類型。這個類型可以幫進(jìn)程傳遞一個或者多個FD去另一個進(jìn)程。當(dāng)Media Framework檢測到安全內(nèi)存類型為kSecureBufferTypeNativeHandle的時候,它會調(diào)用相應(yīng)的處理函數(shù)來處理。分配內(nèi)存的偽代碼請參考上方Pseudo AllocateBufferWrapper的代碼段部分。在DRM進(jìn)程里請參考:system/core/include/cutils/native_handle.h里面的函數(shù);基本上只要取出native_handle_t里面FD數(shù)組里的成員,就是在當(dāng)前進(jìn)程里可以訪問的安全內(nèi)存FD.
硬件所要具備的條件
安全內(nèi)存的實現(xiàn),離不開硬件。硬件需要做到以下幾點:
每個硬件需要有不同的ID來表示自己。
具有防火墻功能,能夠鑒別訪問內(nèi)存的硬件ID,并且根據(jù)ID和防火墻規(guī)則來處理訪問權(quán)限。
需要訪問普通內(nèi)存和安全內(nèi)存的硬件,需要有多種ID,適時切換ID。
能訪問安全內(nèi)存的ID,不能夠去訪問普通內(nèi)存;反之亦然。
硬件復(fù)合器這樣的硬件,不能對兩種內(nèi)存有寫權(quán)限。
假如一個非安全的解碼器假裝是安全的解碼器,它是否能夠偷取信息?只有真正安全的解碼器,才能夠訪問安全內(nèi)存,這是由MPU所保證的。假如非安全的解碼器任意分配了一塊內(nèi)存冒充安全的解碼器,安全解密器會檢查內(nèi)存的屬性進(jìn)而發(fā)現(xiàn)這種冒用;假如它真的分配了安全內(nèi)存(安全內(nèi)存誰都可以分配)但是最終只有HwComposor能夠讀取內(nèi)容并且顯示;其他的非安全模塊均不能存取這塊內(nèi)存。
為何大多使用靜態(tài)預(yù)留的方式實現(xiàn)安全內(nèi)存?因為預(yù)留的方式簡單;MPU僅僅使用范圍檢查就能知道內(nèi)存的屬性;而動態(tài)分配安全內(nèi)存的方法,經(jīng)常需要修改內(nèi)存的屬性,稍有疏漏就會留下安全漏洞。
后續(xù)
DRM本身的意義,越來越薄弱。因為版權(quán)保護(hù)意識的增強,防范越來越不重要。但是針對DRM保護(hù)的技術(shù),繼續(xù)會產(chǎn)生巨大的用途,比如在隱私保護(hù)等領(lǐng)域。舉例,人臉識別算法中的視頻和中間數(shù)據(jù),是有相當(dāng)?shù)囊饬x來保護(hù)它的。Secure Video Path的存在是相當(dāng)有必要的。
-
Android
+關(guān)注
關(guān)注
12文章
3941瀏覽量
127718 -
DRM
+關(guān)注
關(guān)注
0文章
46瀏覽量
15134
原文標(biāo)題:ARM劉永康: 淺談Android數(shù)字版權(quán)管理之視頻保護(hù)
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論