4.5 Slot管理模块:Token的“注册中心“
一个问题:Slot如何被管理?
假设你刚刚读完第三章,了解了PKCS#11的Slot概念:
- Slot是“逻辑插槽“
- Token是“插入的令牌“
- 一个Slot可以有(或没有)Token
现在你打开SoftHSM2源码,想找到Slot管理的代码。
但你可能会困惑:
- 软件HSM没有物理插槽,Slot是什么?
- Slot如何被创建?
- Token如何与Slot绑定?
更深层的问题是:
软件HSM如何“模拟“硬件HSM的Slot机制?
“注册中心“的设计意图
SlotManager就像政府注册中心:
注册中心类比:
政府注册中心(SlotManager)
│
├── 职责:
│ ├── 管理所有注册号(Slot ID)
│ ├── 登录新企业(创建Token)
│ ├── 注销企业(销毁Token)
│ ├── 查询企业列表(GetSlotList)
│ └── 检查企业状态(Token是否存在)
│ │
│ ├── 注册号(Slot ID):
│ │ ├── 每个企业有一个编号
│ │ ├── 编号是逻辑概念
│ │ └── 不代表物理位置
│ │
│ ├── 企业(Token):
│ │ ├── 可能"已注册"(Token存在)
│ │ ├── 可能"未注册"(Token不存在)
│ │ └── 企业有档案信息
│ │
│ └── 软件注册 vs 硬件注册:
│ │ ├── 软件注册:数据库记录即可
│ │ ├── 硬件注册:需要物理检测
│ │ └── 软件注册可以"凭空"创建
│ │
│ └── 车载理解:
│ │ ├── 注册号 = HSM芯片编号
│ │ ├── 企业 = HSM芯片本身
│ │ ├── 多个注册号 = 多个HSM芯片
│ │ └── 注册中心 = 系统的Slot管理模块
│ │
└── 关键设计:
├── Slot是逻辑概念(注册号)
├── Token是数据实体(企业档案)
├── Slot可以没有Token(空注册号)
└── SoftHSM2用文件/数据库模拟Token
SlotManager的地位
SlotManager管理PKCS#11的Slot和Token:
Slot管理架构:
PKCS#11 Slot概念:
┌─────────────────────────────────────┐
│ Slot = 逻辑插槽 │
│ │
│ ├─ 可以插入Token │
│ ├─ 每个Slot有Slot ID │
│ ├─ 每个Slot有Slot信息 │
│ └─ Token存在时才有Token信息 │
│ │
│ 车载理解: │
│ Slot = HSM芯片的通信端口 │
│ Token = HSM芯片本身 │
│ 多个Slot = 多个HSM芯片 │
└─────────────────────────────────────┘
SlotManager职责:
┌─────────────────────────────────────┐
│ 软件HSM vs 硬件HSM │
│ │
│ 软件HSM(SoftHSM2): │
│ ├─ Slot = 配置文件定义 │
│ ├─ Token = 数据库/文件 │
│ ├─ 可动态创建 │
│ └─ 无真实硬件 │
│ │
│ 硬件HSM: │
│ ├─ Slot = SPI/I2C端口 │
│ ├─ Token = 检测到芯片 │
│ ├─ 不可动态创建 │
│ └─ 有真实硬件 │
└─────────────────────────────────────┘
SlotManager类设计
SlotManager是Slot管理的核心类:
/* SlotManager类结构 */
class SlotManager {
public:
// Slot列表
// 教学简化:实际源码使用std::map<CK_SLOT_ID, Slot*>而非std::vector
std::vector<Slot*> slots;
// 初始化(注册中心开业)
CK_RV init();
// Slot操作(注册号查询)
CK_RV getSlotList(CK_BBOOL tokenPresent, // 只返回有Token的Slot
CK_SLOT_ID_PTR pSlotList,
CK_ULONG_PTR pulCount);
Slot* getSlot(CK_SLOT_ID slotID); // 获取指定Slot
bool isValidSlot(CK_SLOT_ID slotID); // 验证Slot ID
// Token操作(企业查询)
bool isTokenPresent(CK_SLOT_ID slotID); // Token是否存在
Token* getToken(CK_SLOT_ID slotID); // 获取Token
// Slot事件(企业变动通知)
CK_RV waitForSlotEvent(CK_FLAGS flags,
CK_SLOT_ID_PTR pSlot,
CK_VOID_PTR pReserved);
private:
// 加载配置(导入已有企业)
CK_RV loadSlotsFromConfig();
// 创建Slot(分配注册号)
CK_RV createSlot(CK_SLOT_ID slotID);
};
SlotManager初始化
SlotManager初始化时加载已有Slot:
CK_RV SlotManager::init()
{
// 1. 清空Slot列表(清空注册簿)
slots.clear();
// 2. 从配置加载Slot(导入已有企业)
CK_RV rv = loadSlotsFromConfig();
if (rv != CKR_OK) {
return rv;
}
// 3. 如果没有Slot,创建默认Slot(分配默认注册号)
if (slots.empty()) {
Slot* slot = new Slot(0); // Slot ID = 0
slots.push_back(slot);
}
return CKR_OK;
}
CK_RV SlotManager::loadSlotsFromConfig()
{
// 从配置文件加载Slot定义
// 获取配置中的Slot数量
CK_ULONG slotCount = Configuration::i()->getSlotCount();
for (CK_ULONG i = 0; i < slotCount; i++) {
// 创建Slot
Slot* slot = new Slot(i);
// 初始化Slot信息
slot->init();
// 加载Token(如果存在)
slot->loadToken();
slots.push_back(slot);
}
return CKR_OK;
}
配置文件示例:
# softhsm2.conf 配置文件
# Token存储目录
directories.tokendir = /var/lib/softhsm2/tokens
# Slot数量(注册号数量)
# SoftHSM2可以动态创建,这个值不是固定限制
Slot类设计
Slot类代表一个逻辑插槽:
class Slot {
public:
Slot(CK_SLOT_ID slotID);
~Slot();
// Slot ID(注册号)
CK_SLOT_ID getSlotID();
// Slot信息(注册号信息)
CK_RV getSlotInfo(CK_SLOT_INFO_PTR pInfo);
// Token操作(企业操作)
bool isTokenPresent();
Token* getToken();
CK_RV getTokenInfo(CK_TOKEN_INFO_PTR pInfo);
// Token生命周期(企业生命周期)
CK_RV initToken(CK_UTF8CHAR_PTR pPin,
CK_ULONG ulPinLen,
CK_UTF8CHAR_PTR pLabel);
CK_RV loadToken(); // 加载已有Token
CK_RV destroyToken(); // 销毁Token
// 机制查询(服务能力查询)
CK_RV getMechanismList(...);
CK_RV getMechanismInfo(...);
private:
CK_SLOT_ID slotID; // Slot ID
SlotInfo* slotInfo; // Slot信息
Token* token; // Token实例(可能为NULL)
bool tokenPresent; // Token是否存在
};
Slot信息结构:
CK_RV Slot::getSlotInfo(CK_SLOT_INFO_PTR pInfo)
{
// 填充Slot信息(注册号信息)
// Slot描述(注册号描述)
strncpy(pInfo->slotDescription,
"SoftHSM2 Slot",
sizeof(pInfo->slotDescription));
// 制造商ID(注册中心名称)
strncpy(pInfo->manufacturerID,
"OpenDNSSEC",
sizeof(pInfo->manufacturerID));
// Slot标志(注册号标志)
pInfo->flags = CKF_TOKEN_PRESENT; // Token存在
// 硬件版本(无关,软件HSM)
pInfo->hardwareVersion.major = 1;
pInfo->hardwareVersion.minor = 0;
// 固件版本(无关,软件HSM)
pInfo->firmwareVersion.major = 1;
pInfo->firmwareVersion.minor = 0;
return CKR_OK;
}
Token类设计
Token类代表一个密码令牌:
class Token {
public:
Token(Slot* slot);
~Token();
// Token信息(企业信息)
CK_RV getTokenInfo(CK_TOKEN_INFO_PTR pInfo);
CK_RV setTokenLabel(const std::string& label);
// PIN管理(密码管理)
CK_RV initPIN(CK_USER_TYPE userType,
CK_UTF8CHAR_PTR pPin,
CK_ULONG ulPinLen);
CK_RV setPIN(CK_USER_TYPE userType,
CK_UTF8CHAR_PTR pOldPin,
CK_ULONG ulOldLen,
CK_UTF8CHAR_PTR pNewPin,
CK_ULONG ulNewLen);
CK_RV verifyPIN(CK_USER_TYPE userType,
CK_UTF8CHAR_PTR pPin,
CK_ULONG ulPinLen);
// 登录状态(登录状态)
bool isLoggedIn();
CK_USER_TYPE getLoggedInUser();
// Session管理(窗口管理)
CK_RV openSession(...);
CK_RV closeSession(...);
CK_RV closeAllSessions();
// Object管理(档案管理)
ObjectStore* getObjectStore();
private:
Slot* slot; // 所属Slot
TokenInfo* tokenInfo; // Token信息
ObjectStore* objectStore; // Object存储
SessionManager* sessions; // Session管理
// PIN存储
ByteString soPINBlob; // SO PIN(加密存储)
ByteString userPINBlob; // User PIN(加密存储)
// 登录状态
bool loggedIn;
CK_USER_TYPE loggedInUser;
};
Token初始化流程
Token初始化创建新的Token:
CK_RV Slot::initToken(CK_UTF8CHAR_PTR pPin,
CK_ULONG ulPinLen,
CK_UTF8CHAR_PTR pLabel)
{
// 1. 检查Slot状态(检查注册号状态)
if (token != NULL) {
// Token已存在,需要先销毁
destroyToken();
}
// 2. 创建Token(新企业注册)
token = new Token(this);
// 3. 设置Token Label(企业名称)
token->setTokenLabel(std::string(pLabel, 32));
// 4. 初始化SO PIN(管理员密码)
CK_RV rv = token->initPIN(CKU_SO, pPin, ulPinLen);
if (rv != CKR_OK) {
delete token;
token = NULL;
return rv;
}
// 5. 初始化Object存储(企业档案库)
token->getObjectStore()->init();
// 6. 标记Token存在
tokenPresent = true;
return CKR_OK;
}
Token初始化流程图:
Token初始化流程(企业注册流程):
检查Slot状态
│
│ Token已存在? → 先销毁
│
▼ Token不存在
创建Token对象
│
▼ 创建成功
设置Token Label
│
▼ Label设置完成
初始化SO PIN
│
│ 失败? → 清理并返回错误
│
▼ PIN设置完成
初始化Object存储
│
▼ 存储初始化完成
标记Token存在
│
▼ 完成
返回CKR_OK
对应softhsm2-util命令:
softhsm2-util --init-token --slot 0 --label "MyToken" --so-pin 12345678
getSlotList实现
getSlotList返回Slot列表:
CK_RV SlotManager::getSlotList(CK_BBOOL tokenPresent,
CK_SLOT_ID_PTR pSlotList,
CK_ULONG_PTR pulCount)
{
// 1. 计算Slot数量(统计注册号)
CK_ULONG count = 0;
if (tokenPresent) {
// 只统计有Token的Slot(只返回有企业的注册号)
for (Slot* slot : slots) {
if (slot->isTokenPresent()) {
count++;
}
}
} else {
// 统计所有Slot(返回所有注册号)
count = slots.size();
}
// 2. 返回数量(返回统计结果)
*pulCount = count;
// 3. 如果pSlotList不为NULL,填充列表(返回注册号列表)
if (pSlotList != NULL) {
CK_ULONG index = 0;
for (Slot* slot : slots) {
if (tokenPresent) {
if (slot->isTokenPresent()) {
pSlotList[index] = slot->getSlotID();
index++;
}
} else {
pSlotList[index] = slot->getSlotID();
index++;
}
}
}
return CKR_OK;
}
使用示例:
/* 获取Slot列表示例 */
CK_SLOT_ID slotList[10];
CK_ULONG slotCount;
// 只获取有Token的Slot
rv = C_GetSlotList(CK_TRUE, slotList, &slotCount);
// slotCount = 实际Slot数量
// slotList = Slot ID数组
// 典型SoftHSM2配置:slotCount = 1,slotList[0] = 0
waitForSlotEvent:Slot变动通知
waitForSlotEvent等待Slot状态变化:
CK_RV SlotManager::waitForSlotEvent(CK_FLAGS flags,
CK_SLOT_ID_PTR pSlot,
CK_VOID_PTR pReserved)
{
// 软件HSM通常不支持Slot事件
// 真实HSM可以检测硬件插入/拔出
// SoftHSM2返回CKR_NO_EVENT
return CKR_NO_EVENT;
}
硬件HSM vs 软件HSM:
Slot事件对比:
硬件HSM:
├── 支持waitForSlotEvent
├── 可以检测Token插入
├── 可以检测Token拔出
├── 有物理事件触发
└── 车载理解:检测HSM芯片连接状态
软件HSM(SoftHSM2):
├── 不支持waitForSlotEvent
├── 返回CKR_NO_EVENT
├── 无物理事件
├── Token状态由软件控制
└── 无硬件检测机制
一个类比:政府注册中心
政府注册中心类比:
SlotManager(注册中心)
│
├── 注册号管理(Slot管理)
│ ├── getSlotList:列出所有注册号
│ ├── getSlot:查询指定注册号
│ ├── isValidSlot:验证注册号有效性
│ └── waitForSlotEvent:等待注册变动(不支持)
│ │
│ ├── 软件注册中心(SoftHSM2):
│ │ ├── 注册号在配置文件定义
│ │ ├── 企业在数据库注册
│ │ ├── 可以凭空创建
│ │ └── 无物理限制
│ │
│ └── 硬件注册中心(真实HSM):
│ │ ├── 注册号对应物理端口
│ │ ├── 企业对应物理芯片
│ │ ├── 创建需要物理检测
│ │ └── 支持硬件事件
│ │
│ └── 车载理解:
│ │ ├── 注册号 = HSM芯片编号
│ │ ├── 注册中心 = SlotManager
│ │ ├── 企业档案 = Token数据
│ │ ├── 企业窗口 = Session
│ │ └── 企业档案库 = ObjectStore
│ │
│ └── 注册流程:
│ │ ├── 1. 分配注册号(分配Slot ID)
│ │ ├── 2. 企业注册(initToken)
│ │ ├── 3. 设置企业名称(setLabel)
│ │ ├── 4. 设置管理员密码(initPIN)
│ │ ├── 5. 创建档案库(ObjectStore)
│ │ └── 6. 企业开业(Token可用)
│ │
│ └── 查询流程:
│ │ ├── 1. 查询注册号列表(getSlotList)
│ │ ├── 2. 查询企业是否存在(isTokenPresent)
│ │ ├── 3. 查询企业信息(getTokenInfo)
│ │ └── 4. 打开企业窗口(openSession)
│ │
└── 关键理解:
├── Slot是逻辑概念(注册号)
├── Token是数据实体(企业档案)
├── 注册号可以空置(无Token)
└── 软件HSM用数据库模拟注册
本篇小结
SlotManager是Token的“注册中心“:
SlotManager职责:
- 管理Slot列表
- 查询Slot信息
- 加载已有Token
- 监听Slot事件(软件HSM不支持)
Slot类:
- Slot ID:注册号
- Slot信息:注册号信息
- Token关联:企业档案
- 机制查询:服务能力
Token类:
- Token信息:企业信息
- PIN管理:密码管理
- Session管理:窗口管理
- Object存储:档案存储
初始化流程:
- 检查Slot状态
- 创建Token
- 设置Label
- 初始化SO PIN
- 初始化ObjectStore
软件vs硬件:
- 软件:Slot在配置定义,Token在数据库
- 硬件:Slot对应物理端口,Token对应芯片
下一节,我们将分析Session管理模块——Session如何被创建和管理。
【下集预告】
SessionManager如何工作?
Session如何被创建?
Session状态如何管理?
登录状态如何共享?
下一节,Session管理模块。