How does OkHttp handle non-matching domains with cert pinning? - okhttp

Let's say I pin a cert for a specific domain:
CertificatePinner certPinner = new CertificatePinner.Builder()
.add("*.domain1.com", CertificatePinner.pin(...))
.build()
OkHttpClient client = new OkHttpClient.Builder()
.certificatePinner(certPinner)
.build
How does OkHttp handle requests to other domains (e.g. api.domain2.com)?
"No matching cert, no bueno --> SecurityException"
"No matching cert, no problemo ++ --> completes request"
++ provided domain2's cert is still signed by C.A. listed in OS-level list of trusted authorities.

"No matching cert, no problemo ++ --> completes request"
Certificate pinning adds a constraint for matching hosts only.

Related

why ruby get the cert trust chain is different from gnutls-cli

I found a strange phenomenon when using the command gnutls-cli and ruby code to test the cert pinning of the website. Sometimes the number of certificate trust chains obtained by the two methods is different.
commandline gnutls-cli github-cloud.s3.amazonaws.com will get 4:
(I removed some redundant information)
Certificate[0] info:
subject `CN=*.s3.amazonaws.com'
pin-sha256="hK1awhGE7onU0O+/0pwyTCX1ngEBhLhdNNtD8P11+xY="
Certificate[1] info:
subject `CN=Amazon,OU=Server CA 1B,O=Amazon,C=US'
pin-sha256="JSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFA="
Certificate[2] info:
subject `CN=Amazon Root CA 1,O=Amazon,C=US'
pin-sha256="++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI="
Certificate[3] info:
subject `CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies\
pin-sha256="KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I="
Using ruby (github-cloud.s3.amazonaws.com):
/CN=*.s3.amazonaws.com
hK1awhGE7onU0O+/0pwyTCX1ngEBhLhdNNtD8P11+xY=
/C=US/O=Amazon/OU=Server CA 1B/CN=Amazon
JSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFA=
/C=US/O=Amazon/CN=Amazon Root CA 1
++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=
commandline gnutls-cli www.netflix.com will get 2:
Certificate[0] info:
subject `CN=www.netflix.com,O=Netflix\, Inc.
pin-sha256:3TGagkVvINvo827M04z0YZlg5kctebcod1Qwb83pA0s=
Certificate[1] info:
subject `CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc
pin-sha256="RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc="
Using ruby (www.netflix.com):
/C=US/ST=California/L=Los Gatos/O=Netflix, Inc./CN=www.netflix.com
3TGagkVvINvo827M04z0YZlg5kctebcod1Qwb83pA0s=
/C=US/O=DigiCert Inc/CN=DigiCert TLS RSA SHA256 2020 CA1
RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=
/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA
r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=
Here is Ruby Code:
#!/usr/bin/env ruby
require 'colorize'
require 'net/http'
require 'openssl'
require 'base64'
domain = "www.netflix.com"
http = Net::HTTP.new(domain, 443)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_callback = lambda do | preverify_ok, cert_store |
return false unless preverify_ok
end_cert = cert_store.chain[0]
return true unless end_cert.to_der == cert_store.current_cert.to_der
cert_store.chain.each do |i|
sha256 = OpenSSL::Digest::SHA256.new
digest = sha256.digest(i.public_key.to_der)
spki = Base64.strict_encode64(digest)
puts i.subject.to_s, spki
end
true
end
res = http.get '/'
ruby code reference to Implementing HTTPS certificate/pubkey pinning with Ruby
Thanks!
Let's take www.netflix.com as an example. There are three certificates used in the trust chain:
The certificate for CN=www.netflix.com with the public key PIN 3TGagkVvINvo827M04z0YZlg5kctebcod1Qwb83pA0s=, which is signed by:
The DigiCert intermediary certificate CN=DigiCert TLS RSA SHA256 2020 CA1 with the public key PIN RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=, which is signed by:
The DigiCert Root CA CN=DigiCert Global Root CA with the public key PIN r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=
#1 & #2 are sent to you by the remote host when you establish a connection to it. #3 exists on your device and is used to verify that #1 & #2 are valid certificates that you can trust.
When you use OpenSSL inside Net::HTTP to connect to these hosts it is being extra informative and printing the complete certificate chain of trust so that you know what Root CA signed #2. You weren't sent #3 over the wire along with #1 and #2 but it's telling you about it anyway because OpenSSL knows that it's part of the trust chain.
When you use gnutls-cli www.netflix.com --print-cert </dev/null 2>&1 to connect it is being succinct and printing only #1 and #2 -- the certificates that were sent by the remote host -- and instead telling you:
Status: The certificate is trusted.
... based on it knowing that the Root CA that you have on disk was used to sign the intermediary certificate and that the intermediary certificate was used to sign the Netflix certificate.
There's nothing different about the connections or the responses received; there's only a difference in what the tools print out when they are run.

Self-signed SSL cert works in IE & curl but no modern browsers

I'm experiencing a somewhat perplexing issue with a self-signed SSL certificate that Windows recognizes as valid, but which no modern browsers will accept.
The certificate is present in the system certificate store (accessed via the Certificates snap-in in MMC), within both the Personal and Trusted Root Certification Authorities folders, and marked as valid.
Per the utility SSL Certificate Verifier, the certificate and certificate chain are valid.
Here is its output:
**************************************************************************
Processing 'localhost'
**************************************************************************
Scan started: 21-09-2020 13:43:33
Generating connection string...
Connection string is: https://localhost:14006/
Entering certificate validation callback function...
Server returned 1 certificates.
Entering server certificate chain validation function...
Leaf certificate issued to: E=REDACTED, CN=localhost, O=localhost, L=New York, S=NY, C=US
Found Subject Alternative Names extension in the certificate.
Fetching SAN values:
DNS Name=localhost
DNS Name=127.0.0.1
DNS Name=::1
IP Address=0000:0000:0000:0000:0000:0000:0000:0001
IP Address=127.0.0.1
Certificate chain successfully passed all checks.
Finished!
Scan ended: 21-09-2020 13:43:33
If I attempt to access the service using Internet Explorer or curl, I get the expected 200 result from the service.
However, if I try to access the service using Edge, Chrome, Opera, or Firefox, I get ERR_CONNECTION_RESET. Before adding the certificate to Firefox's certificate store, I got PR_CONNECT_RESET_ERROR but now that also throws ERR_CONNECTION_RESET.
There are no proxies or VPNs active on my system or anything else that would interfere with Windows' networking. I'm at a complete loss. What on earth is happening here and how do I go about fixing it?
I had exactly the same symptoms - IE and curl working. Chrome, Edge and Firefox not, all reporting ERR_CONNECTION_RESET.
Ultimately it was pinned down to a corrupt HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Cryptography\Configuration\Local\SSL\00010002\Functions key.
nmap reported a single cipher in use when it was broken:
| ssl-enum-ciphers:
| TLSv1.2:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (ecdh_x25519) - A
Reloaded the key with a correct value:
| ssl-enum-ciphers:
| TLSv1.2:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (secp384r1) - A
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (secp384r1) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (ecdh_x25519) - A
The absence of the GCM ciphers is the likely cause, given Chromium was reporting them as obsolete five years ago.
I had a similar problem on chrome where it said
NET::ERR_CERT_COMMON_NAME_INVALID
, turns out the error was with the certificate itself.
Try the to create a certificate with the below link:
SSL Certificate

How do I pass a specific ca cert or path to the Sekken constructor?

When I try to create a new Sekken client, it throws an OpenSSL error that there is a self signed certificate in the chain.
require 'sekken'
url = "https://bridgerinsighteu.lexisnexis.com/webservicesapi/9.0/xgservices.svc?wsdl"
client = Sekken.new(url)
I can duplicate the error from OpenSSL, and I can fix it by passing the location for the SSL cert store.
openssl s_client -showcerts -connect bridgerinsighteu.lexisnexis.com:443
errors with return code 19 (self signed certificate in certificate chain) but
openssl s_client -showcerts -CApath /etc/ssl/certs -connect bridgerinsighteu.lexisnexis.com:443
returns code 0 (ok)
So I'm not sure how or what I need to do to pass that cert path to Sekken to use for the openssl check. Sekken does provide for an HTTPClient gem object to be passed to the constructor, so maybe something there? But I just can't quite get my head around this. Or possibly an environment variable? Does anyone have any ideas about how I can get the Sekken constructor to use a specific cert path or cert?
Machine is Ubuntu 14.04 x64, ruby via rvm is ruby 2.1.1p76, sekken is installed via a Gemfile from github.
openssl s_client -showcerts -CApath /etc/ssl/certs -connect bridgerinsighteu.lexisnexis.com:443
Ignore this. The server is misconfigured, and its sending the CA Root. The server should only send the server's certificate and all intermediates required to build a path to a root. Its up to the client to trust the root.
Here's what your command should look like (avoiding the CA Zoo in /etc/ssl/certs, and trusting only what is needed):
openssl s_client -connect bridgerinsighteu.lexisnexis.com:443 -CAfile <Trustwave Root CA>
You can get <Trustwave Root CA> from Trustwave SSL - Support - Root Download. Get the one named Trustwave Extended Validation CA in PEM format.
Here's what it looks like when using the Trustwave Extended Validation CA (evca.crt). Notice the Verify return code: 0 (ok) at the tail of the output.
$ openssl s_client -connect bridgerinsighteu.lexisnexis.com:443 -CAfile evca.crt
CONNECTED(00000003)
depth=2 C = US, O = SecureTrust Corporation, CN = SecureTrust CA
verify return:1
depth=1 C = US, ST = Illinois, L = Chicago, O = "Trustwave Holdings, Inc.", CN = "Trustwave Organization Validation CA, Level 2", emailAddress = ca#trustwave.com
verify return:1
depth=0 CN = *.lexisnexis.com, O = LexisNexis, L = Miamisburg, ST = Ohio, C = US
verify return:1
---
Certificate chain
0 s:/CN=*.lexisnexis.com/O=LexisNexis/L=Miamisburg/ST=Ohio/C=US
i:/C=US/ST=Illinois/L=Chicago/O=Trustwave Holdings, Inc./CN=Trustwave Organization Validation CA, Level 2/emailAddress=ca#trustwave.com
1 s:/C=US/ST=Illinois/L=Chicago/O=Trustwave Holdings, Inc./CN=Trustwave Organization Validation CA, Level 2/emailAddress=ca#trustwave.com
i:/C=US/O=SecureTrust Corporation/CN=SecureTrust CA
2 s:/C=US/O=SecureTrust Corporation/CN=SecureTrust CA
i:/C=US/O=SecureTrust Corporation/CN=SecureTrust CA
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIFJzCCBA+gAwIBAgITBiMcj33v1H9svkXvkWOYn2JyTTANBgkqhkiG9w0BAQUF
ADCBrjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdD
aGljYWdvMSEwHwYDVQQKExhUcnVzdHdhdmUgSG9sZGluZ3MsIEluYy4xNjA0BgNV
BAMTLVRydXN0d2F2ZSBPcmdhbml6YXRpb24gVmFsaWRhdGlvbiBDQSwgTGV2ZWwg
MjEfMB0GCSqGSIb3DQEJARYQY2FAdHJ1c3R3YXZlLmNvbTAeFw0xMzA1MTUwOTE5
NThaFw0xNjA3MDcxNTE5NThaMGExGTAXBgNVBAMMECoubGV4aXNuZXhpcy5jb20x
EzARBgNVBAoMCkxleGlzTmV4aXMxEzARBgNVBAcMCk1pYW1pc2J1cmcxDTALBgNV
BAgMBE9oaW8xCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAvsygjRx3DKUm/gmceKny65HMUmRzm8FP0Te9eXsQ76OLa3co4hWrF5ZS
bXlDzB6dgCTFnQOwRcsLpVyXlazDugdibjfnqdyStcjI75+J2emRYzHVJ7P9p+Bw
pL1k01POV/pes87abX1ffodK+OwnWDkfABqLaaJlsluv/NJd5cdGTn8C1+7mw3MR
KxUTGuGdsTgV/H5aEQFAP6BIklpywhk+QJb1BN28bR2UMTi0QB6qBNP+oe5aWG6Q
rq/ghb31FF0jmL9pCGVJfY5eewIXjBiEwFSdkxv8rxPmkmjDV9E5/OGKXuALtJIE
SFfM9WwTKHV3rs79QV38yD6Cbf3yVQIDAQABo4IBiDCCAYQwDAYDVR0TAQH/BAIw
ADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0G
A1UdDgQWBBQUma9eMJEc5IUlSe4n6X7eViCS9zAfBgNVHSMEGDAWgBRd2ZaaQMcn
yyybouzPGavIr8yGSDBIBgNVHSAEQTA/MD0GDysGAQQBge0YAwMDAwQEAzAqMCgG
CCsGAQUFBwIBFhxodHRwczovL3NzbC50cnVzdHdhdmUuY29tL0NBME8GA1UdEQRI
MEaCECoubGV4aXNuZXhpcy5jb22CDmxleGlzbmV4aXMuY29tghEqLmxleGlzLW5l
eGlzLmNvbYIPbGV4aXMtbmV4aXMuY29tMDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6
Ly9jcmwudHJ1c3R3YXZlLmNvbS9PVkNBX0wyLmNybDA2BggrBgEFBQcBAQQqMCgw
JgYIKwYBBQUHMAGGGmh0dHA6Ly9vY3NwLnRydXN0d2F2ZS5jb20vMA0GCSqGSIb3
DQEBBQUAA4IBAQAXvdGvtggb6dfa46IFX81rGr69ank7rON6VQaqUrUeExqmSyhw
r+n8wh0YFo69GKVx1LFa1+eWIz48ROtOhveSr/Gib4ujBLtq5urITOcmH4IYj3sw
2VFuaCZ0+TNgqmt6HPTPfBwWjcCRLbtDYPeFFo52HMu+ObeNeVR1Ll58Iijl4sOo
CwaDNFYiveLwcPXgGQhvYn6NFXW0D2cRpeTJzjXOjcLebPY9h//Fl6loZh/APwGT
gKz43mIChdcTQz/caeDjj0VkiSpJ4XDXYRDabSkpzvwJ5AXDC4f4jZy8jxnx9sSP
NnGvKxaJwr2ArewSfYX7W/JtVUAF+wIEVPux
-----END CERTIFICATE-----
subject=/CN=*.lexisnexis.com/O=LexisNexis/L=Miamisburg/ST=Ohio/C=US
issuer=/C=US/ST=Illinois/L=Chicago/O=Trustwave Holdings, Inc./CN=Trustwave Organization Validation CA, Level 2/emailAddress=ca#trustwave.com
---
No client certificate CA names sent
---
SSL handshake has read 3569 bytes and written 831 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-SHA
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol : TLSv1
Cipher : RC4-SHA
Session-ID: 684051C7B37B4A255AE51BFC67CFC4BF...
Session-ID-ctx:
Master-Key: 53C559C9F85A6CB1788BFC20E1A1997C...
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1399081838
Timeout : 300 (sec)
Verify return code: 0 (ok)
So I'm not sure how or what I need to do to pass that cert path to Sekken to use for the openssl check.
All you need to do is specify Trustwave Extended Validation CA (evca.crt) as the root to use in Sekken when building the validation path. I'm not a Sekken guy, but I know how to do it in other languages and libraries like .Net, Java, OpenSSL, PERL, Python, etc.
Since you specified the Ruby tag, here's a test script I was using for some PKI testing with Ruby:
#!/usr/bin/ruby
require 'net/http'
uri = URI('https://bridgerinsighteu.lexisnexis.com:443')
http = Net::HTTP.new(uri.host, uri.port)
# Enable SSL/TLS ?
if uri.scheme == "https"
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = File.join(File.dirname(__FILE__), "evca.crt")
end
req = Net::HTTP::Get.new('/')
http.request(req)
This is just bike shedding... You have the Trustwave Extended Validation CA in /etc/ssl/certs. If the certificate was missing, then s_client would have failed when using CApath.
Trustwave has proven itself to be very untrustworthy. In the past, it facilitated interception of all SSL/TLS traffic by issuing certificates for domains not under the control of the person who wanted the certificates.
"Trust" is tricky to define. One of the better definitions I have seen is "X expects Y to do Z". That is, X expects or trusts Y to do Z because (1) Y says it does Z and (2) X accepts or approves of Z.
If you plug in PKI: "Users expect CAs to follow their CP and CPS". CP is "Certification Practice" and its policy; and CPS Is Certification Practice Statement and its procedure. So the CP and CPS specifies how the CA operates by defining policies (CP), and procedures to implement or enforce the policies (CPS).
If Trustwave followed their own published policies and procedures, then Trustwave would not have issued the certificates to intercept the SSL/TLS traffic. Trustwave did not follow their own policies and procedures, so they have proven themselves to be untrustworthy. Quod erat demonstrandum.
So ok, looks like you can pass an instance of an HTTPClient to the Sekken constructor. But there's a bug in the constructor that keeps it from using the passed client. I hacked my way around it, but hopefully the owner will fix it better? https://github.com/savonrb/sekken/issues/10
Once that is fixed, this is how I solved the problem. I created an instance of the HTTPClient then added the Trustwave root CA cert to the instance cert store and passed that to the Sekken constructor.
require 'sekken'
require 'httpclient'
url = "https://bridgerinsighteu.lexisnexis.com/webservicesapi/9.0/xgservices.svc?wsdl"
# this is the secure trust CA
cert = "/etc/ssl/certs/stca.pem"
http = HTTPClient.new
http.ssl_config.cert_store.add_file cert
client = Sekken.new(url, http)

Using a self-signed certificate

I am just trying to get my head around SSL.
I have set up a Jetty server on my localhost, and generated my own certificate using Keytool.
Now when I go to https://localhost:8443/ I get the can't trust this certificate error.
I use
keytool -export -alias pongus -keystore keystore -file certfile.cer
To create the certificate which I think is what the client needs to authenticate with the server. (This is where I could be very wrong!)
I have the following ruby code :
require 'net/https'
require 'openssl'
require 'open-uri'
puts 'yay' if File.exists?('certfile.cer')
uri = URI.parse("https://localhost:8443/")
http_session = Net::HTTP.new(uri.host, uri.port)
http_session.use_ssl = true
http_session.verify_mode = OpenSSL::SSL::VERIFY_PEER
http_session.ca_file = 'certfile.cer'
res = http_session.start do |http|
# do some requests here
http.get('/')
end
This does print 'yay', so the certfile.cer file does exist.
But I get the errors
/Applications/NetBeans/NetBeans 6.8.app/Contents/Resources/NetBeans/ruby2/jruby-1.4.0/lib/ruby/1.8/net/http.rb:586 warning: can't set verify locations
/Applications/NetBeans/NetBeans 6.8.app/Contents/Resources/NetBeans/ruby2/jruby-1.4.0/lib/ruby/1.8/net/http.rb:586:in `connect': certificate verify failed (OpenSSL::SSL::SSLError)
Any ideas what I am doing wrong?
EDIT
I want to get it so I guarantee that I am connecting to the right server, and the server can guarantee that it is me connecting to it, without any tampering in between. I am developing both the server and the client.
Your client needs access to its
private key.
You dont need the private key for server certificate verification. All you need is the certificate itself which contains the public key. Only the server has the private key. Well described here http://www.helpbytes.co.uk/https.php and here http://www.verisign.com/ssl/ssl-information-center/how-ssl-security-works/
My recommendation is simple. Check your certificate is correct.
openssl x509 -text -in mycert.crt
Also if you have access to the server you can explicitely validate if the certificate and key (used in httpd configuration) are correct (matches): http://kb.wisc.edu/middleware/page.php?id=4064 Please note this is explicit check ran on server. Never give out the private key. This check can be done only by the administrator to verify if the httpd was not misconfigured.
(openssl x509 -noout -modulus -in server.pem | openssl md5 ;\
openssl rsa -noout -modulus -in server.key | openssl md5) | uniq
You can also debug the SSL certificate communication using standard openssl command. Issue this command then wait few seconds and then type QUIT and hit enter. You will see the certificate the server sends out.
openssl s_client -connect your.server.com:443
Also try to import the certificate to your browser and access the URL resource. Browser is able to validate it by clicking on https (Firefox and Chrome). Then you will see the certificate itself and validity information.
All the above was all about the server certificate. This is only one part of the problem. "I am connecting to the right server, and the server can guarantee that it is me connecting to it" Actully in your code above you only check for the server cert. Now. If you want a client certificate (the second part of your statement) than you need this in Ruby:
File.open( "client_certificate.pem", 'rb' ) { |f| cert = f.read }
File.open( "client_key.pem", 'rb' ) { |f| key = f.read }
http_session.cert = OpenSSL::X509::Certificate.new(cert)
http_session.key = OpenSSL::PKey::RSA.new(key, nil)
This is how client cert should be used in Ruby. If your private key is encrypted with a password just pass it instead nil in the second argument of RSA constructor.
I highly recommend to get server certificate working (your code) and then start with client certificate. Please note you keep your current code (ca_cert, validation constant) and add the above four lines to it.
Hope this helps.
Your client needs access to its private key. The private key is not in the certificate, the certificate only contains the public key. Sorry, I don't know ruby, but a common technique is to bundle the private key and certificate in a single PKCS#12, aka p12, file and supply this to the crypto library.
Change
http_session.verify_mode = OpenSSL::SSL::VERIFY_PEER
to
http_session.verify_mode = OpenSSL::SSL::VERIFY_NONE
Once you do that, the SSL will work properly. I have used this multiple times in my development environments, always works flawlessly.

APNS certificate expiry date error with MobileFirst Platform 7.0

When deploying an APNS certificate in a .wlapp file in MFP 7.0, I'm seeing a null-pointer exception when it validates the end-date, even though it has one. ( openssl pkcs12 -in apns-certificate-sandbox.p12 | openssl x509 -noout -enddate returns a valid date in the future).
It seems others have made this work, so I'm guessing it must be something I am doing wrong...has anyone else resolved similar issues with valid Apple Push Notification Service certs failing to be deployed on MFP
Relevant lines from the log:
947: "com.ibm.worklight.admin.services.ApplicationService E FWLSE3000E: A server error was detected.",
"948: com.ibm.worklight.admin.common.util.exceptions.ValidationException: FWLSE3119E: APNS certificate validation failed. See additional messages for details.",
"949: at com.ibm.worklight.admin.util.PushEnvironmentUtil.validateApnsConfiguration(PushEnvironmentUtil.java:232)",
"950: at com.ibm.worklight.admin.util.PushEnvironmentUtil.validatePushConfiguration(PushEnvironmentUtil.java:220)",
[ ... lots more trace here .. ]
"1030: Caused by: java.lang.NullPointerException",
"1031: at java.io.ByteArrayInputStream.(ByteArrayInputStream.java:117)",
"1032: at com.ibm.worklight.admin.util.PushEnvironmentUtil.getCertificateExpiryDate(PushEnvironmentUtil.java:362)",
"1033: at com.ibm.worklight.admin.util.PushEnvironmentUtil.validateApnsConfiguration(PushEnvironmentUtil.java:230)",
Initial hurdle was that the .wlapp file was not being built, so no apns certificate was in the file (it is just in .zip format with a meta directory that should hold the .p12 file). The underlying issue was that the tag's password field in application-descriptor.xml wasn't exactly right: it was following the example from "Push Notifications in iOS applications" at https://developer.ibm.com/mobilefirstplatform/documentation/getting-started-7-0/notifications/push-notifications-native-ios-applications/ :
<pushSender password="apns-certificate-p12 password"/>
when it really should just have the password:
<pushSender password="password"/> </code></pre>
with the file named either apns-certificate-sandbox.p12 or apns-certificate-production.p12 depending on which server is to be used.
Double dumbass on me for not checking the official docs at http://www-01.ibm.com/support/knowledgecenter/SSHS8R_7.0.0/com.ibm.worklight.dev.doc/devref/c_the_application_descriptor.html , which has it described correctly.
Moral: "When in doubt, RTFM"

Resources