How to validate X509Certificate in Swift? - ios

Does anyone know how to validate x509certificate in swift? Or maybe anyone have link with examples of it?
I got certificate from keychain, now I need to validate it.
let keychainQuery: [String: Any] = [kSecClass as String: kSecClassCertificate,
kSecAttrLabel as String: "cert",
kSecReturnRef as String: kCFBooleanTrue]
var result: CFTypeRef?
let status = SecItemCopyMatching(keychainQuery as CFDictionary, &result)
guard status == errSecSuccess else { return }
entry = result as! SecCertificate

I found solution for resolving those problem, but still doesn't check it.
We need to create SecTrust class from our certificate.
let policy: SecPolicy = SecPolicyCreateBasicX509()
let trust: SecTrust?
let status = SecTrustCreateWithCertificates(entry as CFTypeRef, policy, &trust)
Then we can check this for example on date
let trustCertStatus = SecTrustSetVerifyDate(trust!, CFDateCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent()))

Related

Certificate public key missing some bytes when parsed using iOS vs Android

I have a certificate "cert.p12" that I use to sign some data to send it to s SAS server
I read the certificate this way
let data = try Data.init(contentsOf: _certURL)
let password = "somepassword"
let options = [ kSecImportExportPassphrase as String: password ]
var rawItems: CFArray?
let status = SecPKCS12Import(data as CFData,
options as CFDictionary,
&rawItems)
guard status == errSecSuccess else {
print("Error[36]")
return
}
let items = rawItems! as! Array<Dictionary<String, Any>>
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity?
let trust = firstItem[kSecImportItemTrust as String] as! SecTrust
let publicKey = SecTrustCopyKey(trust)
let publicKeyData = SecKeyCopyExternalRepresentation(publicKey!, nil)
let publicKeyString = (publicKeyData! as Data).base64EncodedString()
I get the public key like this
"MInsdfknsiufiwefwef ...... etc"
While in android I get
"[Ghhsdfwe89fwenfnwekfjwef]MInsdfknsiufiwefwef ......etc"
So the key is same except for the first like 40 bytes or so
See the image below where I use text comparison tool
The key at the bottom is the correct one accepted by the SAS server
What could be going wrong or missing in iOS

How to extract certificate and private key PEM strings from PKCS#12 file using swift

I have a use case where the user gives a PKCS#12 file and its password as input, and I need both the certificate's and the private key's PEM strings.
I have managed to create a SecIdentity using the p12, and its documentation says:
A SecIdentity object contains a SecKey object and an associated SecCertificate object.
So, I believe I am in the right path, but I couldn't find a way of extracting the SecKey and the SecCertificate from this SecIdentity.
I have also not found a way to get a PEM string from a SecKey or SecCertificate, but that would only be the last step.
This is the code I've used to create the identity:
let key: NSString = kSecImportExportPassphrase as NSString
let options: NSDictionary = [key : p12Password]
var rawItems: CFArray?
let p12Data = try! Data(contentsOf: p12FileUrl)
let data = p12Data! as NSData
let cfdata = CFDataCreate(kCFAllocatorDefault, data.bytes.assumingMemoryBound(to: UInt8.self), data.length)!
let result: OSStatus = SecPKCS12Import(cfdata, options, &rawItems)
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
}
-- UPDATE --
I made some more progress, and managed to extract the certificate in a DER format (which I haven't tried to convert to PEM so far - I believe it should be easy), but I still have no idea how to get the private key.
if result == errSecSuccess {
let items = rawItems! as! [[String: Any]]
let firstItem = items[0]
let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity
var cert: SecCertificate?
SecIdentityCopyCertificate(identity, &cert)
var certDer = SecCertificateCopyData(cert!) //DER format
var key: SecKey?
SecIdentityCopyPrivateKey(identity, &key)
let keyDict = SecKeyCopyAttributes(key!) //Not sure what we can find here
}

IOS not able to create privatekey if only devicecode is set on device?

Im trying to generate a privatekey that only is accessible when either devicecode or current set of biometrics( that is already registered on device) is used.
It works when i have a finger registered on device, then its all good. But if i delete my registered "touchid-finger", and try to generate a new key, then it returns nil
Errorcode is -25293
Code example:
func generateKey() -> SecKey?{
var error: Unmanaged<CFError>?
let accessControl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
[.devicePasscode,.or,.biometryCurrentSet],
nil)
let attributes:[String : Any] = [kSecAttrType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String:4096,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent:true,
kSecAttrCanSign: true,
kSecAttrApplicationTag: "yes.its.my.tag",
kSecAttrAccessControl:accessControl!]]
let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)
if(error != nil || privateKey == nil) {
fatalError("explode Kittens")
}
return privateKey
}
fyi.Its actually works on simulators but not on real devices.
Am i doing something wrong? Is this working as intended? is it a bug? (sooo many questions :D )
I'm generating a private key, you need to add in ACL object parameter kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly. This will allow you to get a private key using a passcode or biometric data.
guard let aclObject = SecAccessControlCreateWithFlags(
kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.userPresence, .privateKeyUsage], nil) else {
return ""
}
// private key parameters
let privateKeyParams: [String: AnyObject] = [
kSecAttrAccessControl as String: aclObject as AnyObject, //protect with touch id
kSecAttrIsPermanent as String: true as AnyObject
]
// global parameters for our key generation
let parameters: [String: AnyObject] = [
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecAttrKeyType as String: kSecMessECCKeyType,
kSecAttrKeySizeInBits as String: kSecMessECCKeySize as AnyObject,
kSecAttrLabel as String: kSecMessECCSignLabel as AnyObject,
kSecPrivateKeyAttrs as String: privateKeyParams as AnyObject
]
guard let eCCPrivKey = SecKeyCreateRandomKey(parameters as CFDictionary, nil) else {
print("ECC KeyGen Error!")
return ""
}
guard let eCCPubKey = SecKeyCopyPublicKey(eCCPrivKey) else {
print("ECC Pub KeyGen Error")
return ""
}
"The problem relates to how the item is /created/, and that’s not what these flags control.
Indeed, for .biometryCurrentSet to have any meaning at the time of use, there must actually be a current set of biometrics at the time of creation, and I think that’s the source of the errSecAuthFailed.
My recommendation is manually fall back to using just .devicePasscode if there’s no biometrics available. Two ways: A. Catch the error and retry B. Preflight the request using the LocalAuthentication framework."
Apple says no can do :D

SecKeyCopyKeyExchangeResult does not work as nodejs ecdh in iOS 13

I have an application where I generate shared secret between iOS / android / nodejs using ECDH over secp521r1 curve. To ensure that it works I wrote a test that uses data generated with nodejs and that worked until iOS 13.
The source code of the test was:
let publicKey = "BABBvZ56c4bj1Zo73LIt/bBVa3jvGTA1fceoOG/M9TeXHx5ffCggRteEVS+bwrgQWPOwJPHhevNenaVn32ZnhztS0QFBqKGZTF1pKNSvuj+PDKQ625TauNroq+LQdeS+Pn6GVHL0iW5pp84NZ06L97VZ9HYm+g2lMnlUFV8hco2CmwBqHQ=="
let privateKey = "AXn994UN59QCEqmCmXmmNZ3hVZPlMwzTIeBupJGG4CqDWfWLuCTui7qiBfQtCFcQ1ks4NNB/tHEZUJ+bB97+pkJ3"
let otherBase64 = "BAAzWyzdh2e+ZNUCFt4oDADURb8+m9WA7gbWtTo57ZP3U23VuvMnRHf+12GpTSV8A5pt+vZfaR2cT02P+LPRc/kGzgAT2IYIgDz/cKbzMi520ZLa0GYk1xzCuNqFhdBZmrB5w0ymsPLdJzIG1QZ3xu7OufEipm5D41abphLLnbH+OyTX6w=="
let expectedShared = "AQkTOOHPcvlXufR2dm1FHaIJRlTgmxTJMI+h0kJ+nMVNopIP+opSqUNmflsgnJzT8JTodd/eehaaq5vvYdDVciIQ"
// iOS secKey is reconstructed by concatenating public and private key
let otherDataKey = Data.init(base64Encoded: otherBase64)!
var concatenatedKey = Data.init(base64Encoded: publicKey)!
concatenatedKey.append(Data.init(base64Encoded: privateKey)!)
// generate private key
var attributes: [String:Any] =
[
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrKeySizeInBits as String: 521,
kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
]
var error: Unmanaged<CFError>?
guard let secKey = SecKeyCreateWithData(concatenatedKey as CFData, attributes as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate other public key
attributes[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic
guard let otherKey = SecKeyCreateWithData(otherDataKey as CFData, attributes as CFDictionary, nil) else {
XCTAssertTrue(false)
return
}
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeStandardX963SHA256, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
// generate shared secret
XCTAssertEqual((shared as Data).base64EncodedString(), expectedShared);
With iOS 13 I was forced to modify the content of my exchangeOptions dictionary as discussed here (SecKeyCopyKeyExchangeResult() function return an error, "kSecKeyKeyExchangeParameterRequestedSize is missing")
let exchangeOptions: [String: Any] = [SecKeyKeyExchangeParameter.requestedSize.rawValue as String: 66]
The problem is that with this option, the result of SecKeyCopyKeyExchangeResult does not match anymorewith nodejs one (which is also true on iOS 12)
I finally found a solution... In iOS <= 12, leaving exchange parameters empty when trying to use ecdhKeyExchangeStandardX963SHA256 algorithm was falling back to using SecKeyAlgorithm.ecdhKeyExchangeCofactor.
Therefore the fix to reproduce previous behavior is to modify the SecKeyCopyKeyExchangeResult with
// generate shared secret
let exchangeOptions: [String: Any] = [:]
guard let shared = SecKeyCopyKeyExchangeResult(secKey, SecKeyAlgorithm.ecdhKeyExchangeCofactor, otherKey, exchangeOptions as CFDictionary, &error) else {
XCTAssertTrue(false)
return
}
This works at least for iOS 10 to 13

iOS Keychain won't add queries with hardcoded strings

I am following the Apple documentation for adding a password to the keychain located here -> https://developer.apple.com/documentation/security/keychain_services/keychain_items/adding_a_password_to_the_keychain
When I run the following code it works as expected and the status comes back as 0.
let credentials = Credentials(username: "testUserName", password: "testPassword")
let server = "www.example.com"
let account = credentials.username
let password = credentials.password.data(using: String.Encoding.utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: server,
kSecValueData as String: password]
let status = SecItemAdd(query as CFDictionary, nil)
print(status)
When I modify the code with hardcoded strings it fails with a status of -50.
let credentials = Credentials(username: "testUserName", password: "testPassword")
let server = "www.example.com"
//let account = credentials.username
//let password = credentials.password.data(using: String.Encoding.utf8)!
let account = "testUserName"
let password = "testPassword"
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: server,
kSecValueData as String: password]
let status = SecItemAdd(query as CFDictionary, nil)
print("Keychain Save Status: \(status)")
Can someone explain this to me? I also tried explicitly setting the strings to utf8 format with let account = "testUsername".utf8. It doesn't make sense to me that this would fail if the value is a valid string.
Also does anyone have the link to the status code descriptions? I found the descriptions but it doesn't give the associated number code https://developer.apple.com/documentation/security/1542001-security_framework_result_codes
YES, I know my answer on the other track. As per my own experience, I'm using below keychain wrapper in Swift and Objective - C. Happy and Lazy Coding! :)
Swift - Locksmith - https://github.com/matthewpalmer/Locksmith
Objective-C - SSKeychain - https://github.com/samsoffes/sskeychain
//Generate Device UUID
func CreateApplicationDeviceUUID() -> String{
let DeviceUUID = NSUUID().uuidString
print("DeviceUUD==\(DeviceUUID)")
return DeviceUUID
}
//Retrive Device Unique UUID
let keyChainID = Locksmith.loadDataForUserAccount(userAccount: Bundle.main.object(forInfoDictionaryKey:"CFBundleName") as! String)
let retriveuuid = keyChainID?[RDGlobalFunction.deviceAppUUID] //RDGlobalFunction.deviceAppUUID is a Key of KeyChain Value Storage
if(retriveuuid == nil){
let uuid = CreateApplicationDeviceUUID()
do{
try Locksmith.saveData(data: [RDGlobalFunction.deviceAppUUID : uuid], forUserAccount: Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as! String) //Locksmith - SSkeyChain Thirdparty KeyChain Wrapper
}catch{
//Catch Error
}
}
Other Reference Link:
https://www.raywenderlich.com/179924/secure-ios-user-data-keychain-biometrics-face-id-touch-id
https://medium.com/ios-os-x-development/securing-user-data-with-keychain-for-ios-e720e0f9a8e2
https://code.tutsplus.com/tutorials/securing-ios-data-at-rest--cms-28528
I figured out that if I change
let password = "testPassword"
to
let password = "testPassword".data(using: String.Encoding.utf8)!
then it will work as expected. It seems that the kSecValueData parameter must be a utf8 encoded string.

Resources