"Export" x509 certificate in Ruby - ruby-on-rails

I'm communicating with an API that has the following directions:
Install the issued x509 certificate onto the client server.
Export the x509 certificate using the supplied password and default Machine Key Set into memory.
Base64 encode the exported bytes of the x509 certificate.
Add ‘X509Certificate’ as an HTTP header and set its value to the result of step 3.
Step 1 and 4 are easy, but I have no idea on 2 or or the 'export' portion of 3. I have tried Googling for some time and I'm not sure exactly where to even really start.
Would someone point me in the right direction on how to "export" a certificate with "machine key set"?
Here is what I have so far
raw_data = File.read('cert.pfx')
pkcs = OpenSSL::PKCS12.new(raw_data, 'password')
cert = OpenSSL::X509::Certificate.new(pkcs.certificate.to_pem)
Here is equivalent .NET code:
public string GetBase64Cert(string certificateThumbprint)
{
using (X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var foundCertificates = store.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, false);
if (foundCertificates.Count != 1)
{
return null;
}
var certByteArray = foundCertificates[0].Export(X509ContentType.Cert);
store.Close();
return Convert.ToBase64String(certByteArray);
}
}
And equivalent PHP code:
public function setx509($x509file) {
$cert = openssl_x509_parse($x509file);
$base64cert = base64_encode($cert);
return $base64cert;
}

Try
pkcs = OpenSSL::PKCS12.new(File.read('cert.pfx'), 'password')
str = Base64.urlsafe_encode64(pkcs.certificate.to_der)
Probably also str.gsub(/=+$/, '') to cut off padding

Related

Connection to AWS IoT with M2MQTT from .net core

I managed to manually create the AWS IoT config, downloaded the certs and create a console app that could subscribe to a topic. Im now trying to automate the thing creation, which results in the certificate keys being provided by AWS as strings. Im not sure how to use these. I have the root ca downloaded already, which I assume I use for all things.
My file based cert subscriber looks like this:
Console.WriteLine("AWS IOT Dotnet core message listener starting");
string iotendpoint = "blahblah-ats.iot.ap-southeast-2.amazonaws.com";
int BrokerPort = 8883;
string Topic = "topic_1/";
var CaCert = X509Certificate.CreateFromCertFile(#"root-CA.crt");
var ClientCert = new X509Certificate2(#"device.pfx", "password");
var IotClient = new MqttClient(iotendpoint, BrokerPort, true, CaCert, ClientCert, MqttSslProtocols.TLSv1_2);
try
{
IotClient.Connect(Guid.NewGuid().ToString());
Console.WriteLine("Connected to AWS IOT");
IotClient.MqttMsgPublishReceived += Client_MqttMsgPublishReceived;
IotClient.MqttMsgSubscribed += Client_MqttMsgSubscribed;
IotClient.Subscribe(new string[] { Topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_LEAST_ONCE });
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return;
}
To load the cert from file, I tried this:
var keyText = File.ReadAllText("keys.json");
var keys = JsonConvert.DeserializeObject<Keys>(keyText);
var bytes = Encoding.ASCII.GetBytes(keys.PrivateKey.ToCharArray());
var ClientCert = new X509Certificate2(bytes);
with:
class Keys {
public string PublicKey {get;set;}
public string PrivateKey {get;set;}
}
and the keys from AWS in a json file :
{
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4mh2PQ581XN9BmoCvDjlaktm/6gQgqGBItZThcQVMTjveU8H\npjOU2E/9lq7vmdO+96NuuMr9MKtFD+ZWtVExLjMq9hH0MvIvosVt9+6Ggcwz7Kdr\nigprfBMVORV0rgcK+nsd2DmBNrs339fqbTn5UAIFFBpqkNReW7LMl9h6g8hu4aYQ\nJTohDwSmgmNJKlzMJGtVfPggqt+bBi3lUf9NEOEz...
-----END RSA PRIVATE KEY-----\n",
"PublicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4mh2PQ581XN9BmoCvDjl\naktm/6gQgqGBItZThcQVMTjveU8HpjOU2E/9lq7vmdO+96NuuMr9MKtFD+ZWtVEx\nLjMq9hH0MvIvosVt9+6Ggcwz7K...
-----END PUBLIC KEY-----\n"
}
Im getting an error loading the cert:
An unhandled exception of type 'Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException' occurred in System.Security.Cryptography.X509Certificates.dll: 'Cannot find the requested object.'
Can anyone see anything obviously wrong here? I dont understand certificates...
Update:
Using the PEM text produced by the AWS SDK is more correct, but I still get an error connecting - M2MQTT says there is a cert problem, it has no private key. Does it need it?
var pemText = File.ReadAllText("thing.crt");
var bytes = Encoding.ASCII.GetBytes(pemText);
var ClientCert = new X509Certificate2(bytes);
Final hacked together solution looks like this:
var keyText = File.ReadAllText("keys.json"); // saved from AWS SDK when creating IoT Cert.
var keys = JsonConvert.DeserializeObject<Keys>(keyText);
var rsa = RsaHelper.PrivateKeyFromPem(keys.PrivateKey);
var pemText = File.ReadAllText("thing.crt");
var bytes = Encoding.ASCII.GetBytes(pemText);
var ClientCert = new X509Certificate2(bytes);
ClientCert = ClientCert.CopyWithPrivateKey(rsa);
ClientCert = new X509Certificate2(ClientCert.Export(X509ContentType.Pfx,"12345678"), "12345678");
RSAHelper from https://github.com/dejanstojanovic/dotnetcore-token-authentication/blob/asymmetric_rsahelper/Sample.Core.Common/Helpers/RsaHelper.cs
Last trick to Export and Import the PFX from https://github.com/aspnet/KestrelHttpServer/issues/2960 to solve error: "No credentials are available in the security package"
Sidebar - why do we (as an industry) always take something conceptually simple and make it so fricken complicated? :)

Cannot store Certificate to KeyChain using SecKeyChain in Xamarin

After hours spent reading through what's available online to fix this, I decided to post my question here.
My goal is simple: Store an X509Certficate to KeyChain using Xamarin for iOS. This is a self signed certificate that I generated using BouncyCastle library.
I'm successfuly importing it, but when saving to KeyChain using SecKeyChain.Add, the result is always SecStatusCode.Paramwhich the documentation explains is missing or invalid parameter. Here's the method I use
public static bool StoreCertInKeyChain(X509Certificate2 certificate, string password)
{
var data = certificate.Export(X509ContentType.Pkcs12, password);
var options = NSMutableDictionary.FromObjectAndKey(FromObject(password), SecImportExport.Passphrase);
var statusCode = SecImportExport.ImportPkcs12(data, options, out NSDictionary[] result);
if (statusCode != SecStatusCode.Success) return false;
var certChain = result[0][SecImportExport.CertChain];
var record = new SecRecord(SecKind.Certificate)
{
Label = "MyKey",
Account = "Certificate",
ApplicationTag = "MyTag"
};
record.SetValueRef(certChain);
// Using the below code instead, produces the same result
// var cert = new SecCertificate(certChain.Handle);
// record.SetValueRef(cert);
var resultAdd = SecKeyChain.Add(record);
return resultAdd == SecStatusCode.Success;
}
Has anyone ran into this problem? I'm out of ideas what else to try. I followed the examples given on Xamarin documentation site, without success. Thank you
Answering my solution here, in case anyone else runs into the same issue. The problem was that the certificate supplied in the SecRecord wasn't an instance of SecCertificate, so using SecImportExport.ImportPkcs12 was the wrong way to do it. I ended up using SecIdentity.Import instead, which gives a reference to the certificate as well as the private key in it. The certificate and the private key need to be added to key chain separately using an identity. Here's the code that accomplishes this.
var identity = SecIdentity.Import(certificate.Export(X509ContentType.Pkcs12, password), password);
var storedCertificate = SecKeyChain.QueryAsConcreteType(new SecRecord(SecKind.Certificate) { Label = "My Cert" }, out SecStatusCode statusCode);
if (statusCode != SecStatusCode.Success)
{
var record = new SecRecord(SecKind.Certificate);
record.Label = "My Cert";
record.SetValueRef(identity.Certificate);
var result = SecKeyChain.Add(record);
SecKeyChain.AddIdentity(identity);
storedCertificate = SecKeyChain.QueryAsConcreteType(new SecRecord(SecKind.Certificate) { Label = "My Cert" }, out statusCode);
}
var storedIdentity = SecKeyChain.FindIdentity(storedCertificate as SecCertificate);
The certificate can be retrieved using the label, but to get the private key, the identity must be queried using the certificate as parameter in SecKeyChain.FindIdentity. From this point on, access to signing and decryption on the private key is available from the identity instance.

How to implement client certificates and server authentication for iOS

I have recently gone through an extremely arduous process to build something that should be very simple yet appears to be essentially un-findable in any one place. I’d like to try to put everything here to ask if I’m doing anything wrong and, if not, to help anyone who needs this information.
Background: The product/service for which I was trying to provide security is built around WCF services on a Windows server that are accessible only though custom client apps on a PC or an iPad. One server per customer, no browser access. Everything was already TLS secured with authentication and authorization using Windows standard mechanisms and certs from a commercial CA.
To further restrict access, client/server certificates were implemented for the Windows platform using self-signed certificates (commercial CAs are not required in cases where there is mutual authentication with no public/browser access – despite claims to the contrary - and they are harder to manage).
Getting all this to work for the iPad was a terribly documented nightmare with an amazing amount of disinformation or partially correct recommendations. In what follows, I have tried to link to the best sources but I apologize if I have inadvertently missed an attribution. Please comment if there is anything wrong/misleading about this post.
Thanks
The main steps are:
Create a system for generating certificates (easy but non-trivial if it’s a production system)
Transfer the certificates to an iPad (NOT embedded in the app store bundle!)
Save all the received credentials in the app keychain (where Apple says they belong)
Retrieve the saved credentials from the keychain for use in NSURLConnections
Actually authenticate server certificate and return client credentials
Step 1. Generate Certificates
Ref: http://developer-should-know.tumblr.com/post/127063737582/how-to-create-your-own-pki-with-openssl
You can use other methods but OpenSSL for Windows [http://slproweb.com/products.html] is pretty awesome, except that the standard interface is cmdline and the documentation is hard to follow.
What I wish someone had explained to me up front is obvious, yet not:
[a] The app installs to a root level directory and includes configuration files that are used by default for settings that are not specified at the command line
[b] the locations of the intermediate and output files should be specified in the configuration files
[c] certain files need to be created by hand before running the commands
[d] you should build a file/folder structure that suits what you are trying to do and then customize the cfg files accordingly.
In my case, this meant one RootCA for my company, an intermediate cert per customer (set to be good to only make client certificates), a server certificate per customer, and client certificates as required. (this is the minimum config, never use CA/client pairs, keep the root in a lockbox)
Here’s my file structure:
c:\sslcert
root
certs
YourCompany (duplicate this structure as required)
intermediate
server
client
crl (optional)
In top level sslcert folder
.rnd (empty file)
certindex.txt (empty file)
serial.txt (Text file seeded with the text “01”, hold the quotes)
In root folder
RootCA.cfg
In certs\template folder
IntermediateCA.cfg
Set working directory and launch OpenSSL
cd \sslcert
c:\OpenSSL-Win32\bin\openssl.exe
Create root key and certificate in one step
req -config ./root/RootCA.cfg -new -x509 -days 7300 -extensions v3_ca -keyout root/YourCompanyRootCAkey.pem -out root/YourCompanyRootCAcert.cer
NOTE for the beginner: the -extensions allows you to choose to apply one of several subsections in the same cfg file.
Check key and cert (optional)
x509 -noout -text -in root/YourCompanyRootCAcert.cer
Request a new intermediate certificate
req -config certs/YourCompany/IntermediateCA.cfg -new -keyout certs/YourCompany/intermediate/intermediateCAkey.pem -out certs/YourCompany/intermediate/intermediateCAreq.pem  
Sign intermediate certificate using root certificate found in root config
ca -config root/RootCA.cfg -extensions v3_intermediate_ca -days 3650 -notext -in certs/YourCompany/intermediate/intermediateCAreq.pem -out certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer
Check key and cert (optional)
x509 -noout -text -in certs/YourCompany/intermediate/YourCompanyIntermediateCAcert.cer
Create the certificate chain file by concatenating the intermediate and root certificates (this is just a simple append from the command line - the new chain will be added to the final p12 package)
c:\sslcert> type c:\sslcert\certs\YourCompany\intermediate\YourCompanyIntermediateCAcert.cer c:\sslcert\root\YourCompanyRootCAcert.cer > c:\sslcert\certs\YourCompany\intermediate\YourCompanyCAchain.cer
Request a new client key and certificate
genrsa -aes256 -out certs/YourCompany/client/YourCompanyClientkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key
certs/YourCompany/client/YourCompanyClientkey.pem -new -sha256 -out certs/YourCompany/client/YourCompanyClientreq.pem
Sign and test client certificate with intermediate authority
ca -config certs/YourCompany/IntermediateCA.cfg -extensions usr_cert -days 1095 -notext -md sha256 -in certs/YourCompany/client/YourCompanyClientreq.pem -out certs/YourCompany/client/YourCompanyClientcert.cer
x509 -noout -text -in certs/YourCompany/client/YourCompanyClientcert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/client/YourCompanyClientcert.cer
Package client certificate
pkcs12 -export -in certs/YourCompany/client/YourCompanyClientcert.cer -name “YourCompany Smips Client” -inkey certs/YourCompany/client/YourCompanyClientkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/client/YourCompanyClientWithName.p12
Rename pkcs for import into iOS from email/iTunes
c:\sslcert> copy c:\sslcert\certs\YourCompany\client\YourCompanyClient.p12 c:\sslcert\certs\YourCompany\client\YourCompanyClient.yourext12
Request a new server key and certificate
genrsa -aes256 -out certs/YourCompany/server/YourCompanyServerkey.pem 2048
req -config certs/YourCompany/IntermediateCA.cfg -key certs/YourCompany/server/YourCompanyServerkey.pem -new -sha256 -out certs/YourCompany/server/YourCompanyServerreq.pem
Sign and test server certificate with intermediate authority
ca -config certs/YourCompany/IntermediateCA.cfg -extensions server_cert -days 1095 -notext -md sha256 -in certs/YourCompany/server/YourCompanyServerreq.pem -out certs/YourCompany/server/YourCompanyServercert.cer
x509 -noout -text -in certs/YourCompany/server/YourCompanyServercert.cer
verify -CAfile certs/YourCompany/intermediate/YourCompanyCAchain.cer certs/YourCompany/server/YourCompanyServercert.cer
Package server certificate
pkcs12 -export -in certs/YourCompany/server/YourCompanyServercert.cer -name “YourCompany Smips Server” -inkey certs/YourCompany/server/YourCompanyServerkey.pem -certfile certs/YourCompany/intermediate/YourCompanyCAchain.cer -out certs/YourCompany/server/YourCompanyServer.p12
Here are the cfg files:
Root
dir = .
[ ca ]
default_ca = CA_default
[ CA_default ]
serial = $dir/serial.txt
database = $dir/certindex.txt
new_certs_dir = $dir/certs
certs = $dir/certs
private_key = $dir/root/yourcompanyRootCAkey.pem
certificate = $dir/root/yourcompanyRootCAcert.cer
default_days = 7300
default_md = sha256
preserve = no
email_in_dn = no
nameopt = default_ca
certopt = default_ca
policy = policy_strict
[ policy_strict ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 4096 # Size of keys
default_keyfile = key.pem # name of generated keys
default_md = sha256 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
[ req_distinguished_name ]
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
0.organizationName_default = yourcompany
organizationalUnitName_default = yourcompanyRoot Certification
emailAddress_default = info#yourcompany.com
localityName_default = Okeefenokee
stateOrProvinceName_default = Wisconsin
countryName_default = US
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ crl_ext ]
authorityKeyIdentifier=keyid:always
Intermediate
dir = .
# [For non-command-line folks, everything is keyed to the working directory here (.) so if your working prompt says c:\sslcerts> then the cfg will look for serial.txt at c:\sslcerts\serial.txt and bomb if it doesn’t find things laid out accordingly. Thats why you set up a directory structure to match these entries]
[ ca ]
default_ca = CA_default
[ CA_default ]
serial = $dir/serial.txt
database = $dir/certindex.txt
crl_dir = $dir/certs/yourcompany/crl
new_certs_dir = $dir/certs
certs = $dir/certs
private_key = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
certificate = $dir/certs/yourcompany/intermediate/yourcompanyIntermediateCAcert.cer
default_days = 3650
default_md = sha256
preserve = no
email_in_dn = no
nameopt = default_ca
certopt = default_ca
crlnumber = $dir/certs/yourcompany/crl/crlnumber
crl = $dir/certs/yourcompany/crl/crl.pem
crl_extensions = crl_ext
default_crl_days = 365
policy = policy_loose
[ policy_loose ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 4096 # Size of keys
default_keyfile = $dir/certs/yourcompany/intermediate/IntermediateCAkey.pem
default_md = sha256 # message digest
# the old default was md1 - change this]
algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
x509_extensions = v3_intermediate_ca
[ req_distinguished_name ]
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
0.organizationName_default = yourcompany
organizationalUnitName_default = yourcompany Intermediate Certification
emailAddress_default = info#yourcompany.com
localityName_default = Okeefenokee
stateOrProvinceName_default = Wisconsin [should be spelled out]
countryName_default = US
[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
# Important - the pathlen parameter prevents this cert from being used to create new intermediate certs. The subsequent subsections for server and client certs allows you to specify their type and intended usage, as distinct from the intermediate cert, in the same cfg file
[ usr_cert ]
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection
[ server_cert ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[ crl_ext ]
authorityKeyIdentifier = keyid:always
2. Transfer certificates to an iPad
Ref: how to register the app to open the pdf file in my app in ipad
Apple recommends registering a new file type handled by your app and transferring a p12 file renamed with the new custom extension to a device (manually or email) to install client certs. The p12 file should include the public cert chain as well as the client cert info as defined in step 1 above. When you try to open such a file, the device sends a start/wakeup to your app delegate which you need to handle (not in didload because it could be a wake).
This has changed a bit with v8 or 9 but I need to support 7 so this is for the deprecated handler. Same solution though and it starts with adding to the app plist file as shown in the screenshots below.
Note that you will need two new icons and file extension that is not likely to be claimed by another app
Next you need the delegate/handler which should be self-explanatory. Since this part has nothing to do with the normal control flow I am handling all the delegate processing in AppDelegate.m. (is this so wrong?) Set up the methods/variables as required and please ignore the paranoid extra check for file existence...
Ref: https://www.raywenderlich.com/6475/basic-security-in-ios-5-tutorial-part-1
- (BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
if (url) {
self.p12Data = [NSData dataWithContentsOfFile:[url path]];
if (!p12Data) {
[self messageBox:#"Warning" : #"Failed to read data file, cancelling certificate import"];
}
else {
[self presentAlertViewForPassPhrase];
}
NSFileManager * fileManager = [NSFileManager defaultManager];
if ( [fileManager fileExistsAtPath:[url path]] ) {
[fileManager removeItemAtPath:[url path] error:NULL];
}
}
return YES;
}
- (void)presentAlertViewForPassPhrase {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Certificate Credentials"
message:#"Please enter the passphrase for your certificate"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Done", nil];
[alert setAlertViewStyle:UIAlertViewStyleSecureTextInput];
[alert show];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) { // User selected "Done"
UITextField *ppField = [alertView textFieldAtIndex:0];
if ([ppField.text length] > 0) {
[self loadCertificates:ppField.text];
}
//Handle Else
}
else
{ // User selected "Cancel"
[self messageBox:#"Information" : #"Certificate import cancelled"];
}
}
3. Save received credentials in the app keychain
Now that you have the raw p12 data it should be simple to figure out what to do next...NOT. All the documentation seems to be for name/pwd storage and a scary number of posters suggest saving the server cert to the file system, which is ok but makes absolutely no sense when you have the keychain and Apple says that's what it's for. Last but not least how do you distinguish between stored certs and how do you update them?
Long story short, I decided to do a complete delete/resave after trying all kinds of things that do not work to check if it should be an update or an initial load - besides that's what I wanted to do in the first place since it's my app chain. All this is CF stuff and I'm not using ARC because I refuse to port anything I don't have to. As near as I can tell, as long as you allocate CF, cast to NS, and CFRelease after use there are no warnings.
These are key references:
Enumerate all Keychain items in my iOS application
[essential to help visualize what your keychain looks like]
How to delete all keychain items accessible to an app?
What makes a keychain item unique (in iOS)?
http://help.sap.com/saphelp_smp307sdk/helpdata/en/7c/03830b70061014a937d8267bb3f358/content.htm
[https://developer.apple.com/library/ios/samplecode/AdvancedURLConnections/Listings/Credentials_m.html, which says:
// IMPORTANT: SecCertificateRef's are not uniqued (that is, you can get two
// different SecCertificateRef values that described the same fundamental
// certificate in the keychain), nor can they be compared with CFEqual. So
// we match up certificates based on their data values.
The summary is that (duh) the easiest thing to do is to assign a label to the cert so you can look it up uniquely and realize that if you save an identity it will be split up into key and cert automatically, which may - not sure - have led to some difficulty with replacement.
The code (explanation follows):
- (void) loadCertificates:(NSString *)passPhrase {
BOOL lastError = false;
NSMutableDictionary * p12Options = [[NSMutableDictionary alloc] init];
[p12Options setObject:passPhrase forKey:(id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus err = SecPKCS12Import((CFDataRef)p12Data, (CFDictionaryRef)p12Options, &items);
if (err != noErr) {
[self messageBox:#"Error" : #"Unable to extract security information with the supplied credentials. Please retry"];
lastError = true;
}
if (!lastError && err == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
//Clean-up
NSArray *secItemClasses = [NSArray arrayWithObjects:
(id)kSecClassCertificate,
(id)kSecClassKey,
(id)kSecClassIdentity,
nil];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = #{(id)kSecClass: secItemClass};
err = SecItemDelete((CFDictionaryRef)spec);
}
//Client Identity & Certificate
SecIdentityRef clientIdentity = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
NSDictionary *addIdentityQuery = [NSDictionary dictionaryWithObjectsAndKeys:
kClientIdentityLabel, kSecAttrLabel,
(id)clientIdentity, kSecValueRef,
nil];
err = SecItemAdd((CFDictionaryRef)addIdentityQuery, NULL);
if (err == errSecDuplicateItem) {
NSLog(#"Duplicate identity");
}
if (err != noErr) {
[self messageBox:#"Warning" : #"Failed to save the new identity"];
lastError = true;
}
//Server Certificate
CFArrayRef chain = CFDictionaryGetValue(identityDict, kSecImportItemCertChain);
CFIndex N = CFArrayGetCount(chain);
BOOL brk = false;
for (CFIndex i=0; (i < N) && (brk == false); i++) {
SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(chain, i);
CFStringRef summary = SecCertificateCopySubjectSummary(cert);
NSString* strSummary = [[NSString alloc] initWithString:(NSString *)summary];
if ([strSummary containsString:#"Root"] || (i == N)) {
NSDictionary *addCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
kServerCertificateLabel, kSecAttrLabel,
(id)cert, kSecValueRef,
nil];
err = SecItemAdd((CFDictionaryRef)addCertQuery, NULL);
if (err == errSecDuplicateItem) {
NSLog(#"Duplicate root certificate");
}
if (err != noErr) {
[self messageBox:#"Warning" : #"Failed to save the new server certificate"];
lastError = true;
}
brk = true;
}
[strSummary release];
CFRelease(summary);
}
}
else {
[self messageBox:#"Error" : #"Unable to extract security information with the supplied credentials. Please retry"];
lastError = true;
}
[p12Options release];
CFRelease(items);
if (!lastError) [self messageBox:#"Information" : #"Certificate import succeeded"];
}
where kClientIdentityLabel and kServerCertificateLabel are arbitrary labels.
The kSec functions are too many/complex to explain here in detail. Suffice it to say that everything is cleared, then the extracted client identity is saved, followed by extraction of the root CA which is then saved separately. Why the loop? because I didn't know if it is technically correct to assume the root is at the end of the chain but it will be if I generate the p12 so the code is just there for now.
Note that errors from kSec are encoded so this site is indispensable: https://www.osstatus.com
4. Retrieve the saved credentials from the keychain
Once the credentials are in the keychain you can extract them thusly (the failure modes leave something to be desired):
- (void) reloadCredentials {
self.clientCredential = nil;
self.serverCertificateData = nil;
if (self.useClientCertificateIfPresent) {
NSDictionary* idQuery = [NSDictionary dictionaryWithObjectsAndKeys:
kClientIdentityLabel, kSecAttrLabel,
(id)kSecClassIdentity, kSecClass,
kCFBooleanTrue, kSecReturnRef,
kSecMatchLimitAll, kSecMatchLimit,
nil];
CFArrayRef result = nil;
OSStatus err = SecItemCopyMatching((CFDictionaryRef)idQuery, (CFTypeRef*)&result);
if (err == errSecItemNotFound) {
[self messageBox:#"Warning" : #"Client credentials not found. Server connection may fail"];
}
else if (err == noErr && result != nil ) {
SecIdentityRef clientIdentity = (SecIdentityRef)CFArrayGetValueAtIndex(result, 0);
SecCertificateRef clientCertificate;
SecIdentityCopyCertificate(clientIdentity, &clientCertificate);
const void *certs[] = { clientCertificate };
CFArrayRef certsArray = CFArrayCreate(NULL, certs, 1, NULL);
self.clientCredential = [NSURLCredential credentialWithIdentity:clientIdentity certificates:(NSArray*)certsArray
persistence:NSURLCredentialPersistenceNone];
CFRelease(certsArray);
CFRelease(clientCertificate);
CFRelease(result);
}
else {
[self messageBox:#"Warning" : #"Client or Server credentials not found. Server connection may fail"];
}
NSDictionary* serverCertQuery = [NSDictionary dictionaryWithObjectsAndKeys:
kServerCertificateLabel, kSecAttrLabel,
(id)kSecClassCertificate, kSecClass,
kCFBooleanTrue, kSecReturnRef,
kSecMatchLimitAll, kSecMatchLimit,
nil];
CFArrayRef result1 = nil;
err = SecItemCopyMatching((CFDictionaryRef)serverCertQuery, (CFTypeRef*)&result1);
if (err == errSecItemNotFound) {
[self messageBox:#"Warning" : #"Server certificate not found. Server connection may fail"];
}
else if (err == noErr && result1 != nil ) {
SecCertificateRef certRef = (SecCertificateRef)CFArrayGetValueAtIndex(result1, 0);
CFDataRef certRefData = SecCertificateCopyData(certRef);
self.serverCertificateData = (NSData *)certRefData;
CFRelease(certRefData);
CFRelease(result1);
}
else {
[self messageBox:#"Warning" : #"Client or Server credentials not found. Server connection may fail"];
}
}
}
5. Authenticate server certificate and return client credentials
Hoo boy. This is an edit to explain how to actually use the retrieved certificates (it was supposed to be the easy obvious part...)
First, Apple's already dubious documentation is obsoleted by the new Application Transport Security framework (see for instance: http://useyourloaf.com/blog/app-transport-security/). I'm not going to get into it here but the idea is to force everyone to always use https and trusted certs by default. For my scenario, with certificate pinning and mutual authentication between dedicated clients and a private server you can safely turn this feature off by adding a dictionary to your plist as follows:
Next, in Step 4, you already had the client credential to respond to that challenge immediately when it hits but the server cert is floating around as NSData in DER format as created by SecCertificateCopyData and it is not clear what should happen when that challenge arrives.
Turns out that what you are supposed to do is implement the algorithm in section 6 of the "X.509 standard" (https://www.rfc-editor.org/rfc/rfc5280). Luckily, this is implemented behind the scenes by the iOS SecTrustEvaluate function but there is scaffolding to build and weird stuff to understand.
[Slight problem - ran out of space!! Added a new question, including the end of this step.]
https://stackoverflow.com/questions/35964632/correctly-use-a-pinned-self-signed-certificate-in-ios-9-2
[Continuing from other post]
So that's it. Sorry about the not quite production quality but I wanted to slap this together while it was still fresh in my mind. I will update the post if I find errors.
Hope this helps and here's a final link to a very good book that, amongst other things, will give you the creeps about trusting commercial CAs...
https://www.cs.auckland.ac.nz/~pgut001/pubs/book.pdf
[I just(!) realized I can add another answer since the continuation link was down-voted and closed and there are two requests for the additional information that wouldn't fit above. The answer below starts with the question in the deleted post]
...The part that I am still not clear about is why I had to create a new trust and policy to implement an anchor certificate and pinning.
If I simply added the anchor to the trust received from the server I could not successfully return the pointer to the NSURLCredential received from the server, it seemed to get modified and rejected by the sender(?).
The question is, is this really the appropriate processing or can it be condensed? This is getting a bit tiresome but I don't want to accept something just because it "works". My current solution is shown below.
In Step 4, you already had the client credential to respond to that type of challenge without manipulation but the server cert is floating around as NSData in DER format as created by SecCertificateCopyData and it is not clear what should happen when that challenge arrives.
Turns out that what you are supposed to do is implement the algorithm in section 6 of the "X.509 standard" (https://www.rfc-editor.org/rfc/rfc5280). Luckily, this is implemented behind the scenes by the iOS SecTrustEvaluate function but there is scaffolding to build and weird stuff to understand. First the code (following a hat tip to my original source):
SecTrustEvaluate always returns kSecTrustResultRecoverableTrustFailure with SecPolicyCreateSSL
- (void)_handleServerTrustChallenge {
OSStatus status;
BOOL trusted = false;
SecTrustResultType trustResult;
SecTrustRef serverTrust = self.challenge.protectionSpace.serverTrust;
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; //candidate credential for return to sender if valid
if (svDelegate.serverCertificateData) {
//locally stored information
SecCertificateRef storedCertificate = SecCertificateCreateWithData(NULL, (CFDataRef)svDelegate.serverCertificateData);
NSMutableArray *anchorCertArray = [NSMutableArray arrayWithCapacity:1];
[anchorCertArray addObject:(id)storedCertificate];
//incoming credentials from server
NSMutableArray *receivedCertChain = [NSMutableArray array];
for(int i = 0; i < SecTrustGetCertificateCount(serverTrust); i++)
[receivedCertChain addObject:(id) SecTrustGetCertificateAtIndex(serverTrust,i))];
//new custom policy object to use in creating new trust
//YES indicates extendedKeyUsage is set to serverAuth; effectively ignore server name check by specifying incoming name
SecPolicyRef newPolicyRef = SecPolicyCreateSSL(YES, (CFStringRef)self.challenge.protectionSpace.host);
//create and evaluate new trust with pinned certificate
SecTrustRef newTrustRef = NULL;
SecTrustCreateWithCertificates((CFArrayRef) receivedCertChain, newPolicyRef, &newTrustRef);
status = SecTrustSetAnchorCertificates(newTrustRef, (CFArrayRef) anchorCertArray);
if (status == noErr) status = SecTrustSetAnchorCertificatesOnly(newTrustRef, TRUE);
if (status == noErr) status = SecTrustEvaluate(newTrustRef, &trustResult);
//----- debug -------
//CFShow(newPolicyRef);
//NSLog(#"%#", receivedCertChain);
CFRelease(newTrustRef);
CFRelease(newPolicyRef);
CFRelease(storedCertificate);
}
else { //Server certificate not stored, rely on standard trusted Root CA authorities
status = SecTrustEvaluate(serverTrust, &trustResult);
}
trusted = (status == noErr) && (trustResult == kSecTrustResultUnspecified);
if (!trusted) credential = nil;
[self stopWithCredential:credential];
[self.delegate challengeHandlerDidFinish:self];
}
So first I check whether the server cert was loaded (else process via conventional trusted CA method).
Next you select the "trust object" to evaluate. I failed to do this without making a working copy of the trust object I received over the wire from the server b/c it somehow messed up the 'NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]' reference if I used it directly. It appears though from the truly horrible Apple docs that this is a kosher approach (I, for one, recommend skimming the x.509 rfc if you want to understand any of it).
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
[https://developer.apple.com/library/ios/technotes/tn2232/_index.html#//apple_ref/doc/uid/DTS40012884-CH1-SECCUSTOMROOT][2]
The trust requires a 'policy', the incoming certificate chain to evaluate, and one or more 'anchor certificates' which basically defines the origin in an arbitrary coordinate system - everything is validated downstream of the zero point even if it is not a root cert.
So you load the incoming chain and the stored cert into arrays to be provided to the new trust and create a new policy with SecPolicyCreateSSL - this sets a flag to indicate that the certificate should be checked to have been issued for serverAuth and that the incoming server name should be ignored (to allow some infrastructure flexibility).
Next you create the new trust using the new policy and the certificate array to be authenticated. Then you set the anchor and ensure that the chain will only be evaluated against your anchor cert, not just anything in the iOS keychain.
When you evaluate the trust it may seem odd that you accept kSecTrustResultUnspecified and not proceed or something more positive-sounding. In fact, proceed means you are following overrides from the user interface so that is actually bad; unspecified just means there's nothing wrong per the specified policy.
Finally, you return the credential from the incoming trust object (not the new one) and everything should be golden...

How to authenticate Game Center User from 3rd party node.js server

I've been trying to get the new iOS Game Center GKPlayer method, generateIdentityVerificationSignatureWithCompletionHandler, working so we can securely rely on the Game Center credentials for authentication. We're using Node.js as the backend server, and I've been trying to verify the signature but to no avail.
Here is the code on the server side that I have - if there's anyone who can chime in on what's missing, that'd be appreciated. The question has been answered somewhat here: How to authenticate the GKLocalPlayer on my 'third party server'?, but Node.js hasn't specifically been tackled. Note that the code below doesn't ensures the validity of the certificate with a signing authority (yet).
//Client sends the payload below
//json.playerId - UTF-8 string
//json.bundleId - UTF-8 string
//json.timestamp - Hex string
//json.salt - base64 encoded
//json.publicKeyURL - UTF-8 string
//json.signature - base64 encoded
var json = JSON.parse(req.body);
console.log(JSON.stringify(json));
//get the certificate
getCertificate(json.publicKeyURL, function(cert){
//read file from fs for now, since getCertificate returns cert in DER format
fs = require('fs');
fs.readFile('/gc-sb.pem', 'utf8', function (err,data) {
if (err) {
console.log(err);
} else {
console.log(data);
var verifier = crypto.createVerify("sha1WithRSAEncryption");
verifier.write(json.playerId, "utf8");
verifier.write(json.bundleId, "utf8");
verifier.write(json.hexTimestamp, "hex");
verifier.write(json.salt, "base64");
var isValid = verifier.verify(data, json.signature, "base64");
console.log("isvalid: " + isValid);
}
});
});
One thing I've found using the crypto module in node.js is that it seems to want the certificate in PEM format, and I believe the format retrieved from Apple is DER. Until I figure out how to convert the DER file to PEM, I've temporarily converted it using
openssl x509 -in gc-sb.cer -inform der -outform pem -out gc-sb.pem
The main thing for me is being able to validate the signature first. Conversion of the certificate and verifying it against a signing authority will come later :)
EDIT: I've figured it out - I was hashing the playerId, bundleId, timestamp and salt, and then using the hashed value as information to verify. I needed to just put those pieces of information into the verifier to verify without the SHA-1 hash (since the verifier will be taking care of it). I've modified the code above to "make it work". Hope this helps anyone that comes across this.
Here is how you can validate gamecenter identity using nodejs. It convert also the der certificate format to pem on the fly.
var crypto = require('crypto');
var request = require('request');
var ref = require('ref');
var token = require('./test.json');
request({url: token.publicKeyURL, encoding: null}, function (error, response, body) {
if (!error && response.statusCode == 200) {
var verifier = crypto.createVerify("sha1");
verifier.update(token.playerId, "utf8");
verifier.update(token.bundleId, "utf8");
var buf = ref.alloc('uint64');
ref.writeUInt64BE(buf, 0, token.timestamp.toString());
verifier.update(buf);
verifier.update(token.salt, 'base64');
var pmd = '-----BEGIN CERTIFICATE-----';
var base64 = body.toString('base64');
var size = base64.length;
for (var i = 0; i < size; i = i + 64) {
var end = i + 64 < size ? i + 64 : size;
pmd = pmd + '\n' + base64.substring(i, end);
}
pmd = pmd + '\n-----END CERTIFICATE-----';
var valid = verifier.verify(pmd, token.signature, "base64");
console.log(valid);
}
});
It's seems there's a npm package for it.
https://github.com/maeltm/node-gamecenter-identity-verifier

Apple Push Notifications Using Moon-APNS or APNS-Sharp

I am having really hard time figuring out how to send messages from my server to the APNS. I have used Moon-APNS and APNS-Sharp and I am stuck at the same error which is "parameter is incorrect". I generated the p12 file using KeyChain. I dragged the file into my Win 7 virtual environment and put it inside the bin\debug folder. Here is the code for Moon-APNS:
static void Main(string[] args)
{
var deviceToken = "21212d6fefebde4d317cab41afff65631b5a4d47e5d85da305ec610b4013e616";
var payload = new NotificationPayload(deviceToken, "hello world");
var notificationList = new List<NotificationPayload>() { payload };
var push = new PushNotification(true, "PushNotificationTest.p12", "pushchat");
var result = push.SendToApple(notificationList);
Console.WriteLine("Hello World");
}
Anyone has ideas?
I think this will help you :
OSX Keychain
After you've created the appropriate Push Notification Certificate in iPhone Developer Program Portal you should have downloaded a file named something like apn_developer_identity.cer. If you have not done so already, you should open/import this file into Keychain, into your login section.
Finally, if you filter Keychain to show your login container's Certificates, you should see your Certificate listed. Expand the certificate, and there should be a Key underneath/attached to it.
Right Click or Ctrl+Click on the appropriate Certificate and choose Export. Keychain will ask you to choose a password to export to. Pick one and remember it. You should end up with a .p12 file. You will need this file and the password you picked to use the Notification and Feedback Libraries here.
OpenSSL
Here is how to create a PKCS12 format file using open ssl, you will need your developer private key (which can be exported from the keychain) and the CertificateSigningRequest??.certSigningRequest
1. Convert apn_developer_identity.cer (der format) to pem:
openssl x509 -in apn_developer_identity.cer -inform DER -out apn_developer_identity.pem -outform PEM}
2. Next, Convert p12 private key to pem (requires the input of a minimum 4 char password):
openssl pkcs12 -nocerts -out private_dev_key.pem -in private_dev_key.p12
3. (Optional): If you want to remove password from the private key:
openssl rsa -out private_key_noenc.pem -in private_key.pem
4. Take the certificate and the key (with or without password) and create a PKCS#12 format file:
openssl pkcs12 -export -in apn_developer_identity.pem -inkey private_key_noenc.pem -certfile CertificateSigningRequest??.certSigningRequest -name "apn_developer_identity" -out apn_developer_identity.p12
I found Moon-APNS easier to use and configure on my app.
Solved the problem by following the link:
http://code.google.com/p/apns-sharp/wiki/HowToCreatePKCS12Certificate
and then generating the .p12 file using the openssl approach.
Have you tried using APNS-Sharp example project to send notifications? I am using similar code, it works just fine...this is what one of my methods looks like
public void SendToSome(List<string> tokens)
{
//Variables you may need to edit:
//---------------------------------
bool sandbox = true;
//Put your device token in here
//Put your PKCS12 .p12 or .pfx filename here.
// Assumes it is in the same directory as your app
string p12File = "Certificates.p12";
//This is the password that you protected your p12File
// If you did not use a password, set it as null or an empty string
string p12FilePassword = "";
//Number of milliseconds to wait in between sending notifications in the loop
// This is just to demonstrate that the APNS connection stays alive between messages
// int sleepBetweenNotifications = 15000;
//Actual Code starts below:
//--------------------------------
string p12Filename = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, p12File);
NotificationService service = new NotificationService(sandbox, p12Filename, p12FilePassword, 1);
service.SendRetries = 5; //5 retries before generating notificationfailed event
service.ReconnectDelay = 5000; //5 seconds
service.Error += new NotificationService.OnError(service_Error);
service.NotificationTooLong += new NotificationService.OnNotificationTooLong(service_NotificationTooLong);
service.BadDeviceToken += new NotificationService.OnBadDeviceToken(service_BadDeviceToken);
service.NotificationFailed += new NotificationService.OnNotificationFailed(service_NotificationFailed);
service.NotificationSuccess += new NotificationService.OnNotificationSuccess(service_NotificationSuccess);
service.Connecting += new NotificationService.OnConnecting(service_Connecting);
service.Connected += new NotificationService.OnConnected(service_Connected);
service.Disconnected += new NotificationService.OnDisconnected(service_Disconnected);
//The notifications will be sent like this:
// Testing: 1...
// Testing: 2...
// Testing: 3...
// etc...
for (int i = 0; i < tokens.Count; i++)
{
//Create a new notification to send
Notification alertNotification = new Notification();
alertNotification.DeviceToken = tokens[i];
alertNotification.Payload.Alert.Body = Text;
alertNotification.Payload.Sound = "default";
alertNotification.Payload.Badge = 1;
//Queue the notification to be sent
if (service.QueueNotification(alertNotification))
Console.WriteLine("Notification Queued!");
else
Console.WriteLine("Notification Failed to be Queued!");
//Sleep in between each message
if (i < tokens.Count)
{
// Console.WriteLine("Sleeping " + sleepBetweenNotifications + " milliseconds before next Notification...");
// System.Threading.Thread.Sleep(sleepBetweenNotifications);
}
}
Console.WriteLine("Cleaning Up...");
//First, close the service.
//This ensures any queued notifications get sent befor the connections are closed
service.Close();
//Clean up
service.Dispose();
}

Resources