1P by GN⁺ 8시간전 | ★ favorite | 댓글 1개
  • 보안 토큰은 개인키를 장치 밖으로 내보내지 않고 장치 안에서 서명하며, 사용자의 물리적 동작을 요구해 원격 공격자가 임의 서명을 만들기 어렵게 함
  • SSH 인증, U2F, 비밀번호 없는 로컬 로그인, sudo, git 커밋 서명에 쓸 수 있고, 최신 노트북·스마트폰의 내장 보안 장치가 YubiKey를 대체할 수 있음
  • ssh-keygen -t ed25519-sk로 만든 “개인키” 파일은 실제 개인키가 아니라 토큰 안 키를 가리키는 핸들이며, 같은 토큰으로 다른 컴퓨터에서도 같은 SSH 키 파일 생성 가능함
  • MacBook에서는 secure element를 SSH 키로 설정해 Touch ID 기반 SSH 로그인이 가능했고, git 커밋 서명에는 파일 경로 대신 ssh-agentkey:: 형식의 user.signingKey 설정이 필요했음
  • 보안 토큰은 분실 시 개인키를 복구할 수 없고 반복 터치에 익숙해지는 사용성 위험이 있으며, Windows 노트북에서는 Windows Hello가 얼굴 인식·지문·PIN으로 SSH 키 사용을 확인할 수 있었음

보안 토큰의 장점과 한계

  • 원격 공격을 막는 핵심 구조

    • 보안 토큰은 개인키/공개키 쌍을 장치 안에 두고, 공개키는 쉽게 꺼낼 수 있지만 개인키는 장치 밖으로 나가지 않게 만드는 장치임
    • 서명할 데이터 패킷을 장치에 보내면 개인키로 장치 안에서 서명되며, 보통 깜박이는 터치 버튼을 누르는 같은 물리적 사용자 동작이 필요함
    • 원격 공격자가 컴퓨터에 접근해도 사용자가 실제 세계에서 동작하지 않으면 보안 토큰이 임의의 서명을 해주지 않으므로, ~/.ssh 디렉터리에 전체 SSH 개인키/공개키 쌍을 파일로 두는 방식보다 나아 보임
    • SoloKeysNitrokeys처럼 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

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/

  • 이건 Mac 전용처럼 보임

    • 회사에서 최신 Windows 노트북을 쓸 수 있어서, 다음처럼 OpenSSH가 Windows Hello와 연동되게 했음
      winget install Microsoft.OpenSSH.preview  
      ssh-keygen -t ecdsa-sk  
      
      이후에는 예전처럼 동작했고, 키로 어디든 SSH 접속할 때마다 표준 Windows Hello 흐름을 거쳐 지문 인식기, 얼굴 인식, PIN 중 하나를 사용할 수 있었음
      그런 보안 요소가 있는 Linux 시스템은 써볼 수 없었고, 내 Linux 워크스테이션에는 V1 TPM이 있지만 서명 작업을 실제 사용자 존재 확인 뒤에만 실행되게 보장하는 방법은 딱히 모르겠음
      Framework 같은 Linux 노트북을 가진 사람이 시도해볼 수 있을지도 모름. 어쩌면 Asahi에서도 실제로 동작할 수 있음
    • 거의 그렇다고 봄. Linux나 Windows에서 TPM2 FIDO 에뮬레이션 계층이 있는지는 잘 모르겠음
  • 그래서 제공된 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 환경 변수를 확인함

  • 이 글은 SSH만 다루는 것 같은데, 내 컴퓨터의 Secure Enclave나 TPM을 FIDO2 또는 U2F 키로 쓸 방법이 있을까?

    • 당연히 가능하고, 거의 기본 설정만으로 동작해야 함
      Passkey도 웹사이트마다 별도의 개인 키를 쓰는 이런 방식의 한 형태임
  • 이 사실을 보면, 하드웨어에 묶을 수 있는 비대칭 또는 HMAC API 키 지원이 더 흔하지 않다는 게 이상하게 느껴짐
    WebAuthn, DBSC(Device-Bound Session Credentials), OAuth2 DPOP처럼 이런 방향을 더 밀어주는 명세가 늘어나는 건 반가움