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.9 Token存储实现:PIN与元数据的安全存储

Token存储的职责

Token存储负责管理Token的核心元数据:

Token元数据内容:

Token元数据
│
├── 基本信息配置
│   ├── Label(Token名称)
│   ├── Serial(序列号)
│   └── Flags(Token状态标志)
│
├── 安全凭证存储
│   ├── SO PIN(安全管理员PIN)
│   └── User PIN(用户PIN)
│
└── 对象管理接口
    ├── 创建Object
    ├── 删除Object
    ├── 获取Object列表
    └── 清空Token

ObjectStoreToken:抽象基类

ObjectStoreToken定义了Token存储的统一接口:

class ObjectStoreToken
{
public:
    static bool selectBackend(const std::string& backend);
    
    static ObjectStoreToken* createToken(const std::string basePath, 
                                          const std::string tokenDir, 
                                          int umask, 
                                          const ByteString& label, 
                                          const ByteString& serial);
    
    static ObjectStoreToken* accessToken(const std::string &basePath, 
                                          const std::string &tokenDir, 
                                          int umask);
    
    virtual bool setSOPIN(const ByteString& soPINBlob) = 0;
    virtual bool getSOPIN(ByteString& soPINBlob) = 0;
    
    virtual bool setUserPIN(ByteString userPINBlob) = 0;
    virtual bool getUserPIN(ByteString& userPINBlob) = 0;
    
    virtual bool getTokenFlags(CK_ULONG& flags) = 0;
    virtual bool setTokenFlags(const CK_ULONG flags) = 0;
    
    virtual bool getTokenLabel(ByteString& label) = 0;
    virtual bool getTokenSerial(ByteString& serial) = 0;
    
    virtual std::set<OSObject*> getObjects() = 0;
    virtual OSObject* createObject() = 0;
    virtual bool deleteObject(OSObject* object) = 0;
    
    virtual bool isValid() = 0;
    virtual void invalidate() = 0;
    virtual bool clearToken() = 0;
    virtual bool resetToken(const ByteString& label) = 0;
    
    virtual ~ObjectStoreToken() {};
};

两种实现

实现存储方式适用场景
OSToken文件系统默认,简单易用
DBTokenSQLite数据库可选,性能更好

后端选择机制

SoftHSM2支持两种存储后端:

static std::string backendType;

bool ObjectStoreToken::selectBackend(const std::string& backend)
{
    backendType = backend;
    return true;
}

ObjectStoreToken* ObjectStoreToken::createToken(...)
{
    if (backendType == "db") {
        return DBToken::createToken(basePath, tokenDir, umask, label, serial);
    } else {
        return OSToken::createToken(basePath, tokenDir, umask, label, serial);
    }
}

配置方式

softhsm2.conf中配置:

# softhsm2.conf 示例

directories.tokendir = /var/lib/softhsm2/tokens
objectstore.backend = db    # 使用数据库后端
# objectstore.backend = file  # 使用文件后端(默认)

OSToken:文件存储实现

OSToken使用文件系统存储Token元数据:

Token目录结构:

/var/lib/softhsm2/tokens/
│
├── 1234567890abcdef/          ← Token目录
│   │
│   ├── token.object           ← Token元数据文件
│   │   ├── CKA_LABEL
│   │   ├── CKA_SERIAL_NUMBER
│   │   ├── CKA_TOKEN_FLAGS
│   │   ├── SO_PIN_BLOB
│   │   └── USER_PIN_BLOB
│   │
│   ├── generation             ← 版本文件
│   │
│   ├── xxxxxxxx.object        ← Object文件
│   ├── ...
│   │
│   └── xxxxxxxx.lock          ← Object锁文件

token.object文件

Token元数据存储在一个特殊的ObjectFile中:

class OSToken : public ObjectStoreToken
{
private:
    ObjectFile* tokenObject;  // Token元数据存储在这里
};

PIN存储格式

PIN不是明文存储,而是以“Blob“形式存储:

bool OSToken::setSOPIN(const ByteString& soPINBlob)
{
    // soPINBlob是加密后的PIN数据
    return tokenObject->setAttribute(CKA_SO_PIN, OSAttribute(soPINBlob));
}

bool OSToken::getSOPIN(ByteString& soPINBlob)
{
    OSAttribute attr = tokenObject->getAttribute(CKA_SO_PIN);
    soPINBlob = attr.getByteStringValue();
    return true;
}

PIN Blob的结构

PIN Blob包含加密后的PIN和验证信息:

PIN Blob结构:

┌─────────────────────────────────────────────────────────────┐
│                      PIN Blob                               │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  加密的PIN数据                                        │   │
│  │                                                      │   │
│  │  ├── 加密算法标识                                    │   │
│  │  ├── 加密密钥索引                                    │   │
│  │  ├── 加密后的PIN值                                   │   │
│  │  └── IV/Nonce                                        │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  验证数据                                             │   │
│  │                                                      │   │
│  │  ├── Salt                                            │   │
│  │  ├── Hash算法                                        │   │
│  │  └── Hash值(用于快速验证PIN)                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

PIN验证流程

PIN验证流程:

应用程序输入PIN
    │
    │ C_Login(hSession, CKU_USER, pin, pinLen)
    ▼
SoftHSM2接收PIN
    │
    │ 1. 计算PIN的Hash(使用Blob中的Salt)
    ▼
比较Hash值
    │
    │ 如果匹配:
    │ ├── 解密PIN Blob
    │ ├── 获得实际PIN值
    │ └── 使用PIN进行后续操作
    │
    │ 如果不匹配:
    │ └── 返回CKR_PIN_INCORRECT
    ▼
验证完成

Token Flags管理

Token Flags记录Token的状态:

bool OSToken::getTokenFlags(CK_ULONG& flags)
{
    OSAttribute attr = tokenObject->getAttribute(CKA_TOKEN_FLAGS);
    flags = attr.getUnsignedLongValue();
    return true;
}

bool OSToken::setTokenFlags(const CK_ULONG flags)
{
    return tokenObject->setAttribute(CKA_TOKEN_FLAGS, OSAttribute(flags));
}

Token Flags定义

Token Flags定义:

CKF_RNG                        有随机数生成器
CKF_WRITE_PROTECTED            写保护
CKF_LOGIN_REQUIRED             需要登录
CKF_USER_PIN_INITIALIZED       用户PIN已初始化
CKF_RESTORE_KEY_NOT_NEEDED     不需要恢复密钥
CKF_CLOCK_ON_TOKEN             有时钟
CKF_PROTECTED_AUTHENTICATION_PATH  保护认证路径
CKF_DUAL_CRYPTO_OPERATIONS     双重密码操作
CKF_TOKEN_INITIALIZED          Token已初始化
CKF_SECONDARY_AUTHENTICATION   二级认证
CKF_USER_PIN_COUNT_LOW         用户PIN计数低
CKF_USER_PIN_FINAL_TRY         用户PIN最后一次尝试
CKF_USER_PIN_LOCKED            用户PIN锁定
CKF_SO_PIN_COUNT_LOW           SO PIN计数低
CKF_SO_PIN_FINAL_TRY           SO PIN最后一次尝试
CKF_SO_PIN_LOCKED              SO PIN锁定
CKF_SO_PIN_TO_BE_CHANGED       SO PIN需要更改
CKF_USER_PIN_TO_BE_CHANGED     用户PIN需要更改

Token初始化流程

Token初始化创建Token目录和元数据:

OSToken* OSToken::createToken(const std::string basePath, 
                               const std::string tokenDir, 
                               int umask, 
                               const ByteString& label, 
                               const ByteString& serial)
{
    std::string tokenPath = basePath + "/" + tokenDir;
    
    // 1. 创建Token目录
    Directory dir(tokenPath);
    if (!dir.createDirectory()) {
        return NULL;
    }
    
    // 2. 创建Generation文件
    Generation gen(tokenPath + "/generation");
    if (!gen.create()) {
        return NULL;
    }
    
    // 3. 创建Token Object文件
    ObjectFile* tokenObject = new ObjectFile(NULL, 
                                              tokenPath + "/token.object", 
                                              umask, 
                                              tokenPath + "/token.lock", 
                                              true);
    
    // 4. 设置Token属性
    tokenObject->startTransaction();
    
    tokenObject->setAttribute(CKA_LABEL, OSAttribute(label));
    tokenObject->setAttribute(CKA_SERIAL_NUMBER, OSAttribute(serial));
    tokenObject->setAttribute(CKA_TOKEN_FLAGS, OSAttribute(0));
    
    tokenObject->commitTransaction();
    
    // 5. 创建OSToken实例
    OSToken* token = new OSToken(tokenPath, label, umask, serial);
    token->tokenObject = tokenObject;
    
    return token;
}

Token对象索引

OSToken需要枚举Token目录中的所有Object:

bool OSToken::index(bool isFirstTime)
{
    MutexLocker lock(tokenMutex);
    
    // 1. 获取目录中的文件列表
    Directory dir(tokenPath);
    std::list<std::string> files = dir.getFiles();
    
    // 2. 更新当前文件集合
    std::set<std::string> newFiles;
    for (const std::string& file : files) {
        if (file.find(".object") != std::string::npos) {
            newFiles.insert(file);
        }
    }
    
    // 3. 检查新增文件
    for (const std::string& file : newFiles) {
        if (currentFiles.find(file) == currentFiles.end()) {
            // 新文件,创建ObjectFile
            ObjectFile* obj = new ObjectFile(this, 
                                              tokenPath + "/" + file, 
                                              umask, 
                                              tokenPath + "/" + file + ".lock");
            if (obj->isValid()) {
                objects.insert(obj);
                allObjects.insert(obj);
            }
        }
    }
    
    // 4. 检查删除文件
    for (const std::string& file : currentFiles) {
        if (newFiles.find(file) == newFiles.end()) {
            // 文件被删除,使Object无效
            for (auto it = objects.begin(); it != objects.end(); ) {
                ObjectFile* obj = (ObjectFile*)*it;
                if (obj->getFilename() == file) {
                    obj->invalidate();
                    it = objects.erase(it);
                } else {
                    ++it;
                }
            }
        }
    }
    
    // 5. 更新当前文件集合
    currentFiles = newFiles;
    
    return true;
}

DBToken:数据库存储实现

DBToken使用SQLite数据库存储Token:

class DBToken : public ObjectStoreToken
{
public:
    DBToken(const std::string &baseDir, const std::string &tokenName, 
            int umask, const ByteString& label, const ByteString& serial);
    
    DBToken(const std::string &baseDir, const std::string &tokenName, int umask);
    
    static DBToken* createToken(...);
    static DBToken* accessToken(...);
    
    virtual bool setSOPIN(const ByteString& soPINBlob);
    virtual bool getSOPIN(ByteString& soPINBlob);
    virtual bool setUserPIN(ByteString userPINBlob);
    virtual bool getUserPIN(ByteString& userPINBlob);
    
    virtual std::set<OSObject*> getObjects();
    virtual OSObject* createObject();
    virtual bool deleteObject(OSObject* object);
    
    virtual bool isValid();
    virtual bool clearToken();

private:
    DB::Connection *_connection;
    std::map<long long, OSObject*> _allObjects;
    Mutex* _tokenMutex;
};

数据库结构

SQLite数据库表结构:

┌─────────────────────────────────────────────────────────────┐
│                    SQLite Database                          │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  token表                                             │   │
│  │  ├── label                                          │   │
│  │  ├── serial                                         │   │
│  │  ├── flags                                          │   │
│  │  ├── so_pin                                         │   │
│  │  ├── user_pin                                       │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  objects表                                           │   │
│  │  ├── id (主键)                                       │   │
│  │  ├── uuid                                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  attributes_bool表                                   │   │
│  │  ├── object_id                                       │   │
│  │  ├── type                                            │   │
│  │  ├── value                                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  attributes_ulong表                                  │   │
│  │  ├── object_id                                       │   │
│  │  ├── type                                            │   │
│  │  ├── value                                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  attributes_bytes表                                  │   │
│  │  ├── object_id                                       │   │
│  │  ├── type                                            │   │
│  │  ├── value                                           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

DBObject:数据库Object实现

DBObject将属性存储在数据库表中:

class DBObject : public OSObject
{
public:
    DBObject(DB::Connection* connection, long long id);
    
    virtual bool attributeExists(CK_ATTRIBUTE_TYPE type);
    virtual OSAttribute getAttribute(CK_ATTRIBUTE_TYPE type);
    virtual bool setAttribute(CK_ATTRIBUTE_TYPE type, const OSAttribute& attribute);
    virtual bool deleteAttribute(CK_ATTRIBUTE_TYPE type);
    
    virtual bool isValid();
    virtual bool destroyObject();

private:
    DB::Connection* connection;
    long long objectId;
    bool valid;
    Mutex* objectMutex;
};

属性读写

bool DBObject::setAttribute(CK_ATTRIBUTE_TYPE type, const OSAttribute& attribute)
{
    // 根据属性类型选择表
    std::string tableName;
    if (attribute.isBooleanAttribute()) {
        tableName = "attributes_bool";
    } else if (attribute.isUnsignedLongAttribute()) {
        tableName = "attributes_ulong";
    } else {
        tableName = "attributes_bytes";
    }
    
    // 执行SQL
    std::string sql = "INSERT OR REPLACE INTO " + tableName + 
                      " (object_id, type, value) VALUES (?, ?, ?)";
    
    // ...
    return true;
}

OSAttribute DBObject::getAttribute(CK_ATTRIBUTE_TYPE type)
{
    // 尝试从各个表获取
    // 先尝试bool表
    // ...
    
    // 再尝试ulong表
    // ...
    
    // 最后尝试bytes表
    // ...
    
    return OSAttribute();
}

文件存储 vs 数据库存储

两种存储方式的对比:

文件存储 vs 数据库存储对比:

特性                文件存储(OSToken)       数据库存储(DBToken)
─────────────────────────────────────────────────────────────────
实现复杂度           简单                      复杂
性能                 较低(多文件)            较高(单数据库)
并发性能             较差(文件锁)            较好(事务)
数据完整性           较低                      较高
备份恢复             简单(复制目录)          简单(复制数据库)
跨平台               很好                      需SQLite支持
适用场景             小规模、简单场景          大规模、高性能场景
─────────────────────────────────────────────────────────────────

Token销毁流程

Token销毁清空Token内容:

bool OSToken::clearToken()
{
    MutexLocker lock(tokenMutex);
    
    // 1. 使所有Object无效
    for (OSObject* obj : objects) {
        obj->destroyObject();
    }
    objects.clear();
    
    // 2. 删除Token目录中的所有文件
    Directory dir(tokenPath);
    std::list<std::string> files = dir.getFiles();
    for (const std::string& file : files) {
        File::remove(tokenPath + "/" + file);
    }
    
    // 3. 删除Generation文件
    if (gen != NULL) {
        gen->reset();
    }
    
    // 4. 使Token无效
    valid = false;
    
    return true;
}

bool OSToken::resetToken(const ByteString& label)
{
    MutexLocker lock(tokenMutex);
    
    // 1. 清空Token
    clearToken();
    
    // 2. 重新创建Token Object
    tokenObject = new ObjectFile(NULL, 
                                  tokenPath + "/token.object", 
                                  umask, 
                                  tokenPath + "/token.lock", 
                                  true);
    
    // 3. 设置新属性
    tokenObject->startTransaction();
    tokenObject->setAttribute(CKA_LABEL, OSAttribute(label));
    tokenObject->setAttribute(CKA_TOKEN_FLAGS, OSAttribute(0));
    tokenObject->commitTransaction();
    
    // 4. 使Token有效
    valid = true;
    
    return true;
}

一个类比:保险箱库的管理

保险箱库管理类比:

保险箱库(Token)
│
├── 库记录簿(token.object文件)
│   ├── 库名称(Label)
│   ├── 库编号(Serial)
│   ├── 库状态(Flags)
│   │   ├── 是否开放
│   │   ├── 是否锁定
│   │   ├── 是否需要管理员在场
│   │
│   ├── 管理员密码(SO PIN Blob)
│   │   ├── 加密存储
│   │   ├── 带验证Hash
│   │
│   └── 客户密码(User PIN Blob)
│       ├── 加密存储
│       ├── 带验证Hash
│
├── 保险箱存储方式
│   │
│   ├── 文件存储方式(OSToken)
│   │   ├── 每个保险箱一个文件柜
│   │   ├── 内容直接写入文件
│   │   ├── 简单但效率较低
│   │
│   └── 数据库存储方式(DBToken)
│       ├── 所有保险箱记录在一个大账本
│       ├── 按类型分页存储属性
│       ├── 复杂但效率较高
│
└── 库管理操作
    ├── 创建库(createToken)
    ├── 清空库(clearToken)
    ├── 重置库(resetToken)
    └── 销毁库(destroyToken)

本篇小结

今天我们分析了Token存储实现。

ObjectStoreToken接口

  • 定义Token存储的统一接口
  • 支持PIN、Flags、Label等元数据管理
  • 支持Object创建/删除

两种实现

  • OSToken:文件存储,每个Object一个文件
  • DBToken:数据库存储,所有属性在表中

PIN存储

  • 不是明文存储
  • 以Blob形式加密存储
  • 包含Hash用于快速验证

Token Flags

  • 记录Token状态(初始化、锁定等)
  • 记录PIN状态(低计数、锁定等)

后端选择

  • 配置文件指定backend类型
  • file(默认)或db

下一节,我们将分析密码模块架构——CryptoFactory如何抽象密码后端。

【下集预告】

SoftHSM2如何实现密码算法?

CryptoFactory是什么?

Botan和OpenSSL如何被集成?

AES、RSA、SHA如何实现?

下一节,CryptoFactory架构。