How to find the certificate added with SecItemAdd in Keychain - ios

Following apple's doc, I wrote this code to add a certificate to my login keychain on macOS
let certUrl = Bundle.main.url(forResource: "cert", withExtension: "cer")!
let certData = try! Data(contentsOf: certUrl)
let cert = SecCertificateCreateWithData(nil, certData as CFData)!
let addQuery: [String: Any] = [
kSecClass as String: kSecClassCertificate,
kSecValueRef as String: cert,
kSecAttrLabel as String: "My Certificate"
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
guard status == errSecSuccess else {
print(status)
return true
}
print("cert added")
return true
It works, but the problem is that when I opened the Keychain Access app, I couldn't find the added certificate in login keychain's certificate category.
I tried running the code again, and it failed with duplicate certificate error. So the certificate must have been added. I wonder how to find it in Keychain Access? Or certificates added using SecItemAdd are always hidden from the Keychain Access app?
macOS version: 10.15.1

I was using Mac Catalyst. Switching to a cocoa app made it work.

Related

How to securely implement biometry with fallback application password to authenticate signing within Secure Enclave?

I am looking to implement a local authentication flow similar to many banking apps in an iOS (Swift) app for using a key in the Secure Enclave:
By default you set up an app-specific pin code
The user is then able to turn on biometry (Face ID or Touch ID) for quick authentication
The user can then sign a message using their private key stored in the Secure Enclave by either using biometrics or the user can fall back to their app-specific pin (either by choice, or if they cancel or can't be recognized for example).
What I've tried so far
The .applicationPassword flag for SecAccessControlCreateFlags seemed like a reasonable option to allow an application-defined password to be used. Furthermore, the set of possible flags also contain constraints such as such as devicePasscode and biometryCurrentSet to set access constraints that can even be combined by using conjunctions. It should be noted that applicationPassword is not a constraint but an 'option' according to the docs. It's also not very well-documented what this flag actually does. Still, I tried the following:
let flags1: SecAccessControlCreateFlags = [.privateKeyUsage,
.biometryCurrentSet, .or, .applicationPassword]
let flags2: SecAccessControlCreateFlags = [.privateKeyUsage,
.biometryCurrentSet, .applicationPassword]
let access1 = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags1,
&error)
let access2 = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags2,
&error)
Both of these options seem to work essentially the same, where first the phone prompts for biometric verification and then shows a prompt where you can enter the application password.
I tried programmatically supplying the application password using LAContext.setCredential, and this works fine as the application password prompt will no longer be shown, but iOS will still always prompt for biometrics as well (even when using the .or flag). Thus, it seems that the .or flag is not working as I had hoped together with .applicationPassword. However, these access control policies do seem to enforce that both biometrics and application password should pass, which is a nice possibility but not exactly what I was looking for.
I have also tried preventing the respective prompts such as the Face ID prompt from being shown with LAContext.interactionNotAllowed but this also does not work because of the access control flags.
Basic setup
For testing this, I have a simple iOS app set up with the following functions derived from the Secure Enclave documentation:
func getAccessControl() -> SecAccessControl {
var access: SecAccessControl?
access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage,
.biometryCurrentSet,
.or,
.applicationPassword],
nil)!
return access
}
func generatePrivateKey() throws -> SecKey {
let context = LAContext()
context.setCredential("pwd123".data(using: .utf8), type: .applicationPassword)
let attributes: NSDictionary = [
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeySizeInBits: 256,
kSecAttrTokenID: kSecAttrTokenIDSecureEnclave,
kSecUseAuthenticationContext as String: context,
kSecAttrLabel: "label-for-reference",
kSecClass: kSecClassKey,
kSecPrivateKeyAttrs: [
kSecAttrIsPermanent: true,
kSecAttrApplicationTag: "tag-for-reference",
kSecAttrAccessControl: getAccessControl(),
]
]
var error: Unmanaged<CFError>?
guard let privateKey = SecKeyCreateRandomKey(attributes, &error) else {
throw error!.takeRetainedValue() as Error
}
return privateKey
}
func retrieveKey(context: LAContext? = nil) -> SecKey? {
var attributes: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrLabel as String: "label-for-reference",
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnRef as String: true,
]
if let context = context {
attributes[kSecUseAuthenticationContext as String] = context
}
var item: CFTypeRef?
let res = SecItemCopyMatching(attributes as CFDictionary, &item)
if (res == errSecSuccess) {
return (item as! SecKey)
} else {
return nil
}
}
func sign(data: String, key: SecKey) throws -> Data? {
if (SecKeyIsAlgorithmSupported(key, .sign, .ecdsaSignatureMessageX962SHA256)) {
var error: Unmanaged<CFError>?
guard let signature = SecKeyCreateSignature(key,
.ecdsaSignatureMessageX962SHA256,
data.data(using: .utf8)! as CFData,
&error) as Data? else {
throw error!.takeRetainedValue() as Error
}
return signature
}
return nil
}
I can then generate a signature by doing something like this (leaving out some details):
let privateKey = generatePrivateKey()
let context = LAContext()
context.setCredential("pwd123".data(using: .utf8), type: .applicationPassword)
let retrievedKey = retrieveKey(context)
var signedMessage: Data?
try signedMessage = sign(data: "abc", key: retrievedKey!))
What I'd like to learn
I'd like to learn if I'm missing something in this documentation (specifically on the access control of [secure enclave] keychain items), or alternatively, if there is a particular workaround to make this work. For example, I've also thought about chaining the access options, e.g. Face ID --unlocks--> Application passcode --unlocks--> Private key so that the application passcode can either be provided by using Face ID first or directly by the user. However, this would load the application passcode in memory (which may not be an issue when the user already enters their pin code in a custom user interface anyway). How do apps with similar functionality generally solve this?

Swift Keychain - Storing OAuth Credentials: "The specified item already exists in the keychain"

I'm building an iOS app for my website and I'm attempting to use OAuth2 to manage login credentials. On user login, I'm successfully hitting my authentication endpoint with the provided username and password and I'm attempting to store both the Access Token and the Refresh Token in the keychain, so the user doesn't have to provide credentials moving forward.
I'm having trouble storing both refresh token and access token in my keychain, following instructions from these sources:
Adding a Password to the Keychain
Searching for Keychain Items
Updating and Deleting Keychain Items
I'm able to successfully store either the Access Token or the Refresh Token, but no matter which one I store first, when attempting to store the other, I receive the following error message: "The specified item already exists in the keychain."
I added a CheckForExisting function to delete any existing items with the same specifications, but when I attempt to delete the existing keychain item using the same query, I receive a errSecItemNotFound status. So, frustratingly enough, I'm being told that I can't create my item because it already exists, but I can't delete the existing item because no existing item exists.
My hypothesis is that the creation of the Access Token item blocks the creation of the Refresh Token item, so I'm hoping someone can shed some light on the following:
Why is the second item creation being blocked? Does the Keychain have some built in primary key checks that I'm hitting (like can't store more than one kSecClassInternetPassword)?
What's the proper way to differentiate between the two tokens. Right now I'm using kSecAttrLabel, but that's a shot in the dark.
Please note that I'm hoping for an explanation of why my current approach is failing. I absolutely welcome alternative implementations, but I really want to understand what is going on behind the scenes here, so if possible please include an explanation of where an alternative implementation avoids the pitfalls that I seem to have fallen prey to.
Swift4 Code to Store the Tokens:
func StoreTokens(username: String, access_token: String, refresh_token: String) throws {
func CheckForExisting(query: [String: Any]) throws {
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else {
let error_message = SecCopyErrorMessageString(status, nil)!
throw KeychainError.unhandledError(status: error_message)
}
}
let configuration = ConfigurationDetails()
let server = configuration.server
let access_token = access_token.data(using: String.Encoding.utf8)!
let refresh_token = refresh_token.data(using: String.Encoding.utf8)!
let access_token_query: [String: Any] = [
kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: username,
kSecAttrServer as String: server,
kSecAttrLabel as String: "AccessToken",
kSecValueData as String: access_token
]
let refresh_token_query: [String: Any] = [
kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: username,
kSecAttrServer as String: server,
kSecAttrLabel as String: "RefreshToken",
kSecValueData as String: refresh_token
]
try CheckForExisting(query: access_token_query)
let access_status = SecItemAdd(access_token_query as CFDictionary, nil)
guard access_status == errSecSuccess else {
let error_message = SecCopyErrorMessageString(access_status, nil)!
throw KeychainError.unhandledError(status: error_message)
}
try CheckForExisting(query: refresh_token_query)
let refresh_status = SecItemAdd(refresh_token_query as CFDictionary, nil)
guard refresh_status == errSecSuccess else {
let error_message = SecCopyErrorMessageString(refresh_status, nil)!
throw KeychainError.unhandledError(status: error_message)
}
}
According this https://developer.apple.com/documentation/security/errsecduplicateitem looks like the unique key for class kSecClassInternetPassword contains only these properties:
kSecAttrAccount, kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol, kSecAttrAuthenticationType, kSecAttrPort, and kSecAttrPath.
So, kSecAttrLabel is not in the list, and your refresh_token_query duplicates access_token_query.

Client certificates and identities in iOS

I have generated private key and public key to my Swift-based iOS application using SecKeyGeneratePair function.Then, I generated Certificate Signing Request using iOS CSR generationand my server replied with certificate chain in PEM format.I converted PEM-certificate to DER-format using following code:
var modifiedCert = certJson.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
modifiedCert = modifiedCert.replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
modifiedCert = modifiedCert.replacingOccurrences(of: "\n", with: "")
let dataDecoded = NSData(base64Encoded: modifiedCert, options: [])
Now, I should create certificate from DER-data using let certificate = SecCertificateCreateWithData(nil, certDer)
My question is following : How can I connect the certificate with private key I have created in the beginning and get the identity where both of these(keys and certificate) belongs?Maybe, add certificate to keychain and get the identity using SecItemCopyMatching? I have followed the procedure presented in question SecIdentityRef procedure
Edit:
When adding the certificate to keychain, I get the status response 0, which I believe means that certificate has been added to keychain.
let certificate: SecCertificate? = SecCertificateCreateWithData(nil, certDer)
if certificate != nil{
let params : [String: Any] = [
kSecClass as String : kSecClassCertificate,
kSecValueRef as String : certificate!
]
let status = SecItemAdd(params as CFDictionary, &certRef)
print(status)
}
Now when I'm trying to get the identity, I get status -25300 (errSecItemNotFound). Following code is used to get the identity. tag is the private key tag I have used to generate private/public key.
let query: [String: Any] = [
kSecClass as String : kSecClassIdentity,
kSecAttrApplicationTag as String : tag,
kSecReturnRef as String: true
]
var retrievedData: SecIdentity?
var extractedData: AnyObject?
let status = SecItemCopyMatching(query as NSDictionary, &extractedData)
if (status == errSecSuccess) {
retrievedData = extractedData as! SecIdentity?
}
I'm able to get the private key & public key & certificate from the keychain using SecItemCopyMatching and add the certificate to keychain, but querying the SecIdentity does not work. Is it possible that my certificate does not match to my keys? How is that checked?
I printed public key from iOS in base64 format. The following was printed:
MIIBCgKCAQEAo/MRST9oZpO3nTl243o+ocJfFCyKLtPgO/QiO9apb2sWq4kqexHy
58jIehBcz4uGJLyKYi6JHx/NgxdSRKE3PcjU2sopdMN35LeO6jZ34auH37gX41Sl
4HWkpMOB9v/OZvMoKrQJ9b6/qmBVZXYsrSJONbr+74/mI/m1VNtLOM2FIzewVYcL
HHsM38XOg/kjSUsHEUKET/FfJkozgp76r0r3E0khcbxwU70qc77YPgeJHglHcZKF
ZHFbvNz4E9qUy1mWJvoCmAEItWnyvuw+N9svD1Rri3t5qlaBwaIN/AtayHwJWoWA
/HF+Jg87eVvEErqeT1wARzJL2xv5V1O4ZwIDAQAB
Then from the certificate signing request I extracted the public key using openssl (openssl req -in ios.csr -pubkey -noout). The following response was printed:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo/MRST9oZpO3nTl243o+
ocJfFCyKLtPgO/QiO9apb2sWq4kqexHy58jIehBcz4uGJLyKYi6JHx/NgxdSRKE3
PcjU2sopdMN35LeO6jZ34auH37gX41Sl4HWkpMOB9v/OZvMoKrQJ9b6/qmBVZXYs
rSJONbr+74/mI/m1VNtLOM2FIzewVYcLHHsM38XOg/kjSUsHEUKET/FfJkozgp76
r0r3E0khcbxwU70qc77YPgeJHglHcZKFZHFbvNz4E9qUy1mWJvoCmAEItWnyvuw+
N9svD1Rri3t5qlaBwaIN/AtayHwJWoWA/HF+Jg87eVvEErqeT1wARzJL2xv5V1O4
ZwIDAQAB
-----END PUBLIC KEY----
It seems that there is a minor difference in the beginning of the key generated from CSR. (MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A). Based on the question RSA encryption, it seems that MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A is base64-formatted identifier for RSA encryption "1.2.840.113549.1.1.1". So I guess the public key might be fine?
We don't use that same method of CSR, but we have an equivalent thing where we do the following:
Generate key pair
Ship the public key to the remote server
Remote server generates a signed client certificate using the public key
Ship the client certificate back to the iOS device
Add the client certificate to the keychain
Later on, use the client certificate in an NSURLSession or similar.
As you seem to have discovered, iOS needs this extra thing called an "identity" to tie the client cert.
We also discovered that iOS has a weird thing where you need to DELETE the public key from the keychain before you add the client cert and identity into it, otherwise the identity doesn't seem to locate the client certificate properly instead. We chose to add the public key back in but as a "generic password" (i.e arbitrary user data) - we only do this because iOS doesn't have a sensible API for extracting a public key from a cert on the fly, and we need the public key for other strange things we happen to be doing.
If you're just doing TLS client certificate auth, once you have the certificate you won't need an explicit copy of the public key so you can simplify the process by simply deleting it, and skip the "add-back-in-as-generic-password" bit
Please excuse the giant pile of code, crypto stuff always seems to require a lot of work.
Here's bits of code to perform the above tasks:
Generating the keypair, and deleting/re-saving the public key
/// Returns the public key binary data in ASN1 format (DER encoded without the key usage header)
static func generateKeyPairWithPublicKeyAsGenericPassword(privateKeyTag: String, publicKeyAccount: String, publicKeyService: String) throws -> Data {
let tempPublicKeyTag = "TMPPUBLICKEY:\(privateKeyTag)" // we delete this public key and replace it with a generic password, but it needs a tag during the transition
let privateKeyAttr: [NSString: Any] = [
kSecAttrApplicationTag: privateKeyTag.data(using: .utf8)!,
kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly,
kSecAttrIsPermanent: true ]
let publicKeyAttr: [NSString: Any] = [
kSecAttrApplicationTag: tempPublicKeyTag.data(using: .utf8)!,
kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly,
kSecAttrIsPermanent: true ]
let keyPairAttr: [NSString: Any] = [
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits: 2048,
kSecPrivateKeyAttrs: privateKeyAttr,
kSecPublicKeyAttrs: publicKeyAttr ]
var publicKey: SecKey?, privateKey: SecKey?
let genKeyPairStatus = SecKeyGeneratePair(keyPairAttr as CFDictionary, &publicKey, &privateKey)
guard genKeyPairStatus == errSecSuccess else {
log.error("Generation of key pair failed. Error = \(genKeyPairStatus)")
throw KeychainError.generateKeyPairFailed(genKeyPairStatus)
}
// Would need CFRelease(publicKey and privateKey) here but swift does it for us
// we store the public key in the keychain as a "generic password" so that it doesn't interfere with retrieving certificates
// The keychain will normally only store the private key and the certificate
// As we want to keep a reference to the public key itself without having to ASN.1 parse it out of the certificate
// we can stick it in the keychain as a "generic password" for convenience
let findPubKeyArgs: [NSString: Any] = [
kSecClass: kSecClassKey,
kSecValueRef: publicKey!,
kSecAttrKeyType: kSecAttrKeyTypeRSA,
kSecReturnData: true ]
var resultRef:AnyObject?
let status = SecItemCopyMatching(findPubKeyArgs as CFDictionary, &resultRef)
guard status == errSecSuccess, let publicKeyData = resultRef as? Data else {
log.error("Public Key not found: \(status))")
throw KeychainError.publicKeyNotFound(status)
}
// now we have the public key data, add it in as a generic password
let attrs: [NSString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly,
kSecAttrService: publicKeyService,
kSecAttrAccount: publicKeyAccount,
kSecValueData: publicKeyData ]
var result: AnyObject?
let addStatus = SecItemAdd(attrs as CFDictionary, &result)
if addStatus != errSecSuccess {
log.error("Adding public key to keychain failed. Error = \(addStatus)")
throw KeychainError.cannotAddPublicKeyToKeychain(addStatus)
}
// delete the "public key" representation of the public key from the keychain or it interferes with looking up the certificate
let pkattrs: [NSString: Any] = [
kSecClass: kSecClassKey,
kSecValueRef: publicKey! ]
let deleteStatus = SecItemDelete(pkattrs as CFDictionary)
if deleteStatus != errSecSuccess {
log.error("Deletion of public key from keychain failed. Error = \(deleteStatus)")
throw KeychainError.cannotDeletePublicKeyFromKeychain(addStatus)
}
// no need to CFRelease, swift does this.
return publicKeyData
}
NOTE that publicKeyData isn't strictly in DER format, it's in "DER with the first 24 bytes trimmed off" format. I'm not sure what this is called officially, but both microsoft and apple seem to use it as the raw format for public keys. If your server is a microsoft one running .NET (desktop or core) then it will probably be happy with the public key bytes as-is. If it's Java and expects DER you may need to generate the DER header - this is a fixed sequence of 24 bytes you can probably just concatenate on.
Adding the client certificate to the keychain, generating an Identity
static func addIdentity(clientCertificate: Data, label: String) throws {
log.info("Adding client certificate to keychain with label \(label)")
guard let certificateRef = SecCertificateCreateWithData(kCFAllocatorDefault, clientCertificate as CFData) else {
log.error("Could not create certificate, data was not valid DER encoded X509 cert")
throw KeychainError.invalidX509Data
}
// Add the client certificate to the keychain to create the identity
let addArgs: [NSString: Any] = [
kSecClass: kSecClassCertificate,
kSecAttrAccessible: kSecAttrAccessibleAlwaysThisDeviceOnly,
kSecAttrLabel: label,
kSecValueRef: certificateRef,
kSecReturnAttributes: true ]
var resultRef: AnyObject?
let addStatus = SecItemAdd(addArgs as CFDictionary, &resultRef)
guard addStatus == errSecSuccess, let certAttrs = resultRef as? [NSString: Any] else {
log.error("Failed to add certificate to keychain, error: \(addStatus)")
throw KeychainError.cannotAddCertificateToKeychain(addStatus)
}
// Retrieve the client certificate issuer and serial number which will be used to retrieve the identity
let issuer = certAttrs[kSecAttrIssuer] as! Data
let serialNumber = certAttrs[kSecAttrSerialNumber] as! Data
// Retrieve a persistent reference to the identity consisting of the client certificate and the pre-existing private key
let copyArgs: [NSString: Any] = [
kSecClass: kSecClassIdentity,
kSecAttrIssuer: issuer,
kSecAttrSerialNumber: serialNumber,
kSecReturnPersistentRef: true] // we need returnPersistentRef here or the keychain makes a temporary identity that doesn't stick around, even though we don't use the persistentRef
let copyStatus = SecItemCopyMatching(copyArgs as CFDictionary, &resultRef);
guard copyStatus == errSecSuccess, let _ = resultRef as? Data else {
log.error("Identity not found, error: \(copyStatus) - returned attributes were \(certAttrs)")
throw KeychainError.cannotCreateIdentityPersistentRef(addStatus)
}
// no CFRelease(identityRef) due to swift
}
In our code we chose to return a label, and then look up the identity as-required using the label, and the following code. You could also chose to just return the identity ref from the above function rather than the label. Here's our getIdentity function anyway
Getting the identity later on
// Remember any OBJECTIVE-C code that calls this method needs to call CFRetain
static func getIdentity(label: String) -> SecIdentity? {
let copyArgs: [NSString: Any] = [
kSecClass: kSecClassIdentity,
kSecAttrLabel: label,
kSecReturnRef: true ]
var resultRef: AnyObject?
let copyStatus = SecItemCopyMatching(copyArgs as CFDictionary, &resultRef)
guard copyStatus == errSecSuccess else {
log.error("Identity not found, error: \(copyStatus)")
return nil
}
// back when this function was all ObjC we would __bridge_transfer into ARC, but swift can't do that
// It wants to manage CF types on it's own which is fine, except they release when we return them out
// back into ObjC code.
return (resultRef as! SecIdentity)
}
// Remember any OBJECTIVE-C code that calls this method needs to call CFRetain
static func getCertificate(label: String) -> SecCertificate? {
let copyArgs: [NSString: Any] = [
kSecClass: kSecClassCertificate,
kSecAttrLabel: label,
kSecReturnRef: true]
var resultRef: AnyObject?
let copyStatus = SecItemCopyMatching(copyArgs as CFDictionary, &resultRef)
guard copyStatus == errSecSuccess else {
log.error("Identity not found, error: \(copyStatus)")
return nil
}
// back when this function was all ObjC we would __bridge_transfer into ARC, but swift can't do that
// It wants to manage CF types on it's own which is fine, except they release when we return them out
// back into ObjC code.
return (resultRef as! SecCertificate)
}
And finally
Using the identity to authenticate against a server
This bit is in objc because that's how our app happens to work, but you get the idea:
SecIdentityRef _clientIdentity = [XYZ getClientIdentityWithLabel: certLabel];
if(_clientIdentity) {
CFRetain(_clientIdentity);
}
SecCertificateRef _clientCertificate = [XYZ getClientCertificateWithLabel:certLabel];
if(_clientCertificate) {
CFRetain(_clientCertificate);
}
...
- (void)URLSession:(nullable NSURLSession *)session
task:(nullable NSURLSessionTask *)task
didReceiveChallenge:(nullable NSURLAuthenticationChallenge *)challenge
completionHandler:(nullable void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate) {
// supply the appropriate client certificate
id bridgedCert = (__bridge id)_clientCertificate;
NSArray* certificates = bridgedCert ? #[bridgedCert] : #[];
NSURLCredential* credential = [NSURLCredential credentialWithIdentity:identity certificates:certificates persistence:NSURLCredentialPersistenceForSession];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
}
This code took a lot of time to get right. iOS certificate stuff is exceedingly poorly documented, hopefully this helps.
The usual way to generate SSL certificates is that private key is used to generate the CSR, Certificate Signing Request info. In fact, you're hidding as well company, email, etc info with that key signature. With that CSR, then, you sign your certificate, so it will be associated with your private key and info stored in CSR, nevermind the public key. I'm currently not able to see in IOS CSR Generation project where you can pass your generated key: seems to me that CSR generated with IOS CSR Generation project is using it's own generated key, or no private key at all. That will got then logic with the fact that you cannot extract private key from CER or DER, because it isn't there.

certificatesInBundle doesn't append self signed certificates

Using the convenience method ServerTrustPolicy.certificatesInBundle() doesn't appear to work correctly in my case
// MARK: - Bundle Location
/**
Returns all certificates within the given bundle with a `.cer` file extension.
- parameter bundle: The bundle to search for all `.cer` files.
- returns: All certificates within the given bundle.
*/
public static func certificatesInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecCertificate] {
var certificates: [SecCertificate] = []
let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
bundle.pathsForResourcesOfType(fileExtension, inDirectory: nil)
}.flatten())
for path in paths {
if let
certificateData = NSData(contentsOfFile: path), // <-- we get the data of the certificate in bundle
certificate = SecCertificateCreateWithData(nil, certificateData) // <-- The problem is here, the certificate is not set neither errors.
{
certificates.append(certificate) // <-- this doesn't run
}
}
return certificates
}
Probably has to do something with the format of the self-signed certificate. I used exactly the #tip 5 from this blog post. Five Tips for Using Self Signed SSL Certificates with iOS
The question is what is the limitations of the SecCertificateCreateWithData method and which certificate formats are acceptable? Even better where can I read more about this particular issue.
my code appears to be correct it's nothing special, probably one of the most used snippets :P
let defaultManager:Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"localhost": .PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: true,
validateHost: true
)
]
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
The most likely reason that SecCertificateCreateWithData would return nil, is that the file is in PEM not DER format.
As per the documentation, data should contain
A DER (Distinguished Encoding Rules) representation of an X.509
certificate
If your data begins with "-----BEGIN...", then it is the wrong format. PEM can be converted to DER (and vice versa) with OpenSSL - here is a handy reference https://www.sslshopper.com/article-most-common-openssl-commands.html.
Also, in case of a self-signed certificate (judging by "localhost"), the validateCertificateChain property should be false. Otherwise the request will fail with a "cancelled" NSError.
Additionally, starting from iOS9, App Transport Security settings should be set to allow arbitrary loads (in Info.plist). That is the only setting that will permit self-signed certificates to be evaluated by your app. Without it, the Alamofire trust policy mechanism will not get a chance to kick in.
I had a similar problem. Alamofire couldn't find my certificate, the ServerTrustPolicy.certificatesInBundle() method did not return anything.
The problem was that when dragging my certificate into my Xcode project I didn't select "Add to targets: MyProjectName".
Make sure that you downloaded the certificate in der format and added correctly to your project.
after that define a static SessionManager as mentioned below
public static let sharedManager: SessionManager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"subdomain.domain.com": .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: false,
validateHost: true
),
"insecure.expired-apis.com": .disableEvaluation
]
let manager = Alamofire.SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
return manager
}()
then you can call the above sharedManager:
YourHttpClassName.sharedManager.request(url, method: .get, headers: headers).response { response in
print("Request: \(response.request)")
print("Response: \(response.response)")
print("Error: \(response.error)")
debugPrint(response)
}
it should work fine with your self-signed certificate.

Access to shared keychain in iOS 8 share extension returns nil

The problem:
I have an iOS 8 application with a framework for keychain access and a share extension.
Both, the host application and the share extension link the keychain access framework and they both share a keychain.
Everything works in Debug configuration. But when I use the Release configuration the value dataTypeRef is always nil when I try to receive a password from the keychain.
I have already tried to add an App Group but without luck.
Has anyone an idea why it doesn't work for Release builds? Thanks
The code for the keychain access:
In the keychain access framework I add a password to the keychain like this:
public class func setPassword(password: String, account: String, service: String = "kDDHDefaultService") {
var secret: NSData = password.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
let objects: Array = [secClassGenericPassword(), service, account, secret]
let keys: Array = [secClass(), secAttrService(), secAttrAccount(), secValueData()]
let query = NSDictionary(objects: objects, forKeys: keys)
SecItemDelete(query as CFDictionaryRef)
let status = SecItemAdd(query as CFDictionaryRef, nil)
}
To receive the password I do this:
public class func passwordForAccount(account: String, service: String = "kDDHDefaultService") -> String? {
let keys: [AnyObject] = [secClass(), secAttrService(), secAttrAccount(), secReturnData()]
let objects: [AnyObject] = [secClassGenericPassword(), service, account, true]
println("keys \(keys), objects \(objects)")
let queryAttributes = NSDictionary(objects: objects, forKeys: keys)
var dataTypeRef : Unmanaged<AnyObject>?
let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);
if status == errSecItemNotFound {
println("not Found")
return nil
}
if dataTypeRef == nil { // dataTypeRef is always nil in Release builds
println("dataTypeRef == nil")
return nil
}
let retrievedDataRef : CFDataRef = dataTypeRef!.takeRetainedValue() as CFDataRef
let retrievedData : NSData = retrievedDataRef
let password = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
return password as? String
}
Note: This has nothing to do with the share extension it even doesn't work for the host application.
Update: For a test I have added the KeychainAccess class to the target of the host application. But I still can't access the keychain in Release builds.
I think I found a temporary fix. As suggested in a comment to this answer I set the optimization of the Swift compiler to None[-Onone] in the keychain access target and it seems to work now.

Resources