I'm creating an iPhone application that integrates the PayPal API for iOS. The API requires the API credentials when sending a request. I read the PayPal API and it says
Never send Express Checkout requests from your mobile application directly to PayPal. The requests require your PayPal API credentials. Placing your credentials on mobile devices exposes you and PayPal to unacceptable security risks. Send Express Checkout requests only from secure servers.
My question is, what is the best way of storing the API credentials so as to decrease the possibilities for my credentials being exposed or hacked? Is attaching the credentials to an iPhone build risky? Why or how? Is storing these credentials on a secure server reliable enough?
EDIT: how can keychain access api on iOS can help me with this?
Putting the API keys in your app is completely insecure. Via a verity of techniques, anyone who can download the app or gets their hands on a phone with the app on it can simply read the API key. This holds even if you do what #MatthiasBauch suggested and download the secret later. It also holds even if you do what #Rexeisen suggested and obfuscate the string.
You best bet is to user apple's built in subscription services to handle payments ( which may not be applicable and they take a cut but is likely more secure than what you can do on the phone)
In the likely even that you don't want to or can't do that, give each instance of the app a unique id that they register when they download with a server you control. That sever than has the paypal credentials and will make api calls on their behalf . This way, if any given phone is stolen/ has its api key to your server read, you can simply revoke that key and your paypal API keys are still safe. Important caveat: until you actually revoke that app's key, anyone who has it can still use it to make what ever calls your server supports. This could be a very bad thing.
It is risky as you can run the Strings utility on just about any app (try it, it's kinda scary) and get the strings from the code. Generally I'd recommend packaging the secrets in the app, but leave them on a secure server elsewhere. If you must put it in the app, one thing you can do is obfuscate the strings so it's not obvious.
NSString *secret = kTwitterClientSecret;
NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding];
NSString *key = #"Twitter";
[secretData obfuscateOrDeobfuscateWithKey:key];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [NSString stringWithFormat:#"%#/%#-%#", documentsPath, key, #"output"];
[secretData writeToFile:path atomically:NO];
NSLog(#"Wrote obfuscated data to: %#", documentsPath);
Where obfuscateOrDeobfuscateWithKey is a category on NSData
// Inspiration from: http://iosdevelopertips.com/cocoa/obfuscation-encryption-of-string-nsstring.html
- (void)obfuscateOrDeobfuscateWithKey:(NSString *)key
{
// Get pointer to data to obfuscate
char *dataPtr = (char *) [self bytes];
// Get pointer to key data
char *keyData = (char *) [[key dataUsingEncoding:NSUTF8StringEncoding] bytes];
// Points to each char in sequence in the key
char *keyPtr = keyData;
int keyIndex = 0;
// For each character in data, xor with current value in key
for (int x = 0; x < [self length]; x++) {
// Replace current character in data with current character xor'd with current key value.
// Bump each pointer to the next character.
*dataPtr = *dataPtr ^ *keyPtr;
dataPtr++;
keyPtr++;
// If at end of key data, reset count and set key pointer back to start of key value
if (++keyIndex == [key length]) {
keyIndex = 0, keyPtr = keyData;
}
}
}
Then you can declare a constant to be something like
static unsigned char const kTwitterClientSecret[] = {
0x00, 0x00, 0x00, ... etc ...
};
static unsigned int const kTwitterClientSecret_len = LENGTH;
Then to get the string back you can do
[NSString deobfuscatedStringWithBytes:kTwitterClientSecret length:kTwitterClientSecret_len key:#"Twitter"];
Where this is a category on NSString
+ (NSString *)deobfuscatedStringWithBytes:(const void *)bytes length:(NSUInteger)length key:(NSString *)key
{
NSData *deobfuscatedData = [NSData dataWithBytes:bytes length:length];
[deobfuscatedData obfuscateOrDeobfuscateWithKey:key];
return [[NSString alloc] initWithData:deobfuscatedData encoding:NSUTF8StringEncoding];
}
This will do very simple obfuscation and will not show up in strings.
Related
I am making an app based on broadcasting I am using videoCore Lib for Broadcast for that I am using below code to start rtmp session
[_session startRtmpSessionWithURL:urlForStream
andStreamKey:streamID];
urlForStream is url of wowza server like rtmp://some.tv/some/username/username/randamvalue
that Randam value is don't want to override my videos every time so i am using that url and now My Problem is VCSessionState class state is not changing it is not coming to started state and I am getting the Error here is streamsessio.mm class and [NSRL(m_runLoop) run]; i don't kow where i miss please help me out
Considering from my experience of having created my own RTMP protocol library, I thought you have to separate the URL into two parts: tcUrl and stream key. In your case, the random value is likely to be a stream key.
NSString *tcUrl;
NSString *stream;
separateRtmpUrl(urlForStream, &tcUrl, &stream);
[_session startRtmpSessionWithURL:tcUrl andStreamKey:stream];
The definition of the separateRtmpUrl function can be like this.
static void separateRtmpUrl(NSString *baseUrl, NSString **tcUrl, NSString **stream)
{
NSURL *url = [NSURL URLWithString:baseUrl];
*stream = url.pathComponents.lastObject;
*tcUrl = [NSString stringWithFormat:#"%#://%#%#", url.scheme, url.host,
[NSString pathWithComponents:
[url.pathComponents subarrayWithRange:
NSMakeRange(0, url.pathComponents.count - 1)]]];
if (url.query) {
*stream = [#[*stream , url.query] componentsJoinedByString:#"?"];
*tcUrl = [#[*tcUrl , url.query] componentsJoinedByString:#"?"];
}
}
We are using WL.EncryptedCache.open to open the locat storage. This at times takes 4-8 seconds to get the encryption key. Please suggest on how to resolve this performance issue ?
We are using Worklight 6.0
You can generate the secure token locally by overwriting the secure random call: WL.EncryptedCache.secureRandom = function(callback){callback(Math.random()+"")}. That way you don't have to hit the server to get it. It will be significantly less secure and I do not recommend it. Beyond that and running the application on a faster device, there's nothing you can do. Generating a secure key is an expensive operation.
Alternatively, you can replace Math.random above with a cordova plugin exec call that gets a cryptographically secure random string using SecRandomCopyBytes. Some example code:
int bytes = 32;
uint8_t randBytes[bytes];
int rc = SecRandomCopyBytes(kSecRandomDefault, (size_t)bytes, randBytes);
if (rc != 0) {
//handle failure
}
NSMutableString* hexEncoded = [NSMutableString new];
for (int i = 0; i < bytes; i++) {
[hexEncoded appendString:[NSString stringWithFormat:#"%02x", randBytes[i]]];
}
NSString* randomStr = [NSString stringWithFormat:#"%#", hexEncoded];
There are getting started modules that explain how to write cordova plugins for iOS here.
Updates
Edit 2/6/14:
I created a local Apache Tomcat server to test SSL w/ certificate authentication. I was successful! Everything works as expected, using both of my approaches below. (MKNetworkKit, and custom code). While this does tell me my code is working, my original issue is still not solved. I updated the title of the question to more specifically reflect the issue. Does anyone know if SAP Portal needs special settings to accept certificates from an iOS app? Remember, I was able to successfully authenticate using Safari mobile after importing the CA and .p12 into the shared keychain, I was only unsuccessful in code (which I now know the code works, just not with the portal).
I am creating a very simple iOS7 Cordova 3.2 application with a custom plugin to get data from an SSL web service by providing only a .p12 certificate for authentication (no basic auth or other user credentials needed). I am performing all tests on a physical iPad (no simulator). The web service lives on a development SAP NetWeaver portal box using a self signed certificate. For now, I imported the server's CA into the iOS keychain to avoid certificate trust errors. For testing purposes, my .p12 certificate is bundled locally inside the app, in the root of the mainBundle.
When trying to connect to the web service I get the follow error in the console:
CFNetwork SSLHandshake failed (-9825)
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9825)
Error Domain=NSURLErrorDomain Code=-1205 "The server “myhostremoved.com” did not accept the certificate." UserInfo=0x14e99000 {NSErrorFailingURLStringKey=https://myhostremoved.com/sslwebservice/, NSErrorFailingURLKey=https://myhostremoved.com/sslwebservice/, NSLocalizedDescription=The server “myhostremoved.com” did not accept the certificate., NSUnderlyingError=0x14d9b1d0 "The server “myhostremoved.com” did not accept the certificate.", NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x14d94720>}
According to Apple's documentation site, the -9825 error referes to a bad certificate.
There are many questions on SO related to what I am trying to do, but none specifically pertaining to the error I am seeing. I approached the development of the code in two different ways.
First I tried to use code already on SO, adapting it to my use case. See code below:
- (void)startConnection:(CDVInvokedUrlCommand*)command {
NSDictionary *options = [command.arguments objectAtIndex:0];
NSURL *serverURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#", [options objectForKey:#"host"]]];//hostname provided by Cordova plugin, but could just as easily be hardcoded here
NSMutableURLRequest *connectionRequest = [NSMutableURLRequest requestWithURL:serverURL];
NSURLConnection *connection = nil;
connection = [[NSURLConnection alloc] initWithRequest:connectionRequest delegate:self startImmediately:YES];
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
// gets a certificate from local resources
NSString *thePath = [[NSBundle mainBundle] pathForResource:#"mycert" ofType:#"p12"];
NSData *PKCS12Data = [[NSData alloc] initWithContentsOfFile:thePath];
CFDataRef inPKCS12Data = (__bridge CFDataRef)PKCS12Data;
SecIdentityRef identity;
// extract the ideneity from the certificate
[self extractIdentity :inPKCS12Data :&identity];
SecCertificateRef certificate = NULL;
SecIdentityCopyCertificate (identity, &certificate);
const void *certs[] = {certificate};
CFArrayRef certArray = CFArrayCreate(kCFAllocatorDefault, certs, 1, NULL);
// create a credential from the certificate and ideneity, then reply to the challenge with the credential
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:(__bridge NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
- (OSStatus)extractIdentity:(CFDataRef)inP12Data :(SecIdentityRef*)identity {
OSStatus securityError = errSecSuccess;
CFStringRef password = CFSTR("MyCertPassw0rd");
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { password };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
securityError = SecPKCS12Import(inP12Data, options, &items);
if (securityError == 0) {
CFDictionaryRef ident = CFArrayGetValueAtIndex(items,0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue(ident, kSecImportItemIdentity);
*identity = (SecIdentityRef)tempIdentity;
}
if (options) {
CFRelease(options);
}
return securityError;
}
On my second approach, I tried to use the MKNetworkKit library, which abstracts away alot of the code needed to interface with the certificate. All you need to do is provide the path to the certificate and the password. Again, I get the same error as above. This code is below.
- (void)startConnection:(CDVInvokedUrlCommand*)command {
NSDictionary *options = [command.arguments objectAtIndex:0];
NSURL *serverURL = [NSURL URLWithString:[NSString stringWithFormat:#"%#", [options objectForKey:#"host"]]];//hostname provided by Cordova plugin, but could just as easily be hardcoded here
MKNetworkEngine *engine = [[MKNetworkEngine alloc] initWithHostName:serverURL customHeaderFields:nil];
MKNetworkOperation *op = [engine operationWithPath:nil params:nil httpMethod:#"GET" ssl:YES];
NSString *thePath = [[NSBundle mainBundle] pathForResource:#"mycert" ofType:#"p12"];
[op setShouldContinueWithInvalidCertificate:YES];
op.clientCertificate = thePath;
op.clientCertificatePassword = #"MyCertPassw0rd";
[op addCompletionHandler:^(MKNetworkOperation *operation) {
NSLog(#"[operation responseData]-->>%#", [operation responseString]);
}errorHandler:^(MKNetworkOperation *errorOp, NSError* err) {
NSLog(#"MKNetwork request error : %#", [err localizedDescription]);
}];
[engine enqueueOperation:op];
}
I get the same error using both approaches. Any ideas?
Known information
I know that the app is finding .p12 certificate because when I fudge the path to the .p12 cert, I receive an error saying it cannot find the certificate which I don't normally see otherwise.
I know the password I am providing for the certificate file is correct because when I fudge the password, I receive an error regarding the password I don't normally see otherwise.
I don't think certificate trust is an issue because if I remove my server's CA from the iOS keychain, I get an error in the console specifically stating the server cannot be trusted. After adding the CA back, this error no longer occurs, but I get same error as above (-9825)
Others tests
When providing basic auth in code (bypassing certificate authentication), everything works as expected and I receive no errors.
I have also tried all the same steps above, but using a .pfx file instead of my .p12 certificate, same errors.
The SSL service works in mobile Safari. I imported the .p12 certificate into my keychain via the iPhone Configuration utility, and tested the web service via mobile safari. Everything works as expected, no errors occur (no trust errors either), and I receive the expected output.
I am also able to successfully test the web service on my desktop using the rest-client utility
TL;DR
I am trying to authenticate to an SSL web service using only a .p12 certificate in objective-c. I know the server and web service both work with the cert for auth, but I keep getting errors when I try to establish the connection in objective-c, so something must be wrong in my code. Please help!
I finally was able to resolve this problem. I found the answer here
http://oso.com.pl/?p=207&lang=en
It looks like the certificate was being sent twice, causing an error on the server.
Incase the above link dies, I found the same solution already on SO (but for a different question and error).
When using Client Certificate Authentication, why do I keep getting NSURLErrorDomain Code=-1206?
Ultimately, changing this:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:(__bridge NSArray*)certsArray persistence:NSURLCredentialPersistenceNone];
to this:
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:nil persistence:NSURLCredentialPersistenceNone];
solved my issue. It would be nice if anyone could offer more insight into this issue.
In some cases handshake may fail because server also asks intermediate and root certificates.
To fix it do the following:
Authentication challenge part:
OSStatus status = extractIdentityAndTrust(inP12data, &myIdentity, &myTrust, password);
SecCertificateRef myCertificate;
SecIdentityCopyCertificate(myIdentity, &myCertificate);
CFIndex count = SecTrustGetCertificateCount(myTrust);
NSMutableArray* myCertificates = [NSMutableArray arrayWithCapacity:count];
if (count > 1) {
for (int i = 1; i < count; ++i) {
[myCertificates addObject:(__bridge id)SecTrustGetCertificateAtIndex(myTrust, i)];
}
}
NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity certificates:myCertificates persistence: NSURLCredentialPersistenceForSession];
Extracting method
OSStatus extractIdentityAndTrust(CFDataRef inPKCS12Data,
SecIdentityRef *outIdentity,
SecTrustRef *outTrust,
CFStringRef keyPassword) {
OSStatus securityError = errSecSuccess;
const void *keys[] = { kSecImportExportPassphrase };
const void *values[] = { keyPassword };
CFDictionaryRef optionsDictionary = NULL;
/* Create a dictionary containing the passphrase if one
was specified. Otherwise, create an empty dictionary. */
optionsDictionary = CFDictionaryCreate(
NULL, keys,
values, (keyPassword ? 1 : 0),
NULL, NULL);
CFArrayRef items = NULL;
securityError = SecPKCS12Import(inPKCS12Data,
optionsDictionary,
&items);
if (securityError == 0) {
CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex (items, 0);
const void *tempIdentity = NULL;
tempIdentity = CFDictionaryGetValue (myIdentityAndTrust,
kSecImportItemIdentity);
CFRetain(tempIdentity);
*outIdentity = (SecIdentityRef)tempIdentity;
const void *tempTrust = NULL;
tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
CFRetain(tempTrust);
*outTrust = (SecTrustRef)tempTrust;
}
if (optionsDictionary)
CFRelease(optionsDictionary);
if (items)
CFRelease(items);
return securityError; }
Please note that loop from 1 (not 0) is not a mistake. First cert is already added to "myIdentify", so only other certs should be passed as certificates, otherwise most likely you'll receive an error during handshake because of duplicating.
Fellow Coders...
I have server url's set up in my application's global constants file.
I also have a variable called "DebugMode" in my application plist that once switched should change the urls the application will be using.
Constants.h
extern NSString * const LOGIN_URL;
Constants.m
NSString * const LOGIN_URL = #"http://www.url.com";
Anyway I can replicate the following psuedo code below into Objective C?
if([[[[NSBundle mainBundle] infoDictionary] objectForKey:#"DebugMode"] boolValue] == NO)
{
NSString * const LOGIN_URL = #"http://www.production-url.com";
}
else
{
NSString * const LOGIN_URL = #"http://www.qa-url.com";
}
What your asking for isn't exactly possible (at least not in the way your asking for). A constant is setup and established whilst compiling (not strictly true, but for the sake of this explanation, it will do) and thus means that it can not be mutated for any reason at runtime.
The traditional way of changing the values of constants depending on debug and release code is through the preprocessor. Like so:
#if __DEBUG_MODE__ == 1
NSString * const LOGIN_URL = #"http://www.qa-url.com";
#else
NSString * const LOGIN_URL = #"http://www.production-url.com";
#endif
Now __DEBUG_MODE__ needs to be defined before it can do anything, and there are a few ways you could do this. You could add the following line to you prefix header file (.pch)
#define __DEBUG_MODE__ 1 // Change to 0 to disable debug mode.
or add the compiler flag -M__DEBUG_MODE__=1 to the file you wish to effect. This means that whenever __DEBUG_MODE__ is set with a value of 1, the compiler will use your debug constant, and when it has a value of 0 the compiler will use the production constant.
This also has the benefit of keeping debug and production code separate (you should avoid having both in your binary as it can open a whole world of problems and security issues).
Hope this helps.
Whenever I've had a situation like this I've just created a class method in my constants file:
+ (NSString *)loginURL {
if([[[[NSBundle mainBundle] infoDictionary] objectForKey:#"DebugMode"] boolValue] == NO){
return #"http://www.production-url.com";
}
else {
return #"http://www.qa-url.com";
}
}
It also makes it more clear in your code that as the loginURL string is coming via a method, it may be dependent on a run time condition:
NSURL *loginURL = [NSURL URLWithString:[Constants loginURL]];
What is the proper way to check the basic constraints of a certificate ? Below is the code I am currently using to mimic the icons show in the Keychain (the reason for below is that while we have a
SFChooseIdentityPanel * identityPanel = [SFChooseIdentityPanel sharedChooseIdentityPanel];
the equivalent for selecting a CA or a Host/Leaf cert does not exist. And that is useful when setting up/locking down SSL connections.
Unfortunately - I cannot find the OID strings in the header files to cleanly extract the CA:TRUE or false form a cert (or am using the API in the wrong way).
So the questions are
How do I cleanly check for CA:TRUE - while below works - I can imagine that it would get foiled by a malformed cert with text strings in the right places.
Secondly - I am using a heuristic of Issuer==Subject to detect self signed. Is there a cleaner way to do this ?
Finally - from trial and error - below seems to mimic apple her choices in the keychain - but the docs are rather hard to understand. Does kSecTrustResultProceed really mean that the user has set an override and kSecTrustResultUnspecified that in fact the trust is specified by the system basic trust ? While it 'work's - I cannot quite understand the exact interpretation of the docs.
Thanks a lot. Code below.
Dw.
#implementation NSImage (CertificateSelectionPanelExtensions)
+(NSImage *)iconForCertificate:(SecCertificateRef)certificateRef small:(BOOL)isSmall
{
BOOL isCA = FALSE, isInvalid = TRUE, isUserTrust = FALSE;
NSString * issuer = nil, * subject = nil;
const void *keys[] = { kSecOIDX509V1SubjectName, kSecOIDX509V1IssuerName, kSecOIDExtendedKeyUsage, kSecOIDBasicConstraints };
CFArrayRef keySelection = CFArrayCreate(NULL, keys , sizeof(keys)/sizeof(keys[0]), &kCFTypeArrayCallBacks);
CFDictionaryRef vals = SecCertificateCopyValues(certificateRef, keySelection, NULL);
CFArrayRef values;
CFDictionaryRef dict;
dict = CFDictionaryGetValue(vals, kSecOIDBasicConstraints );
values = dict ? CFDictionaryGetValue(dict, kSecPropertyKeyValue) : NULL;
if (values) {
for(int i = 0; i < CFArrayGetCount(values); i++) {
CFDictionaryRef subDict = CFArrayGetValueAtIndex(values, i);
// We cannot find OID defines for the CA - so rely on the lower libraries to give us a string
// of sorts. Not a good idea - as now this code can be foiled by a actual string.
//
NSString *k = [NSString stringWithFormat:#"%#", CFDictionaryGetValue(subDict, kSecPropertyKeyLabel)];
NSString *v = [NSString stringWithFormat:#"%#", CFDictionaryGetValue(subDict, kSecPropertyKeyValue)];
if ([#"Certificate Authority" isEqualToString:k] && [#"Yes" isEqualToString:v]) {
isCA = TRUE;
}
}
};
// Fall back on a simple self-sign check if there where no kSecOIDBasicConstraints.
// set on the cert. Note that it is a DN is equal check - in some cases
// doing a 509v3 Subject/Authority Key Identifier may be better ?? XXXX
//
if (!isCA && !values) {
dict = CFDictionaryGetValue(vals, kSecOIDX509V1SubjectName);
values = dict ? CFDictionaryGetValue(dict, kSecPropertyKeyValue) : NULL;
subject = [NSString stringWithFormat:#"%#", values];
dict = CFDictionaryGetValue(vals, kSecOIDX509V1IssuerName);
values = dict ? CFDictionaryGetValue(dict, kSecPropertyKeyValue) : NULL;
issuer = [NSString stringWithFormat:#"%#", values];
// Crap way of secondgessing CA ness.
if ([issuer isEqualToString:subject])
isCA = TRUE;
};
SecPolicyRef policy = SecPolicyCreateBasicX509(); // SecPolicyCreateSSL(YES,nil);
CFArrayRef chain = CFArrayCreate(NULL, (const void**)(&certificateRef), 1, NULL);
SecTrustRef trustRef;
SecTrustCreateWithCertificates(chain, policy, &trustRef);
SecTrustResultType result;
SecTrustEvaluate (trustRef, &result);
if(result == kSecTrustResultProceed) {
isUserTrust = TRUE;
isInvalid = FALSE;
} else
if (result == kSecTrustResultUnspecified)
isInvalid = FALSE;
CFRelease(trustRef);
CFRelease(chain);
// Images as per /System/Library/Frameworks/SecurityInterface.framework/Versions/A/Resources
// Cert <Small | Large> <Personal | Root> [_Invalid | _UserTrust ]
//
return [NSImage imageNamed:[NSString stringWithFormat:#"Cert%#%#%#",
isSmall ? #"Small" : #"Large",
isCA ? #"Root" : #"Personal",
isInvalid ? #"_Invalid" : (isUserTrust ? #"_UserTrust" : #"")]];
}
#end
The basicConstraints extension is defined in X.509 as follows:
basicConstraints EXTENSION ::= {
SYNTAX BasicConstraintsSyntax
IDENTIFIED BY id-ce-basicConstraints }
BasicConstraintsSyntax ::= SEQUENCE {
cA BOOLEAN DEFAULT FALSE,
pathLenConstraint INTEGER (0..MAX) OPTIONAL }
This, in turn, is encoded according to the Distinguished Encoding Rules (X.690). The individual parts of the BasicConstraintsSyntax sequence do not have their own OIDs.
I wouldn’t claim to be an expert, but I don’t think there is a problem with the test for the cA flag that you are doing. I don’t think the [NSString stringWithFormat:#"%#", ...] part is necessary, mind.
As for checking for self-signed certificates, it seems not unreasonable to test the subject and issuer names; obviously that really tells you whether the certificate claims to be self-signed, and to actually test that it is you’d need to check the signature yourself (whether that’s something you want to do, I don’t know). FYI, on the topic of the key identifiers, according to RFC3280, in the specific case of a self-signed certificate, the authority key identifier can be omitted, so a certificate without an authority key identifier might be an indication that the certificate was self-signed, but it’s entirely possible for someone to deliberately issue a malformed certificate without authority key identifier.
The final question appears to be cleared up by looking at the docs, which indicate that the values mean roughly what you say.
The other thing worth saying is that there is code out there that can help with this kind of thing; for instance, Jens Alfke’s MYCrypto library
Edit (8th Feb 2012)
According to the X.509 Style Guide, it’s possible for the subject name to be empty, in which case you would need to look at the subjectAltName extension instead. It’s very unlikely that this would break a test for self-signed certificates that relied on comparing the subject and issuer names — it seems unreasonable to issue a certificate to yourself providing a DN for the issuer name but then leaving the subject name empty. However, it’s worth bearing in mind.