diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java index 85b14e2f..53ffe9a2 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java @@ -53,23 +53,8 @@ public class Account extends AcmeJsonResource { private static final String KEY_CONTACT = "contact"; private static final String KEY_STATUS = "status"; - protected Account(Session session, URL location) { - super(session); - setLocation(location); - session.setAccountLocation(location); - } - - /** - * Creates a new instance of {@link Account} and binds it to the {@link Session}. - * - * @param session - * {@link Session} to be used - * @param location - * Location URI of the account - * @return {@link Account} bound to the session and location - */ - public static Account bind(Session session, URL location) { - return new Account(session, location); + protected Account(Login login) { + super(login, login.getAccountLocation()); } /** @@ -112,14 +97,14 @@ public class Account extends AcmeJsonResource { */ public Iterator getOrders() throws AcmeException { URL ordersUrl = getJSON().get(KEY_ORDERS).asURL(); - return new ResourceIterator<>(getSession(), KEY_ORDERS, ordersUrl, Order::bind); + return new ResourceIterator<>(getLogin(), KEY_ORDERS, ordersUrl, Login::bindOrder); } @Override public void update() throws AcmeException { LOG.debug("update Account"); - try (Connection conn = getSession().provider().connect()) { - conn.sendSignedRequest(getLocation(), new JSONBuilder(), getSession()); + try (Connection conn = connect()) { + conn.sendSignedRequest(getLocation(), new JSONBuilder(), getLogin()); setJSON(conn.readJsonResponse()); } } @@ -130,7 +115,7 @@ public class Account extends AcmeJsonResource { * @return {@link OrderBuilder} object */ public OrderBuilder newOrder() throws AcmeException { - return new OrderBuilder(getSession()); + return new OrderBuilder(getLogin()); } /** @@ -161,15 +146,15 @@ public class Account extends AcmeJsonResource { } LOG.debug("preAuthorizeDomain {}", domain); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); claims.object("identifier") .put("type", "dns") .put("value", toAce(domain)); - conn.sendSignedRequest(newAuthzUrl, claims, getSession()); + conn.sendSignedRequest(newAuthzUrl, claims, getLogin()); - Authorization auth = new Authorization(getSession(), conn.getLocation()); + Authorization auth = getLogin().bindAuthorization(conn.getLocation()); auth.setJSON(conn.readJsonResponse()); return auth; } @@ -186,14 +171,14 @@ public class Account extends AcmeJsonResource { */ public void changeKey(KeyPair newKeyPair) throws AcmeException { Objects.requireNonNull(newKeyPair, "newKeyPair"); - if (Arrays.equals(getSession().getKeyPair().getPrivate().getEncoded(), + if (Arrays.equals(getLogin().getKeyPair().getPrivate().getEncoded(), newKeyPair.getPrivate().getEncoded())) { throw new IllegalArgumentException("newKeyPair must actually be a new key pair"); } LOG.debug("key-change"); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { URL keyChangeUrl = getSession().resourceUrl(Resource.KEY_CHANGE); PublicJsonWebKey newKeyJwk = PublicJsonWebKey.Factory.newPublicJwk(newKeyPair.getPublic()); @@ -214,9 +199,9 @@ public class Account extends AcmeJsonResource { outerClaim.put("signature", innerJws.getEncodedSignature()); outerClaim.put("payload", innerJws.getEncodedPayload()); - conn.sendSignedRequest(keyChangeUrl, outerClaim, getSession()); + conn.sendSignedRequest(keyChangeUrl, outerClaim, getLogin()); - getSession().setKeyPair(newKeyPair); + getLogin().setKeyPair(newKeyPair); } catch (JoseException ex) { throw new AcmeProtocolException("Cannot sign key-change", ex); } @@ -230,11 +215,11 @@ public class Account extends AcmeJsonResource { */ public void deactivate() throws AcmeException { LOG.debug("deactivate"); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); claims.put(KEY_STATUS, "deactivated"); - conn.sendSignedRequest(getLocation(), claims, getSession()); + conn.sendSignedRequest(getLocation(), claims, getLogin()); setJSON(conn.readJsonResponse()); } @@ -298,13 +283,13 @@ public class Account extends AcmeJsonResource { */ public void commit() throws AcmeException { LOG.debug("modify/commit"); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); if (!editContacts.isEmpty()) { claims.put(KEY_CONTACT, editContacts); } - conn.sendSignedRequest(getLocation(), claims, getSession()); + conn.sendSignedRequest(getLocation(), claims, getLogin()); setJSON(conn.readJsonResponse()); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java index ffdeae48..23a48964 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java @@ -18,6 +18,7 @@ import static org.shredzone.acme4j.toolbox.AcmeUtils.macKeyAlgorithm; import java.net.URI; import java.net.URL; +import java.security.KeyPair; import java.security.PublicKey; import java.util.ArrayList; import java.util.List; @@ -50,6 +51,7 @@ public class AccountBuilder { private Boolean termsOfServiceAgreed; private Boolean onlyExisting; private String keyIdentifier; + private KeyPair keyPair; private SecretKey macKey; /** @@ -110,6 +112,18 @@ public class AccountBuilder { return this; } + /** + * Sets the {@link KeyPair} to be used for this account. + * + * @param keyPair + * Account's {@link KeyPair} + * @return itself + */ + public AccountBuilder useKeyPair(KeyPair keyPair) { + this.keyPair = requireNonNull(keyPair, "keyPair"); + return this; + } + /** * Sets a Key Identifier and MAC key provided by the CA. Use this if your CA requires * an individual account identification, e.g. your customer number. @@ -120,7 +134,7 @@ public class AccountBuilder { * MAC key * @return itself */ - public AccountBuilder useKeyIdentifier(String kid, SecretKey macKey) { + public AccountBuilder withKeyIdentifier(String kid, SecretKey macKey) { if (kid != null && kid.isEmpty()) { throw new IllegalArgumentException("kid must not be empty"); } @@ -139,9 +153,9 @@ public class AccountBuilder { * Base64url encoded MAC key. It will be decoded for your convenience. * @return itself */ - public AccountBuilder useKeyIdentifier(String kid, String encodedMacKey) { + public AccountBuilder withKeyIdentifier(String kid, String encodedMacKey) { byte[] encodedKey = AcmeUtils.base64UrlDecode(requireNonNull(encodedMacKey, "encodedMacKey")); - return useKeyIdentifier(kid, new HmacKey(encodedKey)); + return withKeyIdentifier(kid, new HmacKey(encodedKey)); } /** @@ -152,12 +166,27 @@ public class AccountBuilder { * @return {@link Account} referring to the new account */ public Account create(Session session) throws AcmeException { - LOG.debug("create"); + return createLogin(session).getAccount(); + } - if (session.getAccountLocation() != null) { - throw new IllegalArgumentException("session already seems to have an Account"); + /** + * Creates a new account. + *

+ * This method returns a ready to use {@link Login} for the new {@link Account}. + * + * @param session + * {@link Session} to be used for registration + * @return {@link Login} referring to the new account + */ + public Login createLogin(Session session) throws AcmeException { + requireNonNull(session, "session"); + + if (keyPair == null) { + throw new IllegalStateException("Use AccountBuilder.useKeyPair() to set the account's key pair."); } + LOG.debug("create"); + try (Connection conn = session.provider().connect()) { URL resourceUrl = session.resourceUrl(Resource.NEW_ACCOUNT); @@ -170,19 +199,19 @@ public class AccountBuilder { } if (keyIdentifier != null) { claims.put("externalAccountBinding", createExternalAccountBinding( - keyIdentifier, session.getKeyPair().getPublic(), macKey, resourceUrl)); + keyIdentifier, keyPair.getPublic(), macKey, resourceUrl)); } if (onlyExisting != null) { claims.put("onlyReturnExisting", onlyExisting); } - conn.sendSignedRequest(resourceUrl, claims, session, true); + conn.sendSignedRequest(resourceUrl, claims, session, keyPair); URL location = conn.getLocation(); - Account account = new Account(session, location); - account.setJSON(conn.readJsonResponse()); - return account; + Login login = new Login(location, keyPair, session); + login.getAccount().setJSON(conn.readJsonResponse()); + return login; } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeJsonResource.java b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeJsonResource.java index bd4054fb..eca9cd0c 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeJsonResource.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeJsonResource.java @@ -13,6 +13,7 @@ */ package org.shredzone.acme4j; +import java.net.URL; import java.util.Objects; import org.shredzone.acme4j.connector.Connection; @@ -35,24 +36,13 @@ public abstract class AcmeJsonResource extends AcmeResource { /** * Create a new {@link AcmeJsonResource}. * - * @param session - * {@link Session} the resource is bound with + * @param login + * {@link Login} the resource is bound with + * @param location + * Location {@link URL} of this resource */ - protected AcmeJsonResource(Session session) { - super(session); - } - - /** - * Create a new {@link AcmeJsonResource} and use the given data. - * - * @param session - * {@link Session} the resource is bound with - * @param data - * Initial {@link JSON} data - */ - protected AcmeJsonResource(Session session, JSON data) { - super(session); - setJSON(data); + protected AcmeJsonResource(Login login, URL location) { + super(login, location); } /** @@ -122,7 +112,7 @@ public abstract class AcmeJsonResource extends AcmeResource { public void update() throws AcmeException { String resourceType = getClass().getSimpleName(); LOG.debug("update {}", resourceType); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { conn.sendRequest(getLocation(), getSession()); setJSON(conn.readJsonResponse()); conn.handleRetryAfter(resourceType + " is not completed yet"); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeResource.java b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeResource.java index 3ce03cbc..85ff1213 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeResource.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeResource.java @@ -17,65 +17,67 @@ import java.io.Serializable; import java.net.URL; import java.util.Objects; +import org.shredzone.acme4j.connector.Connection; + /** * A generic ACME resource. */ public abstract class AcmeResource implements Serializable { private static final long serialVersionUID = -7930580802257379731L; - private transient Session session; - private URL location; + private transient Login login; + private final URL location; /** * Create a new {@link AcmeResource}. * - * @param session - * {@link Session} the resource is bound with + * @param login + * {@link Login} the resource is bound with */ - protected AcmeResource(Session session) { - rebind(session); + protected AcmeResource(Login login, URL location) { + this.location = Objects.requireNonNull(location, "location"); + rebind(login); + } + + /** + * Gets the {@link Login} this resource is bound with. + */ + protected Login getLogin() { + if (login == null) { + throw new IllegalStateException("Use rebind() for binding this object to a login."); + } + return login; } /** * Gets the {@link Session} this resource is bound with. */ protected Session getSession() { - if (session == null) { - throw new IllegalStateException("Use rebind() for binding this object to a session."); - } - - return session; + return getLogin().getSession(); } /** - * Sets a new {@link Session}. + * Opens a {@link Connection} to the provider. */ - protected void setSession(Session session) { - this.session = Objects.requireNonNull(session, "session"); + protected Connection connect() { + return getSession().provider().connect(); } /** - * Sets the resource's location. - */ - protected void setLocation(URL location) { - this.location = Objects.requireNonNull(location, "location"); - } - - /** - * Rebinds this resource to a {@link Session}. + * Rebinds this resource to a {@link Login}. *

- * Sessions are not serialized, because they contain volatile session data and also a + * Logins are not serialized, because they contain volatile session data and also a * private key. After de-serialization of an {@link AcmeResource}, use this method to - * rebind it to a {@link Session}. + * rebind it to a {@link Login}. * - * @param session - * {@link Session} to bind this resource to + * @param login + * {@link Login} to bind this resource to */ - public void rebind(Session session) { - if (this.session != null) { - throw new IllegalStateException("Resource is already bound to a session"); + public void rebind(Login login) { + if (this.login != null) { + throw new IllegalStateException("Resource is already bound to a login"); } - setSession(session); + this.login = login; } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java index 25edcd58..f8051661 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java @@ -38,23 +38,8 @@ public class Authorization extends AcmeJsonResource { private static final long serialVersionUID = -3116928998379417741L; private static final Logger LOG = LoggerFactory.getLogger(Authorization.class); - protected Authorization(Session session, URL location) { - super(session); - setLocation(location); - } - - /** - * Creates a new instance of {@link Authorization} and binds it to the - * {@link Session}. - * - * @param session - * {@link Session} to be used - * @param location - * Location of the Authorization - * @return {@link Authorization} bound to the session and location - */ - public static Authorization bind(Session session, URL location) { - return new Authorization(session, location); + protected Authorization(Login login, URL location) { + super(login, location); } /** @@ -90,13 +75,13 @@ public class Authorization extends AcmeJsonResource { * Gets a list of all challenges offered by the server. */ public List getChallenges() { - Session session = getSession(); + Login login = getLogin(); return Collections.unmodifiableList(getJSON().get("challenges") .asArray() .stream() .map(Value::asObject) - .map(session::createChallenge) + .map(login::createChallenge) .collect(toList())); } @@ -124,11 +109,11 @@ public class Authorization extends AcmeJsonResource { */ public void deactivate() throws AcmeException { LOG.debug("deactivate"); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); claims.put("status", "deactivated"); - conn.sendSignedRequest(getLocation(), claims, getSession()); + conn.sendSignedRequest(getLocation(), claims, getLogin()); setJSON(conn.readJsonResponse()); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java index dab4b7e1..b12638a4 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java @@ -17,7 +17,6 @@ import static java.util.Collections.unmodifiableList; import java.io.IOException; import java.io.Writer; -import java.net.URI; import java.net.URL; import java.security.KeyPair; import java.security.cert.CertificateEncodingException; @@ -25,7 +24,6 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.function.BiFunction; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Resource; @@ -47,27 +45,11 @@ public class Certificate extends AcmeResource { private static final long serialVersionUID = 7381527770159084201L; private static final Logger LOG = LoggerFactory.getLogger(Certificate.class); - protected static BiFunction revokeSessionFactory = Session::new; - private ArrayList certChain = null; private ArrayList alternates = null; - protected Certificate(Session session, URL certUrl) { - super(session); - setLocation(certUrl); - } - - /** - * Creates a new instance of {@link Certificate} and binds it to the {@link Session}. - * - * @param session - * {@link Session} to be used - * @param location - * Location of the Certificate - * @return {@link Certificate} bound to the session and location - */ - public static Certificate bind(Session session, URL location) { - return new Certificate(session, location); + protected Certificate(Login login, URL certUrl) { + super(login, certUrl); } /** @@ -79,7 +61,7 @@ public class Certificate extends AcmeResource { public void download() throws AcmeException { if (certChain == null) { LOG.debug("download"); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { conn.sendRequest(getLocation(), getSession()); alternates = new ArrayList<>(conn.getLinks("alternate")); certChain = new ArrayList<>(conn.readCertificates()); @@ -162,14 +144,14 @@ public class Certificate extends AcmeResource { throw new AcmeException("Server does not allow certificate revocation"); } - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); claims.putBase64("certificate", getCertificate().getEncoded()); if (reason != null) { claims.put("reason", reason.getReasonCode()); } - conn.sendSignedRequest(resUrl, claims, getSession(), true); + conn.sendSignedRequest(resUrl, claims, getSession(), getLogin().getKeyPair()); } catch (CertificateEncodingException ex) { throw new AcmeProtocolException("Invalid certificate", ex); } @@ -179,8 +161,8 @@ public class Certificate extends AcmeResource { * Revoke a certificate. This call is meant to be used for revoking certificates if * the account's key pair was lost. * - * @param serverUri - * {@link URI} of the ACME server + * @param session + * {@link Session} connected to the ACME server * @param domainKeyPair * Key pair the CSR was signed with * @param cert @@ -190,10 +172,9 @@ public class Certificate extends AcmeResource { * used when generating OCSP responses and CRLs. {@code null} to give no * reason. */ - public static void revoke(URI serverUri, KeyPair domainKeyPair, X509Certificate cert, + public static void revoke(Session session, KeyPair domainKeyPair, X509Certificate cert, RevocationReason reason) throws AcmeException { LOG.debug("revoke immediately"); - Session session = revokeSessionFactory.apply(serverUri, domainKeyPair); URL resUrl = session.resourceUrl(Resource.REVOKE_CERT); if (resUrl == null) { @@ -207,7 +188,7 @@ public class Certificate extends AcmeResource { claims.put("reason", reason.getReasonCode()); } - conn.sendSignedRequest(resUrl, claims, session, true); + conn.sendSignedRequest(resUrl, claims, session, domainKeyPair); } catch (CertificateEncodingException ex) { throw new AcmeProtocolException("Invalid certificate", ex); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Login.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Login.java new file mode 100644 index 00000000..f65ed041 --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Login.java @@ -0,0 +1,143 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2018 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ +package org.shredzone.acme4j; + +import static java.util.Objects.requireNonNull; + +import java.net.URL; +import java.security.KeyPair; +import java.util.Objects; + +import org.shredzone.acme4j.challenge.Challenge; +import org.shredzone.acme4j.exception.AcmeProtocolException; +import org.shredzone.acme4j.toolbox.JSON; + +/** + * A {@link Login} is a {@link Session} that is connected to an {@link Account} at the + * ACME server. It contains the account's {@link KeyPair} and the {@link URL} of the + * account. + *

+ * Note that {@link Login} objects are not serializable, as they contain a keypair and + * volatile data. + */ +public class Login { + + private final Session session; + private final URL accountLocation; + private final Account account; + private KeyPair keyPair; + + /** + * Creates a new {@link Login}. + * + * @param accountLocation + * Account location {@link URL} + * @param keyPair + * {@link KeyPair} of the account + * @param session + * {@link Session} to be used + */ + public Login(URL accountLocation, KeyPair keyPair, Session session) { + this.accountLocation = Objects.requireNonNull(accountLocation, "accountLocation"); + this.keyPair = Objects.requireNonNull(keyPair, "keyPair"); + this.session = Objects.requireNonNull(session, "session"); + this.account = new Account(this); + } + + /** + * Gets the {@link Session} that is used. + */ + public Session getSession() { + return session; + } + + /** + * Gets the {@link KeyPair} of the ACME account. + */ + public KeyPair getKeyPair() { + return keyPair; + } + + /** + * Gets the location {@link URL} of the account. + */ + public URL getAccountLocation() { + return accountLocation; + } + + /** + * Gets the {@link Account} that is bound to this login. + * + * @return {@link Account} bound to the login + */ + public Account getAccount() { + return account; + } + + /** + * Creates a new instance of {@link Authorization} and binds it to this login. + * + * @param location + * Location of the Authorization + * @return {@link Authorization} bound to the login + */ + public Authorization bindAuthorization(URL location) { + return new Authorization(this, requireNonNull(location, "location")); + } + + /** + * Creates a new instance of {@link Certificate} and binds it to this login. + * + * @param location + * Location of the Certificate + * @return {@link Certificate} bound to the login + */ + public Certificate bindCertificate(URL location) { + return new Certificate(this, requireNonNull(location, "location")); + } + + /** + * Creates a new instance of {@link Order} and binds it to this login. + * + * @param location + * Location URL of the order + * @return {@link Order} bound to the login + */ + public Order bindOrder(URL location) { + return new Order(this, requireNonNull(location, "location")); + } + + /** + * Creates a {@link Challenge} instance for the given challenge data. + * + * @param data + * Challenge JSON data + * @return {@link Challenge} instance + */ + public Challenge createChallenge(JSON data) { + Challenge challenge = session.provider().createChallenge(this, data); + if (challenge == null) { + throw new AcmeProtocolException("Could not create challenge for: " + data); + } + return challenge; + } + + /** + * Sets a different {@link KeyPair}. + */ + protected void setKeyPair(KeyPair keyPair) { + this.keyPair = Objects.requireNonNull(keyPair, "keyPair"); + } + +} diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java index 06991ee9..fb648ed8 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java @@ -34,22 +34,8 @@ public class Order extends AcmeJsonResource { private static final long serialVersionUID = 5435808648658292177L; private static final Logger LOG = LoggerFactory.getLogger(Order.class); - protected Order(Session session, URL location) { - super(session); - setLocation(location); - } - - /** - * Creates a new instance of {@link Order} and binds it to the {@link Session}. - * - * @param session - * {@link Session} to be used - * @param location - * Location URL of the order - * @return {@link Order} bound to the session and location - */ - public static Order bind(Session session, URL location) { - return new Order(session, location); + protected Order(Login login, URL location) { + super(login, location); } /** @@ -103,12 +89,12 @@ public class Order extends AcmeJsonResource { * Gets the {@link Authorization} required for this order. */ public List getAuthorizations() { - Session session = getSession(); + Login login = getLogin(); return Collections.unmodifiableList(getJSON().get("authorizations") .asArray() .stream() .map(Value::asURL) - .map(loc -> Authorization.bind(session, loc)) + .map(login::bindAuthorization) .collect(toList())); } @@ -127,7 +113,7 @@ public class Order extends AcmeJsonResource { public Certificate getCertificate() { return getJSON().get("certificate").optional() .map(Value::asURL) - .map(certUrl -> Certificate.bind(getSession(), certUrl)) + .map(getLogin()::bindCertificate) .orElse(null); } @@ -147,11 +133,11 @@ public class Order extends AcmeJsonResource { */ public void execute(byte[] csr) throws AcmeException { LOG.debug("finalize"); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); claims.putBase64("csr", csr); - conn.sendSignedRequest(getFinalizeLocation(), claims, getSession()); + conn.sendSignedRequest(getFinalizeLocation(), claims, getLogin()); } invalidate(); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java index 6255dc7b..c882a26a 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java @@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory; public class OrderBuilder { private static final Logger LOG = LoggerFactory.getLogger(OrderBuilder.class); - private final Session session; + private final Login login; private final Set domainSet = new LinkedHashSet<>(); private Instant notBefore; @@ -44,10 +44,11 @@ public class OrderBuilder { /** * Create a new {@link OrderBuilder}. * - * @param session {@link Session} to bind with + * @param login + * {@link Login} to bind with */ - protected OrderBuilder(Session session) { - this.session = session; + protected OrderBuilder(Login login) { + this.login = login; } /** @@ -125,6 +126,8 @@ public class OrderBuilder { throw new IllegalArgumentException("At least one domain is required"); } + Session session = login.getSession(); + Object[] identifiers = new Object[domainSet.size()]; Iterator di = domainSet.iterator(); for (int ix = 0; ix < identifiers.length; ix++) { @@ -146,9 +149,9 @@ public class OrderBuilder { claims.put("notAfter", notAfter); } - conn.sendSignedRequest(session.resourceUrl(Resource.NEW_ORDER), claims, session); + conn.sendSignedRequest(session.resourceUrl(Resource.NEW_ORDER), claims, login); - Order order = new Order(session, conn.getLocation()); + Order order = new Order(login, conn.getLocation()); order.setJSON(conn.readJsonResponse()); return order; } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Session.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Session.java index 72355fde..a6397a44 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Session.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Session.java @@ -26,19 +26,13 @@ import java.util.ServiceLoader; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.StreamSupport; -import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.exception.AcmeException; -import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.provider.AcmeProvider; import org.shredzone.acme4j.toolbox.JSON; /** - * A session stores the ACME server URI and the account's key pair. It also tracks - * communication parameters. - *

- * Note that {@link Session} objects are not serializable, as they contain a keypair and - * volatile data. + * A session stores the ACME server URI. It also tracks communication parameters. */ public class Session { private final AtomicReference> resourceMap = new AtomicReference<>(); @@ -46,8 +40,6 @@ public class Session { private final URI serverUri; private final AcmeProvider provider; - private KeyPair keyPair; - private URL accountLocation; private byte[] nonce; private JSON directoryJson; private Locale locale = Locale.getDefault(); @@ -58,11 +50,9 @@ public class Session { * * @param serverUri * URI string of the ACME server - * @param keyPair - * {@link KeyPair} of the ACME account */ - public Session(String serverUri, KeyPair keyPair) { - this(URI.create(serverUri), keyPair); + public Session(String serverUri) { + this(URI.create(serverUri)); } /** @@ -70,14 +60,11 @@ public class Session { * * @param serverUri * {@link URI} of the ACME server - * @param keyPair - * {@link KeyPair} of the ACME account * @throws IllegalArgumentException * if no ACME provider was found for the server URI. */ - public Session(URI serverUri, KeyPair keyPair) { + public Session(URI serverUri) { this.serverUri = Objects.requireNonNull(serverUri, "serverUri"); - this.keyPair = Objects.requireNonNull(keyPair, "keyPair"); final URI localServerUri = serverUri; @@ -93,6 +80,19 @@ public class Session { .orElseThrow(() -> new IllegalArgumentException("No ACME provider found for " + localServerUri)); } + /** + * Logs into an existing account. + * + * @param accountLocation + * Location {@link URL} of the account + * @param accountKeyPair + * Account {@link KeyPair} + * @return {@link Login} to this account + */ + public Login login(URL accountLocation, KeyPair accountKeyPair) { + return new Login(accountLocation, accountKeyPair, this); + } + /** * Gets the ACME server {@link URI} of this session. */ @@ -100,34 +100,6 @@ public class Session { return serverUri; } - /** - * Gets the {@link KeyPair} of the ACME account. - */ - public KeyPair getKeyPair() { - return keyPair; - } - - /** - * Sets a different {@link KeyPair}. - */ - public void setKeyPair(KeyPair keyPair) { - this.keyPair = keyPair; - } - - /** - * Gets the location {@link URL} of the account logged into this session. - */ - public URL getAccountLocation() { - return accountLocation; - } - - /** - * Sets the location {@link URL} of the account logged into this session. - */ - public void setAccountLocation(URL accountLocation) { - this.accountLocation = accountLocation; - } - /** * Gets the last nonce, or {@code null} if the session is new. */ @@ -166,21 +138,6 @@ public class Session { return provider; } - /** - * Creates a {@link Challenge} instance for the given challenge data. - * - * @param data - * Challenge JSON data - * @return {@link Challenge} instance - */ - public Challenge createChallenge(JSON data) { - Challenge challenge = provider().createChallenge(this, data); - if (challenge == null) { - throw new AcmeProtocolException("Could not create challenge for: " + data); - } - return challenge; - } - /** * Gets the {@link URL} of the given {@link Resource}. This may involve connecting to * the server and getting a directory. The result is cached. diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java index aa67baf2..31c63f91 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java @@ -13,13 +13,11 @@ */ package org.shredzone.acme4j.challenge; -import java.net.URL; import java.time.Instant; -import java.util.Objects; import org.shredzone.acme4j.AcmeJsonResource; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Problem; -import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.exception.AcmeException; @@ -51,40 +49,14 @@ public class Challenge extends AcmeJsonResource { /** * Creates a new generic {@link Challenge} object. * - * @param session - * {@link Session} to bind to. + * @param login + * {@link Login} the resource is bound with * @param data * {@link JSON} challenge data */ - public Challenge(Session session, JSON data) { - super(session, data); - } - - /** - * Returns a {@link Challenge} object of an existing challenge. - * - * @param session - * {@link Session} to be used - * @param location - * Challenge location - * @return {@link Challenge} bound to this session and location - */ - @SuppressWarnings("unchecked") - public static T bind(Session session, URL location) throws AcmeException { - Objects.requireNonNull(session, "session"); - Objects.requireNonNull(location, "location"); - - LOG.debug("bind"); - try (Connection conn = session.provider().connect()) { - conn.sendRequest(location, session); - - JSON json = conn.readJsonResponse(); - if (!(json.contains(KEY_TYPE))) { - throw new IllegalArgumentException("Provided URL is not a challenge URL"); - } - - return (T) session.createChallenge(json); - } + public Challenge(Login login, JSON data) { + super(login, data.get(KEY_URL).required().asURL()); + setJSON(data); } /** @@ -146,7 +118,10 @@ public class Challenge extends AcmeJsonResource { throw new AcmeProtocolException("incompatible type " + type + " for this challenge"); } - setLocation(json.get(KEY_URL).required().asURL()); + String loc = json.get(KEY_URL).required().asString(); + if (loc != null && !loc.equals(getLocation().toString())) { + throw new AcmeProtocolException("challenge has changed its location"); + } super.setJSON(json); } @@ -161,11 +136,11 @@ public class Challenge extends AcmeJsonResource { */ public void trigger() throws AcmeException { LOG.debug("trigger"); - try (Connection conn = getSession().provider().connect()) { + try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); prepareResponse(claims); - conn.sendSignedRequest(getLocation(), claims, getSession()); + conn.sendSignedRequest(getLocation(), claims, getLogin()); setJSON(conn.readJsonResponse()); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java index 23caab74..0f585f5e 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java @@ -15,7 +15,7 @@ package org.shredzone.acme4j.challenge; import static org.shredzone.acme4j.toolbox.AcmeUtils.*; -import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.toolbox.JSON; /** @@ -32,13 +32,13 @@ public class Dns01Challenge extends TokenChallenge { /** * Creates a new generic {@link Dns01Challenge} object. * - * @param session - * {@link Session} to bind to. + * @param login + * {@link Login} the resource is bound with * @param data * {@link JSON} challenge data */ - public Dns01Challenge(Session session, JSON data) { - super(session, data); + public Dns01Challenge(Login login, JSON data) { + super(login, data); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Http01Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Http01Challenge.java index 4ec28324..5434ec9a 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Http01Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Http01Challenge.java @@ -13,7 +13,7 @@ */ package org.shredzone.acme4j.challenge; -import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.toolbox.JSON; /** @@ -30,13 +30,13 @@ public class Http01Challenge extends TokenChallenge { /** * Creates a new generic {@link Http01Challenge} object. * - * @param session - * {@link Session} to bind to. + * @param login + * {@link Login} the resource is bound with * @param data * {@link JSON} challenge data */ - public Http01Challenge(Session session, JSON data) { - super(session, data); + public Http01Challenge(Login login, JSON data) { + super(login, data); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TokenChallenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TokenChallenge.java index c32f986c..12dda0b1 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TokenChallenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TokenChallenge.java @@ -19,7 +19,7 @@ import java.security.PublicKey; import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.lang.JoseException; -import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSONBuilder; @@ -37,13 +37,13 @@ public class TokenChallenge extends Challenge { /** * Creates a new generic {@link TokenChallenge} object. * - * @param session - * {@link Session} to bind to. + * @param login + * {@link Login} the resource is bound with * @param data * {@link JSON} challenge data */ - public TokenChallenge(Session session, JSON data) { - super(session, data); + public TokenChallenge(Login login, JSON data) { + super(login, data); } @Override @@ -67,7 +67,7 @@ public class TokenChallenge extends Challenge { */ protected String getAuthorization() { try { - PublicKey pk = getSession().getKeyPair().getPublic(); + PublicKey pk = getLogin().getKeyPair().getPublic(); PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(pk); return getToken() + '.' diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java index 125e2062..a92e9621 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java @@ -15,10 +15,12 @@ package org.shredzone.acme4j.connector; import java.net.HttpURLConnection; import java.net.URL; +import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.List; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; @@ -52,23 +54,26 @@ public interface Connection extends AutoCloseable { void sendRequest(URL url, Session session) throws AcmeException; /** - * Sends a signed POST request. Ensures that the session has a KeyIdentifier set, and - * that the "kid" protected header field is used. + * Sends a signed POST request. Requires a {@link Login} for the session and + * {@link KeyPair}. The {@link Login} account location is sent in a "kid" protected + * header. + *

+ * If the server does not return a 200 class status code, an {@link AcmeException} is + * raised matching the error. * * @param url * {@link URL} to send the request to. * @param claims * {@link JSONBuilder} containing claims. Must not be {@code null}. - * @param session - * {@link Session} instance to be used for signing and tracking + * @param login + * {@link Login} instance to be used for signing and tracking. * @return HTTP 200 class status that was returned */ - int sendSignedRequest(URL url, JSONBuilder claims, Session session) - throws AcmeException; + int sendSignedRequest(URL url, JSONBuilder claims, Login login) throws AcmeException; /** - * Sends a signed POST request. If the session's KeyIdentifier is set, a "kid" - * protected header field is sent. If not, a "jwk" protected header field is sent. + * Sends a signed POST request. Only requires a {@link Session}. The {@link KeyPair} + * is sent in a "jwk" protected header field. *

* If the server does not return a 200 class status code, an {@link AcmeException} is * raised matching the error. @@ -78,14 +83,12 @@ public interface Connection extends AutoCloseable { * @param claims * {@link JSONBuilder} containing claims. Must not be {@code null}. * @param session - * {@link Session} instance to be used for signing and tracking - * @param enforceJwk - * {@code true} to enforce a "jwk" header field even if a KeyIdentifier is - * set, {@code false} to choose between "kid" and "jwk" header field - * automatically + * {@link Session} instance to be used for tracking. + * @param keypair + * {@link KeyPair} to be used for signing. * @return HTTP 200 class status that was returned */ - int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) + int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) throws AcmeException; /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java index 1578ffe9..53244153 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java @@ -41,6 +41,7 @@ import org.jose4j.base64url.Base64Url; import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jws.JsonWebSignature; import org.jose4j.lang.JoseException; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; @@ -153,23 +154,29 @@ public class DefaultConnection implements Connection { } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) throws AcmeException { - return sendSignedRequest(url, claims, session, false); + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) throws AcmeException { + return sendSignedRequest(url, claims, login.getSession(), login.getKeyPair(), login.getAccountLocation()); } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) + throws AcmeException { + return sendSignedRequest(url, claims, session, keypair, null); + } + + private int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair, URL accountLocation) throws AcmeException { Objects.requireNonNull(url, "url"); Objects.requireNonNull(claims, "claims"); Objects.requireNonNull(session, "session"); + Objects.requireNonNull(keypair, "keypair"); assertConnectionIsClosed(); AcmeException lastException = null; for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { try { - return performRequest(url, claims, session, enforceJwk); + return performRequest(url, claims, session, keypair, accountLocation); } catch (AcmeServerException ex) { if (!BAD_NONCE_ERROR.equals(ex.getType())) { throw ex; @@ -245,7 +252,7 @@ public class DefaultConnection implements Connection { String nonceHeader = conn.getHeaderField(REPLAY_NONCE_HEADER); if (nonceHeader == null || nonceHeader.trim().isEmpty()) { - return null; + return null; //NOSONAR: consistent with other getters } if (!BASE64URL_PATTERN.matcher(nonceHeader).matches()) { @@ -291,17 +298,16 @@ public class DefaultConnection implements Connection { * {@link JSONBuilder} containing claims. Must not be {@code null}. * @param session * {@link Session} instance to be used for signing and tracking - * @param enforceJwk - * {@code true} to enforce a "jwk" header field even if a KeyIdentifier is - * set, {@code false} to choose between "kid" and "jwk" header field - * automatically + * @param keypair + * {@link KeyPair} to be used for signing + * @param accountLocation + * If set, the account location is set as "kid" header. If {@code null}, + * the public key is set as "jwk" header. * @return HTTP 200 class status that was returned */ - private int performRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) + private int performRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair, URL accountLocation) throws AcmeException { try { - KeyPair keypair = session.getKeyPair(); - if (session.getNonce() == null) { resetNonce(session); } @@ -319,10 +325,10 @@ public class DefaultConnection implements Connection { jws.setPayload(claims.toString()); jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce())); jws.getHeaders().setObjectHeaderValue("url", url); - if (enforceJwk || session.getAccountLocation() == null) { + if (accountLocation == null) { jws.getHeaders().setJwkHeaderValue("jwk", jwk); } else { - jws.getHeaders().setObjectHeaderValue("kid", session.getAccountLocation()); + jws.getHeaders().setObjectHeaderValue("kid", accountLocation); } jws.setAlgorithmHeaderValue(keyAlgorithm(jwk)); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java index 3b5b1ef9..a2ea3330 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.function.BiFunction; import org.shredzone.acme4j.AcmeResource; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeProtocolException; @@ -36,28 +37,28 @@ import org.shredzone.acme4j.toolbox.JSON; */ public class ResourceIterator implements Iterator { - private final Session session; + private final Login login; private final String field; private final Deque urlList = new ArrayDeque<>(); - private final BiFunction creator; + private final BiFunction creator; private boolean eol = false; private URL nextUrl; /** * Creates a new {@link ResourceIterator}. * - * @param session - * {@link Session} to bind this iterator to + * @param login + * {@link Login} to bind this iterator to * @param field * Field name to be used in the JSON response * @param start * URL of the first JSON array, may be {@code null} for an empty iterator * @param creator * Creator for an {@link AcmeResource} that is bound to the given - * {@link Session} and {@link URL}. + * {@link Login} and {@link URL}. */ - public ResourceIterator(Session session, String field, URL start, BiFunction creator) { - this.session = Objects.requireNonNull(session, "session"); + public ResourceIterator(Login login, String field, URL start, BiFunction creator) { + this.login = Objects.requireNonNull(login, "login"); this.field = Objects.requireNonNull(field, "field"); this.nextUrl = start; this.creator = Objects.requireNonNull(creator, "creator"); @@ -106,7 +107,7 @@ public class ResourceIterator implements Iterator { throw new NoSuchElementException("no more " + field); } - return creator.apply(session, next); + return creator.apply(login, next); } /** @@ -138,6 +139,7 @@ public class ResourceIterator implements Iterator { * there is a "next" header, it is used for the next batch of URLs. */ private void readAndQueue() throws AcmeException { + Session session = login.getSession(); try (Connection conn = session.provider().connect()) { conn.sendRequest(nextUrl, session); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java index 2ec78944..441955ee 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Objects; import java.util.function.BiFunction; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; @@ -40,7 +41,7 @@ import org.shredzone.acme4j.toolbox.JSON; */ public abstract class AbstractAcmeProvider implements AcmeProvider { - private static final Map> CHALLENGES = challengeMap(); + private static final Map> CHALLENGES = challengeMap(); @Override public Connection connect() { @@ -62,8 +63,8 @@ public abstract class AbstractAcmeProvider implements AcmeProvider { } } - private static Map> challengeMap() { - Map> map = new HashMap<>(); + private static Map> challengeMap() { + Map> map = new HashMap<>(); map.put(Dns01Challenge.TYPE, Dns01Challenge::new); map.put(Http01Challenge.TYPE, Http01Challenge::new); @@ -81,21 +82,21 @@ public abstract class AbstractAcmeProvider implements AcmeProvider { * are unique to the provider. */ @Override - public Challenge createChallenge(Session session, JSON data) { - Objects.requireNonNull(session, "session"); + public Challenge createChallenge(Login login, JSON data) { + Objects.requireNonNull(login, "login"); Objects.requireNonNull(data, "data"); String type = data.get("type").required().asString(); - BiFunction constructor = CHALLENGES.get(type); + BiFunction constructor = CHALLENGES.get(type); if (constructor != null) { - return constructor.apply(session, data); + return constructor.apply(login, data); } if (data.contains("token")) { - return new TokenChallenge(session, data); + return new TokenChallenge(login, data); } else { - return new Challenge(session, data); + return new Challenge(login, data); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AcmeProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AcmeProvider.java index 7a477ae8..e875c8c9 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AcmeProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AcmeProvider.java @@ -17,6 +17,7 @@ import java.net.URI; import java.net.URL; import java.util.ServiceLoader; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.connector.Connection; @@ -77,13 +78,13 @@ public interface AcmeProvider { /** * Creates a {@link Challenge} instance for the given challenge data. * - * @param session - * {@link Session} to bind the challenge to + * @param login + * {@link Login} to bind the challenge to * @param data * Challenge {@link JSON} data * @return {@link Challenge} instance, or {@code null} if this provider is unable to * generate a matching {@link Challenge} instance. */ - Challenge createChallenge(Session session, JSON data); + Challenge createChallenge(Login login, JSON data); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java index e70335ad..1621c205 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java @@ -20,6 +20,7 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import java.net.HttpURLConnection; import java.net.URL; +import java.security.KeyPair; import javax.crypto.SecretKey; @@ -47,12 +48,14 @@ public class AccountBuilderTest { */ @Test public void testRegistration() throws Exception { + KeyPair accountKey = TestUtils.createKeyPair(); + TestableConnectionProvider provider = new TestableConnectionProvider() { private boolean isUpdate; @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { - assertThat(session, is(notNullValue())); + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { + assertThat(login, is(notNullValue())); assertThat(url, is(locationUrl)); assertThat(isUpdate, is(false)); isUpdate = true; @@ -60,11 +63,11 @@ public class AccountBuilderTest { } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) { assertThat(session, is(notNullValue())); assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAccount").toString())); - assertThat(enforceJwk, is(true)); + assertThat(keypair, is(accountKey)); isUpdate = false; return HttpURLConnection.HTTP_CREATED; } @@ -85,22 +88,16 @@ public class AccountBuilderTest { AccountBuilder builder = new AccountBuilder(); builder.addContact("mailto:foo@example.com"); builder.agreeToTermsOfService(); + builder.useKeyPair(accountKey); Session session = provider.createSession(); - Account account = builder.create(session); + Login login = builder.createLogin(session); - assertThat(account.getLocation(), is(locationUrl)); + assertThat(login.getAccountLocation(), is(locationUrl)); + + Account account = login.getAccount(); assertThat(account.getTermsOfServiceAgreed(), is(true)); - assertThat(session.getAccountLocation(), is(locationUrl)); - - try { - AccountBuilder builder2 = new AccountBuilder(); - builder2.agreeToTermsOfService(); - builder2.create(session); - fail("registered twice on same session"); - } catch (IllegalArgumentException ex) { - // expected - } + assertThat(account.getLocation(), is(locationUrl)); provider.close(); } @@ -110,16 +107,17 @@ public class AccountBuilderTest { */ @Test public void testRegistrationWithKid() throws Exception { + KeyPair accountKey = TestUtils.createKeyPair(); String keyIdentifier = "NCC-1701"; SecretKey macKey = TestUtils.createSecretKey("SHA-256"); TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) { try { assertThat(session, is(notNullValue())); assertThat(url, is(resourceUrl)); - assertThat(enforceJwk, is(true)); + assertThat(keypair, is(accountKey)); JSON binding = claims.toJSON() .get("externalAccountBinding") @@ -170,12 +168,13 @@ public class AccountBuilderTest { provider.putTestResource(Resource.NEW_ACCOUNT, resourceUrl); AccountBuilder builder = new AccountBuilder(); - builder.useKeyIdentifier(keyIdentifier, AcmeUtils.base64UrlEncode(macKey.getEncoded())); + builder.useKeyPair(accountKey); + builder.withKeyIdentifier(keyIdentifier, AcmeUtils.base64UrlEncode(macKey.getEncoded())); Session session = provider.createSession(); - Account account = builder.create(session); + Login login = builder.createLogin(session); - assertThat(account.getLocation(), is(locationUrl)); + assertThat(login.getAccountLocation(), is(locationUrl)); provider.close(); } @@ -185,13 +184,15 @@ public class AccountBuilderTest { */ @Test public void testOnlyExistingRegistration() throws Exception { + KeyPair accountKey = TestUtils.createKeyPair(); + TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) { assertThat(session, is(notNullValue())); assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAccountOnlyExisting").toString())); - assertThat(enforceJwk, is(true)); + assertThat(keypair, is(accountKey)); return HttpURLConnection.HTTP_OK; } @@ -209,13 +210,13 @@ public class AccountBuilderTest { provider.putTestResource(Resource.NEW_ACCOUNT, resourceUrl); AccountBuilder builder = new AccountBuilder(); + builder.useKeyPair(accountKey); builder.onlyExisting(); Session session = provider.createSession(); - Account account = builder.create(session); + Login login = builder.createLogin(session); - assertThat(account.getLocation(), is(locationUrl)); - assertThat(session.getAccountLocation(), is(locationUrl)); + assertThat(login.getAccountLocation(), is(locationUrl)); provider.close(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java index d47cf0de..1ff0f218 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java @@ -52,7 +52,7 @@ import org.shredzone.acme4j.toolbox.TestUtils; public class AccountTest { private URL resourceUrl = url("http://example.com/acme/resource"); - private URL locationUrl = url("http://example.com/acme/account"); + private URL locationUrl = url(TestUtils.ACCOUNT_URL); private URL agreementUrl = url("http://example.com/agreement.pdf"); /** @@ -64,10 +64,10 @@ public class AccountTest { private JSON jsonResponse; @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { assertThat(url, is(locationUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("updateAccount").toString())); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); jsonResponse = getJSON("updateAccountResponse"); return HttpURLConnection.HTTP_OK; } @@ -97,11 +97,11 @@ public class AccountTest { } }; - Session session = provider.createSession(); - Account account = new Account(session, locationUrl); + Login login = provider.createLogin(); + Account account = new Account(login); account.update(); - assertThat(session.getAccountLocation(), is(locationUrl)); + assertThat(login.getAccountLocation(), is(locationUrl)); assertThat(account.getLocation(), is(locationUrl)); assertThat(account.getTermsOfServiceAgreed(), is(true)); assertThat(account.getContacts(), hasSize(1)); @@ -125,7 +125,7 @@ public class AccountTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { requestWasSent.set(true); assertThat(url, is(locationUrl)); return HttpURLConnection.HTTP_OK; @@ -150,7 +150,7 @@ public class AccountTest { } }; - Account account = new Account(provider.createSession(), locationUrl); + Account account = new Account(provider.createLogin()); // Lazy loading assertThat(requestWasSent.get(), is(false)); @@ -173,10 +173,10 @@ public class AccountTest { public void testPreAuthorizeDomain() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString())); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); return HttpURLConnection.HTTP_CREATED; } @@ -191,7 +191,7 @@ public class AccountTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestResource(Resource.NEW_AUTHZ, resourceUrl); provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new); @@ -199,7 +199,7 @@ public class AccountTest { String domainName = "example.org"; - Account account = new Account(session, locationUrl); + Account account = new Account(login); Authorization auth = account.preAuthorizeDomain(domainName); assertThat(auth.getDomain(), is(domainName)); @@ -224,21 +224,21 @@ public class AccountTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) throws AcmeException { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) throws AcmeException { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString())); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); Problem problem = TestUtils.createProblem(problemType, problemDetail, resourceUrl); throw new AcmeServerException(problem); } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestResource(Resource.NEW_AUTHZ, resourceUrl); - Account account = new Account(session, locationUrl); + Account account = new Account(login); try { account.preAuthorizeDomain("example.org"); @@ -260,8 +260,8 @@ public class AccountTest { // just provide a resource record so the provider returns a directory provider.putTestResource(Resource.NEW_NONCE, resourceUrl); - Session session = provider.createSession(); - Account account = Account.bind(session, locationUrl); + Login login = provider.createLogin(); + Account account = login.getAccount(); try { account.preAuthorizeDomain(null); @@ -298,11 +298,11 @@ public class AccountTest { final TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder payload, Session session) { + public int sendSignedRequest(URL url, JSONBuilder payload, Login login) { try { assertThat(url, is(locationUrl)); - assertThat(session, is(notNullValue())); - assertThat(session.getKeyPair(), is(sameInstance(oldKeyPair))); + assertThat(login, is(notNullValue())); + assertThat(login.getKeyPair(), is(sameInstance(oldKeyPair))); JSON json = payload.toJSON(); String encodedHeader = json.get("protected").asString(); @@ -319,7 +319,7 @@ public class AccountTest { StringBuilder expectedPayload = new StringBuilder(); expectedPayload.append('{'); - expectedPayload.append("\"account\":\"").append(resourceUrl).append("\","); + expectedPayload.append("\"account\":\"").append(locationUrl).append("\","); expectedPayload.append("\"newKey\":{"); expectedPayload.append("\"kty\":\"").append(TestUtils.D_KTY).append("\","); expectedPayload.append("\"e\":\"").append(TestUtils.D_E).append("\","); @@ -341,19 +341,21 @@ public class AccountTest { provider.putTestResource(Resource.KEY_CHANGE, locationUrl); - Session session = new Session(new URI(TestUtils.ACME_SERVER_URI), oldKeyPair) { + Session session = new Session(new URI(TestUtils.ACME_SERVER_URI)) { @Override public AcmeProvider provider() { return provider; }; }; - assertThat(session.getKeyPair(), is(sameInstance(oldKeyPair))); + Login login = new Login(locationUrl, oldKeyPair, session); - Account account = new Account(session, resourceUrl); + assertThat(login.getKeyPair(), is(sameInstance(oldKeyPair))); + + Account account = new Account(login); account.changeKey(newKeyPair); - assertThat(session.getKeyPair(), is(sameInstance(newKeyPair))); + assertThat(login.getKeyPair(), is(sameInstance(newKeyPair))); } /** @@ -362,10 +364,10 @@ public class AccountTest { @Test(expected = IllegalArgumentException.class) public void testChangeSameKey() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider(); - Session session = provider.createSession(); + Login login = provider.createLogin(); - Account account = new Account(session, locationUrl); - account.changeKey(session.getKeyPair()); + Account account = new Account(login); + account.changeKey(login.getKeyPair()); provider.close(); } @@ -377,11 +379,11 @@ public class AccountTest { public void testDeactivate() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { JSON json = claims.toJSON(); assertThat(json.get("status").asString(), is("deactivated")); assertThat(url, is(locationUrl)); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); return HttpURLConnection.HTTP_OK; } @@ -391,7 +393,7 @@ public class AccountTest { } }; - Account account = new Account(provider.createSession(), locationUrl); + Account account = new Account(provider.createLogin()); account.deactivate(); assertThat(account.getStatus(), is(Status.DEACTIVATED)); @@ -406,10 +408,10 @@ public class AccountTest { public void testModify() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { assertThat(url, is(locationUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("modifyAccount").toString())); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); return HttpURLConnection.HTTP_OK; } @@ -424,7 +426,7 @@ public class AccountTest { } }; - Account account = new Account(provider.createSession(), locationUrl); + Account account = new Account(provider.createLogin()); account.setJSON(getJSON("newAccount")); EditableAccount editable = account.modify(); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeJsonResourceTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeJsonResourceTest.java index f4c54d00..ea804a9d 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeJsonResourceTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeJsonResourceTest.java @@ -15,7 +15,9 @@ package org.shredzone.acme4j; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; -import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; +import static org.shredzone.acme4j.toolbox.TestUtils.*; + +import java.net.URL; import org.junit.Test; import org.shredzone.acme4j.exception.AcmeException; @@ -28,16 +30,19 @@ import org.shredzone.acme4j.toolbox.TestUtils; public class AcmeJsonResourceTest { private static final JSON JSON_DATA = getJSON("newAccountResponse"); + private static final URL LOCATION_URL = url("https://example.com/acme/resource/123"); /** - * Test {@link AcmeJsonResource#AcmeJsonResource(Session)}. + * Test {@link AcmeJsonResource#AcmeJsonResource(Login, URL)}. */ @Test - public void testSessionConstructor() throws Exception { - Session session = TestUtils.session(); + public void testLoginConstructor() throws Exception { + Login login = TestUtils.login(); - AcmeJsonResource resource = new DummyJsonResource(session); - assertThat(resource.getSession(), is(session)); + AcmeJsonResource resource = new DummyJsonResource(login, LOCATION_URL); + assertThat(resource.getLogin(), is(login)); + assertThat(resource.getSession(), is(login.getSession())); + assertThat(resource.getLocation(), is(LOCATION_URL)); assertThat(resource.isValid(), is(false)); assertUpdateInvoked(resource, 0); @@ -46,30 +51,16 @@ public class AcmeJsonResourceTest { assertUpdateInvoked(resource, 1); } - /** - * Test {@link AcmeJsonResource#AcmeJsonResource(Session, JSON)}. - */ - @Test - public void testSessionAndJsonConstructor() throws Exception { - Session session = TestUtils.session(); - - AcmeJsonResource resource = new DummyJsonResource(session, JSON_DATA); - assertThat(resource.getSession(), is(session)); - assertThat(resource.isValid(), is(true)); - assertThat(resource.getJSON(), is(JSON_DATA)); - assertUpdateInvoked(resource, 0); - } - /** * Test {@link AcmeJsonResource#setJSON(JSON)}. */ @Test public void testSetJson() throws Exception { - Session session = TestUtils.session(); + Login login = TestUtils.login(); JSON jsonData2 = getJSON("requestOrderResponse"); - AcmeJsonResource resource = new DummyJsonResource(session); + AcmeJsonResource resource = new DummyJsonResource(login, LOCATION_URL); assertThat(resource.isValid(), is(false)); assertUpdateInvoked(resource, 0); @@ -89,13 +80,9 @@ public class AcmeJsonResourceTest { */ @Test public void testInvalidate() throws Exception { - Session session = TestUtils.session(); + Login login = TestUtils.login(); - AcmeJsonResource resource = new DummyJsonResource(session, JSON_DATA); - assertThat(resource.isValid(), is(true)); - assertUpdateInvoked(resource, 0); - - resource.invalidate(); + AcmeJsonResource resource = new DummyJsonResource(login, LOCATION_URL); assertThat(resource.isValid(), is(false)); assertUpdateInvoked(resource, 0); @@ -134,12 +121,13 @@ public class AcmeJsonResourceTest { private int updateCount = 0; - public DummyJsonResource(Session session) { - super(session); + public DummyJsonResource(Login login, URL location) { + super(login, location); } - public DummyJsonResource(Session session, JSON json) { - super(session, json); + public DummyJsonResource(Login login, URL location, JSON json) { + super(login, location); + setJSON(json); } @Override diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeResourceTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeResourceTest.java index 16497629..f39491b5 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeResourceTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeResourceTest.java @@ -35,40 +35,19 @@ public class AcmeResourceTest { */ @Test public void testConstructor() throws Exception { - Session session = TestUtils.session(); + Login login = TestUtils.login(); URL location = new URL("http://example.com/acme/resource"); try { - new DummyResource(null); - fail("Could create resource without session"); + new DummyResource(null, null); + fail("Could create resource without login and location"); } catch (NullPointerException ex) { // expected } - AcmeResource resource = new DummyResource(session); - assertThat(resource.getSession(), is(session)); - - assertThat(resource.getLocation(), is(nullValue())); - resource.setLocation(location); + AcmeResource resource = new DummyResource(login, location); + assertThat(resource.getLogin(), is(login)); assertThat(resource.getLocation(), is(location)); - - try { - resource.setLocation(null); - fail("Could set location to null"); - } catch (NullPointerException ex) { - // expected - } - - try { - resource.setSession(null); - fail("Could set session to null"); - } catch (NullPointerException ex) { - // expected - } - - Session session2 = TestUtils.session(); - resource.setSession(session2); - assertThat(resource.getSession(), is(session2)); } /** @@ -76,11 +55,12 @@ public class AcmeResourceTest { */ @Test public void testSerialization() throws Exception { - Session session = TestUtils.session(); + Login login = TestUtils.login(); + URL location = new URL("http://example.com/acme/resource"); // Create a Challenge for testing - DummyResource challenge = new DummyResource(session); - assertThat(challenge.getSession(), is(session)); + DummyResource challenge = new DummyResource(login, location); + assertThat(challenge.getLogin(), is(login)); // Serialize it byte[] serialized = null; @@ -107,19 +87,19 @@ public class AcmeResourceTest { } assertThat(restored, not(sameInstance(challenge))); - // Make sure the restored object is not attached to a session + // Make sure the restored object is not attached to a login try { - restored.getSession(); + restored.getLogin(); fail("was able to retrieve a session"); } catch (IllegalStateException ex) { - // must fail because we don't have a session in the restored object + // must fail because we don't have a login in the restored object } - // Set a new session - restored.setSession(session); + // Rebind to login + restored.rebind(login); - // Make sure the new session is set - assertThat(restored.getSession(), is(session)); + // Make sure the new login is set + assertThat(restored.getLogin(), is(login)); } /** @@ -127,12 +107,14 @@ public class AcmeResourceTest { */ @Test(expected = IllegalStateException.class) public void testRebind() throws Exception { - Session session = TestUtils.session(); - AcmeResource resource = new DummyResource(session); - assertThat(resource.getSession(), is(session)); + Login login = TestUtils.login(); + URL location = new URL("http://example.com/acme/resource"); - Session session2 = TestUtils.session(); - resource.rebind(session2); // fails to rebind to another session + AcmeResource resource = new DummyResource(login, location); + assertThat(resource.getLogin(), is(login)); + + Login login2 = TestUtils.login(); + resource.rebind(login2); // fails to rebind to another login } /** @@ -140,8 +122,8 @@ public class AcmeResourceTest { */ private static class DummyResource extends AcmeResource { private static final long serialVersionUID = 7188822681353082472L; - public DummyResource(Session session) { - super(session); + public DummyResource(Login login, URL location) { + super(login, location); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java index 1d68e02c..3dcec7cb 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java @@ -100,12 +100,12 @@ public class AuthorizationTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestChallenge("http-01", Http01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new); - Authorization auth = new Authorization(session, locationUrl); + Authorization auth = new Authorization(login, locationUrl); auth.update(); assertThat(auth.getDomain(), is("example.org")); @@ -145,12 +145,12 @@ public class AuthorizationTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestChallenge("http-01", Http01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new); - Authorization auth = new Authorization(session, locationUrl); + Authorization auth = new Authorization(login, locationUrl); // Lazy loading assertThat(requestWasSent.get(), is(false)); @@ -191,12 +191,12 @@ public class AuthorizationTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestChallenge("http-01", Http01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new); - Authorization auth = new Authorization(session, locationUrl); + Authorization auth = new Authorization(login, locationUrl); try { auth.update(); @@ -224,11 +224,11 @@ public class AuthorizationTest { public void testDeactivate() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { JSON json = claims.toJSON(); assertThat(json.get("status").asString(), is("deactivated")); assertThat(url, is(locationUrl)); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); return HttpURLConnection.HTTP_OK; } @@ -238,12 +238,12 @@ public class AuthorizationTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestChallenge("http-01", Http01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new); - Authorization auth = new Authorization(session, locationUrl); + Authorization auth = new Authorization(login, locationUrl); auth.deactivate(); provider.close(); @@ -254,13 +254,13 @@ public class AuthorizationTest { */ private Authorization createChallengeAuthorization() throws IOException { try (TestableConnectionProvider provider = new TestableConnectionProvider()) { - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new); provider.putTestChallenge(Dns01Challenge.TYPE, Dns01Challenge::new); provider.putTestChallenge(DUPLICATE_TYPE, Challenge::new); - Authorization authorization = new Authorization(session, locationUrl); + Authorization authorization = new Authorization(login, locationUrl); authorization.setJSON(getJSON("authorizationChallenges")); return authorization; } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java index 3000c827..15085ce8 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java @@ -23,7 +23,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; -import java.net.URI; import java.net.URL; import java.security.KeyPair; import java.security.cert.X509Certificate; @@ -75,7 +74,7 @@ public class CertificateTest { } }; - Certificate cert = new Certificate(provider.createSession(), locationUrl); + Certificate cert = new Certificate(provider.createLogin(), locationUrl); cert.download(); X509Certificate downloadedCert = cert.getCertificate(); @@ -132,12 +131,11 @@ public class CertificateTest { } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateRequest").toString())); assertThat(session, is(notNullValue())); - assertThat(session.getAccountLocation(), is(nullValue())); - assertThat(enforceJwk, is(true)); + assertThat(keypair, is(notNullValue())); certRequested = false; return HttpURLConnection.HTTP_OK; } @@ -157,7 +155,7 @@ public class CertificateTest { provider.putTestResource(Resource.REVOKE_CERT, resourceUrl); - Certificate cert = new Certificate(provider.createSession(), locationUrl); + Certificate cert = new Certificate(provider.createLogin(), locationUrl); cert.revoke(); provider.close(); @@ -181,11 +179,11 @@ public class CertificateTest { } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString())); assertThat(session, is(notNullValue())); - assertThat(enforceJwk, is(true)); + assertThat(keypair, is(notNullValue())); certRequested = false; return HttpURLConnection.HTTP_OK; } @@ -205,7 +203,7 @@ public class CertificateTest { provider.putTestResource(Resource.REVOKE_CERT, resourceUrl); - Certificate cert = new Certificate(provider.createSession(), locationUrl); + Certificate cert = new Certificate(provider.createLogin(), locationUrl); cert.revoke(RevocationReason.KEY_COMPROMISE); provider.close(); @@ -223,20 +221,17 @@ public class CertificateTest { * Test that a certificate can be revoked by its domain key pair. */ @Test - @SuppressWarnings("resource") public void testRevokeCertificateByKeyPair() throws AcmeException, IOException { final List originalCert = TestUtils.createCertificate(); final KeyPair certKeyPair = TestUtils.createDomainKeyPair(); TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) - throws AcmeException { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString())); assertThat(session, is(notNullValue())); - assertThat(session.getKeyPair(), is(certKeyPair)); - assertThat(enforceJwk, is(true)); + assertThat(keypair, is(certKeyPair)); return HttpURLConnection.HTTP_OK; } }; @@ -244,15 +239,8 @@ public class CertificateTest { provider.putTestResource(Resource.REVOKE_CERT, resourceUrl); Session session = provider.createSession(); - URI serverUri = session.getServerUri(); - Certificate.revokeSessionFactory = (uri, keyPair) -> { - assertThat(uri, is(serverUri)); - session.setKeyPair(keyPair); - return session; - }; - - Certificate.revoke(serverUri, certKeyPair, originalCert.get(0), RevocationReason.KEY_COMPROMISE); + Certificate.revoke(session, certKeyPair, originalCert.get(0), RevocationReason.KEY_COMPROMISE); provider.close(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/LoginTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/LoginTest.java new file mode 100644 index 00000000..d1b36e77 --- /dev/null +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/LoginTest.java @@ -0,0 +1,138 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2018 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ +package org.shredzone.acme4j; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.*; +import static org.shredzone.acme4j.toolbox.TestUtils.url; + +import java.io.IOException; +import java.net.URL; +import java.security.KeyPair; + +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.shredzone.acme4j.challenge.Challenge; +import org.shredzone.acme4j.challenge.Http01Challenge; +import org.shredzone.acme4j.provider.AcmeProvider; +import org.shredzone.acme4j.toolbox.JSON; +import org.shredzone.acme4j.toolbox.JSONBuilder; +import org.shredzone.acme4j.toolbox.TestUtils; + +/** + * Unit tests for {@link Login}. + */ +public class LoginTest { + + private static final URL resourceUrl = url("https://example.com/acme/resource/123"); + + /** + * Test the constructor. + */ + @Test + public void testConstructor() throws IOException { + URL location = url(TestUtils.ACCOUNT_URL); + KeyPair keypair = TestUtils.createKeyPair(); + Session session = TestUtils.session(); + + Login login = new Login(location, keypair, session); + assertThat(login.getAccountLocation(), is(location)); + assertThat(login.getKeyPair(), is(keypair)); + assertThat(login.getSession(), is(session)); + + assertThat(login.getAccount(), is(notNullValue())); + assertThat(login.getAccount().getLogin(), is(login)); + assertThat(login.getAccount().getLocation(), is(location)); + assertThat(login.getAccount().getSession(), is(session)); + } + + /** + * Test the simple binders. + */ + @Test + public void testBinder() throws IOException { + URL location = url(TestUtils.ACCOUNT_URL); + KeyPair keypair = TestUtils.createKeyPair(); + Session session = TestUtils.session(); + + Login login = new Login(location, keypair, session); + + Authorization auth = login.bindAuthorization(resourceUrl); + assertThat(auth, is(notNullValue())); + assertThat(auth.getLogin(), is(login)); + assertThat(auth.getLocation(), is(resourceUrl)); + + Certificate cert = login.bindCertificate(resourceUrl); + assertThat(cert, is(notNullValue())); + assertThat(cert.getLogin(), is(login)); + assertThat(cert.getLocation(), is(resourceUrl)); + + Order order = login.bindOrder(resourceUrl); + assertThat(order, is(notNullValue())); + assertThat(order.getLogin(), is(login)); + assertThat(order.getLocation(), is(resourceUrl)); + } + + /** + * Test that the account's keypair can be changed. + */ + @Test + public void testKeyChange() throws IOException { + URL location = url(TestUtils.ACCOUNT_URL); + KeyPair keypair = TestUtils.createKeyPair(); + Session session = TestUtils.session(); + + Login login = new Login(location, keypair, session); + assertThat(login.getKeyPair(), is(keypair)); + + KeyPair keypair2 = TestUtils.createKeyPair(); + login.setKeyPair(keypair2); + assertThat(login.getKeyPair(), is(keypair2)); + } + + /** + * Test that challenges are correctly created via provider. + */ + @Test + public void testCreateChallenge() throws Exception { + String challengeType = Http01Challenge.TYPE; + URL challengeUrl = url("https://example.com/acme/authz/0"); + + JSON data = new JSONBuilder() + .put("type", challengeType) + .put("url", challengeUrl) + .toJSON(); + + Http01Challenge mockChallenge = mock(Http01Challenge.class); + final AcmeProvider mockProvider = mock(AcmeProvider.class); + + when(mockProvider.createChallenge( + ArgumentMatchers.any(Login.class), + ArgumentMatchers.eq(data))) + .thenReturn(mockChallenge); + + URL location = url(TestUtils.ACCOUNT_URL); + KeyPair keypair = TestUtils.createKeyPair(); + Session session = TestUtils.session(mockProvider); + + Login login = new Login(location, keypair, session); + Challenge challenge = login.createChallenge(data); + assertThat(challenge, is(instanceOf(Http01Challenge.class))); + assertThat(challenge, is(sameInstance((Challenge) mockChallenge))); + + verify(mockProvider).createChallenge(login, data); + } + +} diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java index b5349fa6..40938beb 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java @@ -29,6 +29,7 @@ import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSONBuilder; +import org.shredzone.acme4j.toolbox.TestUtils; /** * Unit tests for {@link OrderBuilder}. @@ -36,7 +37,7 @@ import org.shredzone.acme4j.toolbox.JSONBuilder; public class OrderBuilderTest { private URL resourceUrl = url("http://example.com/acme/resource"); - private URL locationUrl = url("http://example.com/acme/account"); + private URL locationUrl = url(TestUtils.ACCOUNT_URL); /** * Test that a new {@link Order} can be created. @@ -48,10 +49,10 @@ public class OrderBuilderTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("requestOrderRequest").toString())); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); return HttpURLConnection.HTTP_CREATED; } @@ -66,11 +67,11 @@ public class OrderBuilderTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.putTestResource(Resource.NEW_ORDER, resourceUrl); - Account account = new Account(session, locationUrl); + Account account = new Account(login); Order order = account.newOrder() .domains("example.com", "www.example.com") .domain("example.org") diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java index a649aaef..a1157cfb 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java @@ -62,9 +62,9 @@ public class OrderTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); - Order order = new Order(session, locationUrl); + Order order = new Order(login, locationUrl); order.update(); assertThat(order.getStatus(), is(Status.PENDING)); @@ -116,9 +116,9 @@ public class OrderTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); - Order order = new Order(session, locationUrl); + Order order = new Order(login, locationUrl); // Lazy loading assertThat(requestWasSent.get(), is(false)); @@ -151,10 +151,10 @@ public class OrderTest { } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { assertThat(url, is(finalizeUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("finalizeRequest").toString())); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); isFinalized = true; return HttpURLConnection.HTTP_OK; } @@ -170,9 +170,9 @@ public class OrderTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); - Order order = new Order(session, locationUrl); + Order order = new Order(login, locationUrl); order.execute(csr); assertThat(order.getStatus(), is(Status.VALID)); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java index 3da8573d..83283235 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/SessionTest.java @@ -26,13 +26,9 @@ import java.time.Instant; import org.junit.Test; import org.mockito.ArgumentMatchers; -import org.shredzone.acme4j.challenge.Challenge; -import org.shredzone.acme4j.challenge.Http01Challenge; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.provider.AcmeProvider; -import org.shredzone.acme4j.toolbox.JSON; -import org.shredzone.acme4j.toolbox.JSONBuilder; import org.shredzone.acme4j.toolbox.TestUtils; /** @@ -45,44 +41,25 @@ public class SessionTest { */ @Test public void testConstructor() throws IOException { - KeyPair keyPair = TestUtils.createKeyPair(); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); try { - new Session((URI) null, null); + new Session((URI) null); fail("accepted null parameters in constructor"); } catch (NullPointerException ex) { // expected } - try { - new Session(serverUri, null); - fail("accepted null parameters in constructor"); - } catch (NullPointerException ex) { - // expected - } - - try { - new Session((URI) null, keyPair); - fail("accepted null parameters in constructor"); - } catch (NullPointerException ex) { - // expected - } - - Session session = new Session(serverUri, keyPair); + Session session = new Session(serverUri); assertThat(session, not(nullValue())); assertThat(session.getServerUri(), is(serverUri)); - assertThat(session.getKeyPair(), is(keyPair)); - assertThat(session.getAccountLocation(), is(nullValue())); - Session session2 = new Session("https://example.com/acme", keyPair); + Session session2 = new Session(TestUtils.ACME_SERVER_URI); assertThat(session2, not(nullValue())); assertThat(session2.getServerUri(), is(serverUri)); - assertThat(session2.getKeyPair(), is(keyPair)); - assertThat(session2.getAccountLocation(), is(nullValue())); try { - new Session("#*aBaDuRi*#", keyPair); + new Session("#*aBaDuRi*#"); fail("accepted bad URI in constructor"); } catch (IllegalArgumentException ex) { // expected @@ -94,64 +71,34 @@ public class SessionTest { */ @Test public void testGettersAndSetters() throws IOException { - KeyPair kp1 = TestUtils.createKeyPair(); - KeyPair kp2 = TestUtils.createDomainKeyPair(); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); - URL accountUrl = TestUtils.url(TestUtils.ACME_SERVER_URI + "/acct/1"); - Session session = new Session(serverUri, kp1); + Session session = new Session(serverUri); assertThat(session.getNonce(), is(nullValue())); byte[] data = "foo-nonce-bar".getBytes(); session.setNonce(data); assertThat(session.getNonce(), is(equalTo(data))); - assertThat(session.getKeyPair(), is(kp1)); - session.setKeyPair(kp2); - assertThat(session.getKeyPair(), is(kp2)); - - assertThat(session.getAccountLocation(), is(nullValue())); - session.setAccountLocation(accountUrl); - assertThat(session.getAccountLocation(), is(accountUrl)); - assertThat(session.getServerUri(), is(serverUri)); } /** - * Test if challenges are correctly created via provider. + * Test login methods. */ @Test - public void testCreateChallenge() throws IOException { - KeyPair keyPair = TestUtils.createKeyPair(); + public void testLogin() throws IOException { URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); - String challengeType = Http01Challenge.TYPE; - URL challengeUrl = new URL("https://example.com/acme/authz/0"); + URL accountLocation = url(TestUtils.ACCOUNT_URL); + KeyPair accountKeyPair = TestUtils.createKeyPair(); - JSON data = new JSONBuilder() - .put("type", challengeType) - .put("url", challengeUrl) - .toJSON(); + Session session = new Session(serverUri); - Http01Challenge mockChallenge = mock(Http01Challenge.class); - final AcmeProvider mockProvider = mock(AcmeProvider.class); - - when(mockProvider.createChallenge( - ArgumentMatchers.any(Session.class), - ArgumentMatchers.eq(data))) - .thenReturn(mockChallenge); - - Session session = new Session(serverUri, keyPair) { - @Override - public AcmeProvider provider() { - return mockProvider; - }; - }; - - Challenge challenge = session.createChallenge(data); - assertThat(challenge, is(instanceOf(Http01Challenge.class))); - assertThat(challenge, is(sameInstance((Challenge) mockChallenge))); - - verify(mockProvider).createChallenge(session, data); + Login login = session.login(accountLocation, accountKeyPair); + assertThat(login, is(notNullValue())); + assertThat(login.getSession(), is(session)); + assertThat(login.getAccountLocation(), is(accountLocation)); + assertThat(login.getKeyPair(), is(accountKeyPair)); } /** @@ -159,7 +106,6 @@ public class SessionTest { */ @Test public void testDirectory() throws AcmeException, IOException { - KeyPair keyPair = TestUtils.createKeyPair(); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); final AcmeProvider mockProvider = mock(AcmeProvider.class); @@ -168,7 +114,7 @@ public class SessionTest { ArgumentMatchers.eq(serverUri))) .thenReturn(getJSON("directory")); - Session session = new Session(serverUri, keyPair) { + Session session = new Session(serverUri) { @Override public AcmeProvider provider() { return mockProvider; @@ -197,7 +143,6 @@ public class SessionTest { */ @Test public void testNoMeta() throws AcmeException, IOException { - KeyPair keyPair = TestUtils.createKeyPair(); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); final AcmeProvider mockProvider = mock(AcmeProvider.class); @@ -206,7 +151,7 @@ public class SessionTest { ArgumentMatchers.eq(serverUri))) .thenReturn(getJSON("directoryNoMeta")); - Session session = new Session(serverUri, keyPair) { + Session session = new Session(serverUri) { @Override public AcmeProvider provider() { return mockProvider; diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java index 8e2c3a75..08cb236e 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java @@ -19,7 +19,6 @@ import static org.shredzone.acme4j.toolbox.AcmeUtils.parseTimestamp; import static org.shredzone.acme4j.toolbox.TestUtils.*; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; @@ -28,8 +27,8 @@ import java.time.Duration; import java.time.Instant; import org.jose4j.lang.JoseException; -import org.junit.Before; import org.junit.Test; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; @@ -45,51 +44,14 @@ import org.shredzone.acme4j.toolbox.TestUtils; * Unit tests for {@link Challenge}. */ public class ChallengeTest { - private Session session; private URL locationUrl = url("https://example.com/acme/some-location"); - @Before - public void setup() throws IOException { - session = TestUtils.session(); - } - - /** - * Test that a challenge is properly restored. - */ - @Test - public void testChallenge() throws Exception { - TestableConnectionProvider provider = new TestableConnectionProvider() { - @Override - public void sendRequest(URL url, Session session) { - assertThat(url, is(locationUrl)); - } - - @Override - public JSON readJsonResponse() { - return getJSON("updateHttpChallengeResponse"); - } - }; - - Session session = provider.createSession(); - - provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new); - - Http01Challenge challenge = Challenge.bind(session, locationUrl); - - assertThat(challenge.getType(), is(Http01Challenge.TYPE)); - assertThat(challenge.getStatus(), is(Status.VALID)); - assertThat(challenge.getLocation(), is(locationUrl)); - assertThat(challenge.getToken(), is("IlirfxKKXAsHtmzK29Pj8A")); - - provider.close(); - } - /** * Test that after unmarshaling, the challenge properties are set correctly. */ @Test public void testUnmarshal() throws URISyntaxException { - Challenge challenge = new Challenge(session, getJSON("genericChallenge")); + Challenge challenge = new Challenge(TestUtils.login(), getJSON("genericChallenge")); // Test unmarshalled values assertThat(challenge.getType(), is("generic-01")); @@ -113,7 +75,7 @@ public class ChallengeTest { */ @Test public void testRespond() throws JoseException { - Challenge challenge = new Challenge(session, getJSON("genericChallenge")); + Challenge challenge = new Challenge(TestUtils.login(), getJSON("genericChallenge")); JSONBuilder response = new JSONBuilder(); challenge.prepareResponse(response); @@ -126,7 +88,7 @@ public class ChallengeTest { */ @Test(expected = AcmeProtocolException.class) public void testNotAcceptable() throws URISyntaxException { - new Http01Challenge(session, getJSON("dnsChallenge")); + new Http01Challenge(TestUtils.login(), getJSON("dnsChallenge")); } /** @@ -136,10 +98,10 @@ public class ChallengeTest { public void testTrigger() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) { assertThat(url, is(locationUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("triggerHttpChallengeRequest").toString())); - assertThat(session, is(notNullValue())); + assertThat(login, is(notNullValue())); return HttpURLConnection.HTTP_OK; } @@ -149,9 +111,9 @@ public class ChallengeTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); - Http01Challenge challenge = new Http01Challenge(session, getJSON("triggerHttpChallenge")); + Http01Challenge challenge = new Http01Challenge(login, getJSON("triggerHttpChallenge")); challenge.trigger(); @@ -183,9 +145,9 @@ public class ChallengeTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); - Challenge challenge = new Http01Challenge(session, getJSON("triggerHttpChallengeResponse")); + Challenge challenge = new Http01Challenge(login, getJSON("triggerHttpChallengeResponse")); challenge.update(); @@ -220,9 +182,9 @@ public class ChallengeTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); - Challenge challenge = new Http01Challenge(session, getJSON("triggerHttpChallengeResponse")); + Challenge challenge = new Http01Challenge(login, getJSON("triggerHttpChallengeResponse")); try { challenge.update(); @@ -237,55 +199,12 @@ public class ChallengeTest { provider.close(); } - /** - * Test that null is handled properly. - */ - @Test - public void testNullChallenge() throws Exception { - try { - Challenge.bind(session, null); - fail("locationUri accepts null"); - } catch (NullPointerException ex) { - // expected - } - - try { - Challenge.bind(null, locationUrl); - fail("session accepts null"); - } catch (NullPointerException ex) { - // expected - } - } - - /** - * Test that an exception is thrown on a bad location URL. - */ - @Test(expected = IllegalArgumentException.class) - public void testBadBind() throws Exception { - TestableConnectionProvider provider = new TestableConnectionProvider() { - @Override - public void sendRequest(URL url, Session session) { - assertThat(url, is(locationUrl)); - } - - @Override - public JSON readJsonResponse() { - return getJSON("updateAccountResponse"); - } - }; - - Session session = provider.createSession(); - Challenge.bind(session, locationUrl); - - provider.close(); - } - /** * Test that unmarshalling something different like a challenge fails. */ @Test(expected = AcmeProtocolException.class) public void testBadUnmarshall() { - new Challenge(session, getJSON("updateAccountResponse")); + new Challenge(TestUtils.login(), getJSON("updateAccountResponse")); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java index d541c5b2..fcaf174d 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/DnsChallengeTest.java @@ -20,9 +20,8 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import java.io.IOException; -import org.junit.BeforeClass; import org.junit.Test; -import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.toolbox.JSONBuilder; import org.shredzone.acme4j.toolbox.TestUtils; @@ -34,19 +33,14 @@ public class DnsChallengeTest { private static final String KEY_AUTHORIZATION = "pNvmJivs0WCko2suV7fhe-59oFqyYx_yB7tx6kIMAyE.HnWjTDnyqlCrm6tZ-6wX-TrEXgRdeNu9G71gqxSO6o0"; - private static Session session; - - @BeforeClass - public static void setup() throws IOException { - session = TestUtils.session(); - } + private Login login = TestUtils.login(); /** * Test that {@link Dns01Challenge} generates a correct authorization key. */ @Test public void testDnsChallenge() throws IOException { - Dns01Challenge challenge = new Dns01Challenge(session, getJSON("dnsChallenge")); + Dns01Challenge challenge = new Dns01Challenge(login, getJSON("dnsChallenge")); assertThat(challenge.getType(), is(Dns01Challenge.TYPE)); assertThat(challenge.getStatus(), is(Status.PENDING)); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/HttpChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/HttpChallengeTest.java index 63275fa6..2773d2a9 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/HttpChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/HttpChallengeTest.java @@ -20,9 +20,8 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import java.io.IOException; -import org.junit.BeforeClass; import org.junit.Test; -import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.toolbox.JSONBuilder; @@ -37,19 +36,14 @@ public class HttpChallengeTest { private static final String KEY_AUTHORIZATION = "rSoI9JpyvFi-ltdnBW0W1DjKstzG7cHixjzcOjwzAEQ.HnWjTDnyqlCrm6tZ-6wX-TrEXgRdeNu9G71gqxSO6o0"; - private static Session session; - - @BeforeClass - public static void setup() throws IOException { - session = TestUtils.session(); - } + private Login login = TestUtils.login(); /** * Test that {@link Http01Challenge} generates a correct authorization key. */ @Test public void testHttpChallenge() throws IOException { - Http01Challenge challenge = new Http01Challenge(session, getJSON("httpChallenge")); + Http01Challenge challenge = new Http01Challenge(login, getJSON("httpChallenge")); assertThat(challenge.getType(), is(Http01Challenge.TYPE)); assertThat(challenge.getStatus(), is(Status.PENDING)); @@ -68,7 +62,7 @@ public class HttpChallengeTest { */ @Test(expected = AcmeProtocolException.class) public void testNoTokenSet() { - Http01Challenge challenge = new Http01Challenge(session, getJSON("httpNoTokenChallenge")); + Http01Challenge challenge = new Http01Challenge(login, getJSON("httpNoTokenChallenge")); challenge.getToken(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java index 26c7aaef..e882c176 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java @@ -26,6 +26,7 @@ import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import java.security.KeyPair; import java.security.cert.X509Certificate; import java.time.Duration; import java.time.Instant; @@ -43,6 +44,7 @@ import org.jose4j.jwx.CompactSerializer; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentMatchers; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeNetworkException; @@ -64,10 +66,12 @@ import org.shredzone.acme4j.toolbox.TestUtils; public class DefaultConnectionTest { private URL requestUrl = TestUtils.url("http://example.com/acme/"); - private URL accountUrl = TestUtils.url(TestUtils.ACME_SERVER_URI + "/acct/1"); + private URL accountUrl = TestUtils.url(TestUtils.ACCOUNT_URL); private HttpURLConnection mockUrlConnection; private HttpConnector mockHttpConnection; private Session session; + private Login login; + private KeyPair keyPair; @Before public void setup() throws AcmeException, IOException { @@ -84,11 +88,15 @@ public class DefaultConnectionTest { session = TestUtils.session(mockProvider); session.setLocale(Locale.JAPAN); + + keyPair = TestUtils.createKeyPair(); + + login = session.login(accountUrl, keyPair); } /** - * Test if {@link DefaultConnection#updateSession(Session)} does nothing if there is - * no {@code Replay-Nonce} header. + * Test that {@link DefaultConnection#getNonce()} returns {@code null} if there is no + * {@code Replay-Nonce} header. */ @Test public void testNoNonceFromHeader() throws AcmeException { @@ -105,8 +113,8 @@ public class DefaultConnectionTest { } /** - * Test that {@link DefaultConnection#updateSession(Session)} extracts a - * {@code Replay-Nonce} header correctly. + * Test that {@link DefaultConnection#getNonce()} extracts a {@code Replay-Nonce} + * header correctly. */ @Test public void testGetNonceFromHeader() { @@ -123,7 +131,7 @@ public class DefaultConnectionTest { } /** - * Test that {@link DefaultConnection#updateSession(Session)} fails on an invalid + * Test that {@link DefaultConnection#getNonce()} fails on an invalid * {@code Replay-Nonce} header. */ @Test @@ -400,11 +408,10 @@ public class DefaultConnectionTest { when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream()); - session.setAccountLocation(accountUrl); session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - int rc = conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); + int rc = conn.sendSignedRequest(requestUrl, new JSONBuilder(), login); assertThat(rc, is(HttpURLConnection.HTTP_OK)); } @@ -424,11 +431,10 @@ public class DefaultConnectionTest { when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8"))); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); - session.setAccountLocation(accountUrl); session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), login); fail("Expected to fail"); } catch (AcmeUnauthorizedException ex) { assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:unauthorized"))); @@ -460,11 +466,10 @@ public class DefaultConnectionTest { when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8"))); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); - session.setAccountLocation(accountUrl); session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), login); fail("Expected to fail"); } catch (AcmeUserActionRequiredException ex) { assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:userActionRequired"))); @@ -502,11 +507,10 @@ public class DefaultConnectionTest { when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8"))); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); - session.setAccountLocation(accountUrl); session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), login); fail("Expected to fail"); } catch (AcmeRateLimitedException ex) { assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:rateLimited"))); @@ -542,7 +546,6 @@ public class DefaultConnectionTest { when(mockUrlConnection.getOutputStream()) .thenReturn(new ByteArrayOutputStream()); - session.setAccountLocation(accountUrl); session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @@ -554,7 +557,7 @@ public class DefaultConnectionTest { return result.toJSON(); }; }) { - conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), login); fail("Expected to fail"); } catch (AcmeServerException ex) { assertThat(ex.getType(), is(URI.create("urn:zombie:error:apocalypse"))); @@ -582,7 +585,6 @@ public class DefaultConnectionTest { when(mockUrlConnection.getOutputStream()) .thenReturn(new ByteArrayOutputStream()); - session.setAccountLocation(accountUrl); session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @@ -591,7 +593,7 @@ public class DefaultConnectionTest { return JSON.empty(); }; }) { - conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), login); fail("Expected to fail"); } catch (AcmeNetworkException ex) { fail("Did not expect an AcmeNetworkException"); @@ -618,11 +620,10 @@ public class DefaultConnectionTest { when(mockUrlConnection.getOutputStream()) .thenReturn(new ByteArrayOutputStream()); - session.setAccountLocation(accountUrl); session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), login); fail("Expected to fail"); } catch (AcmeException ex) { assertThat(ex.getMessage(), is("HTTP 500: Infernal Server Error")); @@ -690,8 +691,7 @@ public class DefaultConnectionTest { }) { JSONBuilder cb = new JSONBuilder(); cb.put("foo", 123).put("bar", "a-string"); - session.setAccountLocation(accountUrl); - conn.sendSignedRequest(requestUrl, cb, session); + conn.sendSignedRequest(requestUrl, cb, login); } verify(mockUrlConnection).setRequestMethod("POST"); @@ -726,7 +726,7 @@ public class DefaultConnectionTest { JsonWebSignature jws = new JsonWebSignature(); jws.setCompactSerialization(CompactSerializer.serialize(encodedHeader, encodedPayload, encodedSignature)); - jws.setKey(session.getKeyPair().getPublic()); + jws.setKey(login.getKeyPair().getPublic()); assertThat(jws.verifySignature(), is(true)); } @@ -766,7 +766,7 @@ public class DefaultConnectionTest { }) { JSONBuilder cb = new JSONBuilder(); cb.put("foo", 123).put("bar", "a-string"); - conn.sendSignedRequest(requestUrl, cb, session, true); + conn.sendSignedRequest(requestUrl, cb, session, keyPair); } verify(mockUrlConnection).setRequestMethod("POST"); @@ -804,7 +804,7 @@ public class DefaultConnectionTest { JsonWebSignature jws = new JsonWebSignature(); jws.setCompactSerialization(CompactSerializer.serialize(encodedHeader, encodedPayload, encodedSignature)); - jws.setKey(session.getKeyPair().getPublic()); + jws.setKey(login.getKeyPair().getPublic()); assertThat(jws.verifySignature(), is(true)); } @@ -820,7 +820,7 @@ public class DefaultConnectionTest { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { JSONBuilder cb = new JSONBuilder(); - conn.sendSignedRequest(requestUrl, cb, DefaultConnectionTest.this.session, true); + conn.sendSignedRequest(requestUrl, cb, DefaultConnectionTest.this.session, DefaultConnectionTest.this.keyPair); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java index 24393a30..9851e51d 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java @@ -14,10 +14,12 @@ package org.shredzone.acme4j.connector; import java.net.URL; +import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.List; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.toolbox.JSON; @@ -40,13 +42,13 @@ public class DummyConnection implements Connection { } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session) + public int sendSignedRequest(URL url, JSONBuilder claims, Login login) throws AcmeException { throw new UnsupportedOperationException(); } @Override - public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) throws AcmeException { throw new UnsupportedOperationException(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java index 2dbe45b8..9f708137 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java @@ -30,6 +30,7 @@ import java.util.NoSuchElementException; import org.junit.Before; import org.junit.Test; import org.shredzone.acme4j.Authorization; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.toolbox.JSON; @@ -158,11 +159,11 @@ public class ResourceIteratorTest { } }; - Session session = provider.createSession(); + Login login = provider.createLogin(); provider.close(); - return new ResourceIterator<>(session, TYPE, first, Authorization::bind); + return new ResourceIterator<>(login, TYPE, first, Login::bindAuthorization); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/SessionProviderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/SessionProviderTest.java index 5310499a..8c2394cc 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/SessionProviderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/SessionProviderTest.java @@ -16,20 +16,17 @@ package org.shredzone.acme4j.connector; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; -import java.io.IOException; import java.net.URI; import java.net.URL; -import java.security.KeyPair; import java.util.ServiceLoader; -import org.junit.Before; import org.junit.Test; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.provider.AcmeProvider; import org.shredzone.acme4j.toolbox.JSON; -import org.shredzone.acme4j.toolbox.TestUtils; /** * Unit tests for {@link Session#provider()}. Requires that both enclosed @@ -38,20 +35,13 @@ import org.shredzone.acme4j.toolbox.TestUtils; */ public class SessionProviderTest { - private KeyPair keyPair; - - @Before - public void setup() throws IOException { - keyPair = TestUtils.createKeyPair(); - } - /** * There are no testing providers accepting {@code acme://example.org}. Test that * connecting to this URI will result in an {@link IllegalArgumentException}. */ @Test(expected = IllegalArgumentException.class) public void testNone() throws Exception { - new Session(new URI("acme://example.org"), keyPair).provider(); + new Session(new URI("acme://example.org")).provider(); } /** @@ -60,7 +50,7 @@ public class SessionProviderTest { */ @Test public void testConnectURI() throws Exception { - Session session = new Session(new URI("acme://example.com"), keyPair); + Session session = new Session(new URI("acme://example.com")); AcmeProvider provider = session.provider(); assertThat(provider, is(instanceOf(Provider1.class))); @@ -76,7 +66,7 @@ public class SessionProviderTest { */ @Test(expected = IllegalArgumentException.class) public void testDuplicate() throws Exception { - new Session(new URI("acme://example.net"), keyPair).provider(); + new Session(new URI("acme://example.net")).provider(); } public static class Provider1 implements AcmeProvider { @@ -103,7 +93,7 @@ public class SessionProviderTest { } @Override - public Challenge createChallenge(Session session, JSON data) { + public Challenge createChallenge(Login login, JSON data) { throw new UnsupportedOperationException(); } } @@ -131,7 +121,7 @@ public class SessionProviderTest { } @Override - public Challenge createChallenge(Session session, JSON data) { + public Challenge createChallenge(Login login, JSON data) { throw new UnsupportedOperationException(); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeLazyLoadingExceptionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeLazyLoadingExceptionTest.java index 787e5025..7d642ac5 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeLazyLoadingExceptionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/exception/AcmeLazyLoadingExceptionTest.java @@ -21,7 +21,7 @@ import java.net.URL; import org.junit.Test; import org.shredzone.acme4j.AcmeResource; -import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.toolbox.TestUtils; /** @@ -33,8 +33,8 @@ public class AcmeLazyLoadingExceptionTest { @Test public void testAcmeLazyLoadingException() { - Session session = mock(Session.class); - AcmeResource resource = new TestResource(session, resourceUrl); + Login login = mock(Login.class); + AcmeResource resource = new TestResource(login, resourceUrl); AcmeException cause = new AcmeException("Something went wrong"); @@ -50,9 +50,8 @@ public class AcmeLazyLoadingExceptionTest { private static class TestResource extends AcmeResource { private static final long serialVersionUID = 1023419539450677538L; - public TestResource(Session session, URL location) { - super(session); - setLocation(location); + public TestResource(Login login, URL location) { + super(login, location); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java index 3e645b11..723ebc78 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java @@ -24,6 +24,7 @@ import java.net.URL; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; @@ -120,7 +121,7 @@ public class AbstractAcmeProviderTest { */ @Test public void testCreateChallenge() { - Session session = mock(Session.class); + Login login = mock(Login.class); AbstractAcmeProvider provider = new AbstractAcmeProvider() { @Override @@ -134,14 +135,14 @@ public class AbstractAcmeProviderTest { } }; - Challenge c1 = provider.createChallenge(session, getJSON("httpChallenge")); + Challenge c1 = provider.createChallenge(login, getJSON("httpChallenge")); assertThat(c1, not(nullValue())); assertThat(c1, instanceOf(Http01Challenge.class)); - Challenge c2 = provider.createChallenge(session, getJSON("httpChallenge")); + Challenge c2 = provider.createChallenge(login, getJSON("httpChallenge")); assertThat(c2, not(sameInstance(c1))); - Challenge c3 = provider.createChallenge(session, getJSON("dnsChallenge")); + Challenge c3 = provider.createChallenge(login, getJSON("dnsChallenge")); assertThat(c3, not(nullValue())); assertThat(c3, instanceOf(Dns01Challenge.class)); @@ -149,7 +150,7 @@ public class AbstractAcmeProviderTest { .put("type", "foobar-01") .put("url", "https://example.com/some/challenge") .toJSON(); - Challenge c6 = provider.createChallenge(session, json6); + Challenge c6 = provider.createChallenge(login, json6); assertThat(c6, not(nullValue())); assertThat(c6, instanceOf(Challenge.class)); @@ -158,7 +159,7 @@ public class AbstractAcmeProviderTest { .put("token", "abc123") .put("url", "https://example.com/some/challenge") .toJSON(); - Challenge c7 = provider.createChallenge(session, json7); + Challenge c7 = provider.createChallenge(login, json7); assertThat(c7, not(nullValue())); assertThat(c7, instanceOf(TokenChallenge.class)); @@ -166,14 +167,14 @@ public class AbstractAcmeProviderTest { JSON json8 = new JSONBuilder() .put("url", "https://example.com/some/challenge") .toJSON(); - provider.createChallenge(session, json8); + provider.createChallenge(login, json8); fail("Challenge without type was accepted"); } catch (AcmeProtocolException ex) { // expected } try { - provider.createChallenge(session, null); + provider.createChallenge(login, null); fail("null was accepted"); } catch (NullPointerException ex) { // expected diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/TestableConnectionProvider.java b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/TestableConnectionProvider.java index e2539da2..0f8ac7ed 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/TestableConnectionProvider.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/TestableConnectionProvider.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.connector.Connection; @@ -35,7 +36,7 @@ import org.shredzone.acme4j.toolbox.TestUtils; * of {@link Connection} that is always returned on {@link #connect()}. */ public class TestableConnectionProvider extends DummyConnection implements AcmeProvider { - private final Map> creatorMap = new HashMap<>(); + private final Map> creatorMap = new HashMap<>(); private final Map createdMap = new HashMap<>(); private final JSONBuilder directory = new JSONBuilder(); @@ -59,7 +60,7 @@ public class TestableConnectionProvider extends DummyConnection implements AcmeP * @param creator * Creator {@link BiFunction} that creates a matching {@link Challenge} */ - public void putTestChallenge(String type, BiFunction creator) { + public void putTestChallenge(String type, BiFunction creator) { creatorMap.put(type, creator); } @@ -80,10 +81,18 @@ public class TestableConnectionProvider extends DummyConnection implements AcmeP /** * Creates a {@link Session} that uses this {@link AcmeProvider}. */ - public Session createSession() throws IOException { + public Session createSession() { return TestUtils.session(this); } + /** + * Creates a {@link Login} that uses this {@link AcmeProvider}. + */ + public Login createLogin() throws IOException { + Session session = createSession(); + return session.login(new URL(TestUtils.ACCOUNT_URL), TestUtils.createKeyPair()); + } + @Override public boolean accepts(URI serverUri) { throw new UnsupportedOperationException(); @@ -108,7 +117,7 @@ public class TestableConnectionProvider extends DummyConnection implements AcmeP } @Override - public Challenge createChallenge(Session session, JSON data) { + public Challenge createChallenge(Login login, JSON data) { if (creatorMap.isEmpty()) { throw new UnsupportedOperationException(); } @@ -117,9 +126,9 @@ public class TestableConnectionProvider extends DummyConnection implements AcmeP String type = data.get("type").asString(); if (creatorMap.containsKey(type)) { - created = creatorMap.get(type).apply(session, data); + created = creatorMap.get(type).apply(login, data); } else { - created = new Challenge(session, data); + created = new Challenge(login, data); } createdMap.put(type, created); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java index 4279a126..e72627bc 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java @@ -51,6 +51,7 @@ import org.jose4j.json.JsonUtil; import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKey.OutputControlLevel; import org.jose4j.keys.HmacKey; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.provider.AcmeProvider; @@ -70,9 +71,11 @@ public final class TestUtils { public static final String D_THUMBPRINT = "0VPbh7-I6swlkBu0TrNKSQp6d69bukzeQA0ksuX3FFs"; public static final String ACME_SERVER_URI = "https://example.com/acme"; + public static final String ACCOUNT_URL = "https://example.com/acme/account/1"; public static final byte[] DUMMY_NONCE = "foo-nonce-foo".getBytes(); + private TestUtils() { // utility class without constructor } @@ -114,9 +117,20 @@ public final class TestUtils { /** * Creates a {@link Session} instance. It uses {@link #ACME_SERVER_URI} as server URI. */ - public static Session session() throws IOException { - KeyPair keyPair = createKeyPair(); - return new Session(URI.create(ACME_SERVER_URI), keyPair); + public static Session session() { + return new Session(URI.create(ACME_SERVER_URI)); + } + + /** + * Creates a {@link Login} instance. It uses {@link #ACME_SERVER_URI} as server URI, + * {@link #ACCOUNT_URL} as account URL, and a random key pair. + */ + public static Login login() { + try { + return session().login(new URL(ACCOUNT_URL), createKeyPair()); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } } /** @@ -141,9 +155,8 @@ public final class TestUtils { * @param provider * {@link AcmeProvider} to be used in this session */ - public static Session session(final AcmeProvider provider) throws IOException { - KeyPair keyPair = createKeyPair(); - return new Session(URI.create(ACME_SERVER_URI), keyPair) { + public static Session session(final AcmeProvider provider) { + return new Session(URI.create(ACME_SERVER_URI)) { @Override public AcmeProvider provider() { return provider; diff --git a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java b/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java index e93d9e7f..16d82f69 100644 --- a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java +++ b/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java @@ -77,11 +77,11 @@ public class ClientTest { // Create a session for Let's Encrypt. // Use "acme://letsencrypt.org" for production server - Session session = new Session("acme://letsencrypt.org/staging", userKeyPair); + Session session = new Session("acme://letsencrypt.org/staging"); // Get the Account. // If there is no account yet, create a new one. - Account acct = findOrRegisterAccount(session); + Account acct = findOrRegisterAccount(session, userKeyPair); // Load or create a key pair for the domains. This should not be the userKeyPair! KeyPair domainKeyPair = loadOrCreateDomainKeyPair(); @@ -179,19 +179,22 @@ public class ClientTest { * * @param session * {@link Session} to bind with - * @return {@link Account} connected to your account + * @return {@link Login} that is connected to your account */ - private Account findOrRegisterAccount(Session session) throws AcmeException { + private Account findOrRegisterAccount(Session session, KeyPair accountKey) throws AcmeException { // Ask the user to accept the TOS, if server provides us with a link. URI tos = session.getMetadata().getTermsOfService(); if (tos != null) { acceptAgreement(tos); } - Account acct = new AccountBuilder().agreeToTermsOfService().create(session); - LOG.info("Registered a new user, URL: " + acct.getLocation()); + Account account = new AccountBuilder() + .agreeToTermsOfService() + .useKeyPair(accountKey) + .create(session); + LOG.info("Registered a new user, URL: " + account.getLocation()); - return acct; + return account; } /** diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java index d6278c9d..33b314dc 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderHttpIT.java @@ -53,11 +53,12 @@ public class OrderHttpIT { */ @Test public void testHttpValidation() throws Exception { + Session session = new Session(boulderURI()); KeyPair keyPair = createKeyPair(); - Session session = new Session(boulderURI(), keyPair); Account account = new AccountBuilder() .agreeToTermsOfService() + .useKeyPair(keyPair) .create(session); KeyPair domainKeyPair = createKeyPair(); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/AccountIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/AccountIT.java index 0e717a25..c3924c0f 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/AccountIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/AccountIT.java @@ -24,6 +24,7 @@ import org.junit.Ignore; import org.junit.Test; import org.shredzone.acme4j.Account; import org.shredzone.acme4j.AccountBuilder; +import org.shredzone.acme4j.Login; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.exception.AcmeException; @@ -41,25 +42,29 @@ public class AccountIT extends PebbleITBase { @Test public void testCreate() throws AcmeException { KeyPair keyPair = createKeyPair(); - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); // Register a new user - AccountBuilder ab = new AccountBuilder(); - ab.addContact("mailto:acme@example.com"); - ab.agreeToTermsOfService(); + Login login = new AccountBuilder() + .addContact("mailto:acme@example.com") + .agreeToTermsOfService() + .useKeyPair(keyPair) + .createLogin(session); - Account acct = ab.create(session); - URL location = acct.getLocation(); + URL location = login.getAccountLocation(); assertIsPebbleUrl(location); - assertThat(session.getAccountLocation(), is(location)); // Check registered data + Account acct = login.getAccount(); + assertThat(acct.getLocation(), is(location)); assertThat(acct.getContacts(), contains(URI.create("mailto:acme@example.com"))); assertThat(acct.getStatus(), is(Status.VALID)); // Bind another Account object - Session session2 = new Session(pebbleURI(), keyPair); - Account acct2 = Account.bind(session2, location); + Session session2 = new Session(pebbleURI()); + Login login2 = new Login(location, keyPair, session2); + assertThat(login2.getAccountLocation(), is(location)); + Account acct2 = login2.getAccount(); assertThat(acct2.getLocation(), is(location)); assertThat(acct2.getContacts(), contains(URI.create("mailto:acme@example.com"))); assertThat(acct2.getStatus(), is(Status.VALID)); @@ -73,21 +78,25 @@ public class AccountIT extends PebbleITBase { KeyPair keyPair = createKeyPair(); // Register a new user - Session session1 = new Session(pebbleURI(), keyPair); - Account acct1 = new AccountBuilder() + Session session1 = new Session(pebbleURI()); + Login login1 = new AccountBuilder() .addContact("mailto:acme@example.com") .agreeToTermsOfService() - .create(session1); - URL location1 = acct1.getLocation(); + .useKeyPair(keyPair) + .createLogin(session1); + + URL location1 = login1.getAccountLocation(); assertIsPebbleUrl(location1); // Try to register the same account again - Session session2 = new Session(pebbleURI(), keyPair); - Account acct2 = new AccountBuilder() + Session session2 = new Session(pebbleURI()); + Login login2 = new AccountBuilder() .addContact("mailto:acme@example.com") .agreeToTermsOfService() - .create(session2); - URL location2 = acct2.getLocation(); + .useKeyPair(keyPair) + .createLogin(session2); + + URL location2 = login2.getAccountLocation(); assertIsPebbleUrl(location2); assertThat(location1, is(location2)); @@ -100,21 +109,23 @@ public class AccountIT extends PebbleITBase { public void testCreateOnlyExisting() throws AcmeException { KeyPair keyPair = createKeyPair(); - Session session1 = new Session(pebbleURI(), keyPair); - Account acct1 = new AccountBuilder() + Session session1 = new Session(pebbleURI()); + Login login1 = new AccountBuilder() .agreeToTermsOfService() - .create(session1); - URL location1 = acct1.getLocation(); - assertIsPebbleUrl(location1); - assertThat(session1.getAccountLocation(), is(location1)); + .useKeyPair(keyPair) + .createLogin(session1); - Session session2 = new Session(pebbleURI(), keyPair); - Account acct2 = new AccountBuilder() + URL location1 = login1.getAccountLocation(); + assertIsPebbleUrl(location1); + + Session session2 = new Session(pebbleURI()); + Login login2 = new AccountBuilder() .onlyExisting() - .create(session2); - URL location2 = acct2.getLocation(); + .useKeyPair(keyPair) + .createLogin(session2); + + URL location2 = login2.getAccountLocation(); assertIsPebbleUrl(location2); - assertThat(session2.getAccountLocation(), is(location2)); assertThat(location1, is(location2)); } @@ -127,8 +138,8 @@ public class AccountIT extends PebbleITBase { public void testNotExisting() throws AcmeException { try { KeyPair keyPair = createKeyPair(); - Session session = new Session(pebbleURI(), keyPair); - new AccountBuilder().onlyExisting().create(session); + Session session = new Session(pebbleURI()); + new AccountBuilder().onlyExisting().useKeyPair(keyPair).create(session); fail("onlyExisting flag was ignored"); } catch (AcmeServerException ex) { assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:accountDoesNotExist"))); @@ -141,11 +152,12 @@ public class AccountIT extends PebbleITBase { @Test public void testModify() throws AcmeException { KeyPair keyPair = createKeyPair(); - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); Account acct = new AccountBuilder() .addContact("mailto:acme@example.com") .agreeToTermsOfService() + .useKeyPair(keyPair) .create(session); URL location = acct.getLocation(); assertIsPebbleUrl(location); @@ -170,24 +182,27 @@ public class AccountIT extends PebbleITBase { @Ignore // TODO PEBBLE: missing public void testKeyChange() throws AcmeException { KeyPair keyPair = createKeyPair(); - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); - Account acct = new AccountBuilder().agreeToTermsOfService().create(session); + Account acct = new AccountBuilder() + .agreeToTermsOfService() + .useKeyPair(keyPair) + .create(session); URL location = acct.getLocation(); KeyPair newKeyPair = createKeyPair(); acct.changeKey(newKeyPair); try { - Session sessionOldKey = new Session(pebbleURI(), keyPair); - Account oldAccount = Account.bind(sessionOldKey, location); + Session sessionOldKey = new Session(pebbleURI()); + Account oldAccount = sessionOldKey.login(location, keyPair).getAccount(); oldAccount.update(); } catch (AcmeUnauthorizedException ex) { // Expected } - Session sessionNewKey = new Session(pebbleURI(), newKeyPair); - Account newAccount = Account.bind(sessionNewKey, location); + Session sessionNewKey = new Session(pebbleURI()); + Account newAccount = sessionNewKey.login(location, newKeyPair).getAccount(); assertThat(newAccount.getStatus(), is(Status.VALID)); } @@ -198,15 +213,18 @@ public class AccountIT extends PebbleITBase { @Ignore // TODO PEBBLE: missing public void testDeactivate() throws AcmeException { KeyPair keyPair = createKeyPair(); - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); - Account acct = new AccountBuilder().agreeToTermsOfService().create(session); + Account acct = new AccountBuilder() + .agreeToTermsOfService() + .useKeyPair(keyPair) + .create(session); URL location = acct.getLocation(); acct.deactivate(); - Session session2 = new Session(pebbleURI(), keyPair); - Account acct2 = Account.bind(session2, location); + Session session2 = new Session(pebbleURI()); + Account acct2 = session2.login(location, keyPair).getAccount(); assertThat(acct2.getLocation(), is(location)); assertThat(acct2.getStatus(), is(Status.DEACTIVATED)); } diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java index 1d372cdb..c5e29095 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java @@ -98,10 +98,11 @@ public class OrderIT extends PebbleITBase { */ private void orderCertificate(String domain, Validator validator) throws Exception { KeyPair keyPair = createKeyPair(); - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); Account account = new AccountBuilder() .agreeToTermsOfService() + .useKeyPair(keyPair) .create(session); KeyPair domainKeyPair = createKeyPair(); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java index 6a8702a7..cd86bb4f 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderWildcardIT.java @@ -55,10 +55,11 @@ public class OrderWildcardIT extends PebbleITBase { public void testDnsValidation() throws Exception { BammBammClient client = getBammBammClient(); KeyPair keyPair = createKeyPair(); - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); Account account = new AccountBuilder() .agreeToTermsOfService() + .useKeyPair(keyPair) .create(session); KeyPair domainKeyPair = createKeyPair(); diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java index e31ac718..c5f1096b 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/SessionIT.java @@ -18,7 +18,6 @@ import static org.junit.Assert.assertThat; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import java.net.URI; -import java.security.KeyPair; import org.junit.Test; import org.shredzone.acme4j.Metadata; @@ -31,11 +30,9 @@ import org.shredzone.acme4j.exception.AcmeException; */ public class SessionIT extends PebbleITBase { - private final KeyPair keyPair = createKeyPair(); - @Test public void testNonce() throws AcmeException { - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); // No nonce yet on a fresh session assertThat(session.getNonce(), is(nullValue())); @@ -50,7 +47,7 @@ public class SessionIT extends PebbleITBase { @Test public void testResources() throws AcmeException { - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); assertIsPebbleUrl(session.resourceUrl(Resource.NEW_ACCOUNT)); assertIsPebbleUrl(session.resourceUrl(Resource.NEW_NONCE)); @@ -59,7 +56,7 @@ public class SessionIT extends PebbleITBase { @Test public void testMetadata() throws AcmeException { - Session session = new Session(pebbleURI(), keyPair); + Session session = new Session(pebbleURI()); Metadata meta = session.getMetadata(); assertThat(meta, not(nullValue()));