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怎么解密?
单步操作和多步操作有什么区别?
大文件怎么加密?
下一节,加密解密函数。