요즘 노트북은 모두 내장 보안 토큰을 갖고 있다
(ahelwer.ca)- 보안 토큰은 개인키를 장치 밖으로 내보내지 않고 장치 안에서 서명하며, 사용자의 물리적 동작을 요구해 원격 공격자가 임의 서명을 만들기 어렵게 함
- SSH 인증, U2F, 비밀번호 없는 로컬 로그인,
sudo, git 커밋 서명에 쓸 수 있고, 최신 노트북·스마트폰의 내장 보안 장치가 YubiKey를 대체할 수 있음 ssh-keygen -t ed25519-sk로 만든 “개인키” 파일은 실제 개인키가 아니라 토큰 안 키를 가리키는 핸들이며, 같은 토큰으로 다른 컴퓨터에서도 같은 SSH 키 파일 생성 가능함- MacBook에서는 secure element를 SSH 키로 설정해 Touch ID 기반 SSH 로그인이 가능했고, git 커밋 서명에는 파일 경로 대신
ssh-agent와key::형식의user.signingKey설정이 필요했음 - 보안 토큰은 분실 시 개인키를 복구할 수 없고 반복 터치에 익숙해지는 사용성 위험이 있으며, Windows 노트북에서는 Windows Hello가 얼굴 인식·지문·PIN으로 SSH 키 사용을 확인할 수 있었음
보안 토큰의 장점과 한계
-
원격 공격을 막는 핵심 구조
- 보안 토큰은 개인키/공개키 쌍을 장치 안에 두고, 공개키는 쉽게 꺼낼 수 있지만 개인키는 장치 밖으로 나가지 않게 만드는 장치임
- 서명할 데이터 패킷을 장치에 보내면 개인키로 장치 안에서 서명되며, 보통 깜박이는 터치 버튼을 누르는 같은 물리적 사용자 동작이 필요함
- 원격 공격자가 컴퓨터에 접근해도 사용자가 실제 세계에서 동작하지 않으면 보안 토큰이 임의의 서명을 해주지 않으므로,
~/.ssh디렉터리에 전체 SSH 개인키/공개키 쌍을 파일로 두는 방식보다 나아 보임 - SoloKeys와 Nitrokeys처럼 FOSS 펌웨어를 원하는 선택지도 있음
- 더 고급형 보안 토큰은 내장 지문 리더 같은 생체 인증 기능을 더하지만, 핵심은 개인키를 장치 밖으로 내보내지 않는 구조임
-
사용성에서 생기는 위험
- 사용자가 보안 토큰이 깜박일 때마다 누르는 데 익숙해지면, 악의적인 요청에도 무심코 응답할 수 있음
- 연속 서명 작업 중 토큰을 반복해서 누르는 상황에서는 한 번 더 깜박이는 요청을 실제로 알아차리기 어려울 수 있음
- Apple과 Microsoft는 스마트폰의 인증 앱에서 접근 요청마다 무작위 숫자 코드를 붙여 사용자가 입력하게 하는 방식을 쓰지만, 이는 번거롭고 TOTP를 쓰는 Authy나 Google Authenticator 앱 대비 보안 토큰의 사용성 이점을 줄임
-
분실과 백업 문제
- 보안 토큰을 잃어버리면 해당 개인키는 영구히 사라지고 백업할 방법이 없음
- 여러 계정에서 잠기는 위험을 피하려면 보안 토큰을 살 때 최소 2개를 사서 같은 서비스에 등록해야 함
- 대안으로 BIP 39처럼 개인키를 사람이 읽을 수 있는 단어 목록으로 바꿔 적어두는 백업·복원 방식이 있음
- 개인키가 보안 엔클레이브 밖으로 나갈 수 있으면 사용자가 단어 목록을 잘못된 곳에 쓰도록 유도하는 피싱 공격도 가능해짐
- 모든 보안 토큰을 잃을 가능성이 크게 걱정된다면 BIP 39 단어 목록이 시스템 접근을 되찾기 위한 최후 수단이 될 수 있음
SSH와 git에서 보안 토큰 쓰기
-
SSH 개인키를 보안 토큰에 두기
- 일반적으로
ssh-keygen을 실행하면 전체 개인키가 포함된 파일 쌍이 만들어짐 - 보안 토큰에 개인키를 두려면 Yubico의 FIDO/U2F 지침에 따라 libfido2를 설치하고, 보안 토큰을 꽂은 상태에서
ssh-keygen -t ed25519-sk를 실행함 - 이때도 파일 쌍이 생성되지만, “개인키” 파일은 실제 개인키가 아니라 보안 토큰 안에 있는 개인키를 가리키는 핸들임
- 같은 보안 토큰으로
ssh-keygen -t ed25519-sk를 다시 실행하면 어느 컴퓨터에서든 같은 개인키/공개키 파일을 만들 수 있어, SSH 접근 권한이 특정 컴퓨터의 특정 파일이 아니라 보안 토큰과 함께 이동함
- 일반적으로
-
git 인증과 커밋 서명
- 보안 토큰을 누르는 상황의 약 90%는 git 사용 때문임
- git forge들은 push와 pull 작업을 위한 SSH 인증을 구현하고, 위에서 생성한
id_ed25519_sk.pub파일을 올리면 보안 토큰 키 쌍을 허용할 수 있음 - git은 커밋 서명에도 SSH 키를 지원하며, GitHub 문서의 SSH 키로 서명 키 설정하기를 따른 뒤
git config --global commit.gpgsign true를 실행하면 모든 커밋이 자동 서명됨 - git forge가 커밋을 본인 서명으로 인식하려면 공개키를 다시 업로드해야 하며, 이 필드는 보통 SSH 인증용 필드와 별도임
-
커밋 서명의 불편함
- 긴 커밋 목록을 rebase할 때는 모든 커밋을 다시 서명해야 함
- 지문 리더가 있는 YubiKey는 수십 개 커밋을 연속 서명하기에는 지문 인식 실패율이 너무 높아 사용을 중단하게 됨
- git의 “rebase/amend 중심” 래퍼인 jujutsu에는 push 시점에만 커밋에 서명하는 방법이 있음
-
Linux 로컬 로그인과 sudo
- Linux 시스템에서는 Pluggable Authentication Module(PAM)으로 보안 토큰을 비밀번호 없는 로컬 로그인과
sudo권한 상승에 사용함
- Linux 시스템에서는 Pluggable Authentication Module(PAM)으로 보안 토큰을 비밀번호 없는 로컬 로그인과
MacBook의 secure element를 SSH 키로 쓰기
- USB-C 포트에 보안 토큰을 계속 꽂아두면 잘못 떨어뜨리거나 부딪혔을 때 포트와 토큰을 망가뜨릴 수 있는 작은 지렛대처럼 튀어나와 있음
- 2020년형 M1 MacBook Air에서 Arian van Putten의 지침을 따라 내장 보안 요소를 SSH 키로 설정함
sc_auth create-ctk-identity -l ssh -k p-256-ne -t bio
ssh-keygen -w /usr/lib/ssh-keychain.dylib -K -N ""
- 이 명령은
id_ecdsa_sk_rk개인키/공개키 파일 쌍을 만들고, 이 파일들을~/.ssh디렉터리로 옮김 - 여기서도 개인키 파일은 실제 개인키가 아니라 장치 안 키에 대한 핸들이므로 공개적으로 붙여 넣을 수 있는 형태임
- 홈랩 서버에 공개키를 authorized key로 추가하려면 다음처럼 실행함
ssh-copy-id -i ~/.ssh/id_ecdsa_sk_rk.pub <server nickname>
- 이후
~/.ssh/config에 다음 설정을 추가함
Host *
IdentityFile ~/.ssh/id_ecdsa_sk_rk
SecurityKeyProvider=/usr/lib/ssh-keychain.dylib
ssh <server nickname>을 실행하면 로그인 전에 macOS가 지문 요청을 자동으로 띄우고, 이후 SSH 로그인이 정상적으로 진행됨
MacBook secure element로 git 커밋 서명하기
git config --global user.signingKey /Users/ahelwer/.ssh/id_ecdsa_sk_rk를 설정하고.ssh/allowed_signers파일을 갱신해도 git 커밋 서명은 바로 동작하지 않음- git은 커밋 서명에 실패하며,
device not found?같은 오류를 출력함
error: Signing file /var/folders/l5/5wqvq2l10p96wtdtfr6lvrvw0000gn/T//.git_signing_buffer_tmpc4uQgO
Confirm user presence for key ECDSA-SK SHA256:oQDA2SNYb2MoSQcxJVSmWyAeAWPqMp7rxliBRfi87as
Couldn't sign message: device not found?
Signing /var/folders/l5/5wqvq2l10p96wtdtfr6lvrvw0000gn/T//.git_signing_buffer_tmpc4uQgO failed: device not found?
fatal: failed to write commit object
- 해결책은
~/.ssh디렉터리의 파일을 직접 가리키는 대신 ssh-agent를 쓰는 것임 - 위의 튜토리얼에 따라 다음 명령으로 키 쌍을 ssh-agent에 등록함
ssh-add -K -S /usr/lib/ssh-keychain.dylib
- 이후
user.signingKey에는 파일 경로가 아니라~/.ssh/id_ecdsa_sk_rk.pub내용 앞에key::를 붙인 키 자체를~/.gitconfig에 넣음
[user]
name = Andrew Helwer
signingKey = "key::sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBGxFEdnIg6ppz+pQCdd1eisjOV4gxrjMv1Y4SbtdLoSm6CJCgPZ6q7lnNyuQQsdnS4/Tllsc656AQL7BO3OS47cAAAAEc3NoOg== ssh:"
- 이 설정 뒤 MacBook의 secure element에 있는 키로 파일을 서명하고 GitLab Pages 사이트에 push할 수 있었음
Windows와 Linux에서 해본 결과
- 회사 지급 Windows 노트북에서도 빠른 실험을 진행함
winget install Microsoft.OpenSSH.preview
ssh-keygen -t ecdsa-sk
- 이 명령도 개인키/공개키 파일 쌍을 생성했고, SSH 접속 시 Windows Hello의 표준 로그인 흐름을 통해 얼굴 인식, 지문, PIN 중 하나를 받아들였음
- Linux에서는 비슷한 실제 사용자 존재 확인 뒤에 secure element가 걸려 있는 노트북에 접근할 수 없어 데모하지 못함
Lobste.rs 의견들
-
훌륭한 글이고, 이런 게 가능하다는 사실을 드러내는 것만으로도 매우 유익함
개인적으로는 이걸 동작시키는 데 맞는 라이브러리 버전을 찾지 못했지만, 1Password 8은 SSH 키를 안전하게 저장하고 에이전트가 생체 인증으로 키 잠금 해제를 지원한다는 걸 알게 됨
그래서 이제 손가락을 대는 것만으로 git 작업도 하고 SSH 호스트에도 로그인할 수 있음
안내: https://developer.1password.com/docs/ssh/get-started/- Bitwarden도 비슷하게 지원함: https://bitwarden.com/help/ssh-agent/
-
이건 Mac 전용처럼 보임
- 회사에서 최신 Windows 노트북을 쓸 수 있어서, 다음처럼 OpenSSH가 Windows Hello와 연동되게 했음
이후에는 예전처럼 동작했고, 키로 어디든 SSH 접속할 때마다 표준 Windows Hello 흐름을 거쳐 지문 인식기, 얼굴 인식, PIN 중 하나를 사용할 수 있었음winget install Microsoft.OpenSSH.preview ssh-keygen -t ecdsa-sk
그런 보안 요소가 있는 Linux 시스템은 써볼 수 없었고, 내 Linux 워크스테이션에는 V1 TPM이 있지만 서명 작업을 실제 사용자 존재 확인 뒤에만 실행되게 보장하는 방법은 딱히 모르겠음
Framework 같은 Linux 노트북을 가진 사람이 시도해볼 수 있을지도 모름. 어쩌면 Asahi에서도 실제로 동작할 수 있음 - 거의 그렇다고 봄. Linux나 Windows에서 TPM2 FIDO 에뮬레이션 계층이 있는지는 잘 모르겠음
- 회사에서 최신 Windows 노트북을 쓸 수 있어서, 다음처럼 OpenSSH가 Windows Hello와 연동되게 했음
-
그래서 제공된 private key 파일 안에는 정확히 뭐가 들어 있는 거임?
- @wrs가 먼저 답했지만,
ssh:부분, 즉 애플리케이션은 passkey의 origin에 해당하며 호스트나 도메인별 resident key를 만들 때 유용함
예를 들어 같은 물리 Yubikey 안에서도 용도별로 키를 분리하는 데 쓰고 있음
flags는 하드웨어가 키를 어떻게 다뤄야 하는지 지정함 [1]. 에이전트가 자체 제약을 덧붙일 수도 있음
기술적으로는 FIDO 키에 다른 블롭이나 확장도 저장할 수 있고, 이전 직장에서는 인증과 함께 X.509 공개 키 같은 보조 자격 증명을 넘기는 데 써본 적이 있음. 꽤 멋진 방식임
[1]#define SSH_SK_USER_PRESENCE_REQD 0x01 #define SSH_SK_USER_VERIFICATION_REQD 0x04 #define SSH_SK_FORCE_OPERATION 0x10 #define SSH_SK_RESIDENT_KEY 0x20 - Claude에 따르면, 그리고 openssh_key_parser로 검증해보면 구조는 다음과 같음
바깥 래퍼에는 매직값openssh-key-v1\0,cipher=none,kdf=none이 있어서 암호문이 아님
공개 키 블롭 74바이트에는 키 타입sk-ssh-ed25519@openssh.com, 32바이트 Ed25519 점fdcce889…03e7852b, 애플리케이션ssh:가 들어 있으며, 이 값이 SSH와 WebAuthn의 FIDO 자격 증명을 네임스페이스로 분리함
개인 섹션은 248바이트이고cipher=none이라 평문임.checkint1 == checkint2 == 0x46744267랜덤값, 반복된 키 타입과 공개 키, 애플리케이션ssh:,flags: 0x01이 들어 있음
이 플래그는USER_PRESENCE_REQUIRED라 터치가 필요하지만 PIN/사용자 검증은 없고, 비상주 키임
key_handle은 128바이트짜리 불투명 자격 증명 ID로authenticatorGetAssertion에 전달되고, 장치가 내부적으로 풀어서 Ed25519 시드를 복구함
그 밖에 빈reserved, 주석ahelwer@ah-mbair.local, 패딩01 02 03이 있음 - base64 디코더에 넣으면 다음이 나옴
openssh-key-v1����none���none����������J���sk-ssh-ed25519@openssh.com��� 盘˪<F$KW+���ssh:���FtBgFtBg���sk-ssh-ed25519@openssh.com��� 盘˪<F$KW+���ssh:���fІpF$D8"&0[X 'L=Ev ')BjM]$}rTv6Z+p9O8ݹ%V* f.|қ.%I{9 .W !D"8N ai*W�y53 �������ahelwer@ah-mbair.local
여기에는 키 표준 버전 v1, 키 타입sk-ssh-ed25519@openssh.com이 들어 있고 어떤 이유로 반복되며, 사람이 알아보기 쉬운 키 이름ahelwer@ah-mbair.local도 있음
나머지는 OpenSSH 플래그, 예를 들면 PIN이나 사용자 존재 확인 필요 여부, 그리고 OpenSSH가 챌린지와 함께 FIDO/U2F API로 보낼 수 있는 핸들 GUID일 것 같음
OpenSSH는 키 타입, 특히sk를 보고 이게 실제 개인 키가 아니라 보안 요소를 호출해야 한다고 추론할 수 있음
그다음에는 보안 요소와 통신하게 해주는 동적 라이브러리를 어디서 로드할지SecurityKeyProvider설정이나SSH_SK_PROVIDER환경 변수를 확인함
- @wrs가 먼저 답했지만,
-
이 글은 SSH만 다루는 것 같은데, 내 컴퓨터의 Secure Enclave나 TPM을 FIDO2 또는 U2F 키로 쓸 방법이 있을까?
- 당연히 가능하고, 거의 기본 설정만으로 동작해야 함
Passkey도 웹사이트마다 별도의 개인 키를 쓰는 이런 방식의 한 형태임
- 당연히 가능하고, 거의 기본 설정만으로 동작해야 함
-
이 사실을 보면, 하드웨어에 묶을 수 있는 비대칭 또는 HMAC API 키 지원이 더 흔하지 않다는 게 이상하게 느껴짐
WebAuthn, DBSC(Device-Bound Session Credentials), OAuth2 DPOP처럼 이런 방향을 더 밀어주는 명세가 늘어나는 건 반가움