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.12 Object管理函数:密钥与证书的“仓储管理“

Object管理函数概览

PKCS#11规范第5.7节定义了Object管理函数,用于创建、查找、读取、修改和删除Token上的对象。

Object管理函数列表:

函数名                作用
────────────────────────────────────────
C_CreateObject       创建新对象
C_CopyObject         复制对象
C_DestroyObject      删除对象
C_GetObjectSize      获取对象大小
C_GetAttributeValue  读取对象属性
C_SetAttributeValue  设置对象属性
C_FindObjectsInit    初始化对象搜索
C_FindObjects        继续搜索获取对象
C_FindObjectsFinal   结束搜索操作

Object管理是PKCS#11的“仓储系统“——密钥、证书、数据都需要通过这些函数来管理。


C_CreateObject:创建对象

函数原型

CK_RV C_CreateObject(
    CK_SESSION_HANDLE hSession,     // 会话句柄
    CK_ATTRIBUTE_PTR pTemplate,     // 属性模板
    CK_ULONG ulCount,               // 模板属性数量
    CK_OBJECT_HANDLE_PTR phObject   // 返回对象句柄
);

核心机制:通过属性模板定义对象的类型和属性值。

创建对象流程:

应用程序                PKCS#11库                Token
    │                      │                      │
    │ C_CreateObject       │                      │
    │ (template)           │                      │
    │─────────────────────→│                      │
    │                      │ 验证模板             │
    │                      │─────────────────────→│
    │                      │                      │ 创建对象
    │                      │                      │ 分配CKA_UNIQUE_ID
    │                      │─────────────────────→│
    │                      │ 返回对象句柄         │
    │─────────────────────→│                      │
    │ hObject              │                      │

创建密钥对象示例

/* 创建AES密钥对象 */

CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY;
CK_KEY_TYPE keyType = CKK_AES;
CK_BYTE keyId[] = {0x01};
CK_BYTE keyValue[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                      0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
                      0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
                      0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F};  // 32字节密钥
CK_BBOOL true = CK_TRUE;
CK_BBOOL sensitive = CK_TRUE;  // 设置敏感属性
CK_BBOOL extractable = CK_FALSE;  // 不可导出

CK_ATTRIBUTE keyTemplate[] = {
    {CKA_CLASS, &keyClass, sizeof(keyClass)},
    {CKA_KEY_TYPE, &keyType, sizeof(keyType)},
    {CKA_TOKEN, &true, sizeof(true)},      // Token对象(持久化)
    {CKA_ID, keyId, sizeof(keyId)},
    {CKA_VALUE, keyValue, sizeof(keyValue)},
    {CKA_SENSITIVE, &sensitive, sizeof(sensitive)},
    {CKA_EXTRACTABLE, &extractable, sizeof(extractable)}
};

CK_OBJECT_HANDLE hKey;
rv = C_CreateObject(hSession, keyTemplate, 7, &hKey);

if (rv == CKR_OK) {
    printf("AES密钥创建成功,句柄=%lu\n", hKey);
}

重要约束

  • 通过C_CreateObject创建的密钥,CKA_LOCAL = FALSE(不是本地生成的)
  • 密钥的CKA_ALWAYS_SENSITIVE = FALSE
  • 密钥的CKA_NEVER_EXTRACTABLE = FALSE

创建证书对象示例

/* 创建X.509证书对象 */

CK_OBJECT_CLASS certClass = CKO_CERTIFICATE;
CK_CERTIFICATE_TYPE certType = CKC_X_509;
CK_UTF8CHAR label[] = "Server Certificate";
CK_BYTE subject[] = {0x30, 0x31, 0x30, 0x0F, 0x31, 0x0D, 0x30, 0x0B,
                      0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x04, 0x54,
                      0x65, 0x73, 0x74};  // DER编码 Subject=Test
CK_BYTE certValue[] = {0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01,
                        0x00, 0xA0, 0xB1, 0xC2, 0xD3, 0xE4, 0xF5, 0x00};  // DER编码证书(示例)
CK_BYTE certId[] = {0x01};
CK_BBOOL true = CK_TRUE;

CK_ATTRIBUTE certTemplate[] = {
    {CKA_CLASS, &certClass, sizeof(certClass)},
    {CKA_CERTIFICATE_TYPE, &certType, sizeof(certType)},
    {CKA_TOKEN, &true, sizeof(true)},
    {CKA_LABEL, label, sizeof(label)-1},
    {CKA_SUBJECT, subject, sizeof(subject)},
    {CKA_ID, certId, sizeof(certId)},
    {CKA_VALUE, certValue, sizeof(certValue)}
};

CK_OBJECT_HANDLE hCert;
rv = C_CreateObject(hSession, certTemplate, 7, &hCert);

权限约束

创建对象受到Session权限的约束:

权限矩阵:

Session类型          可创建Token对象   可创建Session对象   可创建私有对象
───────────────────────────────────────────────────────────────────
R/O Public           不可以            可以               不可以
R/W Public           可以              可以               不可以
R/O User Functions   不可以            可以               可以(Session对象)
R/W User Functions   可以              可以               可以
───────────────────────────────────────────────────────────────────

注:私有对象(CKA_PRIVATE=TRUE)需要登录后才能创建

C_DestroyObject:删除对象

函数原型

CK_RV C_DestroyObject(
    CK_SESSION_HANDLE hSession,
    CK_OBJECT_HANDLE hObject
);

删除约束

对象删除权限:

CKA_DESTROYABLE = TRUE  可以删除(默认)
CKA_DESTROYABLE = FALSE 不可以删除

特殊对象不能删除:
- 硬件特征对象(CKO_HW_FEATURE)
- 机制对象(CKO_MECHANISM)
- Profile对象(CKO_PROFILE)
/* 删除对象 */

rv = C_DestroyObject(hSession, hKey);
if (rv == CKR_OK) {
    printf("对象已删除\n");
} else if (rv == CKR_ACTION_PROHIBITED) {
    printf("对象不可删除(CKA_DESTROYABLE=FALSE)\n");
}

C_GetAttributeValue:读取属性

函数原型

CK_RV C_GetAttributeValue(
    CK_SESSION_HANDLE hSession,
    CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate,  // 属性模板(填入值)
    CK_ULONG ulCount
);

两步读取法:先获取大小,再读取值。

/* 两步读取属性值 */

CK_ATTRIBUTE template[2];
CK_BYTE *modulus = NULL;
CK_BYTE *exponent = NULL;

/* 第一步:获取属性大小 */

template[0].type = CKA_MODULUS;
template[0].pValue = NULL_PTR;  // 先不提供缓冲区
template[0].ulValueLen = 0;

template[1].type = CKA_PUBLIC_EXPONENT;
template[1].pValue = NULL_PTR;
template[1].ulValueLen = 0;

rv = C_GetAttributeValue(hSession, hPublicKey, template, 2);

/* 根据返回的大小分配缓冲区 */

modulus = malloc(template[0].ulValueLen);
exponent = malloc(template[1].ulValueLen);

/* 第二步:实际读取属性值 */

template[0].pValue = modulus;
template[1].pValue = exponent;

rv = C_GetAttributeValue(hSession, hPublicKey, template, 2);

特殊返回值处理

C_GetAttributeValue有三个“非错误“的特殊返回值:

特殊返回值:

CKR_OK               所有属性读取成功
CKR_ATTRIBUTE_SENSITIVE  某属性敏感(如CKA_SENSITIVE密钥的CKA_VALUE)
CKR_ATTRIBUTE_TYPE_INVALID 某属性不存在
CKR_BUFFER_TOO_SMALL 某属性缓冲区太小

处理规则:
- 即使返回非CKR_OK,其他可读取的属性仍被正确设置
- 需要检查每个属性的ulValueLen判断读取状态

检查敏感属性

CK_ATTRIBUTE getValue = {CKA_VALUE, NULL_PTR, 0};
rv = C_GetAttributeValue(hSession, hPrivateKey, &getValue, 1);

if (rv == CKR_ATTRIBUTE_SENSITIVE) {
    printf("私钥值不可读取(CKA_SENSITIVE=TRUE)\n");
    // 这是安全行为,不是错误
}

C_SetAttributeValue:修改属性

函数原型

CK_RV C_SetAttributeValue(
    CK_SESSION_HANDLE hSession,
    CK_OBJECT_HANDLE hObject,
    CK_ATTRIBUTE_PTR pTemplate,
    CK_ULONG ulCount
);

可修改的属性有限

常见对象的可修改属性:

所有对象:
- CKA_LABEL(标签)

证书对象:
- CKA_ID(密钥标识符)

密钥对象:
- 修改权限取决于Token实现

不可修改的属性:
- CKA_CLASS(对象类型)
- CKA_KEY_TYPE(密钥类型)
- CKA_TOKEN(是否Token对象)
- CKA_MODIFIABLE(创建后不可修改)
- CKA_UNIQUE_ID(唯一标识符)
/* 修改对象标签 */

CK_UTF8CHAR newLabel[] = "Updated Key Label";
CK_ATTRIBUTE setLabel = {CKA_LABEL, newLabel, sizeof(newLabel)-1};

rv = C_SetAttributeValue(hSession, hKey, &setLabel, 1);

C_CopyObject:复制对象

函数原型

CK_RV C_CopyObject(
    CK_SESSION_HANDLE hSession,
    CK_OBJECT_HANDLE hObject,      // 源对象
    CK_ATTRIBUTE_PTR pTemplate,    // 新对象属性修改
    CK_ULONG ulCount,
    CK_OBJECT_HANDLE_PTR phNewObject  // 新对象句柄
);

复制与修改:复制时可以修改某些属性。

/* 复制密钥,修改CKA_TOKEN属性 */

CK_BBOOL tokenTrue = CK_TRUE;  // 复制为Token对象
CK_ATTRIBUTE copyTemplate[] = {
    {CKA_TOKEN, &tokenTrue, sizeof(tokenTrue)}
};

CK_OBJECT_HANDLE hNewKey;
rv = C_CopyObject(hSession, hSessionKey, copyTemplate, 1, &hNewKey);

/* 复制后的密钥:
   - CKA_TOKEN = TRUE(从FALSE改为TRUE)
   - 其他属性继承源密钥
   - CKA_LOCAL继承源密钥的值
*/

CKA_COPYABLE约束

复制权限:

CKA_COPYABLE = TRUE  可以复制(默认)
CKA_COPYABLE = FALSE 不可以复制

一旦设置为FALSE,不能再改回TRUE

对象查找:C_FindObjects系列

对象查找是一个三步操作:

对象查找流程:

┌─────────────────────────────────────┐
│ C_FindObjectsInit(template)         │ 初始化搜索
│                                     │ 指定搜索条件
└─────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ C_FindObjects(phObject, maxCount)   │ 获取匹配对象
│                                     │ 可多次调用
└─────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ C_FindObjectsFinal()                │ 结束搜索
│                                     │ 释放搜索状态
└─────────────────────────────────────┘

查找所有对象

/* 查找所有对象 */

CK_OBJECT_HANDLE hObjects[100];
CK_ULONG ulObjectCount;

/* 初始化搜索(空模板=查找所有) */

rv = C_FindObjectsInit(hSession, NULL_PTR, 0);

/* 获取第一批对象 */

rv = C_FindObjects(hSession, hObjects, 100, &ulObjectCount);
printf("找到 %lu 个对象\n", ulObjectCount);

/* 继续获取(如果有更多) */

while (ulObjectCount > 0) {
    rv = C_FindObjects(hSession, hObjects, 100, &ulObjectCount);
    if (ulObjectCount == 0) break;
    // 处理这批对象...
}

/* 结束搜索 */

rv = C_FindObjectsFinal(hSession);

按属性查找对象

/* 查找指定ID的RSA私钥 */

CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
CK_KEY_TYPE keyType = CKK_RSA;
CK_BYTE keyId[] = {0x01};

CK_ATTRIBUTE searchTemplate[] = {
    {CKA_CLASS, &keyClass, sizeof(keyClass)},
    {CKA_KEY_TYPE, &keyType, sizeof(keyType)},
    {CKA_ID, keyId, sizeof(keyId)}
};

rv = C_FindObjectsInit(hSession, searchTemplate, 3);

CK_OBJECT_HANDLE hPrivateKey;
CK_ULONG count;
rv = C_FindObjects(hSession, &hPrivateKey, 1, &count);

if (count == 1) {
    printf("找到私钥,句柄=%lu\n", hPrivateKey);
}

rv = C_FindObjectsFinal(hSession);

按CKA_UNIQUE_ID查找

CKA_UNIQUE_ID是唯一标识符,查找时最多返回一个对象:

/* 查找唯一标识符的对象 */

CK_BYTE uniqueId[] = {...};  // CKA_UNIQUE_ID值
CK_ATTRIBUTE searchTemplate[] = {
    {CKA_UNIQUE_ID, uniqueId, sizeof(uniqueId)}
};

rv = C_FindObjectsInit(hSession, searchTemplate, 1);

CK_OBJECT_HANDLE hObject;
CK_ULONG count;
rv = C_FindObjects(hSession, &hObject, 1, &count);

// count最多为1(因为CKA_UNIQUE_ID唯一)

rv = C_FindObjectsFinal(hSession);

搜索与登录状态

搜索结果取决于Session的登录状态:

搜索可见性:

未登录(Public Session):
- 只能看到公开对象(CKA_PRIVATE = FALSE)
- 私有对象不可见

已登录(User Functions):
- 可以看到所有对象(包括私有对象)

搜索私有对象但未登录:
- 搜索模板指定CKA_PRIVATE = TRUE
- 返回结果为空(不会报错)
/* 未登录时搜索私钥 */

CK_OBJECT_CLASS privKeyClass = CKO_PRIVATE_KEY;
CK_ATTRIBUTE template[] = {
    {CKA_CLASS, &privKeyClass, sizeof(privKeyClass)}
};

rv = C_FindObjectsInit(hSession, template, 1);

CK_OBJECT_HANDLE hObjects[10];
CK_ULONG count;
rv = C_FindObjects(hSession, hObjects, 10, &count);

// 未登录时,count = 0(即使私钥存在)

rv = C_FindObjectsFinal(hSession);

/* 登录后搜索 */

rv = C_Login(hSession, CKU_USER, userPin, pinLen);

rv = C_FindObjectsInit(hSession, template, 1);
rv = C_FindObjects(hSession, hObjects, 10, &count);
// 登录后,count > 0(私钥可见)

C_GetObjectSize:获取对象大小

函数原型

CK_RV C_GetObjectSize(
    CK_SESSION_HANDLE hSession,
    CK_OBJECT_HANDLE hObject,
    CK_ULONG_PTR pulSize
);

返回对象在Token上占用的存储空间大小(字节)。

CK_ULONG objectSize;
rv = C_GetObjectSize(hSession, hKey, &objectSize);
printf("对象占用 %lu 字节\n", objectSize);

完整对象管理示例

/* 完整的对象管理流程 */

CK_SESSION_HANDLE hSession;
CK_OBJECT_HANDLE hKey;

/* 1. 创建密钥对象 */

CK_ATTRIBUTE keyTemplate[] = {...};
rv = C_CreateObject(hSession, keyTemplate, 7, &hKey);

/* 2. 读取密钥属性 */

CK_ATTRIBUTE getId = {CKA_ID, NULL_PTR, 0};
rv = C_GetAttributeValue(hSession, hKey, &getId, 1);
CK_BYTE *id = malloc(getId.ulValueLen);
getId.pValue = id;
rv = C_GetAttributeValue(hSession, hKey, &getId, 1);

/* 3. 查找密钥 */

CK_ATTRIBUTE searchTemplate[] = {{CKA_ID, id, getId.ulValueLen}};
rv = C_FindObjectsInit(hSession, searchTemplate, 1);
rv = C_FindObjects(hSession, &hKey, 1, &count);
rv = C_FindObjectsFinal(hSession);

/* 4. 修改密钥标签 */

CK_UTF8CHAR newLabel[] = "Production Key";
CK_ATTRIBUTE setLabel = {CKA_LABEL, newLabel, sizeof(newLabel)-1};
rv = C_SetAttributeValue(hSession, hKey, &setLabel, 1);

/* 5. 删除密钥 */

rv = C_DestroyObject(hSession, hKey);

小结:Object管理函数的设计智慧

Object管理要点回顾:

1. 创建对象需要精确模板
   - 每个属性类型和值必须正确
   - 不支持的属性会失败
   - Token自动分配CKA_UNIQUE_ID

2. 属性读取的"两步法"
   - 先获取大小(pValue=NULL)
   - 再分配缓冲区读取
   - 处理敏感属性的不可读

3. 查找是精确匹配
   - 模板属性全部匹配才算找到
   - 空模板=查找所有可见对象
   - 私有对象需要登录后可见

4. 权限层层约束
   - Session类型决定可创建/删除的对象类型
   - CKA_DESTROYABLE/CKA_COPYABLE控制操作权限
   - CKA_MODIFIABLE控制属性修改权限

5. 对象生命周期管理
   - Token对象跨Session持久化
   - Session对象随Session关闭销毁
   - 私有对象随登出失效

Object管理函数是PKCS#11的“仓储系统“,让应用程序能够管理密钥、证书等安全资产。结合密钥生成函数(C_GenerateKeyPair),构成了完整的密钥生命周期管理能力。 下一节,我们将学习加密解密函数——看数据如何被保护。

【下集预告】

C_Encrypt怎么加密?C_Decrypt怎么解密?

单步操作和多步操作有什么区别?

大文件怎么加密?

下一节,加密解密函数。