Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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=返回所有Slot
  • pSlotList: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 ID
  • pSOPin: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管理函数。