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

4.2 从PKCS#11头文件到实现:解剖“标准与现实的桥梁“

一个问题:规范如何变成代码?

假设你是RSA实验室的工程师,1994年你刚刚发布了PKCS#11规范。

规范定义了约68个函数:

  • C_Initialize、C_Finalize
  • C_OpenSession、C_CloseSession
  • C_Sign、C_Verify

现在你需要回答一个问题:

这些函数怎么被应用程序调用?

你可能会想:

  • 每个厂商自己实现这些函数
  • 应用程序需要一种方式找到这些函数
  • 不同厂商的实现如何兼容?

答案在PKCS#11头文件中。


头文件的作用:图纸与施工的“桥梁“

想象你在盖房子:

  • 规范文档是“设计图纸“(描述要建什么)
  • 头文件是“施工蓝图“(定义具体接口)
  • 实现代码是“施工过程“(建造房子)

PKCS#11头文件就是这个“施工蓝图“:

  • 把规范中的抽象概念转为具体类型
  • 定义函数签名(参数、返回值)
  • 定义常量值(错误码、机制类型)

三个头文件的关系

PKCS#11头文件结构:

pkcs11.h(主头文件)
│
│  "总蓝图"
│  ├── 包含pkcs11f.h和pkcs11t.h
│  ├── 版本定义
│  ├── 平台适配
│  └── 入口点声明
│
├── pkcs11f.h(函数声明)
│   │
│   "施工手册"
│   ├── CK_FUNCTION_LIST结构
│   ├── 所有CK_C_*函数指针类型
│   └── 函数原型声明
│
└── pkcs11t.h(类型定义)
    │
    "材料规格"
    ├── CK_*基础类型(CK_RV, CK_ULONG等)
    ├── 结构体定义(CK_MECHANISM, CK_ATTRIBUTE等)
    ├── 常量定义(CKM_*, CKA_*, CKO_*等)
    └── 标志位定义(CKF_*等)

pkcs11.h:主入口文件

pkcs11.h是应用程序唯一需要包含的头文件:

/* pkcs11.h核心内容 */

#include "pkcs11f.h"  // 函数声明
#include "pkcs11t.h"  // 类型定义

/* 版本定义(告诉施工队用什么标准) */
#define CK_PKCS11_VERSION_MAJOR 3
#define CK_PKCS11_VERSION_MINOR 1
#define CK_PKCS11_VERSION_AMENDMENT 0

/* 平台适配(适应不同操作系统) */
#ifdef _WIN32
    #pragma pack(push, cryptoki, 1)  // Windows需要特殊对齐
#else
    /* Linux/Unix无需特殊处理 */
#endif

/* Cryptoki版本结构 */
typedef struct CK_VERSION {
    CK_BYTE major;  // 主版本
    CK_BYTE minor;  // 次版本
} CK_VERSION;

/* 函数列表结构(所有服务窗口的目录) */
typedef struct CK_FUNCTION_LIST {
    CK_VERSION version;               // 版本号
    CK_C_Initialize C_Initialize;     // 初始化窗口
    CK_C_Finalize C_Finalize;         // 清理窗口
    CK_C_GetInfo C_GetInfo;           // 信息窗口
    CK_C_GetSlotList C_GetSlotList;   // Slot列表窗口
    CK_C_OpenSession C_OpenSession;   // 打开Session窗口
    CK_C_CloseSession C_CloseSession; // 关闭Session窗口
    CK_C_Login C_Login;               // 登录窗口
    CK_C_SignInit C_SignInit;         // 签名初始化窗口
    CK_C_Sign C_Sign;                 // 签名窗口
    CK_C_Verify C_Verify;             // 验证窗口
    // ... 约60个函数指针
} CK_FUNCTION_LIST;

为什么用函数列表?

想象一个城市的服务中心:

  • 所有服务窗口都在一栋大楼里
  • 每个窗口有一个编号和位置
  • 公民进入大楼后,可以找到任何窗口

CK_FUNCTION_LIST就是这个“服务目录“:

  • 应用程序获取函数列表指针
  • 通过指针访问所有PKCS#11函数
  • 不需要知道每个函数的具体位置

pkcs11t.h:类型与常量定义

pkcs11t.h定义了所有“材料规格“:

/* pkcs11t.h核心内容 */

/* 基础类型定义(材料规格) */
typedef unsigned long     CK_ULONG;   // 无符号长整数
typedef long              CK_LONG;    // 有符号长整数
typedef unsigned char     CK_BYTE;    // 字节
typedef char              CK_CHAR;    // 字符
typedef CK_BYTE           CK_BBOOL;   // 布尔值

/* Handle类型(编号类型) */
typedef CK_ULONG          CK_SLOT_ID;          // Slot编号
typedef CK_ULONG          CK_SESSION_HANDLE;   // Session编号
typedef CK_ULONG          CK_OBJECT_HANDLE;    // Object编号

/* 返回值类型(施工结果) */
typedef CK_ULONG          CK_RV;               // 返回值

/* 返回值常量(施工成功/失败) */
#define CKR_OK                    0x00000000  // 成功
#define CKR_CANCEL                0x00000001  // 取消
#define CKR_HOST_MEMORY           0x00000002  // 内存不足
#define CKR_SLOT_ID_INVALID       0x00000003  // Slot编号无效
#define CKR_GENERAL_ERROR         0x00000005  // 通用错误
#define CKR_FUNCTION_FAILED       0x00000006  // 函数失败
// ... 更多错误码

/* 对象类型(材料种类) */
#define CKO_DATA                  0x00000000  // 数据对象
#define CKO_CERTIFICATE           0x00000001  // 证书对象
#define CKO_PUBLIC_KEY            0x00000002  // 公钥对象
#define CKO_PRIVATE_KEY           0x00000003  // 私钥对象
#define CKO_SECRET_KEY            0x00000004  // 对称密钥对象

/* 密钥类型(材料型号) */
#define CKK_RSA                   0x00000000  // RSA密钥
#define CKK_DSA                   0x00000001  // DSA密钥
#define CKK_ECDSA                 0x00000003  // ECDSA密钥
#define CKK_AES                   0x0000001F  // AES密钥(31)

/* 机制类型(施工方法) */
#define CKM_RSA_PKCS_KEY_PAIR_GEN 0x00000000  // RSA密钥生成
#define CKM_RSA_PKCS              0x00000001  // RSA PKCS签名
#define CKM_RSA_X_509             0x00000003  // RSA裸签名
#define CKM_AES_KEY_GEN           0x00001080  // AES密钥生成
#define CKM_AES_ECB               0x00001081  // AES ECB加密
#define CKM_AES_CBC               0x00001082  // AES CBC加密
// ... 更多机制

/* 属性类型(材料属性) */
#define CKA_CLASS                 0x00000000  // 对象类
#define CKA_TOKEN                 0x00000001  // 是否Token对象
#define CKA_PRIVATE               0x00000002  // 是否私有
#define CKA_LABEL                 0x00000003  // 标签
#define CKA_KEY_TYPE              0x00000100  // 密钥类型(256)
#define CKA_VALUE                 0x00000017  // 密钥值
#define CKA_MODULUS               0x00000080  // RSA模数
#define CKA_SENSITIVE             0x00000083  // 是否敏感
#define CKA_EXTRACTABLE           0x00000084  // 是否可导出
// ... 更多属性

pkcs11f.h:函数声明

pkcs11f.h定义了所有“施工手册“:

/* pkcs11f.h核心内容 */

/* 函数指针类型定义(CK_PTR即*,CK_PTR CK_C_Initialize展开为*CK_C_Initialize) */
typedef CK_RV (CK_PTR CK_C_Initialize)(
    CK_VOID_PTR pInitArgs);

typedef CK_RV (CK_PTR CK_C_Finalize)(
    CK_VOID_PTR pReserved);

typedef CK_RV (CK_PTR CK_C_OpenSession)(
    CK_SLOT_ID slotID,
    CK_FLAGS flags,
    CK_VOID_PTR pApplication,
    CK_NOTIFY Notify,
    CK_SESSION_HANDLE_PTR phSession);

typedef CK_RV (CK_PTR CK_C_Sign)(
    CK_SESSION_HANDLE hSession,
    CK_BYTE_PTR pData,
    CK_ULONG ulDataLen,
    CK_BYTE_PTR pSignature,
    CK_ULONG_PTR pulSignatureLen);

// ... 约60个函数指针类型

/* 函数原型声明 */
CK_RV C_Initialize(CK_VOID_PTR pInitArgs);
CK_RV C_Finalize(CK_VOID_PTR pReserved);
CK_RV C_OpenSession(...);
CK_RV C_Sign(...);
// ... 约60个函数声明

CK_FUNCTION_LIST:函数列表详解

CK_FUNCTION_LIST是PKCS#11的核心设计:

CK_FUNCTION_LIST设计意图:

问题:
├── 不同厂商实现PKCS#11
├── 应用程序如何找到函数?
├── 不同厂商的函数地址不同
└── 需要一种统一的访问方式

解决方案:
├── 每个厂商导出C_GetFunctionList函数
├── C_GetFunctionList返回函数列表指针
├── 函数列表包含所有函数指针
└── 应用程序通过指针调用函数

调用流程:
┌─────────────────────────────────────┐
│                                     │
│  应用程序                            │
│     │                               │
│     │ dlopen("libsofthsm2.so")      │
│     ▼                               │
│  获取C_GetFunctionList函数地址       │
│     │                               │
│     │ 调用C_GetFunctionList()       │
│     ▼                               │
│  获取CK_FUNCTION_LIST指针           │
│     │                               │
│     │ 通过指针访问所有函数           │
│     ▼                               │
│  调用C_Initialize/C_Sign等          │
│                                     │
└─────────────────────────────────────┘

应用程序如何使用头文件

一个典型应用程序的使用流程:

/* 应用程序使用PKCS#11头文件 */

#include <pkcs11.h>  // 只需包含这一个头文件

int main()
{
    CK_RV rv;
    CK_FUNCTION_LIST_PTR pFunctionList;
    CK_SESSION_HANDLE hSession;
    
    /* 1. 加载PKCS#11库(打开服务中心大楼) */
    void* hModule = dlopen("/usr/lib/softhsm/libsofthsm2.so", RTLD_NOW);
    
    /* 2. 获取C_GetFunctionList函数地址(找到服务目录窗口) */
    CK_RV (*C_GetFunctionList)(CK_FUNCTION_LIST_PTR_PTR) =
        dlsym(hModule, "C_GetFunctionList");
    
    /* 3. 获取函数列表(拿到服务目录) */
    rv = C_GetFunctionList(&pFunctionList);
    
    /* 4. 使用函数列表调用函数(去各个窗口办事) */
    
    /* 初始化 */
    rv = pFunctionList->C_Initialize(NULL);
    
    /* 打开Session */
    rv = pFunctionList->C_OpenSession(0, CKF_SERIAL_SESSION,
                                      NULL, NULL, &hSession);
    
    /* 签名 */
    rv = pFunctionList->C_SignInit(hSession, &mechanism, hKey);
    rv = pFunctionList->C_Sign(hSession, data, dataLen, sig, &sigLen);
    
    /* 清理 */
    rv = pFunctionList->C_Finalize(NULL);
    
    return 0;
}

SoftHSM2如何实现头文件定义

SoftHSM2的main.cpp实现了头文件定义的接口:

/* main.cpp:实现PKCS#11函数导出 */

#include <pkcs11.h>
#include "SoftHSM.h"

/* 函数列表(服务目录) */
static CK_FUNCTION_LIST functionList =
{
    { CRYPTOKI_VERSION_MAJOR, CRYPTOKI_VERSION_MINOR },  // 版本
    
    C_Initialize,   // 初始化窗口
    C_Finalize,     // 清理窗口
    C_GetInfo,      // 信息窗口
    C_GetFunctionList,  // 函数列表窗口
    C_GetSlotList,  // Slot列表窗口
    C_GetSlotInfo,  // Slot信息窗口
    C_GetTokenInfo, // Token信息窗口
    C_OpenSession,  // 打开Session窗口
    C_CloseSession, // 关闭Session窗口
    C_Login,        // 登录窗口
    C_Logout,       // 注销窗口
    C_CreateObject, // 创建对象窗口
    C_DestroyObject,// 销毁对象窗口
    C_FindObjects,  // 查找对象窗口
    C_SignInit,     // 签名初始化窗口
    C_Sign,         // 签名窗口
    C_VerifyInit,   // 验证初始化窗口
    C_Verify,       // 验证窗口
    C_EncryptInit,  // 加密初始化窗口
    C_Encrypt,      // 加密窗口
    C_DecryptInit,  // 解密初始化窗口
    C_Decrypt,      // 解密窗口
    // ... 约60个函数
};

/* 导出函数实现 */
extern "C" CK_RV C_GetFunctionList(CK_FUNCTION_LIST_PTR_PTR ppFunctionList)
{
    *ppFunctionList = &functionList;
    return CKR_OK;
}

extern "C" CK_RV C_Initialize(CK_VOID_PTR pInitArgs)
{
    try {
        return SoftHSM::i()->C_Initialize(pInitArgs);
    } catch (...) {
        FatalException();
    }
    return CKR_FUNCTION_FAILED;
}

extern "C" CK_RV C_Sign(CK_SESSION_HANDLE hSession,
                         CK_BYTE_PTR pData,
                         CK_ULONG ulDataLen,
                         CK_BYTE_PTR pSignature,
                         CK_ULONG_PTR pulSignatureLen)
{
    try {
        return SoftHSM::i()->C_Sign(hSession, pData, ulDataLen,
                                    pSignature, pulSignatureLen);
    } catch (...) {
        FatalException();
    }
    return CKR_FUNCTION_FAILED;
}

// ... 约60个函数实现

一个类比:服务中心的“服务目录“

服务中心类比:

服务中心大楼(PKCS#11库)
│
├── pkcs11.h(总蓝图)
│   ├── 楼层设计
│   ├── 窗口位置
│   └── 服务目录结构
│
├── pkcs11t.h(材料规格)
│   ├── 编号类型(Slot编号、Session编号)
│   ├── 材料类型(公钥、私钥、证书)
│   ├── 施工方法(RSA、AES、SHA)
│   └── 施工结果(成功、失败)
│
├── pkcs11f.h(施工手册)
│   ├── 每个窗口的操作步骤
│   ├── 输入参数规格
│   └── 输出结果规格
│
└── CK_FUNCTION_LIST(服务目录)
    │
    ├── 目录内容:
    │   ├── 初始化窗口位置
    │   ├── Session窗口位置
    │   ├── 签名窗口位置
    │   ├── 加密窗口位置
    │   └── ...
    │
    └── 使用方式:
        ├── 公民进入大楼
        ├── 拿到服务目录
        ├── 根据目录找到窗口
        └── 在窗口办事

关键设计:
├── 不同服务中心(不同厂商HSM)
├── 目录格式相同(CK_FUNCTION_LIST)
├── 公民只需学会看目录
└── 可以去任何服务中心办事

本篇小结

PKCS#11头文件是规范与实现的“桥梁“:

三个头文件

  • pkcs11.h:主入口,包含其他头文件
  • pkcs11t.h:类型定义(CK_ULONG, CK_RV, CKM_, CKA_
  • pkcs11f.h:函数声明(所有函数原型)

CK_FUNCTION_LIST

  • 函数指针列表,约60个函数
  • 应用程序通过它访问所有函数
  • 不同厂商导出相同的结构

使用流程

  • dlopen加载库
  • dlsym获取C_GetFunctionList
  • 调用C_GetFunctionList获取函数列表
  • 通过函数列表调用函数

SoftHSM2实现

  • main.cpp导出CK_FUNCTION_LIST
  • extern “C“确保C兼容
  • try-catch捕获异常

下一节,我们将深入SoftHSM.cpp——看核心类如何设计,单例模式如何工作。

【下集预告】

  • SoftHSM.cpp有398KB,13830行代码

  • SoftHSM类如何设计?

  • 单例模式如何实现?

  • 各模块如何被管理?

下一节,SoftHSM.cpp深度解析。