Querying iOS Keychain using Swift - ios

I'm stuck converting the Keychain query result using Swift.
My request seems to be working:
let queryAttributes = NSDictionary(objects: [kSecClassGenericPassword, "MyService", "MyAccount", true],
forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData])
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
var dataTypeRef : Unmanaged<AnyObject>?
let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);
let retrievedData : NSData = dataTypeRef!.takeRetainedValue() as NSData
*** ^^^^can't compile this line^^^^
})
My problem is, code won't compile:
Bitcast requires both operands to be pointer or neither
%114 = bitcast %objc_object* %113 to %PSs9AnyObject_, !dbg !487
Bitcast requires both operands to be pointer or neither
%115 = bitcast %PSs9AnyObject_ %114 to i8*, !dbg !487
LLVM ERROR: Broken function found, compilation aborted!
I don't know how to convert Unmanaged<AnyObject> to NSData.
Any ideas?

Use withUnsafeMutablePointer function and UnsafeMutablePointer struct to retrieving the data, such as the following:
var result: AnyObject?
var status = withUnsafeMutablePointer(&result) { SecItemCopyMatching(queryAttributes, UnsafeMutablePointer($0)) }
if status == errSecSuccess {
if let data = result as NSData? {
if let string = NSString(data: data, encoding: NSUTF8StringEncoding) {
// ...
}
}
}
it works fine with release (Fastest [-O]) build.

Looks like you have hit a compiler bug, which you should report. You can take a different path to retrieving the value, such as the following:
var dataTypeRef :Unmanaged<AnyObject>?
let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);
let opaque = dataTypeRef?.toOpaque()
if let op = opaque? {
let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
}
The error manifests itself when using AnyObject as a type parameter T of Unmanaged<T>. The following code snippet compiles without issue, which uses a more specific type, CFError:
let url = NSURL(string:"dummy")
var errorRef: Unmanaged<CFError>?
let succeeded = CTFontManagerRegisterFontsForURL(url, .Process, &errorRef)
if errorRef {
let error = errorRef!.takeRetainedValue()
}
As the Keychain API returns a different result depending of the query attributes, the use of AnyObject is required.

To get this to work you need to access the retained value for each keychain constant first. For example:
let kSecClassValue = kSecClass.takeRetainedValue() as NSString
let kSecAttrAccountValue = kSecAttrAccount.takeRetainedValue() as NSString
let kSecValueDataValue = kSecValueData.takeRetainedValue() as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword.takeRetainedValue() as NSString
let kSecAttrServiceValue = kSecAttrService.takeRetainedValue() as NSString
let kSecMatchLimitValue = kSecMatchLimit.takeRetainedValue() as NSString
let kSecReturnDataValue = kSecReturnData.takeRetainedValue() as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne.takeRetainedValue() as NSString
You'll then need to reference the constant you created in the keychain dictionary object like so.
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
I wrote a blog post about it at: http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/
Hope this helps!
rshelby

Related

Apple in app provisioning 'Could not add card'

I am implementing apple in-app provisioning and I follow all steps in the apple guide but in the end, I get a message 'Could not add card' but don't have any error throw this process.
This is how I create PKAddPaymentPassViewController
let cardInfoPass = PKAddPaymentPassRequestConfiguration.init(encryptionScheme: PKEncryptionScheme.ECC_V2);
cardInfoPass?.cardholderName = cardholderName as? String; //The name of the person as shown on the card.
cardInfoPass?.primaryAccountSuffix = primaryAccountSuffix as? String; //The last four or five digits of the card’s number.
cardInfoPass?.localizedDescription = localizedDescription as? String; //A short description of the card.
cardInfoPass?.paymentNetwork = PKPaymentNetwork.masterCard;
cardInfoPass?.primaryAccountIdentifier = primaryAccountIdentifier as? String; // A primary account identifier, used to filter out pass libraries.
cardholderName is the name written on the card
primaryAccountSuffix last 4 digit written on the card
localizedDescription bank name
paymentNetwork we are using master card
primaryAccountIdentifier it is number from iTunes something light this 1MNJDDA667.com.bank.package.name
I think this part is correct I can open the apple wallet modal and all this data are there but when I continue in a modal on the end I need to get certificate and send this certificate to our BE and be should send me back 3 values and they send it to me
...
let certificateLeaf = certificates[0].base64EncodedString();
let certificateSubCA = certificates[1].base64EncodedString();
let nonceString = nonce.base64EncodedString();
let nonceSignature = nonceSignature.base64EncodedString();
...
let reqDataDic: [String: Any] = [
"cardId": cardId,
"applePublicCertificate": certificateSubCA,
"nonce": nonceString,
"nonceSignature": nonceSignature,
"customerId": customerId,
"deviceId": deviceId,
]
....
var request = URLRequest(url: url)
request.httpMethod = "POST"
....
request.httpBody = try? JSONSerialization.data(withJSONObject: reqDataDic, options: .prettyPrinted)
UPDATE2: we are now sending nonce and nonceSignature as HEX like this
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return self.map { String(format: format, $0) }.joined()
}
}
...
let nonceData = Data(bytes: nonce)
let nonceHex = nonceData.hexEncodedString();
let nonceSignatureData = Data(bytes: nonceSignature)
let nonceSignatureHex = nonceSignatureData.hexEncodedString();
BE send me back all values that I need: activationData, ephemeralPublicKey, encryptedPassData it returns it as a JSON object so I need to convert it to Data and all these values put into handler
this is how I am putting data to handler:
if let dictionaryJson = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] {
let activationDataString = dictionaryJson["activationData"] as! String;
let ephemeralPublicKeyString = dictionaryJson["ephemeralPublicKey"] as! String;
let encryptedPassDataString = dictionaryJson["encryptedPassData"] as! String;
let activationData = activationDataString.data(using: .utf8)
let ephemeralPublicKey = Data(base64Encoded: ephemeralPublicKeyString)
let encryptedPassData = Data(base64Encoded: encryptedPassDataString)
let paymentPassRequest = PKAddPaymentPassRequest.init()
paymentPassRequest.activationData = activationData;
paymentPassRequest.encryptedPassData = encryptedPassData;
paymentPassRequest.ephemeralPublicKey = ephemeralPublicKey;
handler(paymentPassRequest)
}
I fill all data into paymentPassRequest and all looks ok xCode is not complaining.
And at this moment apple wallet shows an alert dialog with Could not add a card with 2 buttons try it later or try it again ....
I have a card whitelisted on the MasterCard side
I tried it on simulators, real devices, and also on app in TestFlight
UPDATE:
We found an error from the Apple
Response:
https://nc-pod4-smp-device.apple.com:443/broker/v4/devices/042D1xxxxxxxxxxxxx2C52/cards 500
{
Connection = close;
"Content-Length" = 81;
"Content-Type" = "application/json";
Date = "Thu, 08 Jul 2021 08:35:25 GMT";
Vary = "accept-language";
"X-Pod" = "nc-pod4";
"X-Pod-Region" = "paymentpass.com.apple";
"x-conversation-id" = b2axxxxxxxxxxx9e6a4d;
}
{
statusCode = 500;
statusMessage = "Broker Service Response exception";
}
you are encoding nonce, nonce signature with Hex format for sending it to your server, and after getting the response back, you are trying to convert them with base64 and utf8. Try with Hex, it should work.
We are using the below conversions
- (NSData *)dataFromHexString:(NSString *)string
{
string = [string lowercaseString];
NSMutableData *data= [NSMutableData new];
unsigned char whole_byte;
char byte_chars[3] = {'\0','\0','\0'};
int i = 0;
int length = string.length;
while (i < length-1) {
char c = [string characterAtIndex:i++];
if (c < '0' || (c > '9' && c < 'a') || c > 'f')
continue;
byte_chars[0] = c;
byte_chars[1] = [string characterAtIndex:i++];
whole_byte = strtol(byte_chars, NULL, 16);
[data appendBytes:&whole_byte length:1];
}
return data;
}
-(NSMutableString *) convertToString:(NSData *)data{
NSUInteger capacity = data.length * 2;
NSMutableString *sbuf = [NSMutableString stringWithCapacity:capacity];
const unsigned char *buf = data.bytes;
NSInteger i;
for (i=0; i<data.length; ++i) {
[sbuf appendFormat:#"%02x", (NSUInteger)buf[i]];
}
return sbuf;
}
let activationData = activationDataString.data(using: .utf8)
I think this encoded is only for VISA.
For MasterCard it has to be base64:
let activationData = Data(base64Encoded: activationDataString)
I ran into this exact issue implementing our Card Provisioning. In our case we had to do both of the following:
Make sure our PKAddPaymentPassRequest fields were all set properly. All three of the fields we were provided by our API (activationData, encryptedPassData, ephemeralPublicKey) were base64 encoded so they all had to be converted to Data's as such: paymentPassRequest.ephemeralPublicKey = Data(base64Encoded: <YOUR EPHEMERAL PUBLIC KEY STRING>, options: [])
We had to create new TestFlight builds in order to fully test this workflow. I ran into that exact same 500 response from Apple anytime I tried profiling or running the app from Xcode directly, even if it was on a physical device. It wasn't until I ran the TestFlight build that it finally worked properly.

Value of optional type [String] not wrapped

I'm currently a making IOS app using Stripe. When I tried to implement a Stripe card object( the image below), I got a compiler error on the line
var expMonth: NSNumber = Int(expArr[0])!
var expYear: NSNumber = Int(expArr[1])!
saying
"Type of optional type [String] not wrapped. not unwrapped; did you mean to use ! or ??"
func buttonPressed(_: UIButton) {
let creditCard = STPCardParams()
creditCard.number = cardNumberTextField.text
creditCard.cvc = cvvTextField.text
if (expDateTextField.text?.isEmpty == nil){
let expArr = expDateTextField.text?.components(separatedBy: "/")
if (expArr?.count)! > 1 {
var expMonth: NSNumber = Int(expArr[0])!
var expYear: NSNumber = Int(expArr[1])!
creditCard.expMonth = expMonth.uintValue
creditCard.expYear = expYear.uintValue
How can I fix this error? Your help would be appreciated!
Your expArr is an optional I suspect, try unwrapping it first:
var expMonth: NSNumber = NSNumber(value: Int(expArr![0])!)
var expYear: NSNumber = NSNumber(value: Int(expArr![1])!)
Also I'm not even sure you can directly assign an int to NSNumber as NSNumber is a pointer type but that might have changed in Swift 3.
Converting from int to NSNumber:
NSNumber(value: intValue)

Implementing AnyObject to NSString Issue

I'm trying to put AnyObject value to NSString and print it but unfortunatlly it is always crashing without showing the reason just EXC. After debugging I found it is crashing after currStr. So please where would be my issue?
var lastRefresh: AnyObject = self.currDic["Response"]!.objectForKey("Datasource")!
self.currStr = lastRefresh as NSString
println("Data: \(self.curreStr)")
My lastRefresh output:
{
Cookie = 0nss1w45xzuocyqgvrjcmr31;
Message = "\U0130\U015flem Ba\U015farl\U0131";
Response = {
Datasource = (
{
Content = "";
Date = "20.01.2015";
Time = "08:32:22";
Title = "Example1";
},
{
Content = "";
Date = "20.01.2015";
Time = "08:32:22";
Title = "Example2";
},
From your comments It's clear that, you need to convert the lastRefresh object to NSData for using it in NSXMLParser.
From your NSLog it looks like a Dictionary or Array. You can convert it to data using:
var yourData : NSData = NSJSONSerialization.dataWithJSONObject(lastRefresh, options: NSJSONWritingOptions.PrettyPrinted, error: nil)!

Adding item to keychain using Swift

I'm trying to add an item to the iOS keychain using Swift but can't figure out how to type cast properly. From WWDC 2013 session 709, given the following Objective-C code:
NSData *secret = [#"top secret" dataWithEncoding:NSUTF8StringEncoding];
NSDictionary *query = #{
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecAttrService: #"myservice",
(id)kSecAttrAccount: #"account name here",
(id)kSecValueData: secret,
};
OSStatus = SecItemAdd((CFDictionaryRef)query, NULL);
Attempting to do it in Swift as follows:
var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var query: NSDictionary = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: "MyService",
kSecAttrAccount: "Some account",
kSecValueData: secret
]
yields the error "Cannot convert the expression's type 'Dictionary' to 'DictionaryLiteralConvertible'.
Another approach I took was to use Swift and the - setObject:forKey: method on a Dictionary to add kSecClassGenericPassword with the key kSecClass.
In Objective-C:
NSMutableDictionary *searchDictionary = [NSMutableDictionary dictionary];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
In the Objective-C code, the CFTypeRef of the various keychain item class keys are bridged over using id. In the Swift documentation it's mentioned that Swift imports id as AnyObject. However when I attempted to downcast kSecClass as AnyObject for the method, I get the error that "Type 'AnyObject' does not conform to NSCopying.
Any help, whether it's a direct answer or some guidance about how to interact with Core Foundation types would be appreciated.
EDIT 2
This solution is no longer valid as of Xcode 6 Beta 2. If you are using Beta 1 the code below may work.
var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let query = NSDictionary(objects: [kSecClassGenericPassword, "MyService", "Some account", secret], forKeys: [kSecClass,kSecAttrService, kSecAttrAccount, kSecValueData])
OSStatus status = SecItemAdd(query as CFDictionaryRef, NULL)
To use Keychain Item Attribute keys as dictionary keys you have to unwrap them by using either takeRetainedValue or takeUnretainedValue (as appropriate). Then you can cast them to NSCopying. This is because they are CFTypeRefs in the header, which aren't all copyable.
As of Xcode 6 Beta 2 however, this causes Xcode to crash.
You simply need to downcast the literal:
let dict = ["hi": "Pasan"] as NSDictionary
Now dict is an NSDictionary. To make a mutable one, it's very similar to Objective-C:
let mDict = dict.mutableCopy() as NSMutableDictionary
mDict["hola"] = "Ben"
In the xcode 6.0.1 you must do this!!
let kSecClassValue = NSString(format: kSecClass)
let kSecAttrAccountValue = NSString(format: kSecAttrAccount)
let kSecValueDataValue = NSString(format: kSecValueData)
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword)
let kSecAttrServiceValue = NSString(format: kSecAttrService)
let kSecMatchLimitValue = NSString(format: kSecMatchLimit)
let kSecReturnDataValue = NSString(format: kSecReturnData)
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne)
Perhaps things have improved since. On Xcode 7 beta 4, no casting seems to be necessary except when dealing with the result AnyObject?. Specifically, the following seems to work:
var query : [NSString : AnyObject] = [
kSecClass : kSecClassGenericPassword,
kSecAttrService : "MyAwesomeService",
kSecReturnAttributes : true, // return dictionary in result parameter
kSecReturnData : true // include the password value
]
var result : AnyObject?
let err = SecItemCopyMatching(query, &result)
if (err == errSecSuccess) {
// on success cast the result to a dictionary and extract the
// username and password from the dictionary.
if let result = result as ? [NSString : AnyObject],
let username = result[kSecAttrAccount] as? String,
let passdata = result[kSecValueData] as? NSData,
let password = NSString(data:passdata, encoding:NSUTF8StringEncoding) as? String {
return (username, password)
}
} else if (status == errSecItemNotFound) {
return nil;
} else {
// probably a program error,
// print and lookup err code (e.g., -50 = bad parameter)
}
To add a key if it was missing:
var query : [NSString : AnyObject] = [
kSecClass : kSecClassGenericPassword,
kSecAttrService : "MyAwesomeService",
kSecAttrLabel : "MyAwesomeService Password",
kSecAttrAccount : username,
kSecValueData : password.dataUsingEncoding(NSUTF8StringEncoding)!
]
let result = SecItemAdd(query, nil)
// check that result is errSecSuccess, etc...
A few things to point out: your initial problem might have been that str.dataUsingEncoding returns an Optional. Adding '!' or better yet, using an if let to handle nil return, would likely make your code work. Printing out the error code and looking it up in the docs will help a lot in isolating the problem (I was getting err -50 = bad parameter, until I noticed a problem with my kSecClass, nothing to do with data types or casts!).
This seemed to work fine or at least compiler didn't have kittens - UNTESTED beyond that
var secret: NSData = "Top Secret".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var array1 = NSArray(objects:"\(kSecClassGenericPassword)", "MyService", "Some account", secret)
var array2 = NSArray(objects:"\(kSecClass)","\(kSecAttrService)", "\(kSecAttrAccount)","\(kSecValueData)")
let query = NSDictionary(objects: array1, forKeys: array2)
println(query)
let status = SecItemAdd(query as CFDictionaryRef, nil)
Seems to work fine in Beta 2
In order to get this to work, you need to access the retained values of the keychain constants instead. For example:
let kSecClassValue = kSecClass.takeRetainedValue() as NSString
let kSecAttrAccountValue = kSecAttrAccount.takeRetainedValue() as NSString
let kSecValueDataValue = kSecValueData.takeRetainedValue() as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword.takeRetainedValue() as NSString
let kSecAttrServiceValue = kSecAttrService.takeRetainedValue() as NSString
let kSecMatchLimitValue = kSecMatchLimit.takeRetainedValue() as NSString
let kSecReturnDataValue = kSecReturnData.takeRetainedValue() as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne.takeRetainedValue() as NSString
You can then reference the values in the MSMutableDictionary like so:
var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])
I wrote a blog post about it at:
http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/
Hope this helps!
rshelby
Swift 3
let kSecClassKey = String(kSecClass)
let kSecAttrAccountKey = String(kSecAttrAccount)
let kSecValueDataKey = String(kSecValueData)
let kSecClassGenericPasswordKey = String(kSecClassGenericPassword)
let kSecAttrServiceKey = String(kSecAttrService)
let kSecMatchLimitKey = String(kSecMatchLimit)
let kSecReturnDataKey = String(kSecReturnData)
let kSecMatchLimitOneKey = String(kSecMatchLimitOne)
you can also do it inside the dictionary itself alá:
var query: [String: Any] = [
String(kSecClass): kSecClassGenericPassword,
String(kSecAttrService): "MyService",
String(kSecAttrAccount): "Some account",
String(kSecValueData): secret
]
however, this is more expensive for the compiler, even more so since you're probably using the query in multiple places.
more convenient to use the cocoa pods SSKeychain
+ (NSArray *)allAccounts;
+ (NSArray *)accountsForService:(NSString *)serviceName;
+ (NSString *)passwordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)deletePasswordForService:(NSString *)serviceName account:(NSString *)account;
+ (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account;

BAD_INSTRUCTION within swift closure

func loadMe() -> Void {
println(me.initUrl());
let url:NSURL = NSURL(string:me.initUrl())
let request:NSURLRequest = NSURLRequest(URL:url)
let queue:NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(request, queue:queue, completionHandler:{response, data, error in
if data {
var jerr:NSError?
var json:NSDictionary = NSJSONSerialization.JSONObjectWithData(data, options:NSJSONReadingOptions.MutableContainers, error:&jerr) as NSDictionary
if !jerr {
println(json.description)
let time:String = json.valueForKey("time") as String
println(time)
// self.me.setDictionary(json)
}
}
})
}
The code above works fine up to the point where i want to assign a value from an NSDictionary to a variable.
let time:String = json.valueForKey("time") as String
is followed by EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subcode=0x0) but
println(json.description)
prints out a valid NSDictionary. Does anybody know a solution to this problem?
The valueForKey() method returns an optional value; you are forcing that value to a String and apparently that is causing the crash. I can get the Swift REPL to crash with:
1> var json = ["a": "1", "b": "2"]
json: Dictionary<String, String> = {
[0] = {
key = "a"
value = "1"
}
[1] = {
key = "b"
value = "2"
}
}
2> let data:String = json["b"] as String
Bitcast requires both operands to be pointer or neither
%58 = bitcast i8* %57 to %SS.232
Bitcast requires both operands to be pointer or neither
%110 = bitcast i8* %109 to %SS.232
Stored value type does not match pointer operand type!
store %SS.232 %110, i8** %.core7._baseAddress.value, align 8
i8*LLVM ERROR: Broken function found, compilation aborted!
Assertion failed: (err == 0), function ~Mutex, file /SourceCache/lldb_KLONDIKE/lldb_KLONDIKE-320.3.100/source/Host/common/Mutex.cpp, line 246.
Abort trap: 6
The solution is to forgo the String coercion. In my example:
2> let data:String? = json["b"]
data: String? = "2"
3> let data:String? = json["c"]
data: String? = nil
I suspect you added the 'as String' 1) knowing that the value is actually a String and 2) to avoid a compiler error. The proper approach is to use ? to indicate an optional value.
I solved my problem with casting to the correct type of the original NSDictionary value after i realised that not all values of the NSDictionary were NSStrings. If your service returns a mixed type JSON object like this
{"id":2, "name":"AC Vent Temp", ...}
you'll have to fetch it's values like that.
var id:int = sensor.valueForKey("id") as Int;
var name:String? = sensor.valueForKey("name") as String;

Resources