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: kSecAccessControlUserPresence ou 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
}