How to Fix “SSL Certificate Error: certificate_unknown” in APNs Push Notification Server


2 views

The error message "certificate_unknown" typically occurs when Apple Push Notification service (APNs) cannot validate your SSL certificate chain. This happens most frequently when:

1. The certificate is self-signed
2. Intermediate certificates are missing
3. The certificate is expired or revoked
4. Incorrect certificate format is used

Apple's servers perform rigorous certificate validation:

  • Checks the certificate chain up to a trusted root CA
  • Validates the certificate is issued for APNs service
  • Verifies the certificate hasn't expired
  • Ensures the private key matches

First, verify your certificate using OpenSSL:

openssl pkcs12 -info -in privateKey.p12 -nodes

Check for these critical elements:

- Certificate chain completeness
- Valid "Not Before" and "Not After" dates
- Correct subject fields including:
  CN=Apple Push Services: com.yourdomain.app
  OU=YOUR_TEAM_ID
  O=Your Company

For the JavaPNS library specifically, try these modifications:

// Modified connection code with explicit trust store
System.setProperty("javax.net.ssl.trustStore", "/path/to/cacerts");
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");

// Alternative approach with custom SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
    public void checkClientTrusted(X509Certificate[] chain, String authType) {}
    public void checkServerTrusted(X509Certificate[] chain, String authType) {}
    public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}}, new SecureRandom());

PushNotificationManager pushManager = new PushNotificationManager();
pushManager.initializeConnection(HOST, PORT, certificate, passwd, sslContext);

When creating your .p12 file:

  1. Export from Keychain Access with private key
  2. Include all intermediate certificates
  3. Use strong password protection
  4. Verify the certificate type is "Production" or "Sandbox" as needed

Use Wireshark to monitor the SSL handshake:

Filter: ssl.handshake and ip.addr == 17.0.0.0/8
Look for:
- Client Hello
- Server Hello
- Certificate exchange
- Alert messages

Here's a more robust implementation using newer Java SSL features:

KeyStore keyStore = KeyStore.getInstance("PKCS12");
try (InputStream keyStream = new FileInputStream(certificate)) {
    keyStore.load(keyStream, passwd.toCharArray());
}

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(keyStore, passwd.toCharArray());

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(kmf.getKeyManagers(), null, null);

SSLSocketFactory factory = sslContext.getSocketFactory();
SSLSocket socket = (SSLSocket)factory.createSocket(HOST, PORT);
socket.startHandshake();

When implementing Apple Push Notification service (APNs), the "certificate_unknown" SSL error typically occurs when the server cannot establish a trusted connection with APNs. This indicates a problem with your certificate chain or keystore configuration.

  • Using an expired or revoked certificate
  • Incorrect .p12 file generation
  • Missing intermediate certificates
  • Password mismatch
  • Using production certificate with sandbox environment or vice versa

1. Validate Certificate Chain

First, check your certificate chain using OpenSSL:

openssl pkcs12 -info -in privateKey.p12 -nodes

This should show your certificate along with the Apple Worldwide Developer Relations Intermediate Certificate.

2. Verify Environment Matching

// Sandbox vs Production endpoints
private static final String SANDBOX_HOST = "gateway.sandbox.push.apple.com";
private static final String PRODUCTION_HOST = "gateway.push.apple.com";

// Make sure you're using the correct endpoint for your certificate type

Here's an improved implementation with better error handling:


import javapns.Push;
import javapns.notification.PushNotificationPayload;
import org.json.JSONException;

public class RobustPushNotification {

    private static final String HOST = "gateway.sandbox.push.apple.com";
    private static final int PORT = 2195;
    private static final String CERT_PATH = "/path/to/certificate.p12";
    private static final String PASSWORD = "your_password";
    private static final String DEVICE_TOKEN = "device_token_here";

    public static void main(String[] args) {
        try {
            // Enhanced payload with more options
            PushNotificationPayload payload = PushNotificationPayload.complex();
            payload.addAlert("Hello World!");
            payload.addBadge(1);
            payload.addSound("default");
            
            // Push with feedback
            Push.payload(payload, CERT_PATH, PASSWORD, false, DEVICE_TOKEN);
            
        } catch (JSONException e) {
            System.err.println("JSON Error: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("Push Failed: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
  1. Download both certificates from Apple Developer portal
  2. Combine them into a single .p12 file:
    openssl pkcs12 -export \
        -in aps_development.cer \
        -inkey yourPrivateKey.key \
        -certfile AppleWWDRCA.cer \
        -out pushcert.p12
  3. Verify the certificate is not expired
  4. Ensure the certificate matches your app bundle ID

For more control, use the PushNotificationManager directly:


PushNotificationManager pushManager = new PushNotificationManager();
pushManager.initializeConnection(
    HOST, 
    PORT, 
    CERT_PATH, 
    PASSWORD, 
    SSLConnectionHelper.KEYSTORE_TYPE_PKCS12
);

// Send multiple notifications efficiently
List<PushedNotification> notifications = pushManager.sendNotifications(
    payload, 
    Collections.singletonList(DEVICE_TOKEN)
);

pushManager.stopConnection();