키링 (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)의 신뢰 앵커입니다. - fscrypt와 dm-crypt는 키링 서비스를 통해 암호화 키를 안전하게 전달받습니다.
Key Retention Service 아키텍처
Key Retention Service는 Linux 2.6.10에서 David Howells에 의해 도입되었습니다. 핵심 목적은 암호화 키와 인증 토큰을 커널 공간에 안전하게 보관하고, 프로세스 간 공유를 제어하며, 키의 생명주기를 자동 관리하는 것입니다. 소스 코드는 security/keys/ 디렉토리에 위치합니다.
키링 서브시스템의 주요 구성 요소는 다음과 같습니다.
| 구성 요소 | 소스 위치 | 역할 |
|---|---|---|
| key.c | security/keys/key.c | struct key 생성, 검색, 삭제, 참조 카운트 관리 |
| keyring.c | security/keys/keyring.c | 키링(키 컬렉션) 관리, 연관 배열 기반 검색 |
| keyctl.c | security/keys/keyctl.c | keyctl() 시스템 콜 구현 |
| request_key.c | security/keys/request_key.c | request_key upcall 메커니즘 |
| gc.c | security/keys/gc.c | 만료/폐기 키의 가비지 컬렉션 |
| permission.c | security/keys/permission.c | 키 접근 권한 검사 |
| encrypted-keys/ | security/keys/encrypted-keys/ | encrypted 키 타입 구현 |
| trusted-keys/ | security/keys/trusted-keys/ | TPM sealed trusted 키 타입 구현 |
| big_key.c | security/keys/big_key.c | shmem 기반 대용량 키 저장 |
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; /* 키링의 경우: 하위 키 목록 */
};
};
};
주요 필드를 분석합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
usage | refcount_t | 참조 카운트. key_get()/key_put()으로 증감. 0이 되면 GC 대상 |
serial | key_serial_t | 커널 전역 고유 일련번호. 사용자 공간에서 키를 식별하는 핸들 |
sem | rw_semaphore | 키 페이로드 읽기/쓰기 동기화. read는 키 사용, write는 키 갱신 |
perm | key_perm_t | 30비트 접근 권한. possessor/user/group/other 각 7비트 + 2비트 예약 |
flags | unsigned long | KEY_FLAG_DEAD, KEY_FLAG_REVOKED, KEY_FLAG_IN_QUOTA 등 상태 플래그 |
state | short | 양수: 인스턴스화 완료, 0: 대기 중, 음수: 오류 코드 |
payload | union key_payload | 실제 키 데이터. key_type의 콜백(Callback) 함수가 해석 |
type | struct 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의 주요 콜백 함수:
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_SIZE | kmalloc (직접 메모리) | 없음 | 커널 직접 매핑(Mapping) |
| > PAGE_SIZE ~ 1 MiB | shmem/tmpfs 파일 | AES-GCM (커널 RNG IV) | 페이지 캐시 (스왑 가능) |
asymmetric 키 타입 (X.509 / PKCS#7)
asymmetric 키 타입은 X.509 인증서, PKCS#7 서명, 공개키/개인키 쌍을 저장합니다. 커널 모듈 서명 검증, IMA/EVM 서명 검증, kexec 서명 검증 등에서 사용됩니다.
/* 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);
};
지원되는 비대칭 키 알고리즘:
| 알고리즘 | 커널 설정 | 용도 |
|---|---|---|
| RSA | CONFIG_CRYPTO_RSA | 모듈 서명, X.509, PKCS#7 |
| ECDSA (P-256, P-384) | CONFIG_CRYPTO_ECDSA | IMA 서명, 경량 인증서 |
| EdDSA (Ed25519) | CONFIG_CRYPTO_ECRDSA | GOST 표준 서명 |
| SM2 | CONFIG_CRYPTO_SM2 | 중국 국가 표준 (GB/T 32918) |
키링 계층 구조
Linux는 프로세스별, 사용자별로 키링을 계층적으로 관리합니다. 키를 검색할 때 thread → process → session 순으로 탐색하며, 각 키링은 struct key의 특수 인스턴스입니다(key_type은 "keyring").
| 키링 | 특수 ID | 수명 | 공유 범위 | 생성 시점 |
|---|---|---|---|---|
| Thread | KEY_SPEC_THREAD_KEYRING (-1) | 스레드(Thread) 종료 시 파괴 | 해당 스레드만 | 첫 접근 시 지연(Latency) 생성 |
| Process | KEY_SPEC_PROCESS_KEYRING (-2) | 프로세스 종료 시 파괴 | 같은 TGID의 모든 스레드 | 첫 접근 시 지연 생성 |
| Session | KEY_SPEC_SESSION_KEYRING (-3) | 세션 종료 시 파괴 | 세션의 모든 프로세스 | PAM 또는 keyctl session |
| User | KEY_SPEC_USER_KEYRING (-4) | UID 네임스페이스(Namespace) 수명 | 같은 UID의 모든 프로세스 | UID 첫 사용 시 |
| User Session | KEY_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_ID | 0 | 특수 키링 ID를 실제 serial로 변환 |
KEYCTL_JOIN_SESSION_KEYRING | 1 | 새 세션 키링 생성 또는 기존 키링에 조인 |
KEYCTL_UPDATE | 2 | 키 페이로드 갱신 |
KEYCTL_REVOKE | 3 | 키 폐기 (사용 불가 처리) |
KEYCTL_DESCRIBE | 6 | 키 메타데이터 조회 (타입, UID, GID, perm) |
KEYCTL_CLEAR | 7 | 키링의 모든 키 제거 |
KEYCTL_LINK | 8 | 키를 키링에 링크 |
KEYCTL_UNLINK | 9 | 키를 키링에서 언링크 |
KEYCTL_SEARCH | 10 | 키링 재귀 검색 |
KEYCTL_READ | 11 | 키 페이로드 읽기 |
KEYCTL_SET_TIMEOUT | 15 | 키 만료 시간 설정 (초 단위) |
KEYCTL_SET_PERM | 5 | 키 접근 권한 설정 |
KEYCTL_INVALIDATE | 21 | 키 즉시 무효화 (GC 대기 없이) |
KEYCTL_RESTRICT_KEYRING | 29 | 키링에 추가 가능한 키 제한 |
KEYCTL_WATCH_KEY | 32 | 키 변경 이벤트 감시 (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)의 인증 토큰 획득에 주로 사용됩니다.
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)가 만료, 폐기, 무효화된 키를 자동으로 정리합니다.
/* 키 상태 (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/maxkeys | 200 | 일반 사용자의 최대 키 개수 |
kernel/keys/maxbytes | 20000 | 일반 사용자의 최대 키 바이트 |
kernel/keys/root_maxkeys | 1000000 | root의 최대 키 개수 |
kernel/keys/root_maxbytes | 25000000 | root의 최대 키 바이트 |
kernel/keys/gc_delay | 300 | GC 실행 지연 시간 (초) |
할당량 초과: 키 생성 시 할당량을 초과하면 -EDQUOT을 반환합니다. 대량의 키를 다루는 서비스(NFS4 등)에서는 KEY_FLAG_IN_QUOTA 플래그를 해제하여 할당량에서 제외하거나, sysctl로 제한을 늘려야 합니다.
키 접근 권한
키 접근 권한은 파일 시스템의 rwx와 유사하지만, 4개의 카테고리에 각 7개의 권한 비트를 사용합니다. 총 30비트(key_perm_t)로 구성됩니다.
| 비트 | 권한 | 설명 |
|---|---|---|
| bit 0 | VIEW | 키 속성 조회 (describe) |
| bit 1 | READ | 키 페이로드 읽기 |
| bit 2 | WRITE | 키 페이로드 갱신, 키링에 링크/언링크 |
| bit 3 | SEARCH | 키링 검색 시 이 키 발견 가능 |
| bit 4 | LINK | 키를 다른 키링에 링크 가능 |
| bit 5 | SETATTR | 속성 변경 (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 | (기본 내장) | 커널 빌드 시 내장된 X.509 인증서 | 불가 (읽기 전용(Read-Only)) |
.secondary_trusted_keys | CONFIG_SECONDARY_TRUSTED_KEYRING | 빌트인 키로 서명된 인증서 추가 가능 | 제한적 추가 |
.machine | CONFIG_INTEGRITY_MACHINE_KEYRING | MOK(Machine Owner Key) 인증서 | UEFI MOK 관리 |
.platform | CONFIG_INTEGRITY_PLATFORM_KEYRING | UEFI Secure Boot db 인증서 | UEFI 설정에서 관리 |
.blacklist | CONFIG_SYSTEM_BLACKLIST_KEYRING | 거부할 인증서/모듈 해시(Hash) | 제한적 추가 |
.ima | CONFIG_IMA_KEYRINGS_PERMIT_SIGNED_BY_BUILTIN_OR_SECONDARY | IMA 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를 통해 사용자 공간에 비동기로 전달합니다. 키의 생성, 갱신, 폐기, 무효화, 만료 이벤트를 감시할 수 있습니다.
/* 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_KEYS | Y | Key Retention Service 핵심 (필수) |
CONFIG_KEYS_REQUEST_CACHE | N | request_key 결과 태스크별 캐시(Cache) |
CONFIG_PERSISTENT_KEYRINGS | N | 영구 키링 (UID 네임스페이스 독립) |
CONFIG_TRUSTED_KEYS | M | TPM/TEE sealed 키 타입 |
CONFIG_TRUSTED_KEYS_TPM | Y | TPM2 기반 trusted 키 |
CONFIG_TRUSTED_KEYS_TEE | N | ARM TrustZone TEE 기반 trusted 키 |
CONFIG_TRUSTED_KEYS_CAAM | N | NXP CAAM 기반 trusted 키 |
CONFIG_ENCRYPTED_KEYS | M | 암호화된 키 타입 |
CONFIG_BIG_KEYS | Y | 대용량 키 타입 (shmem 기반) |
CONFIG_KEY_DH_OPERATIONS | N | Diffie-Hellman 키 교환 |
CONFIG_KEY_NOTIFICATIONS | N | watch_queue 키 알림 |
CONFIG_ASYMMETRIC_KEY_TYPE | Y | 비대칭 키 타입 (X.509/PKCS#7) |
CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE | Y | 공개키 하위 타입 |
CONFIG_X509_CERTIFICATE_PARSER | Y | X.509 인증서 파서 |
CONFIG_PKCS7_MESSAGE_PARSER | Y | PKCS#7 메시지 파서 |
CONFIG_SYSTEM_TRUSTED_KEYRING | Y | .builtin_trusted_keys 키링 |
CONFIG_SECONDARY_TRUSTED_KEYRING | N | .secondary_trusted_keys 키링 |
CONFIG_SYSTEM_BLACKLIST_KEYRING | N | .blacklist 키링 |
CONFIG_INTEGRITY_PLATFORM_KEYRING | N | UEFI .platform 키링 |
CONFIG_INTEGRITY_MACHINE_KEYRING | N | MOK .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/ 디렉토리의 주요 흐름을 추적합니다.
키링 내부의 연관 배열(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_QUOTA | 0x0000 | 할당량 적용 (기본) |
KEY_ALLOC_NOT_IN_QUOTA | 0x0002 | 할당량 면제 (커널 내부 키) |
KEY_ALLOC_BUILT_IN | 0x0004 | 빌트인 키 (부팅 시 생성) |
KEY_ALLOC_BYPASS_RESTRICTION | 0x0008 | 링크 제한 우회 |
KEY_ALLOC_UID_KEYRING | 0x0010 | UID 키링 생성 |
KEY_ALLOC_SET_KEEP | 0x0020 | GC 보호 설정 |
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;
}
| 네트워크 서비스 | 키 타입 | 핸들러 | 용도 |
|---|---|---|---|
| NFS4 | user (id_resolver) | nfsidmap | UID/GID → 이름 매핑 |
| NFS4 Kerberos | rxrpc | gssproxy | Kerberos TGT 캐싱 |
| CIFS/SMB | logon (cifs) | cifscreds | SMB 비밀번호 캐싱 |
| AFS | rxrpc | afs_settoken | AFS 토큰 관리 |
| DNS | dns_resolver | key.dns_resolver | 커널 DNS 해석 |
| kTLS | asymmetric / user | tlshd | TLS 세션 키/인증서 |
TPM 연동
TPM(Trusted Platform Module)과 키링의 연동은 키 보호의 최고 수준을 제공합니다. trusted 키 타입의 seal/unseal 흐름, PCR 바인딩, 정책 기반 봉인을 상세히 분석합니다.
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-users | UID별 키 수/할당량 |
| 세션 키링 트리 | keyctl show @s | 계층적 키링 구조 |
| 빌트인 신뢰 키 | keyctl show %:.builtin_trusted_keys | X.509 인증서 목록 |
| TPM 키 지원 | grep TRUSTED_KEYS /boot/config-$(uname -r) | CONFIG_TRUSTED_KEYS=m |
| 할당량 설정 | sysctl kernel.keys.maxkeys | 200 (기본) |
| keyutils 설치 | keyctl --version | 버전 출력 |
관련 문서
외부 참고 자료
- Kernel Key/Keyring Service — Core Documentation — 커널 키/키링 서비스 핵심 공식 문서입니다
- Trusted and Encrypted Keys — TPM 봉인(Sealed) 키 및 암호화 키 공식 문서입니다
- Kernel Keys — Documentation Index — 커널 키 관리 문서 전체 목록입니다
- keyctl(2) — Linux man page — keyctl 시스템 콜 매뉴얼 페이지입니다
- add_key(2) — Linux man page — 키 추가 시스템 콜 매뉴얼입니다
- request_key(2) — Linux man page — 키 요청 시스템 콜 매뉴얼입니다
- keyrings(7) — Linux man page — 키링 개요 및 사용법 매뉴얼입니다
- Linux 커널 security/keys/ 소스 코드 — 키링 서브시스템 커널 소스입니다
- keyctl(1) — Linux man page — keyctl 사용자 공간 도구 매뉴얼입니다
- Kernel Crypto API — 키 타입에서 사용하는 커널 암호화 프레임워크 문서입니다