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