001/*
002 * acme4j - Java ACME client
003 *
004 * Copyright (C) 2017 Richard "Shred" Körber
005 *   http://acme4j.shredzone.org
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
013 */
014package org.shredzone.acme4j.it.pebble;
015
016import static java.util.concurrent.TimeUnit.SECONDS;
017import static java.util.stream.Collectors.toList;
018import static org.assertj.core.api.Assertions.assertThat;
019import static org.awaitility.Awaitility.await;
020
021import java.time.Duration;
022import java.time.Instant;
023import java.time.temporal.ChronoUnit;
024
025import org.bouncycastle.asn1.x509.GeneralName;
026import org.junit.jupiter.api.Test;
027import org.shredzone.acme4j.AccountBuilder;
028import org.shredzone.acme4j.Session;
029import org.shredzone.acme4j.Status;
030import org.shredzone.acme4j.challenge.Dns01Challenge;
031
032/**
033 * Tests a complete wildcard certificate order. Wildcard certificates currently only
034 * support dns-01 challenge.
035 */
036public class OrderWildcardIT extends PebbleITBase {
037
038    private static final String TEST_DOMAIN = "example.com";
039    private static final String TEST_WILDCARD_DOMAIN = "*.example.com";
040
041    /**
042     * Test if a wildcard certificate can be ordered via dns-01 challenge.
043     */
044    @Test
045    public void testDnsValidation() throws Exception {
046        var client = getBammBammClient();
047        var keyPair = createKeyPair();
048        var session = new Session(pebbleURI());
049
050        var account = new AccountBuilder()
051                    .agreeToTermsOfService()
052                    .useKeyPair(keyPair)
053                    .create(session);
054
055        var domainKeyPair = createKeyPair();
056
057        var notBefore = Instant.now().truncatedTo(ChronoUnit.MILLIS);
058        var notAfter = notBefore.plus(Duration.ofDays(20L));
059
060        var order = account.newOrder()
061                    .domain(TEST_WILDCARD_DOMAIN)
062                    .domain(TEST_DOMAIN)
063                    .notBefore(notBefore)
064                    .notAfter(notAfter)
065                    .create();
066        assertThat(order.getNotBefore().orElseThrow()).isEqualTo(notBefore);
067        assertThat(order.getNotAfter().orElseThrow()).isEqualTo(notAfter);
068        assertThat(order.getStatus()).isEqualTo(Status.PENDING);
069
070        for (var auth : order.getAuthorizations()) {
071            assertThat(auth.getIdentifier().getDomain()).isEqualTo(TEST_DOMAIN);
072            assertThat(auth.getStatus()).isEqualTo(Status.PENDING);
073
074            if (auth.getStatus() == Status.VALID) {
075                continue;
076            }
077
078            var challenge = auth.findChallenge(Dns01Challenge.class).orElseThrow();
079
080            var challengeDomainName = Dns01Challenge.toRRName(TEST_DOMAIN);
081
082            client.dnsAddTxtRecord(challengeDomainName, challenge.getDigest());
083            cleanup(() -> client.dnsRemoveTxtRecord(challengeDomainName));
084
085            challenge.trigger();
086
087            await()
088                .pollInterval(1, SECONDS)
089                .timeout(30, SECONDS)
090                .conditionEvaluationListener(cond -> updateAuth(auth))
091                .untilAsserted(() -> assertThat(
092                        auth.getStatus()).isNotIn(Status.PENDING, Status.PROCESSING));
093
094            assertThat(auth.getStatus()).isEqualTo(Status.VALID);
095        }
096
097        order.execute(domainKeyPair);
098
099        await()
100            .pollInterval(1, SECONDS)
101            .timeout(30, SECONDS)
102            .conditionEvaluationListener(cond -> updateOrder(order))
103            .untilAsserted(() -> assertThat(
104                    order.getStatus()).isNotIn(Status.PENDING, Status.PROCESSING));
105
106
107        var cert = order.getCertificate().getCertificate();
108        assertThat(cert).isNotNull();
109        assertThat(cert.getNotAfter()).isNotEqualTo(notBefore);
110        assertThat(cert.getNotBefore()).isNotEqualTo(notAfter);
111        assertThat(cert.getSubjectX500Principal().getName()).satisfiesAnyOf(
112                name -> assertThat(name).contains("CN=" + TEST_DOMAIN),
113                name -> assertThat(name).contains("CN=" + TEST_WILDCARD_DOMAIN)
114        );
115
116        var san = cert.getSubjectAlternativeNames().stream()
117                .filter(it -> ((Number) it.get(0)).intValue() == GeneralName.dNSName)
118                .map(it -> (String) it.get(1))
119                .collect(toList());
120        assertThat(san).contains(TEST_DOMAIN, TEST_WILDCARD_DOMAIN);
121    }
122
123}