- CollectionView
- 야곰아카데미
- LanguageGuide
- Apple
- 애플
- Accessibility
- UILabel
- iPad
- 애플사이다
- iTerm
- TOSS
- HIG
- GOF
- lineBreakMode
- 스위프트
- Swift
- lineBreakStrategy
- github
- Keychain
- UIKit
- DiffableDataSource
- 디자인패턴
- Combine+UIKit
- IOS
- 앱개발
- Human Interface Guidelines
- orthogonalScrollingBehavior
- 전달인자 레이블
- WWDC
- Split View
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Today
- Total
애플사이다의 iOS 개발 일지
[Keychain] 공식문서 읽는 방법 - 공식문서만으로 키체인을 이해해보자 (긴글 주의) 본문
[Keychain] 공식문서 읽는 방법 - 공식문서만으로 키체인을 이해해보자 (긴글 주의)
Applecider 2022. 8. 26. 23:53공식문서를 읽다 보면 링크를 타고 다른 문서를 읽게 되고,
또 그 안에 링크를 타고 다른 글을 읽어야 하고... 가 반복되기 때문에 지칠 때가 있다.
이번 포스트에서는 공식문서들을 효율적으로 떠돌아다니는 방법을 소개하고자 한다.
다른 키워드에 비해 Keychain에 대한 공식문서 양이 (그나마) 방대하지 않아서 (← 수박 조언 감사해요)
오직 공식문서만으로 Keychain을 이해해보는 시도를 했다.
아래의 떠돌이 과정 (빨간색 글씨 참고)을 통해 공식문서의 중요도를 판단하는 데 도움이 되었으면 한다.
1. 최상단의 문서 - Keychain Services
Keychain에 대한 설명이 시작되는 문서는 Keychain Services이다.
- 요약 : Keychain service란 사용자 대신 작은 데이터 덩어리(small chunks of data)를 안전하게 저장하기 위한 기능이다.
- 이게 왜 필요할까? 사용자가 비밀번호 등의 데이터를 일일이 기억하는 것이 번거롭기 때문이다.
- Keychain service API는 앱에게 작은 데이터를 Keychain에 저장할 수 있는 메커니즘을 제공한다.
- Keychain (열쇠고리)은 작은 데이터를 저장하는 “암호화된 데이터베이스” (encrypted database)이다.
- 작은 데이터란 비밀번호, 신용카드 정보, 짧은 메모 등의 기밀 정보, 그리고 사용자가 인지하지 못하는 암호키 (cryptographic keys), 인증서 등을 말한다.
*다음은 문서 하단 Topics의 API Components의 3개 링크를 따라가 읽으면 된다.
2. API Components
2-1) Keychain Item
- 요약 : Keychain에 Data (기밀 정보)를 저장할 때, Data를 포장해서 Item 형태로 저장한다.
- Data와 Attribute를 함께 포장해서 1개의 Item으로 만든다. Attribute를 통해 Data를 검색/접근한다.
- Keychain service는 무슨 일을 할까? Data를 암호화하고, Keychain에 Item을 저장하는 것을 관리한다. (Keychain은 디스크에 저장된 “암호화된 데이터베이스”이다.)
- 나중에 “승인된 프로세스” (authorized processes)를 통해 Keychain service를 사용하여 Item을 찾고 Data의 암호를 해제한다.
*문서 하단 Topics에 First Steps 항목이 있다면 반드시 읽어야 한다. 기초 개념을 다루기 때문이다.
*아래의 Adding Keychain Items, Keychain Item Search, Keychain Item Modification 항목도 중요해보인다.
(Legacy 어쩌구 주제들은 '과거에 이 방식을 사용했구나' 하고 넘어가면 된다.)
*일단 1번 문서와 연결된 나머지 링크들을 먼저 읽자.
2-2) Keychains
- 요약 : macOS의 전체 Keychain을 생성하고 관리한다.
- iOS Keychain과 macOS Keychain의 특성이 다르다.
- iOS
- 여러 앱들은 single Keychain에 접근할 수 있다.
*single Keychain이란?
앱마다 개별적인 Keychain을 가지는 게 아니라, 여러 앱들이 동일한 열쇠고리를 공유한다는 의미이다. - 아이폰의 잠금/잠금 해제 상태에 따라 Keychain도 자동으로 아이폰과 동일한 잠금/잠금 해제 상태가 된다.
- 여러 앱들이 동일한 Keychain을 공유하고 있지만, 1개 앱이 Keychain에 들어있는 모든 Item들에 접근할 수 있는 것이 아니다. 특정 앱은 자신이 가진 Keychain Item에만 접근 가능하다.
(앱이 소속된 group이 있는 경우, 해당 group의 Item에는 접근 가능하다.)
- 여러 앱들은 single Keychain에 접근할 수 있다.
- macOS
- 여러 개의 Keychain을 가질 수 있다.
- 맥북의 Keychain Access 앱을 통해 사용자가 직접 Keychain을 관리할 수 있고, iOS와 마찬가지로 default Keychain으로 암시적으로 작업할 수 있다.
- 그럼에도 Keychain service API는 개발자가 직접 Keychain을 조작 (생성/수정)할 수 있도록 함수를 제공한다.
ex. 특정 앱 전용의 Keychain을 생성/관리할 수 있다. - ”강력한 접근 제어 메커니즘”은 일반적으로 “키 체인 접근 utility”를 복제하려는 앱 이외의 다른 앱에 대해 이것 (Keychain 조작)을 불필요하게 만든다.
*요약 부분을 때 'macOS에 대한 내용이니까 당장 앱 개발할 때는 필요 없겠구나'를 알 수 있다.
*문서 하단 Topics를 훑어보며 'Creation and Deletion, Search 등이 있구나'를 체크하고 넘어가면 된다.
2-3) Access Control Lists (AC List)
- 요약 : macOS에서 Keychain Item에 접근 가능한 앱을 제어한다.
- macOS에서 1개 Keychain Item은 Access 인스턴스를 가진다.
(Access 인스턴스에는 “AC List”가 들어있다. AC List는 여러 개의 “Entry(항목)”로 구성된다.) - Entry에는 Operations 배열 및 (해당 item을 사용하여 operations를 수행할 수 있음을 신뢰할 수 있는) Trusted app 배열이 들어있다.
- AC List는 해당 Keychain Item에 대한 접근 가능성 (Accessibility)을 제어한다.
- 이러한 AC List는 어떻게 사용될까?
특정 앱이 문서에 서명 (sign) 하기 위해 Keychain Item에 접근하려 한다면, 시스템은 AC List를 뒤져서 원하는 operation을 갖고 있는 Entry를 찾아낸다.- 만약 해당 Entry가 없으면 시스템은 접근을 거부한다. (거부 당한 앱은 다른 작업을 시도하거나, 사용자에게 알림을 줄 수 있다.)
- 해당 Entry가 있으면 시스템은 Entry의 Trusted app 배열에 그 앱이 있는지 확인한다.
- 만약 앱이 있으면 시스템은 접근권한을 부여 (grants access)한다.
- 앱이 없으면 시스템은 사용자에게 확인 메시지를 표시한다. 사용자는 Deny, Allow, Always Allow 등을 선택할 수 있다. 사용자가 접근을 허용하면, 시스템은 그 앱을 Entry의 Trusted app에 추가한다.
- Important: AC List는 iOS, 그리고 iCloud Keychain을 사용하는 macOS 앱에는 사용 불가하다.
이러한 환경에서 Keychain Item을 공유하려면, Access Group을 사용해야 한다.
(Sharing Access to Keychain Items Among a Collection of Apps 링크를 훑어보면, “하나의 개발 팀”에서 개발한 family apps가 있고, 그 앱들이 동일한 사용자 정보를 활용하는 상황이라면, Access Group을 지정해서 사용자 정보 (Keychain Items)를 공유할 수 있다는 내용이 나온다.)
*문서 하단 Topics에 Access Creation, Access Query 등이 있음을 훑고 넘어간다.
*여기까지 1번 문서 Topics의 API Components를 모두 읽었다. 이제 다시 2-1번 문서에서 봤던 First Steps 링크를 보러간다.
3. Article - Using the Keychain to Manage User Secrets
- 요약 : 사용자의 작은 기밀정보를 Keychain에 보관하여 사용자가 일일이 기억할 필요가 없게 하자.
- Keychain service를 통해 사용자는 암호화된 저장소에 쉽게 접근할 수 있다.
ex. 인터넷 비밀번호를 저장하기 위해 Keychain Item을 사용한 프로세스 예시- 아래는 사용자의 인터넷 비밀번호가 인증되면 (End 단계), 네트워크 접근을 할 수 있음을 나타내는 그림이다.
- 비밀번호가 들어있는 Keychain Item을 Add 또는 Modify하거나 비밀번호를 인증하는 과정을 설명한다.
- SecItemCopyMatching을 사용하여 Item을 검색한다. Item이 없으면 SecItemAdd를 통해 Item을 Add한다.
- Item이 있으면 Authenticate(인증)을 진행한다. 인증에 실패하면 SecItemUpdate 을 통해 Item을 Modify한다.
- Involve the User When Needed (필요시에만 사용자를 통해 입력값을 받음)
- 앱이 최초로 Credentials (자격 증명)을 필요로 할 때는 Keychain에 저장된 비밀번호가 없다.
- 이 경우 앱은 사용자에게 메시지를 표시하여 Credentials를 입력받는다. (위 그림의 오른쪽 Flow 참고)
- 앱은 SecItemAdd 함수를 통해 이 사용자 입력값을 저장한다. 이제 앱은 네트워크 접근을 이어갈 수 있다.
(본문 링크는 중요하므로 꼭 확인해야 한다.)- 참고 - 오른쪽 Flow에서 사용자 입력값에 대한 Authenticate (인증)이 필요한 이유는?
- 이 상황에서 '인증'이란 사용자의 id/pw가 일치하는지 서버에게 물어보는 것이다.
- mac에서 사용하던 웹사이트를, 처음으로 앱에서 로그인할 때, id/pw는 있고, 키체인은 없을 때 오른쪽 Flow를 탄다.
- 제대로 된 사용자 입력값을 받을 때까지 Item Add를 하지 않는다.
- 참고 - 오른쪽 Flow에서 사용자 입력값에 대한 Authenticate (인증)이 필요한 이유는?
- 나중에 서버가 재인증 (reauthentication)을 요구할 때, 앱은 사용자를 거치지 않고 Keychain에 저장한 Credentials를 꺼내올 수 있다.
- Keychain에 Item을 추가하는 방법은 Adding a Password to the Keychain 문서를 확인하면 된다.
(마찬가지로 본문 링크이므로 꼭 확인해야 한다.)
- Avoid Bothering the User in the Common Case
- 사용자로부터 비밀번호를 입력받거나, 새 비밀번호를 설정하는 등의 interaction을 최소화하는 것이 좋다.
따라서 일반적으로 가운데 Flow를 따른다. - 사용자가 앱을 오랜만에 사용했을 때 등의 상황에서 Secure network resource는 주기적으로 reauthentication을 요구한다.
- 이에 대응하기 위해 앱은 SecItemCopyMatching 함수를 통해 Keychain에서 해당 비밀번호를 찾는다.
이 비밀번호로 인증이 성공하면 사용자 interaction이 필요 없어서 좋다.
- 사용자로부터 비밀번호를 입력받거나, 새 비밀번호를 설정하는 등의 interaction을 최소화하는 것이 좋다.
- Handle Changes Gracefully
- 가끔 사용자가 앱 영역 밖에서 비밀번호를 수정할 수 있다. (ex. 웹사이트에서 사용자가 비밀번호를 변경하는 경우)
- 이때 앱의 subsequent keychain item을 찾아서 얻은 비밀번호는 수정 이전의 비밀번호이므로 인증에 실패하게 된다.
- 이 경우 사용자를 통해 새로운 비밀번호를 받고, SecItemUpdate 함수를 통해 기존 저장값을 수정한다.
(위 그림의 왼쪽 Flow 참고) - SecItemDelete 함수를 통해 Keychain에서 비밀번호를 완전히 삭제할 수 있다.
*문서 하단 Topics의 Item Creation and Modification의 3개 링크가 중요해 보인다. (2-1번 문서의 상위 Topic에도 있었던 링크)
*See Also의 First Steps 링크를 먼저 보는 것이 좋다. 더 기초적인 개념일 테니..
(위 링크를 따라가도 좋지만, 어차피 본문에 First Steps 링크가 들어있다.)
4. Class - SecKeychainItem
- 요약 : Keychain을 나타내는 opaque 타입이다.
*opaque 타입이란?
반환타입이 Opaque 타입인 함수는 반환 정보를 외부에 숨길 수 있다.
예를 들어 함수의 반환타입이 Opaque 타입이라면, "A 프로토콜을 채택한 타입"이면 모두 가능하도록 설정할 수 있다. - Keychain에 저장된 certificate (인증서)에 대한 SecKeyChainItem 객체는 SecCertificate로 안전하게 캐스팅될 수 있다.
(본문 링크인 SecCertificate을 훑어보면, 특정 인증서를 나타내는 CoreFoundation 추상 타입의 객체임을 알 수 있다.)
*인증서 관련 개념은 나중에 찾아보기로 하고 일단 패스...
(Apple은 네이밍을 신경 쓴다. 클래스 이름을 먼저 확인하자. 클래스 요약을 보면 이 내용은 OS, 네트워크 프로토콜 관련 내용이므로 일단 패스해도 된다고 유추할 수 있다.)
*문서 하단 Topics에 또 First Steps가 있다. 젠장...
그래도 하나는 이미 읽은 링크이고, 하나는 2번 문서의 First Steps 마지막 링크와 일치하니까 다행이다..
이제 이 마지막 링크를 보자.
5. Function - SecKeychainItemGetTypeID()
- 요약 : Keychain Item 객체가 속한 Opaque 타입의 identifier를 반환한다.
- 이 함수의 반환값을 CFTypeID identifier와 비교할 수 있다. CFTypeID identifier는 CFGetTypeID(_:) 함수의 반환값이다.
(본문 링크를 훑어보면 CFTypeID는 UInt의 typealias이고, Core Foundation에서 타입 identifier를 정의하며, 타입 identifier란 CoreFoundation 객체가 "속해 있는" Opaque 타입을 식별하는 UInt 값임을 알 수 있다. 대충 넘어가자..) - 이 함수의 반환값은 release 마다, platform 마다 변경될 수 있다.
*문서 하단 See Also의 First Steps는 이미 읽은 링크들이다.
이제 3번 문서의 Item Creation and Modification의 링크를 따라간다. 이게 본론인 듯. 드디어 코드가 나온다..
6. Article - Adding a Password to the Keychain
- 요약 : 사용자를 대신해서 Keychain에 네트워크 Credentials (자격 증명)을 추가하는 방법을 설명한다.
- Keychain service를 사용하면 사용자 비밀번호를 간단히 저장할 수 있다.
(1) Get Set Up (설정하기)
- 앱에서 사용할 Credentials 정보를 저장할 구조체를 정의해라.
- 그다음, Keychain 접근 결과를 나타내기 위한 Error 열거형을 정의해라.
- 그리고 앱의 서버를 입력해라.
struct Credentials {
var username: String
var password: String
}
enum KeychainError: Error {
case noPassword
case unexpectedPasswordData
case unhandledError(status: OSStatus)
}
static let server = "www.example.com"
(2) Create an Add Query
- 위에서 정의한 Credentials 구조체의 인스턴스, server 상수를 사용하여 Add query를 생성해라.
let credentials = Credentials(username: "Applecider", password: "1234")
let account = credentials.username
let password = credentials.password.data(using: String.Encoding.utf8)! // 비밀번호는 Data 타입으로 인코딩함
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: server,
kSecValueData as String: password]
- 위 코드의 query 딕셔너리를 자세히 보자.
- 첫 번째 key-value : kSecClass가 있는 key-value는 “Item이 어떤 종류의 정보인지” 나타낸다.
예시의 value인 kSecClassInternetPassword는 "Item이 Internet password (인터넷 비밀번호)임"을 나타낸다.
→ ❗Keychain service는 이것을 보고, Data가 기밀 정보이고 암호화를 필요로 한다고 추론한다. 또한 Item이 Attribute를 가지는 것을 보장한다. 여기서 Attribute는 다른 인터넷 비밀번호와 이 Item을 구별하는 역할을 한다.
(다른 인터넷 비밀번호의 Attribute와 이 Item의 Attribute가 구별된다는 뜻인 듯)
This also ensures that the item has attributes that distinguish it from other Internet passwords, such as the server and account to which it applies. - 두세 번째 key-value : 위에서 말한 Attribute 정보를 제공하고 있다.
(상수 account를 통해 사용자의 이름을, 상수 server를 통해 도메인 이름을 첨부한다.) - 가장 중요한 비밀번호는 Data 인스턴스로 인코딩하여 query에 넣는다.
- Note: Keychain service는 관련된 kSecClassGenericPassword 를 제공한다. Generic password (일반적인 비밀번호, 제네릭 타입 아님)는 Internet password와 비슷하지만, remote access와 관련된 Attribute가 없다. kSecAttrServer 등의 Attribate가 필요 없을 때 사용하면 된다.
- (본문 링크를 타고 kSecClassGenericPassword 를 훑어보면, 일반적인 비밀번호 Item을 나타내는 “값”이며, 전역 변수임을 알 수 있다. 아래의 Attribute들은 이 클래스의 Item에 적용된다. 그리고 이 전역변수의 타입은 CFString 클래스인데, Core Foundation에 속한다. iOS 개발에 대부분 사용되는 Foundation 프레임워크의 Definition을 뜯어보면 이 Core Foundation을 import하고 있다.)
- 참고 - 접두어 k- 는 왜 붙일까? (참고: What does the 'k' prefix indicate in Apple's APIs?)
- The k means “constant” in hungarian notation. (Hungarian noun “konstans” means “constant”.)
- macintosh 프로그래밍 초기부터 사용한 네이밍 컨벤션이다.
(3) Add the Item
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
- query가 완성되면 SecItemAdd 함수에 전달하면 된다.
*본문 링크는 중요하니까 SecItemAdd 함수에 대한 본문 링크를 먼저 확인해보자. (7번 문서 요약 참고) - 일반적으로 Add operation의 두 번째 argument에서 참조 형태로 제공되는 return data를 무시한다.
하지만 함수의 return status를 확인하여 operation이 성공했는지 항상 확인하는 것이 좋다.
ex. 주어진 Attribute를 가진 Item이 이미 존재하는 경우 Add operation이 실패할 수 있다.
(4) Ensure Searchability
- 나중에 Item을 검색하려면, Attribute를 활용해야 한다.
- 위 예시에서 server 및 account는 Item을 다른 Item과 구별하는 특징이다.
- server와 같은 constant attribute는 검색하는 동안 동일한 값을 사용하지만, account는 런타임 때 사용자가 제공한 값을 가지고 있으므로 dynamic attribute이다.
앱이 다양한 Attribute를 가지는 유사한 Item을 추가할 일이 없다면, dynamic attribute를 search 매개변수에서 생략할 수 있다.
*뒷부분은 나중에 보자...
*(본문 링크 SecItemAdd 함수를 봤다면) Add Item을 다 봤으니 Search Item 부분을 읽어보자. (9번 문서 요약 참고)
7. Function - SecItemAdd(_:_:)
func SecItemAdd(_ attributes: CFDictionary,
_ result: UnsafeMutablePointer<CFTypeRef?>?) -> OSStatus
- 요약 : 1개 이상의 Item을 Keychain에 추가한다.
- 문서가 길지만.. 침착히 매개변수 attributes, result 그리고 반환타입을 알아보자.
- attributes 매개변수
- 타입은 딕셔너리 (CFDictionary)이다. (CFDictionary 클래스는 immutable 딕셔너리 객체에 대한 참조이다.)
- 딕셔너리의 역할은 Keychain에 추가할 Item을 설명하는 것이다. query가 이 매개변수에 전달된다.
- ✅ query에는 Item의 class, data, atrribute 정보가 담겨있다.
- Item의 Class
- Items의 Classes에 따라 다른 Attribute와 behavior이 활용된다. kSecClass라는 key에 대한 value는 무엇을 의미할까? ✅ value는 Keychain service에게 “저장할 Data가 어떤 종류인지” 알려주는 역할을 한다. 저장할 Data가 비밀번호인지, certificate인지, cryptographic key인지 나타낼 수 있다. (ex. kSecClassGenericPassword는 저장할 데이터가 일반적인 비밀번호임을 알려준다.)
- 본문 링크의 kSecClass를 보면, 딕셔너리의 key이고 value는 Item의 Class를 나타내는 전역 변수임을 알 수 있다.
- Item Class Keys and Values를 보면, Keychain Item의 종류 (비밀번호, certificate 등)에 따라 Class를 지정해야 함을 알 수 있다.
*문서 하단 Topics의 Item Class의 Key와 Value로 들어갈 수 있는 k- 상수들을 확인할 수 있다!
훑어보니 중요한 문서인 듯 (8번 문서 요약 참고)
- Data
- ✅ 암호화시켜서 저장하게 될 데이터를 의미한다. kSecValueData라는 key를 사용하고, key의 value에 저장할 Data (ex. 비밀번호)를 넣는다. Keychain service는 Item의 종류에 따라 이 Data를 암호화할지 말지를 결정한다. Item이 기밀정보라면 (즉, 비밀번호 타입이거나 private key를 포함하고 있는 경우) Data를 암호화한다.
- 본문 링크의 kSecValueData를 보면, 이 딕셔너리 key의 value는 CFData 타입이다.
- Attribute (optional)
- Attribute key 정보를 말한다. ✅ 이 key는 나중에 Item을 검색할 수 있게 해주고, Data가 사용되거나 공유되는 방법을 나타낸다. 원하는 만큼 Attribute를 추가할 수 있지만, Item Class에 따라 추가할 수 있는 Attribute의 종류가 정해져 있다.
- 본문 링크의 Item Attribute Keys and Values를 보면, Attribute 정보를 나타내기 위한 key/value를 확인할 수 있다. (ex. 아래의 Topics의 Password Attribute Keys를 보면 저장할 Data가 비밀번호인 경우에 사용 가능한 Attribute key들이 나와있다.)
- 반환 타입 (optional)
- 1개 이상의 반환 타입 key를 말한다. 이 key가 의미하는 것은 ✅ 성공적으로 완료되면 (SecItemAdd 메서드를 통한 Add Operation이 완료되면) 반환되도록 할 Data가 무엇인지이다.
→ 이걸 언제 사용할까?
“Keychain”에 Item을 추가하는 것 (Add Operation)과 별개로 “서버”에도 정보를 보내서 저장하게 할 수도 있다. 이때 서버에 보내는 작업을 할 때 정보를 result 매개변수에 담아서 보내면 된다. (Keychain을 통해 암호화한 비밀번호를 result에 담아서 서버에게 보낸다는 뜻이다.) 그리고 서버에 정보를 보낼 때 Item에 담긴 기밀정보를 모두 보내도 되지만, 기밀정보가 많다면 원하는 정보만 필터링해서 보낼 수도 있다. 보통 SecItemAdd 함수의 반환값을 무시하는데, 이때는 result 매개변수에 반환값 key를 넣을 필요가 없다. - 본문 링크의 Item Return Result Keys를 보면, SecItemAdd 함수는 result 매개변수를 통해 Item의 Data 및 Attribute를 반환하고, 이 result 매개변수에게 pointer를 제공한다. (???) query 딕셔너리에 Item result key를 사용하여 result를 어떤 형태 (format)로 나타낼지 지정한다. (???)
*이 부분을 이해하기 힘들었다. 이 함수의 result 매개변수를 먼저 보는 것이 좋겠다.
- 1개 이상의 반환 타입 key를 말한다. 이 key가 의미하는 것은 ✅ 성공적으로 완료되면 (SecItemAdd 메서드를 통한 Add Operation이 완료되면) 반환되도록 할 Data가 무엇인지이다.
- Item의 Class
- result 매개변수
- 타입은 UnsafeMutablePointer<CFTypeRef?>? 이다.
- CFTypeRef는 AnyObject의 typealias이다.
(Core Foundation에서 정의된 base 타입이다. polymorphic 함수 (???)의 반환(타입)으로 사용된다.) - UnsafeMutablePointer는 Generic Structure @frozen struct UnsafeMutablePointer<Pointee> 이다.
이 인스턴스를 사용하여 메모리에 있는 특정 타입 (Pointee 타입)의 데이터에 접근할 수 있다. - 반환 시 (SecItemAdd 메서드의 반환) 새롭게 추가된 Item에 대한 참조이다. 이 매개변수의 정확한 타입은 attribute 매개변수의 특정 value에 따라 결정된다. (attribute 매개변수의 4. 반환 타입 key의 value를 말하는 듯)
- result가 필요 없으면 nil을 할당하고, 만약 필요하면 앱 내부에서 참조한 객체를 메모리에서 해제해줘야 한다. (???)
- 참고 - result는 왜 포인터/참조 사용할까?
사실 정답이 없는 문제이다. 값을 전달해도 되지만, 여기서는 참조를 넘겨주도록 구현했을 뿐이다.
- 반환값
- “result code”라고만 설명되어 있다.
- 타입은 OSStatus 이다. (근데 이상하게도 OSStatus 타입은 링크가 없다..)
- 본문 링크의 Security Framework Result Codes를 보면, Evaluate result codes common to many Security framework functions... 보안 관련 기능인가..?
- OSStatus을 구글링하면, 특정 함수의 “result code”라고 나온다. Keychain 관련 오류라고 봐도 될 듯?
- OSStatus 의미를 찾아주는 사이트: https://www.osstatus.com/
- Discussion
- Keychain에 여러 Item을 한꺼번에 추가하려면, kSecUseItemList key를 사용하고, value에 딕셔너리들의 배열을 넣으면 된다. 단, 비밀번호가 아닌 Item만 가능하다.
- Xcode는 application-identifier entitlement (자격)을 앱 bundle에 추가한다. Keychain service는 이 entitlement를 사용하여 앱의 Keychain Item에 대한 접근권한을 부여한다.
*이제 attributes 매개변수 부분의 본문 링크인 Item Class Keys and Values를 살펴보자.
8. Item Class Keys and Values
- 요약 : Keychain Item의 Class를 지정해라.
- Keychain Item의 종류 (ex. 비밀번호, certificate 등)에 따라 Class를 지정해야 한다.
- Item의 Class는 무엇을 의미할까? 적용할 Attribute를 정하고, 시스템에게 저장할 Data를 암호화할지 말지를 결정하게 한다.
(ex. 비밀번호는 암호화하고, certificate은 암호화하지 않는다.) - key와 아래 목록의 value를 사용하여 새로 추가할 Item의 Class를 지정한다. (Item을 추가하려면 SecItemAdd 함수를 호출하면서 attributes 매개변수에 key/value 딕셔너리를 전달하면 된다.) ← 이렇게 보니 공식문서가 친절하긴 하다...
- 나중에 Item을 검색 (search)할 때는 query 딕셔너리의 동일한 key/value를 사용하면 된다. 이때 SecItemCopyMatching(_:_:), SecItemUpdate(_:_:), SecItemDelete(_:) 함수 중 하나를 호출한다.
*아래 Topics의 Item Class의 Key와 Value로 들어갈 수 있는 k- 들을 확인할 수 있다!
9. Article - Searching for Keychain Items
- 요약 : 개발자가 지정한 검색 criteria (기준)에 따라 Keychain Item을 검색한다.
- Keychain Item을 검색하려면 query 딕셔너리를 사용하면 된다. ❗query 딕셔너리는 Keychain service API에게 어떤 Item Attribute를 검색할지, match (일치하는 항목)을 찾으면 무엇을 반환할지 알려준다.
- Attribute가 있어야 data를 찾을 수 있다!
정확히는 Keychain Item이 아니라 ”search query에 Attribute가 있어야” 제대로 반환된다.
- Attribute가 있어야 data를 찾을 수 있다!
- 또한 검색을 구체화하기 위해 개발자는 query 딕셔너리를 통해 추가적인 매개변수를 지정할 수 있다.
ex. 문자열 Attribute를 match하거나, 일치 항목의 개수를 제한하고 싶은 경우, case sensitivity (대소문자 구분 여부) 등을 제어하면 된다. - Keychain에 비밀번호를 추가하는 저번 예시를 생각해보자. 사용자는 네트워크 서비스에 대한 Credentials (앱에 저장됨)를 제공하고, 앱을 사용하다가 다른 작업으로 넘어간다. 사용자가 다시 앱을 사용할 때, 앱이 계속 작동하려면 서버에서 reauthenticate이 필요할 수 있다. 이때 앱은 Keychain을 통해 비밀번호를 load한다.
(1) ❗Create a Search Query
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: server, // 이 Attribute를 가지는 Item을 검색하겠음
kSecMatchLimit as String: kSecMatchLimitOne, // 검색 결과를 limit함
kSecReturnAttributes as String: true, // Item을 찾으면 Attribute와 Data를 꺼내겠음
kSecReturnData as String: true]
- query 딕셔너리를 구성하여 검색을 시작한다.
- 이 query는 server attribute가 일치하는 인터넷 비밀번호 Item을 검색한다. (10번 문서 요약 참고)
- kSecMatchLimit라는 search 매개변수를 통해 result (검색 결과로 나온 Item)를 single value로 제한한다.
이 경우 keychain에서 찾은 첫 번째 항목만 얻게 된다.
*SecItemCopyMatching 메서드 참고 - query 매개변수의 구성요소 중 search 매개변수가 있다. - 이 query는 Item의 Attribute 및 Data를 모두 요구하고 있다.
둘 다 필요한 이유는 kSecAttrAccount attribute가 사용자 name을 포함하고 있고, Item의 Data가 비밀번호 자체를 가지고 있기 때문이다. (Item Add query에서 사용한 Attribute를 말함)
(2) Initiate the Search
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item) // add 함수와 달리 result 매개변수가 nil이 아님
guard status != errSecItemNotFound else { throw KeychainError.noPassword } // 비밀번호를 저장한 적이 없는 경우 발생하는 error
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
- SecItemCopyMatching 함수를 호출하여 검색을 시작한다. *본문 링크를 따라 이 함수를 먼저 알아보자. (10번 문서 요약 참고)
- 먼저 반환된 status 값을 테스트해야 한다. match되는 Item이 없으면 error가 발생할 수 있다.
ex. 주어진 server에 비밀번호를 이전에 추가하지 않은 경우 - 이 경우 error가 발생하면 errSecItemNotFound result를 얻게 된다.
이 error를 통해 앱의 로그인 Flow를 처음 진행하고 있음을 감지할 수 있다. - 검색에 성공하면 SecItemCopyMatching 메서드는 item 매개변수를 통해 result (검색 결과)를 제공한다.
반환된 Item의 타입은 query의 특성에 따라 다르다.
(3) Extract the Result
- 검색할 때 여러 반환 타입을 요청했고 (Item의 Attribute 및 Data를 요청했음), single result로 제한했으므로 result가 딕셔너리일 것을 예상해야 한다.
- result를 [String: Any] 타입으로 타입 캐스팅하면, 검색해서 찾은 Item에 접근할 수 있다.
- kSecAttrAccount key와 관련된 attribute 값으로부터 username을 복구할 수 있다.
- kSecValueData key를 사용하여 비밀번호 Data를 추출할 수 있다. (추출해서 String 타입으로 인코딩하면 된다.)
guard let existingItem = item as? [String : Any],
let passwordData = existingItem[kSecValueData as String] as? Data,
let password = String(data: passwordData, encoding: String.Encoding.utf8),
let account = existingItem[kSecAttrAccount as String] as? String
else {
throw KeychainError.unexpectedPasswordData
}
let credentials = Credentials(username: account, password: password)
*위에서 말했듯이 SecItemCopyMatching 함수를 알아볼 차례이다.
10. Function - SecItemCopyMatching(::)
- 요약 : search query와 match되는 1개 이상의 Keychain Item을 반환하거나, 특정 Item의 Attribute를 복사한다.
- query 매개변수
- SecItemAdd 메서드와 구조가 유사하다. CFDictionary 타입이다.
- Item의 Class (SecItemAdd 메서드와 동일하므로 생략)
- Attribute
- 검색할 Item이 가져야 할 Attribute를 지정하여 검색 범위를 좁힌다!
- Search 매개변수
- 다양한 방법으로 검색의 조건을 설정한다. ex. result (검색 결과)를 특정 개수의 Item으로 제한할 수 있다.
- Search Attribute Keys and Values 문서를 통해 특정 조건을 설정하기 위한 key/value를 확인 가능하다.
- 1개 이상의 반환 타입
- 검색할 대상이 무엇인지 나타낸다. 이때 대상은 the item’s attributes, the item’s data, a reference to the data, a persistent reference to the data, or a combination of these 등으로 지정할 수 있다.
→ 검색할 대상이 항상 Data가 아닐 수 있고, 2번의 검색 범위를 좁히는 Attribute를 활용해서 Item을 찾고, 그 내부의 다른 Attribute를 반환하도록 할 수 있다는 뜻
→ Item에 든 내용이 많을 수도 있는데, 그중에 원하는 값들을 선별해서 받아올 수 있다. - Item Return Result Keys 문서를 통해 적절한 key/value를 확인 가능하다.
- 만약 1개 이상의 반환 타입을 지정하면, 검색 결과로 요청한 타입들을 가지고 있는 딕셔너리를 반환해준다.
- 만약 여러 개의 result를 검색하도록 허용했다면, Item 배열 형태로 반환된다.
- 검색할 대상이 무엇인지 나타낸다. 이때 대상은 the item’s attributes, the item’s data, a reference to the data, a persistent reference to the data, or a combination of these 등으로 지정할 수 있다.
- SecItemAdd 메서드와 구조가 유사하다. CFDictionary 타입이다.
- result 매개변수
- UnsafeMutablePointer<CFTypeRef?>? 타입이다.
- 반환 시 (SecItemCopyMatching 메서드의 반환), 검색하여 찾은 Item에 대한 참조이다.
(찾은 Item의 주소를 가리키는 포인터 타입)
- 아래 코드를 보자.
- 첫째 줄 : 변수 item의 타입은 CFTypeRef? 이다. 참조 (주소)를 저장하겠다는 뜻이다.
- 둘째 줄 : SecItemCopyMatching 메서드를 통해 검색하여 찾은 Keychain Item의 참조를 변수 item에 할당한다. 근데 왜 item 앞에 &가 붙어있을까? (포인터가 가리키는) 참조를 나타내기 위해서이다.
→ 이때 Data이든 Attribute이든 “복사값” (참조가 아니라)을 반환한다는 게 중요하다. &item의 원본은 키체인에 있다!
키체인 참조를 직접 주면 함부로 수정할 수 있어서 위험하기 때문이다.
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
- 반환값
- OSStatus 타입이다. result code이다.
- Discussion
- default로 첫 번째로 match된 Item을 반환한다.
*나머지 내용은 다음에...
- default로 첫 번째로 match된 Item을 반환한다.
*아까 보기로 했던 링크를 확인해보자.
11. Article - Updating and Deleting Keychain Items
- 요약 : 사용자 데이터가 변경됐을 때 Keychain Item을 수정한다.
- Overview
- 사용자가 앱 외부에서 비밀번호를 변경 (ex. 웹 사이트에서 변경)하면, 앱 Bundle에 저장된 Keychain의 비밀번호와 일치하지 않으므로 로그인에 실패하게 된다. 이 경우 사용자로부터 새로운 Credentials를 입력받아 Keychain에 변경사항을 반영해야 한다.
- 이때 SecItemAdd 메서드를 사용하면 안 된다. (기존에 Keychain에 등록해둔 Item과 동일한 Attributes를 가지는 Item은 공존할 수 없기 때문이다.) 기존의 Item을 업데이트해야 한다.
(1) Prepare a Search Query and New Attributes
// search query
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: server]
// 업데이트할 새로운 Item
let account = credentials.username
let password = credentials.password.data(using: String.Encoding.utf8)! // String->Data로 전환한 비밀번호
let attributes: [String: Any] = [kSecAttrAccount as String: account,
kSecValueData as String: password]
- Item을 업데이트하려면 두 가지가 필요하다.
- 먼저 1) search query를 통해 Item을 찾아야 한다.
(이건 implicit search이고, SecItemCopyMatching 메서드는 explicit search라고 부름)
위 예시는 kSecClass, kSecAttrServer 두 가지에 부합하는 Item을 검색하도록 했다. - 그다음 2) 업데이트할 새로운 Item 정보가 담긴 딕셔너리를 준비해야 한다.
이때 사용자의 Credentials 중에서 account 정보는 동일하고, password만 바꾸는 것도 가능하다.
(2) Execute an Update
let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
guard status != errSecItemNotFound else { throw KeychainError.noPassword }
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
- 이제 SecItemUpdate 메서드를 호출하면, 작성한 Attribute에 해당하는 모든 Item이 업데이트된다. (12번 문서 요약 참고)
- 마찬가지로 반환된 OSStatus를 확인해야 한다.
(3) Delete Items That You No Longer Need
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
- 필요 없는 Item은 Keychain에서 삭제하면 된다. (ex. 사용자가 앱에서 서버 로그아웃할 때)
- Item 업데이트할 때 사용한 search query를 사용해서 SecItemDelete 메서드를 호출하면 된다. (13번 문서 요약 참고)
- default로 Keychain service는 search query에 match되는 모든 Item을 삭제한다.
특정 Item만 삭제하려면 search query에 kSecMatchItemList key를 추가하면 된다.
*마지막으로 update / delete 관련 메서드를 보자.
12. Function - SecItemUpdate(::)
- 요약 : search query에 match되는 Item을 수정한다.
- query 매개변수
- (Item을 add/search하는 메서드와 마찬가지로) CFDictionary 타입이다.
- 업데이트하려는 Keychain Item에 대한 search query이다.
- attributesToUpdate 매개변수
- CFDictionary 타입이다.
- 1) 업데이트하려는 값에 대한 Attribute, 2) 새로운 값을 포함하는 딕셔너리이다.
- meta Attribute (???)는 이 딕셔너리에 넣을 수 없다.
- 반환값
- result code이다.
13. Function - SecItemDelete(_:)
- 요약 : search query에 match되는 Item을 삭제한다.
- query 매개변수
- 삭제하려는 Keychain Item에 대한 search query이다.
- 반환값
- result code이다.
- Discussion
- default로 search query에 해당하는 모든 Item을 삭제한다.
- key를 추가하면 특정 조건의 Item만 삭제하도록 변경할 수 있다.
- Reference
- Apple Developer Documentation > Keychain Services
- Apple Developer Documentation > Keychain Item
- Apple Developer Documentation > Keychains
- Apple Developer Documentation > Access Control Lists
- Apple Developer Documentation > Article - Using the Keychain to Manage User Secrets
- Apple Developer Documentation > Class - SecKeychainItem
- Apple Developer Documentation > SecKeychainItemGetTypeID()
- Apple Developer Documentation > Article - Adding a Password to the Keychain
- Apple Developer Documentation > SecItemAdd(_:_:)
- Apple Developer Documentation > Item Class Keys and Values
- Apple Developer Documentation > Article - Searching for Keychain Items
- Apple Developer Documentation > SecItemCopyMatching(::)
- Apple Developer Documentation > Article - Updating and Deleting Keychain Items
- Apple Developer Documentation > SecItemUpdate(::)
- Apple Developer Documentation > SecItemDelete(_:)
🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.
'iOS > 영문 공식문서 뜯어보기-iOS' 카테고리의 다른 글
[HIG] Notifications - title, content, actions 설정 시 고려사항 (0) | 2022.10.03 |
---|---|
[Keychain] 키체인 사용하기 (예제 코드) (0) | 2022.08.27 |
[AutoLayout] CHCR이란? Label의 default CHCR 값은? - 공식문서 오류 정정 (0) | 2022.06.16 |
[CollectionView] Diffable DataSource 이해하기 (3/3) - 상품 배너/목록/상세 화면을 구현한 예제코드 (1) | 2022.06.12 |
[CollectionView] Diffable DataSource 이해하기 (2/3) - 흔히 하는 실수, Modern Collection Views 예제 코드 (0) | 2022.05.30 |