The story: I call a request where I am getting a JWS token which I parse with the JOSESwift library. In the response I have a x5u parameter, which is a URL pointing to a certificate, which was used to sign the payload. I download the certificate and using JOSESwift I verify that. With some simplification I am doing the following:
let serverCert = // Getting the downloaded certificate as a SecCertificate object
var publicKey: SecKey!
publicKey = SecCertificateCopyKey(serverCert)
let rsaVerifier = RSAVerifier(algorithm: .ES256, publicKey: publicKey)
if let headerVerifier = Verifier(verifyingAlgorithm: .RS256, publicKey: rsaVerifier.publicKey) {
do {
_ = try jws.validate(using: headerVerifier)
print("Verifying success")
} catch {
print("Verifying failed with error:", error)
}
}
Question1: This is working nice so far. Now I want to verify that the downloaded certificate was indeed signed by a specific certificate I store locally in my application. And that's where I am stuck, that I am unable to find out how it could be done.
Question2: A requirement for the functionality is that the locally stored certificate can be self-signed, but also it can be some other certificate from a chain. So basically I should trust the locally stored certificate regardless it is self-signed or not. Is it doable, or when verifying we should always know about the root certificate ?
[rootCert] <--signed by-- [localCert] <--signed by-- [receivedCert] // We have only the local and receivedCert
My thoughts/ What I tried: My first thought was to use a SecTrust object, with setting the locally stored certificate as the trust anchor, and use the SecTrustEvaluateWithError(_:_:) to check if it's is a correct chain(what I conclude eventually will check also if the received certificate was signed by the one I store locally). I have seen working this in SSLPinning implementations, but somehow it is returning me false, even though I double checked that the certificates are correct. Here is what I am doing:
let serverCert = // Getting my server cert as a SecCertificate object
let localCert = // Getting my server cert as a SecCertificate object
let policy = SecPolicyCreateBasicX509()
var optionalTrust: SecTrust?
let status = SecTrustCreateWithCertificates([serverCert] as AnyObject,
policy,
&optionalTrust)
guard status == errSecSuccess else { return }
let trust = optionalTrust! // Safe to force unwrap now
SecTrustSetAnchorCertificates(trust, [localCert] as CFArray)
var error: CFError?
print("Veryfing result: ", SecTrustEvaluateWithError(trust, &error))
print(error)
Is it a good approach to verify that the received certification was signed by the local one ? If yes than what I am doing wrong that I am getting false ? If the approach is bad, what else can I try here ?
I found a similar question, but it is in Java(but maybe can give a hint what I am trying to accomplish): Verify x509 signature Java. However it also mentions the root certificate, so it again rises me the Question2 if it's doable without the root/self-signed certificate.
Update
As suggested in the comments, I am printing the error I am getting from SecTrustEvaluateWithError:
Error Domain=NSOSStatusErrorDomain Code=-25318 "“AuthInfoKey:18”
certificate is not trusted"
UserInfo={NSLocalizedDescription=“AuthInfoKey:18” certificate is not
trusted, NSUnderlyingError=0x280efb9c0 {Error
Domain=NSOSStatusErrorDomain Code=-25318 "Certificate 0
“AuthInfoKey:18” has errors: Unable to build chain to root (possible
missing intermediate);" UserInfo={NSLocalizedDescription=Certificate 0
“AuthInfoKey:18” has errors: Unable to build chain to root (possible
missing intermediate);}}}
The issues seems reasonable as I do not have access to the root certificate as mentioned above, I want to verify an intermediate certificate.
So again my question is: it possible to check the serverCert(an intermediate certificate) was signed by my local certificate(an other intermediate certificate) with SecTrustEvaluateWithError ? For me it seems it is working only if one has also the root certificate.
I am trying to implement server name indication on the server side of a OS X application: The server should pick a certificate based on the peer host name provided by the client.
Does anybody know whether this can be achieved using the Security.framework?
SSLSetCertificate takes only one leaf certificate and I cannot find any callback for providing a certificate based on a host name.
In openSSL for example, there is the SSL_CTX_set_tlsext_servername_callback for this purpose.
Any help is greatly appreciated.
There is a new feature in OS X 10.11 (El Capitan) which makes this possible. Sadly, there is currently zero documentation on this feature, but I nevertheless found out how it works:
You have to enable the new option kSSLSessionOptionBreakOnClientHello on your SSL context with:
SSLSetSessionOption(context, kSSLSessionOptionBreakOnClientHello, YES);
This causes the handshake to break after is has received the hostname from the client and it returns the status errSSLClientHelloReceived. You can then get the hostname, use it to look up the appropriate certificate and apply the certificate to the context. Then you can continue the handshake.
OSStatus status = SSLHandshake(context);
// ...
if(status == errSSLClientHelloReceived)
{
size_t hostnameLength;
SSLGetPeerDomainNameLength(context, &hostnameLength);
char hostname[hostnameLength];
SSLGetPeerDomainName(context, hostname, &hostnameLength);
SecIdentityRef cert = ... ; // Look up certificate using hostname
SSLSetCertificate(context, (__bridge CFArrayRef)#[(__bridge id) cert]);
// Repeat from start by calling SSLHandshake
}
I've developed an iOS app, that receives Push Notifications. I'm sending them from a .NET environment using PushSharp. Everything went beautifully while developing, and the Pushs were successfully sent:
var push = new PushBroker();
var appleCert = File.ReadAllBytes(#"Utils\Cert.Development.p12");
push.RegisterAppleService(new ApplePushChannelSettings(false, appleCert, "*******"));
push.QueueNotification(new AppleNotification()
.ForDeviceToken(token)
.WithContentAvailable(1)
);
push.StopAllServices();
Now, the app has been approved, and it's at AppStore. I have generate the correct production certificate:
var push = new PushBroker();
var appleCert = File.ReadAllBytes(#"Utils\Cert.Production.p12");
push.RegisterAppleService(new ApplePushChannelSettings(true, appleCert, "*******"));
push.QueueNotification(new AppleNotification()
.ForDeviceToken(token)
.WithContentAvailable(1)
);
push.StopAllServices();
but when I try to send the push from PushSharp, it throws the following exception:
You have selected the Production server, yet your Certificate does not appear to be the Production certificate! Please check to ensure you have the correct certificate!
I'm pretty sure I've followed all the steps. I've downloaded the production certificate that was binded with the provisioning file set in the app to publish. Opened it and exported the .p12.
I'm also sure I'm not using a development one by accident, because, if I set PushSharp for development, using this last certificate, it throws the following error:
You have selected the Development/Sandbox (Not production) server, yet your Certificate does not appear to be the Development/Sandbox (Not production) certificate! Please check to ensure you have the correct certificate!
How can the certificate be neither Development, nor Production?
Is there somewhere I can validate the file? Please give me some insigth on this matter, as I have no clue where to start
Apple has changed the name. Please go to ApplePushChannelSettings.cs and change the name as below.
From
if (production && !subjectName.Contains("Apple Production IOS Push Services"))
To
if (production && !subjectName.Contains("Apple Push Services"))
I need to do this when I renewing my expired cert yesterday. Change the name, rebuild it and upload to server, then it's working again.
Apple has introduced a new universal push certificate that enables connection to both the APNs Production and Development environments.That's why the production certificate common name has been changed from Apple Production IOS Push Services to Apple Push Services.
You should change the code on the provider push server to be compatible with the new common name.
When you create production certificate (.p12) for .NET, Always export like selecting the certificate only. see the attached image
http://davidbits.blogspot.in/2016/02/error-you-have-selected-production.html
Issue was in PushSharp Library just update it to Version .3 this is becuase apple has changed Push Certificate Name From (Apple Production Push Service) to (Apple Push Service)
and pushSharp check the name of Certificate :
(production && !subjectName.Contains("Apple Push Services")).
Error: You have selected the Production server, yet your Certificate
does not appear to be the Production certificate! Please check to
ensure you have the correct certificate!
Solution: (production && !subjectName.Contains("Apple Push Services"))
Invoke Following snippet Send Device Token & Message
public void PendingNotification(string DeviceToken,string message)
{
try
{
int port = 2195;
//Developer
String hostname = "gateway.sandbox.push.apple.com";
//Production
//String hostname = "gateway.push.apple.com";
String certificatePassword = "XXXXXX";
string certificatePath = Server.MapPath("~/Cert.p12");
TcpClient client = new TcpClient(hostname, port);
X509Certificate2 clientCertificate = new X509Certificate2(System.IO.File.ReadAllBytes(certificatePath), certificatePassword);
X509Certificate2Collection certificatesCollection = new X509Certificate2Collection(clientCertificate);
SslStream sslStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
sslStream.AuthenticateAsClient(hostname, certificatesCollection, SslProtocols.Tls, false);
//String DeviceToken = "a5062b62aacbe6a499e02351c3f233ce87004574ff01965dff5f6bb8f15cae13";
String LoginName = "Name";
int Counter = 1; //Badge Count;
String Message = message;
String UID = "your choice UID";
string payload = "{\"aps\":{\"alert\":\"" + Message + "\",\"badge\":" + Counter + ",\"sound\":\"default\"},\"UID\":\"" + UID + "\",\"LoginName\":\"" + LoginName + "\"}";
MemoryStream memoryStream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(memoryStream);
writer.Write((byte)0);
writer.Write((byte)0);
writer.Write((byte)32);
writer.Write(HexStringToByteArray(DeviceToken.ToUpper()));
writer.Write((byte)0);
writer.Write((byte)payload.Length);
byte[] b1 = System.Text.Encoding.UTF8.GetBytes(payload);
writer.Write(b1);
writer.Flush();
byte[] array = memoryStream.ToArray();
sslStream.Write(array);
}
catch (Exception ex)
{
//Response.Write(ex.Message);
}
}
public static byte[] HexStringToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}
Follow the steps in the following link to generate Production SSL Certificate:
How To Create APNS Certificate
If that didn't work,double check on your code,make sure you're reading from the correct certificate file, and you're passing True to ApplePushChannelSettings
I'd recommend using something from the 3.0 nuget preview releases. This issue is fixed in these releases and will not be backported to 2.x.
PushSharp 2.x > Push.Apple > ApplePushChannelSettings.cs
On ApplePushChannelSettings.cs find DetectProduction() and CheckProductionCertificateMatching() and replace '.Contains("Apple Production IOS Push Services")' by '.Contains("Apple Push Services")'
It occurs because apple changed the certificate name from "Apple Production IOS Push Services" to "Apple Push Services", the PushSharp identify the type(production/development) by the certificate name.
I need to implement iOS push notification in my app. I ve been trying this for a while and have done the iOS side code changes required. I'm able to get the device token from APNS. But im stuck with the provider side implementation.
I'm using a java provider and using JAVAPNS library to implement the provider side logic. What puzzles me is the certificate installation and stuff required at the provider.
My provider runs on unix/windows machine and I am not sure how to proceed with the SSL certificate installation here.
I have obtained SSL certificate for push notification from the apple developer site. But how do I proceed with the p12 file?
I do not find many explanations on this on the net though there are a great deal of discussions and tutorial about the iOS side implementation of push notification.
The SSL certificate is used to establish the socket connection to gateway.push.apple.com from your server side code. Here is some Ruby code as an example:
APN_SSL_KEY_FILE = 'lib/SSLCert_Private_Key.pem'
APN_SSL_HOST = 'gateway.push.apple.com'
APN_SSL_PORT = 2195
APN_SSL_PASSWORD = '<password>'
def configure_apn_cert
##apn_cert = File.read(File.join(RAILS_ROOT, APN_SSL_KEY_FILE))
##apn_context = OpenSSL::SSL::SSLContext.new
##apn_context.key = OpenSSL::PKey::RSA.new(##apn_cert, APN_SSL_PASSWORD)
##apn_context.cert = OpenSSL::X509::Certificate.new(##apn_cert)
end
def create_and_configure_apn_server
configure_apn_cert if not ##apn_cert
puts "APN Service: Configuring APN SOCKET and SSL connection"
#apn_socket = TCPSocket.new(APN_SSL_HOST, APN_SSL_PORT)
#apn_ssl = OpenSSL::SSL::SSLSocket.new(#apn_socket, ##apn_context)
#apn_ssl.sync = true
#apn_ssl.connect
#apn_is_active = false; # reopen the TCP/SSL sockets for now
end
You get a .pem file with:
$ openssl pkcs12 -in myfile.p12 -out myfile.pem
If you're using JavaPNS, it's as simple as :
import javapns.Push;
public class PushTest {
public static void main(String[] args) {
Push.alert("Hello World!", "keystore.p12", "keystore_password", false, "Your token");
}
}
i am trying to develop an app which tries to call a https(godaddy ssl) url.
i have successfully installed certificate in key store using this code.
public void addCertToDeviceKeyStore(Certificate certificate) {
KeyStore keyStore = DeviceKeyStore.getInstance();
// check if certificate is not already in the DeviceKeyStore
if (!keyStore.isMember(certificate)) {
try {
String SFN = certificate.getSubjectFriendlyName();
CertificateStatus CS = certificate.getStatus();
keyStore.set(null, SFN, certificate, CS, keyStore.getTicket());
} catch (Exception e) {
}
}
}
after adding certificate also
why i am getting security prompt for keystore password and for trusting the connection?
is there any way to avoid these security prompts?
You can avoid this by making your device find the root certificate. There is a problem with godaddy certificate where the BlackBerry can't find the root.
You need to edit the .htaccess file to point to the root certificate.
http://help.godaddy.com/topic/742/article/5238
Here is an example of solution, with the same kind of problem but on another platform.
http://blog.boxedice.com/2009/05/11/godaddy-ssl-certificates-and-cannot-verify-identity-on-macsafari/