2024. 6. 17. 17:07ㆍ공부/패스키
영어랑 CS 공부가 미숙하여 의역이나 오역된 부분이 많으니 이상하다 싶은 부분들은 원문 참조 부탁드립니다.
Web Authentication API
Web Authentication API는 자격 증명 관리 API(패스워드 관리자)의 확장으로, 공개 키 암호화를 사용하여 강력한 인증(ex: 로그인)이 가능하게 한다. 이를 이용하면 비밀번호 없는 인증과 SMS 문자 없이도 안전한 다중 인증(MFA)이 가능하다.
패스키 관련 추가 문서들(구글의 Cloud Translation API를 통해 번역되어 있음)
패스키는 Webauthn의 일종입니다.
WebAuthn 컨셉과 사용법
WebAuthn는 웹사이트 회원가입, 인증, 다중 요소 인증 시 비밀번호나 SMS 문자를 통한 방법이 아닌 비대칭(공개키) 암호화를 사용하는 방식을 뜻한다. 이 경우 다음과 같은 장점들이 있다.
- 피싱 방지: 웹사이트의 주소에 따라 서명이 바뀌기에 가짜 로그인 사이트를 통한 로그인 시도 방지한다.
- 데이터 유출 시 위험 감소: 개인 키가 필요하기 때문에 해커들이 공개키만으로 로그인이 불가능하기에 개발자는 공개키를 해시할 필요가 없다.
- 기존 비밀번호 유출 공격에 대한 면역: 일부 유저들은 여러 사이트에서 동일한 패스워드를 사용하기에 다른 웹사이트를 통해 비밀번호가 유출될 수 있으나, WebAuthn의 경우 그 영향을 받지 않는다.
많은 웹사이트들은 회원가입 및 인증 기능이 구현되어 있는데, WebAuthn은 그러한 인증 부분에 대한 대체나 개선 방안으로서 활용 가능하다. WebAuthn은 유저와 인증기(구글, apple 인증이나 USB) 간의 소통을 추상화하며 아래와 같은 기능을 제공한다.
- navigator.credentials.create()를 publicKey 옵션과 같이 사용하여 사용자는 인증기에 새로운 자격 증명을 생성할 수 있다. - 새로운 계정 정보를 등록하거나, 기존 계정 정보에 새 비대칭 키 쌍을 연결시킬 수 있음
- 새 계정 등록 시, 해당 자격 증명은 서버(서비스 혹은 신뢰 당사자)에 저장되며, 이후 유저가 로그인 시 사용 가능하다.
- 비대칭 키 쌍은 인증기에 저장되며, 이후 MFA(다단계 인증) 중 신뢰 당사자에 포함된 유저들이 인증을 하는 데 사용된다. 인증기는 운영체제나 USB, 블루투스 보안 키와 같은 형태로도 가능하다.
- navigator.credentials.get()를 publicKey와 같이 사용하여 사용자는 서버에 인증(로그인) 시 인증기에 저장된 기존의 자격 증명을 사용할 수 있다.(로그인 혹은 다단계 인증 시 활용될 수 있음)
가장 기본적인 형태에서 create()와 get()는 서버로부터 "challenge"라고 하는 매우 큰 임의의 숫자를 받고, 이를 개인 키를 통해 서명하여 다시 서버에 돌려준다. 이것은 네트워크에 아무런 유출이 없이 사용자가 인증에 필요한 개인 키를 갖고 있음을 증명한다.
"challenge"는 최소 16바이트 크기의 무작위 정보 버퍼여야 한다.
키 쌍 생성 및 유저 등록
- 서버(신뢰 당사자)에서 등록 프로세스를 처리하는 웹 어플리케이션으로 사용자 정보와 서버 정보를 "challenge"와 같이 보낸다.(이 경우 적절한 보안 메커니즘을 활용해서 전달해야한다. ex: Fetch, XMLHttpRequest)
- 참고: 서버와 웹 어플리케이션 간의 정보 공유 형식은 어플리케이션에 따라 다르다. 권장되는 접근 방식은 자격 증명 정보를 JSON 형식으로 변환하는 것이다. PublicKeyCredential에는 다음과 같은 JSON 형식을 인증 API에서 필요한 형식으로 변환시키는 메서드들이 있다.
parseCreationOptionsFromJSON(), parseRequestOptionsFromJSON(), PublicKeyCredential.toJSON()
- 웹 어플리케이션에서 서버(신뢰 당사자)를 대신하여 인증기에 navigator.credentials.create()를 통하여 새로운 자격 증명 생성을 요청한다. 이 요청에는 특정 장치 기능을 요청하는 publickKey 옵션(생체 인증과 같이 인증기가 제공하는 인증 방식)이 전달된다. 일반적은 create() 형태는 아래와 같다. create()의 매개변수들은 변조되지 않았다는 것을 증명하는 서명이 포함된 SHA-256 해시값과 함께 인증기에 전달된다.
- 인증기는 유저의 동의를 받아 키 쌍을 생성한 후 public key와 선택적으로 서명된 증명을 웹 어플리케이션으로 전달한다. 이는 create()가 호출 완료될 때 반환되는 promise가 PublicKeyCredential 객체 인스턴스 형태로 반환될 때 전달된다.(PublicKeyCredential.response 속성에 증명 정보가 포함되어 있음).
- 웹 어플리케이션에서 적절한 메커니즘을 활용하여 PublicKeyCredential를 서버에 전달한다.
- 서버는 추후 인증을 위해 public key를 사용자 정보와 같이 저장한다. 이 과정에서 등록이 실제 완료되었는지, 조작되어 있지 않는지 확인하는 검증을 수행한다. 검증은 다음 과정들을 포함한다.
- challenge가 기존에 보냈던 것과 동일한지 비교
- 출처(origin) 비교
- 키 쌍을 생성하는 데 사용된 인증자의 모델에 대해 서명 및 증명이 올바른 인증서 체인을 사용하고 있는지 확인
let credential = await navigator.credentials.create({
publicKey: {
challenge: new Uint8Array([117, 61, 252, 231, 191, 241, ...]),
rp: { id: "acme.com", name: "ACME Corporation" },
user: {
id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]),
name: "jamiedoe",
displayName: "Jamie Doe"
},
pubKeyCredParams: [ {type: "public-key", alg: -7} ]
}
});
경고: 증명은 신뢰 당사자가 인증 장치의 출처를 확인할 수 있는 방법을 제공한다. 따라서 신뢰 당사자는 인증기 허용 목록을 계속 유지시키려 하면 안 된다.
사용자 인증
사용자가 WebAuthn을 통해 등록한 후, 서비스에 인증(로그인 등)을 시도할 수 있다. 인증 절차는 등록 절차와 비슷하지만 아래와 같은 차이점이 있다.
- 사용자나 신뢰 당사자의 정보를 요구하지 않는다.
- 인증기의 키쌍이 아닌 인증할 서비스를 위해 만들었던 키 쌍을 사용하여 주장(assertion)을 만든다.
일반적인 인증 흐름은 아래와 같다.
- 신뢰 당사자가 challenge를 생성하여 적절한 보안 메커니즘을 이용하여 신뢰당사자 및 사용자 자격 증명 목록과 같이 사용자 에이전트에게 전달한다. 또한 해당 자격 증명이 어디에 있는지(예: 로컬 내장 인증 장치, USB 등)에 대한 정보도 같이 제공이 가능하다.
- 브라우저는 navigator.credentials.get() 호출을 통해 인증기에 challenge 서명을 요청한다. get() 호출에는 publicKey 옵션이 포함된 자격증명을 같이 전달한다. 일반적인 get() 호출은 아래와 같다. get()의 매개변수들은 호출 시 인증을 위해 인증기에 전달된다.
- 인증기가 전달받은 자격 증명을 갖고 있고, challeng에 서명이 가능한 경우, 유저의 동의를 받아 서명된 인증을 웹 어플리케이션에 반환한다. 이는 get() 호출이 완료될 때 반환되는 promise가 PublicKeyCredential 객체 인스턴스 형태로 반환될 때 전달된다.(PublicKeyCredential.response 속성에 증명 정보가 포함되어 있음).
- 웹 어플리케이션은 인증 검증을 위해 신뢰당사자 서버로 서명된 인증을 전달한다. 검증 부분은 다음과 같다.
- 기존 저장되어 있던 public key를 사용하여 인증 장치에 서명이 되었는지
- 기존 서버에서 생성된 challenge와 인증기에서 서명을 한 챌린지가 일치한 지
- 신뢰당사자의 id가 이 서비스에서 비롯된 건지
- 서버에서 검증이 완료되면, 인증 절차는 성공적으로 마무리된다.
let credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array([139, 66, 181, 87, 7, 203, ...]),
rpId: "acme.com",
allowCredentials: [{
type: "public-key",
id: new Uint8Array([64, 66, 25, 78, 168, 226, 174, ...])
}],
userVerification: "required",
}
});
API 접근 제어
WebAuthn의 가용성은 아래 두 지시어를 지정하는 Permissions Policy를 통해 제어할 수 있으며, 아래 두 지시어 설정이 가능하다.
- publickey-credentials-create: publicKey 옵션과 같이 navigator.credentials.create()의 가용성을 제어한다.
- publickey-credentials-get: publicKey 옵션과 같이 navigator.credentials.get()의 가용성을 제어한다.
두 지시어는 기본적으로 "self" 허용 목록 값을 가지므로, 기본적으로 이러한 메서드는 최상위 레벨 문서 context에서 사용할 수 있다.
추가적으로 get()는 최상위 문서와 동일한 출처에서 로드된 중첩 브라우징 컨텍스트에서도 사용할 수 있다. 만약 publickey-credentials-get 및 publickey-credentials-create Permission-Policy 지시어에 의해 허용된다면, get() 및 create() 메서드는 최상위 문서와 다른 출처에서 로드된 중첩 브라우징 컨텍스트(iframe 내)에서도 사용할 수 있다. 크로스-오리진 create() 호출의 경우, iframe에서 허가가 allow= 속성으로 부여된 경우, 해당 프레임은 또한 일시적 활성화(Transient activation)를 가져야 한다.
참고 : 정책이 이러한 메서드의 사용을 금지하는 경우 해당 메서드에서 반환된 Promise는 NotAllowedError DOMException과 함께 거부됩니다.
기본 접근 제어
특정 하위 도메인에 대한 액세스만 허용하려면 다음과 같이 제공하면 된다.
Permissions-Policy: publickey-credentials-get=("https://subdomain.example.com")
Permissions-Policy: publickey-credentials-create=("https://subdomain.example.com")
iframe 내에서 create() 및 get() 호출 허용
절차는 아래와 같다.
- 임베딩 사이트에서 allow 속성을 통해 권한 제공:
- get()를 사용하는 경우
<iframe src="https://auth.provider.com" allow="publickey-credentials-get *"> </iframe>
- create()를 사용하는 경우
<iframe src="https://auth.provider.com" allow="publickey-credentials-create 'self' https://a.auth.provider.com https://b.auth.provider.com"> </iframe>
- get()를 사용하는 경우
- 신뢰 당사자는 Permissions-Policy 헤더를 통해 위 액세스에 대한 권한을 제공해야 한다.
- 헤더:
Permissions-Policy: publickey-credentials-get=* Permissions-Policy: publickey-credentials-create=*
- iframe 내 특정 URL에 대해서만 신뢰 당사자 사이트를 임베드할 수 있도록 하려면:
Permissions-Policy: publickey-credentials-get=("https://subdomain.example.com") Permissions-Policy: publickey-credentials-create=("https://*.auth.provider.com")
- 헤더:
인터페이스
AuthenticatorAssertionResponse
인증자가 CredentialsContainer.get() 호출로 시작된 인증 요청을 성공적으로 처리할 수 있는 키 쌍을 가지고 있음을 서비스에 증명한다. get() Promise가 반환될 때 얻은 PublicKeyCredential 인스턴스의 response 속성에서 사용할 수 있다.
AuthenticatorAttestationResponse
WebAuthn 자격 증명 등록(CredentialsContainer.create() 호출)의 결과를 제공한다. 서버가 WebAuthn 인증을 수행하는 데 필요한 자격 증명 ID 및 공용 키와 같은 정보를 포함한다. create() Promise가 반환될 때 얻은 PublicKeyCredential 인스턴스의 response 속성에서 사용할 수 있다.
AuthenticatorResponse
위 두 인터페이스(AuthenticatorAssertionResponse, AuthenticatorAttestationResponse)의 기본 인터페이스다.
PublicKeyCredential
서비스에 로그인하기 위한 자격 증명으로, 피싱에 안전하고 데이터 유출에 강한 비대칭 키 쌍을 사용한다. create() 또는 get() 호출로 반환된 Promise가 반환될 때 얻는다.
다른 인터페이스로 확장
CredentialsContainer.create() publicKey 옵션
publicKey 옵션을 사용하여 create()를 호출하면, 인증 장치를 통해 새로운 비대칭 키 자격 증명의 생성
CredentialsContainer.get() , publicKey 옵션
publicKey 옵션을 사용하여 get()를 호출하면, 사용자 에이전트가 기존 자격 증명 세트를 사용하여 신뢰 당사자에 인증하도록 요청
예시
원문 참조