MASTG-KNOW-0057: Serviços Keychain
As APIs do keychain do iOS podem (e devem) ser usadas para implementar autenticação local. Durante esse processo, o aplicativo armazena no keychain um token de autenticação secreto ou outro dado secreto que identifica o usuário. Para autenticar em um serviço remoto, o usuário deve desbloquear o keychain usando sua senha ou impressão digital para obter o dado secreto.
O keychain permite salvar itens com o atributo especial SecAccessControl, que permitirá acesso ao item no keychain apenas após o usuário passar pela autenticação do Touch ID (ou código de acesso, se tal fallback for permitido pelos parâmetros do atributo).
Nota sobre a temporariedade das chaves no Keychain: Diferente do macOS e do Android, o iOS não suporta temporariedade na acessibilidade de um item no keychain: quando não há verificação de segurança adicional ao acessar o keychain (ex:
kSecAccessControlUserPresenceou similar está definido), então uma vez que o dispositivo é desbloqueado, uma chave estará acessível.
No exemplo a seguir, salvaremos a string "test_strong_password" no keychain. A string pode ser acessada apenas no dispositivo atual enquanto o código de acesso estiver definido (parâmetro kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) e após autenticação por Touch ID apenas para os dedos atualmente cadastrados (parâmetro SecAccessControlCreateFlags.biometryCurrentSet):
// 1. Criar o objeto AccessControl que representará as configurações de autenticação
var error: Unmanaged<CFError>?
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
SecAccessControlCreateFlags.biometryCurrentSet,
&error) else {
// falha ao criar objeto AccessControl
return
}
// 2. Criar a query de serviços do keychain. Atenção: kSecAttrAccessControl é mutuamente exclusivo com o atributo kSecAttrAccessible
var query: [String: Any] = [:]
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrLabel as String] = "com.me.myapp.password" as CFString
query[kSecAttrAccount as String] = "OWASP Account" as CFString
query[kSecValueData as String] = "test_strong_password".data(using: .utf8)! as CFData
query[kSecAttrAccessControl as String] = accessControl
// 3. Salvar o item
let status = SecItemAdd(query as CFDictionary, nil)
if status == noErr {
// salvo com sucesso
} else {
// erro ao salvar
}
// 4. Agora podemos solicitar o item salvo do keychain. Os serviços do keychain apresentarão o diálogo de autenticação ao usuário e retornarão os dados ou nil dependendo se uma impressão digital válida foi fornecida ou não.
// 5. Criar a query
var query = [String: Any]()
query[kSecClass as String] = kSecClassGenericPassword
query[kSecReturnData as String] = kCFBooleanTrue
query[kSecAttrAccount as String] = "My Name" as CFString
query[kSecAttrLabel as String] = "com.me.myapp.password" as CFString
query[kSecUseOperationPrompt as String] = "Please, pass authorisation to enter this area" as CFString
// 6. Obter o item
var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
if status == noErr {
let password = String(data: queryResult as! Data, encoding: .utf8)!
// senha recebida com sucesso
} else {
// autorização não passou
}