[Swift] Keychain...CF...뭐...? 네?

호랭이·2022년 9월 29일
0

🍎 Swift

목록 보기
11/13

프로젝트에서 github Oauth를 이용한 로그인 기능을 구현했는데, 토큰을 유저디폴트에 저장하기에는 보안상 위험성이 크다고 생각해서 키체인에 저장하는 것으로 결정했다.

난생 처음으로 키체인 저장을 구현해보려고 했는데...
저장하고 불러오는 건 알겠는데, 사용하는 타입들과 코드의 방식이 생소하게 느껴졌다.
그래서 키체인과 키체인 구현에 관련된 것들에 대해서 한번 제대로 공부해보고 정리한 뒤 구현해보기로 했다.

역시 시작은 공식문서로!

애플 공식 문서_Keychain Services

Securely store small chunks of data on behalf of the user.
사용자를 대신하여 데이터의 작은 덩어리를 안전하게 저장한다.

Keychains

전체 키체인을 생성하고 관리한다.
iOS에서 앱은 단일 키체인에 접근할 수 있다. 이 키체인은 사용자가 장치를 잠금 해제하면 자동으로 해제되고, 장치가 잠기면 따라서 잠긴다.
앱은 자체 키체인 항목 또는 앱이 속한 그룹과 공유된 항목에만 액세스할 수 있다. 키체인 컨테이너 자체를 관리할 수 없다.
반면 macOS는 임의의 수의 키체인을 지원한다. iOS와 마찬가지로 키체인 접근 앱으로 이를 관리하고 기본 키체인을 사용하여 암시적으로 사용하기 위해 사용자에게 의존한다.
하지만 Keychain Sevice API는 키체인을 직접 조작하는 것에 사용할 수 있는 기능을 제공한다. 예를 들어 앱 전용 키체인을 만들고 관리할 수 있다.

내가 진행하는 프로젝트는 iOS 대상이기 때문에 당연히 앱 자체의 키체인을 관리해야한다!

macOS에서는 범용적으로 키체인을 사용하기 위해 키체인 자체에 대한 설정과 접근 앱, 권한 등 설정해야할 것이 더 있을 것 같다.

Keychain Service의 개요

키체인은 API 서비스이다.
몰랐다...

  1. 키체인 API는 암호화된 데이터베이스에 사용자 데이터의 작은 비트를 저장하는 메커니즘을 제공한다.
  2. 키체인은 비밀번호에 국한되지 않는다. 카드 정보나 짧은 메모와 같이 사용자의 비밀 정보를 저장할 수 있다. 인증서, 암호화 키 등 사용자가 필요하지만 인식하지 못하는 정보를 저장할 수도 있다.

    위 그림을 보면 비밀번호 외에도 다양한 항목을 보관할 수 있다는 것을 알 수 있다.


API 구성 요소

Keychain Items

  • Class SeckeychainItem
    암호 또는 암호화 키를 저장하기 위해 keychain item으로 패키징한다.
    keychain item은 데이터 자체와 함께, 공개적으로 제공할 수 있는 attributes set을 제공하여 항목의 접근성을 제어하고 검색할 수 있도록 한다.

    위의 그림과 같이, 키체인 서비스는 디스크에 저장된 암호화 데이터베이스인 키체인에 데이터를 암호화하여 저장한다. 데이터 attributes 또한 저장한다. 후에, 인증된 프로세스는 키체인 서비스를 이용하여 item을 찾고 해독한다.

  • func SecItemAdd(CFDictionary, UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
    하나 이상의 항목을 키체인에 추가한다.

  • func SecItemCopyMatching(CFDictionary, UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
    검색 쿼리와 일치하는 하나 이상의 keychain item을 반환하거나 특정 keychain item의 attributes를 복사한다.

  • func SecItemUpdate(CFDictionary, CFDictionary) -> OSStatus
    검색 쿼리와 일치하는 항목을 수정한다.

등등... 관련 메소드들이 정말 많다.

바로 이 부분에서 내가 CF로 시작하는 타입에 관한 의문이 생겼던 것인데, 일단 키체인에 대해 다 알아본 뒤에 찾아보도록 하겠다.

Item Class Keys and Values

저장할 데이터의 종류에 따라 다양한 Keychain Item이 있다.

  • let kSecClassGenericPassword: CFString
    일반 암호 항목을 나타내는 값이다.
  • let kSecClassInternetPassword: CFString
    인터넷 비밀번호 항목을 나타내는 값이다.
  • let kSecClassCertificate: CFString
    인증서 항목을 나타내는 값이다.
  • let kSecClassKey: CFString
    암호화 키 항목을 나타내는 값이다.
  • let kSecClassIdentity: CFString
    ID 항목을 나타내는 값이다.

Item Attribute Keys and Values

Keychain Item의 속성값이다.
액세스 그룹, 생성 날짜, 설명, 작성자 등등 다양한 키를 나타낼 수 있는 클래스들이 존재한다.

구현

쓰기, 읽기, 검색 기능 모두 기본적으로 쿼리를 이용한다. 키와 값의 쌍으로 쿼리를 구현한다.

let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                              kSecAttrAccount: id,
                              kSecValueData: data]
  1. 쿼리의 첫번째로 kSecClass를 지정하여 Keychain Item의 클래스를 지정한다. 나머지는 attribute key를 지정한다.
    여기서 kSecValueData는 암호화되는 데이터이다.
    쿼리에는 Keychain Item의 클래스, 데이터, attributes(선택적), 반환타입(선택적)이 포함될 수 있다.

  2. 이 쿼리를 전달인자로 SecItemAdd 등의 메소드를 호출한다.
    그 전에 먼저 메소드를 살펴보면, SecItemAdd 메소드는 아래와 같은 타입들이 사용된다.

func SecItemAdd(
    _ attributes: CFDictionary,
    _ result: UnsafeMutablePointer<CFTypeRef?>?
) -> OSStatus

attributes로 쿼리가 사용되는 것이다.
result는 뭐지...?🧐

result
On return, a reference to the newly added items. The exact type of the result is based on the values supplied in attributes, as discussed in Item Return Result Keys. Pass nil if you don’t need the result. Otherwise, your app becomes responsible for releasing the referenced object.

반환되는 값이다. 정확한 타입은 Item Return Result Keys에서 명시한 값에 기반한다. Item Return Result Keys를 사용하여 result를 받지 않는다면 nil값을 넘긴다.

Item Return Result Keys?

  • kSecReturnRef
    SecKeychainItem, SecKey, SecCertificate, SecIdentity 또는 CFData 유형의 참조를 반환
  • kSecReturnPersistentRef
    Disk에 저장하거나 프로세스 간에 전달할 수 있는 CFData 유형의 항목 참조를 반환
  • kSecReturnData
    실제 데이터를 저장하는 CFData 인스턴스를 반환
  • kSecReturnAttributes
    반환값들을 담은 딕셔너리를 반환

다시 구현으로 돌아와서,
3번에서 호출한 SecItemAdd 메소드는 OSStatus 타입을 반환한다.
이 반환값은 오류 여부, 데이터의 상태, 디스크의 상태 등등 다양한 것들을 변수로 나타낸다.

코드

func create(id: String, data: String) -> Bool {
	let query: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
                                  kSecAttrAccount: id,
                                  kSecValueData: data]
	return SecItemAdd(query as CFDictionary, nil) == errSecSuccess
}

errSecSuccess오류가 없음을 의미한다.
SecItemAdd 메소드가 오류 없이 실행되었다면 create 메소드는 true를 반환하게 된다.

CF로 시작되는 타입

CF어쩌구 하는 타입들은 바로...Core Foundation 프레임워크 타입이다.

Core Foundation은 애플리케이션 서비스, 애플리케이션 환경 및 애플리케이션 자체에 유용한 기본 소프트웨어 서비스를 제공하는 프레임워크이다.

기본데이터 타입과 여러 콜렉션 타입들에 low-level 함수로 접근하여 Foundation 프레임워크에 원활하게 연결된다.

  • 다양한 프레임워크 및 라이브러리 간에 코드 및 데이터 공유 가능
  • 어느 정도의 운영 체제 독립성을 가능하게 함
  • 유니코드 문자열로 국제화 지원
  • 플러그인 아키텍처, XML 속성 목록 및 기본 설정을 포함한 공통 API 및 기타 유용한 기능 제공

추상화

일부 Core Foundation 타입 및 함수는 서로 다른 운영 체제에서 특정 구현이 있는 것을 추상화한 것이다. 따라서 같은 API를 사용하는 코드를 다른 플랫폼으로 쉽게 이식할 수 있다.
macOS와 iOS에 동시에 사용되는 키체인에 Core Foundation을 사용한 이유..! 아하!

국제화

Core Foundation이 애플리케이션 개발에 제공하는 주요 이점 중 하나는 국제화 지원이다. String 개체를 통해 Core Foundation은 모든 OS X 및 Cocoa 프로그래밍 인터페이스 및 구현에서 쉽고 강력하며 일관된 국제화를 돕는다.
이 부분의의 필수적인 부분은 16비트 유니코드 문자의 배열을 나타내는 인스턴스인 CFString 유형이다. CFString 객체는 메가바이트급의 문자를 담을 수 있을 만큼 유연하지만, 문자 데이터를 통신하는 모든 프로그래밍 인터페이스에서 사용하기에 충분히 간단하고 저수준이다. 표준 C 문자열과 크게 다르지 않은 성능으로 이를 수행한다.




이렇게 키체인 서비스구성, 사용법, 더 나아가서 Core Fountation 프레임워크까지 알아봤다!
역시 공식 문서가 최고라는 걸 다시 한 번 느꼈다.👍👍👍

profile
삐약

0개의 댓글