一、? ? ? ? 前言
Linux加密框架是內核安全子系統的重要組成部份,同時,它又一個的獨立子系統形式出現,從它出現在內核根目錄下的crypto/就可以看出其地位了。
Crypto實現較為復雜,其主要體現在其OOP的設計思路和高度的對像抽像與封裝模型,作者展現了其出色的架構設計水準和面向對像的抽像能力。本文力圖從加密框架的重要應用,即IPSec(xfrm)的兩個重要協議AH和ESP對加密框架的使用,展現其設計與實現。
內核版本:2.6.31.13
二、? ? ? ? 算法模版
1.? ? ? ? 模版的基本概念
算法模版是加密框架的第一個重要概念。內核中有很多算法是動態生成的,例如cbc(des)算法。內核并不存在這樣的算法,它事實上是cbc和des的組合,但是內核加密框架從統一抽像管理的角度。將cbc(des)看做一個算法,在實際使用時動態分配并向內核注冊該算法。這樣,可以將cbc抽像為一個模版,它可以同任意的加密算法進行組合。算法模版使用結構crypto_template來描述,其結構原型:
點擊(此處)折疊或打開
struct crypto_template?{
struct list_head list;?//模版鏈表成員,用于注冊
struct hlist_head instances;?//算法實例鏈表首部
struct module?*module;?//模塊指針
struct crypto_instance?*(*alloc)(struct rtattr?**tb);?//算法實例分配
void?(*free)(struct crypto_instance?*inst);?//算法實例釋放
char name[CRYPTO_MAX_ALG_NAME];?//模版名稱
};
例如,一個名為cbc的算法模版,可以用它來動態分配cbc(des),cbc(twofish)……諸如此類。
crypto/algapi.c下包含了模版的一些常用操作。最為常見的就是模版的注冊與注銷,其實質是對以crypto_template_list為首的鏈表的操作過程:
點擊(此處)折疊或打開
static LIST_HEAD(crypto_template_list);
int?crypto_register_template(struct crypto_template?*tmpl)
{
struct crypto_template?*q;
int?err?=?-EEXIST;
down_write(&crypto_alg_sem);
//遍歷crypto_template_list,看當前模板是否被注冊
list_for_each_entry(q,?&crypto_template_list,?list)?{
if?(q?==?tmpl)
goto out;
}
//注冊之
list_add(&tmpl->list,?&crypto_template_list);
//事件通告
crypto_notify(CRYPTO_MSG_TMPL_REGISTER,?tmpl);
err?=?0;
out:
up_write(&crypto_alg_sem);
return?err;
}
EXPORT_SYMBOL_GPL(crypto_register_template);
注銷算法模版,除了模版本身,還有一個重要的內容是處理算法模版產生的算法實例,關于算法實例,后文詳述。
點擊(此處)折疊或打開
void crypto_unregister_template(struct crypto_template?*tmpl)
{
struct crypto_instance?*inst;
struct hlist_node?*p,?*n;
struct hlist_head?*list;
LIST_HEAD(users);
down_write(&crypto_alg_sem);
BUG_ON(list_empty(&tmpl->list));
//注銷算法模版,并重新初始化模版的list成員
list_del_init(&tmpl->list);
//首先移除模版上的所有算法實例
list?=?&tmpl->instances;
hlist_for_each_entry(inst,?p,?list,?list)?{
int?err?=?crypto_remove_alg(&inst->alg,?&users);
BUG_ON(err);
}
crypto_notify(CRYPTO_MSG_TMPL_UNREGISTER,?tmpl);
up_write(&crypto_alg_sem);
//釋放模版的所有算法實例分配的內存
hlist_for_each_entry_safe(inst,?p,?n,?list,?list)?{
BUG_ON(atomic_read(&inst->alg.cra_refcnt)?!=?1);
tmpl->free(inst);
}
crypto_remove_final(&users);
}
EXPORT_SYMBOL_GPL(crypto_unregister_template);
2.? ? ? ? 算法模版的查找
點擊(此處)折疊或打開
crypto_lookup_template函數根據名稱,查找相應的模版:
struct crypto_template?*crypto_lookup_template(const?char?*name)
{
return try_then_request_module(__crypto_lookup_template(name),?name);
}
__crypto_lookup_template完成實質的模版模找工作,而try_then_request_module則嘗試動態插入相應的內核模塊,如果需要的話:
點擊(此處)折疊或打開
static struct crypto_template?*__crypto_lookup_template(const?char?*name)
{
struct crypto_template?*q,?*tmpl?=?NULL;
down_read(&crypto_alg_sem);
//遍歷crypto_template_list鏈,匹備模版名稱
list_for_each_entry(q,?&crypto_template_list,?list)?{
if?(strcmp(q->name,?name))
continue;
//查找命中,需要對其增加引用,以防止其正在使用時,模塊被卸載。完成該操作后返回查找到的模版
if?(unlikely(!crypto_tmpl_get(q)))
continue;
tmpl?=?q;
break;
}
up_read(&crypto_alg_sem);
return tmpl;
}
3.? ? ? ? 模版的算法實例分配時機
模版可以看做一個靜態的概念,其只有被動態創建后才具有生命力,本文將模版通過alloc分配創建的算法(對像)稱為“實例(instance)”。
算法模版的核心作用是,上層調用者構造一個完整合法的算法名稱,如hmac(md5),觸發模版的alloc動作,為該名稱分配一個算法實例,類似于為類實例化一個對像,最終的目的還是使用算法本身。對于xfrm來說,一個典型的算法模版的實例分配觸發流程如下所述:
xfrm包裹了一層加密框架支持,參后文“ xfrm加密框架”一節,其算法查找函數為xfrm_find_algo,它調用crypto_has_alg函數進行算法的查找,以驗證自己支持的算法是否被內核支持,如xfrm支持cbc(des),但此時并不知道內核是否有這個算法(如果該算法首次被使用,則還沒有分配算法實例)。crypto_has_alg會調用crypto_alg_mod_lookup完成查找工作,crypto_alg_mod_lookup函數查找不命中,會調用crypto_probing_notify函數進行請求探測:
點擊(此處)折疊或打開
struct crypto_alg?*crypto_alg_mod_lookup(const?char?*name,?u32 type,?u32 mask)
{
……
ok?=?crypto_probing_notify(CRYPTO_MSG_ALG_REQUEST,?larval);
……
}
請求是通過通知鏈表來通告的:
點擊(此處)折疊或打開
int?crypto_probing_notify(unsigned long val,?void?*v)
{
int?ok;
ok?=?blocking_notifier_call_chain(&crypto_chain,?val,?v);
if?(ok?==?NOTIFY_DONE)?{
request_module("cryptomgr");
ok?=?blocking_notifier_call_chain(&crypto_chain,?val,?v);
}
return ok;
}
在algboss.c中注冊了一個名為cryptomgr_notifier的通告塊結構,其通告處理函數為cryptomgr_notify
點擊(此處)折疊或打開
static struct notifier_block cryptomgr_notifier?=?{
.notifier_call?=?cryptomgr_notify,
};
static?int?__init cryptomgr_init(void)
{
return crypto_register_notifier(&cryptomgr_notifier);
}
static void __exit cryptomgr_exit(void)
{
int?err?=?crypto_unregister_notifier(&cryptomgr_notifier);
BUG_ON(err);
}
這樣,當有算法被使用的時候,會調用通告塊的處理函數cryptomgr_notify,因為此時的消息是CRYPTO_MSG_ALG_REQUEST,所以cryptomgr_schedule_probe進行算法的探測:
點擊(此處)折疊或打開
static?int?cryptomgr_notify(struct notifier_block?*this,?unsigned long msg,
void?*data)
{
switch?(msg)?{
case?CRYPTO_MSG_ALG_REQUEST:
return cryptomgr_schedule_probe(data);
……
return NOTIFY_DONE;
}
cryptomgr_schedule_probe啟動一個名為cryptomgr_probe的內核線程來進行算法模版的探測:
點擊(此處)折疊或打開
static?int?cryptomgr_schedule_probe(struct crypto_larval?*larval)
{
……
//構造param,以供后面使用
……
thread?=?kthread_run(cryptomgr_probe,?param,?"cryptomgr_probe");
……
}
cryptomgr_probe完成具體的算法探測過程:
點擊(此處)折疊或打開
static?int?cryptomgr_probe(void?*data)
{
struct cryptomgr_param?*param?=?data;
struct crypto_template?*tmpl;
struct crypto_instance?*inst;
int?err;
//查找算法模版
tmpl?=?crypto_lookup_template(param->template);
if?(!tmpl)
goto?err;
//循環調用模版的alloc函數分配算法實列,并將模版注冊之
//這里值得注意的是循環的條件,當返回碼為-EAGAIN時,會循環再次嘗試
//這樣使用的一個場景后面會分析到
do?{
inst?=?tmpl->alloc(param->tb);
if?(IS_ERR(inst))
err?=?PTR_ERR(inst);
else?if?((err?=?crypto_register_instance(tmpl,?inst)))
tmpl->free(inst);
}?while?(err?==?-EAGAIN?&&?!signal_pending(current));
//查找中會增加引用,這里已經用完了釋放之
crypto_tmpl_put(tmpl);
if?(err)
goto?err;
out:
kfree(param);
module_put_and_exit(0);
err:
crypto_larval_error(param->larval,?param->otype,?param->omask);
goto out;
}
理解了算法的注冊與查找后,再來理解這個函數就非常容易了,其核心在do{}while循環中,包含了算法實例的分配和注冊動作。針對每一種算法模版,其alloc動作不盡一致。后文會對xfrm使用的算法模版一一闡述。
為什么不把“算法實例”直接稱之為“算法”,這是因為實例包含了更多的內容,其由結構struct crypto_instance可以看出:
點擊(此處)折疊或打開
struct crypto_instance?{
struct crypto_alg alg;?//對應的算法名稱
struct crypto_template?*tmpl;?//所屬的算法模版
struct hlist_node list;?//鏈表成員
void?*__ctx[]?CRYPTO_MINALIGN_ATTR;?//上下文信息指針
};
內核使用struct crypto_alg描述一個算法(該結構在后文使用時再來分析),可見一個算法實例除了包含其對應的算法,還包含更多的內容。
當分配成功后,cryptomgr_probe會調用crypto_register_instance將其注冊,以期將來可以順利地找到并使用它:
點擊(此處)折疊或打開
int?crypto_register_instance(struct crypto_template?*tmpl,
struct crypto_instance?*inst)
{
struct crypto_larval?*larval;
int?err;
//對算法進行合法性檢查,并構造完整的驅動名稱
err?=?crypto_check_alg(&inst->alg);
if?(err)
goto?err;
//設置算法內核模塊指針指向所屬模版
inst->alg.cra_module?=?tmpl->module;
down_write(&crypto_alg_sem);
//注冊算法實例對應的算法
larval?=?__crypto_register_alg(&inst->alg);
if?(IS_ERR(larval))
goto unlock;
//成功后,將算法再注冊到所屬的模版上面
hlist_add_head(&inst->list,?&tmpl->instances);
//設置模版指針
inst->tmpl?=?tmpl;
unlock:
up_write(&crypto_alg_sem);
err?=?PTR_ERR(larval);
if?(IS_ERR(larval))
goto?err;
crypto_wait_for_test(larval);
err?=?0;
err:
return?err;
}
注冊的一個重要工作,就是調用__crypto_register_alg將實例所對應的算法注冊到加密框架子系統中。算法注冊成功后,上層調用者就可以調用crypto_alg_mod_lookup等函數進行查找,并使用該算法了。
三、? ? ? ? HMAC
MAC(消息認證碼)與hash函數非常相似,只是生成固定長度的消息摘要時需要秘密的密鑰而已。
HAMC是密鑰相關的哈希運算消息認證碼(keyed-Hash Message Authentication Code),HMAC運算利用哈希算法,以一個密鑰和一個消息為輸入,生成一個消息摘要作為輸出。具體的算法描述詳見:http://baike.baidu.com/view/1136366.htm?fr=ala0_1。
根據HMAC的特點(可以和類似md5、sha等hash算法組合,構造出hmac(md5)這樣的算法),Linux 加密框架將其抽像為一個算法模版。本章將假設上層調用者使用了名為hmac(md5)的算法,展示這一算法是如何被構造、初始化及調用以實現數據驗證的。
1.? ? ? ? 算法模版的注冊與注銷
點擊(此處)折疊或打開
static struct crypto_template hmac_tmpl?=?{
.name?=?"hmac",
.alloc?=?hmac_alloc,
.free?=?hmac_free,
.module?=?THIS_MODULE,
};
點擊(此處)折疊或打開
static?int?__init hmac_module_init(void)
{
return crypto_register_template(&hmac_tmpl);
}
點擊(此處)折疊或打開
static void __exit hmac_module_exit(void)
{
crypto_unregister_template(&hmac_tmpl);
}
模版的注冊與注銷前文已經描述過了。
2.? ? ? ? 算法實例的分配
當一個算法需要被使用卻查找不到的時候,會嘗試調用其模版對應分配相應的算法實列,這也適用于hmac,其alloc函數指針指向hmac_alloc:
點擊(此處)折疊或打開
static struct crypto_instance?*?hmac_alloc?(struct rtattr?**tb)
{
struct crypto_instance?*inst;
struct crypto_alg?*alg;
int?err;
int?ds;
//類型檢查,所屬算法必需為hash類型
err?=?crypto_check_attr_type(tb,?CRYPTO_ALG_TYPE_HASH);
if?(err)
return ERR_PTR(err);
//根據參數名稱,查找相應的子算法,如md5,shax等
alg?=?crypto_get_attr_alg(tb,?CRYPTO_ALG_TYPE_HASH,
CRYPTO_ALG_TYPE_HASH_MASK);
//查找失敗
if?(IS_ERR(alg))
return ERR_CAST(alg);
//初始化算法實例
inst?=?ERR_PTR(-EINVAL);
//計算算法實列的消息摘要大小(輸出大小)
ds?=?alg->cra_type?==?&crypto_hash_type??
alg->cra_hash.digestsize?:
alg->cra_type??
__crypto_shash_alg(alg)->digestsize?:
alg->cra_digest.dia_digestsize;
if?(ds?>?alg->cra_blocksize)
goto out_put_alg;
//分配一個算法實列,這樣,一個新的算法,如hmac(md5)就橫空出世了
inst?=?crypto_alloc_instance("hmac",?alg);
//分配失敗
if?(IS_ERR(inst))
goto out_put_alg;
//初始化算法實例,其相應的成員等于其子算法中的對應成員
//類型
inst->alg.cra_flags?=?CRYPTO_ALG_TYPE_HASH;
//優先級
inst->alg.cra_priority?=?alg->cra_priority;
//計算消息摘要的塊長度(輸入大小)
inst->alg.cra_blocksize?=?alg->cra_blocksize;
//對齊掩碼
inst->alg.cra_alignmask?=?alg->cra_alignmask;
//類型指針指向crypto_hash_type
inst->alg.cra_type?=?&crypto_hash_type;
//消息摘要大小
inst->alg.cra_hash.digestsize?=?ds;
//計算算法所需的上下文空間大小
inst->alg.cra_ctxsize?=?sizeof(struct hmac_ctx)?+
ALIGN(inst->alg.cra_blocksize?*?2?+?ds,
sizeof(void?*));
//初始化和退出函數
inst->alg.cra_init?=?hmac_init_tfm;
inst->alg.cra_exit?=?hmac_exit_tfm;
//置相應hash算法的操作函數,包含hash函數標準的init/update/final和digest/setkey
inst->alg.cra_hash.init?=?hmac_init;
inst->alg.cra_hash.update?=?hmac_update;
inst->alg.cra_hash.final?=?hmac_final;
//消息摘要函數
inst->alg.cra_hash.digest?=?hmac_digest;
//setkey(密鑰設置函數)
inst->alg.cra_hash.setkey?=?hmac_setkey;
out_put_alg:
crypto_mod_put(alg);
return inst;
}
每個模版的alloc動作雖不同,但是它們基本上遵循一些共性的操作:
1、? ? ? ? 合法性檢驗,如類型檢查;
2、? ? ? ? 取得其子算法(即被模版所包裹的算法,如hmac(md5)中,就是md5)的算法指針;?
3、? ? ? ? 調用crypto_alloc_instance分配一個相應的算法實列;
4、? ? ? ? 對分配成功的算法實例進行實始化,這也是理解該算法實例最核心的部份,因為它初始化算法運行所需的一些必要參數和虛函數指針;
crypto_alloc_instance(algapi.c) 函數用于分配一個算法實例,這個函數有兩個重要功能,一個是分配內存空間,另一個是初始化spawn。
點擊(此處)折疊或打開
//name:?模版名稱?
//alg:模版的子算法
struct crypto_instance?*crypto_alloc_instance(const?char?*name,
struct crypto_alg?*alg)
{
struct crypto_instance?*inst;
struct crypto_spawn?*spawn;
int?err;
//分配一個算法實例,crypto_instance結構的最后一個成員ctx是一個指針變量,所以,在分配空間的時候,在其尾部追加相應的空間,可以使用ctx訪問之。
//另一個重要的概念是,算法實例中包含了算法,這個分配,同時也完成了算法實例對應的算法的分配工作。
inst?=?kzalloc(sizeof(*inst)?+?sizeof(*spawn),?GFP_KERNEL);
if?(!inst)
return ERR_PTR(-ENOMEM);
err?=?-ENAMETOOLONG;
//構造完成的算法名稱
if?(snprintf(inst->alg.cra_name,?CRYPTO_MAX_ALG_NAME,?"%s(%s)",?name,
alg->cra_name)?>=?CRYPTO_MAX_ALG_NAME)
goto err_free_inst;
//構造完整的算法驅動名稱
if?(snprintf(inst->alg.cra_driver_name,?CRYPTO_MAX_ALG_NAME,?"%s(%s)",
name,?alg->cra_driver_name)?>=?CRYPTO_MAX_ALG_NAME)
goto err_free_inst;
//spawn指向算法實例的上下文成員,可以這樣做是因為__ctx是一個可變長的成員,在分配實例的時候,
//在尾部增加了一個spawn的空間
spawn?=?crypto_instance_ctx(inst);
//初始化spawn
err?=?crypto_init_spawn(spawn,?alg,?inst,
CRYPTO_ALG_TYPE_MASK?|?CRYPTO_ALG_ASYNC);
if?(err)
goto err_free_inst;
return inst;
err_free_inst:
kfree(inst);
return ERR_PTR(err);
}
crypto_instance_ctx取出算法實例的ctx指針,返回值是void *,這意味著可以根具不同的需要,將其轉換為所需的類型:
點擊(此處)折疊或打開
static inline void?*crypto_instance_ctx(struct crypto_instance?*inst)
{
return inst->__ctx;
}
一個算法實例被分配成員后,其會被注冊至加密子系統,這樣,一個算法,例如,hmac(md5)就可以直接被使用了。
3.? ? ? ? 待孵化的卵
? ? ? ? 已經看到了從模版到算法實例的第一層抽像,每個算法在每一次被使用時,它們的運行環境不盡相同,例如,可能會擁有不同的密鑰。將算法看成一個類,則在每一次運行調用時,需要為它產生一個“對像”,這在內核中被稱為transform,簡稱為tfm。后文會詳細看到分配一個tfm的過程,現在引入這一概念,主要是為了分析spawn。
加密或認證算法,在調用時,都需要分配其算法對應的tfm,在分配算法實例的同時,并沒有為之分配相應的tfm結構,這是因為真正的算法還沒有被調用,這并不是進行tfm結構分配的最佳地點。在初始化算法實例的時候,加密框架使用了XXX_spawn_XXX函數簇來解決這一問題。這樣的算法對像,被稱為spawn(卵)。也就是說,在算法實例分配的時候,只是下了一個蛋(設置好spawn),等到合適的時候來對其進行孵化,這個“合適的時候”,通常指為調用算法實際使用的時候。
在crypto_alloc_instance分配算法實例的時候,就順便分配了spawn,然后調用crypto_init_spawn對其進行初始化:
點擊(此處)折疊或打開
int?crypto_init_spawn(struct crypto_spawn?*spawn,?struct crypto_alg?*alg,
struct crypto_instance?*inst,?u32 mask)
{
int?err?=?-EAGAIN;
//初始化其成員
spawn->inst?=?inst;
spawn->mask?=?mask;
down_write(&crypto_alg_sem);
if?(!crypto_is_moribund(alg))?{
//加入鏈表,每個spawn,都被加入到算法的cra_users鏈,即算做算法的一個用戶
list_add(&spawn->list,?&alg->cra_users);
//spawn的alg成員指針指向當前成員,這就方便引用了
spawn->alg?=?alg;
err?=?0;
}
up_write(&crypto_alg_sem);
return?err;
}
所以,所謂算法的spawn的初始化,就是初始化crypto_spawn結構,核心的操作是設置其對應的算法實例、算法,以及一個加入算法的鏈表的過程。
4.? ? ? ? 算法的初始化
有了算法實例,僅表示內核擁有這一種“算法”——加引號的意思是說,它可能并不以類似md5.c這樣的源代碼形式存現,而是通過模版動態創建的。實際要使用該算法,需要為算法分配“運行的對像”,即tfm。
4.1? ? ? ? tfm
內核加密框架中,使用結構crypto_alg來描述一個算法,每一個算法(實例)相當于一個類,在實際的使用環境中,需要為它分配一個對像,在內核加密框架中,這個“對像”被稱為transform(簡稱tfm)。transform意味“變換”,可能譯為“蛻變”更為合適。作者對它的注釋是:
/*
* Transforms: user-instantiated objects which encapsulate algorithms
* and core processing logic.??Managed via crypto_alloc_*() and
* crypto_free_*(), as well as the various helpers below.
……
*/
tfm是加密框架中一個極為重要的概念,它由結構crypto_tfm描述:
點擊(此處)折疊或打開
struct crypto_tfm?{
u32 crt_flags;
union?{
struct ablkcipher_tfm ablkcipher;
struct aead_tfm aead;
struct blkcipher_tfm blkcipher;
struct cipher_tfm cipher;
struct hash_tfm hash;
struct ahash_tfm ahash;
struct compress_tfm compress;
struct rng_tfm rng;
}?crt_u;
void?(*exit)(struct crypto_tfm?*tfm);
struct crypto_alg?*__crt_alg;
void?*__crt_ctx[]?CRYPTO_MINALIGN_ATTR;
};
這些成員的作用,將在后面一一看到,值得注意的是,針對每種算法不同,結構定義了一個名為crt_u的聯合體,以對應每種算法的tfm的具體操作,例如加密/解密,求hash,壓縮/解壓等,加密框架引入了一組名為xxx_tfm的結構封裝,xxx表示算法類型,也就是crt_u成員。其定義如下:
點擊(此處)折疊或打開
struct ablkcipher_tfm?{
int?(*setkey)(struct crypto_ablkcipher?*tfm,?const?u8?*key,
unsigned?int?keylen);
int?(*encrypt)(struct ablkcipher_request?*req);
int?(*decrypt)(struct ablkcipher_request?*req);
int?(*givencrypt)(struct skcipher_givcrypt_request?*req);
int?(*givdecrypt)(struct skcipher_givcrypt_request?*req);
struct crypto_ablkcipher?*base;
unsigned?int?ivsize;
unsigned?int?reqsize;
};
struct aead_tfm?{
int?(*setkey)(struct crypto_aead?*tfm,?const?u8?*key,
unsigned?int?keylen);
int?(*encrypt)(struct aead_request?*req);
int?(*decrypt)(struct aead_request?*req);
int?(*givencrypt)(struct aead_givcrypt_request?*req);
int?(*givdecrypt)(struct aead_givcrypt_request?*req);
struct crypto_aead?*base;
unsigned?int?ivsize;
unsigned?int?authsize;
unsigned?int?reqsize;
};
struct blkcipher_tfm?{
void?*iv;
int?(*setkey)(struct crypto_tfm?*tfm,?const?u8?*key,
unsigned?int?keylen);
int?(*encrypt)(struct blkcipher_desc?*desc,?struct scatterlist?*dst,
struct scatterlist?*src,?unsigned?int?nbytes);
int?(*decrypt)(struct blkcipher_desc?*desc,?struct scatterlist?*dst,
struct scatterlist?*src,?unsigned?int?nbytes);
};
struct cipher_tfm?{
int?(*cit_setkey)(struct crypto_tfm?*tfm,
const?u8?*key,?unsigned?int?keylen);
void?(*cit_encrypt_one)(struct crypto_tfm?*tfm,?u8?*dst,?const?u8?*src);
void?(*cit_decrypt_one)(struct crypto_tfm?*tfm,?u8?*dst,?const?u8?*src);
};
struct hash_tfm?{
int?(*init)(struct hash_desc?*desc);
int?(*update)(struct hash_desc?*desc,
struct scatterlist?*sg,?unsigned?int?nsg);
int?(*final)(struct hash_desc?*desc,?u8?*out);
int?(*digest)(struct hash_desc?*desc,?struct scatterlist?*sg,
unsigned?int?nsg,?u8?*out);
int?(*setkey)(struct crypto_hash?*tfm,?const?u8?*key,
unsigned?int?keylen);
unsigned?int?digestsize;
};
struct ahash_tfm?{
int?(*init)(struct ahash_request?*req);
int?(*update)(struct ahash_request?*req);
int?(*final)(struct ahash_request?*req);
int?(*digest)(struct ahash_request?*req);
int?(*setkey)(struct crypto_ahash?*tfm,?const?u8?*key,
unsigned?int?keylen);
unsigned?int?digestsize;
unsigned?int?reqsize;
};
struct compress_tfm?{
int?(*cot_compress)(struct crypto_tfm?*tfm,
const?u8?*src,?unsigned?int?slen,
u8?*dst,?unsigned?int?*dlen);
int?(*cot_decompress)(struct crypto_tfm?*tfm,
const?u8?*src,?unsigned?int?slen,
u8?*dst,?unsigned?int?*dlen);
};
struct rng_tfm?{
int?(*rng_gen_random)(struct crypto_rng?*tfm,?u8?*rdata,
unsigned?int?dlen);
int?(*rng_reset)(struct crypto_rng?*tfm,?u8?*seed,?unsigned?int?slen);
};
為了直接訪問這些成員,定義了如下宏:
點擊(此處)折疊或打開
#define crt_ablkcipher crt_u.ablkcipher
#define crt_aead crt_u.aead
#define crt_blkcipher crt_u.blkcipher
#define crt_cipher crt_u.cipher
#define crt_hash crt_u.hash
#define crt_ahash crt_u.ahash
#define crt_compress crt_u.compress
#define crt_rng crt_u.rng
這樣,要訪問hash算法的hash成員,就可以直接使用crt_hash,而不是crt_u.hash。
每種算法訪問tfm都使用了二次封裝,例如:
點擊(此處)折疊或打開
struct crypto_ablkcipher?{
struct crypto_tfm base;
};
struct crypto_aead?{
struct crypto_tfm base;
};
struct crypto_blkcipher?{
struct crypto_tfm base;
};
struct crypto_cipher?{
struct crypto_tfm base;
};
struct crypto_comp?{
struct crypto_tfm base;
};
struct crypto_hash?{
struct crypto_tfm base;
};
struct crypto_rng?{
struct crypto_tfm base;
};
其base成員就是相應算法的tfm。因為它們擁有相應的起始地址,可以很方便地強制類型轉換來操作,內核為此專門定義了一組函數,以hash為例,完成這一工作的是crypto_hash_cast:
點擊(此處)折疊或打開
static inline struct crypto_hash?*__crypto_hash_cast(struct crypto_tfm?*tfm)
{
return?(struct crypto_hash?*)tfm;
}
static inline struct crypto_hash?*crypto_hash_cast(struct crypto_tfm?*tfm)
{
BUG_ON((crypto_tfm_alg_type(tfm)?^ CRYPTO_ALG_TYPE_HASH)?&
CRYPTO_ALG_TYPE_HASH_MASK);
return __crypto_hash_cast(tfm);
}
當然,針對各種不同的算法,還有許多不同的XXX_cast函數。這些cast函數,將tfm強制轉換為其所屬的算法類型的封裝結構。
4.2 tfm的分配
對于算法的實始化,其核心功能就是分配一個tfm,并設置其上下文環境,例如密鑰等參數,然后初始化上述struct xxx_tfm結構。對于hash類的算法來講,分配tfm是由crypto_alloc_hash(crypt.h) 這個API來完成的,以AH為例,在其初始化過程中有:
點擊(此處)折疊或打開
static?int?ah_init_state(struct xfrm_state?*x)
{
struct crypto_hash?*tfm;
……
tfm?=?crypto_alloc_hash(x->aalg->alg_name,?0,?CRYPTO_ALG_ASYNC);
if?(IS_ERR(tfm))
goto?error;
……
}
AH調用crypto_alloc_hash為SA中指定的算法(如hmac(md5))分配一個tfm,第二個參數為0,第三個參數指明了AH使用異步模式。
點擊(此處)折疊或打開
static inline struct crypto_hash?*crypto_alloc_hash(const?char?*alg_name,
u32 type,?u32 mask)
{
//初始化相應的類型的掩碼
type?&=?~CRYPTO_ALG_TYPE_MASK;?//清除類型的CRYPTO_ALG_TYPE_MASK位
mask?&=?~CRYPTO_ALG_TYPE_MASK;?//清除掩碼的CRYPTO_ALG_TYPE_MASK位
type?|=?CRYPTO_ALG_TYPE_HASH;?//置類型CRYPTO_ALG_TYPE_HASH位
mask?|=?CRYPTO_ALG_TYPE_HASH_MASK;?//置掩碼CRYPTO_ALG_TYPE_HASH_MASK位
//最終的分配函數是crypto_alloc_base,它分配一個base(每個算法的tfm),再將其強制類型轉換為所需要結構類型
return __crypto_hash_cast(crypto_alloc_base(alg_name,?type,?mask));
}
crypto_alloc_base首先檢查相應的算法是否存在,對于hmac(md5)這個例子,xfrm在SA的增加中,會觸發相應的算法查找,最終會調用hmac模版的alloc分配算法實例(當然也包括算法本身),然后向內核注冊算法及算法實例,所以,查找會命中。接下來的工作,是調用tfm的核心分配函數__crypto_alloc_tfm進行分配,其實現如下:
點擊(此處)折疊或打開
struct crypto_tfm?*crypto_alloc_base(const?char?*alg_name,?u32 type,?u32 mask)
{
struct crypto_tfm?*tfm;
int?err;
for?(;;)?{
struct crypto_alg?*alg;
//根據算法名稱,查找相應的算法,它會首先嘗試已經加載的算法,如果失敗,也會嘗試
//動態插入內核模塊
alg?=?crypto_alg_mod_lookup(alg_name,?type,?mask);
//查找失敗,返回退出循環
if?(IS_ERR(alg))?{
err?=?PTR_ERR(alg);
goto?err;
}
//查找成功,為算法分配tfm
tfm?=?__crypto_alloc_tfm(alg,?type,?mask);
//分配成功,返回之
if?(!IS_ERR(tfm))
return tfm;
//釋放引用計算,因為查找會增加引用
crypto_mod_put(alg);
//獲取返回錯誤值,根據其值,決定是否要繼續嘗試
err?=?PTR_ERR(tfm);
err:
if?(err?!=?-EAGAIN)
break;
if?(signal_pending(current))?{
err?=?-EINTR;
break;
}
}
return ERR_PTR(err);
}
__crypto_alloc_tfm是內核加密框架中又一重要的函數,它完成了對算法tfm的分配和初始化的工作:
點擊(此處)折疊或打開
struct crypto_tfm?*__crypto_alloc_tfm(struct crypto_alg?*alg,?u32 type,
u32 mask)
{
struct crypto_tfm?*tfm?=?NULL;
unsigned?int?tfm_size;
int?err?=?-ENOMEM;
//計算tfm所需的空間大小,它包括了tfm結構本身和算法上下文大小
tfm_size?=?sizeof(*tfm)?+?crypto_ctxsize(alg,?type,?mask);
//分配tfm
tfm?=?kzalloc(tfm_size,?GFP_KERNEL);
if?(tfm?==?NULL)
goto out_err;
//__crt_alg成員指向其所屬的算法,對于hmac而言,它就是hmac(xxx),例如hmac(md5)
tfm->__crt_alg?=?alg;
//初始化tfm選項
err?=?crypto_init_ops(tfm,?type,?mask);
if?(err)
goto out_free_tfm;
//調用算法的初始化函數,初始化tfm,這有個先決條件是tfm本身沒有exit函數的實現
if?(!tfm->exit?&&?alg->cra_init?&&?(err?=?alg->cra_init(tfm)))
goto cra_init_failed;
goto out;
cra_init_failed:
crypto_exit_ops(tfm);
out_free_tfm:
if?(err?==?-EAGAIN)
crypto_shoot_alg(alg);
kfree(tfm);
out_err:
tfm?=?ERR_PTR(err);
out:
return tfm;
}
crypto_init_ops負責初始化tfm的選項,對于一個真正的算法(例如md5、dst)和一個偽算法(我說的“偽”,是指由模版動態分配的,如hmac(xxx), authenc(xxx,xxx)),因為并不存在這樣的算法,只是內核的一個抽像,故稱為"偽",它們的初始化過程是截然不同的。一個偽算法,它都設置了其所屬的類型cra_type,例如,對于hmac(xxx)而言,它指向了crypto_hash_type。這樣,初始化時,實質上調用的是其所屬類型的init函數:
點擊(此處)折疊或打開
static?int?crypto_init_ops(struct crypto_tfm?*tfm,?u32 type,?u32 mask)
{
//獲取tfm所屬算法的所屬類型
const?struct crypto_type?*type_obj?=?tfm->__crt_alg->cra_type;
//如果設置了類型,調用類型的init
if?(type_obj)
return type_obj->init(tfm,?type,?mask);
//否則,判斷算法的類型,調用相應的初始化函數,這些在不同的算法實現中分析
switch?(crypto_tfm_alg_type(tfm))?{
case?CRYPTO_ALG_TYPE_CIPHER:
return crypto_init_cipher_ops(tfm);
case?CRYPTO_ALG_TYPE_DIGEST:
if?((mask?&?CRYPTO_ALG_TYPE_HASH_MASK)?!=
CRYPTO_ALG_TYPE_HASH_MASK)
return crypto_init_digest_ops_async(tfm);
else
return crypto_init_digest_ops(tfm);
case?CRYPTO_ALG_TYPE_COMPRESS:
return crypto_init_compress_ops(tfm);
default:
break;
}
BUG();
return?-EINVAL;
}
算法類型的概念很好理解,因為若干個hmac(xxx)都擁有一此相同的類型屬性(其它偽算法同樣如此),所以可以將它們抽像管理。
對于hash類型的算法而言,它們擁有一個共同的類型crypto_hash_type,其定義在hash.c中:
點擊(此處)折疊或打開
const?struct crypto_type crypto_hash_type?=?{
.ctxsize?=?crypto_hash_ctxsize,
.init?=?crypto_init_hash_ops,
#ifdef CONFIG_PROC_FS
.show?=?crypto_hash_show,
#endif
};
它的init函數指針指向crypto_init_hash_ops:
點擊(此處)折疊或打開
static?int?crypto_init_hash_ops(struct crypto_tfm?*tfm,?u32 type,?u32 mask)
{
struct hash_alg?*alg?=?&tfm->__crt_alg->cra_hash;
//其消息摘要大小不同超過1/8個頁面
if?(alg->digestsize?>?PAGE_SIZE?/?8)
return?-EINVAL;
//根據掩碼位,判斷是同步初始化還是異步,對于crypto_alloc_hash調用下來的而言,它
//設置了CRYPTO_ALG_TYPE_HASH_MASK位,所以是同步初始化
if?((mask?&?CRYPTO_ALG_TYPE_HASH_MASK)?!=?CRYPTO_ALG_TYPE_HASH_MASK)
return crypto_init_hash_ops_async?(tfm);
else
return crypto_init_hash_ops_sync(tfm);
}
在我們AH的例子中,AH使用了異步模式,所以crypto_init_hash_ops_async會被調用。
前述hash_tfm結構封裝了hash類型的算法的通用的操作:
點擊(此處)折疊或打開
struct hash_tfm?{
int?(*init)(struct hash_desc?*desc);
int?(*update)(struct hash_desc?*desc,
struct scatterlist?*sg,?unsigned?int?nsg);
int?(*final)(struct hash_desc?*desc,?u8?*out);
int?(*digest)(struct hash_desc?*desc,?struct scatterlist?*sg,
unsigned?int?nsg,?u8?*out);
int?(*setkey)(struct crypto_hash?*tfm,?const?u8?*key,
unsigned?int?keylen);
unsigned?int?digestsize;
};
先來看同步模式的初始化操作,crypto_init_hash_ops_sync函數負責初始化這一結構:
點擊(此處)折疊或打開
static?int?crypto_init_hash_ops_sync(struct crypto_tfm?*tfm)
{
struct hash_tfm?*crt?=?&tfm->crt_hash;
struct hash_alg?*alg?=?&tfm->__crt_alg->cra_hash;
//置tfm相應操作為算法本身的對應操作,
//對于hmac(xxx)算法而言,這些東東在hmac_alloc中已經初始化過了,也就是hmac_init等函數
crt->init?=?alg->init;
crt->update?=?alg->update;
crt->final?=?alg->final;
crt->digest?=?alg->digest;
crt->setkey?=?hash_setkey;
crt->digestsize?=?alg->digestsize;
return 0;
}
異步模式則稍有不同,它使用了hash類型算法的通用函數:
點擊(此處)折疊或打開
static?int?crypto_init_hash_ops_async(struct crypto_tfm?*tfm)
{
struct ahash_tfm?*crt?=?&tfm->crt_ahash;
struct hash_alg?*alg?=?&tfm->__crt_alg->cra_hash;
crt->init?=?hash_async_init;
crt->update?=?hash_async_update;
crt->final?=?hash_async_final;
crt->digest?=?hash_async_digest;
crt->setkey?=?hash_async_setkey;
crt->digestsize?=?alg->digestsize;
return 0;
}
不論是同步還是異步,算法的tfm都得到的相應的初始化。回到__crypto_alloc_tfm中來,__crypto_alloc_tfm函數的最后一步是調用算法的cra_init函數(如果它存在的話),對于hmac(xxx)而言,它在分配的時候指向hmac_init_tfm。hmac_init_tfm的主要工作就是對hmac(xxx)的spawn進行孵化操作。還記得“待孵化的卵”嗎?前面講了只是初始化它,現在到了孵化的時候了
點擊(此處)折疊或打開
static?int?hmac_init_tfm(struct crypto_tfm?*tfm)
{
struct crypto_hash?*hash;
//因為算法實例的第一個成員就是alg,在注冊算法時,就是注冊的它,所以可以很方便地通過tfm的__crt_alg強制類型轉換得到對應的算法實例
struct crypto_instance?*inst?=?(void?*)tfm->__crt_alg;
//取得算法實例的__ctx域,也就是spawn
struct crypto_spawn?*spawn?=?crypto_instance_ctx(inst);
//取得tfm的上下文指針
struct hmac_ctx?*ctx?=?hmac_ctx(__crypto_hash_cast(tfm));
//對hmac(xxx)進行孵化,以hmac(md5)為例,這將得到一個md5算法的tfm,當然,通過強制類型轉換,它被封裝在結構crypto_hash中
hash?=?crypto_spawn_hash(spawn);
if?(IS_ERR(hash))
return PTR_ERR(hash);
//設置子算法指向孵化的tfm
ctx->child?=?hash;
return 0;
}
crypto_spawn_hash展示了如何對hash算法簇進行spawn的孵化操作:
點擊(此處)折疊或打開
static inline struct crypto_hash?*crypto_spawn_hash(struct crypto_spawn?*spawn)
{
//初始化孵化所需的類型和掩碼
u32 type?=?CRYPTO_ALG_TYPE_HASH;
u32 mask?=?CRYPTO_ALG_TYPE_HASH_MASK;
//調用crypto_spawn_tfm孵化一個tfm,并強制類型轉換
return __crypto_hash_cast(crypto_spawn_tfm(spawn,?type,?mask));
}
最后的任務交給了crypto_spawn_tfm函數,它為算法孵化一個tfm,因為spawn的alg成員指向了所要孵化的算法,使得這一操作很容易實現
點擊(此處)折疊或打開
struct crypto_tfm?*crypto_spawn_tfm(struct crypto_spawn?*spawn,?u32 type,
u32 mask)
{
struct crypto_alg?*alg;
struct crypto_alg?*alg2;
struct crypto_tfm?*tfm;
down_read(&crypto_alg_sem);
//要孵化的spawn所屬的算法
alg?=?spawn->alg;
alg2?=?alg;
//查找算法所屬模塊
if?(alg2)
alg2?=?crypto_mod_get(alg2);
up_read(&crypto_alg_sem);
//如果其所屬模塊沒了,則標注算法為DYING,出錯退回
if?(!alg2)?{
if?(alg)
crypto_shoot_alg(alg);
return ERR_PTR(-EAGAIN);
}
//初始化tfm
tfm?=?ERR_PTR(-EINVAL);
//驗證掩碼標志位
if?(unlikely((alg->cra_flags ^ type)?&?mask))
goto out_put_alg;
//為算法分配相應的tfm,這樣,一個算法的spawn就孵化完成了
tfm?=?__crypto_alloc_tfm(alg,?type,?mask);
if?(IS_ERR(tfm))
goto out_put_alg;
return tfm;
out_put_alg:
crypto_mod_put(alg);
return tfm;
}
又繞回了__crypto_alloc_tfm函數,其實現之前已經分析過了,對于一個普通的算法(非模版產生的算法,如md5),其初始化工作略有不同,在了解其初始化工作之前,需要對一個實際的算法作了解。
順例說一句,內核的這種抽像管理方式,功能異常地強大,可以想像,它可以抽像更多層的嵌套。所以hmac(xxx)中,xxx不一定就是一個md5之類,可能還是一層形如xxx(xxx)的抽像,理論上,它可以像變形金剛一樣。
4.3 小結一下
本節分析了一個算法的tfm是如何生成的,因為算法可以是多層的組裝,在生成上層算法的同時,它也要為其所包含的算法分配tfm,這一過程稱之為spawn。
?
評論
查看更多