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;
015
016import java.io.IOException;
017import java.util.Arrays;
018import java.util.Objects;
019
020import javax.annotation.ParametersAreNonnullByDefault;
021
022import org.apache.http.HttpResponse;
023import org.apache.http.HttpStatus;
024import org.apache.http.client.ClientProtocolException;
025import org.apache.http.client.HttpClient;
026import org.apache.http.client.methods.HttpPost;
027import org.apache.http.entity.ContentType;
028import org.apache.http.entity.StringEntity;
029import org.apache.http.impl.client.HttpClients;
030import org.apache.http.util.EntityUtils;
031import org.shredzone.acme4j.toolbox.JSONBuilder;
032
033/**
034 * The BammBamm client connects to the pebble-challtestsrv.
035 */
036@ParametersAreNonnullByDefault
037public class BammBammClient {
038    private static final HttpClient CLIENT = HttpClients.createDefault();
039
040    private final String baseUrl;
041
042    /**
043     * Creates a new BammBamm client.
044     *
045     * @param baseUrl
046     *            Base URL of the pebble-challtestsrv server to connect to.
047     */
048    public BammBammClient(String baseUrl) {
049        this.baseUrl = Objects.requireNonNull(baseUrl) + '/';
050    }
051
052    /**
053     * Adds a HTTP token.
054     *
055     * @param token
056     *            Token to add
057     * @param challenge
058     *            Challenge to respond with
059     */
060    public void httpAddToken(String token, String challenge) throws IOException {
061        JSONBuilder jb = new JSONBuilder();
062        jb.put("token", token);
063        jb.put("content", challenge);
064        sendRequest("add-http01", jb.toString());
065    }
066
067    /**
068     * Removes a HTTP token.
069     *
070     * @param token
071     *            Token to remove
072     */
073    public void httpRemoveToken(String token) throws IOException {
074        JSONBuilder jb = new JSONBuilder();
075        jb.put("token", token);
076        sendRequest("del-http01", jb.toString());
077    }
078
079    /**
080     * Adds an A Record to the DNS. Only one A Record is supported per domain. If another
081     * A Record is set, it will replace the existing one.
082     *
083     * @param domain
084     *            Domain of the A Record
085     * @param ip
086     *            IP address or domain name. If a domain name is used, it will be resolved
087     *            and the IP will be used.
088     */
089    public void dnsAddARecord(String domain, String ip) throws IOException {
090        JSONBuilder jb = new JSONBuilder();
091        jb.put("host", domain);
092        jb.array("addresses", Arrays.asList(ip));
093        sendRequest("add-a", jb.toString());
094    }
095
096    /**
097     * Removes an A Record from the DNS.
098     *
099     * @param domain
100     *            Domain to remove the A Record from
101     */
102    public void dnsRemoveARecord(String domain) throws IOException {
103        JSONBuilder jb = new JSONBuilder();
104        jb.put("host", domain);
105        sendRequest("clear-a", jb.toString());
106    }
107
108    /**
109     * Adds a TXT Record to the DNS. Only one TXT Record is supported per domain. If
110     * another TXT Record is set, it will replace the existing one.
111     *
112     * @param domain
113     *            Domain to add the TXT Record to
114     * @param txt
115     *            TXT record to add
116     */
117    public void dnsAddTxtRecord(String domain, String txt) throws IOException {
118        JSONBuilder jb = new JSONBuilder();
119        jb.put("host", domain + '.');
120        jb.put("value", txt);
121        sendRequest("set-txt", jb.toString());
122    }
123
124    /**
125     * Removes a TXT Record from the DNS.
126     *
127     * @param domain
128     *            Domain to remove the TXT Record from
129     */
130    public void dnsRemoveTxtRecord(String domain) throws IOException {
131        JSONBuilder jb = new JSONBuilder();
132        jb.put("host", domain + '.');
133        sendRequest("clear-txt", jb.toString());
134    }
135
136    /**
137     * Adds a certificate for TLS-ALPN tests.
138     *
139     * @param domain
140     *            Certificate domain to be added
141     * @param keyauth
142     *            Key authorization to be used for validation
143     */
144    public void tlsAlpnAddCertificate(String domain, String keyauth) throws IOException {
145        JSONBuilder jb = new JSONBuilder();
146        jb.put("host", domain);
147        jb.put("content", keyauth);
148        sendRequest("add-tlsalpn01", jb.toString());
149    }
150
151    /**
152     * Removes a certificate.
153     *
154     * @param domain
155     *            Certificate domain to be removed
156     */
157    public void tlsAlpnRemoveCertificate(String domain) throws IOException {
158        JSONBuilder jb = new JSONBuilder();
159        jb.put("host", domain);
160        sendRequest("del-tlsalpn01", jb.toString());
161    }
162
163    /**
164     * Sends a request to the pebble-challtestsrv.
165     *
166     * @param call
167     *            Endpoint to be called
168     * @param body
169     *            JSON body
170     */
171    private void sendRequest(String call, String body) throws IOException {
172        try {
173            HttpPost httppost = new HttpPost(baseUrl + call);
174            httppost.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
175
176            HttpResponse response = CLIENT.execute(httppost);
177
178            EntityUtils.consume(response.getEntity());
179
180            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
181                throw new IOException(response.getStatusLine().getReasonPhrase());
182            }
183        } catch (ClientProtocolException ex) {
184            throw new IOException(ex);
185        }
186    }
187
188}