3.10 初始化与Slot管理函数:打开密码世界的“大门“
函数的调用序列
使用PKCS#11有一个固定的调用序列:
PKCS#11标准调用序列:
1. C_Initialize() 初始化库
↓
2. C_GetSlotList() 获取Slot列表
↓
3. C_GetSlotInfo() 查询Slot信息(可选)
C_GetTokenInfo() 查询Token信息(可选)
↓
4. C_OpenSession() 打开Session
↓
5. C_Login() 登录(可选,取决于Token)
↓
6. 密码操作...
C_FindObjects() 查找密钥
C_Sign() 签名
C_Encrypt() 加密
...
↓
7. C_Logout() 注销
↓
8. C_CloseSession() 关闭Session
↓
9. C_Finalize() 清理库
这个序列像打开一扇大门的步骤:先敲门、再进门、登记身份、办事、离开、关门。
C_Initialize:敲门
功能:初始化PKCS#11库,为后续操作做准备。
原型:
CK_RV C_Initialize(CK_VOID_PTR pInitArgs);
参数:
pInitArgs:初始化参数,可以为NULL_PTR(单线程应用)- 多线程应用需要传入CK_C_INITIALIZE_ARGS结构
返回值:
- CKR_OK:成功
- CKR_CRYPTOKI_ALREADY_INITIALIZED:已经初始化
- CKR_ARGUMENTS_BAD:参数无效
- 其他错误码
示例:
/* 单线程应用初始化 */
CK_RV rv;
rv = C_Initialize(NULL_PTR);
if (rv != CKR_OK) {
printf("初始化失败:%08x\n", rv);
return rv;
}
/* 多线程应用初始化 */
CK_C_INITIALIZE_ARGS initArgs = {NULL_PTR, NULL_PTR, NULL_PTR, NULL_PTR,
CKF_OS_LOCKING_OK, NULL_PTR};
rv = C_Initialize(&initArgs);
if (rv != CKR_OK) {
printf("初始化失败:%08x\n", rv);
return rv;
}
注意事项:
- C_Initialize只能调用一次,重复调用返回CKR_CRYPTOKI_ALREADY_INITIALIZED
- 多线程应用必须设置CKF_OS_LOCKING_OK标志
- 在调用任何其他PKCS#11函数之前,必须先调用C_Initialize
C_Finalize:关门
功能:清理PKCS#11库,释放资源。
原型:
CK_RV C_Finalize(CK_VOID_PTR pReserved);
参数:
pReserved:保留参数,必须为NULL_PTR
返回值:
- CKR_OK:成功
- CKR_CRYPTOKI_NOT_INITIALIZED:库未初始化
示例:
CK_RV rv;
rv = C_Finalize(NULL_PTR);
if (rv != CKR_OK) {
printf("清理失败:%08x\n", rv);
}
注意事项:
- 所有Session必须先关闭(C_CloseSession)
- 所有Object必须先销毁(如果不再需要)
- C_Finalize之后不能再调用任何PKCS#11函数(除了C_Initialize)
C_GetInfo:查看图书馆目录
功能:获取PKCS#11库的信息。
原型:
CK_RV C_GetInfo(CK_INFO_PTR pInfo);
CK_INFO结构:
typedef struct CK_INFO {
CK_VERSION cryptokiVersion; /* Cryptoki版本 */
CK_UTF8CHAR manufacturerID[32]; /* 厂商ID */
CK_FLAGS flags; /* 标志 */
CK_UTF8CHAR libraryDescription[32]; /* 库描述 */
CK_VERSION libraryVersion; /* 库版本 */
} CK_INFO;
示例:
CK_INFO info;
CK_RV rv;
rv = C_GetInfo(&info);
if (rv == CKR_OK) {
printf("Cryptoki版本:%d.%d\n", info.cryptokiVersion.major, info.cryptokiVersion.minor);
printf("厂商:%s\n", info.manufacturerID);
printf("库描述:%s\n", info.libraryDescription);
}
C_GetSlotList:查找可用窗口
功能:获取Slot列表。
原型:
CK_RV C_GetSlotList(CK_BBOOL tokenPresent,
CK_SLOT_ID_PTR pSlotList,
CK_ULONG_PTR pulCount);
参数:
tokenPresent:TRUE=只返回有Token的Slot,FALSE=返回所有SlotpSlotList:Slot ID数组(可以为NULL_PTR,先获取数量)pulCount:Slot数量
调用模式:
/* 两步调用模式:先获取数量,再获取列表 */
CK_ULONG ulSlotCount;
CK_SLOT_ID_PTR pSlotList;
CK_RV rv;
// 第一步:获取Slot数量
rv = C_GetSlotList(CK_TRUE, NULL_PTR, &ulSlotCount);
if (rv != CKR_OK) {
printf("获取Slot数量失败\n");
return rv;
}
if (ulSlotCount == 0) {
printf("没有Token\n");
return CKR_NO_TOKEN;
}
// 第二步:分配内存,获取Slot列表
pSlotList = (CK_SLOT_ID_PTR)malloc(ulSlotCount * sizeof(CK_SLOT_ID));
rv = C_GetSlotList(CK_TRUE, pSlotList, &ulSlotCount);
if (rv != CKR_OK) {
free(pSlotList);
printf("获取Slot列表失败\n");
return rv;
}
// 使用Slot列表
for (CK_ULONG i = 0; i < ulSlotCount; i++) {
printf("Slot %lu: ID = %lu\n", i, pSlotList[i]);
}
free(pSlotList);
C_GetSlotInfo:查看窗口详情
功能:获取Slot的详细信息。
原型:
CK_RV C_GetSlotInfo(CK_SLOT_ID slotID,
CK_SLOT_INFO_PTR pInfo);
CK_SLOT_INFO结构:
typedef struct CK_SLOT_INFO {
CK_UTF8CHAR slotDescription[64]; /* Slot描述 */
CK_UTF8CHAR manufacturerID[32]; /* 厂商ID */
CK_FLAGS flags; /* Slot标志 */
CK_VERSION hardwareVersion; /* 硬件版本 */
CK_VERSION firmwareVersion; /* 固件版本 */
} CK_SLOT_INFO;
/* flags可能值 */
#define CKF_TOKEN_PRESENT 0x00000001 /* Token存在 */
#define CKF_REMOVABLE_DEVICE 0x00000002 /* 可移除设备 */
#define CKF_HW_SLOT 0x00000004 /* 硬件Slot */
示例:
CK_SLOT_INFO slotInfo;
CK_RV rv;
rv = C_GetSlotInfo(slotID, &slotInfo);
if (rv == CKR_OK) {
printf("Slot描述:%s\n", slotInfo.slotDescription);
printf("厂商:%s\n", slotInfo.manufacturerID);
printf("Token存在:%s\n", (slotInfo.flags & CKF_TOKEN_PRESENT) ? "是" : "否");
printf("可移除:%s\n", (slotInfo.flags & CKF_REMOVABLE_DEVICE) ? "是" : "否");
printf("硬件Slot:%s\n", (slotInfo.flags & CKF_HW_SLOT) ? "是" : "否");
}
C_GetTokenInfo:查看保险箱详情
功能:获取Token的详细信息。
原型:
CK_RV C_GetTokenInfo(CK_SLOT_ID slotID,
CK_TOKEN_INFO_PTR pInfo);
CK_TOKEN_INFO结构:
typedef struct CK_TOKEN_INFO {
CK_UTF8CHAR label[32]; /* Token标签 */
CK_UTF8CHAR manufacturerID[32]; /* 厂商ID */
CK_UTF8CHAR model[16]; /* 型号 */
CK_UTF8CHAR serialNumber[16]; /* 序列号 */
CK_FLAGS flags; /* Token标志 */
CK_ULONG ulMaxSessionCount; /* 最大Session数 */
CK_ULONG ulSessionCount; /* 当前Session数 */
CK_ULONG ulMaxRwSessionCount; /* 最大读写Session数 */
CK_ULONG ulRwSessionCount; /* 当前读写Session数 */
CK_ULONG ulMaxPinLen; /* 最大PIN长度 */
CK_ULONG ulMinPinLen; /* 最小PIN长度 */
CK_ULONG ulTotalPublicMemory; /* 公共内存总量 */
CK_ULONG ulFreePublicMemory; /* 公共内存空闲 */
CK_ULONG ulTotalPrivateMemory; /* 私有内存总量 */
CK_ULONG ulFreePrivateMemory; /* 私有内存空闲 */
CK_VERSION hardwareVersion; /* 硬件版本 */
CK_VERSION firmwareVersion; /* 固件版本 */
CK_CHAR utcTime[16]; /* UTC时间 */
} CK_TOKEN_INFO;
/* flags重要值 */
#define CKF_RNG 0x00000001 /* 有随机数生成器 */
#define CKF_WRITE_PROTECTED 0x00000002 /* 写保护 */
#define CKF_LOGIN_REQUIRED 0x00000004 /* 需要登录 */
#define CKF_USER_PIN_INITIALIZED 0x00000008 /* 用户PIN已初始化 */
#define CKF_TOKEN_INITIALIZED 0x00000400 /* Token已初始化 */
#define CKF_SO_PIN_TO_BE_CHANGED 0x00000800 /* SO PIN需要修改 */
#define CKF_USER_PIN_TO_BE_CHANGED 0x00001000 /* 用户PIN需要修改 */
示例:
CK_TOKEN_INFO tokenInfo;
CK_RV rv;
rv = C_GetTokenInfo(slotID, &tokenInfo);
if (rv == CKR_OK) {
printf("Token标签:%s\n", tokenInfo.label);
printf("厂商:%s\n", tokenInfo.manufacturerID);
printf("型号:%s\n", tokenInfo.model);
printf("序列号:%s\n", tokenInfo.serialNumber);
printf("需要登录:%s\n", (tokenInfo.flags & CKF_LOGIN_REQUIRED) ? "是" : "否");
printf("PIN长度范围:%lu-%lu\n", tokenInfo.ulMinPinLen, tokenInfo.ulMaxPinLen);
}
C_InitToken:初始化保险箱
功能:初始化Token,设置SO PIN和标签。
原型:
CK_RV C_InitToken(CK_SLOT_ID slotID,
CK_UTF8CHAR_PTR pSOPin,
CK_ULONG ulSOPinLen,
CK_UTF8CHAR_PTR pLabel);
参数:
slotID:Slot IDpSOPin:Security Officer PIN(安全管理员PIN)ulSOPinLen:SO PIN长度pLabel:Token标签(32字节)
注意事项:
- 只能由SO执行
- Token未初始化时调用
- 会清除Token中的所有Object
示例:
CK_UTF8CHAR soPin[] = "12345678";
CK_UTF8CHAR label[] = "MySecureToken";
CK_RV rv;
rv = C_InitToken(slotID, soPin, sizeof(soPin)-1, label);
if (rv == CKR_OK) {
printf("Token初始化成功\n");
} else if (rv == CKR_TOKEN_ALREADY_INITIALIZED) {
printf("Token已初始化\n");
} else {
printf("Token初始化失败:%08x\n", rv);
}
C_InitPIN:设置用户PIN
功能:初始化用户PIN。
原型:
CK_RV C_InitPIN(CK_SESSION_HANDLE hSession,
CK_UTF8CHAR_PTR pPin,
CK_ULONG ulPinLen);
注意事项:
- 必须在读写Session中调用
- 必须以SO身份登录后调用
示例:
CK_UTF8CHAR userPin[] = "123456";
CK_RV rv;
// SO登录后
rv = C_Login(hSession, CKU_SO, soPin, soPinLen);
// 初始化用户PIN
rv = C_InitPIN(hSession, userPin, sizeof(userPin)-1);
if (rv == CKR_OK) {
printf("用户PIN初始化成功\n");
}
C_SetPIN:修改PIN
功能:修改当前登录用户的PIN。
原型:
CK_RV C_SetPIN(CK_SESSION_HANDLE hSession,
CK_UTF8CHAR_PTR pOldPin,
CK_ULONG ulOldPinLen,
CK_UTF8CHAR_PTR pNewPin,
CK_ULONG ulNewPinLen);
示例:
CK_UTF8CHAR oldPin[] = "123456";
CK_UTF8CHAR newPin[] = "newpass";
CK_RV rv;
rv = C_SetPIN(hSession, oldPin, sizeof(oldPin)-1, newPin, sizeof(newPin)-1);
if (rv == CKR_OK) {
printf("PIN修改成功\n");
}
一个完整的初始化流程示例
CK_RV rv;
CK_C_INITIALIZE_ARGS initArgs = {NULL_PTR, NULL_PTR, NULL_PTR, NULL_PTR,
CKF_OS_LOCKING_OK, NULL_PTR};
CK_ULONG ulSlotCount;
CK_SLOT_ID_PTR pSlotList;
CK_SLOT_ID slotID;
CK_TOKEN_INFO tokenInfo;
// 1. 初始化库
rv = C_Initialize(&initArgs);
if (rv != CKR_OK) {
fprintf(stderr, "C_Initialize失败:%08x\n", rv);
return -1;
}
// 2. 获取Slot列表
rv = C_GetSlotList(CK_TRUE, NULL_PTR, &ulSlotCount);
if (rv != CKR_OK || ulSlotCount == 0) {
fprintf(stderr, "没有可用的Token\n");
C_Finalize(NULL_PTR);
return -1;
}
pSlotList = malloc(ulSlotCount * sizeof(CK_SLOT_ID));
rv = C_GetSlotList(CK_TRUE, pSlotList, &ulSlotCount);
if (rv != CKR_OK) {
free(pSlotList);
C_Finalize(NULL_PTR);
return -1;
}
// 3. 选择第一个Slot
slotID = pSlotList[0];
free(pSlotList);
// 4. 查询Token信息
rv = C_GetTokenInfo(slotID, &tokenInfo);
if (rv == CKR_OK) {
printf("Token:%s\n", tokenInfo.label);
printf("需要登录:%s\n", (tokenInfo.flags & CKF_LOGIN_REQUIRED) ? "是" : "否");
}
// 后续:打开Session、登录、执行操作...
// 清理
C_Finalize(NULL_PTR);
return 0;
本篇小结
初始化与Slot管理函数是PKCS#11的“入门“操作:
函数序列:
- C_Initialize → C_GetSlotList → C_OpenSession → C_Login → 操作 → C_Logout → C_CloseSession → C_Finalize
核心函数:
- C_Initialize/C_Finalize:库的初始化和清理
- C_GetSlotList:获取Slot列表(两步调用模式)
- C_GetSlotInfo/C_GetTokenInfo:查询Slot/Token信息
- C_InitToken/C_InitPIN/C_SetPIN:Token初始化和PIN管理
重要概念:
- Slot:HSM的物理/逻辑接口
- Token:HSM的逻辑实例,存储密钥和证书
- SO PIN:安全管理员PIN,用于初始化Token
- User PIN:普通用户PIN,用于日常使用
下一节,我们将深入Session管理函数——如何与Token建立交互会话。
【下集预告】
C_OpenSession:如何打开Session?
C_CloseSession:如何关闭?
C_Login/C_Logout:如何登录注销?
Session状态:读/写、登录/未登录
下一节,Session管理函数。