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
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
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
}
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
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
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.