[Security] 判別式存取控制表 (Discretionary Access Control List) 實作:旗標的概念

在前一篇文章中,我簡單的說明了 DACL 的資料結構組成,以及簡單的存取方法,如果資料長度愈長的話,可以設定的權限就愈多,也可以有更多的組合,但資料量仍然可以控制在很小的長度,這是因為在 DACL 中,每個權限都是一個旗標值 (flag),而一個 bit 就代表一個旗標,所以 bit 多的話旗標就愈多,一個 byte 可以容納 8 個 bits,也就是可以有 8 種旗標,之所以會選擇位元來作為旗標,是因為 bit 的值不是 0 就是 1,單純有力。

在前一篇文章中,我簡單的說明了 DACL 的資料結構組成,以及簡單的存取方法,如果資料長度愈長的話,可以設定的權限就愈多,也可以有更多的組合,但資料量仍然可以控制在很小的長度,這是因為在 DACL 中,每個權限都是一個旗標值 (flag),而一個 bit 就代表一個旗標,所以 bit 多的話旗標就愈多,一個 byte 可以容納 8 個 bits,也就是可以有 8 種旗標,之所以會選擇位元來作為旗標,是因為 bit 的值不是 0 就是 1,單純有力。

例如,如果系統中有 C, R, U, D 四種權限,那麼我們可以定義一個 byte 中的前 4 個位元分別代表 C, R, U, D 的權限,所以可以有這樣的設計:

byte _permission = new byte();
this._permission = 0x02; // Read permission.

這時 _permission 的 bit 分布是 00000010 (0x02),而第二個 bit R 的值為 1,代表允許 R 的權限,但 C, U, D 的權限是拒絕的。如果現在我們要加入 D 的權限 (00001000 = 0x08) 時,我們只要這樣做:

this._permission = this._permission | 0x08; // OR operator.

此時 _permission 的 bit 分布會變成 00001010 (0x0a),第二和第四個 bit 均為 1,表示允許 R 和 D 的授權,而應用程式只要這樣做驗證:

bool allowResult = (this._permission & 0x02) == 0x02; // AND operator.

只要將權限表的 bit 值和要檢查的權限值做 AND 運算,學過計算機概論的人應該都知道,AND 運算之下只有兩個都是 1 時才會回傳 1 (true),否則回傳 0 (false),所以 _permission 和 0x02 的 AND 運算後,如果是允許授權的使用者,一定會得到 0x02 的結果值,否則會得到 0x00,表示沒有獲得授權。

當然,我們也可以直接進行複合授權的檢驗,例如 0x0a (同時檢查有沒有 R 和 D 的權限),一樣經過 AND 運算,只要檢查運算過的值等於 0x0a,就表示有被授權,否則就是沒有授權。

另外,我們可以善用列舉值來表示權限,例如:

public enum AccessPermissions
{
    Create = 0x01 << 0,
    Read   = 0x01 << 1,
    Update = 0x01 << 2,
    Delete = 0x01 << 3

}

<< 運算子是向左移動 bit 數的指令,0x01 << 0 表示不移動bit  (0001),而 0x01 << 1 表示向左移動 1 個 bit (0010),0x01 << 2 為 0100,0x01 << 3 則是 1000,有了列舉值以後,程式碼就可以變得更有可讀性:

bool allowResult = (this._permission & AccessPermissions.Read) == AccessPermissions.Read; // AND operator.

一個 byte 最多可以放到 256 (值域 0-255, 0xff, 11111111),所以如果列舉權限要超過 8 個的話,可能就需要使用兩個 byte 來存放,而且要用兩個權限列舉,順帶提一件事,API 中有所謂的高位元區 (high level bits) 與低位元區 (low level bits) 的說法,這和 CPU 與資料的排列方式有關,以本文所指的例子,如果今天有 16 種權限要設定,就會需要用到兩個 bytes,則本例會用 00000000 00000000,且由右往左讀的順序,這個方式稱為 Little Endian (反向則是稱為 Big Endian),因此在 bit stream 中,第一個 bit 一定是最右邊的 bit,然後往左邊移動,所以在前面的例子 00001010 是 C = 0, R = 1, U = 0, D = 1,而 00000011 為 C = 1, R = 1, U = 0, D = 0。在處理位 bit stream 時要注意這一點,否則很容易看錯。有點離題了,前面提到列舉如果超過 8 個的話,在編譯時會收到值不可超過 255 的錯誤訊息。

在 .NET Framework 中也有一個對應的功能,稱為 FlagAttribute,我們可以將上面的列舉改成:

[Flag]
public enum AccessPermissions
{
    Create = 0x01,
    Read   = 0x02,
    Update = 0x04,
    Delete = 0x08

}

.NET Framework 會自動將值視為位元列舉 (bit enumeration) 值,和使用 "<<" 有相同的效果。

之所以花這些篇幅介紹位元排列,是因為它會在設計 DACL 時扮演重要角色。

Reference:

Endianness: http://en.wikipedia.org/wiki/Endianness
FlagAttribute: http://weblogs.asp.net/wim/archive/2004/04/07/109095.aspx