Sandbox apple pay testing handshake failure - ios

I am having trouble validating the merchant in my apple pay sandbox environment. Taken from https://developer.apple.com/reference/applepayjs/applepaysession#2166532, once my server then calls the Start Session endpoint at the provided URL, I get a 500 error.
I've dug around and this 500 error is happening somewhere in the network layer. As listed on the apple page (https://developer.apple.com/reference/applepayjs/), I need the following requirements met:
All pages that include Apple Pay must be served over HTTPS. done, server has ssl/https sitewide
To enable merchant validation, your server must allow access over HTTPS (TCP over port 443) to the Apple Pay IP addresses provided in Listing 1 below. done, server is open to all ips on port 443
Your server must support the Transport Layer Security (TLS) 1.2 protocol and one of the cipher suites listed in Table 1. server does support tls 1.2, since I send requests on tls 1.2 to apple pay's development server (below)
I've been using Wireshark to check what's going on, and I seem to be failing once the server is in the ChangeCipherSpec phase, after the server sends back the cipher spec to the client. (Reference for ssl procedure: https://support.f5.com/csp/article/K15292). As you can see from my image, I'm communicating to the apple pay sandbox server, passing in the same supported tls protocol and cipher suite that the error would suggest -> Handshake Failure (40), so something else is going on and I don't know where to look
If you look at the ServerHello message, you can see the server found and accepted the cipher suite that matches the client, which also matches one of the required ciphers that apple pay supports
I can add other details as necessary

The issue was that our server did not have TLS 1.2 enabled by default. Enabling TLS 1.2 and disabling TLS 1.0 fixed the issue - Win 2008
edit
There are a few things that needed to happen. Our server was on .net 4.5, which does not use tls 1.2 by default (apple requires tls 1.2 to be used). So, we upgraded our solution to .net 4.6, and also forced tls 1.2 for our request. Additionally, we have to include the merchant id certificate in our request to apple (which isn't mentioned in the docs very well).
You can find the github repo of the source I used here (https://github.com/justeat/ApplePayJSSample), but here is my code that I needed to put in my solution to make things work (I also had to export my merchant certificate from my mac's keychain that gave me a .p12 file. I imported this .p12 file into my server's computer certificate store)
[System.Web.Http.HttpPost]
public async Task<ContentResult> GetApplePaySession([FromBody] string url)
{
// http://stackoverflow.com/a/36912392/1837080
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
// Load the merchant certificate for two-way TLS authentication with the Apple Pay server.
var certificate = LoadMerchantCertificate();
// Get the merchant identifier from the certificate to send in the validation payload.
var merchantIdentifier = GetMerchantIdentifier(certificate);
// Create the JSON payload to POST to the Apple Pay merchant validation URL.
var payload = new ApplePayRequest()
{
merchantIdentifier = merchantIdentifier,
domainName = System.Web.HttpContext.Current.Request.Url.Host,
displayName = "[display name from apple developer portal]"
};
JObject merchantSession;
// Create an HTTP client with the merchant certificate
// for two-way TLS authentication over HTTPS.
using (var httpClient = CreateHttpClient(certificate))
{
var jsonPayload = JsonConvert.SerializeObject(payload);
using (var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"))
{
// POST the data to create a valid Apple Pay merchant session.
using (var response = await httpClient.PostAsync(url, content))
{
response.EnsureSuccessStatusCode();
// Read the opaque merchant session JSON from the response body.
var merchantSessionJson = await response.Content.ReadAsStringAsync();
merchantSession = JObject.Parse(merchantSessionJson);
}
}
}
// Return the merchant session as JSON.
return Content(merchantSession.ToString(), "application/json");
}
#region Apple Pay helper methods
private X509Certificate2 LoadMerchantCertificate()
{
X509Certificate2 certificate;
// Load the certificate from the current user's certificate store. This
// is useful if you do not want to publish the merchant certificate with
// your application, but it is also required to be able to use an X.509
// certificate with a private key if the user profile is not available,
// such as when using IIS hosting in an environment such as Microsoft Azure.
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
// when using thumbprint from mmc, look at:
// http://stackoverflow.com/a/14852713
// there is a hidden character that you must delete
var certificates = store.Certificates.Find(
X509FindType.FindByThumbprint,
"[thumbprint]",
validOnly: false);
if (certificates.Count < 1)
{
throw new InvalidOperationException(
// ReSharper disable once UseStringInterpolation
string.Format(
"Could not find Apple Pay merchant certificate with thumbprint '{0}' from store '{1}' in location '{2}'.",
"‎[thumpprint]", store.Name, store.Location));
}
certificate = certificates[0];
}
return certificate;
}
private string GetMerchantIdentifier(X509Certificate2 certificate)
{
// This OID returns the ASN.1 encoded merchant identifier
var extension = certificate.Extensions["1.2.840.113635.100.6.32"];
// Convert the raw ASN.1 data to a string containing the ID
return extension == null ? string.Empty : Encoding.ASCII.GetString(extension.RawData).Substring(2);
}
private HttpClient CreateHttpClient(X509Certificate2 certificate)
{
var handler = new WebRequestHandler();
handler.ClientCertificates.Add(certificate);
return new HttpClient(handler, disposeHandler: true);
}
#endregion

I just recently went through this. For me I had to merge the PEM and KEY files into a PFX. Then I was able to run the start session call from ubuntu 16.04 using .net core 2.1
private HttpClient CreateHttpClient()
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.SslProtocols = SslProtocols.Tls12;
handler.ClientCertificates.Add(new X509Certificate2(#"/path/yourcombinedpfx.pfx"));
return new HttpClient(handler); ;
}

Related

How to verify X.509 certificate was signed by another certificate?

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.

How to implement server-side sever name indication using Security.framework under OS X/iOS

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
}

APN Production certificate not being recognized by PushSharp

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.

certificate at provider for push notification in iOS

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");
}
}

how to remove KeyStore security prompt

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/

Resources