TPM 2.0 (Trusted Platform Module)
Trusted Platform Module(TPM) 2.0은 TCG(Trusted Computing Group)가 표준화한 전용 보안 암호화(Encryption) 코프로세서입니다. 4개의 계층 구조(Platform/Storage/Endorsement/Null), PCR 뱅크 기반 Measured Boot, HMAC/Policy 세션을 통한 Enhanced Authorization, NV 인덱스 영구 저장, CRB/TIS 하드웨어 인터페이스, 그리고 Linux 커널 서브시스템(tpm_chip, tpm_space, tpmrm)을 커널 소스 기반으로 심층 분석합니다. EK/SRK/AK 키 계층, Seal/Unseal, 원격 증명(Quote), LUKS2 봉인, dTPM/fTPM/sTPM 비교, vTPM(swtpm/SVSM), tpm2-tools/tpm2-tss/FAPI 활용까지 포괄합니다.
전제 조건: 커널 보안 개요, Secure Boot, 암호화 서브시스템 문서를 먼저 읽으세요. TPM은 이들 기술의 하드웨어 신뢰 기반(Root of Trust)으로 동작하므로, 보안 부팅 체인과 암호화 기본 개념을 이해하면 학습 효율이 크게 높아집니다.
일상 비유: TPM은 은행 금고 시스템과 같습니다. SRK(Storage Root Key)는 마스터 금고 — 내부에 더 작은 금고(하위 키)를 넣을 수 있습니다. EK(Endorsement Key)는 여권 — 이 칩의 신원을 증명하지만 절대 외부로 반출되지 않습니다. PCR은 봉인 테이프 — 한번 붙이면 떼어낼 수 없고, 위에 덧붙이기(extend)만 가능합니다. 누군가 테이프를 교체하면 값이 달라져 즉시 감지됩니다. Seal은 금고에 무언가를 넣되, 봉인 테이프가 특정 패턴일 때만 열리게 하는 것입니다. AK(Attestation Key)는 공증 도장 — PCR 값을 서명하여 외부 검증자에게 "이 시스템은 변조되지 않았다"고 증명합니다.
핵심 요약
- 전용 보안 코프로세서 — TPM은 메인 CPU와 독립된 하드웨어(또는 펌웨어(Firmware))로, 키 생성·서명·암호화·해시(Hash)를 수행하며 비밀 키가 TPM 외부로 노출되지 않습니다.
- 4개 계층 구조 — Platform(펌웨어), Storage(OS/사용자), Endorsement(프라이버시/증명), Null(임시) 계층이 각각 독립된 권한과 시드(seed)를 보유합니다.
- PCR 뱅크 — SHA-1/SHA-256/SHA-384 등 다중 해시 알고리즘별 PCR 뱅크가 부팅 과정(Boot Process)의 각 단계를 측정(extend)하여 Measured Boot 체인을 형성합니다.
- Enhanced Authorization — HMAC 세션과 Policy 세션을 조합하여 "PCR 값이 X이고 비밀번호가 Y이면 허용" 같은 복합 정책을 구성합니다.
- Linux 커널 서브시스템 —
drivers/char/tpm/에 위치하며,tpm_chip구조체(Struct)와/dev/tpmrm0리소스 매니저가 멀티프로세스 접근을 관리합니다. - 실무 활용 — LUKS2 디스크 암호화 봉인, 원격 증명(Remote Attestation), IMA/EVM 무결성(Integrity) 검증, Secure Boot 정책의 하드웨어 신뢰 기반으로 널리 사용됩니다.
단계별 이해
TPM 2.0의 핵심 개념을 은행 금고 비유로 단계별로 이해해 보겠습니다.
1단계: SRK — 마스터 금고
Storage Root Key(SRK)는 TPM 내부의 최상위 저장 키입니다. SRK는 TPM이 처음 프로비저닝될 때 생성되며, 이 키 아래에 모든 하위 키가 계층적으로 파생됩니다. 은행의 마스터 금고처럼, SRK 자체는 절대 외부로 반출되지 않고 TPM 내부에서만 사용됩니다. 하위 키를 "감싸서(wrap)" 외부에 저장할 수 있지만, 감싼 키를 풀려면 반드시 TPM을 거쳐야 합니다.
2단계: EK — 여권(신원 증명)
Endorsement Key(EK)는 TPM 제조사가 공장에서 주입하는 고유 신원 키입니다. 여권이 개인의 정체를 증명하듯, EK 인증서는 "이 TPM은 진짜 TPM 칩이다"를 제3자에게 증명합니다. EK는 절대로 서명 연산에 사용되지 않으며(restricted decrypt 전용), 오직 신원 확인과 AK 인증 프로토콜에만 참여합니다. 이 설계는 프라이버시 보호를 위한 것입니다 — EK로 직접 서명하면 모든 서명이 동일한 키로 추적되기 때문입니다.
3단계: PCR — 봉인 테이프
Platform Configuration Register(PCR)는 시스템 상태를 기록하는 단방향 레지스터(Register)입니다. 봉인 테이프에 비유하면:
- Extend 연산: 기존 테이프 위에 새 테이프를 덧붙이는 것 —
PCR_new = Hash(PCR_old || data) - 되돌리기 불가: 한번 extend된 PCR은 재부팅 없이 원래 값으로 되돌릴 수 없습니다
- 측정 체인: BIOS → 부트로더(Bootloader) → 커널 → initrd → 사용자 공간(User Space), 각 단계가 다음 단계를 PCR에 기록합니다
- 변조 감지: 중간에 악성 코드가 삽입되면 PCR 값이 예상과 달라져 즉시 감지됩니다
4단계: Seal/Unseal — 조건부 금고
Seal은 비밀 데이터를 TPM에 봉인하되, 특정 PCR 값(봉인 테이프 패턴)이 일치할 때만 열리게 하는 것입니다. 예를 들어 LUKS2 디스크 암호화 키를 TPM에 봉인하면서 "PCR 7(Secure Boot 정책)이 정상이고, PCR 11(커널 해시)이 기대값과 같을 때만 키를 해제하라"고 정책을 설정합니다. 시스템이 변조되면 PCR 값이 달라져 키 해제가 거부됩니다.
5단계: AK — 공증 도장
Attestation Key(AK)는 원격 증명(Remote Attestation)에 사용되는 서명 키입니다. AK는 restricted signing 키로, TPM 내부에서 생성된 데이터(PCR Quote 등)에만 서명할 수 있습니다 — 외부 데이터에 자유롭게 서명하는 것이 아닙니다. 이를 통해 원격 검증자는 "이 Quote는 정말 TPM이 생성한 것이지, 소프트웨어가 위조한 것이 아니다"를 확인할 수 있습니다.
6단계: 세션과 정책 — 복합 자물쇠
TPM 2.0의 Enhanced Authorization은 단순한 비밀번호를 넘어 복합 정책을 지원합니다. "비밀번호가 맞고(PolicyPassword) PCR 7이 특정 값이고(PolicyPCR) 로컬리티가 3 이상이면(PolicyLocality) 허용"처럼 AND/OR 논리로 정책을 조합합니다. HMAC 세션은 비밀번호를 평문으로 전송하지 않고 HMAC으로 증명하며, Policy 세션은 정책 다이제스트를 순차적으로 구축하여 최종 인가를 결정합니다.
TPM 2.0 vs 1.2: TPM 1.2는 SHA-1과 RSA만 지원하고, 단일 Owner 계층만 있었습니다. TPM 2.0은 알고리즘 민첩성(SHA-256, ECC, AES 등), 4개 독립 계층, Enhanced Authorization 정책 엔진(Policy Engine)을 도입하여 유연성과 보안성을 크게 향상시켰습니다.
TPM 아키텍처 개요
TPM(Trusted Platform Module)은 TCG(Trusted Computing Group)가 표준화한 보안 코프로세서 사양입니다. 2014년에 공개된 TPM 2.0(Library Specification)은 TPM 1.2의 근본적인 한계를 해결하며 알고리즘 민첩성, 다중 계층 구조, 복합 인가 정책을 도입했습니다.
TPM 1.2 vs TPM 2.0 비교
| 항목 | TPM 1.2 | TPM 2.0 |
|---|---|---|
| 해시 알고리즘 | SHA-1 전용 | SHA-256, SHA-384, SHA-512, SHA-1, SM3 (알고리즘 민첩성) |
| 비대칭 알고리즘 | RSA 2048 전용 | RSA 1024/2048/3072/4096, ECC P-256/P-384/BN-P256, SM2 |
| 대칭 알고리즘 | 없음 | AES-128/192/256 (CFB, CTR), HMAC, XOR |
| 계층 구조 | 단일 Owner | 4개 독립 계층 (Platform, Storage, Endorsement, Null) |
| 키 유형 | 고정 (SRK, AIK 등) | 유연한 키 속성 (restricted/unrestricted, sign/decrypt, fixedTPM 등) |
| 인가 방식 | OIAP/OSAP (단순 세션) | Enhanced Authorization (PolicyPCR, PolicyOR 등 조합) |
| NV 저장소 | 단순 바이트 배열 | 타입별 NV (Ordinary, Counter, Bits, Extend, PIN) |
| PCR 뱅크 | SHA-1 단일 뱅크 24개 | 알고리즘별 복수 뱅크 (SHA-1/SHA-256 각 24개 등) |
| 플랫폼 독립성 | PC 중심 | PC, 모바일, IoT, 자동차, 서버 범용 |
| 사양 버전 | TCG TPM 1.2 rev 116 | TCG TPM Library (Part 1-4), PTP, PC Client |
물리적 인터페이스
| 인터페이스 | 버스(Bus) | 속도 | 대표 칩 | 비고 |
|---|---|---|---|---|
| LPC | Low Pin Count | ~33 MHz | Infineon SLB 9665 | 레거시 PC, TIS 인터페이스 |
| SPI | Serial Peripheral Interface | ~43 MHz | Infineon SLB 9670, Nuvoton NPCT75x | 현재 주류, CRB/TIS 모두 지원 |
| I2C | Inter-Integrated Circuit | ~1 MHz | Infineon SLB 9673, Nuvoton NPCT7xx | 임베디드/IoT, 낮은 핀 수 |
| fTPM | 펌웨어 (TEE 내부) | 메모리 버스 | Intel PTT (CSME), AMD PSP fTPM | 별도 칩 없이 펌웨어로 구현 |
TCG 사양 구조: TPM 2.0 사양은 4개 파트로 구성됩니다. Part 1 (Architecture): 개념과 설계, Part 2 (Structures): 데이터 타입과 구조체, Part 3 (Commands): 명령어 정의, Part 4 (Supporting Routines): 참조 구현 코드. 이외에 PTP(Platform TPM Profile)가 하드웨어 인터페이스를, PC Client가 PC 플랫폼 바인딩을 정의합니다.
TPM 내부 구성요소
TPM 2.0의 내부는 독립적인 기능 블록으로 구성됩니다. 각 블록의 역할을 상세히 살펴보겠습니다.
Crypto Engine
TPM의 핵심 연산 유닛으로, 모든 암호화 연산을 수행합니다. RSA 키 생성은 수백 밀리초에서 수 초가 소요되며, ECC는 상대적으로 빠릅니다. AES는 대칭 암호화에, HMAC과 KDF(Key Derivation Function)는 세션 키 파생과 인가 검증에 사용됩니다. 알고리즘 민첩성(Algorithm Agility) 설계로, TPM 구현체가 지원하는 알고리즘 목록은 TPM2_GetCapability(TPM_ALG_FIRST..TPM_ALG_LAST)로 조회합니다.
Random Number Generator
NIST SP 800-90A 규격의 DRBG(Deterministic Random Bit Generator)를 기반으로 합니다. 하드웨어 엔트로피 소스(noise ring oscillator, thermal noise 등)에서 시드를 추출하고, DRBG가 결정적 확장을 수행합니다. TPM2_GetRandom 명령으로 최대 64바이트의 난수를 요청할 수 있으며, TPM2_StirRandom으로 외부 엔트로피를 주입합니다.
Key Generation
TPM 2.0은 두 가지 KDF를 사용합니다. KDFa는 HMAC 기반 카운터 모드 KDF로, 키 파생·HMAC 세션 키·nonce 생성에 사용됩니다. KDFe는 ECDH 공유 비밀에서 대칭 키를 파생할 때 사용됩니다. Primary Key 생성 시, 계층의 seed와 고정 템플릿을 KDFa에 입력하여 항상 동일한 키를 재생성(re-derivation)할 수 있습니다.
TPM 2.0 명령 구조
모든 TPM 명령은 표준화된 바이트 스트림 형식을 따릅니다:
/* TPM 2.0 Command Header */
typedef struct {
TPMI_ST_COMMAND_TAG tag; /* TPM_ST_SESSIONS or TPM_ST_NO_SESSIONS */
UINT32 commandSize; /* 전체 명령 바이트 크기 */
TPM_CC commandCode; /* 명령 코드 (예: TPM_CC_CreatePrimary = 0x00000131) */
} TPM2_COMMAND_HEADER;
/* TPM 2.0 Response Header */
typedef struct {
TPM_ST tag; /* TPM_ST_SESSIONS or TPM_ST_NO_SESSIONS */
UINT32 responseSize; /* 전체 응답 바이트 크기 */
TPM_RC responseCode; /* TPM_RC_SUCCESS = 0x00000000 */
} TPM2_RESPONSE_HEADER;
/* 전체 명령 구조: [Header][Handles][AuthArea][Parameters] */
/* 전체 응답 구조: [Header][Handles][ParameterSize][Parameters][AuthArea] */
Object 슬롯 제한: TPM은 동시에 로드할 수 있는 transient object 수가 제한됩니다 (일반적으로 3~7개). 이 제한을 커널의 tpmrm(Resource Manager)이 자동으로 context save/load하여 해결합니다.
TPM 계층 구조
TPM 2.0은 4개의 독립적인 계층(Hierarchy)을 도입했습니다. 각 계층은 고유한 인가 값(authValue), 정책(authPolicy), 증명 값(proof), 기본 시드(primarySeed)를 보유합니다.
Platform Hierarchy (PH)
펌웨어/BIOS가 제어하는 계층입니다. 부팅 초기에 platformAuth를 설정하고, OS에 제어를 넘기기 전에 비활성화하는 것이 일반적입니다. Platform Hierarchy만이 PCR을 리셋하고, TPM2_ChangePPS로 Platform Primary Seed를 변경할 수 있습니다. TPMA_NV_PLATFORMCREATE 속성이 설정된 NV 인덱스는 Platform Hierarchy 권한으로만 생성/삭제됩니다.
Endorsement Hierarchy (EH)
프라이버시와 신원 증명에 특화된 계층입니다. EK(Endorsement Key)가 이 계층에서 생성되며, TPM2_Clear 시에도 epSeed가 유지되어 동일한 EK를 재생성할 수 있습니다. 이는 TPM의 제조사 인증서(EK Certificate)와의 일관성을 보장합니다. Endorsement Hierarchy는 TPM2_ChangeEPS로 의도적으로 시드를 변경할 수 있지만, 이 경우 기존 EK 인증서가 무효화(Invalidation)됩니다.
Storage Hierarchy (SH)
OS와 사용자가 일상적으로 사용하는 주 계층입니다. SRK(Storage Root Key)가 이 계층의 Primary Key로 생성되며, 모든 사용자 키, 봉인 데이터, 어플리케이션 키가 SRK 아래에 계층적으로 위치합니다. TPM2_Clear 시 shSeed가 재생성되어 모든 SH 키와 NV 인덱스가 삭제됩니다.
Null Hierarchy (NH)
부팅할 때마다 새로운 nullSeed가 생성되는 임시 계층입니다. 인가 없이 사용 가능하며(nullAuth는 빈 값), 세션에서 임시 키를 생성하거나 ECDH 키 교환에 활용됩니다. 영구 저장이 불가능하므로 재부팅하면 모든 Null Hierarchy 키가 사라집니다.
| 속성 | Platform | Endorsement | Storage | Null |
|---|---|---|---|---|
| Primary Seed 핸들 | 0x4000000C | 0x4000000B | 0x40000001 | 0x40000007 |
| 제어 주체 | BIOS/펌웨어 | 프라이버시 관리자 | OS/사용자 | 누구나 |
| TPM2_Clear 영향 | ppSeed 유지 | epSeed 유지 | shSeed 재생성 | 해당 없음 |
| 영구 객체 저장 | 가능 | 가능 | 가능 | 불가 |
| 대표 키 | Platform 키 | EK | SRK | 임시 키 |
| PCR 리셋 권한 | PCR 0-7 가능 | 없음 | 없음 | 없음 |
키 계층과 키 유형
TPM 2.0의 키는 계층적 트리 구조를 형성합니다. 각 계층의 Primary Seed에서 Primary Key가 파생되고, Primary Key 아래에 자식 키를 생성할 수 있습니다.
EK (Endorsement Key)
EK는 TPM의 신원을 증명하는 유일한 키입니다. 제조사가 공장에서 EK 인증서를 발급하며, TCG EK Credential Profile에 따른 표준 템플릿으로 생성됩니다. EK는 restricted decrypt 속성을 가지므로 서명에는 사용할 수 없고, 오직 TPM2_ActivateCredential 프로토콜에서 AK를 인증하는 데 사용됩니다.
SRK (Storage Root Key)
SRK는 Storage Hierarchy의 Primary Key로, 모든 사용자 키의 최상위 부모 역할을 합니다. restricted decrypt 속성을 가지며, 자식 키를 감싸는(wrap) 용도로 사용됩니다. TCG에서 정의한 표준 SRK 템플릿(RSA-2048 또는 ECC P-256)을 사용하면, 동일한 shSeed에서 항상 동일한 SRK가 재생성됩니다.
AK (Attestation Key)
AK는 원격 증명(Remote Attestation)에 사용되는 restricted signing 키입니다. restricted 속성으로 인해 TPM 내부에서 생성된 데이터(Quote, Certify 결과 등)에만 서명할 수 있으며, 임의의 외부 데이터에 서명할 수 없습니다. 이 제한이 Quote의 무결성을 보장합니다.
키 생성 코드
/* SRK 생성을 위한 Primary Key 템플릿 (TCG EK Credential Profile) */
TPM2B_PUBLIC srk_template = {
.publicArea = {
.type = TPM2_ALG_RSA,
.nameAlg = TPM2_ALG_SHA256,
.objectAttributes = (
TPMA_OBJECT_RESTRICTED |
TPMA_OBJECT_DECRYPT |
TPMA_OBJECT_FIXEDTPM |
TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_SENSITIVEDATAORIGIN |
TPMA_OBJECT_USERWITHAUTH |
TPMA_OBJECT_NODA
),
.parameters.rsaDetail = {
.symmetric = {
.algorithm = TPM2_ALG_AES,
.keyBits = { .aes = 128 },
.mode = { .aes = TPM2_ALG_CFB },
},
.scheme = { .scheme = TPM2_ALG_NULL },
.keyBits = 2048,
.exponent = 0, /* 기본값 65537 */
},
},
};
/* TPM2_CreatePrimary 호출 */
TSS2_RC rc = Esys_CreatePrimary(
esys_ctx,
ESYS_TR_RH_OWNER, /* Storage Hierarchy */
ESYS_TR_PASSWORD, /* ownerAuth 세션 */
ESYS_TR_NONE, ESYS_TR_NONE,
&in_sensitive,
&srk_template,
&outside_info,
&creation_pcr,
&srk_handle, /* 생성된 SRK 핸들 */
&out_public,
&creation_data,
&creation_hash,
&creation_ticket
);
| 키 유형 | 계층 | restricted | sign/decrypt | 용도 |
|---|---|---|---|---|
| EK | Endorsement | Yes | decrypt | TPM 신원 증명, AK 인증 |
| SRK | Storage | Yes | decrypt | 자식 키 래핑, 키 트리 루트 |
| AK | Endorsement/Storage | Yes | sign | Quote, Certify (원격 증명) |
| Storage Key | Storage | Yes | decrypt | 중간 래핑 키 |
| Signing Key | Storage | No | sign | 범용 서명 (코드 서명 등) |
| Sealed Data | Storage | - | - | 비밀 데이터 봉인 |
PCR 상세
Platform Configuration Register(PCR)는 TPM의 핵심 보안 메커니즘으로, 시스템 상태를 암호학적으로 기록합니다. PCR은 extend-only 레지스터로, 현재 값에 새 데이터를 해시하여 덧붙이는 것만 가능합니다.
PCR Extend 연산
PCR Extend는 다음과 같은 단방향 연산입니다:
PCR_new = Hash(PCR_old || data)
예시 (SHA-256, PCR 초기값 = 0x0000...0000):
PCR_0 = SHA-256(0x0000...0000 || measurement_1)
= 0x3a7f...8b2e
PCR_0 = SHA-256(0x3a7f...8b2e || measurement_2)
= 0x91c4...d017
...
이 연산의 핵심 속성은 비가역성입니다. PCR 값을 이전 상태로 되돌리는 것이 계산적으로 불가능하므로, 측정 체인의 무결성이 보장됩니다. PCR은 TPM2_Startup(CLEAR) 시 0으로 초기화되며, TPM2_Startup(STATE)(절전 복귀)에서는 이전 값을 유지합니다.
PCR 뱅크
TPM 2.0의 알고리즘 민첩성에 따라, 각 해시 알고리즘별로 독립적인 PCR 뱅크가 존재합니다. TPM2_PCR_Extend 명령은 지정된 뱅크의 PCR만 갱신하며, TPM2_PCR_Event는 모든 활성 뱅크를 동시에 갱신합니다.
| 뱅크 | 다이제스트 크기 | 상태 | 비고 |
|---|---|---|---|
| SHA-1 | 20 바이트 | 레거시 호환 | NIST 권장 중단, 그러나 하위 호환성 유지 |
| SHA-256 | 32 바이트 | 기본 뱅크 | 현재 주류, FIPS 140-3 준수 |
| SHA-384 | 48 바이트 | 고보안 | 정부/군사 요구사항 |
| SHA-512 | 64 바이트 | 선택적 | 일부 TPM에서 지원 |
| SM3-256 | 32 바이트 | 중국 표준 | 중국 시장 규격 준수 |
TCG PC Client PCR 할당 표준
| PCR | 측정 대상 | Extend 주체 | 설명 |
|---|---|---|---|
| 0 | SRTM, BIOS, Host Platform | BIOS/UEFI | Core Root of Trust for Measurement 코드 |
| 1 | Host Platform Configuration | BIOS/UEFI | BIOS 설정, EFI 변수 |
| 2 | Option ROM Code | BIOS/UEFI | 확장 ROM 실행 코드 (네트워크, 스토리지) |
| 3 | Option ROM Configuration | BIOS/UEFI | 확장 ROM 설정 데이터 |
| 4 | IPL Code (MBR/부트로더) | BIOS/UEFI | 부트 매니저, GRUB, systemd-boot 코드 |
| 5 | IPL Configuration | BIOS/UEFI | 부트 매니저 설정, GPT 테이블 |
| 6 | State Transitions | BIOS/UEFI | Wake 이벤트, 전원 상태 전환 |
| 7 | Secure Boot Policy | BIOS/UEFI | SecureBoot 변수: PK, KEK, db, dbx, MokList |
| 8-15 | OS 정의 | 부트로더/OS | GRUB: 커널/initrd(8,9), IMA(10), systemd-boot(11,12) |
| 16 | Debug | 누구나 | 테스트용, 리셋 가능 (Platform Hierarchy) |
| 17-22 | DRTM | DRTM 에이전트 | Intel TXT / AMD SKINIT 전용 |
| 23 | Application Support | 어플리케이션 | 사용자 어플리케이션 용도 |
Locality 개념
PCR 접근은 Locality(지역성)에 의해 제어됩니다. Locality는 TPM에 명령을 보내는 소프트웨어의 신뢰 수준을 나타냅니다:
| Locality | 접근 주체 | PCR Extend 범위 | PCR Reset 범위 |
|---|---|---|---|
| 0 | 일반 OS/사용자 공간 | PCR 0-15, 23 | PCR 16, 23 |
| 1 | OS 특권 모드 | PCR 0-15, 20, 23 | PCR 16, 20, 23 |
| 2 | 시스템 소프트웨어 (GRUB 등) | PCR 0-15, 20, 23 | PCR 16, 20, 23 |
| 3 | DRTM (TXT ACM) | PCR 0-23 | PCR 16-23 |
| 4 | Intel TXT 환경 | PCR 17-18 | PCR 17-18 |
tpm2-tools PCR 조작
# PCR 값 읽기 (SHA-256 뱅크, 모든 PCR)
tpm2_pcrread sha256
# 특정 PCR만 읽기 (SHA-256 뱅크, PCR 0, 7, 11)
tpm2_pcrread sha256:0,7,11
# PCR Extend: PCR 23에 파일 해시를 기록
tpm2_pcrextend 23:sha256=$(sha256sum /boot/vmlinuz | cut -d' ' -f1)
# PCR Event: 파일 내용을 모든 활성 뱅크의 PCR 23에 동시 기록
tpm2_pcrevent 23 /boot/vmlinuz
# PCR Reset: PCR 16 초기화 (디버그용)
tpm2_pcrreset 16
# PCR 할당 정보 조회
tpm2_getcap pcrs
PCR Fragility: PCR에 직접 바인딩된 Seal 정책은 커널, initrd, 부트로더 업데이트 시 PCR 값이 변경되어 키 해제가 실패합니다. 이를 PCR Fragility 문제라 하며, systemd-cryptenroll의 signed PCR policy(--tpm2-public-key-pcrs)가 이 문제를 해결합니다. 자세한 내용은 TPM 기반 디스크 암호화 섹션을 참고하세요.
TPM 2.0 세션
TPM 2.0의 세션은 명령 인가와 파라미터 보호의 핵심 메커니즘입니다. 모든 인가가 필요한 명령은 세션을 통해 수행되며, 세션은 최대 3개까지 동시에 하나의 명령에 첨부할 수 있습니다.
HMAC 세션
HMAC 세션은 authValue(비밀번호)를 평문으로 전송하지 않고 HMAC으로 증명하는 방식입니다. 세션 키는 KDFa(sessionKey = KDFa(hashAlg, sessionSecret, "ATH", nonceTPM, nonceCaller, bits))로 파생되며, 이 키로 명령 파라미터의 HMAC을 계산합니다. decrypt/encrypt 속성을 설정하면 명령 파라미터와 응답 파라미터를 각각 AES-CFB로 암호화하여 버스 스니핑 공격을 방어합니다.
Policy 세션
Policy 세션은 Enhanced Authorization의 핵심입니다. 세션을 시작한 후 여러 Policy 명령(TPM2_PolicyPCR, TPM2_PolicyPassword 등)을 순차적으로 호출하면, 각 명령이 세션의 policyDigest를 갱신합니다. 최종 policyDigest가 대상 객체의 authPolicy와 일치하면 인가가 승인됩니다.
Trial Policy 세션
Trial 세션은 TPM이 실제로 정책을 검증하지 않고 policyDigest 값만 계산합니다. 이 값을 객체 생성 시 authPolicy로 설정하는 데 사용됩니다. 즉, "이 정책을 만족해야 이 키를 사용할 수 있다"는 조건을 객체에 바인딩하는 과정입니다.
# HMAC 세션 시작 (salted, unbound)
tpm2_startauthsession --policy-session -S session.ctx
# Policy 세션으로 PCR 정책 설정
tpm2_policypcr -S session.ctx -l sha256:0,7
# Trial 세션으로 policyDigest 계산 (객체 생성 전)
tpm2_startauthsession --trial-session -S trial.ctx
tpm2_policypcr -S trial.ctx -l sha256:0,7
tpm2_policygetdigest -S trial.ctx -o policy.digest
tpm2_flushcontext trial.ctx
Enhanced Authorization 정책
Enhanced Authorization(EA)은 TPM 2.0의 가장 강력한 기능 중 하나로, 단순 비밀번호를 넘어 다양한 조건을 논리적으로 조합하여 인가 정책을 구성합니다.
정책 구성 원리
정책은 policyDigest라는 해시 값으로 표현됩니다. 각 Policy 명령은 현재 세션의 policyDigest를 갱신합니다:
policyDigest = Hash(policyDigest_old || TPM_CC_PolicyPCR || pcrs || pcrDigest)
policyDigest = Hash(policyDigest_old || TPM_CC_PolicyPassword)
...
최종 policyDigest == 객체의 authPolicy → 인가 승인
여러 Policy 명령을 순차적으로 호출하면 AND 논리가 됩니다 (모든 조건 충족 필요). PolicyOR는 여러 브랜치의 OR 논리를 제공하여, 브랜치 중 하나만 충족하면 됩니다.
복합 정책 예시: PCR + 비밀번호 OR 관리자 키
# 1단계: Trial 세션으로 Branch A의 digest 계산
tpm2_startauthsession --trial-session -S trial_a.ctx
tpm2_policypcr -S trial_a.ctx -l sha256:7
tpm2_policypassword -S trial_a.ctx
tpm2_policygetdigest -S trial_a.ctx -o branch_a.digest
tpm2_flushcontext trial_a.ctx
# 2단계: Trial 세션으로 Branch B의 digest 계산
tpm2_startauthsession --trial-session -S trial_b.ctx
tpm2_policysecret -S trial_b.ctx -c admin_key.ctx
tpm2_policygetdigest -S trial_b.ctx -o branch_b.digest
tpm2_flushcontext trial_b.ctx
# 3단계: Trial 세션으로 PolicyOR의 최종 digest 계산
tpm2_startauthsession --trial-session -S trial_or.ctx
tpm2_policyor -S trial_or.ctx -l sha256:branch_a.digest,branch_b.digest
tpm2_policygetdigest -S trial_or.ctx -o final_policy.digest
tpm2_flushcontext trial_or.ctx
# 4단계: 최종 정책으로 키 생성
tpm2_create -C srk.ctx -L final_policy.digest \
-u key.pub -r key.priv -a "sign|fixedtpm|fixedparent|sensitivedataorigin"
# 5단계: 사용 시 Branch A로 인가 (PCR + 비밀번호)
tpm2_startauthsession --policy-session -S session.ctx
tpm2_policypcr -S session.ctx -l sha256:7
tpm2_policypassword -S session.ctx
tpm2_policyor -S session.ctx -l sha256:branch_a.digest,branch_b.digest
tpm2_sign -c key.ctx -p session:session.ctx+mypassword -o sig.out msg.dat
PolicyAuthorize — 정책의 정책: PolicyAuthorize는 외부 서명 키가 새로운 정책을 서명하면, 객체를 재생성하지 않고도 정책을 갱신할 수 있습니다. 이는 PCR 값이 커널 업데이트 등으로 변경될 때 특히 유용하며, systemd-cryptenroll의 signed PCR policy가 이 메커니즘을 활용합니다.
NV 인덱스
TPM의 NV(Non-Volatile) 저장소는 재부팅 후에도 유지되는 영구 데이터를 저장합니다. NV 인덱스는 단순한 바이트 배열이 아니라, 타입별로 특화된 동작을 지원합니다.
NV 인덱스 타입
| 타입 | 동작 | 용도 |
|---|---|---|
| Ordinary | 임의 읽기/쓰기 | 인증서 저장, 구성 데이터 |
| Counter | 단조 증가 전용 (감소 불가) | 롤백(Rollback) 방지, 부팅 횟수 기록 |
| Bits | 개별 비트 설정 전용 (해제 불가) | 일회성 플래그, 기능 활성화 |
| Extend | PCR과 동일한 extend 연산 | 감사 로그, 이벤트 누적 해시 |
| PIN Pass | 실패 카운터 (성공 시 리셋) | 비밀번호 시도 제한 |
| PIN Fail | 실패 카운터 (리셋 불가) | 영구 잠금(Lock) 임계값 |
NV 속성 (TPMA_NV)
| 속성 | 비트 | 설명 |
|---|---|---|
PPWRITE | 0 | Platform Hierarchy로 쓰기 가능 |
OWNERWRITE | 1 | Owner(Storage) Hierarchy로 쓰기 가능 |
AUTHWRITE | 2 | NV 인덱스 자체 authValue로 쓰기 가능 |
POLICYWRITE | 3 | NV 인덱스 authPolicy로 쓰기 가능 |
PPREAD | 16 | Platform Hierarchy로 읽기 가능 |
OWNERREAD | 17 | Owner Hierarchy로 읽기 가능 |
AUTHREAD | 18 | NV 인덱스 자체 authValue로 읽기 가능 |
POLICYREAD | 19 | NV 인덱스 authPolicy로 읽기 가능 |
WRITTEN | 29 | 한 번 이상 기록됨 (읽기 전용(Read-Only)) |
WRITEDEFINE | 13 | Write Lock 후 TPM2_Clear까지 쓰기 잠금 |
WRITE_STCLEAR | 14 | Write Lock 후 재부팅까지 쓰기 잠금 |
GLOBALLOCK | 5 | TPM2_NV_GlobalWriteLock으로 일괄 잠금 |
READ_STCLEAR | 12 | Read Lock 후 재부팅까지 읽기 잠금 |
PLATFORMCREATE | 30 | Platform Hierarchy가 생성 (Clear에 영향 안 받음) |
NV 인덱스 핸들 범위
| 범위 | 용도 | 예시 |
|---|---|---|
0x01000000 ~ 0x013FFFFF | Owner 정의 | 사용자 데이터 |
0x01400000 ~ 0x017FFFFF | Owner 정의 (TPM_NT 활용) | 카운터, extend 등 |
0x01800000 ~ 0x01BFFFFF | Platform 정의 | 펌웨어 구성 |
0x01C00000 ~ 0x01FFFFFF | TCG/제조사 정의 | EK 인증서 (0x01C00002) |
# NV 인덱스 정의: 32바이트, Owner 읽기/쓰기, 인증 필요
tpm2_nvdefine 0x01000001 -s 32 -a "ownerread|ownerwrite|authread|authwrite" -p mysecret
# NV 인덱스에 데이터 쓰기
echo -n "Hello TPM NV Storage!" | tpm2_nvwrite 0x01000001 -i - -P mysecret
# NV 인덱스에서 데이터 읽기
tpm2_nvread 0x01000001 -P mysecret
# 카운터형 NV 인덱스 정의 및 증가
tpm2_nvdefine 0x01500001 -s 8 -a "ownerread|ownerwrite|nt=counter"
tpm2_nvincrement 0x01500001
# NV 인덱스 삭제
tpm2_nvundefine 0x01000001
# 모든 NV 인덱스 목록 조회
tpm2_getcap handles-nv-index
EK 인증서 NV 인덱스: 대부분의 TPM은 NV 인덱스 0x01C00002(RSA EK 인증서)와 0x01C0000A(ECC EK 인증서)에 제조사가 사전 프로비저닝한 EK 인증서를 저장합니다. tpm2_getekcertificate 또는 tpm2_nvread 0x01C00002로 추출할 수 있습니다.
TPM 2.0 핵심 명령어
TPM 2.0은 약 130여 개의 명령을 정의합니다. 여기서는 실무에서 가장 빈번하게 사용되는 핵심 명령을 범주별로 정리합니다.
시작/종료
| 명령 | 코드 | 설명 |
|---|---|---|
TPM2_Startup | 0x0144 | TPM 초기화. CLEAR(리셋) 또는 STATE(절전 복귀) |
TPM2_Shutdown | 0x0145 | TPM 종료 준비. STATE(절전) 또는 CLEAR(재부팅) |
TPM2_SelfTest | 0x0143 | 내부 자가 테스트 실행 |
키 관리
| 명령 | 코드 | 설명 |
|---|---|---|
TPM2_CreatePrimary | 0x0131 | 계층의 Primary Seed에서 Primary Key 생성 (EK, SRK 등) |
TPM2_Create | 0x0153 | 부모 키 아래에 자식 키/봉인 데이터 생성 |
TPM2_Load | 0x0157 | 생성된 키를 TPM 메모리에 로드 (transient handle 획득) |
TPM2_LoadExternal | 0x0167 | 외부 키를 TPM에 로드 (서명 검증(Signature Verification) 등) |
TPM2_EvictControl | 0x0120 | transient 키를 persistent 핸들에 영구 저장 |
TPM2_FlushContext | 0x0165 | transient 객체/세션 메모리 해제 |
TPM2_ContextSave | 0x0162 | 객체 상태를 외부로 내보내기 (컨텍스트 스왑(Swap)) |
TPM2_ContextLoad | 0x0161 | 내보낸 객체 상태를 복원 |
암호 연산
| 명령 | 코드 | 설명 |
|---|---|---|
TPM2_Sign | 0x015D | TPM 키로 외부 데이터에 서명 |
TPM2_VerifySignature | 0x0177 | 로드된 키로 서명 검증 |
TPM2_RSA_Encrypt | 0x0174 | RSA 공개키 암호화 (OAEP, PKCS1_v1_5) |
TPM2_RSA_Decrypt | 0x0159 | RSA 개인키 복호화(Decryption) |
TPM2_ECDH_KeyGen | 0x0163 | ECDH 키 쌍 생성, Z 포인트 반환 |
TPM2_ECDH_ZGen | 0x0154 | ECDH 공유 비밀 Z 계산 |
TPM2_Hash | 0x017B | TPM 내부에서 해시 연산 |
TPM2_HMAC | 0x0155 | TPM 키로 HMAC 연산 |
봉인/해제 및 증명
| 명령 | 코드 | 설명 |
|---|---|---|
TPM2_Create (seal) | 0x0153 | sensitive 영역에 비밀 데이터를 포함하여 봉인 객체 생성 |
TPM2_Unseal | 0x015E | 봉인 객체에서 비밀 데이터 추출 (인가 필요) |
TPM2_Quote | 0x0158 | PCR 값에 AK로 서명 (원격 증명용) |
TPM2_Certify | 0x0148 | 객체의 공개 영역에 서명 (키 증명) |
TPM2_MakeCredential | 0x0147 | EK로 credential 래핑 (AK 인증 프로토콜) |
TPM2_ActivateCredential | 0x0147 | 래핑된 credential 풀기 (AK가 이 EK의 TPM에 있음을 증명) |
PCR / 난수 / NV / 기능 조회
| 명령 | 코드 | 설명 |
|---|---|---|
TPM2_PCR_Read | 0x017E | PCR 값 읽기 |
TPM2_PCR_Extend | 0x0182 | PCR에 다이제스트 extend |
TPM2_PCR_Event | 0x013C | PCR에 이벤트 데이터 해시+extend (모든 뱅크) |
TPM2_PCR_Reset | 0x013D | PCR 초기화 (허용된 PCR만) |
TPM2_GetRandom | 0x017B | 하드웨어 난수 바이트 획득 |
TPM2_StirRandom | 0x0146 | 외부 엔트로피를 RNG에 주입 |
TPM2_NV_DefineSpace | 0x012A | NV 인덱스 정의 |
TPM2_NV_Write | 0x0137 | NV 인덱스에 쓰기 |
TPM2_NV_Read | 0x014E | NV 인덱스에서 읽기 |
TPM2_GetCapability | 0x017A | TPM 기능/속성 조회 |
TPM2_TestParms | 0x018A | 알고리즘/파라미터 지원 여부 확인 |
CreatePrimary → Create → Load → Sign 흐름
# 1. SRK 생성 (Storage Hierarchy Primary Key)
tpm2_createprimary -C o -G rsa2048 -g sha256 -c srk.ctx
# 2. SRK 아래에 서명 키 생성
tpm2_create -C srk.ctx -G rsa2048 -g sha256 \
-u sign_key.pub -r sign_key.priv \
-a "sign|fixedtpm|fixedparent|sensitivedataorigin|userwithauth" \
-p keypass123
# 3. 서명 키를 TPM에 로드
tpm2_load -C srk.ctx -u sign_key.pub -r sign_key.priv -c sign_key.ctx
# 4. 서명 수행
echo "Hello TPM" > message.dat
tpm2_sign -c sign_key.ctx -g sha256 -o signature.dat -p keypass123 message.dat
# 5. 서명 검증
tpm2_verifysignature -c sign_key.ctx -g sha256 -s signature.dat -m message.dat
# 6. 키를 persistent 핸들에 영구 저장 (선택)
tpm2_evictcontrol -C o -c sign_key.ctx 0x81010001
# 이후부터는 0x81010001 핸들로 직접 사용 가능
tpm2_sign -c 0x81010001 -g sha256 -o sig2.dat -p keypass123 message.dat
리눅스 커널 TPM 서브시스템
Linux 커널의 TPM 서브시스템은 drivers/char/tpm/에 위치하며, 하드웨어 추상화 레이어를 통해 다양한 TPM 칩과 인터페이스를 지원합니다.
핵심 자료 구조
/* drivers/char/tpm/tpm.h */
struct tpm_chip {
struct device dev;
struct device devs; /* /dev/tpm0 */
struct cdev cdev;
struct cdev cdevs;
struct rw_semaphore ops_sem; /* ops 접근 보호 */
const struct tpm_class_ops *ops; /* HW-specific 연산 */
struct tpm_space work_space; /* tpmrm용 작업 공간 */
unsigned long timeout_a; /* TPM_TIMEOUT_A (ms) */
unsigned long timeout_b;
unsigned long timeout_c;
unsigned long timeout_d;
unsigned long duration[4]; /* 명령 유형별 최대 실행 시간 */
unsigned int flags; /* TPM_CHIP_FLAG_* */
int dev_num; /* tpm0, tpm1, ... */
struct mutex tpm_mutex; /* 직렬화 */
struct tpm_bank_info *allocated_banks;
int nr_allocated_banks;
int locality;
};
/* 하드웨어별 연산 콜백 */
struct tpm_class_ops {
unsigned int flags;
int (*recv)(struct tpm_chip *chip, u8 *buf, size_t len);
int (*send)(struct tpm_chip *chip, u8 *buf, size_t len);
void (*cancel)(struct tpm_chip *chip);
u8 (*status)(struct tpm_chip *chip);
bool (*update_timeouts)(struct tpm_chip *chip, unsigned long *timeout_cap);
int (*request_locality)(struct tpm_chip *chip, int loc);
int (*relinquish_locality)(struct tpm_chip *chip, int loc);
u8 req_complete_mask;
u8 req_complete_val;
u8 req_canceled;
};
/dev/tpm0 vs /dev/tpmrm0
| 장치 | 리소스 관리 | 멀티프로세스 | 용도 |
|---|---|---|---|
/dev/tpm0 | 없음 (raw 접근) | 하나의 프로세스(Process)만 독점 | 레거시, tpm2-abrmd 백엔드 |
/dev/tpmrm0 | 커널 내장 Resource Manager | 여러 프로세스 동시 접근 | 기본 권장, context 자동 관리 |
/dev/tpmrm0는 커널 6.x에서 기본 접근 경로입니다. 각 프로세스가 open()하면 독립적인 tpm_space가 할당되어, transient 객체와 세션의 context save/load를 커널이 자동으로 관리합니다. 이를 통해 프로세스 A의 키 핸들이 프로세스 B에 의해 flush되는 문제가 방지됩니다.
tpm_transmit 흐름
/* drivers/char/tpm/tpm-interface.c */
ssize_t tpm_transmit(struct tpm_chip *chip, struct tpm_space *space,
u8 *buf, size_t bufsiz, unsigned int flags)
{
int rc;
/* 1. 직렬화: 한 번에 하나의 명령만 */
rc = tpm_try_get_ops(chip); /* down_read(&chip->ops_sem) + chip->locality request */
/* 2. tpm_space: 이전 세션 context 복원 */
if (space)
rc = tpm2_prepare_space(chip, space, buf, bufsiz);
/* 3. 명령 전송 */
rc = chip->ops->send(chip, buf, count);
/* 4. 완료 대기 (polling 또는 IRQ) */
tpm_cmd_ready(chip, status);
/* 5. 응답 수신 */
len = chip->ops->recv(chip, buf, bufsiz);
/* 6. tpm_space: 세션 context 저장 */
if (space)
rc = tpm2_commit_space(chip, space, buf, &len);
/* 7. 직렬화 해제 */
tpm_put_ops(chip); /* relinquish_locality + up_read */
return len;
}
커널 내부 TPM 사용: 커널 자체도 TPM을 사용합니다 — IMA의 PCR extend, trusted key 타입의 seal/unseal, 하드웨어 RNG 등. 이들은 tpm_chip_find_get()으로 칩을 찾고 tpm_send()/tpm2_get_random() 등의 내부 API를 호출합니다. 자세한 내용은 키링(Keyring) 문서의 trusted 키 타입을 참고하세요.
sysfs 인터페이스
커널은 TPM 칩 정보를 sysfs를 통해 사용자 공간에 노출합니다. 주요 디렉토리는 /sys/class/tpm/tpm0/와 /sys/kernel/security/tpm0/입니다:
| sysfs 경로 | 내용 | 설명 |
|---|---|---|
/sys/class/tpm/tpm0/tpm_version_major | 2 | TPM 스펙 주 버전 (1 또는 2) |
/sys/class/tpm/tpm0/device/description | TPM 2.0 Device | TPM 장치 설명 문자열 |
/sys/class/tpm/tpm0/caps | 제조사, 버전 | TPM 1.2 호환용 기능 정보 |
/sys/class/tpm/tpm0/durations | short/med/long ms | 명령 유형별 최대 실행 시간 |
/sys/class/tpm/tpm0/timeouts | A/B/C/D ms | 통신 타임아웃 값 4종 |
/sys/class/tpm/tpm0/pcrs | PCR 값 목록 | TPM 1.2 전용, 2.0은 tpm2_pcrread 사용 |
# TPM 버전 확인
cat /sys/class/tpm/tpm0/tpm_version_major
# 출력: 2
# TPM 디바이스 설명
cat /sys/class/tpm/tpm0/device/description
# 출력: TPM 2.0 Device
# 타임아웃/duration 값 확인 (ms 단위)
cat /sys/class/tpm/tpm0/timeouts
# 출력: 750 2000 200 30 [original]
cat /sys/class/tpm/tpm0/durations
# 출력: 200000 400000 2000000 [original]
# securityfs를 통한 이벤트 로그 접근
ls -la /sys/kernel/security/tpm0/
# binary_bios_measurements — 바이너리 TCG 이벤트 로그
# ascii_bios_measurements — 텍스트 형식 이벤트 로그 (레거시)
TPM 이벤트 로그 파싱
커널은 부팅 과정에서 ACPI, EFI, 또는 Device Tree 경로를 통해 펌웨어가 작성한 이벤트 로그를 읽어 securityfs에 노출합니다. 이벤트 로그 소스를 탐색하는 우선순위(Priority)는 다음과 같습니다:
tpm_read_log_efi()— EFI 변수EventLogAddr/EventLogSize에서 로그 주소와 크기를 읽음 (UEFI 시스템 우선)tpm_read_log_acpi()— ACPITCPA/TPM2테이블의 로그 영역에서 읽음tpm_read_log_of()— Device Tree의linux,sml-base/linux,sml-size프로퍼티에서 읽음 (ARM/PowerPC)
/* drivers/char/tpm/eventlog/common.c — TCG Crypto Agile 이벤트 구조 */
struct tcg_pcr_event2_head {
u32 pcr_idx; /* PCR 인덱스 (0-23) */
u32 event_type; /* EV_* 상수 */
u32 count; /* 해시 알고리즘 개수 */
/* 이후 count × { alg_id(u16) + digest(가변) } */
/* 마지막: event_size(u32) + event_data(가변) */
};
/* SHA-256 Crypto Agile 로그 예시 (count=2인 경우) */
/* pcr_idx=7 | event_type=EV_EFI_VARIABLE_DRIVER_CONFIG
* | count=2
* | alg_id=0x0004 (SHA-1) | digest[20]
* | alg_id=0x000B (SHA-256) | digest[32]
* | event_size | event_data (EFI 변수 이름 + 값)
*/
주요 이벤트 타입
| 이벤트 타입 | 값 | PCR | 설명 |
|---|---|---|---|
EV_S_CRTM_VERSION | 0x00000008 | 0 | CRTM 버전 (펌웨어 초기 코드) |
EV_POST_CODE | 0x00000001 | 0-1 | POST 코드/BIOS 모듈 |
EV_SEPARATOR | 0x00000004 | 0-7 | 부팅 단계 경계 (Pre-OS → OS 전환) |
EV_EFI_VARIABLE_DRIVER_CONFIG | 0x80000001 | 1,3,5,7 | EFI 변수 (SecureBoot, PK, KEK, db, dbx) |
EV_EFI_BOOT_SERVICES_APPLICATION | 0x80000003 | 4 | EFI 부트 서비스 애플리케이션 (shim, GRUB) |
EV_EFI_ACTION | 0x80000007 | 1,2,3,4,5,6 | EFI 액션 이벤트 문자열 |
EV_EFI_VARIABLE_AUTHORITY | 0x800000E0 | 7 | Secure Boot 검증에 사용된 인증서/해시 |
EV_EFI_GPT_EVENT | 0x80000006 | 5 | GPT 파티션 테이블 해시 |
EV_IPL | 0x0000000D | 4,8,9 | 부트로더/커널/initrd 측정 |
EV_EFI_HCRTM_EVENT | 0x80000010 | 0 | 호스트 플랫폼 CRTM |
TPM 칩 드라이버 등록(Driver Registration) 흐름
TPM 하드웨어 드라이버(tpm_tis, tpm_crb 등)가 프로브(Probe)되면 다음과 같은 등록 흐름을 거칩니다:
/* TPM 칩 등록 흐름 (drivers/char/tpm/tpm-chip.c) */
/* 1단계: 칩 구조체 할당 */
chip = tpmm_chip_alloc(dev, ops);
/* - devm_kzalloc으로 tpm_chip 할당
* - dev_num 할당 (ida_alloc)
* - mutex, ops_sem 초기화
* - cdev 초기화 */
/* 2단계: TPM 2.0 프로브 */
rc = tpm2_probe(chip);
/* - TPM2_GetCapability(TPM_PT_TOTAL_COMMANDS) 전송
* - 응답 성공 → TPM_CHIP_FLAG_TPM2 설정
* - 응답 실패 → TPM 1.2로 취급 */
/* 3단계: 자동 시작 */
rc = tpm2_auto_startup(chip);
/* - tpm2_startup(TPM2_SU_CLEAR) — TPM 초기화
* - tpm2_do_selftest() — 자체 테스트
* - tpm2_get_pcr_allocation() — 활성 PCR 뱅크 조회
* - tpm_get_timeouts() — 타임아웃 값 획득 */
/* 4단계: 칩 등록 */
rc = tpm_chip_register(chip);
/* - tpm_add_char_device() — /dev/tpm0 + /dev/tpmrm0 생성
* - tpm_add_hwrng() — hwrng 프레임워크에 TPM RNG 등록
* - tpm_bios_log_setup() — securityfs 이벤트 로그 노출
* - tpm_add_ppi() — Physical Presence Interface sysfs */
커널 내부 TPM API
커널의 다른 서브시스템이 TPM 기능을 사용할 때 호출하는 주요 내부 API입니다:
| 함수 | 용도 | 호출자 |
|---|---|---|
tpm_chip_find_get() | 기본 TPM 칩 참조 획득 | IMA, trusted keys, hwrng |
tpm_pcr_read() | PCR 값 읽기 | IMA (boot_aggregate 계산) |
tpm_pcr_extend() | PCR extend 연산 | IMA (파일 측정), 커널 부팅 |
tpm_get_random() | TPM RNG에서 난수 획득 | hwrng 프레임워크 |
tpm_send() | 임의 TPM 명령 전송 | trusted keys (seal/unseal) |
tpm_is_tpm2() | TPM 2.0 여부 확인 | 여러 서브시스템 |
tpm_chip_put() | TPM 칩 참조 해제 | 사용 완료 후 |
/* IMA에서의 TPM 사용 예시 (security/integrity/ima/ima_init.c) */
static int ima_init_tpm(void)
{
/* 기본 TPM 칩 찾기 */
ima_tpm_chip = tpm_default_chip();
if (!ima_tpm_chip)
pr_info("No TPM chip found, activating TPM-bypass!\n");
return 0;
}
/* boot_aggregate 계산 — PCR 0-7 읽어서 해시 */
static int ima_calc_boot_aggregate(struct ima_digest_data *hash)
{
struct tpm_digest d;
int i, rc;
for (i = 0; i <= 7; i++) {
rc = tpm_pcr_read(ima_tpm_chip, i, &d);
/* PCR 값을 연결하여 해시 업데이트 */
crypto_shash_update(shash, d.digest, d.digest_size);
}
crypto_shash_final(shash, hash->digest);
return 0;
}
devm 관리: tpmm_chip_alloc()은 devm_ 접두사 함수를 사용하여, 드라이버 언바인딩 시 tpm_chip 구조체와 관련 리소스가 자동으로 정리됩니다. 이는 에러 경로에서의 메모리 누수를 방지합니다.
CRB vs TIS 인터페이스
TPM과 호스트 사이의 통신 인터페이스는 TIS(FIFO 기반)와 CRB(Command Response Buffer, 메모리 매핑(Mapping) 기반) 두 가지가 있습니다.
ACPI TPM2 테이블
시스템 펌웨어는 ACPI TPM2 테이블을 통해 TPM의 인터페이스 유형과 주소를 커널에 알려줍니다. StartMethod 필드가 인터페이스 유형을 결정합니다:
| StartMethod | 값 | 인터페이스 | 비고 |
|---|---|---|---|
| ACPI Start | 2 | TIS | ACPI _DSM 메서드 호출 |
| CRB | 7 | CRB | fTPM 기본, 최신 플랫폼 |
| CRB with ACPI Start | 8 | CRB | CRB + ACPI 초기화 |
| TIS 1.3 | 6 | TIS | 레거시 TIS |
| 비교 항목 | TIS (FIFO) | CRB |
|---|---|---|
| 데이터 전송 | 바이트 단위 FIFO R/W | 메모리 매핑 버퍼(Buffer) 직접 접근 |
| 프로토콜 오버헤드(Overhead) | 높음 (STS 폴링(Polling), 바이트 루프) | 낮음 (단일 START 트리거) |
| DMA 지원 | 불가 | 가능 (CMD/RSP 주소 분리) |
| 명령 버퍼 | FIFO 공유 (명령/응답 교대) | 명령/응답 버퍼 분리 가능 |
| fTPM 호환 | 에뮬레이션 필요 | 기본 지원 |
| Locality | 0x1000 × N 오프셋(Offset) | LOC_CTRL 레지스터 |
| 대표 용도 | dTPM (SPI/LPC), 레거시 | fTPM (Intel PTT, AMD PSP) |
# ACPI TPM2 테이블 확인
sudo cat /sys/firmware/acpi/tables/TPM2 | hexdump -C | head -5
# dmesg에서 TPM 인터페이스 확인
dmesg | grep -i tpm
# 예: tpm_crb MSFT0101:00: [Firmware Bug]: ACPI table has 0 start method
# 예: tpm_tis 00:05: 2.0 TPM (device-id 0x1B, rev-id 16)
# TPM 칩 정보 확인
cat /sys/class/tpm/tpm0/device/description
cat /sys/class/tpm/tpm0/tpm_version_major
TIS 상세 프로토콜
TIS(FIFO) 인터페이스는 바이트 단위 FIFO를 통해 TPM과 통신합니다. 각 locality는 0x1000 바이트 오프셋으로 분리되며, 하나의 명령 전송은 다음 단계를 거칩니다:
- Locality 요청:
TPM_ACCESS레지스터에requestUse비트를 설정하고,activeLocality비트가 설정될 때까지 폴링 - 명령 준비:
TPM_STS에commandReady비트 설정 → TPM이 명령 수신 준비 상태로 전환 - 명령 쓰기:
TPM_DATA_FIFO에 명령 바이트를 순차적으로 쓰기, 각 쓰기 후TPM_STS.expect비트로 추가 데이터 필요 여부 확인 - 실행 트리거:
TPM_STS에tpmGo비트 설정 → TPM이 명령 처리 시작 - 완료 대기:
TPM_STS.dataAvail비트 폴링 (또는 인터럽트(Interrupt) 대기) - 응답 읽기:
TPM_DATA_FIFO에서 응답 바이트를 순차적으로 읽기 - Locality 반환:
TPM_ACCESS에activeLocality비트 설정하여 반환
TIS Locality 관리
| Locality | 주소 범위 | 용도 | 사용 주체 |
|---|---|---|---|
| Locality 0 | 0xFED40000 | SRTM (Static Root of Trust) | BIOS/UEFI, 일반 OS |
| Locality 1 | 0xFED41000 | 예약 (TCG 정의) | 환경별 정의 |
| Locality 2 | 0xFED42000 | DRTM (Dynamic Root of Trust) | Intel TXT ACM, AMD SKINIT |
| Locality 3 | 0xFED43000 | Maintenance / AUX | 펌웨어 보조 |
| Locality 4 | 0xFED44000 | 특수 (Intel TXT 전용) | Intel TXT SINIT ACM |
SPI TPM 전송
현대 dTPM은 대부분 SPI 버스를 사용합니다. SPI TPM은 표준 TIS 레지스터 레이아웃을 SPI 프레임으로 래핑합니다:
/* SPI TPM 프레임 포맷 (TCG PC Client Platform TPM Profile Spec) */
/* Byte 0: R/W (0=Write, 1=Read) | Transfer size (6 bits) */
/* Byte 1-3: 24-bit TIS 레지스터 주소 */
/* Wait State: TPM이 준비되지 않으면 MISO=0 반환 (wait polling) */
/* MISO=1이면 데이터 전송 시작 */
/* Data: 실제 레지스터 데이터 (1-64 바이트) */
/* drivers/char/tpm/tpm_tis_spi_main.c — SPI wait-state 처리 */
static int tpm_tis_spi_flow(struct tpm_tis_spi_phy *phy,
u32 addr, u16 len, u8 *buf,
u8 direction)
{
/* 1. 4바이트 헤더 전송 (방향 + 크기 + 주소) */
/* 2. wait-state 폴링: TPM이 MISO에 0x01을 반환할 때까지 */
/* 최대 timeout 동안 SPI 클럭 계속 공급 */
/* 3. 데이터 전송/수신 */
}
SPI clock stretching: SPI TPM에서 wait-state 폴링은 TPM이 내부 연산을 완료할 때까지 SPI 클럭을 계속 공급해야 합니다. 이 과정에서 SPI 버스가 점유되므로, 같은 SPI 버스에 연결된 다른 디바이스(SPI 플래시 등)의 접근이 지연(Latency)될 수 있습니다. 일부 SoC에서는 이것이 fTPM 지연 문제의 원인이 됩니다.
CRB 상세 프로토콜
CRB(Command Response Buffer)는 메모리 매핑 버퍼를 사용하여 FIFO 오버헤드를 제거합니다. CRB의 명령 전송 흐름은 다음과 같습니다:
- goIdle → cmdReady 전환:
CRB_CTRL_REQ에cmdReady비트를 설정하고,CRB_CTRL_STS에서tpmIdle비트가 클리어될 때까지 대기 - 명령 쓰기:
CRB_CTRL_CMD_ADDR이 가리키는 메모리 버퍼에 명령 바이트를 직접 복사 (memcpy 한 번) - 실행 트리거:
CRB_CTRL_START에Start비트를 설정 - 완료 대기:
CRB_CTRL_START의Start비트가 클리어될 때까지 폴링 - 응답 읽기:
CRB_CTRL_RSP_ADDR이 가리키는 메모리 버퍼에서 응답을 직접 읽기 - goIdle:
CRB_CTRL_REQ에goIdle비트를 설정하여 TPM을 유휴 상태(Idle State)로 전환
/* drivers/char/tpm/tpm_crb.c — CRB 명령 전송 */
static int crb_send(struct tpm_chip *chip, u8 *buf, size_t len)
{
struct crb_priv *priv = dev_get_drvdata(&chip->dev);
/* goIdle → cmdReady 상태 전환 */
crb_cmd_ready(chip);
/* 명령 버퍼에 직접 복사 (FIFO 없이 단일 memcpy) */
memcpy_toio(priv->cmd, buf, len);
/* CRB_START 비트 설정 — TPM 명령 실행 시작 */
iowrite32(CRB_START_INVOKE,
&priv->regs_t->ctrl_start);
return 0;
}
ACPI TPM2 테이블 상세
/* ACPI TPM2 테이블 구조 (TCG ACPI Specification) */
struct acpi_table_tpm2 {
struct acpi_table_header header; /* 표준 ACPI 헤더 */
u16 platform_class; /* 0=PC Client, 1=Server */
u16 reserved;
u64 control_address; /* CRB 제어 영역 주소 또는 TIS 베이스 */
u32 start_method; /* StartMethod 값 (아래 표 참조) */
/* start_method별 Platform-Specific Parameters (최대 12바이트) */
u8 start_method_params[12];
/* 선택적: Log Area Minimum Length + Log Area Start Address */
};
/* 커널의 ACPI TPM2 테이블 파싱 (drivers/char/tpm/tpm_crb.c) */
static int crb_acpi_add(struct acpi_device *device)
{
struct acpi_table_tpm2 *buf;
acpi_status st;
st = acpi_get_table(ACPI_SIG_TPM2, 1,
(struct acpi_table_header **)&buf);
/* buf->start_method에 따라:
* 7 (CRB) → crb_map_io() 호출
* 8 (CRB with ACPI Start) → ACPI _DSM 메서드도 호출
* 6 (TIS) → tpm_tis 드라이버로 위임 */
}
| StartMethod 값 | 이름 | 동작 방식 | 대표 플랫폼 |
|---|---|---|---|
| 0 | Reserved | 사용 금지 | - |
| 2 | ACPI Start | ACPI _DSM 메서드로 명령 전송 | 레거시 dTPM |
| 6 | TIS 1.3 | 메모리 매핑 TIS FIFO (0xFED40000) | dTPM (LPC/SPI) |
| 7 | CRB | CRB 레지스터 직접 접근 | Intel PTT, AMD fTPM |
| 8 | CRB with ACPI Start | CRB + ACPI _DSM 초기화 | 일부 ARM 서버 |
| 9 | CRB with SMC | CRB + ARM SMC 호출 | ARM TrustZone fTPM |
| 11 | CRB with ARM FF-A | CRB + Firmware Framework for Arm | ARM FF-A fTPM |
인터럽트 지원: TIS는 TPM_INT_ENABLE/TPM_INT_STATUS/TPM_INT_VECTOR 레지스터를 통해 인터럽트를 지원하지만, 많은 실무 환경에서 인터럽트가 불안정하여 기본적으로 폴링 모드로 동작합니다. 커널 파라미터 tpm_tis.interrupts=0이 기본값이며, 인터럽트를 활성화하려면 tpm_tis.interrupts=1로 설정합니다. CRB는 인터럽트 대신 CRB_CTRL_START 비트 폴링만 사용합니다.
TPM 드라이버 내부 구현
TPM 드라이버의 내부 구현을 커널 소스 기반으로 분석합니다. 명령 전송, 리소스 관리, 전원 관리(Power Management)의 주요 경로를 추적합니다.
tpm_transmit() 상세 흐름
모든 TPM 명령은 tpm_transmit()를 통해 직렬화(Serialization)됩니다. 이 함수는 다음 단계를 수행합니다:
- Ops 획득:
tpm_try_get_ops()—chip->ops_sem읽기 잠금 획득, locality 요청 - Space 준비:
tpm2_prepare_space()— tpmrm의 경우, 이전 세션/객체 context를 TPM에 복원 - 명령 전송:
chip->ops->send()— 하드웨어별 전송 (TIS FIFO 쓰기 또는 CRB 버퍼 복사) - 완료 대기:
tpm_cmd_ready()— STS 레지스터 폴링 또는 IRQ 대기 - 응답 수신:
chip->ops->recv()— 하드웨어별 수신 - Space 커밋:
tpm2_commit_space()— 세션/객체 context를 space 버퍼에 저장 - Ops 해제:
tpm_put_ops()— locality 반환, 읽기 잠금 해제
Timeout 관리
TPM은 4가지 타임아웃 값을 자체 보고합니다:
| 타임아웃 | 용도 | 일반적인 값 |
|---|---|---|
| TIMEOUT_A | 명령 수신 확인 | 750 ms |
| TIMEOUT_B | 명령 처리 대기 | 2000 ms |
| TIMEOUT_C | 긴 명령 (키 생성) | 200 ms (기본), 실제 수 초 |
| TIMEOUT_D | locality 획득 | 30 ms |
명령 유형별 duration은 TPM2_GetCapability(TPM_CAP_TPM_PROPERTIES, TPM_PT_MAX_COMMAND_SIZE)와 TPM2_SelfTest 결과로 결정됩니다. RSA-2048 키 생성(TPM2_CreatePrimary)은 dTPM에서 수 초가 소요될 수 있습니다.
tpm_space 리소스 관리
/* drivers/char/tpm/tpm.h */
struct tpm_space {
u32 context_tbl[3]; /* transient object 핸들 (최대 3개) */
u8 *context_buf; /* ContextSave 결과 저장 */
u32 session_tbl[64]; /* 세션 핸들 테이블 */
u8 *session_buf; /* 세션 ContextSave 결과 */
u32 buf_size; /* 버퍼 크기 */
};
/* tpmrm open 시 호출 — 프로세스별 독립 공간 할당 */
int tpm2_init_space(struct tpm_space *space, unsigned int buf_size)
{
space->context_buf = kzalloc(buf_size, GFP_KERNEL);
space->session_buf = kzalloc(buf_size, GFP_KERNEL);
return 0;
}
/dev/tpmrm0를 open()하면 tpm2_init_space()가 호출되어 해당 file descriptor에 전용 tpm_space가 할당됩니다. 명령 전송 전에 tpm2_prepare_space()가 이전에 저장된 context를 TPM에 복원하고, 응답 수신 후 tpm2_commit_space()가 현재 context를 다시 저장합니다. 이 과정이 프로세스 간 핸들 충돌을 방지합니다.
전원 관리
/* 절전(suspend) 시 */
int tpm_pm_suspend(struct device *dev)
{
struct tpm_chip *chip = dev_get_drvdata(dev);
/* TPM2_Shutdown(STATE) — 상태 보존 모드로 종료 */
tpm2_shutdown(chip, TPM2_SU_STATE);
return 0;
}
/* 복귀(resume) 시 */
int tpm_pm_resume(struct device *dev)
{
struct tpm_chip *chip = dev_get_drvdata(dev);
/* TPM2_Startup(STATE) — 보존된 상태로 재개 */
tpm2_startup(chip, TPM2_SU_STATE);
return 0;
}
tpm_chip_register 흐름: TPM 드라이버 프로브 시 tpmm_chip_alloc() → tpm_chip_register()가 호출됩니다. 이 과정에서 tpm2_probe()로 TPM 2.0 여부를 확인하고, tpm2_auto_startup()으로 Startup/SelfTest를 실행하며, tpm_add_char_device()로 /dev/tpm0과 /dev/tpmrm0를 등록합니다.
dTPM vs fTPM vs sTPM 비교
fTPM 취약점(Vulnerability) 사례
| 취약점 | 년도 | 영향 | 설명 |
|---|---|---|---|
| AMD fTPM 타이밍 공격 | 2023 | AMD Zen 2/3 | fTPM의 ECDSA 서명 타이밍에서 비밀 키 추출 가능 (faulTPM) |
| Intel CSME 취약점 (CVE-2019-0090) | 2019 | Intel 2016 이전 칩셋 | CSME ROM 취약점으로 PTT fTPM 키 추출 가능 |
| AMD PSP 버그 (CVE-2021-26311) | 2021 | AMD EPYC | PSP 메모리 손상으로 fTPM 상태 변조 가능 |
| TPM.FAIL (CVE-2019-11090) | 2019 | Intel PTT, STMicro dTPM | ECDSA nonce 타이밍 공격으로 키 복구 |
AMD fTPM 랜덤 지연 현상: AMD Zen 3 플랫폼에서 fTPM이 간헐적으로 수백 밀리초의 지연(stutter)을 유발하는 현상이 보고되었습니다. 이는 fTPM이 SPI 플래시에 NV 데이터를 쓸 때 발생하며, 오디오 끊김이나 마우스 지연으로 나타납니다. BIOS에서 fTPM을 dTPM으로 전환하면 해결되지만, dTPM이 없는 시스템에서는 tpm_tis.interrupts=0 커널 파라미터로 완화할 수 있습니다.
ARM TrustZone fTPM: ARM 기반 플랫폼에서는 OP-TEE(Open Portable Trusted Execution Environment) 위에 fTPM을 구현합니다. 커널의 CONFIG_TCG_FTPM_TEE 드라이버가 TEE Client API를 통해 OP-TEE fTPM TA(Trusted Application)와 통신합니다. 자세한 내용은 ARM TrustZone & OP-TEE 문서를 참고하세요.
fTPM 보안 분석 상세
| 취약점/이슈 | CVE/ID | 년도 | 영향 플랫폼 | 공격 유형 | 완화/수정 |
|---|---|---|---|---|---|
| faulTPM | - | 2023 | AMD Zen 2/3 | 전압 결함 주입 (voltage glitching)으로 fTPM 키 추출 | 물리적 접근 필요, 펌웨어 업데이트로 일부 완화 |
| TPM-FAIL ECDSA | CVE-2019-11090 | 2019 | Intel PTT, STMicro ST33 | ECDSA nonce 생성의 타이밍 편차에서 비밀키 복구 (격자 공격) | TPM 펌웨어 업데이트, 상수 시간 구현 적용 |
| Intel CSME ROM | CVE-2019-0090 | 2019 | Intel 10세대 이전 칩셋 | CSME ROM 취약점으로 PTT fTPM 루트 키 추출 | 칩셋 교체 필요 (ROM 패치(Patch) 불가) |
| Intel SA-00391 | CVE-2020-8705 | 2020 | Intel 다수 플랫폼 | CSME/SPS/TXE 메모리 손상으로 특권 상승 | CSME 펌웨어 업데이트 |
| AMD fTPM stutter | SB-1029 | 2022 | AMD Zen 3 데스크톱 | 보안이 아닌 성능 이슈 — fTPM NV 쓰기 시 수백 ms 지연 | dTPM 전환, BIOS 업데이트, 또는 tpm_tis.interrupts=0 |
| AMD PSP SEV | CVE-2021-26311 | 2021 | AMD EPYC | PSP 메모리 손상으로 fTPM 상태 변조 가능 | PSP 펌웨어 업데이트 |
fTPM 사이드 채널 완화: fTPM은 메인 CPU와 메모리를 공유하는 경우(Intel PTT는 CSME 별도 코어이지만, AMD PSP도 전용 코어), 캐시(Cache) 타이밍 공격에 노출될 수 있습니다. 완화 방법: (1) TPM 펌웨어의 상수 시간(constant-time) 암호 연산 구현, (2) Intel CSME의 캐시 파티셔닝, (3) AMD PSP의 메모리 격리(Isolation). 높은 보안이 요구되는 환경에서는 CC EAL4+ 인증을 받은 dTPM을 권장합니다.
dTPM 물리 보안
dTPM은 별도의 물리적 칩이므로, 호스트 CPU와의 통신 버스(SPI/LPC/I2C)를 물리적으로 도청(sniffing)하거나 중간자 공격(interposer)을 수행할 수 있습니다:
| 공격 방법 | 난이도 | 필요 장비 | 대응 |
|---|---|---|---|
| SPI 버스 스니핑 (TPM Genie) | 중간 | 로직 분석기, SPI 프로브 | 버스 암호화 (TPM 2.0 파라미터 암호화 세션) |
| LPC 버스 인터포저 | 중간 | FPGA 보드, LPC 어댑터 | SPI TPM으로 전환, 물리적 접근 통제 |
| 전원 분석 (SPA/DPA) | 높음 | 오실로스코프, 전류 프로브 | 전력 필터링, 마스킹 구현 (CC 인증 칩) |
| 글리칭 (전압/클럭) | 높음 | ChipWhisperer, 전압 인젝터 | 글리치 검출기, 이상 전압 리셋 (CC 인증 칩) |
| 디캡 (실리콘 개봉) | 매우 높음 | 화학 에칭, SEM/FIB | 액티브 실드, 메모리 암호화 (EAL5+ 칩) |
dTPM 칩 제조사 비교
| 제조사 | 칩 시리즈 | 인터페이스 | 인증 | 특징 |
|---|---|---|---|---|
| Infineon | SLB 9670/9672 | SPI, I2C | CC EAL4+, FIPS 140-2 L2 | 가장 널리 사용, 서버/데스크톱 주류 |
| Nuvoton | NPCT750/755 | SPI, I2C | CC EAL4+, FIPS 140-2 L2 | Chromebook에서 많이 사용, Google 협력 |
| STMicroelectronics | ST33TPHF2ESPI | SPI, I2C | CC EAL4+, FIPS 140-2 L2 | 자동차/산업용 온도 범위 지원 |
| Microchip | ATECC608B-TNGTPM | I2C | FIPS 140-2 L3 | IoT/임베디드 특화, 저전력 |
| Cr50/Ti50 (Titan C) | SPI, I2C | FIPS 140-2 L1 | Chromebook 전용, Google 커스텀 펌웨어 |
실무 선택 가이드
| 기준 | dTPM | fTPM | sTPM |
|---|---|---|---|
| 보안 요구 수준 | 높음~최고 (금융, 정부, 규제) | 중간 (일반 기업, 소비자) | 없음 (개발/테스트 전용) |
| 비용 | 칩당 $1-5 + BOM | 추가 비용 없음 | 무료 |
| 물리적 공격 저항 | 높음 (전용 보안 실리콘) | 중간 (TEE 의존) | 없음 |
| 소프트웨어 공격 저항 | 높음 (격리된 실행 환경) | 중간 (CPU 취약점 연쇄 가능) | 없음 (동일 OS 내) |
| 성능 | 느림 (버스 지연) | 중간 | 빠름 |
| 교체 유연성 | 하드웨어 교체 필요 | 펌웨어 업데이트 | 소프트웨어 업데이트 |
| 인증 획득 가능 | CC EAL4+, FIPS L2-3 | FIPS L1-2 | 불가 |
Multi-TPM 시나리오: 일부 서버 플랫폼은 dTPM과 fTPM을 동시에 장착합니다. 이 경우 BIOS에서 어떤 TPM을 기본 장치로 노출할지 선택합니다. Linux 커널은 /dev/tpm0으로 첫 번째 검출된 TPM을, 두 번째를 /dev/tpm1으로 등록합니다. 단, 대부분의 소프트웨어(systemd-cryptenroll, Keylime 등)는 tpm0만 사용하므로, BIOS에서 주 TPM을 올바르게 설정해야 합니다.
Measured Boot와 TPM
Measured Boot는 시스템 부팅 과정의 각 단계를 TPM PCR에 측정하여, 나중에 부팅 과정이 변조되지 않았음을 검증할 수 있게 합니다.
SRTM (Static Root of Trust for Measurement)
SRTM은 CPU 리셋부터 시작하는 측정 체인입니다. CRTM(Core Root of Trust for Measurement) — 일반적으로 BIOS/UEFI ROM의 최초 실행 코드 — 이 신뢰의 출발점이며, 이후 각 단계가 다음 단계를 PCR에 기록합니다. SRTM의 한계는 CRTM 자체의 무결성이 전제 조건이라는 점입니다.
DRTM (Dynamic Root of Trust for Measurement)
DRTM은 런타임에 새로운 신뢰 기반을 설정합니다. Intel TXT의 GETSEC[SENTER] 또는 AMD의 SKINIT 명령이 CPU 하드웨어 레벨에서 모든 코어를 정지시키고, 서명된 ACM(Authenticated Code Module)을 실행하여 깨끗한 측정 환경을 만듭니다. DRTM은 PCR 17-22를 사용하며, SRTM과 독립적으로 동작합니다.
이벤트 로그 읽기
# 바이너리 이벤트 로그 위치 (securityfs)
ls -la /sys/kernel/security/tpm0/binary_bios_measurements
# tpm2-tools로 이벤트 로그 파싱
tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements
# 특정 PCR에 대한 이벤트만 필터
tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements \
| grep -A5 "PCRIndex: 7"
# PCR Replay 검증: 이벤트 로그에서 재계산한 PCR과 실제 PCR 비교
tpm2_eventlog --pcrs=sha256 /sys/kernel/security/tpm0/binary_bios_measurements
tpm2_pcrread sha256:0,1,2,3,4,5,6,7
systemd-boot PCR 정책: systemd-boot(sd-boot)는 UKI(Unified Kernel Image)와 함께 다음 PCR을 사용합니다: PCR 11(UKI 해시), PCR 12(커널 명령줄), PCR 15(시스템 확장 이미지). systemd-measure 도구로 부팅 전에 예상 PCR 값을 계산하고 서명할 수 있어, PCR fragility 문제를 해결합니다. 자세한 내용은 TPM 기반 디스크 암호화 섹션을 참고하세요.
IMA와 PCR 10: Linux 커널의 IMA(Integrity Measurement Architecture)는 실행되거나 읽히는 파일을 PCR 10에 측정합니다. IMA 이벤트 로그는 /sys/kernel/security/ima/ascii_runtime_measurements에서 확인할 수 있으며, 원격 증명에서 OS 런타임 무결성을 검증하는 핵심 요소입니다. 자세한 내용은 IMA/EVM 무결성 검증 문서를 참고하세요.
DRTM 상세 분석
DRTM(Dynamic Root of Trust for Measurement)은 이미 실행 중인 시스템에서 새로운 신뢰 기반을 동적으로 설정하는 메커니즘입니다. SRTM과 달리 부팅 초기 코드의 무결성에 의존하지 않으므로, 후기 부팅(late launch) 보안을 제공합니다.
Intel TXT (Trusted Execution Technology)
Intel TXT의 GETSEC[SENTER] 명령은 다음 단계를 CPU 하드웨어 레벨에서 강제합니다:
- 환경 정화: 모든 AP(Application Processor)를 WAIT-FOR-SIPI 상태로 강제 전환, 인터럽트 비활성화, DMA 차단
- SINIT ACM 검증: Intel이 서명한 Authenticated Code Module의 서명을 CPU 내장 키로 검증
- PCR 17-22 리셋: Locality 4에서만 가능한 DRTM PCR을 하드웨어적으로 초기화
- Measured Launch: SINIT ACM과 MLE(Measured Launch Environment, 예: tboot)를 PCR 17-18에 측정
- 제어 전달: 측정이 완료된 MLE에 제어를 넘기고, MLE가 OS 커널을 시작
AMD SKINIT
AMD의 DRTM은 SKINIT 명령을 사용하며, Intel TXT보다 단순한 구조입니다:
AMD SKINIT 흐름:
1. SKINIT 명령 실행 (Ring 0에서)
2. CPU가 인터럽트/DMA 차단
3. Secure Loader Block (SLB, 최대 64KB) 해시 → PCR 17 extend
4. SLB 실행 — SLB가 커널 측정 후 부팅
5. DEV (Device Exclusion Vector)로 DMA 보호 설정
Linux 커널 DRTM 지원 상태: tboot는 GRUB multiboot 프로토콜을 통해 Intel TXT DRTM을 구현합니다. AMD SKINIT은 커널에서 CONFIG_SECURE_LAUNCH로 지원이 진행 중이며, "Secure Launch" 패치셋이 활발히 개발되고 있습니다. ARM 플랫폼에서는 DRTM 표준이 아직 초기 단계입니다.
TCG Crypto Agile Event Log 상세
TCG Crypto Agile 로그 형식(TCG_PCR_EVENT2)은 다중 해시 알고리즘을 지원하며, TPM 2.0의 표준 이벤트 로그 형식입니다:
/* TCG PC Client Platform Firmware Profile Specification */
struct tcg_pcr_event2 {
u32 pcr_index; /* PCR 인덱스 (0-23) */
u32 event_type; /* EV_* 이벤트 타입 */
u32 digest_count; /* 다이제스트 개수 (뱅크 수) */
struct {
u16 alg_id; /* TPM_ALG_SHA1=0x0004, SHA256=0x000B */
u8 digest[]; /* 알고리즘별 다이제스트 길이 */
} digests[digest_count];
u32 event_size; /* 이벤트 데이터 길이 */
u8 event[]; /* 이벤트 설명 데이터 */
};
/* 이벤트 로그의 첫 번째 항목은 항상 TCG_EfiSpecIDEvent (레거시 호환) */
struct tcg_efi_specid_event_head {
u8 signature[16]; /* "Spec ID Event03" */
u32 platform_class; /* 0=PC Client */
u8 spec_version_minor;
u8 spec_version_major;
u8 spec_errata;
u8 uintn_size; /* 1=u32, 2=u64 */
u32 num_algs; /* 지원 알고리즘 수 */
/* 이후: alg_id(u16) + digest_size(u16) × num_algs */
};
PCR Replay 검증 알고리즘
이벤트 로그에서 PCR 값을 재계산하여 TPM의 실제 PCR 값과 비교하는 과정을 PCR Replay라 합니다. 이것이 원격 증명의 핵심입니다:
PCR Replay 의사코드:
pcr_expected[0..23] = {0x00...00} // 모든 PCR을 0으로 초기화
for each event in event_log:
pcr_idx = event.pcr_index
digest = event.digests[SHA-256]
// PCR extend 연산 재현
pcr_expected[pcr_idx] = SHA-256(pcr_expected[pcr_idx] || digest)
// 검증: 재계산된 PCR과 TPM Quote의 PCR 비교
for each pcr_idx in quote_pcr_selection:
assert pcr_expected[pcr_idx] == quote_pcr_digest[pcr_idx]
// 불일치 → 이벤트 로그가 변조되었거나 누락된 이벤트 존재
# PCR Replay를 tpm2-tools로 수행
tpm2_eventlog --pcrs=sha256 /sys/kernel/security/tpm0/binary_bios_measurements
# 출력 예시:
# sha256:
# 0 : 0x3A2B1C... (계산값)
# 7 : 0x5D4E2F... (계산값)
# 실제 TPM PCR 값과 비교
tpm2_pcrread sha256:0,1,2,3,4,5,6,7
# 이벤트 로그 상세 파싱 — 개별 이벤트 확인
tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements 2>/dev/null | \
grep -B2 -A5 "EventType"
# EFI 변수 관련 이벤트만 필터 (Secure Boot 정책)
tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements | \
grep -A10 "EV_EFI_VARIABLE"
IMA와 TPM 연동
IMA(Integrity Measurement Architecture)는 커널 레벨에서 파일 실행/접근을 PCR 10에 측정하여, BIOS 이벤트 로그 이후의 OS 런타임 무결성을 보장합니다:
/* security/integrity/ima/ima_queue.c — IMA PCR extend */
static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr)
{
int result;
/* CONFIG_IMA_MEASURE_PCR_IDX (기본 10)에 extend */
result = tpm_pcr_extend(ima_tpm_chip, pcr, digests_arg);
return result;
}
/* IMA 템플릿 엔트리 구조 */
struct ima_template_entry {
int pcr; /* PCR 인덱스 (기본 10) */
struct tpm_digest *digests; /* 뱅크별 다이제스트 */
struct ima_template_desc *template_desc;
u32 template_data_len;
struct ima_field_data *template_data;
};
boot_aggregate 계산
IMA 이벤트 로그의 첫 번째 항목은 항상 boot_aggregate이며, BIOS PCR 값(PCR 0-7)의 해시로 계산됩니다. 이를 통해 BIOS 측정과 IMA 측정이 연결됩니다:
boot_aggregate = SHA-256(PCR[0] || PCR[1] || ... || PCR[7])
IMA 이벤트 로그 첫 줄 (ascii_runtime_measurements):
10 ima-ng sha256: boot_aggregate
# IMA 이벤트 로그 첫 줄 확인 — boot_aggregate
head -1 /sys/kernel/security/ima/ascii_runtime_measurements
# 출력: 10 abc123... ima-ng sha256:def456... boot_aggregate
# boot_aggregate 수동 검증
# 1. PCR 0-7 값 읽기
tpm2_pcrread sha256:0,1,2,3,4,5,6,7 -o pcr_values.bin
# 2. 연결된 PCR 값의 SHA-256 해시 계산
sha256sum pcr_values.bin
# 3. IMA 로그의 boot_aggregate와 비교
BIOS 이벤트 로그 vs IMA 이벤트 로그: BIOS 이벤트 로그(binary_bios_measurements)는 PCR 0-9에 대한 펌웨어/부트로더 측정을 기록합니다. IMA 이벤트 로그(ascii_runtime_measurements)는 PCR 10에 대한 OS 런타임 측정을 기록합니다. 원격 증명에서는 두 로그를 모두 수집하여 전체 부팅 체인과 런타임 무결성을 검증합니다. 자세한 IMA 정책과 설정은 IMA/EVM 무결성 검증 문서를 참고하세요.
PCR fragility 문제와 해결
PCR fragility(취약성)는 TPM 기반 봉인의 가장 큰 실무 문제입니다. 커널 업데이트, BIOS 업데이트, Secure Boot 정책 변경 등이 PCR 값을 변경하여 봉인된 비밀에 접근할 수 없게 만듭니다:
| PCR | 변경 원인 | 빈도 | 봉인 바인딩 권장 여부 |
|---|---|---|---|
| PCR 0 | BIOS/펌웨어 업데이트 | 낮음 (연 1-2회) | 주의 필요 — 펌웨어 업데이트 전 재봉인 |
| PCR 1 | BIOS 설정 변경 | 낮음 | 주의 필요 |
| PCR 4 | 부트로더(GRUB/sd-boot) 변경 | 중간 | 비권장 — 커널 업데이트마다 변경 |
| PCR 7 | Secure Boot 정책 (PK/KEK/db/dbx) | 낮음 | 권장 — 가장 안정적인 PCR |
| PCR 8 | 커널 명령줄, GRUB 설정 | 높음 | 비권장 — 빈번한 변경 |
| PCR 9 | initrd 내용 | 높음 | 비권장 — 커널 업데이트마다 변경 |
| PCR 11 | UKI(Unified Kernel Image) 해시 | 중간 | Signed Policy와 함께 사용 |
# === Signed PCR Policy를 사용한 자동 갱신 워크플로 ===
# 1. 서명 키 쌍 (오프라인에 안전하게 보관)
openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 \
-out pcr-sign-key.pem
openssl pkey -in pcr-sign-key.pem -pubout -out pcr-sign-pubkey.pem
# 2. 커널 패키지 빌드/설치 시 예상 PCR 값 서명
# → 패키지 관리자의 post-install hook에 통합
systemd-measure sign \
--linux=/boot/vmlinuz-6.8.0 \
--initrd=/boot/initrd.img-6.8.0 \
--osrel=/etc/os-release \
--cmdline=/proc/cmdline \
--private-key=pcr-sign-key.pem \
--public-key=pcr-sign-pubkey.pem \
--phase=enter-initrd:leave-initrd:sysinit:ready \
> /boot/loader/entries/6.8.0-pcrsig.json
# 3. TPM 봉인은 공개키로만 등록 (최초 1회)
systemd-cryptenroll --tpm2-device=auto \
--tpm2-public-key=pcr-sign-pubkey.pem \
--tpm2-public-key-pcrs=11 \
/dev/sda3
# → 이후 커널 업데이트마다 서명만 갱신하면 자동으로 동작
# → TPM 봉인 객체 재생성 불필요!
원격 증명 (Remote Attestation)
원격 증명(Remote Attestation)은 TPM의 가장 중요한 응용 중 하나로, 원격 시스템의 소프트웨어 상태가 변조되지 않았음을 제3자(Verifier)에게 암호학적으로 증명합니다.
AK 인증: MakeCredential / ActivateCredential
원격 증명에서 검증자는 Attester의 AK가 진짜 TPM 내부에 존재하는지 확인해야 합니다. 이를 위해 EK를 활용하는 MakeCredential/ActivateCredential 프로토콜을 사용합니다:
- Attester가 AK 공개키와 EK 공개키(또는 EK 인증서)를 Verifier에게 전송
- Verifier가
TPM2_MakeCredential(EK_pub, AK_name, secret)으로 credential 블롭 생성 - Attester가
TPM2_ActivateCredential(EK, AK, credential_blob)으로 secret 복구 - secret을 복구할 수 있다는 것은 AK가 EK와 같은 TPM에 있음을 증명
# 1. AK 생성
tpm2_createak -C ek.ctx -c ak.ctx -u ak.pub -n ak.name -G rsa2048
# 2. Quote 생성 (PCR 0,7,11에 대해 nonce와 함께)
tpm2_quote -c ak.ctx -l sha256:0,7,11 \
-q $(openssl rand -hex 32) \
-m quote.msg -s quote.sig -o quote.pcrs -g sha256
# 3. Quote 검증 (검증자 측)
tpm2_checkquote -u ak.pub -m quote.msg -s quote.sig \
-f quote.pcrs -g sha256 -q
# MakeCredential / ActivateCredential 프로토콜
# (검증자 측) credential 래핑
echo "challenge-secret" > secret.data
tpm2_makecredential -e ek.pub -s secret.data -n ak.name -o credential.blob
# (Attester 측) credential 풀기
tpm2_activatecredential -c ak.ctx -C ek.ctx \
-i credential.blob -o recovered_secret.data
Keylime 원격 증명 프레임워크
Keylime은 CNCF 프로젝트로, 대규모 시스템의 TPM 기반 원격 증명을 자동화합니다:
| 컴포넌트 | 역할 | 위치 |
|---|---|---|
| Registrar | Agent 등록, EK/AK 인증 | 중앙 서버 |
| Verifier | 주기적 Quote 검증, 정책 평가 | 중앙 서버 |
| Agent | TPM Quote 생성, IMA 로그 전송 | 대상 시스템 |
| Tenant | 정책 설정, Agent 등록 요청 CLI | 관리자 워크스테이션 |
# Keylime Agent 등록
keylime_tenant -c add -t --uuid \
--allowlist /path/to/allowlist.txt \
--exclude /path/to/exclude.txt
# 증명 상태 확인
keylime_tenant -c status -t --uuid
기밀 컴퓨팅(Confidential Computing)과 증명: AMD SEV-SNP, Intel TDX 등의 기밀 컴퓨팅 환경에서도 증명(Attestation)은 핵심 요소입니다. 이 경우 CPU가 생성하는 증명 보고서가 TPM Quote와 유사한 역할을 합니다. 자세한 내용은 기밀 컴퓨팅 문서를 참고하세요.
MakeCredential/ActivateCredential 프로토콜 상세
이 프로토콜은 AK(Attestation Key)가 EK(Endorsement Key)와 동일한 TPM에 존재함을 증명하는 핵심 메커니즘입니다. 암호학적으로 다음과 같이 동작합니다:
MakeCredential의 내부 암호학적 동작은 다음과 같습니다:
- Verifier가 랜덤
secret을 생성하고,TPM2_MakeCredential(EK_pub, AK_name, secret)을 호출 - 내부적으로:
secret을 AK_name의 해시로 바인딩(KDF)한 후, EK 공개키로 RSA-OAEP 암호화 →credential_blob생성 - Attester가
TPM2_ActivateCredential(EK, AK, credential_blob)을 TPM에서 실행 - TPM 내부에서: EK 비밀키로 복호화 → AK_name을 현재 TPM에 로드된 AK의 name과 비교 → 일치하면
secret반환 - 이 과정이 성공하면, AK가 EK와 동일한 물리적 TPM에 바운딩되어 있음이 암호학적으로 증명됨
EK 인증서 체인 검증: EK 인증서는 TPM 제조사(Infineon, Nuvoton, STMicro 등)가 서명합니다. 검증자는 제조사의 CA 인증서로 EK 인증서의 진위를 확인합니다. EK 인증서는 TPM의 NV 인덱스 0x01C00002(RSA) 또는 0x01C0000A(ECC)에 저장되어 있거나, 제조사의 온라인 서비스(예: ekop.intel.com)에서 다운로드할 수 있습니다.
Privacy CA vs DAA
| 방식 | 프라이버시 | 복잡도 | 설명 |
|---|---|---|---|
| Privacy CA | 중간 (CA가 모든 AK 매핑을 알 수 있음) | 낮음 | 신뢰할 수 있는 CA가 AK 인증서를 발급. EK→AK 링크를 CA가 보유. 구현이 단순하여 실무에서 가장 많이 사용 |
| DAA (Direct Anonymous Attestation) | 높음 (익명성 보장) | 높음 | 그룹 서명 기반으로, 검증자가 어떤 TPM인지 식별 불가능. TPM2_Commit/TPM2_Sign으로 구현. FIDO 키와 유사한 프라이버시 모델 |
| ECDAA | 높음 | 중간 | ECC 기반 DAA. TPM 2.0 스펙에 포함 (TPM_ALG_ECDAA). 성능이 RSA DAA보다 우수하지만 구현체가 제한적 |
Keylime 운영
Keylime의 주요 운영 기능을 상세히 살펴봅니다:
Keylime Allowlist/Excludelist 관리
# === Keylime 설치 ===
sudo dnf install keylime keylime-verifier keylime-registrar keylime-agent
# === Allowlist 생성 (IMA 정책용) ===
# 현재 시스템의 파일 해시를 allowlist로 생성
keylime_create_allowlist -o /etc/keylime/allowlist.txt \
-a sha256 -e /boot -e /proc -e /sys
# Excludelist: 변동이 잦은 파일 제외
cat > /etc/keylime/excludelist.txt << 'EXCLUDE'
/var/log/.*
/tmp/.*
/run/.*
/sys/.*
EXCLUDE
# === Agent 등록 및 모니터링 ===
# Agent에 allowlist + IMA 정책 적용
keylime_tenant -c add \
-t 192.168.1.100 \
--uuid d432fbb3-d2f1-4a97-9ef7-75bd81c00000 \
--allowlist /etc/keylime/allowlist.txt \
--exclude /etc/keylime/excludelist.txt \
--ima-sign-verification-keys /etc/keys/ima-pubkey.pem
# Agent 상태 확인
keylime_tenant -c status \
-t 192.168.1.100 \
--uuid d432fbb3-d2f1-4a97-9ef7-75bd81c00000
# Agent 삭제 (모니터링 중지)
keylime_tenant -c delete \
-t 192.168.1.100 \
--uuid d432fbb3-d2f1-4a97-9ef7-75bd81c00000
# === Revocation 액션 설정 ===
# /etc/keylime/keylime.conf
# [agent]
# revocation_actions = local_action_rm_ssh_key,
# local_action_block_network
# revocation_notification_ip = 10.0.0.1
# revocation_notification_port = 8992
Keylime Runtime Policy: Keylime v7.x부터는 JSON 기반 Runtime Policy로 allowlist/excludelist를 대체합니다. 정책 파일에서 파일별 해시, IMA 서명 검증, 커스텀 검증 스크립트를 선언적으로 구성할 수 있으며, keylime_create_policy 도구로 기존 시스템에서 정책을 자동 생성할 수 있습니다.
키 봉인/해제 (Seal/Unseal)
Seal/Unseal은 TPM의 가장 실용적인 기능 중 하나로, 비밀 데이터를 TPM에 봉인하되 특정 조건(PCR 값, 비밀번호 등)이 충족될 때만 해제할 수 있게 합니다.
봉인 흐름 (Seal)
- SRK 생성/로드:
TPM2_CreatePrimary(SH)로 Storage Root Key를 생성하거나, persistent handle에서 로드 - 정책 다이제스트 계산: Trial 세션으로 원하는 정책(예: PolicyPCR + PolicyPassword)의 다이제스트를 계산
- 봉인 객체 생성:
TPM2_Create(parent=SRK, sensitive=비밀데이터, authPolicy=정책다이제스트) - 결과 저장: 생성된
public과private블롭을 파일시스템(Filesystem)에 저장 (SRK로 래핑되어 있으므로 안전)
해제 흐름 (Unseal)
- SRK 재생성: 동일한 SRK 템플릿으로
TPM2_CreatePrimary(SH)— 같은 shSeed에서 동일 SRK 파생 - 봉인 객체 로드:
TPM2_Load(SRK, public_blob, private_blob) - 정책 세션 시작:
TPM2_StartAuthSession(PolicySession) - 정책 만족:
TPM2_PolicyPCR,TPM2_PolicyPassword등 순서대로 호출 - 데이터 해제:
TPM2_Unseal(봉인객체, PolicySession)→ 비밀 데이터 반환
# === Seal: 비밀 데이터를 PCR 7에 바인딩하여 봉인 ===
# 1. SRK 생성
tpm2_createprimary -C o -G rsa2048 -g sha256 -c srk.ctx
# 2. 정책 다이제스트 계산 (Trial 세션)
tpm2_startauthsession --trial-session -S trial.ctx
tpm2_policypcr -S trial.ctx -l sha256:7
tpm2_policygetdigest -S trial.ctx -o pcr7_policy.digest
tpm2_flushcontext trial.ctx
# 3. 비밀 데이터 봉인
echo "my-super-secret-key-material" > secret.dat
tpm2_create -C srk.ctx -L pcr7_policy.digest \
-i secret.dat -u sealed.pub -r sealed.priv
# === Unseal: PCR 7이 변하지 않았을 때만 성공 ===
# 4. 봉인 객체 로드
tpm2_load -C srk.ctx -u sealed.pub -r sealed.priv -c sealed.ctx
# 5. 정책 세션으로 Unseal
tpm2_startauthsession --policy-session -S session.ctx
tpm2_policypcr -S session.ctx -l sha256:7
tpm2_unseal -c sealed.ctx -p session:session.ctx -o recovered_secret.dat
# 복구된 비밀 확인
cat recovered_secret.dat
# 출력: my-super-secret-key-material
커널 trusted 키 타입
Linux 커널은 trusted 키 타입을 통해 TPM Seal/Unseal을 커널 키링 인프라에 통합합니다:
# TPM으로 봉인된 trusted 키 생성 (32바이트, PCR 7에 바인딩)
keyctl add trusted tpm-master "new 32 pcrinfo=0:sha256:7" @s
# 키 내용 조회 (봉인된 블롭 형태)
keyctl pipe > sealed_blob.bin
# 재부팅 후 봉인 블롭에서 키 복원
keyctl add trusted tpm-master "load $(cat sealed_blob.bin | base64)" @s
# trusted 키를 마스터로 encrypted 키 생성 (dm-crypt 등에 사용)
keyctl add encrypted dm-key "new ecb(aes) 64 trusted:tpm-master" @s
trusted vs encrypted 키: trusted 키는 TPM으로 봉인되어 TPM 없이는 절대 평문을 얻을 수 없습니다. encrypted 키는 마스터 키(trusted 또는 user 키)로 AES 암호화되며, 마스터 키를 알면 소프트웨어로 복호화 가능합니다. 최상의 보안을 위해 trusted 키를 마스터로, encrypted 키를 실제 암호화에 사용합니다. 자세한 내용은 키링 서비스 문서를 참고하세요.
복합 정책 봉인 예제
실무에서는 PCR 바인딩만으로는 부족하여, 비밀번호와 PCR을 조합하는 복합 정책(PolicyOR)을 사용합니다. 이를 통해 "PCR 7이 특정 값이고 비밀번호도 맞을 때" 또는 "관리자 비밀번호만으로도 해제 가능"한 유연한 정책을 구성할 수 있습니다:
# === 복합 정책: PolicyPCR + PolicyPassword (AND 조합) ===
# 1. SRK 생성
tpm2_createprimary -C o -c srk.ctx
# 2. 복합 정책 다이제스트 계산 (Trial 세션)
tpm2_startauthsession --trial-session -S trial.ctx
# PolicyPCR: PCR 7이 현재 값일 때만
tpm2_policypcr -S trial.ctx -l sha256:7
# PolicyAuthValue: 추가로 비밀번호도 필요
tpm2_policyauthvalue -S trial.ctx
# 정책 다이제스트 추출
tpm2_policygetdigest -S trial.ctx -o compound_policy.dgst
tpm2_flushcontext trial.ctx
# 3. 비밀 데이터 봉인 (정책 + 비밀번호 양쪽 모두 필요)
echo "top-secret-data" > secret.dat
tpm2_create -C srk.ctx -G keyedhash \
-L compound_policy.dgst \
-p "mysealpassword" \
-i secret.dat \
-u compound_sealed.pub -r compound_sealed.priv \
-a "fixedtpm|fixedparent"
# 4. Unseal (PCR + 비밀번호 양쪽 모두 제공)
tpm2_load -C srk.ctx -u compound_sealed.pub \
-r compound_sealed.priv -c compound_sealed.ctx
tpm2_startauthsession --policy-session -S session.ctx
tpm2_policypcr -S session.ctx -l sha256:7
tpm2_policyauthvalue -S session.ctx
tpm2_unseal -c compound_sealed.ctx \
-p "session:session.ctx+mysealpassword" \
-o recovered.dat
cat recovered.dat
# 출력: top-secret-data
# === PolicyOR: 복수 정책 중 하나만 만족하면 OK ===
# 정책 A: PCR 7 바인딩 (일반 부팅)
tpm2_startauthsession --trial-session -S trial_a.ctx
tpm2_policypcr -S trial_a.ctx -l sha256:7
tpm2_policygetdigest -S trial_a.ctx -o policy_a.dgst
tpm2_flushcontext trial_a.ctx
# 정책 B: 관리자 비밀번호만으로 해제 (복구용)
tpm2_startauthsession --trial-session -S trial_b.ctx
tpm2_policyauthvalue -S trial_b.ctx
tpm2_policygetdigest -S trial_b.ctx -o policy_b.dgst
tpm2_flushcontext trial_b.ctx
# PolicyOR: A 또는 B 중 하나만 만족하면 OK
tpm2_startauthsession --trial-session -S trial_or.ctx
tpm2_policyor -S trial_or.ctx \
-l sha256:policy_a.dgst,policy_b.dgst
tpm2_policygetdigest -S trial_or.ctx -o policyor.dgst
tpm2_flushcontext trial_or.ctx
# 봉인 (PolicyOR 정책 적용)
tpm2_create -C srk.ctx -G keyedhash \
-L policyor.dgst -p "admin-recovery-pw" \
-i secret.dat -u or_sealed.pub -r or_sealed.priv \
-a "fixedtpm|fixedparent"
TPM 기반 디스크 암호화 실무
systemd-cryptenroll 실무
# === 방법 1: Direct PCR Binding ===
# LUKS2 파티션에 TPM2 키슬롯 추가 (PCR 7 바인딩)
sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/sda3
# PCR 7+11 바인딩 + PIN 추가 (2FA)
sudo systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7+11 \
--tpm2-with-pin=yes /dev/sda3
# /etc/crypttab 설정
# root /dev/sda3 - tpm2-device=auto,tpm2-pcrs=7
# === 방법 2: Signed PCR Policy (권장) ===
# 서명 키 쌍 생성
openssl genpkey -algorithm RSA -out pcr-sign-key.pem -pkeyopt rsa_keygen_bits:2048
openssl pkey -in pcr-sign-key.pem -pubout -out pcr-sign-pubkey.pem
# 현재 부팅 예상 PCR 값 서명 (systemd-measure)
sudo systemd-measure sign \
--linux=/boot/vmlinuz \
--initrd=/boot/initrd.img \
--private-key=pcr-sign-key.pem \
--public-key=pcr-sign-pubkey.pem \
--phase=enter-initrd:leave-initrd:sysinit:ready \
> pcr-signature.json
# Signed Policy로 TPM 등록
sudo systemd-cryptenroll --tpm2-device=auto \
--tpm2-public-key=pcr-sign-pubkey.pem \
--tpm2-public-key-pcrs=11 \
--tpm2-signature=pcr-signature.json \
/dev/sda3
# === Clevis + Tang (Network-Bound Disk Encryption) ===
# Tang 서버 설정
sudo systemctl enable tangd.socket --now
# Clevis로 LUKS에 Tang 바인딩 추가
sudo clevis luks bind -d /dev/sda3 tang '{"url":"http://tang.local:7500"}'
# TPM + Tang 결합 (SSS: Shamir Secret Sharing)
sudo clevis luks bind -d /dev/sda3 sss '{"t":1,"pins":{"tpm2":{"pcr_ids":"7"},"tang":{"url":"http://tang.local:7500"}}}'
복구 전략 필수: TPM 봉인을 유일한 잠금 해제 수단으로 사용하면 안 됩니다. TPM이 고장나거나 교체되면 데이터에 접근할 수 없게 됩니다. 반드시 복구 키슬롯(패스프레이즈 또는 복구 키)을 함께 유지하세요: systemd-cryptenroll --recovery-key /dev/sda3
LUKS2 키슬롯 관리
LUKS2는 최대 32개의 키슬롯을 지원하며, TPM 봉인과 기존 패스프레이즈를 병행 관리할 수 있습니다:
# === LUKS2 키슬롯 상태 확인 ===
sudo cryptsetup luksDump /dev/sda3 | grep -A5 "Keyslots:"
# 0: luks2 (패스프레이즈)
# 1: luks2 (TPM2, systemd-cryptenroll)
# 2: luks2 (복구 키)
# === 키슬롯 상세 정보 ===
sudo systemd-cryptenroll /dev/sda3
# SLOT TYPE
# 0 password
# 1 tpm2
# 2 recovery
# === TPM 키슬롯 교체 (PCR 값 변경 후) ===
# 기존 TPM 키슬롯 제거
sudo systemd-cryptenroll --wipe-slot=tpm2 /dev/sda3
# 새 PCR 값으로 재등록
sudo systemd-cryptenroll --tpm2-device=auto \
--tpm2-pcrs=7 /dev/sda3
# === 복구 키 생성 (반드시 안전하게 백업!) ===
sudo systemd-cryptenroll --recovery-key /dev/sda3
# 출력: pufkqn-th84jv-x37qkm-... ← 이 키를 오프라인에 보관
# === FIDO2 보안 키 + TPM 결합 ===
sudo systemd-cryptenroll --fido2-device=auto \
--fido2-with-user-verification=yes /dev/sda3
Clevis + Tang (Network-Bound) 상세
Clevis/Tang은 네트워크 기반 디스크 암호화 해제(NBDE)를 제공합니다. 서버가 특정 네트워크에 접속되어 있을 때만 자동으로 디스크를 해제하므로, 물리적 도난 시 데이터를 보호합니다:
| 컴포넌트 | 역할 | 프로토콜 |
|---|---|---|
| Tang 서버 | ECMR(Elliptic Curve McCallum-Relyea) 키 교환 | HTTP REST, 상태 없음(stateless) |
| Clevis 클라이언트 | LUKS 키슬롯에 Tang 바인딩 추가 | Shamir Secret Sharing 지원 |
| clevis-dracut/initrd | 부팅 시 Tang 서버와 통신하여 자동 해제 | initrd 내 네트워크 초기화 필요 |
# === Tang 서버 설정 ===
sudo dnf install tang
sudo systemctl enable tangd.socket --now
# 기본 포트: 80 (systemd socket activation)
# Tang 서버 키 확인
tang-show-keys 80
# === Clevis 클라이언트 설정 ===
sudo dnf install clevis clevis-luks clevis-dracut
# LUKS에 Tang 바인딩 추가
sudo clevis luks bind -d /dev/sda3 tang \
'{"url":"http://tang.internal:80"}'
# === TPM + Tang 결합 (Shamir Secret Sharing) ===
# t=1: TPM 또는 Tang 둘 중 하나만 있으면 해제
sudo clevis luks bind -d /dev/sda3 sss '{
"t": 1,
"pins": {
"tpm2": {"pcr_ids": "7"},
"tang": {"url": "http://tang.internal:80"}
}
}'
# t=2: TPM과 Tang 모두 필요 (AND 조건)
sudo clevis luks bind -d /dev/sda3 sss '{
"t": 2,
"pins": {
"tpm2": {"pcr_ids": "7"},
"tang": {"url": "http://tang.internal:80"}
}
}'
# initrd 재생성 (부팅 시 자동 해제 지원)
sudo dracut -fv --regenerate-all
Tang vs Escrow 서버: Tang은 상태 없는(stateless) 서버로, 어떤 클라이언트의 키도 저장하지 않습니다. 이는 Tang 서버가 탈취되어도 암호화 키를 복구할 수 없음을 의미합니다. 반면 일반 키 에스크로 서버는 키를 저장하므로 서버 보안이 곧 데이터 보안입니다. Tang은 ECMR(McCallum-Relyea) 프로토콜을 사용하여, 서버와 클라이언트가 각각 비밀 없이 Diffie-Hellman 유사 키 교환을 수행합니다.
TPM과 커널 난수 생성기
TPM은 내장된 하드웨어 난수 생성기를 통해 Linux 커널의 엔트로피 풀에 기여합니다. CONFIG_HW_RANDOM_TPM이 활성화되면 TPM이 hwrng 프레임워크에 등록됩니다.
커널 통합
/* drivers/char/tpm/tpm-chip.c */
static int tpm_hwrng_read(struct hwrng *rng, void *data,
size_t max, bool wait)
{
struct tpm_chip *chip = container_of(rng, struct tpm_chip, hwrng);
/* TPM2_GetRandom 호출 — 최대 64바이트 */
return tpm_get_random(chip, data, min_t(size_t, max, 64));
}
/* hwrng 프레임워크에 등록 */
static int tpm_add_hwrng(struct tpm_chip *chip)
{
chip->hwrng.name = dev_name(&chip->dev);
chip->hwrng.read = tpm_hwrng_read;
chip->hwrng.quality = 1000; /* 높은 품질 — 1024 중 1000 */
return hwrng_register(&chip->hwrng);
}
성능 특성
| 특성 | dTPM (SPI) | fTPM | 비고 |
|---|---|---|---|
| 처리량(Throughput) | ~50 KB/s | ~500 KB/s | TPM 버스 속도에 제한 |
| 요청당 최대 | 64 바이트 | 64 바이트 | TPM2_GetRandom 제한 |
| 지연 | ~5-20 ms | ~1-5 ms | 명령 왕복 시간 |
| hwrng quality | 1000 | 1000 | 1024 중 1000 (높음) |
# hwrng 상태 확인
cat /sys/class/misc/hw_random/rng_current
# 출력: tpm-rng-0 또는 tpm0
# 사용 가능한 hwrng 목록
cat /sys/class/misc/hw_random/rng_available
# TPM에서 직접 난수 획득
tpm2_getrandom 32 --hex
# 외부 엔트로피를 TPM RNG에 주입
tpm2_stirrandom < /dev/urandom
hwrng 프레임워크와 TPM: 여러 hwrng 소스가 있을 때 quality 값이 가장 높은 소소가 기본 선택됩니다. TPM은 quality=1000으로 등록되어, 다른 hwrng 소스보다 높은 우선순위를 갖는 경우가 많습니다. 그러나 TPM의 처리량 한계로 인해 대량 난수 생성에는 CPU의 RDRAND/RDSEED나 전용 TRNG가 더 적합합니다. 자세한 내용은 HWRNG/TRNG 문서를 참고하세요.
TPM RNG 엔트로피 품질 분석
TPM의 내장 RNG는 물리적 노이즈 소스(열 노이즈, 메타스테이블 래치 등)를 사용하여 진정한 무작위 수를 생성합니다. 하지만 fTPM의 경우 소프트웨어 DRBG(Deterministic Random Bit Generator)를 사용할 수 있으므로 주의가 필요합니다:
| 특성 | dTPM (하드웨어 TRNG) | fTPM (DRBG 기반) | sTPM (OpenSSL PRNG) |
|---|---|---|---|
| 엔트로피 소스 | 물리적 노이즈 (열, 샷) | TEE 내 DRBG + 하드웨어 시드 | 호스트 OS PRNG |
| NIST SP 800-90B 준수 | 예 (인증 시) | 일부 (TEE 구현에 의존) | 아니오 |
| 예측 저항 (Prediction Resistance) | 지원 (TRNG 자체) | 구현에 따라 다름 | 미지원 |
| 재시드 빈도 | 매 요청 (연속 엔트로피) | 주기적 (DRBG 리시드 간격) | 호스트 커널 의존 |
| 부팅 초기 엔트로피 | 즉시 가용 | TEE 초기화 후 가용 | 호스트 엔트로피 풀에 의존 |
# TPM RNG 품질 테스트 (간이)
# 1. TPM에서 대량 난수 수집
for i in $(seq 1 1000); do
tpm2_getrandom 64 >> /tmp/tpm_random.bin
done
# 2. ent 명령으로 엔트로피 분석
ent /tmp/tpm_random.bin
# 엔트로피 = 7.999xxx bits/byte (이상적: 8.0)
# 카이제곱: 50% 부근이 이상적
# 3. rng-tools로 FIPS 140-2 테스트
rngtest < /tmp/tpm_random.bin
# rngtest: FIPS 140-2 successes: 998
# rngtest: FIPS 140-2 failures: 2 (2% 이하 허용)
# 4. TPM RNG를 기본 hwrng로 강제 설정
echo tpm-rng-0 | sudo tee /sys/class/misc/hw_random/rng_current
# 5. rngd로 TPM 엔트로피를 커널 풀에 자동 주입
sudo rngd -r /dev/hwrng -f -t 10
vTPM (Virtual TPM)
swtpm 설정
# swtpm 설치 (Fedora/RHEL)
sudo dnf install swtpm swtpm-tools
# TPM 상태 디렉토리 생성 및 초기화
mkdir -p /tmp/mytpm
swtpm_setup --tpmstate /tmp/mytpm --tpm2 \
--create-ek-cert --create-platform-cert \
--allow-signing --overwrite
# swtpm 실행 (Unix socket 모드)
swtpm socket --tpmstate dir=/tmp/mytpm \
--tpm2 --ctrl type=unixio,path=/tmp/mytpm/swtpm-sock \
--log level=20 &
# QEMU에서 swtpm 연결
qemu-system-x86_64 \
-machine q35,accel=kvm \
-chardev socket,id=chrtpm,path=/tmp/mytpm/swtpm-sock \
-tpmdev emulator,id=tpm0,chardev=chrtpm \
-device tpm-tis,tpmdev=tpm0 \
-drive file=disk.qcow2,if=virtio \
...
libvirt XML 설정
<!-- libvirt domain XML에 TPM 추가 -->
<devices>
<tpm model="tpm-tis">
<backend type="emulator" version="2.0">
<active_pcr_banks>
<sha256/>
<sha384/>
</active_pcr_banks>
</backend>
</tpm>
</devices>
vtpm_proxy 커널 모듈(Kernel Module)
CONFIG_TCG_VTPM_PROXY를 활성화하면 커널 내부에서 가상 TPM 장치를 생성할 수 있습니다. 주로 테스트와 개발 용도로 사용됩니다:
/* vtpm_proxy 사용 예시 */
int fd = open("/dev/vtpmx", O_RDWR);
struct vtpm_proxy_new_dev new_dev = {
.flags = VTPM_PROXY_FLAG_TPM2,
};
ioctl(fd, VTPM_PROXY_IOC_NEW_DEV, &new_dev);
/* new_dev.major, new_dev.minor — 새 /dev/tpmN 장치 번호
* new_dev.tpm_num — 할당된 TPM 번호
* fd를 통해 TPM 명령/응답을 사용자 공간에서 처리 */
vTPM 라이브 마이그레이션: vTPM 상태는 보안에 민감하므로 라이브 마이그레이션이 복잡합니다. swtpm은 상태를 직렬화하여 마이그레이션을 지원하지만, 마이그레이션 중 TPM 상태가 노출될 수 있습니다. 기밀 컴퓨팅 환경의 SVSM vTPM은 마이그레이션 시 증명 체인이 끊어지는 문제가 있으며, 현재 활발히 연구 중입니다. 자세한 내용은 기밀 컴퓨팅 문서를 참고하세요.
swtpm 내부 아키텍처
swtpm은 libtpms 라이브러리를 기반으로 TPM 2.0 명령을 완전히 소프트웨어로 구현합니다. 내부 구조는 다음과 같습니다:
| 컴포넌트 | 역할 | 설명 |
|---|---|---|
libtpms | TPM 상태 머신 | TPM 2.0 명령 프로세서, 암호 연산, PCR/NV/키 관리의 완전한 소프트웨어 구현. IBM의 ms-tpm-20-ref 기반 |
swtpm | 프로세스 래퍼 | libtpms를 데몬으로 래핑, socket/chardev/cuse 인터페이스 제공. QEMU/libvirt와 연동 |
swtpm_setup | 초기 프로비저닝 | EK 인증서 생성, 초기 상태 설정. swtpm_localca를 사용하여 자체 서명 EK 인증서 발급 |
swtpm_localca | 로컬 CA | EK/플랫폼 인증서를 발급하는 로컬 인증 기관 |
swtpm 상태 파일
# swtpm 상태 디렉토리 구조
ls -la /var/lib/swtpm-localca/
# issuercert.pem — 로컬 CA 인증서
# signkey.pem — 로컬 CA 서명 키
# certserial — 인증서 일련번호 카운터
# VM별 TPM 상태 디렉토리
ls -la /tmp/mytpm/
# tpm2-00.permall — 영구 상태 (NV, persistent 키, 계층 시드)
# tpm2-00.volatilestate — 휘발성 상태 (세션, PCR 값)
# swtpm-sock — Unix 소켓 (QEMU 연결용)
# swtpm 상태 백업 및 복원 (마이그레이션)
# 1. VM 일시 정지
# 2. swtpm에 SIGSTOP → volatilestate 파일 생성
# 3. tpm2-00.permall + tpm2-00.volatilestate 복사
# 4. 대상 호스트에서 swtpm 재시작 (동일 상태 로드)
# swtpm 고급 옵션
swtpm socket --tpmstate dir=/var/lib/swtpm/vm1 \
--tpm2 \
--ctrl type=unixio,path=/var/run/swtpm-vm1.sock \
--flags not-need-init,startup-clear \
--seccomp action=log \
--pid file=/var/run/swtpm-vm1.pid \
--log level=5,file=/var/log/swtpm-vm1.log &
SVSM vTPM
SVSM(Secure VM Service Module)은 AMD SEV-SNP의 VMPL(Virtual Machine Privilege Level) 아키텍처를 활용하여, 하이퍼바이저(Hypervisor)로부터 격리된 vTPM을 구현합니다:
coconut-svsm 프로젝트의 vTPM 구현 특징:
- Rust 기반 TCB: coconut-svsm은 Rust로 작성되어 메모리 안전성이 보장됩니다. 전체 SVSM 바이너리는 약 30KB로, 일반 OS 커널(수 MB)에 비해 공격 표면이 극히 작습니다.
- VMPL 격리: vTPM 상태(키, PCR, NV 데이터)는 VMPL 0 메모리에만 존재하므로, Guest OS(VMPL 2-3)나 하이퍼바이저가 접근할 수 없습니다.
- SVSM 호출 인터페이스: Guest는
VMGEXIT명령으로 SVSM에 TPM 명령을 전달합니다. SVSM 프로토콜은 공유 메모리 버퍼(Guest가 쓰기, SVSM이 읽기)를 통해 명령/응답을 교환합니다. - 증명 체인: SEV-SNP 증명 보고서에 SVSM의 측정값(VMPL 0 코드 해시)이 포함되므로, 원격 검증자는 vTPM 구현의 무결성까지 검증할 수 있습니다.
SVSM vTPM 사용 사례: 클라우드 환경에서 vTPM은 일반적으로 하이퍼바이저가 제공하므로, 하이퍼바이저 관리자가 vTPM 상태를 열람할 수 있습니다. SVSM vTPM은 이 문제를 해결하여, 기밀 VM의 TPM 상태를 CSP(Cloud Service Provider)로부터 완전히 보호합니다. AWS Nitro Enclaves, Azure Confidential VMs, Google Confidential Space 등에서 유사한 개념이 적용되고 있습니다. 자세한 내용은 기밀 컴퓨팅의 SVSM 섹션을 참고하세요.
vtpm_proxy 커널 모듈 상세
CONFIG_TCG_VTPM_PROXY를 활성화하면 사용자 공간에서 TPM 명령을 처리하는 가상 TPM 장치를 생성할 수 있습니다. 이는 TPM 드라이버 개발, CI/CD 파이프라인(Pipeline), 그리고 swtpm 없이도 간단한 TPM 테스트를 수행할 때 유용합니다:
/* vtpm_proxy 사용 — 완전한 예제 */
#include <linux/vtpm_proxy.h>
#include <sys/ioctl.h>
#include <fcntl.h>
int main(void) {
int fd, vtpm_fd;
struct vtpm_proxy_new_dev new_dev = {
.flags = VTPM_PROXY_FLAG_TPM2, /* TPM 2.0 모드 */
};
/* 1. /dev/vtpmx 열기 */
fd = open("/dev/vtpmx", O_RDWR);
/* 2. 새 vTPM 장치 생성 */
ioctl(fd, VTPM_PROXY_IOC_NEW_DEV, &new_dev);
/* → /dev/tpmN 과 /dev/tpmrmN 이 자동 생성됨
* new_dev.major: 장치 주 번호
* new_dev.minor: 장치 부 번호
* new_dev.tpm_num: 할당된 TPM 번호 (N) */
vtpm_fd = new_dev.fd; /* TPM 명령을 수신할 fd */
/* 3. 이벤트 루프: TPM 명령 수신 → 처리 → 응답 전송 */
while (1) {
u8 cmd[4096], rsp[4096];
ssize_t cmd_len, rsp_len;
/* read()로 TPM 명령 수신 (blocking) */
cmd_len = read(vtpm_fd, cmd, sizeof(cmd));
/* TPM 명령 처리 (libtpms 또는 커스텀 로직) */
rsp_len = process_tpm_command(cmd, cmd_len, rsp);
/* write()로 TPM 응답 전송 */
write(vtpm_fd, rsp, rsp_len);
}
return 0;
}
# vtpm_proxy 모듈 로드
sudo modprobe tpm_vtpm_proxy
# /dev/vtpmx 확인
ls -la /dev/vtpmx
# crw------- 1 root root 10, 123 ... /dev/vtpmx
# swtpm을 vtpm_proxy 백엔드로 사용 (테스트 용도)
swtpm chardev --vtpm-proxy \
--tpmstate dir=/tmp/vtpm-test \
--tpm2 \
--flags startup-clear
# → /dev/tpmN 자동 생성, tpm2-tools로 직접 접근 가능
# CI/CD에서 TPM 기능 테스트
export TPM2TOOLS_TCTI=device:/dev/tpm1
tpm2_startup -c
tpm2_pcrread sha256:0
vtpm_proxy vs swtpm: vtpm_proxy는 커널 수준의 가상 TPM 장치를 제공하여, 사용자 공간 프로그램이 /dev/tpmN과 동일한 인터페이스로 접근할 수 있습니다. swtpm은 QEMU의 chardev 백엔드로 동작하여 VM에 vTPM을 제공합니다. 커널 TPM 서브시스템 개발이나 TPM 의존 소프트웨어의 CI 테스트에는 vtpm_proxy + swtpm 조합이 가장 편리합니다.
TPM 프로비저닝과 오너십
TPM 프로비저닝은 TPM을 처음 사용하기 위해 계층별 인가 값을 설정하고, EK 인증서를 확인하며, 기본 키 구조를 생성하는 과정입니다.
인가 값 설정
# 계층별 인가 값(비밀번호) 설정
# Owner (Storage) Hierarchy
tpm2_changeauth -c o new_owner_password
# Endorsement Hierarchy
tpm2_changeauth -c e new_endorsement_password
# Lockout (Dictionary Attack 복구)
tpm2_changeauth -c l new_lockout_password
# Platform Hierarchy (일반적으로 BIOS가 설정)
tpm2_changeauth -c p new_platform_password
# 인가 값 변경 (기존 비밀번호 필요)
tpm2_changeauth -c o -p old_owner_password new_owner_password
EK 인증서 관리
# EK 인증서 NV 인덱스에서 추출
tpm2_nvread 0x01C00002 -o ek_cert_rsa.der
openssl x509 -inform DER -in ek_cert_rsa.der -text -noout
# ECC EK 인증서
tpm2_nvread 0x01C0000A -o ek_cert_ecc.der
# EK 공개키 직접 생성 (인증서 없는 경우)
tpm2_createek -c ek.ctx -G rsa2048 -u ek.pub
# 온라인 EK 인증서 다운로드 (제조사 서버)
tpm2_getekcertificate -o ek_cert.pem -u ek.pub \
https://ekop.intel.com/ekcertservice/
Dictionary Attack 보호
TPM은 사전 공격(Dictionary Attack)을 방어하기 위해 인가 실패 횟수를 추적하고, 임계값 초과 시 잠금(lockout)합니다:
# DA 파라미터 설정
tpm2_dictionarylockout --setup-parameters \
--max-tries=5 \
--recovery-time=600 \
--lockout-recovery-time=86400
# DA 잠금 해제 (lockout 인가 필요)
tpm2_dictionarylockout --clear-lockout -p lockout_password
# 현재 DA 상태 확인
tpm2_getcap properties-variable | grep -E "lockout|DA"
TPM2_Clear
# TPM 초기화 (Storage Hierarchy 전체 삭제!)
# 주의: 모든 SH 키, NV 인덱스, 인가 값이 삭제됩니다
tpm2_clear -c p # Platform 인가로 Clear
# Clear 후 영향:
# - shSeed 재생성 → 모든 SH Primary Key 변경
# - ownerAuth, endorsementAuth, lockoutAuth → 빈 값으로 리셋
# - 모든 persistent 객체 삭제 (SH)
# - 모든 NV 인덱스 삭제 (PLATFORMCREATE 제외)
# - epSeed 유지 → EK는 동일하게 재생성 가능
TPM2_Clear의 파괴적 영향: TPM2_Clear는 Storage Hierarchy의 모든 데이터를 삭제합니다. LUKS2 TPM 봉인 키, trusted 키, 어플리케이션 키가 모두 무효화되어 접근 불가능해집니다. Clear를 실행하기 전에 반드시 모든 TPM 의존 서비스의 복구 경로(복구 키, 패스프레이즈 등)를 확인하세요.
NV 인덱스 고급 활용
TPM NV(Non-Volatile) 저장 영역은 단순한 데이터 저장 외에도 카운터, 비트 필드, 확장(extend) 인덱스 등 다양한 특수 유형을 지원합니다:
| NV 유형 | 속성 | 동작 | 용도 |
|---|---|---|---|
| Ordinary | nt=0 | 일반 읽기/쓰기 | 인증서, 설정 데이터 저장 |
| Counter | nt=counter | 단조 증가만 가능 (롤백 불가) | 부팅 카운터, 안티롤백 |
| Bit Field | nt=bits | 비트 OR만 가능 (한번 설정하면 해제 불가) | 일회성 플래그, 기능 락 |
| Extend | nt=extend | PCR처럼 extend만 가능 | 커스텀 측정, 로그 무결성 |
| PIN Pass | nt=pinfail | 카운터 + DA 보호 연동 | PIN 실패 횟수 추적 |
# === NV 인덱스 유형별 사용 예제 ===
# 1. Ordinary: EK 인증서 저장 (기본 NV)
tpm2_nvdefine 0x01800001 -C o -s 2048 \
-a "ownerread|ownerwrite|authread|authwrite"
echo "my-application-config-data" | \
tpm2_nvwrite 0x01800001 -C o -i -
tpm2_nvread 0x01800001 -C o
# 2. Counter: 부팅 카운터 (안티롤백)
tpm2_nvdefine 0x01800002 -C o -s 8 \
-a "ownerread|ownerwrite|nt=counter"
tpm2_nvincrement 0x01800002 -C o
tpm2_nvread 0x01800002 -C o -s 8 | xxd
# 3. Bit Field: 일회성 프로비저닝 플래그
tpm2_nvdefine 0x01800003 -C o -s 8 \
-a "ownerread|ownerwrite|nt=bits"
# 비트 0 설정 (한번 설정하면 해제 불가)
tpm2_nvsetbits 0x01800003 -C o -i 0x01
# 비트 1도 설정
tpm2_nvsetbits 0x01800003 -C o -i 0x02
# 현재 값: 0x03 (비트 0과 1이 설정됨)
# 4. Extend: PCR과 유사한 해시 체인
tpm2_nvdefine 0x01800004 -C o -s 32 \
-a "ownerread|ownerwrite|nt=extend"
echo "event1" | sha256sum | awk '{print $1}' | xxd -r -p | \
tpm2_nvextend 0x01800004 -C o -i -
echo "event2" | sha256sum | awk '{print $1}' | xxd -r -p | \
tpm2_nvextend 0x01800004 -C o -i -
# 5. 쓰기 잠금 (Write Once — 재부팅까지 잠금)
tpm2_nvdefine 0x01800005 -C o -s 64 \
-a "ownerread|ownerwrite|write_stclear"
echo "immutable-after-write" | \
tpm2_nvwrite 0x01800005 -C o -i -
tpm2_nvwritelock 0x01800005 -C o
# 이후 쓰기 시도 → TPM_RC_NV_LOCKED
# 재부팅하면 잠금 해제
# NV 인덱스 목록 조회
tpm2_getcap handles-nv-index
# NV 인덱스 정보 조회
tpm2_nvreadpublic 0x01800001
TCG 표준 NV 인덱스
| NV 인덱스 범위 | 소유자 | 용도 |
|---|---|---|
0x01C00002 | TCG (Endorsement) | RSA 2048 EK 인증서 |
0x01C0000A | TCG (Endorsement) | ECC P-256 EK 인증서 |
0x01C00003 | TCG (Endorsement) | RSA EK nonce |
0x01C00004 | TCG (Endorsement) | RSA EK 템플릿 |
0x01400001 | TCG (Platform) | PPI (Physical Presence Interface) 요청 |
0x01800000-0x01BFFFFF | Owner | 사용자 정의 NV 인덱스 |
0x01C00000-0x01FFFFFF | Endorsement | TCG 정의 NV 인덱스 |
프로비저닝 자동화
대규모 배포 환경에서 TPM 프로비저닝을 자동화하는 방법:
#!/bin/bash
# TPM 프로비저닝 자동화 스크립트
set -euo pipefail
# 1. TPM 기본 상태 확인
if ! tpm2_getcap properties-fixed > /dev/null 2>&1; then
echo "ERROR: TPM 장치에 접근할 수 없습니다"
exit 1
fi
# 2. TPM 버전 확인
TPM_VERSION=$(cat /sys/class/tpm/tpm0/tpm_version_major)
if [ "$TPM_VERSION" != "2" ]; then
echo "ERROR: TPM 2.0이 필요합니다 (현재: TPM $TPM_VERSION)"
exit 1
fi
# 3. EK 생성 및 인증서 확인
tpm2_createek -c /tmp/ek.ctx -G rsa2048 -u /tmp/ek.pub
if tpm2_nvread 0x01C00002 -o /tmp/ek_cert.der 2>/dev/null; then
echo "EK 인증서 존재 — 제조사 CA 검증 가능"
openssl x509 -inform DER -in /tmp/ek_cert.der \
-subject -issuer -noout
else
echo "WARNING: EK 인증서 없음 — 자체 생성된 EK"
fi
# 4. Owner/Endorsement 인가 값 설정 (빈 값이면)
if tpm2_changeauth -c o "$(openssl rand -hex 16)" 2>/dev/null; then
echo "Owner 인가 값 설정 완료"
else
echo "Owner 인가 값이 이미 설정되어 있음"
fi
# 5. SRK 영구 핸들 등록
tpm2_createprimary -C o -G rsa2048 -g sha256 -c /tmp/srk.ctx
tpm2_evictcontrol -C o -c /tmp/srk.ctx 0x81000001 || \
echo "SRK가 이미 0x81000001에 존재함"
# 6. AK 생성 및 영구 핸들 등록
tpm2_createak -C /tmp/ek.ctx -c /tmp/ak.ctx \
-u /tmp/ak.pub -n /tmp/ak.name -G rsa2048
tpm2_evictcontrol -C o -c /tmp/ak.ctx 0x81010001 || \
echo "AK가 이미 0x81010001에 존재함"
# 7. DA 보호 파라미터 설정
tpm2_dictionarylockout --setup-parameters \
--max-tries=10 \
--recovery-time=300 \
--lockout-recovery-time=3600
echo "TPM 프로비저닝 완료"
echo " EK pub: /tmp/ek.pub"
echo " AK pub: /tmp/ak.pub"
echo " SRK handle: 0x81000001"
echo " AK handle: 0x81010001"
프로비저닝 보안 고려사항: 프로비저닝 스크립트에서 설정한 인가 값(비밀번호)은 반드시 안전하게 저장해야 합니다. 인가 값을 분실하면 해당 계층의 객체에 접근할 수 없으며, TPM2_Clear만이 복구 방법입니다. 서버 환경에서는 인가 값을 HashiCorp Vault나 AWS Secrets Manager 같은 비밀 관리 시스템에 저장하고, 프로비저닝 이후 로컬 복사본을 삭제하는 것을 권장합니다.
TPM 리소스 한계
TPM은 제한된 하드웨어 리소스를 가지므로, 동시에 로드 가능한 객체와 세션 수에 주의해야 합니다:
| 리소스 | 일반적인 한계 | 확인 방법 | 비고 |
|---|---|---|---|
| Transient 객체 슬롯 | 3개 | TPM_PT_HR_TRANSIENT_MIN | 초과 시 TPM_RC_OBJECT_MEMORY |
| 활성 세션 슬롯 | 64개 | TPM_PT_HR_LOADED_MIN | HMAC + Policy 세션 합산 |
| NV 영역 크기 | ~8-16 KB | TPM_PT_NV_BUFFER_MAX | NV 인덱스 정의 + 데이터 합산 |
| Persistent 객체 | ~7개 | TPM_PT_HR_PERSISTENT_MIN | evictcontrol로 영구 저장된 키 |
| PCR 뱅크 | SHA-1 + SHA-256 (+ SHA-384) | tpm2_pcrread | BIOS에서 활성 뱅크 설정 |
# TPM 리소스 한계 확인
tpm2_getcap properties-fixed | grep -E "TRANSIENT|LOADED|PERSISTENT|NV_BUFFER"
# TPM_PT_HR_TRANSIENT_MIN: 0x3 ← 3개 transient 슬롯
# TPM_PT_HR_PERSISTENT_MIN: 0x7 ← 7개 persistent 핸들
# TPM_PT_NV_BUFFER_MAX: 0x800 ← 단일 NV read/write 최대 2048 바이트
# 현재 사용 중인 핸들 확인
tpm2_getcap handles-transient
tpm2_getcap handles-persistent
tpm2_getcap handles-loaded-session
# transient 슬롯 정리
tpm2_flushcontext -t # 모든 transient 객체 flush
tpm2_flushcontext -s # 모든 세션 flush
tpm2-tools 종합 가이드
tpm2-tss 계층별 비교
| 계층 | 추상화 수준 | 세션 관리 | 암호 연산 | 용도 |
|---|---|---|---|---|
| FAPI | 최고 (JSON, key path) | 자동 | 자동 | 어플리케이션 통합, 프로비저닝 |
| ESAPI | 높음 (ESYS_TR 핸들) | 자동 | 자동 | tpm2-tools, 일반 개발 |
| SAPI | 낮음 (1:1 명령 매핑) | 수동 | 수동 | 성능 최적화, 커스텀 프로토콜 |
TCTI 전송 인터페이스
| TCTI | 대상 | 환경 변수 |
|---|---|---|
device | /dev/tpmrm0 | TPM2TOOLS_TCTI=device:/dev/tpmrm0 |
tabrmd | tpm2-abrmd (D-Bus) | TPM2TOOLS_TCTI=tabrmd:bus_name=com.intel.tss2.Tabrmd |
mssim | MS TPM Simulator | TPM2TOOLS_TCTI=mssim:host=localhost,port=2321 |
swtpm | swtpm Unix socket | TPM2TOOLS_TCTI=swtpm:path=/tmp/swtpm-sock |
실전 레시피 모음
# === RSA 키 생성 및 서명 ===
tpm2_createprimary -C o -c srk.ctx
tpm2_create -C srk.ctx -G rsa2048:rsassa-sha256 -u rsa.pub -r rsa.priv \
-a "sign|fixedtpm|fixedparent|sensitivedataorigin|userwithauth"
tpm2_load -C srk.ctx -u rsa.pub -r rsa.priv -c rsa.ctx
echo "sign this" | tpm2_sign -c rsa.ctx -g sha256 -o rsa_sig.dat
# === ECC 키 생성 및 서명 ===
tpm2_create -C srk.ctx -G ecc256:ecdsa-sha256 -u ecc.pub -r ecc.priv \
-a "sign|fixedtpm|fixedparent|sensitivedataorigin|userwithauth"
tpm2_load -C srk.ctx -u ecc.pub -r ecc.priv -c ecc.ctx
echo "sign this" | tpm2_sign -c ecc.ctx -g sha256 -o ecc_sig.dat
# === RSA 암호화/복호화 ===
tpm2_create -C srk.ctx -G rsa2048:oaep-sha256 -u enc.pub -r enc.priv \
-a "decrypt|fixedtpm|fixedparent|sensitivedataorigin|userwithauth"
tpm2_load -C srk.ctx -u enc.pub -r enc.priv -c enc.ctx
echo "plaintext" | tpm2_rsaencrypt -c enc.ctx -o cipher.dat
tpm2_rsadecrypt -c enc.ctx -o plain.dat cipher.dat
# === NV 카운터 ===
tpm2_nvdefine 0x01500001 -s 8 -a "ownerread|ownerwrite|nt=counter"
tpm2_nvincrement 0x01500001
tpm2_nvread 0x01500001 | xxd
# === 난수 생성 ===
tpm2_getrandom 32 --hex
tpm2_getrandom 1024 -o random.bin
# === TPM 기능 조회 ===
tpm2_getcap algorithms
tpm2_getcap commands
tpm2_getcap properties-fixed
tpm2_getcap handles-persistent
tpm2_getcap handles-transient
실전 레시피 추가
# === PCR 정책으로 봉인된 ECC 키 생성 + 서명 검증 ===
# 1. SRK 생성
tpm2_createprimary -C o -c srk.ctx
# 2. PCR 정책 다이제스트 계산 (PCR 0+7)
tpm2_startauthsession --trial-session -S trial.ctx
tpm2_policypcr -S trial.ctx -l sha256:0,7
tpm2_policygetdigest -S trial.ctx -o pcr_policy.dgst
tpm2_flushcontext trial.ctx
# 3. ECC P-256 서명 키 생성 (PCR 정책 바인딩)
tpm2_create -C srk.ctx -G ecc256:ecdsa-sha256 \
-u ecc_sign.pub -r ecc_sign.priv \
-L pcr_policy.dgst \
-a "sign|fixedtpm|fixedparent|sensitivedataorigin"
# 4. 키 로드
tpm2_load -C srk.ctx -u ecc_sign.pub -r ecc_sign.priv -c ecc_sign.ctx
# 5. PCR 정책 세션으로 서명
echo "Hello TPM" > message.txt
sha256sum message.txt | awk '{print $1}' | xxd -r -p > msg_digest.bin
tpm2_startauthsession --policy-session -S session.ctx
tpm2_policypcr -S session.ctx -l sha256:0,7
tpm2_sign -c ecc_sign.ctx -g sha256 \
-o ecc_sig.dat -p session:session.ctx msg_digest.bin
# 6. 서명 검증 (공개키 추출 + OpenSSL 검증)
tpm2_readpublic -c ecc_sign.ctx -f pem -o ecc_pubkey.pem
openssl dgst -sha256 -verify ecc_pubkey.pem \
-signature ecc_sig.dat message.txt
# === NV 카운터 생성 + 증가 + 읽기 ===
# 단조 증가 카운터 (Monotonic Counter) 정의
tpm2_nvdefine 0x01500010 -C o -s 8 \
-a "ownerread|ownerwrite|nt=counter"
# 카운터 증가 (총 3회)
tpm2_nvincrement 0x01500010 -C o
tpm2_nvincrement 0x01500010 -C o
tpm2_nvincrement 0x01500010 -C o
# 카운터 값 읽기
tpm2_nvread 0x01500010 -C o -s 8 | xxd
# 출력: 00000000: 0000 0000 0000 0003 ........
# NV 인덱스 삭제
tpm2_nvundefine 0x01500010 -C o
# === 키 인증 (TPM2_Certify) ===
# AK가 특정 키가 이 TPM에 존재함을 인증
# 1. AK 생성
tpm2_createek -c ek.ctx
tpm2_createak -C ek.ctx -c ak.ctx -u ak.pub -n ak.name
# 2. 대상 키 생성
tpm2_createprimary -C o -c srk.ctx
tpm2_create -C srk.ctx -G rsa2048:rsassa-sha256 \
-u target.pub -r target.priv -c target.ctx
# 3. AK로 대상 키 인증 (Certify)
tpm2_certify -c target.ctx -C ak.ctx \
-g sha256 -o certify.out -s certify.sig
# 4. 인증 검증
tpm2_verifysignature -c ak.ctx -g sha256 \
-m certify.out -s certify.sig
# === HMAC 세션 사용 ===
# 명령 파라미터의 무결성을 HMAC으로 보호
# 비밀번호로 보호된 키 생성
tpm2_createprimary -C o -c srk.ctx
tpm2_create -C srk.ctx -G rsa2048:rsassa-sha256 \
-u hmac_key.pub -r hmac_key.priv -p "myKeyAuth" \
-a "sign|fixedtpm|fixedparent|sensitivedataorigin|userwithauth"
tpm2_load -C srk.ctx -u hmac_key.pub -r hmac_key.priv -c hmac_key.ctx
# HMAC 세션 시작 (명령 파라미터 보호)
tpm2_startauthsession --hmac-session -S hmac_session.ctx
# HMAC 세션 + 비밀번호로 서명
echo "data" | tpm2_sign -c hmac_key.ctx -g sha256 \
-o sig.dat -p "session:hmac_session.ctx+myKeyAuth"
tpm2_flushcontext hmac_session.ctx
# === EK 인증서 추출 + 검증 ===
# RSA EK 인증서 NV에서 추출
tpm2_nvread 0x01C00002 -o ek_rsa_cert.der
# 인증서 내용 확인
openssl x509 -inform DER -in ek_rsa_cert.der -text -noout | head -20
# Subject: ...Infineon/Nuvoton/STMicro...
# Issuer: TPM 제조사 CA
# 인증서 체인 검증 (제조사 CA 인증서 필요)
# Infineon 예시:
wget -q https://pki.infineon.com/OptigaRsaMfrCA049/OptigaRsaMfrCA049.crt
openssl verify -CAfile OptigaRsaMfrCA049.crt ek_rsa_cert.der
# ECC EK 인증서 (별도 NV 인덱스)
tpm2_nvread 0x01C0000A -o ek_ecc_cert.der 2>/dev/null || \
echo "ECC EK 인증서 없음 (이 TPM은 RSA EK만 지원)"
# 온라인 EK 인증서 다운로드 (Intel PTT)
tpm2_createek -c ek.ctx -G rsa2048 -u ek.pub
tpm2_getekcertificate -o ek_cert_online.pem -u ek.pub \
https://ekop.intel.com/ekcertservice/
tpm2-abrmd 상세
tpm2-abrmd(Access Broker and Resource Manager Daemon)는 D-Bus 기반의 사용자 공간 TPM 리소스 관리자입니다. 커널의 /dev/tpmrm0이 도입되기 전에 멀티프로세스 TPM 접근을 관리하기 위해 개발되었습니다:
| 비교 항목 | /dev/tpmrm0 (커널 RM) | tpm2-abrmd |
|---|---|---|
| 위치 | 커널 내부 | 사용자 공간 데몬 |
| IPC 메커니즘 | 파일 디스크립터(File Descriptor) | D-Bus (system bus) |
| TCTI 이름 | device:/dev/tpmrm0 | tabrmd:bus_name=com.intel.tss2.Tabrmd |
| 세션 관리 | fd별 자동 context save/load | D-Bus 연결별 context save/load |
| 추가 설정 | 불필요 | systemd 서비스, D-Bus 정책 필요 |
| 권장 환경 | 일반 용도 (기본 권장) | 레거시, 커스텀 접근 제어(Access Control) 필요 시 |
# tpm2-abrmd 설치 및 설정
sudo dnf install tpm2-abrmd
sudo systemctl enable tpm2-abrmd --now
# D-Bus 서비스 확인
dbus-send --system --print-reply \
--dest=com.intel.tss2.Tabrmd \
/com/intel/tss2/Tabrmd \
org.freedesktop.DBus.Introspectable.Introspect
# tpm2-tools에서 abrmd TCTI 사용
export TPM2TOOLS_TCTI="tabrmd:bus_name=com.intel.tss2.Tabrmd"
tpm2_pcrread sha256:0
# abrmd 상태 확인
systemctl status tpm2-abrmd
journalctl -u tpm2-abrmd -f
FAPI 활용
FAPI(Feature API)는 tpm2-tss의 최상위 추상화 계층으로, JSON 기반 프로파일과 키 경로(key path) 개념을 통해 TPM을 훨씬 간단하게 사용할 수 있습니다.
FAPI 구성
// /etc/tpm2-tss/fapi-config.json
{
"profile_name": "P_ECCP256",
"profile_dir": "/etc/tpm2-tss/fapi-profiles/",
"user_dir": "~/.local/share/tpm2-tss/user/keystore",
"system_dir": "/var/lib/tpm2-tss/system/keystore",
"tcti": "device:/dev/tpmrm0",
"log_dir": "/var/log/tpm2-tss",
"ek_cert_less": "yes"
}
FAPI 키 경로 체계
| 경로 | 설명 |
|---|---|
/P_ECCP256 | 프로파일 루트 (ECC P-256 프로파일) |
/P_ECCP256/HS | Storage Hierarchy |
/P_ECCP256/HS/SRK | Storage Root Key |
/P_ECCP256/HS/SRK/mykey | 사용자 정의 자식 키 |
/P_ECCP256/HE | Endorsement Hierarchy |
/P_ECCP256/HE/EK | Endorsement Key |
/P_ECCP256/HN | Null Hierarchy |
/ext/mykey | 외부 키 (TPM 외부) |
FAPI C 프로그래밍
#include <tss2/tss2_fapi.h>
int main(void) {
FAPI_CONTEXT *ctx = NULL;
TSS2_RC rc;
uint8_t *signature = NULL;
size_t sig_size;
char *sig_json = NULL;
char *cert = NULL;
/* 1. FAPI 컨텍스트 초기화 */
rc = Fapi_Initialize(&ctx, NULL);
/* 2. TPM 프로비저닝 (최초 1회) */
rc = Fapi_Provision(ctx, NULL, NULL, NULL);
/* 3. 서명 키 생성 */
rc = Fapi_CreateKey(ctx,
"/P_ECCP256/HS/SRK/mySignKey", /* 키 경로 */
"sign,noDa", /* 속성 */
NULL, /* 정책 경로 */
NULL /* authValue */
);
/* 4. 서명 수행 */
uint8_t digest[32] = { 0x01, 0x02, /* ... */ };
rc = Fapi_Sign(ctx,
"/P_ECCP256/HS/SRK/mySignKey",
NULL, /* padding scheme */
digest, sizeof(digest),
&signature, &sig_size,
&sig_json, &cert
);
/* 5. 정리 */
Fapi_Free(signature);
Fapi_Free(sig_json);
Fapi_Free(cert);
Fapi_Finalize(&ctx);
return 0;
}
tss2 CLI 도구
# FAPI 프로비저닝
tss2 provision
# 서명 키 생성
tss2 createkey --path /P_ECCP256/HS/SRK/mykey --type sign,noDa
# 서명
echo "data to sign" > data.txt
tss2 sign --keyPath /P_ECCP256/HS/SRK/mykey --digest data.txt --signature sig.pem
# 암호화
tss2 encrypt --keyPath /P_ECCP256/HS/SRK/myEncKey --plainText plain.txt --cipherText cipher.json
# 키 목록 조회
tss2 list --searchPath /
# 키 삭제
tss2 delete --path /P_ECCP256/HS/SRK/mykey
FAPI vs ESAPI 선택 기준: 일반적인 어플리케이션(키 생성, 서명, 암호화)은 FAPI로 충분하며, 코드량이 ESAPI 대비 1/3 수준입니다. 그러나 세밀한 세션 제어, 커스텀 인가 정책, PCR 직접 조작이 필요하면 ESAPI를 사용하세요. SAPI는 성능 최적화나 비표준 TPM 확장 명령에만 권장됩니다.
TPM2 PKCS#11 통합
tpm2-pkcs11은 TPM을 PKCS#11 토큰으로 노출하여, OpenSSL, OpenSSH, Firefox/Chrome 등 PKCS#11을 지원하는 어플리케이션에서 TPM 키를 직접 사용할 수 있게 합니다:
# tpm2-pkcs11 설치
sudo dnf install tpm2-pkcs11 tpm2-pkcs11-tools
# PKCS#11 토큰 초기화
tpm2_ptool init --primary-auth=owner_password
tpm2_ptool addtoken --pid=1 --sopin=sopin123 --userpin=userpin123 \
--label=tpm-token
# RSA 서명 키 추가
tpm2_ptool addkey --algorithm=rsa2048 --label=tpm-token \
--userpin=userpin123 --key-label=my-rsa-key
# ECC 서명 키 추가
tpm2_ptool addkey --algorithm=ecc256 --label=tpm-token \
--userpin=userpin123 --key-label=my-ecc-key
# PKCS#11 모듈 경로
PKCS11_MODULE="/usr/lib64/pkcs11/libtpm2_pkcs11.so"
# OpenSSL에서 TPM 키로 서명
openssl dgst -sha256 -sign "pkcs11:token=tpm-token;object=my-rsa-key" \
-engine pkcs11 -keyform engine \
-out signature.bin document.txt
# OpenSSH에서 TPM 키를 SSH 키로 사용
ssh-keygen -D "$PKCS11_MODULE" > tpm_ssh_pubkey.pub
# → 이 공개키를 서버의 authorized_keys에 등록
ssh -I "$PKCS11_MODULE" user@server
# pkcs11-tool로 토큰 정보 조회
pkcs11-tool --module="$PKCS11_MODULE" --list-objects
# pkcs11-tool로 서명 수행
pkcs11-tool --module="$PKCS11_MODULE" \
--sign --mechanism SHA256-RSA-PKCS \
--label my-rsa-key --pin userpin123 \
--input-file data.bin --output-file sig.bin
OpenSSL TPM2 Provider
tpm2-openssl은 OpenSSL 3.x의 Provider 인터페이스를 통해 TPM 키를 투명하게 사용할 수 있게 합니다:
# tpm2-openssl 설치
sudo dnf install tpm2-openssl
# TPM에 RSA 키 생성
openssl genpkey -provider tpm2 -algorithm RSA -pkeyopt bits:2048 \
-out tpm_rsa_key.pem
# TPM 키로 CSR(인증서 서명 요청) 생성
openssl req -new -provider tpm2 -provider default \
-key tpm_rsa_key.pem -out request.csr \
-subj "/CN=TPM-Protected-Server"
# TPM 키로 TLS 서버 실행
openssl s_server -provider tpm2 -provider default \
-cert server.crt -key tpm_rsa_key.pem \
-port 4433
TPM 키 보호 TLS: tpm2-openssl이나 tpm2-pkcs11을 사용하면 TLS 서버의 개인키를 TPM 내부에 보호할 수 있습니다. 개인키가 파일시스템에 평문으로 존재하지 않으므로, 서버가 해킹되더라도 개인키 탈취가 불가능합니다. Nginx는 PKCS#11 엔진을, Apache httpd는 mod_ssl의 SSLCryptoDevice를 통해 TPM 키를 사용할 수 있습니다.
커널 설정 옵션
TPM 관련 Kconfig
| 설정 | 모듈 | 설명 |
|---|---|---|
CONFIG_TCG_TPM | tpm | TPM 서브시스템 코어 — 필수 |
CONFIG_TCG_TIS | tpm_tis | TIS 인터페이스 (LPC/SPI dTPM) |
CONFIG_TCG_TIS_SPI | tpm_tis_spi | SPI 버스 TPM (현대 dTPM 주류) |
CONFIG_TCG_TIS_SPI_CR50 | tpm_tis_spi | Google Cr50/Ti50 (Chromebook) |
CONFIG_TCG_TIS_I2C | tpm_tis_i2c | I2C 버스 TPM (임베디드/IoT) |
CONFIG_TCG_TIS_I2C_CR50 | cr50_i2c | Google Cr50 I2C 인터페이스 |
CONFIG_TCG_TIS_I2C_INFINEON | tpm_i2c_infineon | Infineon SLB 9635/9645 (I2C) |
CONFIG_TCG_CRB | tpm_crb | CRB 인터페이스 (fTPM 기본) |
CONFIG_TCG_FTPM_TEE | tpm_ftpm_tee | TEE 기반 fTPM (OP-TEE) |
CONFIG_TCG_VTPM_PROXY | tpm_vtpm_proxy | vTPM 프록시 (테스트용) |
CONFIG_TCG_TIS_ST33ZP24_SPI | st33zp24_spi | STMicro ST33ZP24 (SPI) |
CONFIG_TCG_TIS_ST33ZP24_I2C | st33zp24_i2c | STMicro ST33ZP24 (I2C) |
CONFIG_TCG_ATMEL | tpm_atmel | Atmel TPM (레거시) |
CONFIG_TCG_INFINEON | tpm_infineon | Infineon TPM (레거시 LPC) |
CONFIG_TCG_NSC | tpm_nsc | National Semiconductor (레거시) |
TPM 관련 보안 기능
| 설정 | 설명 |
|---|---|
CONFIG_TRUSTED_KEYS | trusted 키 타입 (TPM/TEE 백엔드) |
CONFIG_TRUSTED_KEYS_TPM | trusted 키의 TPM 백엔드 |
CONFIG_ENCRYPTED_KEYS | encrypted 키 타입 |
CONFIG_HW_RANDOM_TPM | TPM을 hwrng 소스로 등록 |
CONFIG_IMA | IMA (Integrity Measurement Architecture) |
CONFIG_IMA_MEASURE_PCR_IDX | IMA PCR 인덱스 (기본 10) |
CONFIG_EVM | EVM (Extended Verification Module) |
CONFIG_INTEGRITY_PLATFORM_KEYRING | UEFI 플랫폼 키링 |
CONFIG_INTEGRITY_MACHINE_KEYRING | 머신 키링 (MOK) |
개발용 .config 스니펫
# TPM 2.0 기본 지원
CONFIG_TCG_TPM=y
CONFIG_TCG_TIS=y
CONFIG_TCG_TIS_SPI=m
CONFIG_TCG_TIS_I2C=m
CONFIG_TCG_CRB=y
CONFIG_TCG_FTPM_TEE=m
CONFIG_TCG_VTPM_PROXY=m
# TPM 기반 키링
CONFIG_TRUSTED_KEYS=y
CONFIG_TRUSTED_KEYS_TPM=y
CONFIG_ENCRYPTED_KEYS=y
# TPM 난수
CONFIG_HW_RANDOM=y
CONFIG_HW_RANDOM_TPM=y
# 무결성 측정
CONFIG_IMA=y
CONFIG_IMA_MEASURE_PCR_IDX=10
CONFIG_EVM=y
CONFIG_INTEGRITY_SIGNATURE=y
# 보안 부팅 체인
CONFIG_INTEGRITY_PLATFORM_KEYRING=y
CONFIG_INTEGRITY_MACHINE_KEYRING=y
CONFIG_LOAD_UEFI_KEYS=y
환경별 추천 커널 설정 프로파일
# ═══════════════════════════════════════════
# 프로파일 1: 서버 환경 (최소 필수 + IMA + Audit)
# ═══════════════════════════════════════════
CONFIG_TCG_TPM=y
CONFIG_TCG_TIS=y # dTPM SPI/LPC
CONFIG_TCG_CRB=y # fTPM (Intel PTT / AMD PSP)
CONFIG_HW_RANDOM_TPM=y # TPM RNG → 엔트로피 풀
CONFIG_TRUSTED_KEYS=y
CONFIG_TRUSTED_KEYS_TPM=y
CONFIG_ENCRYPTED_KEYS=y
CONFIG_IMA=y
CONFIG_IMA_MEASURE_PCR_IDX=10
CONFIG_IMA_APPRAISE=y # 파일 무결성 검증
CONFIG_EVM=y
CONFIG_INTEGRITY_SIGNATURE=y
CONFIG_INTEGRITY_PLATFORM_KEYRING=y
CONFIG_INTEGRITY_MACHINE_KEYRING=y
CONFIG_INTEGRITY_AUDIT=y # IMA 이벤트 audit 로그
# CONFIG_TCG_VTPM_PROXY is not set (서버에서 불필요)
# ═══════════════════════════════════════════
# 프로파일 2: 개발/테스트 환경 (vtpm_proxy + swtpm)
# ═══════════════════════════════════════════
CONFIG_TCG_TPM=y
CONFIG_TCG_TIS=y
CONFIG_TCG_CRB=y
CONFIG_TCG_VTPM_PROXY=m # vTPM 테스트용 프록시
CONFIG_HW_RANDOM_TPM=y
CONFIG_TRUSTED_KEYS=m
CONFIG_TRUSTED_KEYS_TPM=m
CONFIG_ENCRYPTED_KEYS=m
CONFIG_IMA=y
CONFIG_EVM=y
# swtpm + QEMU vTPM 테스트에 필요
CONFIG_VHOST=y
CONFIG_VHOST_NET=m
# ═══════════════════════════════════════════
# 프로파일 3: 임베디드/IoT (fTPM + 최소 메모리)
# ═══════════════════════════════════════════
CONFIG_TCG_TPM=y
CONFIG_TCG_CRB=y # fTPM 전용
CONFIG_TCG_FTPM_TEE=y # OP-TEE fTPM (ARM)
CONFIG_HW_RANDOM_TPM=y
CONFIG_TRUSTED_KEYS=y
CONFIG_TRUSTED_KEYS_TPM=y
CONFIG_ENCRYPTED_KEYS=y
# CONFIG_TCG_TIS is not set (dTPM 없으므로 불필요)
# CONFIG_TCG_TIS_SPI is not set
# CONFIG_TCG_TIS_I2C is not set
# CONFIG_TCG_VTPM_PROXY is not set
# CONFIG_IMA is not set (메모리 절약, 필요 시 활성화)
빌드 의존성
TPM 관련 Kconfig 옵션들의 의존성 관계를 이해하면 빌드 오류를 방지할 수 있습니다:
| 설정 | 의존 대상 | 비고 |
|---|---|---|
CONFIG_TCG_TPM | CRYPTO | TPM 명령에 해시/암호 연산 필요 |
CONFIG_TCG_TIS_SPI | TCG_TIS_CORE, SPI | TIS 코어 + SPI 서브시스템 |
CONFIG_TCG_TIS_I2C | TCG_TIS_CORE, I2C | TIS 코어 + I2C 서브시스템 |
CONFIG_TCG_CRB | ACPI | CRB는 ACPI TPM2 테이블 필수 |
CONFIG_TCG_FTPM_TEE | TEE, OPTEE | TEE 프레임워크 + OP-TEE 드라이버 |
CONFIG_TRUSTED_KEYS_TPM | TCG_TPM, KEYS, ENCRYPTED_KEYS | trusted 키는 encrypted 키 타입에도 의존 |
CONFIG_HW_RANDOM_TPM | HW_RANDOM, TCG_TPM | hwrng 프레임워크 + TPM 코어 |
CONFIG_IMA | INTEGRITY, SECURITY | IMA는 integrity 프레임워크의 하위 기능 |
CONFIG_EVM | INTEGRITY, SECURITY, KEYS | EVM은 키링 시스템 의존 |
빌드 검증: TPM 관련 설정 후 make olddefconfig를 실행하면 의존성 누락으로 자동 비활성화되는 옵션을 확인할 수 있습니다. scripts/diffconfig로 변경 사항을 비교하세요. 또한 make menuconfig에서 해당 옵션의 Depends on: 항목을 확인하면 정확한 의존 관계를 파악할 수 있습니다.
트러블슈팅
디버깅(Debugging) 명령 모음
# TPM 드라이버 로그 확인
dmesg | grep -i tpm
journalctl -k | grep -i tpm
# TPM 장치 존재 확인
ls -la /dev/tpm* /dev/tpmrm*
# sysfs TPM 정보
cat /sys/class/tpm/tpm0/device/description
cat /sys/class/tpm/tpm0/tpm_version_major
# TPM 고정 속성 조회
tpm2_getcap properties-fixed
# TPM 가변 속성 조회 (DA 상태, 잠금 정보 등)
tpm2_getcap properties-variable
# 지원 알고리즘 확인
tpm2_getcap algorithms
# ACPI TPM2 테이블
sudo cat /sys/firmware/acpi/tables/TPM2 | hexdump -C
# udev 규칙 설정 (tss 그룹 접근 허용)
# /etc/udev/rules.d/60-tpm.rules
# KERNEL=="tpm[0-9]*", TAG+="systemd", GROUP="tss", MODE="0660"
# KERNEL=="tpmrm[0-9]*", TAG+="systemd", GROUP="tss", MODE="0660"
sudo udevadm control --reload-rules && sudo udevadm trigger
주요 오류 코드
| 오류 코드 | 값 | 원인 | 해결 |
|---|---|---|---|
TPM_RC_LOCKOUT | 0x0921 | DA 임계값 초과 잠금 | tpm2_dictionarylockout --clear-lockout |
TPM_RC_AUTH_FAIL | 0x098E | 인가 값 불일치 | 올바른 비밀번호/정책 확인 |
TPM_RC_NV_LOCKED | 0x0148 | NV 쓰기 잠금 상태 | 재부팅 (WRITE_STCLEAR) 또는 해제 |
TPM_RC_RETRY | 0x0922 | TPM이 바쁨 (재시도 요청) | 잠시 후 재시도 |
TPM_RC_YIELDED | 0x0800 | TPM이 양보 (긴 연산 중) | 계속 폴링 |
TPM_RC_OBJECT_MEMORY | 0x0902 | transient 슬롯 부족 | tpm2_flushcontext 또는 tpmrm 사용 |
TPM_RC_SESSION_MEMORY | 0x0903 | 세션 슬롯 부족 | 사용하지 않는 세션 flush |
TPM_RC_POLICY_FAIL | 0x099D | 정책 조건 불충족 | PCR 값, 정책 순서 확인 |
알려진 이슈
AMD fTPM 랜덤 딜레이: AMD Zen 3 이상 플랫폼에서 fTPM이 간헐적으로 수백 밀리초의 지연을 유발합니다. NV 쓰기 시 SPI 플래시 접근이 원인입니다. 커널 파라미터 tpm_tis.interrupts=0으로 완화하거나, BIOS에서 dTPM으로 전환하면 해결됩니다.
Intel PTT BIOS 설정: Intel 플랫폼에서 TPM이 감지되지 않으면, BIOS에서 "Intel Platform Trust Technology(PTT)" 또는 "Security Device Support"가 활성화되어 있는지 확인하세요. 일부 BIOS는 "fTPM"이나 "dTPM" 중 하나만 활성화하도록 옵션이 분리되어 있습니다.
TPM_RC 에러 코드 상세
TPM 2.0의 에러 코드는 Format 0(TPM 1.2 호환)과 Format 1(TPM 2.0 전용) 두 가지 형식이 있습니다. Format 1 에러 코드는 파라미터/핸들/세션 번호를 인코딩합니다:
TPM_RC Format 1 에러 코드 구조 (32비트):
Bit 31-12: 0 (예약)
Bit 11-8: P/H/S 인덱스 (어떤 파라미터/핸들/세션이 문제인지)
Bit 7: Format 비트 (1=Format 1)
Bit 6: P 비트 (1=Parameter 관련, 0=Handle/Session 관련)
Bit 5-0: 에러 번호
예: TPM_RC_VALUE | TPM_RC_P | TPM_RC_2
= 0x0004 | 0x0040 | 0x0200 = 0x0244
→ "두 번째 파라미터의 값이 유효하지 않음"
| 에러 코드 | 값 | 카테고리 | 원인 | 해결 방법 |
|---|---|---|---|---|
TPM_RC_INITIALIZE | 0x0100 | 명령 | TPM이 초기화되지 않음 (Startup 미호출) | tpm2_startup -c 실행 |
TPM_RC_FAILURE | 0x0101 | 명령 | TPM 자체 테스트 실패 | tpm2_selftest -f 실행, 펌웨어 업데이트 |
TPM_RC_DISABLED | 0x0120 | 명령 | 해당 계층이 비활성화됨 | BIOS에서 계층 활성화 또는 tpm2_hierarchycontrol |
TPM_RC_LOCKOUT | 0x0921 | 세션 | DA 잠금 — 인가 실패 임계값 초과 | tpm2_dictionarylockout --clear-lockout -p <lockout_pw> |
TPM_RC_AUTH_FAIL | 0x098E | 세션 | 인가 값(비밀번호/HMAC) 불일치 | 올바른 인가 값 확인, DA 카운터 확인 |
TPM_RC_POLICY_FAIL | 0x099D | 세션 | 정책 조건 미충족 | PCR 값 확인, 정책 순서 검증 |
TPM_RC_VALUE | 0x0004 | 파라미터 | 파라미터 값이 유효 범위 밖 | 명령 파라미터 검토 |
TPM_RC_SIZE | 0x0015 | 파라미터 | 데이터 크기가 예상과 다름 | 명령 구조 크기 확인 |
TPM_RC_KEY_SIZE | 0x002C | 파라미터 | 키 크기가 지원되지 않음 | tpm2_getcap algorithms로 지원 크기 확인 |
TPM_RC_NV_LOCKED | 0x0148 | NV | NV 인덱스가 쓰기 잠금 상태 | 재부팅 (STCLEAR) 또는 tpm2_nvwritelock 해제 |
TPM_RC_NV_SPACE | 0x014F | NV | NV 저장 공간 부족 | 불필요한 NV 인덱스 삭제 |
TPM_RC_OBJECT_MEMORY | 0x0902 | 리소스 | transient 객체 슬롯 부족 (보통 3개) | tpm2_flushcontext -t 또는 /dev/tpmrm0 사용 |
TPM_RC_SESSION_MEMORY | 0x0903 | 리소스 | 활성 세션 슬롯 부족 | tpm2_flushcontext -s |
TPM_RC_RETRY | 0x0922 | 경고 | TPM이 바빠서 재시도 요청 | 짧은 대기 후 재시도 |
TPM_RC_NV_UNAVAILABLE | 0x0923 | 경고 | NV 영역이 아직 사용 불가 (부팅 초기) | 잠시 대기 후 재시도 |
실전 디버깅 시나리오
시나리오 1: systemd-cryptenroll TPM_RC_LOCKOUT
# 문제: systemd-cryptenroll이 TPM_RC_LOCKOUT (0x0921) 에러 발생
# 원인: 잘못된 비밀번호를 여러 번 입력하여 DA 잠금 활성화
# 1. DA 상태 확인
tpm2_getcap properties-variable | grep -i lockout
# lockoutCounter: 5 ← DA 실패 횟수가 maxTries에 도달
# maxTries: 5
# recoveryTime: 600 ← 자동 복구까지 600초
# 2. 방법 A: lockout 비밀번호로 즉시 해제
tpm2_dictionarylockout --clear-lockout -p
# 3. 방법 B: recoveryTime만큼 대기 (비밀번호 모를 때)
# → 600초(10분) 대기하면 카운터가 자동 감소
# 4. 방법 C: TPM Clear + 재프로비저닝 (최후 수단)
# 주의: 모든 키/NV 데이터 삭제!
tpm2_clear -c p
시나리오 2: PCR 값이 전부 0
# 문제: tpm2_pcrread에서 모든 PCR이 0x000...000
# 원인: BIOS에서 Measured Boot가 비활성화됨
# 1. PCR 값 확인
tpm2_pcrread sha256:0,1,7
# sha256:
# 0 : 0x0000000000000000... ← 모두 0이면 측정 안 됨
# 2. BIOS 설정 확인 항목:
# - "Measured Boot" 또는 "TCG Boot" → Enabled
# - "Secure Boot" → 함께 활성화 권장
# - TPM 장치 자체는 활성화되어 있어도 측정이 비활성화일 수 있음
# 3. 이벤트 로그 존재 여부 확인
ls -la /sys/kernel/security/tpm0/binary_bios_measurements
# 파일이 없거나 크기가 0 → 펌웨어가 측정을 수행하지 않음
시나리오 3: AMD fTPM 랜덤 프리즈
# 문제: AMD Ryzen 5000/7000에서 간헐적 마우스/오디오 끊김
# 원인: fTPM이 SPI 플래시에 NV 데이터를 쓸 때 수백 ms 블록킹
# 1. 증상 확인
dmesg | grep -i "tpm\|ftpm"
# tpm_crb MSFT0101:00: [Firmware Bug]: ...
# 간헐적으로 수백 ms 지연 로그
# 2. 해결 방법 A: BIOS에서 fTPM 비활성화 (dTPM이 있는 경우)
# BIOS → Security → Trusted Computing → fTPM → Disabled
# BIOS → Security → Trusted Computing → dTPM → Enabled
# 3. 해결 방법 B: 커널 파라미터 (완전한 해결은 아님)
# /etc/default/grub:
# GRUB_CMDLINE_LINUX="tpm_tis.interrupts=0"
# 4. 해결 방법 C: BIOS 업데이트
# AMD AGESA 1.2.0.7 이후 버전에서 일부 완화됨
시나리오 4: /dev/tpm0 권한 거부
# 문제: Permission denied on /dev/tpm0 or /dev/tpmrm0
# 원인: 일반 사용자에게 장치 접근 권한 없음
# 1. 현재 권한 확인
ls -la /dev/tpm* /dev/tpmrm*
# crw-rw---- 1 tss root 10, 224 ... /dev/tpm0
# crw-rw---- 1 tss tss 10, 225 ... /dev/tpmrm0
# 2. tss 그룹에 사용자 추가
sudo usermod -aG tss $USER
# → 재로그인 필요
# 3. udev 규칙 생성 (영구 설정)
sudo tee /etc/udev/rules.d/60-tpm.rules << 'EOF'
# TPM 장치에 tss 그룹 접근 허용
KERNEL=="tpm[0-9]*", TAG+="systemd", GROUP="tss", MODE="0660"
KERNEL=="tpmrm[0-9]*", TAG+="systemd", GROUP="tss", MODE="0660"
EOF
# 4. udev 규칙 적용
sudo udevadm control --reload-rules
sudo udevadm trigger
시나리오 5: tpm2_pcrread 행
# 문제: tpm2_pcrread가 무기한 대기 (hang)
# 원인: TPM이 self-test에서 멈춤 또는 이전 명령이 응답하지 않음
# 1. TPM 상태 확인
dmesg | tail -20 | grep -i tpm
# "tpm tpm0: tpm2_get_random failed with -62" → -ETIME (타임아웃)
# 2. TPM 리셋 시도 (소프트 리셋)
# cancel 파일에 쓰기로 현재 명령 취소
echo 1 | sudo tee /sys/class/tpm/tpm0/cancel
# 3. TPM 드라이버 리로드
sudo modprobe -r tpm_crb # 또는 tpm_tis
sudo modprobe tpm_crb
# 4. 지속되면: 플랫폼 재부팅 (TPM 하드웨어 리셋)
# fTPM의 경우 BIOS에서 "TPM Reset" 또는 "Clear TPM" 실행
# 5. 디버깅: ftrace로 TPM 내부 호출 추적
echo 1 | sudo tee /sys/kernel/debug/tracing/events/tpm/enable
cat /sys/kernel/debug/tracing/trace_pipe | head -50
TPM 보안 모범 사례
TPM을 실무 환경에서 안전하게 운영하기 위한 보안 모범 사례를 정리합니다.
계층 인가 관리
| 계층 | 인가 설정 권장 | 비밀번호 저장 위치 | 비고 |
|---|---|---|---|
| Owner (SH) | 반드시 설정 | 비밀 관리 시스템 (Vault 등) | SRK, persistent 키 관리 권한 |
| Endorsement (EH) | 반드시 설정 | 비밀 관리 시스템 | EK 생성/인증서 접근 권한 |
| Lockout | 반드시 설정 | 오프라인 안전 저장 (금고) | DA 잠금 해제용 — 분실 시 TPM Clear 필요 |
| Platform | BIOS 관리 | BIOS 설정 | 일반적으로 사용자가 직접 관리하지 않음 |
키 관리 원칙
- Persistent 핸들 최소화: persistent 객체는 TPM NV 공간을 영구 점유하므로, SRK와 AK 정도만 persistent로 등록하고 나머지는 필요 시 transient로 로드
- 키 계층 활용: SRK를 루트로 하여 용도별 하위 키(서명용, 암호화용, 봉인용)를 생성. 키 블롭을 파일시스템에 저장하여 필요 시 로드
- 키 속성 제한:
fixedTPM|fixedParent|sensitiveDataOrigin속성으로 키가 다른 TPM으로 복제되거나 외부에서 주입되는 것을 방지 - 정책 기반 접근: 비밀번호 대신 PolicyPCR, PolicyAuthorize 등 정책 기반 인가를 사용하여 조건부 접근 제어 구현
운영 보안 체크리스트
TPM 운영 체크리스트:
- 계층 인가 값이 기본(빈 값)이 아닌지 확인
- DA 보호 파라미터가 적절하게 설정되었는지 확인 (maxTries ≤ 10)
- EK 인증서가 존재하고 제조사 CA로 검증 가능한지 확인
- TPM 펌웨어가 최신 버전인지 확인 (알려진 취약점 패치)
- Measured Boot가 활성화되고 이벤트 로그가 정상 생성되는지 확인
- LUKS2 봉인 사용 시 복구 키슬롯이 존재하는지 확인
/dev/tpm0,/dev/tpmrm0권한이 적절한지 확인 (tss 그룹)- 불필요한 persistent 객체와 NV 인덱스가 없는지 주기적 점검
TPM 기반 서비스 배포 아키텍처
일반적인 실수와 방지
| 실수 | 결과 | 방지 방법 |
|---|---|---|
| 복구 키 없이 TPM만으로 LUKS 봉인 | TPM 교체/고장 시 데이터 영구 손실 | 반드시 --recovery-key로 복구 키슬롯 추가 |
| 모든 PCR에 바인딩 (PCR 0-9) | 사소한 변경에도 잠금, 빈번한 재봉인 필요 | PCR 7(Secure Boot 정책)만 또는 Signed PCR Policy 사용 |
| 계층 인가 값 미설정 | 누구나 TPM 오너 조작 가능 | 프로비저닝 시 Owner/Endorsement/Lockout 인가 값 설정 |
| DA 보호 미설정 | 무제한 비밀번호 시도 가능 | maxTries=10 이하, recoveryTime=300초 이상 설정 |
| dTPM SPI 버스 보안 미고려 | 물리적 SPI 스니핑으로 명령/응답 노출 | 파라미터 암호화 세션 사용, 또는 fTPM 전환 |
| TPM 펌웨어 업데이트 미적용 | 알려진 취약점(TPM-FAIL 등) 노출 | 제조사 보안 공지 모니터링, 정기 업데이트 |
관련 문서
외부 참고 자료
- TCG TPM 2.0 Library Specification — TPM 2.0 핵심 사양서(Part 1~4)입니다
- TCG PC Client Platform Firmware Profile — PC 플랫폼 TPM 펌웨어 프로파일 사양입니다
- TPM — Kernel Documentation — 커널 TPM 서브시스템 공식 문서입니다
- tpm2-software GitHub Organization — tpm2-tools, tpm2-tss, tpm2-abrmd 등 TPM2 소프트웨어 스택입니다
- tpm2-tools GitHub — TPM 2.0 명령줄 도구 소스 코드입니다
- swtpm GitHub — 소프트웨어 TPM 에뮬레이터 (QEMU/libvirt 연동)입니다
- TPM2 Software Community — TPM2 소프트웨어 커뮤니티 문서 사이트입니다
- Linux 커널 TPM 드라이버 소스 — 커널 TPM 드라이버 소스 코드입니다
- ioctl_tpm(2) — Linux man page — /dev/tpm0 ioctl 인터페이스 매뉴얼입니다
- TPM 2.0 — A Brief Introduction — TPM 2.0 기초 개념 소개 자료입니다