Keychain error -34018 (errSecMissingEntitlement)

This thread has been locked by a moderator.

This is a continuation of from the old forums: https://devforums.apple.com/thread/246122


Calling SecItemCopyMatching will sometimes return an OSStatus of -34018 (errSecMissingEntitlement). This seems to happen when the system is running low on memory. This has not been fixed in iOS9. I've of course filed radars about this and I would encourage others to do the same while iOS 9 is under development.

Up vote post of briandw
99k views

Replies

Ditto, started seeing this today.


result = SecItemAdd((__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainItemData], NULL);


(lldb) p result

(OSStatus) $1 = -34018


😢

Did you notice if this improved things? I moved my keychain access from '....aplicationDidFinishLaunching...' to the '...applicationDidBecomeActive..' but we are still seeing the issue; although it appears to be less often.

Quinn,


OK, I followed your advice and openned a defect; I hope Apple can find the root cause and provide an OS fix or at least a workaround on the code side.

Don't hold your breath.....this bug has been in the OS for ages and no fix is in sight.


Only two options:

  • Either Apple has decided this is not worth fixing and is ignoring it -> we are screwed
  • Or Apple is trying but CAN'T fix this -> we are royally screwed


Our only course of action is to make as much noise as possible to attract Apple/Others/the Media's Attention and bring the issue to the spotlight to ensure Apple commits the appropriate resources to fixing this.


This is affecting at least a few major developers (I know Facebook/Parse runs into this issue), so we must unite to make our voices heard.


Everybody should file their own bugs about this so Apple understands it's affecting many of us. And then make as much noise as possible. Should we consider a petition to Tim?


Merry Christmas everyone. At least I know what I want front Santa this year....!

same here, on SecItemCopyMatching, XCode 7.2, iOS 9.2, running attached to debugger


you can see the code here: https://github.com/tradle/react-native-ecc


SecItemAdd goes through: https://github.com/tradle/react-native-ecc/blob/master/ios/RNECC.m#L141

SecItemCopyMatching intermittently fails with the dreaded 34018: https://github.com/tradle/react-native-ecc/blob/master/ios/RNECC.m#L399


if there's any other information i can provide to speed up the resolution of this bug, don't hesitate to ping me at mark@tradle.io

I can confirm I have another case of this bug in official releases running iOS 9.2 on more than one device. In my case that scares a lot due to we trust on the KeyStore very sensitive and crucial information for the app.


As a software engineer, It strongly draws my attention that this bug is still alive after some years bearing in mind its importance.

Same here. iPhone 6, iOS 9.2 (13C75), using Alamofire and AlamofireImage. I’m working in Swift, with a Keychain struct I created myself.


After three or four retrievals (it’s pretty consistent), SecItemCopyMatching returns -34018. (See Keychain.password(), below; the error hits at the call to SecItemCopyMatching().) Resetting the password during the run of the app does not help; killing and restarting it clears the error for another three or four cycles, which I don’t have to say is unacceptable. The stored password is still there; Keychain.password() returns it as expected. The bug manifests even if no other SecItem* call is made.


(I see I don’t release returnPointer in that func; I’ll correct that in my own copy. I don’t think it should affect the bug, it’s just an 8-byte leak. I’m leaving the fix out of this listing to preserve my example.)


I use the password by embedding a hash in the body of my transactions. (I use POSTs or PUTs, even in cases that would ordinarily use GETs.) I don’t claim this is what an experienced developer would do; I’m just describing the circumstances. All transactions are performed through Alamofire; this sometimes entails a loop of transactions such as the upload of a few images; the password is retrieved only once for each series.


Quinn’s previous suggestion that this may be an entitlements or signing issue doesn’t seem to hold up here:


  • The app runs impeccably in the simulator (which says very little about signing).
  • SecItemCopyMatching() does work a few times before failing. (Three times, I think — always the same number.)
  • I’ve winnowed out some signing identities that might have conflicted; this came up because…
  • … the forced codesign script phase (suggested elsewhere) complained it couldn’t disambiguate the identity CNs. Cutting the certificates down silenced that error but did not stop the -34018.
  • I’ve copied and pasted among the bundle ID and keychain-sharing group entitlement.
  • I’ve registered the bundle ID specifically with Apple (should not be necessary).
  • I’ve forced the provisioning profile and signature.
  • I’ve allowed Xcode to repair all the damage I did in those last few steps.


I’m appending my Keychain struct for thoroughness. I DO NOT recommend it for use in others’ work. I have not qualified it as a product. Developers must be particularly on their guard about security practices, and I have no illusions about mine.


//
//  Keychain.swift
//  Incident
//
//  Created by Fritz Anderson on 11/28/15.
//  Copyright © 2015 The University of Chicago. All rights reserved.
//

import Foundation
import Security
import CommonCrypto

func hashPassword(password: String) -> NSData {
    let hashLength = 256/8
    let stringData = password.dataUsingEncoding(NSUTF8StringEncoding)!
    let digestBytes = UnsafeMutablePointer.alloc(hashLength)
    CC_SHA256(stringData.bytes, UInt32(stringData.length), digestBytes)
    return NSData(bytes: digestBytes, length: hashLength)
}

extension NSData {
    var hexString: String {
        let contents = UnsafeBufferPointer(
            start: UnsafePointer(bytes),
            count: length)
        return contents.reduce("") {
            $0 + String($1, radix: 16)
        }
    }
}

func credentialsDictionary(user: String? = nil,
    host: String? = nil) throws -> [String: AnyObject]
{
    let userName = user ?? (DefaultsKeys.AccountName.value() as! String)
    var retval: [String: AnyObject] = ["account" : userName]
    
    let hostName = host ?? Globals.HostName.value!
    
    var chain = Keychain(user: userName, host: hostName)
    if let password = try chain.password() {
        let hash = hashPassword(password).hexString
        retval["password"] = hash
    }
    else {
        throw KeychainError.NoPasswordDefined(user: userName, host: hostName)
    }
    return retval
}

func credentialsJSONData(user: String? = nil,
    host: String? = nil) throws -> NSData
{
    let dict = try credentialsDictionary(user, host: host)
    return try NSJSONSerialization.dataWithJSONObject(dict, options: [])
}

public enum KeychainError : ErrorType {
    case Duplicate
    case OperationOnMissing
    case NotAuthorized
    case Parameter
    case Generic(osError: Int)
    case NoPasswordDefined(user: String, host: String)
    
    /// The `KeychainError` case matching an `OSStatus` value.
    ///
    /// - parameters:
    ///     - status: the `OSStatus` to match
    ///
    /// - returns: Optional `KeychainError`: `nil` if the code was `noErr`; `.Generic` if it was not covered by any of the other cases; or one of those cases if it matches a recognized code. See the source for this `func` for details.
    public static func fromOSStatus(status: OSStatus) -> KeychainError? {
        switch status {
        case noErr: return                  nil
        case errSecDuplicateItem: return    .Duplicate
        case errSecItemNotFound: return     .OperationOnMissing
        case errSecAuthFailed: return       .NotAuthorized
        case -50: return                    .Parameter
        default: return                     .Generic(osError: Int(status))
        }
    }
    
    /// A description of the represented error, tagged with an optional context string.
    ///
    /// The optional context string is prepended to the returned description. Example, without a context:
    ///
    ///     "There was no such entry."
    ///
    /// If the context is "changing the password":
    ///
    ///     "While changing the password: There was no such entry."
    ///
    /// Notice that the context string is wrapped in "While _ : ". Adjust your phrasing accordingly.
    ///
    /// - parameters:
    ///     - attempting: `nil` (the default) if there is to be no tag; otherwise a string to insert in the description.
    ///
    /// - returns: A description of the error, including the context string if one was supplied.
    public func explain(attempting: String? = nil) -> String {
        let circumstance: String
        if let attempting = attempting {
            circumstance = "While \(attempting): "
        }
        else { circumstance = "" }
        
        let expansion: String
        
        switch self {
        case .Duplicate: expansion =            "Another entry was already there."
        case .OperationOnMissing: expansion =   "There was no such entry."
        case .NotAuthorized: expansion =        "The operation was not authorized."
        case .Parameter: expansion =            "An internal error (paramErr) occurred.\n\n"
            + "Please report this to me@example.com"
        case let .Generic(osError): expansion =  "An unexpected error occurred (\(osError))."
        case let .NoPasswordDefined(user: user, host: host):
            expansion = "No password was defined for \(user) on \(host)."
        }
        
        return circumstance + expansion
    }
}


/**
 Convenient access to the system keychain.
 
 `struct Keychain` simplifies CRUD operations on the system
 keychain by wrapping every search and initialization parameter
 internally, where client code can’t see.
 
 There is no custom initializer; call `Keychain(user:,host:)`
 
 The client initializes the struct with username and host.
 After that,
 * `password()` retrieves the password, if the matching
 record is in the keychain.
 * `setPassword(_:)` creates or updates the keychain record.
 * `delete()` removes the record.
 
 Getting or setting the password is always a mutation, because the underlying Security Framework calls set `osStatus`.
 
 - throws: Any of a number of `KeychainError`s.
 The `enum` specifies the likeliest errors. Any other is thrown
 as `KeychainError.Generic`; examine `osStatus` for the code.
 */

struct Keychain {
    // MARK: Publicly-accessible data
    
    /// The login ID for the user, such as `criedel`.
    let user:       String
    
    /// The name of the host for the user’s account, such as `www.example.com`.
    let host:       String
    
    /// The error value (or `noErr`) from the last Keychain call.
    var osStatus:   OSStatus = noErr
    
    init(user: String, host: String) {
        self.user = user
        self.host = host
    }
    
    // MARK: Getter/setter for password
    
    /// The password for the user + host as stored in the keychain.
    ///
    /// The absence of the user + host, or a password for the combination,
    /// is one of the expected outcomes; the function will return `nil`
    /// in that case.
    ///
    /// This `func` takes care of the **Retrieve** part of the CRUD pattern.
    ///
    /// - returns: the password if the user + host is registered, and a password is set; otherwise `nil`
    ///
    /// - throws: Any of the `KeychainError`s for a misconfigured or forbidden access.
    mutating func password() throws -> String? {
        let returnPointer = UnsafeMutablePointer.alloc(1)
        returnPointer.initialize(nil)
        
        var terms = searchPattern()
        terms[kSecReturnData] = true
        osStatus = SecItemCopyMatching(terms, returnPointer)
        
        switch osStatus {
        case noErr:
            
            if let password = (returnPointer.memory as? NSData),
                retval = String(data: password, encoding: NSUTF8StringEncoding)
            { return retval }
            else { return nil }
            
        case errSecItemNotFound:
            return nil
            
        default:
            throw KeychainError.fromOSStatus(osStatus)!
        }
    }
    
    /// Set a (new) password for the (new) user + host.
    ///
    /// * If the combination is registered, but the password is different, the record is updated.
    /// * If they are not registered, the record is created, including the desired password.
    ///
    /// This `func` takes care of both the **Create** and **Update** parts of the CRUD pattern.
    ///
    /// - throws: Any of the `KeychainError` errors thrown by the underlying implementation.
    ///
    /// - parameters:
    ///     - newPassword: The password to set for the user + host. The `func` will not validate it.
    mutating func setPassword(newPassword: String) throws {
        if let old = try? password(),
            oldPassword = old
        {
            if newPassword == oldPassword {
                //  The password is already set. Nothing to do.
                return
            }
            else {
                //  Item exists but must be updated
                try updatePasswordInKeychain(newPassword)
            }
        }
        else {
            // nil oldPassword, meaning the item must be created.
            try createKeychainItemWithPassword(newPassword)
        }
    }
    
    /// Remove the user + host combination from the keychain.
    ///
    /// This `func` takes care of the **Delete** part of the CRUD pattern.
    ///
    /// - throws: Any of the `KeychainError` errors thrown by the underlying implementation.
    mutating func delete() throws {
        osStatus = SecItemDelete(searchPattern())
        if let error = KeychainError.fromOSStatus(osStatus) {
            throw error
        }
    }
    
    // MARK: Convenience keychain access
    
    /// A minimal dictionary of search criteria for the user + host
    private func searchPattern() -> [NSString: AnyObject] {
        let retval: [NSString: AnyObject] = [
            // Entry class
            kSecClass         : kSecClassInternetPassword,
            kSecAttrProtocol  : kSecAttrProtocolHTTPS,
            kSecAttrAuthenticationType
                : kSecAttrAuthenticationTypeHTTPBasic,
            kSecAttrAccount   : user,
            kSecAttrServer    : host
        ]
        return retval
    }
    
    /// Create a keychain item for the user + host, including password.
    ///
    /// It is assumed that no item matching the user + host is in the keychain.
    ///
    /// - parameters:
    ///     - password: The password to store in the item.
    ///
    /// - throws: Any of the `KeychainError` errors, most likely `.Duplicate`.
    private mutating func createKeychainItemWithPassword(password: String) throws {
        let passwordData = password.dataUsingEncoding(NSUTF8StringEncoding)!
        var values = searchPattern()
        values[kSecValueData] = passwordData
        //  Prevent synchronization with other devices:
        values[kSecAttrSynchronizable] = false
        //  The device has to have been unlocked since last restart, and will not be transferred to another device (as from backup)
        values[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly

        osStatus = SecItemAdd(values, nil)
        if let error = KeychainError.fromOSStatus(osStatus) {
            throw error
        }
    }
    
    /// Replace the password for an existing keychain item for user + host.
    ///
    /// It is assumed that an item matching the user + host is in the keychain.
    ///
    /// - parameters:
    ///     - password: The password to store in the item.
    ///
    /// - throws: Any of the `KeychainError` errors, most likely `.OperationOnMissing`.
    private mutating func updatePasswordInKeychain(password: String) throws {
        let passwordData = password.dataUsingEncoding(NSUTF8StringEncoding)!
        let updateData: [NSString: AnyObject] = [ kSecValueData: passwordData,
            //  Prevent synchronization with other devices:
            kSecAttrSynchronizable: false,
            //  The device has to have been unlocked since last restart, and will not be transferred to another device (as from backup)
            kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
        ]
        osStatus = SecItemUpdate(searchPattern(), updateData)
        if let error = KeychainError.fromOSStatus(osStatus) {
            throw error
        }
    }
}

Well, we are into the new year, engineers and managers at Apple are refreshed from a holiday break, so let's hope we hear something soon.

I don't know if this might be helpfull, but I have a mixed objc (started with objc) and swift app. I use SSKeychain (written in ObjC) to store and read info from the keychain.

The part in objc works fine: I can retrieve stored info.

When I try to get them using swift I obtain that error, the other weird issue is that on simulator and with ad-hoc builds everything seems to work.

I get this error only if I run the app on device (9.2) while debugging.

Other curious stuff is that after a restart I can retrieve stored info at the same line of code where I get this error.

Pretty annoing.

Has anyone seen keychain error -34018 with iOS 9.2.1?

Yes, unfortunately we are still experiencing this problem in iOS 9.2.1. Very frustrating and disappointing to be honest.

Quinn, You said the only fix you are aware of was to "restart the process." What process? securityd? It woudl be useful to know what to look for in the Activity Monitor Instrument.


And as a user application, we can't restart a system daemon like securityd, can we?




Duncan Champney

WareTo

You said the only fix you are aware of was to "restart the process." What process?

The app itself.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I'm going completely mad at the moment 😟

How can it be this annoying?


In my case it's even weirder! I have a lot of logs concerning this issue. I have logs whith memory pressure being really high (marked with a nice "Received memory warning." message from os) and the access still works, and cases with a vast free memory and still get this error!!! I guess this being related to memory pressure might be a complete hoax.

On the other hand, I always face this problem on consecutive runs, i.e. My first run will always work (regardless of memory pressure) second or third work 50% and after that it just fails. And to add some more interesting observations, I don't need to actually free any memory or restart the device, just waiting on it for a couple of minutes will do!


Somebody kill me now. I don't want to raise a child in a world were I can't rely on apple to fix an error so wide spread!


On my tomb stone you'll read: "There was a keychain, but there were no access"

As per some other developers' speculations, most of these problems are arisen from a situation where Keychain has been accessed too often! In my case I did access three time in a row and this happened. Because my use of Keychain was only related to some public/private key form of access, I was able to completely resolve my problem by caching the keys!


It seems rather redundant to say that even if there is a security measurement to prevent some attacks on the Keychain, it is resulting in way too much false positives here and needs to be solved somehow.


For further info I strongly suggest everybody to follow this SO question: http://stackoverflow.com/questions/20344255/secitemadd-and-secitemcopymatching-returns-error-code-34018-errsecmissingentit