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.boulder;
015
016import static java.util.concurrent.TimeUnit.SECONDS;
017import static org.assertj.core.api.Assertions.assertThat;
018import static org.awaitility.Awaitility.await;
019
020import java.net.URI;
021import java.security.KeyPair;
022
023import org.junit.jupiter.api.Test;
024import org.shredzone.acme4j.AccountBuilder;
025import org.shredzone.acme4j.Authorization;
026import org.shredzone.acme4j.Order;
027import org.shredzone.acme4j.Session;
028import org.shredzone.acme4j.Status;
029import org.shredzone.acme4j.challenge.Http01Challenge;
030import org.shredzone.acme4j.exception.AcmeException;
031import org.shredzone.acme4j.exception.AcmeLazyLoadingException;
032import org.shredzone.acme4j.it.BammBammClient;
033import org.shredzone.acme4j.util.KeyPairUtils;
034
035/**
036 * Tests a complete certificate order with different challenges.
037 */
038public class OrderHttpIT {
039
040    private static final String TEST_DOMAIN = "example.com";
041
042    private final String bammbammUrl = System.getProperty("bammbammUrl", "http://localhost:14001");
043
044    private final BammBammClient client = new BammBammClient(bammbammUrl);
045
046    /**
047     * Test if a certificate can be ordered via http-01 challenge.
048     */
049    @Test
050    public void testHttpValidation() throws Exception {
051        var session = new Session(boulderURI());
052        var keyPair = createKeyPair();
053
054        var account = new AccountBuilder()
055                    .agreeToTermsOfService()
056                    .useKeyPair(keyPair)
057                    .create(session);
058
059        var domainKeyPair = createKeyPair();
060
061        var order = account.newOrder().domain(TEST_DOMAIN).create();
062
063        for (var auth : order.getAuthorizations()) {
064            var challenge = auth.findChallenge(Http01Challenge.class).orElseThrow();
065
066            client.httpAddToken(challenge.getToken(), challenge.getAuthorization());
067
068            challenge.trigger();
069
070            await()
071                .pollInterval(1, SECONDS)
072                .timeout(30, SECONDS)
073                .conditionEvaluationListener(cond -> updateAuth(auth))
074                .untilAsserted(() -> assertThat(auth.getStatus()).isNotIn(Status.PENDING, Status.PROCESSING));
075
076            assertThat(auth.getStatus()).isEqualTo(Status.VALID);
077
078            client.httpRemoveToken(challenge.getToken());
079        }
080
081        order.execute(domainKeyPair);
082
083        await()
084            .pollInterval(1, SECONDS)
085            .timeout(30, SECONDS)
086            .conditionEvaluationListener(cond -> updateOrder(order))
087            .untilAsserted(() -> assertThat(order.getStatus()).isNotIn(Status.PENDING, Status.PROCESSING));
088
089        var cert = order.getCertificate().getCertificate();
090        assertThat(cert.getNotAfter()).isNotNull();
091        assertThat(cert.getNotBefore()).isNotNull();
092        assertThat(cert.getSubjectX500Principal().getName()).contains("CN=" + TEST_DOMAIN);
093    }
094
095    /**
096     * @return The {@link URI} of the Boulder server to test against.
097     */
098    protected URI boulderURI() {
099        return URI.create("http://localhost:4001/directory");
100    }
101
102    /**
103     * Creates a fresh key pair.
104     *
105     * @return Created new {@link KeyPair}
106     */
107    protected KeyPair createKeyPair() {
108        return KeyPairUtils.createKeyPair(2048);
109    }
110
111    /**
112     * Safely updates the authorization, catching checked exceptions.
113     *
114     * @param auth
115     *            {@link Authorization} to update
116     */
117    private void updateAuth(Authorization auth) {
118        try {
119            auth.update();
120        } catch (AcmeException ex) {
121            throw new AcmeLazyLoadingException(auth, ex);
122        }
123    }
124
125    /**
126     * Safely updates the order, catching checked exceptions.
127     *
128     * @param order
129     *            {@link Order} to update
130     */
131    private void updateOrder(Order order) {
132        try {
133            order.update();
134        } catch (AcmeException ex) {
135            throw new AcmeLazyLoadingException(order, ex);
136        }
137    }
138
139}