好的,我们继续学习 Linux 系统编程中的重要函数。这次我们介绍 add_key 函数,它是 Linux 密钥保留服务(Key Retention Service)的一部分,用于向内核密钥管理系统添加新密钥。


1. 函数介绍 見出しへのリンク

add_key 是一个 Linux 系统调用,属于密钥保留服务(Key Retention Service)的一部分。它用于向内核的密钥管理系统中添加一个新的密钥

Linux 密钥保留服务提供了一个框架来管理各种类型的密钥和凭证,包括:

  • 用户密钥(user keys)
  • 登录密钥(login keys)
  • 密钥环密钥(keyring keys)
  • PKCS#7 消息密钥
  • 大量密钥(big keys)
  • 可信密钥(trusted keys)
  • 加密密钥(encrypted keys)

这个服务的主要用途包括:

  • 文件系统加密:管理加密文件系统的密钥
  • 网络认证:存储 Kerberos 票据等认证信息
  • 内核模块:为内核模块提供密钥管理
  • 安全通信:管理 TLS/SSL 会话密钥
  • 用户空间应用:为应用程序提供安全的密钥存储

add_key 函数允许用户空间程序将密钥安全地存储在内核中,这些密钥可以被其他有权限的进程访问,并且在系统重启后会丢失(除非使用持久化密钥环)。


2. 函数原型 見出しへのリンク

#include <sys/types.h>
#include <keyutils.h>

key_serial_t add_key(const char *type, const char *description,
                     const void *payload, size_t plen,
                     key_serial_t keyring);

3. 功能 見出しへのリンク

  • 创建新密钥: 在内核密钥管理系统中创建一个指定类型和描述的新密钥。
  • 设置密钥数据: 将提供的有效载荷(payload)数据与新密钥关联。
  • 链接到密钥环: 将新创建的密钥链接到指定的密钥环中。
  • 返回密钥序列号: 成功时返回新密钥的唯一序列号。

4. 参数 見出しへのリンク

  • const char *type: 密钥的类型字符串,指定要创建的密钥类型。常见类型包括:
    • "user": 用户定义的密钥
    • "login": 登录相关的密钥
    • "keyring": 密钥环
    • "big_key": 大量密钥(存储在 tmpfs 或加密的环回文件系统中)
    • "trusted": 可信平台模块(TPM)保护的密钥
    • "encrypted": 加密密钥
  • const char *description: 密钥的描述字符串,用于标识密钥。在同一密钥环中,描述必须唯一。
  • const void *payload: 指向密钥数据的指针。对于某些密钥类型,这可能是指向构造参数的指针。
    • 可以为 NULL,表示创建一个空密钥。
  • size_t plen: 有效载荷数据的长度(字节)。
    • 如果 payloadNULL,此值应为 0。
  • key_serial_t keyring: 目标密钥环的序列号,新密钥将被链接到这个密钥环中。
    • 可以使用特殊值:
      • KEY_SPEC_THREAD_KEYRING: 当前线程的密钥环
      • KEY_SPEC_PROCESS_KEYRING: 当前进程的密钥环
      • KEY_SPEC_SESSION_KEYRING: 当前会话的密钥环
      • KEY_SPEC_USER_KEYRING: 当前用户的密钥环
      • KEY_SPEC_USER_SESSION_KEYRING: 当前用户的会话密钥环

5. 返回值 見出しへのリンク

  • 成功时: 返回新创建密钥的序列号(正整数)。
  • 失败时: 返回 -1,并设置全局变量 errno 来指示具体的错误原因:
    • EACCES: 调用进程没有权限在指定密钥环中创建密钥。
    • EDQUOT: 密钥配额已满。
    • EEXIST: 具有相同描述的密钥已在目标密钥环中存在。
    • EINVAL: 参数无效(如类型字符串不支持)。
    • ENOKEY: 指定的密钥环不存在。
    • ENOMEM: 内存不足。
    • EPERM: 操作不被允许(如权限不足)。
    • ERANGE: 有效载荷大小超出限制。

6. 相似函数,或关联函数 見出しへのリンク

  • keyctl(int cmd, ...): 通用的密钥控制函数,用于执行各种密钥管理操作。
  • request_key(const char *type, const char *description, const char *callout_info, key_serial_t keyring): 请求一个已存在的密钥或通过调用用户空间程序创建密钥。
  • keyctl_add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t keyring): add_key 的别名。
  • keyctl_read(key_serial_t key, char *buffer, size_t buflen): 读取密钥的有效载荷。
  • keyctl_describe(key_serial_t key, char *buffer, size_t buflen): 获取密钥的描述信息。

7. 示例代码 見出しへのリンク

示例 1:基本的用户密钥创建 見出しへのリンク

#define _GNU_SOURCE
#include <sys/types.h>
#include <keyutils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

int main() {
    key_serial_t key1, key2;
    const char *secret_data = "This is my secret key data";
    char read_buffer[256];
    long read_result;
    
    printf("=== Add_key 基本使用演示 ===\n");
    printf("当前进程 UID: %d, GID: %d\n\n", getuid(), getgid());
    
    // 1. 创建一个简单的用户密钥
    printf("1. 创建用户密钥\n");
    key1 = add_key("user", "my_first_key", secret_data, strlen(secret_data), 
                   KEY_SPEC_SESSION_KEYRING);
    if (key1 == -1) {
        perror("创建用户密钥失败");
        printf("注意: 需要适当的权限,某些系统可能需要 root 权限\n");
        exit(EXIT_FAILURE);
    }
    printf("   成功创建密钥,序列号: %d\n", key1);
    
    // 2. 创建另一个用户密钥
    const char *another_secret = "Another secret value";
    key2 = add_key("user", "my_second_key", another_secret, strlen(another_secret),
                   KEY_SPEC_SESSION_KEYRING);
    if (key2 == -1) {
        perror("创建第二个用户密钥失败");
    } else {
        printf("   成功创建第二个密钥,序列号: %d\n", key2);
    }
    
    // 3. 尝试读取密钥数据(需要使用 keyctl)
    printf("\n3. 读取密钥数据\n");
    read_result = keyctl(KEYCTL_READ, key1, (unsigned long)read_buffer, sizeof(read_buffer), 0);
    if (read_result == -1) {
        perror("读取密钥数据失败");
    } else {
        read_buffer[read_result] = '\0';
        printf("   从密钥 %d 读取到数据: %s\n", key1, read_buffer);
    }
    
    // 4. 获取密钥描述信息
    printf("\n4. 获取密钥描述信息\n");
    read_result = keyctl(KEYCTL_DESCRIBE, key1, (unsigned long)read_buffer, sizeof(read_buffer), 0);
    if (read_result == -1) {
        perror("获取密钥描述失败");
    } else {
        read_buffer[read_result] = '\0';
        printf("   密钥 %d 的描述信息: %s", key1, read_buffer);
    }
    
    // 5. 创建空密钥
    printf("\n5. 创建空密钥\n");
    key_serial_t empty_key = add_key("user", "empty_key", NULL, 0, KEY_SPEC_SESSION_KEYRING);
    if (empty_key == -1) {
        perror("创建空密钥失败");
    } else {
        printf("   成功创建空密钥,序列号: %d\n", empty_key);
    }
    
    // 6. 演示重复密钥创建(应该失败)
    printf("\n6. 演示重复密钥创建\n");
    key_serial_t duplicate_key = add_key("user", "my_first_key", "duplicate data", 15,
                                        KEY_SPEC_SESSION_KEYRING);
    if (duplicate_key == -1) {
        if (errno == EEXIST) {
            printf("   创建重复密钥失败(预期): %s\n", strerror(errno));
            printf("   说明: 同一密钥环中不能有相同描述的密钥\n");
        } else {
            perror("   创建重复密钥失败(其他错误)");
        }
    }
    
    // 7. 查看当前会话密钥环中的密钥
    printf("\n7. 查看会话密钥环信息\n");
    key_serial_t session_keyring = keyctl(KEYCTL_GET_KEYRING_ID, KEY_SPEC_SESSION_KEYRING, 1);
    if (session_keyring != -1) {
        printf("   当前会话密钥环 ID: %d\n", session_keyring);
        
        read_result = keyctl(KEYCTL_DESCRIBE, session_keyring, 
                           (unsigned long)read_buffer, sizeof(read_buffer), 0);
        if (read_result != -1) {
            read_buffer[read_result] = '\0';
            printf("   会话密钥环描述: %s", read_buffer);
        }
    }
    
    // 8. 清理密钥(通过撤销)
    printf("\n8. 清理密钥\n");
    if (keyctl(KEYCTL_REVOKE, key1, 0, 0, 0) == -1) {
        perror("撤销密钥失败");
    } else {
        printf("   成功撤销密钥 %d\n", key1);
    }
    
    if (keyctl(KEYCTL_REVOKE, key2, 0, 0, 0) == -1) {
        perror("撤销第二个密钥失败");
    } else {
        printf("   成功撤销密钥 %d\n", key2);
    }
    
    return 0;
}

示例 2:不同密钥类型的演示 見出しへのリンク

#define _GNU_SOURCE
#include <sys/types.h>
#include <keyutils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void demonstrate_key_types() {
    printf("=== 不同密钥类型演示 ===\n");
    
    // 1. 用户密钥(最常见的类型)
    printf("1. 用户密钥 (user)\n");
    key_serial_t user_key = add_key("user", "demo_user_key", "user data", 9,
                                   KEY_SPEC_SESSION_KEYRING);
    if (user_key != -1) {
        printf("   成功创建用户密钥: %d\n", user_key);
        keyctl(KEYCTL_REVOKE, user_key, 0, 0, 0);
    } else {
        printf("   创建失败: %s\n", strerror(errno));
    }
    
    // 2. 登录密钥
    printf("\n2. 登录密钥 (login)\n");
    key_serial_t login_key = add_key("login", "demo_login_key", "login data", 10,
                                    KEY_SPEC_SESSION_KEYRING);
    if (login_key != -1) {
        printf("   成功创建登录密钥: %d\n", login_key);
        keyctl(KEYCTL_REVOKE, login_key, 0, 0, 0);
    } else {
        printf("   创建失败: %s\n", strerror(errno));
        printf("   说明: 登录密钥可能需要特殊权限\n");
    }
    
    // 3. 密钥环
    printf("\n3. 密钥环 (keyring)\n");
    key_serial_t keyring = add_key("keyring", "demo_keyring", NULL, 0,
                                  KEY_SPEC_SESSION_KEYRING);
    if (keyring != -1) {
        printf("   成功创建密钥环: %d\n", keyring);
        
        // 在新密钥环中创建密钥
        key_serial_t key_in_ring = add_key("user", "key_in_new_ring", "data", 4, keyring);
        if (key_in_ring != -1) {
            printf("   在新密钥环中创建密钥: %d\n", key_in_ring);
            keyctl(KEYCTL_REVOKE, key_in_ring, 0, 0, 0);
        }
        
        keyctl(KEYCTL_REVOKE, keyring, 0, 0, 0);
    } else {
        printf("   创建密钥环失败: %s\n", strerror(errno));
    }
    
    // 4. 大量密钥
    printf("\n4. 大量密钥 (big_key)\n");
    // 创建较大的数据
    char *big_data = malloc(32768); // 32KB
    if (big_data) {
        memset(big_data, 'A', 32768);
        key_serial_t big_key = add_key("big_key", "demo_big_key", big_data, 32768,
                                      KEY_SPEC_SESSION_KEYRING);
        if (big_key != -1) {
            printf("   成功创建大量密钥: %d\n", big_key);
            keyctl(KEYCTL_REVOKE, big_key, 0, 0, 0);
        } else {
            printf("   创建大量密钥失败: %s\n", strerror(errno));
        }
        free(big_data);
    }
}

void demonstrate_keyring_targets() {
    printf("\n=== 不同密钥环目标演示 ===\n");
    
    const char *key_data = "test data";
    
    // 测试各种密钥环目标
    struct {
        key_serial_t keyring;
        const char *name;
    } keyrings[] = {
        {KEY_SPEC_THREAD_KEYRING, "线程密钥环"},
        {KEY_SPEC_PROCESS_KEYRING, "进程密钥环"},
        {KEY_SPEC_SESSION_KEYRING, "会话密钥环"},
        {KEY_SPEC_USER_KEYRING, "用户密钥环"},
        {KEY_SPEC_USER_SESSION_KEYRING, "用户会话密钥环"}
    };
    
    for (int i = 0; i < 5; i++) {
        char desc[50];
        snprintf(desc, sizeof(desc), "key_for_%s", keyrings[i].name);
        
        key_serial_t key = add_key("user", desc, key_data, strlen(key_data), 
                                  keyrings[i].keyring);
        if (key != -1) {
            printf("   在%s中创建密钥成功: %d\n", keyrings[i].name, key);
            keyctl(KEYCTL_REVOKE, key, 0, 0, 0);
        } else {
            printf("   在%s中创建密钥失败: %s\n", keyrings[i].name, strerror(errno));
        }
    }
}

int main() {
    printf("Add_key 函数不同密钥类型演示\n\n");
    
    demonstrate_key_types();
    demonstrate_keyring_targets();
    
    printf("\n=== 密钥类型总结 ===\n");
    printf("user:      用户定义的通用密钥\n");
    printf("login:     登录相关的认证密钥\n");
    printf("keyring:   密钥环,用于组织其他密钥\n");
    printf("big_key:   大量密钥,存储在特殊文件系统中\n");
    printf("trusted:   TPM 保护的可信密钥\n");
    printf("encrypted: 加密密钥\n\n");
    
    printf("=== 密钥环目标总结 ===\n");
    printf("KEY_SPEC_THREAD_KEYRING:        当前线程\n");
    printf("KEY_SPEC_PROCESS_KEYRING:       当前进程\n");
    printf("KEY_SPEC_SESSION_KEYRING:       当前会话\n");
    printf("KEY_SPEC_USER_KEYRING:          当前用户\n");
    printf("KEY_SPEC_USER_SESSION_KEYRING:  当前用户会话\n");
    
    return 0;
}

示例 3:错误处理和安全考虑 見出しへのリンク

#define _GNU_SOURCE
#include <sys/types.h>
#include <keyutils.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

void demonstrate_error_handling() {
    printf("=== Add_key 错误处理演示 ===\n");
    
    // 1. EEXIST - 密钥已存在
    printf("1. EEXIST 错误演示\n");
    key_serial_t key = add_key("user", "unique_key", "data", 4, KEY_SPEC_SESSION_KEYRING);
    if (key != -1) {
        key_serial_t duplicate = add_key("user", "unique_key", "more data", 9, 
                                        KEY_SPEC_SESSION_KEYRING);
        if (duplicate == -1 && errno == EEXIST) {
            printf("   创建重复密钥失败(预期): %s\n", strerror(errno));
        }
        keyctl(KEYCTL_REVOKE, key, 0, 0, 0);
    }
    
    // 2. EINVAL - 无效参数
    printf("\n2. EINVAL 错误演示\n");
    key_serial_t invalid_key = add_key("invalid_type", "test", "data", 4, 
                                      KEY_SPEC_SESSION_KEYRING);
    if (invalid_key == -1 && errno == EINVAL) {
        printf("   使用无效密钥类型失败(预期): %s\n", strerror(errno));
    }
    
    // 3. ENOKEY - 密钥环不存在
    printf("\n3. ENOKEY 错误演示\n");
    key_serial_t no_keyring = add_key("user", "test", "data", 4, 999999);
    if (no_keyring == -1 && errno == ENOKEY) {
        printf("   使用不存在的密钥环失败(预期): %s\n", strerror(errno));
    }
    
    // 4. ERANGE - 数据太大
    printf("\n4. ERANGE 错误演示\n");
    char *huge_data = malloc(1024 * 1024); // 1MB
    if (huge_data) {
        memset(huge_data, 'A', 1024 * 1024);
        key_serial_t big_key = add_key("user", "huge_key", huge_data, 1024 * 1024,
                                      KEY_SPEC_SESSION_KEYRING);
        if (big_key == -1 && errno == ERANGE) {
            printf("   创建过大的密钥失败(预期): %s\n", strerror(errno));
        } else if (big_key != -1) {
            printf("   成功创建大密钥\n");
            keyctl(KEYCTL_REVOKE, big_key, 0, 0, 0);
        }
        free(huge_data);
    }
}

void demonstrate_security_considerations() {
    printf("\n=== 安全考虑演示 ===\n");
    
    printf("密钥保留服务的安全特性:\n");
    printf("1. 访问控制: 密钥有 UID/GID 和权限位\n");
    printf("2. 隔离性: 不同用户的密钥相互隔离\n");
    printf("3. 生命周期: 进程退出时密钥可能被清理\n");
    printf("4. 加密存储: 某些密钥类型提供加密保护\n\n");
    
    // 演示密钥权限
    key_serial_t key = add_key("user", "secure_key", "secret", 6, 
                              KEY_SPEC_SESSION_KEYRING);
    if (key != -1) {
        printf("创建密钥用于权限演示: %d\n", key);
        
        // 获取密钥信息
        char info_buffer[256];
        long info_len = keyctl(KEYCTL_DESCRIBE, key, (unsigned long)info_buffer, 
                              sizeof(info_buffer), 0);
        if (info_len != -1) {
            info_buffer[info_len] = '\0';
            printf("密钥信息: %s", info_buffer);
        }
        
        keyctl(KEYCTL_REVOKE, key, 0, 0, 0);
    }
}

void demonstrate_practical_usage() {
    printf("\n=== 实际使用场景 ===\n");
    
    printf("1. 文件系统加密密钥管理:\n");
    printf("   - 存储加密文件系统的主密钥\n");
    printf("   - 管理用户会话的解密密钥\n\n");
    
    printf("2. 网络认证凭据:\n");
    printf("   - 存储 Kerberos 票据\n");
    printf("   - 保存 TLS 会话密钥\n\n");
    
    printf("3. 应用程序密钥管理:\n");
    printf("   - 数据库连接密码\n");
    printf("   - API 访问令牌\n");
    printf("   - 加密操作的对称密钥\n\n");
    
    // 模拟应用程序使用场景
    printf("模拟应用程序密钥使用:\n");
    const char *app_keys[] = {
        "database_password",
        "api_token",
        "encryption_key"
    };
    
    for (int i = 0; i < 3; i++) {
        char key_data[50];
        snprintf(key_data, sizeof(key_data), "secret_data_for_%s", app_keys[i]);
        
        key_serial_t app_key = add_key("user", app_keys[i], key_data, 
                                      strlen(key_data), KEY_SPEC_SESSION_KEYRING);
        if (app_key != -1) {
            printf("   为 %s 创建密钥: %d\n", app_keys[i], app_key);
            // 在实际应用中,这里会使用密钥进行操作
            keyctl(KEYCTL_REVOKE, app_key, 0, 0, 0);
        }
    }
}

int main() {
    printf("Add_key 函数安全和错误处理演示\n\n");
    
    demonstrate_error_handling();
    demonstrate_security_considerations();
    demonstrate_practical_usage();
    
    printf("\n=== 使用建议 ===\n");
    printf("1. 错误处理: 始终检查返回值和 errno\n");
    printf("2. 权限管理: 确保有足够的权限创建密钥\n");
    printf("3. 资源清理: 使用完密钥后及时撤销\n");
    printf("4. 类型选择: 根据用途选择合适的密钥类型\n");
    printf("5. 密钥环: 选择合适的密钥环目标\n");
    printf("6. 数据大小: 注意密钥数据大小限制\n\n");
    
    printf("=== 系统要求 ===\n");
    printf("1. 需要 Linux 内核 2.6.10+\n");
    printf("2. 需要安装 keyutils 库\n");
    printf("3. 某些功能可能需要 root 权限\n");
    printf("4. 需要内核配置支持密钥保留服务\n");
    
    return 0;
}

编译和运行说明 見出しへのリンク

# 安装 keyutils 开发包(Ubuntu/Debian)
sudo apt-get install libkeyutils-dev

# 安装 keyutils 开发包(CentOS/RHEL/Fedora)
sudo yum install keyutils-libs-devel
# 或
sudo dnf install keyutils-libs-devel

# 编译示例
gcc -o add_key_basic add_key_basic.c -lkeyutils
gcc -o add_key_types add_key_types.c -lkeyutils
gcc -o add_key_errors add_key_errors.c -lkeyutils

# 运行示例
./add_key_basic
./add_key_types
./add_key_errors

重要注意事项 見出しへのリンク

  1. 内核支持: 需要 Linux 内核 2.6.10+ 和相应的配置支持
  2. 权限要求: 某些操作可能需要 root 权限或特定的能力
  3. 库依赖: 需要链接 libkeyutils
  4. 资源管理: 使用完密钥后应及时撤销,避免资源泄漏
  5. 安全性: 密钥在内核中存储,比用户空间存储更安全
  6. 生命周期: 密钥的生命周期与进程和密钥环相关
  7. 类型限制: 不同密钥类型有不同的用途和限制
  8. 大小限制: 密钥数据有大小限制(通常几 KB 到几 MB)