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; 015 016import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; 017import static org.assertj.core.api.Assertions.assertThat; 018import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 019import static org.junit.jupiter.api.Assertions.assertThrows; 020import static org.shredzone.acme4j.toolbox.AcmeUtils.parseTimestamp; 021import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; 022import static org.shredzone.acme4j.toolbox.TestUtils.url; 023 024import java.net.HttpURLConnection; 025import java.net.InetAddress; 026import java.net.URL; 027import java.time.Duration; 028import java.util.Arrays; 029 030import org.assertj.core.api.AutoCloseableSoftAssertions; 031import org.junit.jupiter.api.Test; 032import org.shredzone.acme4j.connector.Resource; 033import org.shredzone.acme4j.exception.AcmeNotSupportedException; 034import org.shredzone.acme4j.provider.TestableConnectionProvider; 035import org.shredzone.acme4j.toolbox.JSON; 036import org.shredzone.acme4j.toolbox.JSONBuilder; 037import org.shredzone.acme4j.toolbox.TestUtils; 038 039/** 040 * Unit tests for {@link OrderBuilder}. 041 */ 042public class OrderBuilderTest { 043 044 private final URL resourceUrl = url("http://example.com/acme/resource"); 045 private final URL locationUrl = url(TestUtils.ACCOUNT_URL); 046 047 /** 048 * Test that a new {@link Order} can be created. 049 */ 050 @Test 051 public void testOrderCertificate() throws Exception { 052 var notBefore = parseTimestamp("2016-01-01T00:00:00Z"); 053 var notAfter = parseTimestamp("2016-01-08T00:00:00Z"); 054 055 var provider = new TestableConnectionProvider() { 056 @Override 057 public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { 058 assertThat(url).isEqualTo(resourceUrl); 059 assertThatJson(claims.toString()).isEqualTo(getJSON("requestOrderRequest").toString()); 060 assertThat(login).isNotNull(); 061 return HttpURLConnection.HTTP_CREATED; 062 } 063 064 @Override 065 public JSON readJsonResponse() { 066 return getJSON("requestOrderResponse"); 067 } 068 069 @Override 070 public URL getLocation() { 071 return locationUrl; 072 } 073 }; 074 075 var login = provider.createLogin(); 076 077 provider.putTestResource(Resource.NEW_ORDER, resourceUrl); 078 079 var account = new Account(login); 080 var order = account.newOrder() 081 .domains("example.com", "www.example.com") 082 .domain("example.org") 083 .domains(Arrays.asList("m.example.com", "m.example.org")) 084 .identifier(Identifier.dns("d.example.com")) 085 .identifiers(Arrays.asList( 086 Identifier.dns("d2.example.com"), 087 Identifier.ip(InetAddress.getByName("192.0.2.2")))) 088 .notBefore(notBefore) 089 .notAfter(notAfter) 090 .create(); 091 092 try (var softly = new AutoCloseableSoftAssertions()) { 093 softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder( 094 Identifier.dns("example.com"), 095 Identifier.dns("www.example.com"), 096 Identifier.dns("example.org"), 097 Identifier.dns("m.example.com"), 098 Identifier.dns("m.example.org"), 099 Identifier.dns("d.example.com"), 100 Identifier.dns("d2.example.com"), 101 Identifier.ip(InetAddress.getByName("192.0.2.2"))); 102 softly.assertThat(order.getNotBefore().orElseThrow()) 103 .isEqualTo("2016-01-01T00:10:00Z"); 104 softly.assertThat(order.getNotAfter().orElseThrow()) 105 .isEqualTo("2016-01-08T00:10:00Z"); 106 softly.assertThat(order.getExpires().orElseThrow()) 107 .isEqualTo("2016-01-10T00:00:00Z"); 108 softly.assertThat(order.getStatus()).isEqualTo(Status.PENDING); 109 softly.assertThat(order.isAutoRenewing()).isFalse(); 110 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 111 .isThrownBy(order::getAutoRenewalStartDate); 112 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 113 .isThrownBy(order::getAutoRenewalEndDate); 114 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 115 .isThrownBy(order::getAutoRenewalLifetime); 116 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 117 .isThrownBy(order::getAutoRenewalLifetimeAdjust); 118 softly.assertThatExceptionOfType(AcmeNotSupportedException.class) 119 .isThrownBy(order::isAutoRenewalGetEnabled); 120 softly.assertThat(order.getLocation()).isEqualTo(locationUrl); 121 softly.assertThat(order.getAuthorizations()).isNotNull(); 122 softly.assertThat(order.getAuthorizations()).hasSize(2); 123 } 124 125 provider.close(); 126 } 127 128 /** 129 * Test that a new auto-renewal {@link Order} can be created. 130 */ 131 @Test 132 public void testAutoRenewOrderCertificate() throws Exception { 133 var autoRenewStart = parseTimestamp("2018-01-01T00:00:00Z"); 134 var autoRenewEnd = parseTimestamp("2019-01-01T00:00:00Z"); 135 var validity = Duration.ofDays(7); 136 var predate = Duration.ofDays(6); 137 138 var provider = new TestableConnectionProvider() { 139 @Override 140 public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { 141 assertThat(url).isEqualTo(resourceUrl); 142 assertThatJson(claims.toString()).isEqualTo(getJSON("requestAutoRenewOrderRequest").toString()); 143 assertThat(login).isNotNull(); 144 return HttpURLConnection.HTTP_CREATED; 145 } 146 147 @Override 148 public JSON readJsonResponse() { 149 return getJSON("requestAutoRenewOrderResponse"); 150 } 151 152 @Override 153 public URL getLocation() { 154 return locationUrl; 155 } 156 }; 157 158 var login = provider.createLogin(); 159 160 provider.putMetadata("auto-renewal", JSON.empty()); 161 provider.putTestResource(Resource.NEW_ORDER, resourceUrl); 162 163 var account = new Account(login); 164 var order = account.newOrder() 165 .domain("example.org") 166 .autoRenewal() 167 .autoRenewalStart(autoRenewStart) 168 .autoRenewalEnd(autoRenewEnd) 169 .autoRenewalLifetime(validity) 170 .autoRenewalLifetimeAdjust(predate) 171 .autoRenewalEnableGet() 172 .replaces("aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE") 173 .create(); 174 175 try (var softly = new AutoCloseableSoftAssertions()) { 176 softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder(Identifier.dns("example.org")); 177 softly.assertThat(order.getNotBefore()).isEmpty(); 178 softly.assertThat(order.getNotAfter()).isEmpty(); 179 softly.assertThat(order.isAutoRenewing()).isTrue(); 180 softly.assertThat(order.getAutoRenewalStartDate().orElseThrow()).isEqualTo(autoRenewStart); 181 softly.assertThat(order.getAutoRenewalEndDate()).isEqualTo(autoRenewEnd); 182 softly.assertThat(order.getAutoRenewalLifetime()).isEqualTo(validity); 183 softly.assertThat(order.getAutoRenewalLifetimeAdjust().orElseThrow()).isEqualTo(predate); 184 softly.assertThat(order.isAutoRenewalGetEnabled()).isTrue(); 185 softly.assertThat(order.getLocation()).isEqualTo(locationUrl); 186 } 187 188 provider.close(); 189 } 190 191 /** 192 * Test that a new {@link Order} with ancestor domain can be created. 193 */ 194 @Test 195 public void testOrderCertificateWithAncestor() throws Exception { 196 var notBefore = parseTimestamp("2016-01-01T00:00:00Z"); 197 var notAfter = parseTimestamp("2016-01-08T00:00:00Z"); 198 199 var provider = new TestableConnectionProvider() { 200 @Override 201 public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { 202 assertThat(url).isEqualTo(resourceUrl); 203 assertThatJson(claims.toString()).isEqualTo(getJSON("requestOrderRequestSub").toString()); 204 assertThat(login).isNotNull(); 205 return HttpURLConnection.HTTP_CREATED; 206 } 207 208 @Override 209 public JSON readJsonResponse() { 210 return getJSON("requestOrderResponseSub"); 211 } 212 213 @Override 214 public URL getLocation() { 215 return locationUrl; 216 } 217 }; 218 219 var login = provider.createLogin(); 220 221 provider.putTestResource(Resource.NEW_ORDER, resourceUrl); 222 provider.putMetadata("subdomainAuthAllowed", true); 223 224 var account = new Account(login); 225 var order = account.newOrder() 226 .identifier(Identifier.dns("foo.bar.example.com").withAncestorDomain("example.com")) 227 .notBefore(notBefore) 228 .notAfter(notAfter) 229 .create(); 230 231 try (var softly = new AutoCloseableSoftAssertions()) { 232 softly.assertThat(order.getIdentifiers()).containsExactlyInAnyOrder( 233 Identifier.dns("foo.bar.example.com")); 234 softly.assertThat(order.getNotBefore().orElseThrow()) 235 .isEqualTo("2016-01-01T00:10:00Z"); 236 softly.assertThat(order.getNotAfter().orElseThrow()) 237 .isEqualTo("2016-01-08T00:10:00Z"); 238 softly.assertThat(order.getExpires().orElseThrow()) 239 .isEqualTo("2016-01-10T00:00:00Z"); 240 softly.assertThat(order.getStatus()).isEqualTo(Status.PENDING); 241 softly.assertThat(order.getLocation()).isEqualTo(locationUrl); 242 softly.assertThat(order.getAuthorizations()).isNotNull(); 243 softly.assertThat(order.getAuthorizations()).hasSize(2); 244 } 245 246 provider.close(); 247 } 248 249 /** 250 * Test that a new {@link Order} with ancestor domain fails if not supported. 251 */ 252 @Test 253 public void testOrderCertificateWithAncestorFails() throws Exception { 254 var provider = new TestableConnectionProvider(); 255 256 var login = provider.createLogin(); 257 258 provider.putTestResource(Resource.NEW_ORDER, resourceUrl); 259 260 assertThat(login.getSession().getMetadata().isSubdomainAuthAllowed()).isFalse(); 261 262 var account = new Account(login); 263 assertThatExceptionOfType(AcmeNotSupportedException.class).isThrownBy(() -> 264 account.newOrder() 265 .identifier(Identifier.dns("foo.bar.example.com").withAncestorDomain("example.com")) 266 .create() 267 ); 268 269 provider.close(); 270 } 271 272 /** 273 * Test that an auto-renewal {@link Order} cannot be created if unsupported by the CA. 274 */ 275 @Test 276 public void testAutoRenewOrderCertificateFails() { 277 assertThrows(AcmeNotSupportedException.class, () -> { 278 var provider = new TestableConnectionProvider(); 279 provider.putTestResource(Resource.NEW_ORDER, resourceUrl); 280 281 var login = provider.createLogin(); 282 283 var account = new Account(login); 284 account.newOrder() 285 .domain("example.org") 286 .autoRenewal() 287 .create(); 288 289 provider.close(); 290 }); 291 } 292 293 /** 294 * Test that auto-renew and notBefore/notAfter cannot be mixed. 295 */ 296 @Test 297 public void testAutoRenewNotMixed() throws Exception { 298 var someInstant = parseTimestamp("2018-01-01T00:00:00Z"); 299 300 var provider = new TestableConnectionProvider(); 301 var login = provider.createLogin(); 302 303 var account = new Account(login); 304 305 assertThrows(IllegalArgumentException.class, () -> { 306 OrderBuilder ob = account.newOrder().autoRenewal(); 307 ob.notBefore(someInstant); 308 }, "accepted notBefore"); 309 310 assertThrows(IllegalArgumentException.class, () -> { 311 OrderBuilder ob = account.newOrder().autoRenewal(); 312 ob.notAfter(someInstant); 313 }, "accepted notAfter"); 314 315 assertThrows(IllegalArgumentException.class, () -> { 316 OrderBuilder ob = account.newOrder().notBefore(someInstant); 317 ob.autoRenewal(); 318 }, "accepted autoRenewal"); 319 320 assertThrows(IllegalArgumentException.class, () -> { 321 OrderBuilder ob = account.newOrder().notBefore(someInstant); 322 ob.autoRenewalStart(someInstant); 323 }, "accepted autoRenewalStart"); 324 325 assertThrows(IllegalArgumentException.class, () -> { 326 OrderBuilder ob = account.newOrder().notBefore(someInstant); 327 ob.autoRenewalEnd(someInstant); 328 }, "accepted autoRenewalEnd"); 329 330 assertThrows(IllegalArgumentException.class, () -> { 331 OrderBuilder ob = account.newOrder().notBefore(someInstant); 332 ob.autoRenewalLifetime(Duration.ofDays(7)); 333 }, "accepted autoRenewalLifetime"); 334 335 provider.close(); 336 } 337 338}