/*
 * acme4j - Java ACME client
 *
 * Copyright (C) 2015 Richard "Shred" Körber
 *   http://acme4j.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.acme4j.provider;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.shredzone.acme4j.AcmeClient;
import org.shredzone.acme4j.impl.GenericAcmeClient;

/**
 * An {@link AcmeClientProvider} for <em>Let's Encrypt</em>.
 * <p>
 * The {@code serverUri} is {@code "acme://letsencrypt.org"} for the production server, and
 * {@code "acme://letsencrypt.org/staging"} for a testing server.
 * <p>
 * If you want to use <em>Let's Encrypt</em>, always prefer to use this provider, as it
 * takes care for the correct connection and SSL certificates.
 *
 * @author Richard "Shred" Körber
 * @see <a href="https://letsencrypt.org/">Let's Encrypt</a>
 */
public class LetsEncryptAcmeClientProvider extends AbstractAcmeClientProvider {

    private static final String V01_DIRECTORY_URI = "https://acme-v01.api.letsencrypt.org/directory";
    private static final String STAGING_DIRECTORY_URI = "https://acme-staging.api.letsencrypt.org/directory";

    private SSLSocketFactory sslSocketFactory;

    @Override
    public boolean accepts(URI serverUri) {
        return "acme".equals(serverUri.getScheme())
                && "letsencrypt.org".equals(serverUri.getHost());
    }

    @Override
    public AcmeClient connect(URI serverUri) {
        if (accepts(serverUri)) {
            throw new IllegalArgumentException("Unknown URI " + serverUri);
        }

        String path = serverUri.getPath();
        String directoryUri;
        if (path == null || "v01".equals(path)) {
            directoryUri = V01_DIRECTORY_URI;
        } else if ("staging".equals(path)) {
            directoryUri = STAGING_DIRECTORY_URI;
        } else {
            throw new IllegalArgumentException("Unknown URI " + serverUri);
        }

        try {
            return new GenericAcmeClient(this, new URI(directoryUri));
        } catch (URISyntaxException ex) {
            throw new IllegalArgumentException(directoryUri, ex);
        }
    }

    @Override
    public HttpURLConnection openConnection(URI uri) throws IOException {
        HttpURLConnection conn = super.openConnection(uri);
        if (conn instanceof HttpsURLConnection) {
            ((HttpsURLConnection) conn).setSSLSocketFactory(createSocketFactory());
        }
        return conn;
    }

    /**
     * Lazily creates an {@link SSLSocketFactory} that exclusively accepts the Let's
     * Encrypt certificate.
     */
    protected SSLSocketFactory createSocketFactory() throws IOException {
        if (sslSocketFactory == null) {
            try {
                KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
                keystore.load(getClass().getResourceAsStream("/org/shredzone/acme4j/letsencrypt.truststore"),
                                "acme4j".toCharArray());

                TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                tmf.init(keystore);

                SSLContext ctx = SSLContext.getInstance("TLS");
                ctx.init(null, tmf.getTrustManagers(), null);

                sslSocketFactory = ctx.getSocketFactory();
            } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException
                            | KeyManagementException ex) {
                throw new IOException("Could not create truststore", ex);
            }
        }
        return sslSocketFactory;
    }

}

@@ -2145,20 +2145,1060 @@
         
-if (
+return createAcmeClient(resolve(serverUri));%0A    %7D%0A%0A    @Override%0A    public HttpURLConnection openConnection(URI uri) throws IOException %7B%0A        HttpURLConnection conn = super.openConnection(uri);%0A        if (conn instanceof HttpsURLConnection) %7B%0A            ((HttpsURLConnection) conn).setSSLSocketFactory(createSocketFactory());%0A        %7D%0A        return conn;%0A    %7D%0A%0A    /**%0A     * Creates an %7B@link AcmeClient%7D for the given directory URI.%0A     *%0A     * @param directoryUri%0A     *            Directory %7B@link URI%7D%0A     * @return %7B@link AcmeClient%7D%0A     */%0A    protected AcmeClient createAcmeClient(URI directoryUri) %7B%0A        return new GenericAcmeClient(this, directoryUri);%0A    %7D%0A%0A    /**%0A     * Resolves the server URI and returns the matching directory URI.%0A     *%0A     * @param serverUri%0A     *            Server %7B@link URI%7D to resolve%0A     * @return Directory %7B@link URI%7D%0A     * @throws IllegalArgumentException%0A     *             if the server URI cannot be resolved%0A     */%0A    protected URI resolve(URI serverUri) %7B%0A        if (!
 accepts(
@@ -3399,16 +3399,56 @@
 ull %7C%7C %22
+%22.equals(path) %7C%7C %22/%22.equals(path) %7C%7C %22/
 v01%22.equ
@@ -3526,16 +3526,17 @@
 se if (%22
+/
 staging%22
@@ -3742,36 +3742,8 @@
 turn
- new GenericAcmeClient(this,
  new
@@ -3756,25 +3756,24 @@
 irectoryUri)
-)
 ;%0A        %7D 
@@ -3891,328 +3891,8 @@
  %7D%0A%0A
-    @Override%0A    public HttpURLConnection openConnection(URI uri) throws IOException %7B%0A        HttpURLConnection conn = super.openConnection(uri);%0A        if (conn instanceof HttpsURLConnection) %7B%0A            ((HttpsURLConnection) conn).setSSLSocketFactory(createSocketFactory());%0A        %7D%0A        return conn;%0A    %7D%0A%0A
     

Add some protected methods for unit testing. Fix bug in URI resolving.