키링 (Key Retention Service)

Linux 커널의 Key Retention Service는 암호화(Encryption) 키, 인증 토큰, 기타 보안 데이터를 커널 공간(Kernel Space)에 안전하게 저장하고 관리하는 프레임워크입니다. struct key 기반의 자료구조, 다양한 key_type(user, logon, encrypted, trusted, big_key, asymmetric), 프로세스별 키링 계층, keyctl 시스템 콜(System Call), request_key upcall 메커니즘, TPM Sealed Keys, X.509/PKCS#7 인증서 검증, fscrypt/dm-crypt 키 연동, watch_queue 알림까지 커널 소스 기반으로 심층 분석합니다.

ℹ️

선행 지식: 이 문서는 커널 보안 개요암호화 서브시스템의 기본 개념을 이해하고 있다고 가정합니다. TPM 관련 내용은 Secure Boot 문서도 함께 참고하세요.

💡

일상 비유: 키링은 열쇠 보관함(key cabinet)과 같습니다. 각 열쇠(key)는 이름표가 붙어 있고, 보관함(keyring)은 중첩될 수 있습니다. 열쇠마다 유형이 다르고(자동차 키, 사무실 키, 금고 키), 누가 사용할 수 있는지 접근 권한이 지정됩니다. TPM sealed key는 금고 안에 잠긴 열쇠로, 금고(TPM)가 없으면 꺼낼 수 없습니다.

핵심 요약

  • Key Retention Service는 암호화 키, 인증 토큰 등을 커널 공간에 캐싱하여 사용자 공간(User Space) 프로세스(Process)와 커널 서비스가 안전하게 공유할 수 있게 합니다.
  • struct key는 모든 키의 기본 자료구조이며, 참조 카운트(Reference Count), 만료 시간, 접근 권한, 페이로드(Payload)를 포함합니다.
  • key_type 시스템으로 user, logon, encrypted, trusted, big_key, asymmetric 등 다양한 키 유형을 플러그인 방식으로 지원합니다.
  • 프로세스별 키링 계층(session/process/thread)과 사용자별 키링(user/user_session)으로 키의 범위와 수명을 제어합니다.
  • add_key(), request_key(), keyctl() 3개의 시스템 콜로 커널 키 관리를 수행합니다.
  • request_key upcall은 커널이 키를 찾지 못할 때 /sbin/request-key를 호출하여 사용자 공간에서 키를 생성하는 메커니즘입니다.
  • 플랫폼 키링(.builtin_trusted_keys, .machine, .platform)은 Secure Boot 체인과 커널 모듈(Kernel Module) 서명 검증(Signature Verification)의 신뢰 앵커입니다.
  • fscryptdm-crypt는 키링 서비스를 통해 암호화 키를 안전하게 전달받습니다.

Key Retention Service 아키텍처

Key Retention Service는 Linux 2.6.10에서 David Howells에 의해 도입되었습니다. 핵심 목적은 암호화 키와 인증 토큰을 커널 공간에 안전하게 보관하고, 프로세스 간 공유를 제어하며, 키의 생명주기를 자동 관리하는 것입니다. 소스 코드는 security/keys/ 디렉토리에 위치합니다.

사용자 공간 커널 공간 keyctl 유틸리티 libkeyutils /sbin/request-key fscryptctl cryptsetup add_key() / request_key() / keyctl() key_type 레지스트리 user, logon, encrypted... 키링 계층 session/process/thread 접근 제어 permissions / ACL GC / 만료 key_gc_work fscrypt dm-crypt 모듈 서명 검증 IMA/EVM TLS handshake TPM 서브시스템 seal / unseal / PCR

키링 서브시스템의 주요 구성 요소는 다음과 같습니다.

구성 요소소스 위치역할
key.csecurity/keys/key.cstruct key 생성, 검색, 삭제, 참조 카운트 관리
keyring.csecurity/keys/keyring.c키링(키 컬렉션) 관리, 연관 배열 기반 검색
keyctl.csecurity/keys/keyctl.ckeyctl() 시스템 콜 구현
request_key.csecurity/keys/request_key.crequest_key upcall 메커니즘
gc.csecurity/keys/gc.c만료/폐기 키의 가비지 컬렉션
permission.csecurity/keys/permission.c키 접근 권한 검사
encrypted-keys/security/keys/encrypted-keys/encrypted 키 타입 구현
trusted-keys/security/keys/trusted-keys/TPM sealed trusted 키 타입 구현
big_key.csecurity/keys/big_key.cshmem 기반 대용량 키 저장

struct key 자료구조

struct key(include/linux/key.h)는 키링 서비스의 핵심 자료구조입니다. 모든 키(키링 포함)는 이 구조체(Struct)의 인스턴스로 표현됩니다.

/* include/linux/key.h */
struct key {
    refcount_t          usage;          /* 참조 카운트 */
    key_serial_t        serial;         /* 키 일련번호 (고유 ID) */
    union {
        struct list_head graveyard_link;
        struct rb_node   serial_node;
    };
    struct rw_semaphore  sem;            /* 키 내용 접근 세마포어 */
    struct key_user     *user;          /* 소유자 사용자 정보 */
    void                *security;      /* LSM 보안 레이블 */
    union {
        time64_t        expiry;         /* 만료 시각 (0 = 무기한) */
        time64_t        revoked_at;     /* 폐기 시각 */
    };
    time64_t            last_used_at;   /* 마지막 사용 시각 */
    kuid_t              uid;            /* 소유자 UID */
    kgid_t              gid;            /* 소유자 GID */
    key_perm_t          perm;           /* 접근 권한 비트마스크 */
    unsigned short      quotalen;       /* 할당량에 반영되는 페이로드 길이 */
    unsigned short      datalen;        /* 페이로드 실제 길이 */
    short               state;          /* 키 상태 */
    unsigned long       flags;          /* KEY_FLAG_* 플래그 */

    union {
        struct keyring_index_key index_key;  /* 검색 인덱스 키 */
        struct {
            unsigned long           hash;
            unsigned long           len_desc;
            struct key_type        *type;       /* 키 타입 */
            struct key_tag         *domain_tag;
            char                   *description; /* 키 설명(이름) */
        };
    };

    union {
        union key_payload  payload;    /* 키 페이로드 (실제 데이터) */
        struct {
            struct list_head name_link;
            struct assoc_array keys;      /* 키링의 경우: 하위 키 목록 */
        };
    };
};

주요 필드를 분석합니다.

필드타입설명
usagerefcount_t참조 카운트. key_get()/key_put()으로 증감. 0이 되면 GC 대상
serialkey_serial_t커널 전역 고유 일련번호. 사용자 공간에서 키를 식별하는 핸들
semrw_semaphore키 페이로드 읽기/쓰기 동기화. read는 키 사용, write는 키 갱신
permkey_perm_t30비트 접근 권한. possessor/user/group/other 각 7비트 + 2비트 예약
flagsunsigned longKEY_FLAG_DEAD, KEY_FLAG_REVOKED, KEY_FLAG_IN_QUOTA 등 상태 플래그
stateshort양수: 인스턴스화 완료, 0: 대기 중, 음수: 오류 코드
payloadunion key_payload실제 키 데이터. key_type의 콜백(Callback) 함수가 해석
typestruct key_type *이 키의 타입. 인스턴스화, 검증, 비교, 파괴 등의 콜백 제공
ℹ️

키 일련번호(serial): 양의 정수로 자동 할당되며, 사용자 공간에서 키를 참조할 때 사용합니다. 특수 일련번호가 있습니다: KEY_SPEC_THREAD_KEYRING(-1), KEY_SPEC_PROCESS_KEYRING(-2), KEY_SPEC_SESSION_KEYRING(-3), KEY_SPEC_USER_KEYRING(-4), KEY_SPEC_USER_SESSION_KEYRING(-5).

key_type 시스템

struct key_type은 키의 유형을 정의하는 플러그인 인터페이스입니다. 각 key_type은 키 페이로드의 인스턴스화, 검증, 비교, 읽기, 파괴 등의 콜백 함수를 제공합니다.

struct key_type instantiate / describe / destroy user logon encrypted trusted big_key asymmetric 사용자 공간 read 일반 blob 저장 가장 기본적 타입 read 차단 커널 전용 접근 dm-crypt 키 등 마스터 키로 암호화 user/trusted 래핑 AES-256-GCM TPM seal/unseal PCR 바인딩 하드웨어 보호 대용량 키 shmem/tmpfs 저장 AES 암호화 보관 X.509 인증서 PKCS#7 서명 검증 모듈/IMA 검증 keyring (특수 key_type) 키 컬렉션 — 다른 키를 링크

struct key_type의 주요 콜백 함수:

struct key_type {
    const char          *name;          /* 타입 이름 ("user", "logon" 등) */
    size_t              def_datalen;    /* 기본 페이로드 크기 */
    unsigned            flags;          /* KEY_TYPE_* 플래그 */

    /* 키 인스턴스화 — 페이로드 데이터를 키에 저장 */
    int  (*preparse)(struct key_preparsed_payload *prep);
    void (*free_preparse)(struct key_preparsed_payload *prep);
    int  (*instantiate)(struct key *key,
                        struct key_preparsed_payload *prep);

    /* 키 갱신 */
    int  (*update)(struct key *key,
                   struct key_preparsed_payload *prep);

    /* 키 매칭 — 검색 시 비교 함수 */
    int  (*match_preparse)(struct key_match_data *match_data);
    void (*match_free)(struct key_match_data *match_data);

    /* 키 폐기/파괴 */
    void (*revoke)(struct key *key);
    void (*destroy)(struct key *key);

    /* 사용자 공간으로 키 내용 읽기 */
    long (*read)(const struct key *key,
                char *buffer, size_t buflen);

    /* /proc/keys 표시 */
    void (*describe)(const struct key *key,
                     struct seq_file *m);
};

새로운 키 타입을 등록하려면 register_key_type()을, 해제하려면 unregister_key_type()을 호출합니다. 커널 모듈에서 사용자 정의 키 타입을 추가할 수 있습니다.

user / logon 키 타입

user 타입은 가장 기본적인 키 타입으로, 임의의 바이트 블롭(blob)을 저장합니다. 사용자 공간에서 KEYCTL_READ로 내용을 읽을 수 있습니다.

/* security/keys/user_defined.c */
struct key_type key_type_user = {
    .name                   = "user",
    .def_datalen            = 0,
    .preparse               = user_preparse,
    .free_preparse          = user_free_preparse,
    .instantiate            = generic_key_instantiate,
    .update                 = user_update,
    .revoke                 = user_revoke,
    .destroy                = user_destroy,
    .describe               = user_describe,
    .read                   = user_read,    /* 사용자 공간 read 허용 */
};
# user 타입 키 생성 및 읽기
$ keyctl add user mykey "hello world" @s
123456789
$ keyctl print 123456789
hello world
$ keyctl pipe 123456789 | hexdump -C
00000000  68 65 6c 6c 6f 20 77 6f  72 6c 64              |hello world|

logon 타입은 user 타입과 동일한 저장 방식을 사용하지만, read 콜백이 없어 사용자 공간에서 키 내용을 읽을 수 없습니다. 커널만 접근 가능하므로 dm-crypt, fscrypt, ecryptfs 등에서 사용합니다.

struct key_type key_type_logon = {
    .name                   = "logon",
    .def_datalen            = 0,
    .preparse               = user_preparse,
    .free_preparse          = user_free_preparse,
    .instantiate            = generic_key_instantiate,
    .update                 = user_update,
    .revoke                 = user_revoke,
    .destroy                = user_destroy,
    .describe               = user_describe,
    /* .read 콜백 없음 → 사용자 공간에서 read 불가 */
};
비교 항목user 타입logon 타입
사용자 공간 read허용 (KEYCTL_READ)차단 (-EOPNOTSUPP)
사용자 공간 write허용 (add_key())허용 (add_key())
커널 접근허용허용
주요 용도범용 시크릿 저장dm-crypt, fscrypt 키
description 접두사제한 없음서비스명:설명 형식 필수
⚠️

logon 키의 description 규칙: logon 타입 키는 반드시 서비스명:설명 형식이어야 합니다(예: dm-crypt:my-volume). 콜론이 없으면 -EINVAL을 반환합니다. 이는 서로 다른 서비스 간 키 이름 충돌을 방지합니다.

encrypted 키 타입

encrypted 키는 마스터 키(user 또는 trusted 타입)로 암호화되어 저장되는 키입니다. 마스터 키 없이는 평문을 복원할 수 없으므로, 디스크에 안전하게 저장할 수 있습니다. 암호화 알고리즘은 AES-256-GCM을 사용합니다.

# 1. 마스터 키 생성 (user 타입)
$ keyctl add user master-key "$(dd if=/dev/urandom bs=32 count=1 2>/dev/null)" @s
456789012

# 2. encrypted 키 생성 (마스터 키로 보호)
$ keyctl add encrypted my-enc-key "new user:master-key 64" @s
567890123

# 3. encrypted 키의 암호문 읽기 (평문이 아닌 암호화된 blob)
$ keyctl pipe 567890123 | hexdump -C | head -5

# 4. 디스크에 저장 후 나중에 복원
$ keyctl pipe 567890123 > /etc/keys/my-enc-key.blob
$ keyctl add encrypted my-enc-key "load $(cat /etc/keys/my-enc-key.blob)" @s

encrypted 키의 구조:

/* security/keys/encrypted-keys/encrypted.c */
struct encrypted_key_payload {
    struct rcu_head    rcu;
    char              *format;          /* "default" 또는 "ecryptfs" */
    char              *master_desc;     /* 마스터 키 description */
    char              *datalen;         /* 평문 데이터 길이 */
    u8                *iv;              /* 초기화 벡터 */
    u8                *encrypted_data;  /* 암호화된 데이터 */
    unsigned short     payload_datalen;  /* 전체 페이로드 길이 */
    u8                 decrypted_data[]; /* 복호화된 평문 (커널 메모리) */
};
💡

마스터 키 계층화: encrypted 키의 마스터 키를 trusted 타입으로 사용하면, TPM 하드웨어까지 이어지는 키 보호 체인을 구성할 수 있습니다. encrypted(trusted:tpm-master) → dm-crypt 볼륨 키 형태가 가장 안전한 구성입니다.

trusted 키 타입 (TPM Sealed Keys)

trusted 키는 TPM(Trusted Platform Module) 하드웨어에 의해 봉인(seal)되는 키입니다. TPM의 SRK(Storage Root Key)로 암호화되어 저장되며, 특정 PCR(Platform Configuration Register) 값과 바인딩할 수 있습니다. TPM 없이는 키를 복원할 수 없으므로 최고 수준의 키 보호를 제공합니다.

trusted 키의 핵심 구조체:

/* security/keys/trusted-keys/trusted_core.c */
struct trusted_key_payload {
    struct rcu_head    rcu;
    unsigned int       key_len;         /* 평문 키 길이 */
    unsigned int       blob_len;        /* 봉인 blob 길이 */
    unsigned char      migratable;      /* 마이그레이션 허용 여부 */
    unsigned char      old_format;      /* 이전 형식 호환 */
    unsigned char      key[MAX_KEY_SIZE + 1];  /* 평문 키 데이터 */
    unsigned char      blob[MAX_BLOB_SIZE];    /* TPM 봉인 blob */
};
ℹ️

trusted 키 소스: Linux 5.13부터 TPM 외에 ARM TrustZone TEE(OP-TEE)와 CAAM(NXP Cryptographic Acceleration and Assurance Module)도 trusted 키의 백엔드로 사용할 수 있습니다. CONFIG_TRUSTED_KEYS_TPM, CONFIG_TRUSTED_KEYS_TEE, CONFIG_TRUSTED_KEYS_CAAM으로 선택합니다.

💡

TPM Sealed Keys: seal/unseal 흐름 다이어그램, EA 정책(PolicyPCR/PolicyPassword) 봉인, keyctl 실습 예제, tpm2-tools 실전 레시피 등은 TPM 2.0 문서를 참조하세요.

big_key 키 타입

big_key 타입은 대용량 키 데이터(최대 1MiB)를 효율적으로 저장합니다. 작은 키(기본적으로 PAGE_SIZE 이하)는 일반 kmalloc 메모리에 저장하지만, 큰 키는 shmem/tmpfs 파일에 AES 암호화하여 저장합니다. 이로써 커널 직접 메모리 사용을 최소화하면서도 대용량 인증서 체인이나 Kerberos 티켓을 저장할 수 있습니다.

/* security/keys/big_key.c */
enum {
    big_key_data,       /* 작은 키: kmalloc 직접 저장 */
    big_key_path,       /* 큰 키: shmem 파일 경로 */
};

struct key_type key_type_big_key = {
    .name           = "big_key",
    .preparse       = big_key_preparse,
    .free_preparse  = big_key_free_preparse,
    .instantiate    = generic_key_instantiate,
    .revoke         = big_key_revoke,
    .destroy        = big_key_destroy,
    .describe       = big_key_describe,
    .read           = big_key_read,
    .def_datalen    = 0,
};

/* 큰 키 저장 시 AES 암호화 */
static int big_key_preparse(struct key_preparsed_payload *prep)
{
    if (prep->datalen > BIG_KEY_FILE_THRESHOLD) {
        /* shmem 파일 생성 + AES-GCM 암호화 후 저장 */
        big_key_crypt(BIG_KEY_ENC, ...);
    } else {
        /* 일반 kmalloc으로 직접 저장 */
        prep->payload.data[big_key_data] = kmemdup(...);
    }
}
키 크기저장 방식암호화메모리 영향
≤ PAGE_SIZEkmalloc (직접 메모리)없음커널 직접 매핑(Mapping)
> PAGE_SIZE ~ 1 MiBshmem/tmpfs 파일AES-GCM (커널 RNG IV)페이지 캐시 (스왑 가능)

asymmetric 키 타입 (X.509 / PKCS#7)

asymmetric 키 타입은 X.509 인증서, PKCS#7 서명, 공개키/개인키 쌍을 저장합니다. 커널 모듈 서명 검증, IMA/EVM 서명 검증, kexec 서명 검증 등에서 사용됩니다.

PKCS#7 서명 모듈/IMA/kexec pkcs7_verify() 서명 검증 + 인증서 추출 x509_validate_trust() 체인 상의 인증서 검증 .builtin_trusted_keys 커널 빌트인 신뢰 앵커 .secondary_trusted_keys .machine .platform 검증 성공 검증 실패 → -EKEYREJECTED
/* crypto/asymmetric_keys/asymmetric_type.c */
struct key_type key_type_asymmetric = {
    .name        = "asymmetric",
    .preparse    = asymmetric_key_preparse,
    .instantiate = generic_key_instantiate,
    .match_preparse = asymmetric_key_match_preparse,
    .destroy     = asymmetric_key_destroy,
    .describe    = asymmetric_key_describe,
    .lookup_restriction = asymmetric_lookup_restriction,
};

/* 비대칭 키 하위 타입 */
struct asymmetric_key_subtype {
    const char *name;
    int (*verify_signature)(const struct key *key,
                           const struct public_key_signature *sig);
    int (*query)(const struct kernel_pkey_params *params,
               struct kernel_pkey_query *info);
};

지원되는 비대칭 키 알고리즘:

알고리즘커널 설정용도
RSACONFIG_CRYPTO_RSA모듈 서명, X.509, PKCS#7
ECDSA (P-256, P-384)CONFIG_CRYPTO_ECDSAIMA 서명, 경량 인증서
EdDSA (Ed25519)CONFIG_CRYPTO_ECRDSAGOST 표준 서명
SM2CONFIG_CRYPTO_SM2중국 국가 표준 (GB/T 32918)

키링 계층 구조

Linux는 프로세스별, 사용자별로 키링을 계층적으로 관리합니다. 키를 검색할 때 thread → process → session 순으로 탐색하며, 각 키링은 struct key의 특수 인스턴스입니다(key_type은 "keyring").

프로세스별 키링 계층 Thread Keyring Process Keyring Session Keyring 스레드 전용, clone 시 비공유 TGID 공유, 같은 프로세스의 모든 스레드 세션 공유, fork 시 상속, PAM 관리 사용자별 키링 User Keyring User Session Keyring UID별, 모든 세션에서 공유 검색 순서 1 2 3 4 키링 내 키 예시 Session Keyring (@s): user: api-token encrypted: vol-key keyring (중첩) trusted: tpm-key logon: nfs4-key
키링특수 ID수명공유 범위생성 시점
ThreadKEY_SPEC_THREAD_KEYRING (-1)스레드(Thread) 종료 시 파괴해당 스레드만첫 접근 시 지연(Latency) 생성
ProcessKEY_SPEC_PROCESS_KEYRING (-2)프로세스 종료 시 파괴같은 TGID의 모든 스레드첫 접근 시 지연 생성
SessionKEY_SPEC_SESSION_KEYRING (-3)세션 종료 시 파괴세션의 모든 프로세스PAM 또는 keyctl session
UserKEY_SPEC_USER_KEYRING (-4)UID 네임스페이스(Namespace) 수명같은 UID의 모든 프로세스UID 첫 사용 시
User SessionKEY_SPEC_USER_SESSION_KEYRING (-5)UID 네임스페이스 수명같은 UID의 모든 프로세스UID 첫 사용 시
# 현재 프로세스의 키링 확인
$ keyctl show
Session Keyring
 123456789 --alswrv  1000  1000  keyring: _ses
 234567890 --alswrv  1000  1000   \_ keyring: _uid.1000
 345678901 --alswrv  1000  1000   \_ user: my-secret

# 새 세션 키링 생성
$ keyctl session my-session
Joined session keyring: 456789012

# 키링 재귀 검색
$ keyctl search @s user my-secret

keyctl 시스템 콜 / API

키링 서비스는 3개의 시스템 콜을 통해 사용자 공간과 상호작용합니다.

/* include/uapi/linux/keyctl.h */

/* 1. 키 생성/추가 */
key_serial_t add_key(
    const char *type,        /* 키 타입 ("user", "logon", ...) */
    const char *description, /* 키 설명/이름 */
    const void *payload,     /* 키 데이터 */
    size_t      plen,        /* 데이터 길이 */
    key_serial_t ringid      /* 대상 키링 (@s, @u 등) */
);

/* 2. 키 검색/요청 */
key_serial_t request_key(
    const char *type,        /* 키 타입 */
    const char *description, /* 키 설명 */
    const char *callout_info, /* upcall 매개변수 (없으면 NULL) */
    key_serial_t dest_keyring /* 결과 키를 저장할 키링 */
);

/* 3. 키 제어 (다목적) */
long keyctl(
    int operation,           /* KEYCTL_* 명령 */
    ...                      /* 명령별 가변 인자 */
);

주요 KEYCTL_* 명령:

명령설명
KEYCTL_GET_KEYRING_ID0특수 키링 ID를 실제 serial로 변환
KEYCTL_JOIN_SESSION_KEYRING1새 세션 키링 생성 또는 기존 키링에 조인
KEYCTL_UPDATE2키 페이로드 갱신
KEYCTL_REVOKE3키 폐기 (사용 불가 처리)
KEYCTL_DESCRIBE6키 메타데이터 조회 (타입, UID, GID, perm)
KEYCTL_CLEAR7키링의 모든 키 제거
KEYCTL_LINK8키를 키링에 링크
KEYCTL_UNLINK9키를 키링에서 언링크
KEYCTL_SEARCH10키링 재귀 검색
KEYCTL_READ11키 페이로드 읽기
KEYCTL_SET_TIMEOUT15키 만료 시간 설정 (초 단위)
KEYCTL_SET_PERM5키 접근 권한 설정
KEYCTL_INVALIDATE21키 즉시 무효화 (GC 대기 없이)
KEYCTL_RESTRICT_KEYRING29키링에 추가 가능한 키 제한
KEYCTL_WATCH_KEY32키 변경 이벤트 감시 (watch_queue)

커널 내부 API:

/* 커널 모듈에서 키 검색 */
struct key *request_key(struct key_type *type,
                       const char *description,
                       const char *callout_info);

/* 키 페이로드에 접근 (RCU 보호) */
const void *user_key_payload_locked(const struct key *key);

/* 키 참조 카운트 관리 */
struct key *key_get(struct key *key);
void key_put(struct key *key);

/* 키 인스턴스화 (request_key upcall 콜백에서 사용) */
int key_instantiate_and_link(struct key *key,
                            const void *data, size_t datalen,
                            struct key *keyring,
                            struct key *authkey);

keyctl 유저스페이스 도구

keyutils 패키지는 키링 서비스의 사용자 공간 인터페이스를 제공합니다. keyctl 명령어와 libkeyutils 라이브러리로 구성됩니다.

# 패키지 설치
$ apt install keyutils         # Debian/Ubuntu
$ dnf install keyutils         # Fedora/RHEL

# 기본 키 관리
$ keyctl add user mykey "secret data" @s         # 키 추가
$ keyctl update 123456 "new data"                # 키 갱신
$ keyctl print 123456                            # 키 읽기 (텍스트)
$ keyctl pipe 123456 > /tmp/key.bin              # 키 읽기 (바이너리)
$ keyctl revoke 123456                           # 키 폐기
$ keyctl unlink 123456 @s                        # 키링에서 제거

# 키링 관리
$ keyctl show                                     # 현재 키링 트리 표시
$ keyctl show @s                                  # 세션 키링만 표시
$ keyctl newring mykeyring @s                     # 서브 키링 생성
$ keyctl link 123456 %:mykeyring                  # 키를 서브 키링에 링크

# 키 메타데이터 조회
$ keyctl describe 123456
user;1000;1000;3f010000;mykey

# 접근 권한 변경 (possessor=all, user=view+read, group=none, other=none)
$ keyctl setperm 123456 0x3f030000

# 만료 시간 설정 (3600초 = 1시간)
$ keyctl timeout 123456 3600

# 세션 키링 전환
$ keyctl session newsession /bin/bash             # 새 세션으로 bash 실행

# /proc/keys: 현재 프로세스가 볼 수 있는 모든 키
$ cat /proc/keys
00000001 I--Q---  1 perm 1f3f0000  0  0 keyring  _uid_ses.0: 1
00000002 I--Q---  2 perm 1f3f0000  0  0 keyring  _uid.0: empty

# /proc/key-users: 사용자별 키 사용 통계
$ cat /proc/key-users
    0:   4 3/3 2/200 48/20000
 1000:   2 2/2 2/200 38/20000

libkeyutils C 라이브러리 사용 예:

#include <keyutils.h>

int main(void)
{
    key_serial_t key;

    /* 세션 키링에 user 타입 키 추가 */
    key = add_key("user", "my-api-token",
                  "Bearer xyz123", 13, KEY_SPEC_SESSION_KEYRING);
    if (key < 0) {
        perror("add_key");
        return 1;
    }

    /* 키 검색 */
    key = request_key("user", "my-api-token",
                      NULL, KEY_SPEC_SESSION_KEYRING);

    /* 키 페이로드 읽기 */
    char buf[256];
    long len = keyctl_read(key, buf, sizeof(buf));

    /* 키 만료 설정 (1시간) */
    keyctl_set_timeout(key, 3600);

    return 0;
}

request_key 메커니즘

request_key()는 키를 검색하되, 찾지 못하면 사용자 공간의 /sbin/request-key 프로그램을 호출하여 키를 생성하는 upcall 메커니즘입니다. NFS4, CIFS, AFS 등 네트워크 파일시스템(Filesystem)의 인증 토큰 획득에 주로 사용됩니다.

1. request_key() 커널/사용자 공간 호출 2. 키링 검색 thread → process → session 발견 → 키 반환 미발견 → upcall 3. 미완성 키 생성 KEY_FLAG_USER_CONSTRUCT 4. /sbin/request-key 실행 call_usermodehelper() 5. request-key.conf 파싱 /etc/request-key.conf 6. 핸들러 실행 키 획득 프로그램 7. keyctl instantiate 키 페이로드 설정 → 완성 8. 호출자 깨우기 대기 중인 request_key() 완료 /etc/request-key.conf 예: create user nfs4:* * /usr/sbin/nfs4_id_to_key %k %d %c %S create user dns_resolver:* * /usr/sbin/key.dns_resolver %k

request-key.conf 형식:

# /etc/request-key.conf
# 동작   타입       설명패턴       콜아웃정보   프로그램
create   user       dns_resolver:*   *         /sbin/key.dns_resolver %k
create   user       nfs4:*           *         /usr/sbin/nfs4_id_to_key %k %d %c %S
create   big_key    *                *         /usr/share/keyutils/request-key-debug.sh %k %d %c %S
negate   *          *                *         /bin/keyctl negate %k 30 %S

키 생명주기

키는 생성부터 파괴까지 여러 상태를 거칩니다. 가비지 컬렉터(key_gc_work)가 만료, 폐기, 무효화된 키를 자동으로 정리합니다.

미완성 UNINSTANTIATED 활성 INSTANTIATED 갱신됨 UPDATED 만료 EXPIRED 폐기 REVOKED 음수 NEGATIVE 파괴 key_gc_work instantiate update expiry keyctl_revoke negate (upcall 실패) refcount=0 가비지 컬렉터 (key_gc_work) - 만료된 키 수거 (key_schedule_gc) - dead/revoked 키 정리 - 주기적 워크큐 기반 실행
/* 키 상태 (key->state) */
#define KEY_IS_UNINSTANTIATED  0    /* 아직 페이로드 없음 */
#define KEY_IS_POSITIVE        1    /* 정상 인스턴스화 */

/* 키 플래그 (key->flags) */
#define KEY_FLAG_DEAD          0    /* 키 타입 해제 → 사용 불가 */
#define KEY_FLAG_REVOKED       1    /* 키 폐기됨 */
#define KEY_FLAG_IN_QUOTA      2    /* 할당량 적용 대상 */
#define KEY_FLAG_USER_CONSTRUCT 3    /* 사용자 공간에서 구성 중 */
#define KEY_FLAG_ROOT_CAN_CLEAR 4    /* root가 키링 비우기 가능 */
#define KEY_FLAG_INVALIDATED   5    /* 즉시 무효화됨 */
#define KEY_FLAG_BUILTIN       6    /* 커널 빌트인 키 */
#define KEY_FLAG_ROOT_CAN_INVAL 7   /* root가 무효화 가능 */
#define KEY_FLAG_KEEP          8    /* GC 보호 (영구 유지) */

키 할당량 (Quota)

키링 서비스는 사용자별 키 할당량을 적용하여 DoS 공격을 방지합니다. 각 사용자(UID)는 최대 키 개수와 최대 바이트 수에 제한을 받습니다.

# 현재 할당량 확인
$ cat /proc/sys/kernel/keys/maxkeys
200

$ cat /proc/sys/kernel/keys/maxbytes
20000

# root의 할당량 (별도 설정)
$ cat /proc/sys/kernel/keys/root_maxkeys
1000000

$ cat /proc/sys/kernel/keys/root_maxbytes
25000000

# 사용자별 현재 사용량 (/proc/key-users)
# UID: nkeys objs/quota bytes/quota
$ cat /proc/key-users
    0:    10 10/1000000  500/25000000
 1000:     5  5/200      300/20000
sysctl 매개변수기본값설명
kernel/keys/maxkeys200일반 사용자의 최대 키 개수
kernel/keys/maxbytes20000일반 사용자의 최대 키 바이트
kernel/keys/root_maxkeys1000000root의 최대 키 개수
kernel/keys/root_maxbytes25000000root의 최대 키 바이트
kernel/keys/gc_delay300GC 실행 지연 시간 (초)
⚠️

할당량 초과: 키 생성 시 할당량을 초과하면 -EDQUOT을 반환합니다. 대량의 키를 다루는 서비스(NFS4 등)에서는 KEY_FLAG_IN_QUOTA 플래그를 해제하여 할당량에서 제외하거나, sysctl로 제한을 늘려야 합니다.

키 접근 권한

키 접근 권한은 파일 시스템의 rwx와 유사하지만, 4개의 카테고리에 각 7개의 권한 비트를 사용합니다. 총 30비트(key_perm_t)로 구성됩니다.

비트권한설명
bit 0VIEW키 속성 조회 (describe)
bit 1READ키 페이로드 읽기
bit 2WRITE키 페이로드 갱신, 키링에 링크/언링크
bit 3SEARCH키링 검색 시 이 키 발견 가능
bit 4LINK키를 다른 키링에 링크 가능
bit 5SETATTR속성 변경 (perm, timeout, UID/GID)
bit 6(예약)향후 확장용

4개의 카테고리:

카테고리비트 위치적용 대상
Possessor비트 24~30키를 소유(possess)하고 있는 프로세스
User비트 16~22키의 UID와 같은 UID를 가진 프로세스
Group비트 8~14키의 GID와 같은 GID를 가진 프로세스
Other비트 0~6위 어디에도 해당하지 않는 프로세스
# 권한 확인
$ keyctl describe %:session
keyring;1000;1000;3f1b0000;_ses

# 권한 해석: 0x3f1b0000
# Possessor: 0x3f = VIEW|READ|WRITE|SEARCH|LINK|SETATTR (전체)
# User:      0x1b = VIEW|READ|SEARCH|LINK
# Group:     0x00 = 없음
# Other:     0x00 = 없음

# 권한 설정 예
$ keyctl setperm 123456 0x3f1f0000  # possessor=all, user=all-setattr
ℹ️

Possessor 개념: "소유(possession)"는 파일의 소유자와 다릅니다. 프로세스의 키링 계층(thread → process → session)에서 검색 가능한 키를 소유하고 있다고 봅니다. 같은 UID이더라도 키가 다른 세션 키링에 있으면 possessor가 아닐 수 있습니다.

Linux 5.4부터 ACL(Access Control List) 기반 접근 제어(Access Control)도 지원합니다:

/* 키 ACL 항목 */
struct key_ace {
    unsigned int    type;    /* KEY_ACE_SUBJ_* */
    unsigned int    perm;    /* KEY_ACE_* 권한 비트 */
    union {
        kuid_t      uid;
        kgid_t      gid;
    };
};

플랫폼 키링

커널은 부팅 시 여러 시스템 키링을 생성하여 Secure Boot, 모듈 서명, IMA 등의 신뢰 앵커로 사용합니다.

.builtin_trusted_keys 커널 빌트인 인증서 (vmlinux에 내장) .secondary_trusted_keys 런타임 추가 인증서 (제한적) .machine 머신 소유자 인증서 (MOK) .platform UEFI db/dbx 인증서 .blacklist 해시 블랙리스트 (UEFI dbx) .ima IMA 정책 서명 검증 키 서명 검증 검증 용도 모듈 서명 / IMA / kexec / PKCS#7
키링커널 설정내용수정 가능 여부
.builtin_trusted_keys(기본 내장)커널 빌드 시 내장된 X.509 인증서불가 (읽기 전용(Read-Only))
.secondary_trusted_keysCONFIG_SECONDARY_TRUSTED_KEYRING빌트인 키로 서명된 인증서 추가 가능제한적 추가
.machineCONFIG_INTEGRITY_MACHINE_KEYRINGMOK(Machine Owner Key) 인증서UEFI MOK 관리
.platformCONFIG_INTEGRITY_PLATFORM_KEYRINGUEFI Secure Boot db 인증서UEFI 설정에서 관리
.blacklistCONFIG_SYSTEM_BLACKLIST_KEYRING거부할 인증서/모듈 해시(Hash)제한적 추가
.imaCONFIG_IMA_KEYRINGS_PERMIT_SIGNED_BY_BUILTIN_OR_SECONDARYIMA appraisal 검증 키서명된 키만 추가
# 빌트인 신뢰 키링의 인증서 확인
$ keyctl show %:.builtin_trusted_keys
Keyring
 123456 ---lswrv  0  0  keyring: .builtin_trusted_keys
 234567 ---lswrv  0  0   \_ asymmetric: Red Hat kernel signing key: ...
 345678 ---lswrv  0  0   \_ asymmetric: Fedora kernel signing key 5: ...

# 플랫폼 키링 (UEFI db 인증서)
$ keyctl show %:.platform
Keyring
 456789 ---lswrv  0  0  keyring: .platform
 567890 ---lswrv  0  0   \_ asymmetric: Microsoft Corporation UEFI CA 2011: ...

fscrypt 키 체인

fscrypt(파일시스템 수준 암호화)는 v2 정책부터 커널 키링 서비스를 통해 키를 관리합니다. 각 파일시스템 슈퍼블록(Superblock)에 .fscrypt 키링이 생성되며, 사용자는 fscryptctl 또는 FS_IOC_ADD_ENCRYPTION_KEY ioctl로 키를 추가합니다.

/* fs/crypto/keyring.c */

/* fscrypt 마스터 키 — 파일시스템 키링에 저장 */
struct fscrypt_master_key {
    struct hlist_node       mk_node;
    struct rw_semaphore     mk_sem;
    refcount_t             mk_struct_refs;
    struct rcu_head        mk_rcu_head;
    struct fscrypt_key_specifier mk_spec;

    /* 정책별 파생 키 */
    struct fscrypt_prepared_key mk_direct_keys[FSCRYPT_MODE_MAX + 1];
    struct fscrypt_prepared_key mk_iv_ino_lblk_64_keys[FSCRYPT_MODE_MAX + 1];
    struct fscrypt_prepared_key mk_iv_ino_lblk_32_keys[FSCRYPT_MODE_MAX + 1];
};
# fscrypt v2 정책 설정 흐름

# 1. 마스터 키 생성
$ fscryptctl generate_key > /etc/fscrypt/master.key

# 2. 파일시스템에 키 추가 (ioctl 기반)
$ fscryptctl add_key /mnt/encrypted < /etc/fscrypt/master.key
Added key with identifier: 0x1234567890abcdef

# 3. 디렉토리에 암호화 정책 적용
$ fscryptctl set_policy 0x1234567890abcdef /mnt/encrypted/private/

# 4. 키링에서 확인
$ keyctl show %:.fscrypt
Keyring
 789012 ---lswrv  0  0  keyring: .fscrypt
 890123 ---lswrv  0  0   \_ fscrypt-provisioning: ...

# 5. 키 제거 (잠금)
$ fscryptctl remove_key /mnt/encrypted 0x1234567890abcdef
💡

v1 vs v2 정책: v1 정책은 사용자 공간에서 키 파생을 수행하고 process keyring에 키를 저장했습니다. v2 정책은 커널 내부에서 HKDF-SHA512로 키를 파생하고, 파일시스템별 .fscrypt 키링에 저장합니다. v2가 보안적으로 더 안전하며, 다중 사용자 환경에서의 키 격리(Isolation)도 개선되었습니다.

dm-crypt 키 연동

dm-crypt는 블록 디바이스 수준 암호화를 제공하며, 커널 키링에서 암호화 키를 가져올 수 있습니다. logon 타입 키를 사용하여 키가 사용자 공간에 노출되는 것을 방지합니다.

# 방법 1: logon 키로 dm-crypt 볼륨 키 제공

# 1. logon 키 생성 (사용자 공간에서 read 불가)
$ keyctl add logon dm-crypt:my-volume "$(dd if=/dev/urandom bs=64 count=1 2>/dev/null)" @s
901234567

# 2. dmsetup으로 디바이스 생성 (키링에서 키 참조)
$ dmsetup create my-crypt --table \
    "0 $(blockdev --getsz /dev/sdb) crypt aes-xts-plain64 :64:logon:dm-crypt:my-volume 0 /dev/sdb 0"

# 방법 2: trusted + encrypted 키 체인

# 1. TPM으로 보호되는 마스터 키
$ keyctl add trusted tpm-master "new 32" @s
$ keyctl pipe %:tpm-master > /etc/keys/tpm-master.blob

# 2. encrypted 볼륨 키 (TPM 마스터로 보호)
$ keyctl add encrypted dm-crypt:secure-vol "new trusted:tpm-master 64" @s
$ keyctl pipe %:dm-crypt:secure-vol > /etc/keys/volume.blob

# 3. 부팅 시 키 체인 복원
$ keyctl add trusted tpm-master "load $(cat /etc/keys/tpm-master.blob)" @s
$ keyctl add encrypted dm-crypt:secure-vol "load $(cat /etc/keys/volume.blob)" @s

# 4. cryptsetup으로 볼륨 열기
$ cryptsetup open --type plain --key-file /proc/self/fd/0 --key-size 512 \
    /dev/sdb my-crypt <<< "$(keyctl pipe %:dm-crypt:secure-vol)"
/* drivers/md/dm-crypt.c — 키링에서 키 로드 */
static int crypt_set_keyring_key(struct crypt_config *cc,
                                  const char *key_string)
{
    struct key *key;
    const struct user_key_payload *ukp;

    /* key_string 형식: ":size:logon:description" */
    key = request_key(&key_type_logon, key_desc, NULL);
    if (IS_ERR(key))
        return PTR_ERR(key);

    down_read(&key->sem);
    ukp = user_key_payload_locked(key);
    /* ukp->data에서 암호화 키 복사 */
    memcpy(cc->key, ukp->data, cc->key_size);
    up_read(&key->sem);

    key_put(key);
    return 0;
}

watch_queue 알림

Linux 5.8에서 도입된 watch_queue는 키 변경 이벤트를 pipe를 통해 사용자 공간에 비동기로 전달합니다. 키의 생성, 갱신, 폐기, 무효화, 만료 이벤트를 감시할 수 있습니다.

키 이벤트 발생 update/revoke/expire... post_one_notification() 알림 메시지 생성 watch_queue 링 버퍼 pipe 내부 페이지 pipe fd read()/poll() 사용자 이벤트 처리 키 알림 이벤트 (key_notification) NOTIFY_KEY_INSTANTIATED 키 인스턴스화 완료 NOTIFY_KEY_UPDATED 페이로드 갱신 NOTIFY_KEY_REVOKED 키 폐기 NOTIFY_KEY_INVALIDATED 키 무효화 NOTIFY_KEY_LINKED NOTIFY_KEY_UNLINKED NOTIFY_KEY_CLEARED
/* watch_queue를 사용한 키 감시 (사용자 공간) */
#include <linux/watch_queue.h>
#include <linux/keyctl.h>

int main(void)
{
    int pipefd[2];
    pipe2(pipefd, O_NOTIFICATION_PIPE);

    /* pipe를 watch_queue로 설정 */
    ioctl(pipefd[0], IOC_WATCH_QUEUE_SET_SIZE, 256);

    /* 세션 키링 감시 시작 */
    keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, pipefd[0], 0x01);

    /* 이벤트 읽기 루프 */
    struct key_notification kn;
    while (read(pipefd[0], &kn, sizeof(kn)) > 0) {
        printf("Key %u: event %u, aux %u\n",
               kn.key_id, kn.subtype, kn.aux);
    }
}

커널 설정 종합

커널 설정기본값설명
CONFIG_KEYSYKey Retention Service 핵심 (필수)
CONFIG_KEYS_REQUEST_CACHENrequest_key 결과 태스크별 캐시(Cache)
CONFIG_PERSISTENT_KEYRINGSN영구 키링 (UID 네임스페이스 독립)
CONFIG_TRUSTED_KEYSMTPM/TEE sealed 키 타입
CONFIG_TRUSTED_KEYS_TPMYTPM2 기반 trusted 키
CONFIG_TRUSTED_KEYS_TEENARM TrustZone TEE 기반 trusted 키
CONFIG_TRUSTED_KEYS_CAAMNNXP CAAM 기반 trusted 키
CONFIG_ENCRYPTED_KEYSM암호화된 키 타입
CONFIG_BIG_KEYSY대용량 키 타입 (shmem 기반)
CONFIG_KEY_DH_OPERATIONSNDiffie-Hellman 키 교환
CONFIG_KEY_NOTIFICATIONSNwatch_queue 키 알림
CONFIG_ASYMMETRIC_KEY_TYPEY비대칭 키 타입 (X.509/PKCS#7)
CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPEY공개키 하위 타입
CONFIG_X509_CERTIFICATE_PARSERYX.509 인증서 파서
CONFIG_PKCS7_MESSAGE_PARSERYPKCS#7 메시지 파서
CONFIG_SYSTEM_TRUSTED_KEYRINGY.builtin_trusted_keys 키링
CONFIG_SECONDARY_TRUSTED_KEYRINGN.secondary_trusted_keys 키링
CONFIG_SYSTEM_BLACKLIST_KEYRINGN.blacklist 키링
CONFIG_INTEGRITY_PLATFORM_KEYRINGNUEFI .platform 키링
CONFIG_INTEGRITY_MACHINE_KEYRINGNMOK .machine 키링
# 최소 구성 (기본 키링 + trusted + encrypted)
CONFIG_KEYS=y
CONFIG_TRUSTED_KEYS=m
CONFIG_ENCRYPTED_KEYS=m
CONFIG_BIG_KEYS=y
CONFIG_ASYMMETRIC_KEY_TYPE=y
CONFIG_X509_CERTIFICATE_PARSER=y
CONFIG_SYSTEM_TRUSTED_KEYRING=y

# 완전 구성 (watch_queue + 플랫폼 키링 + DH)
CONFIG_KEYS=y
CONFIG_KEYS_REQUEST_CACHE=y
CONFIG_PERSISTENT_KEYRINGS=y
CONFIG_TRUSTED_KEYS=m
CONFIG_TRUSTED_KEYS_TPM=y
CONFIG_TRUSTED_KEYS_TEE=y
CONFIG_ENCRYPTED_KEYS=m
CONFIG_BIG_KEYS=y
CONFIG_KEY_DH_OPERATIONS=y
CONFIG_KEY_NOTIFICATIONS=y
CONFIG_ASYMMETRIC_KEY_TYPE=y
CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y
CONFIG_X509_CERTIFICATE_PARSER=y
CONFIG_PKCS7_MESSAGE_PARSER=y
CONFIG_SYSTEM_TRUSTED_KEYRING=y
CONFIG_SECONDARY_TRUSTED_KEYRING=y
CONFIG_SYSTEM_BLACKLIST_KEYRING=y
CONFIG_INTEGRITY_PLATFORM_KEYRING=y
CONFIG_INTEGRITY_MACHINE_KEYRING=y

커널 내부 구현

키링 서비스의 핵심 내부 구현을 분석합니다. security/keys/ 디렉토리의 주요 흐름을 추적합니다.

register_key_type() 흐름 모듈 init module_init() register_key_type() security/keys/key.c 중복 이름 체크 key_types_sem write lock key_types 리스트 추가 list_add(&type->link) add_key() 시스템 콜 내부 SYSCALL_DEFINE5 key_type 검색 type->preparse() key_alloc() key_instantiate_and_link() 핵심 내부 자료구조 key_serial_tree (RB 트리) — serial로 키 검색 key_user_tree (RB 트리) — UID별 할당량 추적 assoc_array — 키링 내 키 저장 (4원 트라이)

키링 내부의 연관 배열(assoc_array) 자료구조:

/* lib/assoc_array.c — 키링의 키 저장소 */
/*
 * 키링은 assoc_array를 사용하여 하위 키를 저장합니다.
 * 4원 트라이(radix-4 trie) 기반으로, 키의 index_key 해시를
 * 2비트씩 분할하여 트리를 구성합니다.
 *
 * 장점:
 * - O(1) 평균 검색/삽입 (해시 기반)
 * - RCU 안전한 동시 읽기
 * - 메모리 효율적 (노드당 최대 16개 슬롯)
 */

struct assoc_array {
    struct assoc_array_ptr *root;  /* 루트 노드/리프 포인터 */
    unsigned long          nr_leaves_on_tree; /* 키 개수 */
};

/* 키링에서 키 검색 (RCU 보호) */
struct key *keyring_search_rcu(struct key *keyring,
                              struct keyring_index_key *index_key)
{
    rcu_read_lock();
    /* assoc_array_find()로 해시 기반 O(1) 검색 */
    struct assoc_array_ptr *slot;
    slot = assoc_array_find(&keyring->keys, &keyring_assoc_array_ops,
                            index_key);
    rcu_read_unlock();
    return assoc_array_ptr_to_leaf(slot);
}

키 검색 시 키링 재귀 탐색 과정:

/* security/keys/keyring.c — 재귀 검색 */
struct key *keyring_search(struct key *keyring,
                          struct key_type *type,
                          const char *description,
                          bool recurse)
{
    /*
     * 검색 알고리즘:
     * 1. 현재 키링에서 타입+설명 매칭 키 검색
     * 2. 매칭 키 발견 → 접근 권한 체크 → 반환
     * 3. recurse=true이면 하위 키링도 재귀 검색
     * 4. 순환 참조 방지: 깊이 6 제한
     */
    struct keyring_search_context ctx = {
        .index_key.type     = type,
        .index_key.description = description,
        .cred               = current_cred(),
        .match_data.cmp     = key_default_cmp,
        .match_data.raw_data = description,
        .flags              = KEYRING_SEARCH_LOOKUP_DIRECT,
    };

    return search_nested_keyrings(keyring, &ctx);
}
ℹ️

LSM 연동: 키의 모든 주요 동작(생성, 읽기, 검색, 링크 등)에서 LSM 후크(Hook)가 호출됩니다. SELinux의 경우 security_key_alloc(), security_key_permission() 등을 통해 키 접근을 정책으로 제어합니다. 타입 시행(key_create, key_write, key_search 등)은 SELinux 정책 파일에서 정의합니다.

커널 내부 API

커널 모듈이나 서브시스템에서 키를 관리할 때 사용하는 핵심 내부 API를 상세히 분석합니다. 이 함수들은 security/keys/ 디렉토리에 구현되어 있으며, EXPORT_SYMBOL로 모듈에 공개됩니다.

key_alloc() — 키 할당

key_alloc()은 새 키 객체를 할당하고 초기화합니다. 이 시점에서 키는 아직 인스턴스화되지 않은 상태(uninstantiated)입니다.

/* security/keys/key.c */
struct key *key_alloc(
    struct key_type *type,           /* 키 타입 */
    const char *desc,               /* 키 설명(이름) */
    kuid_t uid,                      /* 소유자 UID */
    kgid_t gid,                      /* 소유자 GID */
    const struct cred *cred,       /* 호출자 자격증명 */
    key_perm_t perm,                 /* 접근 권한 */
    unsigned long flags,             /* KEY_ALLOC_* 플래그 */
    struct key_restriction *restrict_link /* 링크 제한 */
)
{
    struct key *key;
    int ret;

    /* 1. 키 타입 유효성 검사 */
    if (!type)
        return ERR_PTR(-EINVAL);

    /* 2. 할당량 검사 (KEY_ALLOC_NOT_IN_QUOTA가 없으면) */
    if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) {
        ret = key_payload_reserve(key, type->def_datalen);
        if (ret < 0)
            return ERR_PTR(ret);  /* -EDQUOT */
    }

    /* 3. struct key 메모리 할당 */
    key = kmem_cache_zalloc(key_jar, GFP_KERNEL);

    /* 4. 일련번호 할당 (RB 트리 삽입) */
    key->serial = key_alloc_serial(key);

    /* 5. LSM 보안 레이블 할당 */
    ret = security_key_alloc(key, cred, flags);

    /* 6. 기본 필드 초기화 */
    key->type = type;
    key->uid = uid;
    key->gid = gid;
    key->perm = perm;
    refcount_set(&key->usage, 1);

    return key;
}
플래그설명
KEY_ALLOC_IN_QUOTA0x0000할당량 적용 (기본)
KEY_ALLOC_NOT_IN_QUOTA0x0002할당량 면제 (커널 내부 키)
KEY_ALLOC_BUILT_IN0x0004빌트인 키 (부팅 시 생성)
KEY_ALLOC_BYPASS_RESTRICTION0x0008링크 제한 우회
KEY_ALLOC_UID_KEYRING0x0010UID 키링 생성
KEY_ALLOC_SET_KEEP0x0020GC 보호 설정

key_instantiate_and_link() — 키 인스턴스화

할당된 키에 페이로드를 설정하고 대상 키링에 링크합니다. request_key upcall의 최종 단계에서도 이 함수가 호출됩니다.

/* security/keys/key.c */
int key_instantiate_and_link(
    struct key *key,          /* 인스턴스화할 키 */
    const void *data,        /* 페이로드 데이터 */
    size_t datalen,           /* 데이터 길이 */
    struct key *keyring,      /* 링크할 대상 키링 */
    struct key *authkey        /* 인증 키 (upcall 시) */
)
{
    struct key_preparsed_payload prep;
    int ret;

    /* 1. preparse: key_type->preparse() 호출 */
    prep.data = data;
    prep.datalen = datalen;
    if (key->type->preparse) {
        ret = key->type->preparse(&prep);
        if (ret < 0)
            return ret;
    }

    /* 2. 페이로드 할당량 갱신 */
    key_payload_reserve(key, prep.quotalen);

    /* 3. instantiate: key_type->instantiate() 호출 */
    ret = key->type->instantiate(key, &prep);

    /* 4. 키링에 링크 */
    if (keyring)
        __key_link(key, keyring);

    /* 5. 대기 중인 request_key() 호출자 깨우기 */
    mark_key_instantiated(key, 0);
    wake_up_bit(&key->flags, KEY_FLAG_USER_CONSTRUCT);

    return 0;
}

request_key_and_link() — 검색 + upcall + 링크

request_key_and_link()은 커널 내부에서 가장 범용적인 키 검색 함수입니다. 키를 찾지 못하면 upcall을 수행하고, 결과를 대상 키링에 자동 링크합니다.

/* security/keys/request_key.c */
struct key *request_key_and_link(
    struct key_type *type,
    const char *description,
    struct key_tag *domain_tag,
    const char *callout_info,
    size_t callout_len,
    void *aux,
    struct key *dest_keyring,
    unsigned long flags
)
{
    /* 검색 순서:
     * 1. 요청 캐시 (CONFIG_KEYS_REQUEST_CACHE)
     * 2. thread keyring
     * 3. process keyring
     * 4. session keyring (재귀)
     * 5. user/user_session keyring
     * 6. callout_info가 있으면 upcall
     */
    struct key *key;

    key = search_process_keyrings(type, description, ...);
    if (!IS_ERR(key))
        return key;

    /* 키 미발견 → upcall 수행 */
    if (callout_info)
        key = construct_key_and_link(...);

    return key;
}

key_validate() — 키 유효성 검사

/* security/keys/key.c — 키 사용 전 유효성 확인 */
int key_validate(const struct key *key)
{
    unsigned long flags = READ_ONCE(key->flags);
    time64_t now = ktime_get_real_seconds();

    /* 폐기된 키 */
    if (flags & (1 << KEY_FLAG_REVOKED))
        return -EKEYREVOKED;

    /* 만료된 키 */
    if (key->expiry && now >= key->expiry)
        return -EKEYEXPIRED;

    /* 무효화된 키 */
    if (flags & (1 << KEY_FLAG_INVALIDATED))
        return -ENOKEY;

    return 0;
}

커널 모듈에서 키 활용 실전 패턴

/* 커널 모듈에서 키 검색, 사용, 해제 패턴 */
static int my_module_get_secret(char *buf, size_t buflen)
{
    struct key *key;
    const struct user_key_payload *upayload;
    int ret;

    /* 1. 키 검색 (upcall 없이) */
    key = request_key(&key_type_user,
                      "my-module:secret", NULL);
    if (IS_ERR(key))
        return PTR_ERR(key);

    /* 2. 키 유효성 검사 */
    ret = key_validate(key);
    if (ret < 0)
        goto out;

    /* 3. 키 세마포어 잠금 (읽기) */
    down_read(&key->sem);

    /* 4. 페이로드 접근 (RCU 보호) */
    upayload = user_key_payload_locked(key);
    if (!upayload || upayload->datalen > buflen) {
        ret = -EINVAL;
    } else {
        memcpy(buf, upayload->data, upayload->datalen);
        ret = upayload->datalen;
    }

    up_read(&key->sem);

out:
    /* 5. 키 참조 해제 */
    key_put(key);
    return ret;
}

네트워크 인증 키

키링 서비스는 네트워크 파일시스템(NFS, CIFS, AFS)과 DNS 해석, TLS 핸드셰이크(Handshake) 등에서 인증 자격증명을 관리하는 데 핵심적으로 사용됩니다.

dns_resolver 키 타입

dns_resolver 키 타입은 커널이 호스트명을 IP 주소로 해석해야 할 때 사용합니다. AFS, CIFS 등 커널 네트워크 파일시스템이 서버 주소를 해석할 때 request_key() upcall로 DNS 조회를 수행합니다.

/* net/dns_resolver/dns_key.c */
struct key_type key_type_dns_resolver = {
    .name           = "dns_resolver",
    .flags          = KEY_TYPE_NET_DOMAIN,
    .preparse       = dns_resolver_preparse,
    .free_preparse  = dns_resolver_free_preparse,
    .instantiate    = generic_key_instantiate,
    .describe       = dns_resolver_describe,
    .read           = dns_resolver_read,
    .destroy        = dns_resolver_destroy,
    .match_preparse = dns_resolver_match_preparse,
    .cmp            = dns_resolver_cmp,
};

/* 커널 내부에서 DNS 조회 */
struct key *dns_key = request_key_net(
    &key_type_dns_resolver,
    "afsdb:*.example.com",  /* DNS 질의 */
    net,                     /* 네트워크 네임스페이스 */
    "afsdb"                  /* callout 정보 */
);
# /etc/request-key.conf에 DNS 핸들러 설정
create  dns_resolver  *  *  /usr/sbin/key.dns_resolver %k

# DNS 키 수동 확인
$ keyctl request dns_resolver "afsdb:example.com" "" @s
$ keyctl show @s
Session Keyring
 ... dns_resolver: afsdb:example.com → 192.168.1.100

NFS4 인증 키

NFS4에서는 Kerberos 인증이 필요한 경우 키링을 통해 자격증명을 관리합니다. request_key() upcall로 nfs4_id_to_key 핸들러가 호출되어 사용자 ID를 Kerberos 주체(principal)로 매핑합니다.

# NFS4 ID 매핑 키 핸들러 설정
# /etc/request-key.d/id_resolver.conf
create  id_resolver  *  *  /usr/sbin/nfsidmap -t 600 %k %d

# NFS4 Kerberos 키탭 기반 인증 흐름
# 1. gssproxy가 Kerberos 티켓을 관리
$ systemctl enable gssproxy
$ systemctl start gssproxy

# 2. NFS 마운트 시 sec=krb5 사용
$ mount -t nfs4 -o sec=krb5 server:/export /mnt/nfs

# 3. 커널이 request_key()로 인증 토큰 요청
# → gssproxy가 키탭에서 TGT 획득 → 키링에 캐싱

# 4. 캐싱된 키 확인
$ keyctl show @s
Session Keyring
 ... user: nfs4:krb5cc_1000 → [Kerberos credential cache]

CIFS/SMB 인증 키

# CIFS 마운트 시 키링 기반 비밀번호 관리

# 1. 비밀번호를 키링에 저장 (세션 키링)
$ cifscreds add server.example.com
Password: ****

# 2. 내부적으로 logon 키 타입으로 저장됨
$ keyctl show @s
Session Keyring
 ... logon: cifs:username@server.example.com

# 3. 마운트 시 키링에서 자동 검색
$ mount -t cifs //server.example.com/share /mnt/cifs -o sec=ntlmssp

# 4. 비밀번호 갱신
$ cifscreds update server.example.com

커널 TLS 핸드셰이크 키

Linux 6.2부터 커널 TLS(kTLS) 핸드셰이크가 지원됩니다. 키링 서비스는 TLS 세션 키와 인증서를 관리하는 데 사용됩니다.

/* net/handshake/tlshd.c — 커널 TLS 핸드셰이크 */

/* TLS 핸드셰이크 완료 후 세션 키를 키링에 저장 */
static int tls_handshake_done(struct sock *sk,
    struct tls_handshake_args *args)
{
    struct key *peerid;

    /* 피어 인증서를 asymmetric 키로 저장 */
    peerid = key_alloc(&key_type_asymmetric,
                       "tls:peer-cert", ...);

    /* PSK(Pre-Shared Key) 모드에서는 user 키 사용 */
    args->ta_keyring = request_key(
        &key_type_keyring, "_tls", NULL);

    return 0;
}
네트워크 서비스키 타입핸들러용도
NFS4user (id_resolver)nfsidmapUID/GID → 이름 매핑
NFS4 KerberosrxrpcgssproxyKerberos TGT 캐싱
CIFS/SMBlogon (cifs)cifscredsSMB 비밀번호 캐싱
AFSrxrpcafs_settokenAFS 토큰 관리
DNSdns_resolverkey.dns_resolver커널 DNS 해석
kTLSasymmetric / usertlshdTLS 세션 키/인증서

TPM 연동

TPM(Trusted Platform Module)과 키링의 연동은 키 보호의 최고 수준을 제공합니다. trusted 키 타입의 seal/unseal 흐름, PCR 바인딩, 정책 기반 봉인을 상세히 분석합니다.

TPM 2.0 하드웨어 SRK (저장 루트 키) PCR 레지스터 EA 정책 엔진 RNG 엔진 HMAC / 대칭 엔진 커널 키링 계층 trusted 키 TPM seal/unseal 보호 encrypted 키 trusted 키로 래핑 dm-crypt fscrypt IMA/EVM ecryptfs seal / unseal PCR 바인딩 정책 PCR[0-7] = UEFI/GRUB/커널/initramfs 측정값 PCR 값이 변경되면 unseal 실패 → 부팅 환경 변조 탐지 PolicyPCR + PolicyPassword → 이중 인증 가능 디스크 저장 sealed blob만 저장 (평문 없음)

trusted 키 생성과 PCR 바인딩

# TPM2 trusted 키 — 기본 생성
$ keyctl add trusted my-tpm-key "new 32" @s
123456789

# PCR 바인딩으로 생성 (PCR 0,2,7에 바인딩)
$ keyctl add trusted sealed-key "new 32 pcrinfo=0:sha256=...,2:sha256=...,7:sha256=..." @s

# sealed blob을 디스크에 저장
$ keyctl pipe 123456789 > /etc/keys/my-tpm-key.blob

# 재부팅 후 키 복원 (unseal)
$ keyctl add trusted my-tpm-key "load $(cat /etc/keys/my-tpm-key.blob)" @s

# PCR 값이 변경된 경우 (예: 커널 업데이트 후)
$ keyctl add trusted my-tpm-key "load $(cat /etc/keys/my-tpm-key.blob)" @s
# → -EPERM: PCR 불일치로 unseal 실패

# 키 갱신 (새 PCR 값으로 재봉인)
$ keyctl add trusted my-tpm-key "update pcrinfo=0:sha256=..." @s
$ keyctl pipe 123456789 > /etc/keys/my-tpm-key.blob  # 새 blob 저장

trusted + encrypted 키 체인

가장 안전한 키 보호 구성은 TPM trusted 키를 마스터로 사용하고, encrypted 키로 실제 볼륨 키를 보호하는 이중 구조입니다.

/* TPM → trusted → encrypted → dm-crypt 키 체인 */

/*
 * 보호 계층:
 * [TPM SRK] ──seal──→ [trusted 키 (32B 마스터)]
 *                            │
 *                      AES-GCM 래핑
 *                            │
 *                            ▼
 *                      [encrypted 키 (64B 볼륨 키)]
 *                            │
 *                            ▼
 *                      [dm-crypt 블록 암호화]
 *
 * 공격자가 디스크만 탈취한 경우:
 * - encrypted blob만 존재 (평문 없음)
 * - 마스터 키는 TPM에 봉인됨
 * - TPM 없이는 마스터 키 추출 불가
 * - PCR 바인딩 시 부팅 환경 변경도 차단
 */
# 전체 키 체인 설정 예시

# 1단계: TPM trusted 마스터 키
$ keyctl add trusted tpm-master "new 32 pcrinfo=0,7" @s
$ keyctl pipe %:tpm-master > /etc/keys/tpm-master.blob
$ chmod 400 /etc/keys/tpm-master.blob

# 2단계: encrypted 볼륨 키 (trusted 마스터로 보호)
$ keyctl add encrypted vol-key "new trusted:tpm-master 64" @s
$ keyctl pipe %:vol-key > /etc/keys/vol-key.blob
$ chmod 400 /etc/keys/vol-key.blob

# 3단계: dm-crypt 볼륨에 적용
$ dmsetup create secure-vol --table \
    "0 $(blockdev --getsz /dev/sdb) crypt \
     aes-xts-plain64 :64:logon:dm-crypt:vol-key 0 /dev/sdb 0"

# 부팅 시 자동 복원 스크립트 (/etc/initramfs-tools/scripts/local-top/)
#!/bin/sh
keyctl add trusted tpm-master "load $(cat /etc/keys/tpm-master.blob)" @s
keyctl add encrypted vol-key "load $(cat /etc/keys/vol-key.blob)" @s

키 보안과 감사

키링 서비스의 보안 모델은 다층적 접근 제어와 감사(Audit) 기능을 포함합니다. 키의 생성부터 파괴까지 모든 과정에서 보안 검증이 수행됩니다.

LSM 보안 후크

키의 모든 주요 동작에서 LSM(Linux Security Module) 후크가 호출됩니다. SELinux, AppArmor 등의 보안 모듈은 이 후크를 통해 키 접근 정책을 시행합니다.

LSM 후크호출 시점SELinux 권한
security_key_alloc()키 할당 시key { create }
security_key_free()키 해제 시-
security_key_permission()키 접근 시key { view read write search link setattr }
security_key_getsecurity()보안 레이블 조회-

SELinux 키 접근 정책 예시

# SELinux 정책에서 키 접근 제어
# /etc/selinux/policy/modules/my_key_policy.te

# 특정 도메인이 user 타입 키를 생성/검색 가능
allow my_app_t user_key_t:key { create write view read search link };

# 특정 도메인의 trusted 키 접근 차단
neverallow untrusted_app_t trusted_key_t:key *;

# 키 접근 감사 로깅
auditallow my_app_t user_key_t:key { write };

커널 감사 이벤트

커널 감사 서브시스템은 키 관련 이벤트를 기록합니다. auditctl로 키 감사 규칙을 설정할 수 있습니다.

# 키 생성/삭제 감사 규칙
$ auditctl -a always,exit -S add_key -k key_create
$ auditctl -a always,exit -S keyctl -k key_control

# 감사 로그 확인
$ ausearch -k key_create
type=SYSCALL msg=audit(1234567890.123:456):
  arch=c000003e syscall=248 success=yes exit=789012345
  a0=7f1234 a1=7f5678 a2=7f9abc a3=fffffffe
  key="key_create"

# 키 관련 감사 요약
$ aureport --key --summary

키 보안 모범 사례

항목권장 사항이유
키 타입 선택비밀 데이터는 logon 타입 사용사용자 공간 read 차단
만료 설정모든 임시 키에 만료 시간 설정키 누적 방지, DoS 예방
TPM 활용장기 비밀은 trusted 키 사용하드웨어 보호, cold boot 방어
권한 최소화필요한 최소 권한만 설정Possessor 외 접근 차단
키링 분리서비스별 별도 키링 사용키 격리, 범위 제한
감사 로깅중요 키 작업 감사 활성화사고 추적, 컴플라이언스
키 순환정기적 키 갱신 자동화장기 사용 키 탈취 위험 감소

실전 예제 종합

키링 서비스를 활용한 실전 시나리오와 코드 예제를 종합합니다.

시나리오 1: 애플리케이션 시크릿 관리

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <keyutils.h>

/* 데이터베이스 비밀번호를 키링에 안전하게 저장하고 사용하는 예제 */

int store_db_password(const char *db_name, const char *password) {
    char desc[256];
    key_serial_t key;

    /* logon 키로 저장 → 사용자 공간에서 read 불가 */
    snprintf(desc, sizeof(desc), "myapp:db:%s", db_name);
    key = add_key("logon", desc,
                  password, strlen(password),
                  KEY_SPEC_SESSION_KEYRING);
    if (key < 0) {
        perror("add_key");
        return -1;
    }

    /* 1시간 후 만료 설정 */
    keyctl_set_timeout(key, 3600);

    /* 접근 권한 설정: possessor만 접근, 다른 사용자 차단 */
    keyctl_setperm(key, 0x3f000000);

    printf("키 저장 완료: serial=%d\n", key);
    return key;
}

int retrieve_api_token(const char *service, char *buf, size_t len) {
    char desc[256];
    key_serial_t key;
    long ret;

    /* user 타입 키 검색 (read 가능) */
    snprintf(desc, sizeof(desc), "myapp:api:%s", service);
    key = request_key("user", desc,
                      NULL, KEY_SPEC_SESSION_KEYRING);
    if (key < 0) {
        perror("request_key");
        return -1;
    }

    /* 키 페이로드 읽기 */
    ret = keyctl_read(key, buf, len);
    if (ret < 0) {
        perror("keyctl_read");
        return -1;
    }

    return (int)ret;
}

int main(void) {
    char token[512];

    /* API 토큰 저장 */
    add_key("user", "myapp:api:github",
            "ghp_xxxxxxxxxxxxxxxxxxxx", 24,
            KEY_SPEC_SESSION_KEYRING);

    /* 토큰 검색 및 사용 */
    int len = retrieve_api_token("github", token, sizeof(token));
    if (len > 0)
        printf("Token: %.*s\n", len, token);

    return 0;
}
# 컴파일 및 실행
$ gcc -o key-demo key-demo.c -lkeyutils
$ ./key-demo
키 저장 완료: serial=123456789
Token: ghp_xxxxxxxxxxxxxxxxxxxx

시나리오 2: systemd 서비스 키 관리

# systemd 서비스에서 키링 활용

# 1. 서비스 시작 전 키를 세션 키링에 주입
# /etc/systemd/system/myapp.service.d/keys.conf
[Service]
ExecStartPre=/usr/bin/keyctl add user myapp:db-password "$(cat /etc/myapp/db.key)" @s
ExecStartPre=/usr/bin/keyctl timeout %%:myapp:db-password 7200

# 2. 애플리케이션은 키링에서 비밀번호 검색
# → /etc/myapp/db.key 파일은 부팅 후 삭제 가능

# 3. 키 순환 자동화 (cron)
# /etc/cron.daily/rotate-myapp-keys
#!/bin/bash
NEW_PASS=$(openssl rand -base64 32)
keyctl update %:myapp:db-password "$NEW_PASS"
# DB 비밀번호도 함께 변경하는 로직 필요

시나리오 3: SSH 에이전트 대체

# 커널 키링을 SSH 키 캐싱에 활용

# 1. SSH 키를 사용자 세션 키링에 저장
$ keyctl add user ssh:id_rsa "$(cat ~/.ssh/id_rsa)" @us
$ keyctl timeout %:ssh:id_rsa 28800  # 8시간 만료

# 2. 접근 권한: 소유자만 읽기 가능
$ keyctl setperm %:ssh:id_rsa 0x3f030000

# 3. 키 목록 확인
$ keyctl show @us
Session Keyring
 ... user: ssh:id_rsa [8시간 후 만료]

# 4. 키 폐기 (로그아웃 시)
$ keyctl revoke %:ssh:id_rsa

시나리오 4: watch_queue 기반 키 모니터링 도구

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <keyutils.h>
#include <linux/watch_queue.h>

/* 키 변경 이벤트를 실시간 감시하는 모니터링 도구 */
int main(void)
{
    int pipefd[2];
    struct watch_notification_filter filter;

    /* watch_queue pipe 생성 */
    if (pipe2(pipefd, O_NOTIFICATION_PIPE) < 0) {
        perror("pipe2");
        return 1;
    }

    /* 큐 크기 설정 */
    ioctl(pipefd[0], IOC_WATCH_QUEUE_SET_SIZE, 256);

    /* 필터: 모든 키 이벤트 수신 */
    filter.nr_filters = 1;
    filter.__reserved = 0;
    filter.filters[0].type = WATCH_TYPE_KEY_NOTIFY;
    filter.filters[0].subtype_filter[0] = 0xFFFFFFFF;
    ioctl(pipefd[0], IOC_WATCH_QUEUE_SET_FILTER, &filter);

    /* 세션 키링 감시 시작 */
    keyctl_watch_key(KEY_SPEC_SESSION_KEYRING, pipefd[0], 0x01);

    printf("키 이벤트 감시 중...\n");

    /* 이벤트 읽기 루프 */
    struct key_notification kn;
    while (read(pipefd[0], &kn, sizeof(kn)) == sizeof(kn)) {
        const char *event_names[] = {
            "instantiated", "updated", "linked",
            "unlinked", "cleared", "revoked",
            "invalidated", "setattr"
        };
        printf("[KEY EVENT] key=%u event=%s aux=%u\n",
               kn.key_id,
               event_names[kn.subtype],
               kn.aux);
    }

    close(pipefd[0]);
    close(pipefd[1]);
    return 0;
}

키링 운영 요약 체크리스트

점검 항목명령어기대 결과
커널 키링 지원 확인grep CONFIG_KEYS /boot/config-$(uname -r)CONFIG_KEYS=y
현재 키 목록cat /proc/keys키 serial, 타입, 설명 표시
사용자별 키 통계cat /proc/key-usersUID별 키 수/할당량
세션 키링 트리keyctl show @s계층적 키링 구조
빌트인 신뢰 키keyctl show %:.builtin_trusted_keysX.509 인증서 목록
TPM 키 지원grep TRUSTED_KEYS /boot/config-$(uname -r)CONFIG_TRUSTED_KEYS=m
할당량 설정sysctl kernel.keys.maxkeys200 (기본)
keyutils 설치keyctl --version버전 출력

외부 참고 자료