HttpsJwksTest.java

/*
 * Copyright 2012-2018 Brian Campbell
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jose4j.jwk;

import org.jose4j.http.Get;
import org.jose4j.http.Response;
import org.jose4j.http.SimpleResponse;
import org.jose4j.keys.X509Util;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;

/**
 *
 */
public class HttpsJwksTest
{
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Test
    public void testExpiresDateHeadersPerRfc() throws Exception
    {
        /*
              3 different HTTP date formats per
              http://tools.ietf.org/html/rfc7231#section-7.1.1.1  or
              http://tools.ietf.org/html/rfc2616#section-3.3.1
              Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
              Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
              Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
         */
        long actualDateMs = 784111777000L;
        long actualCacheLife = 60L;
        long fakeCurrentTime = 784111717000L;

        Map<String, List<String>> headers = Collections.singletonMap("Expires", Collections.singletonList("Sun, 06 Nov 1994 08:49:37 GMT"));
        SimpleResponse simpleResponse = new Response(200, "OK", headers, "doesn't matter");
        assertThat(actualDateMs, equalTo(HttpsJwks.getExpires(simpleResponse)));
        assertThat(actualCacheLife, equalTo(HttpsJwks.getCacheLife(simpleResponse, fakeCurrentTime)));

        headers = Collections.singletonMap("Expires", Collections.singletonList("Sunday, 06-Nov-94 08:49:37 GMT"));
        simpleResponse = new Response(200, "OK", headers, "doesn't matter");
        assertThat(actualDateMs, equalTo(HttpsJwks.getExpires(simpleResponse)));
        assertThat(actualCacheLife, equalTo(HttpsJwks.getCacheLife(simpleResponse, fakeCurrentTime)));

        headers = Collections.singletonMap("Expires", Collections.singletonList("Sun Nov  6 08:49:37 1994"));
        simpleResponse = new Response(200, "OK", headers, "*still* doesn't matter");
        assertThat(actualDateMs, equalTo(HttpsJwks.getExpires(simpleResponse)));
        assertThat(actualCacheLife, equalTo(HttpsJwks.getCacheLife(simpleResponse, fakeCurrentTime)));
    }

    @Test
    public void testCacheLifeFromCacheControlMaxAge() throws Exception
    {
        String[] headerValues =
        {
            "public, max-age=23760, must-revalidate, no-transform",
            "public, max-age=    23760 , must-revalidate",
            "public,max-age = 23760, must-revalidate",
            "public, max-age=23760, must-revalidate, no-transform",
            "must-revalidate,public,max-age=23760,no-transform",
            "max-age =23760, must-revalidate, public",
            "max-age=23760",
            "max-age =23760",
            "max-age = 23760 ",
            "max-age=23760,",
            "fake=\"f,a,k,e\",public, max-age=23760, must-revalidate=\"this , shouldn't be here\", whatever",
        };

        for (String headerValue : headerValues)
        {
            Map<String, List<String>> headers = new HashMap<>();
            headers.put("Expires", Collections.singletonList("Expires: Tue, 27 Jan 2015 16:00:10 GMT")); // Cache-Control takes precedence over this
            headers.put("Cache-Control", Collections.singletonList(headerValue));
            SimpleResponse simpleResponse = new Response(200, "OK", headers, "doesn't matter");
            long cacheLife = HttpsJwks.getCacheLife(simpleResponse);
            assertThat("it done broke on this one " + headerValue, 23760L , equalTo(cacheLife));
        }
    }

    // todo more tests

    @Test
    @Ignore // skip this one b/c of external dependency and manual intervention needed
    public void testKindaSimplisticConcurrent() throws Exception
    {
        X509Util x509Util = new X509Util();
        X509Certificate certificate = x509Util.fromBase64Der(
                "MIICUDCCAbkCBETczdcwDQYJKoZIhvcNAQEFBQAwbzELMAkGA1UEBhMCVVMxCzAJ\n" +
                "BgNVBAgTAkNPMQ8wDQYDVQQHEwZEZW52ZXIxFTATBgNVBAoTDFBpbmdJZGVudGl0\n" +
                "eTEXMBUGA1UECxMOQnJpYW4gQ2FtcGJlbGwxEjAQBgNVBAMTCWxvY2FsaG9zdDAe\n" +
                "Fw0wNjA4MTExODM1MDNaFw0zMzEyMjcxODM1MDNaMG8xCzAJBgNVBAYTAlVTMQsw\n" +
                "CQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRUwEwYDVQQKEwxQaW5nSWRlbnRp\n" +
                "dHkxFzAVBgNVBAsTDkJyaWFuIENhbXBiZWxsMRIwEAYDVQQDEwlsb2NhbGhvc3Qw\n" +
                "gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJLrpeiY/Ai2gGFxNY8Tm/QSO8qg\n" +
                "POGKDMAT08QMyHRlxW8fpezfBTAtKcEsztPzwYTLWmf6opfJT+5N6cJKacxWchn/\n" +
                "dRrzV2BoNuz1uo7wlpRqwcaOoi6yHuopNuNO1ms1vmlv3POq5qzMe6c1LRGADyZh\n" +
                "i0KejDX6+jVaDiUTAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAMojbPEYJiIWgQzZc\n" +
                "QJCQeodtKSJl5+lA8MWBBFFyZmvZ6jUYglIQdLlc8Pu6JF2j/hZEeTI87z/DOT6U\n" +
                "uqZA83gZcy6re4wMnZvY2kWX9CsVWDCaZhnyhjBNYfhcOf0ZychoKShaEpTQ5UAG\n" +
                "wvYYcbqIWC04GAZYVsZxlPl9hoA=\n");

        X509Certificate x509Certificate = x509Util.fromBase64Der(
                "MIIDVjCCAj6gAwIBAgIGAV+2AcB2MA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYT\n" +
                "AlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRUwEwYDVQQKEwxQaW5n\n" +
                "SWRlbnRpdHkxFDASBgNVBAsTC0RldmVsb3BtZW50MRIwEAYDVQQDEwlsb2NhbGhv\n" +
                "c3QwHhcNMTcxMTEzMTUzMTI5WhcNMjcxMTE0MTUzMTI5WjBsMQswCQYDVQQGEwJV\n" +
                "UzELMAkGA1UECBMCQ08xDzANBgNVBAcTBkRlbnZlcjEVMBMGA1UEChMMUGluZ0lk\n" +
                "ZW50aXR5MRQwEgYDVQQLEwtEZXZlbG9wbWVudDESMBAGA1UEAxMJbG9jYWxob3N0\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjAjwydAR/F3pNntK9YCa\n" +
                "5ewJ6qrW8BUbZsB7G3FvK2lD2jezp47PswQ6M2QbNAlkA7zv8qOcBb2lleoCyc70\n" +
                "127MvkD3Pfrw5BSXR1LH8QhxIeayRVK0T28qmfMU9fc9zyn0rpB4eeC5KYSe9pXW\n" +
                "vg+MUpE2hnW0ZaTkXWQxriBU46DEuiJ8qhd2ACoxHo1NQEWBTJt2fWVWe9Ai/YuE\n" +
                "72g7DQdxZTamwo74Gp4RBZuVS+4xh42e0chktkNKNRo5/8Nkhb8CWNgjwakiNPXL\n" +
                "4bIzyh0+/KQoUoyRz61Oj5Q6FXbIlEJhDFFZGuHgiMi1JR0CvQEoYng80d3Lj/Dw\n" +
                "XwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAZcFMBOONUTdm9xZZ5Cnwlti+rzmE4\n" +
                "w0JRAEazFXLdMim1PWwlaJ7dvqqLXJklLV/4uSocvoMFNAjpoR1v27mj7aWe0WD9\n" +
                "NU/z+yiEvSjSzCoWWUbBYLxgOz+ZaP78SOU+SFzuZYaBj7GiZbj4MkDaozReDMmM\n" +
                "uGJsSJeKo3qQVja6ma71gDyupTg9pu8h+Dk7NUB8AOEIX8bncheKKtiC/IiPa/PO\n" +
                "nik9VuDu1Oq/W7d6bRzw3GrBq5puPX9ATonRqRqmWu4AMRi0G5kA75rWXes9TOII\n" +
                "BK3F71z66Z6qlxIYDZl5qYIJEIE71/YqWIEzR4Cqpu59c3oJ7obdyGTz");

        long start = System.currentTimeMillis();

        String location = "https://localhost:9031/pf/JWKS";
//        location = "https://login.salesforce.com/id/keys";
//        location = "https://www.googleapis.com/oauth2/v3/certs";
//        location = "https://login.microsoftonline.com/consumers/discovery/v2.0/keys";

        Get get = new Get();
        get.setTrustedCertificates(certificate, x509Certificate);

        final HttpsJwks httpsJwks = new HttpsJwks(location);
        httpsJwks.setSimpleHttpGet(get);
        httpsJwks.setDefaultCacheDuration(1);
        httpsJwks.setRetainCacheOnErrorDuration(1);

        Callable<List> task = new Callable<List>()
        {
            @Override
            public List<JsonWebKey> call() throws Exception
            {
                List<JsonWebKey> jsonWebKeys = null;
                for (long i = 200000000 ; i > 0 ; i--)
                {
                    jsonWebKeys = httpsJwks.getJsonWebKeys();
                    assertFalse(jsonWebKeys.isEmpty());
                    if (i % 11000000 == 0) { httpsJwks.refresh(); }
                    if (i % 10000000 == 0) { log.debug("... working ... " + i + " ... " + Thread.currentThread().toString());}
                }
                return jsonWebKeys;
            }
        };

        int threadCount = 15;
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        List<Callable<List>> tasks = Collections.nCopies(threadCount, task);

        List<Future<List>> futures = executorService.invokeAll(tasks);
        log.debug("=== and done ===");

        for (Future<List> future : futures)
        {
            log.debug(future.get().toString());
        }

        System.out.println(System.currentTimeMillis() - start);
    }
}