Separate Login from Session

This involves a lot of refactoring and some API changes. However, it allows to clean up some parts of the code that I always considered ugly.
pull/61/head
Richard Körber 2018-02-21 20:01:51 +01:00
parent a111187245
commit dadaf2493f
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
46 changed files with 862 additions and 825 deletions

View File

@ -53,23 +53,8 @@ public class Account extends AcmeJsonResource {
private static final String KEY_CONTACT = "contact"; private static final String KEY_CONTACT = "contact";
private static final String KEY_STATUS = "status"; private static final String KEY_STATUS = "status";
protected Account(Session session, URL location) { protected Account(Login login) {
super(session); super(login, login.getAccountLocation());
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);
} }
/** /**
@ -112,14 +97,14 @@ public class Account extends AcmeJsonResource {
*/ */
public Iterator<Order> getOrders() throws AcmeException { public Iterator<Order> getOrders() throws AcmeException {
URL ordersUrl = getJSON().get(KEY_ORDERS).asURL(); 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 @Override
public void update() throws AcmeException { public void update() throws AcmeException {
LOG.debug("update Account"); LOG.debug("update Account");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
conn.sendSignedRequest(getLocation(), new JSONBuilder(), getSession()); conn.sendSignedRequest(getLocation(), new JSONBuilder(), getLogin());
setJSON(conn.readJsonResponse()); setJSON(conn.readJsonResponse());
} }
} }
@ -130,7 +115,7 @@ public class Account extends AcmeJsonResource {
* @return {@link OrderBuilder} object * @return {@link OrderBuilder} object
*/ */
public OrderBuilder newOrder() throws AcmeException { 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); LOG.debug("preAuthorizeDomain {}", domain);
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
claims.object("identifier") claims.object("identifier")
.put("type", "dns") .put("type", "dns")
.put("value", toAce(domain)); .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()); auth.setJSON(conn.readJsonResponse());
return auth; return auth;
} }
@ -186,14 +171,14 @@ public class Account extends AcmeJsonResource {
*/ */
public void changeKey(KeyPair newKeyPair) throws AcmeException { public void changeKey(KeyPair newKeyPair) throws AcmeException {
Objects.requireNonNull(newKeyPair, "newKeyPair"); Objects.requireNonNull(newKeyPair, "newKeyPair");
if (Arrays.equals(getSession().getKeyPair().getPrivate().getEncoded(), if (Arrays.equals(getLogin().getKeyPair().getPrivate().getEncoded(),
newKeyPair.getPrivate().getEncoded())) { newKeyPair.getPrivate().getEncoded())) {
throw new IllegalArgumentException("newKeyPair must actually be a new key pair"); throw new IllegalArgumentException("newKeyPair must actually be a new key pair");
} }
LOG.debug("key-change"); LOG.debug("key-change");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
URL keyChangeUrl = getSession().resourceUrl(Resource.KEY_CHANGE); URL keyChangeUrl = getSession().resourceUrl(Resource.KEY_CHANGE);
PublicJsonWebKey newKeyJwk = PublicJsonWebKey.Factory.newPublicJwk(newKeyPair.getPublic()); PublicJsonWebKey newKeyJwk = PublicJsonWebKey.Factory.newPublicJwk(newKeyPair.getPublic());
@ -214,9 +199,9 @@ public class Account extends AcmeJsonResource {
outerClaim.put("signature", innerJws.getEncodedSignature()); outerClaim.put("signature", innerJws.getEncodedSignature());
outerClaim.put("payload", innerJws.getEncodedPayload()); outerClaim.put("payload", innerJws.getEncodedPayload());
conn.sendSignedRequest(keyChangeUrl, outerClaim, getSession()); conn.sendSignedRequest(keyChangeUrl, outerClaim, getLogin());
getSession().setKeyPair(newKeyPair); getLogin().setKeyPair(newKeyPair);
} catch (JoseException ex) { } catch (JoseException ex) {
throw new AcmeProtocolException("Cannot sign key-change", ex); throw new AcmeProtocolException("Cannot sign key-change", ex);
} }
@ -230,11 +215,11 @@ public class Account extends AcmeJsonResource {
*/ */
public void deactivate() throws AcmeException { public void deactivate() throws AcmeException {
LOG.debug("deactivate"); LOG.debug("deactivate");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
claims.put(KEY_STATUS, "deactivated"); claims.put(KEY_STATUS, "deactivated");
conn.sendSignedRequest(getLocation(), claims, getSession()); conn.sendSignedRequest(getLocation(), claims, getLogin());
setJSON(conn.readJsonResponse()); setJSON(conn.readJsonResponse());
} }
@ -298,13 +283,13 @@ public class Account extends AcmeJsonResource {
*/ */
public void commit() throws AcmeException { public void commit() throws AcmeException {
LOG.debug("modify/commit"); LOG.debug("modify/commit");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
if (!editContacts.isEmpty()) { if (!editContacts.isEmpty()) {
claims.put(KEY_CONTACT, editContacts); claims.put(KEY_CONTACT, editContacts);
} }
conn.sendSignedRequest(getLocation(), claims, getSession()); conn.sendSignedRequest(getLocation(), claims, getLogin());
setJSON(conn.readJsonResponse()); setJSON(conn.readJsonResponse());
} }

View File

@ -18,6 +18,7 @@ import static org.shredzone.acme4j.toolbox.AcmeUtils.macKeyAlgorithm;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.security.KeyPair;
import java.security.PublicKey; import java.security.PublicKey;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -50,6 +51,7 @@ public class AccountBuilder {
private Boolean termsOfServiceAgreed; private Boolean termsOfServiceAgreed;
private Boolean onlyExisting; private Boolean onlyExisting;
private String keyIdentifier; private String keyIdentifier;
private KeyPair keyPair;
private SecretKey macKey; private SecretKey macKey;
/** /**
@ -110,6 +112,18 @@ public class AccountBuilder {
return this; 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 * 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. * an individual account identification, e.g. your customer number.
@ -120,7 +134,7 @@ public class AccountBuilder {
* MAC key * MAC key
* @return itself * @return itself
*/ */
public AccountBuilder useKeyIdentifier(String kid, SecretKey macKey) { public AccountBuilder withKeyIdentifier(String kid, SecretKey macKey) {
if (kid != null && kid.isEmpty()) { if (kid != null && kid.isEmpty()) {
throw new IllegalArgumentException("kid must not be empty"); 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. * Base64url encoded MAC key. It will be decoded for your convenience.
* @return itself * @return itself
*/ */
public AccountBuilder useKeyIdentifier(String kid, String encodedMacKey) { public AccountBuilder withKeyIdentifier(String kid, String encodedMacKey) {
byte[] encodedKey = AcmeUtils.base64UrlDecode(requireNonNull(encodedMacKey, "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 * @return {@link Account} referring to the new account
*/ */
public Account create(Session session) throws AcmeException { 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.
* <p>
* 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()) { try (Connection conn = session.provider().connect()) {
URL resourceUrl = session.resourceUrl(Resource.NEW_ACCOUNT); URL resourceUrl = session.resourceUrl(Resource.NEW_ACCOUNT);
@ -170,19 +199,19 @@ public class AccountBuilder {
} }
if (keyIdentifier != null) { if (keyIdentifier != null) {
claims.put("externalAccountBinding", createExternalAccountBinding( claims.put("externalAccountBinding", createExternalAccountBinding(
keyIdentifier, session.getKeyPair().getPublic(), macKey, resourceUrl)); keyIdentifier, keyPair.getPublic(), macKey, resourceUrl));
} }
if (onlyExisting != null) { if (onlyExisting != null) {
claims.put("onlyReturnExisting", onlyExisting); claims.put("onlyReturnExisting", onlyExisting);
} }
conn.sendSignedRequest(resourceUrl, claims, session, true); conn.sendSignedRequest(resourceUrl, claims, session, keyPair);
URL location = conn.getLocation(); URL location = conn.getLocation();
Account account = new Account(session, location); Login login = new Login(location, keyPair, session);
account.setJSON(conn.readJsonResponse()); login.getAccount().setJSON(conn.readJsonResponse());
return account; return login;
} }
} }

View File

@ -13,6 +13,7 @@
*/ */
package org.shredzone.acme4j; package org.shredzone.acme4j;
import java.net.URL;
import java.util.Objects; import java.util.Objects;
import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Connection;
@ -35,24 +36,13 @@ public abstract class AcmeJsonResource extends AcmeResource {
/** /**
* Create a new {@link AcmeJsonResource}. * Create a new {@link AcmeJsonResource}.
* *
* @param session * @param login
* {@link Session} the resource is bound with * {@link Login} the resource is bound with
* @param location
* Location {@link URL} of this resource
*/ */
protected AcmeJsonResource(Session session) { protected AcmeJsonResource(Login login, URL location) {
super(session); super(login, location);
}
/**
* 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);
} }
/** /**
@ -122,7 +112,7 @@ public abstract class AcmeJsonResource extends AcmeResource {
public void update() throws AcmeException { public void update() throws AcmeException {
String resourceType = getClass().getSimpleName(); String resourceType = getClass().getSimpleName();
LOG.debug("update {}", resourceType); LOG.debug("update {}", resourceType);
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
conn.sendRequest(getLocation(), getSession()); conn.sendRequest(getLocation(), getSession());
setJSON(conn.readJsonResponse()); setJSON(conn.readJsonResponse());
conn.handleRetryAfter(resourceType + " is not completed yet"); conn.handleRetryAfter(resourceType + " is not completed yet");

View File

@ -17,65 +17,67 @@ import java.io.Serializable;
import java.net.URL; import java.net.URL;
import java.util.Objects; import java.util.Objects;
import org.shredzone.acme4j.connector.Connection;
/** /**
* A generic ACME resource. * A generic ACME resource.
*/ */
public abstract class AcmeResource implements Serializable { public abstract class AcmeResource implements Serializable {
private static final long serialVersionUID = -7930580802257379731L; private static final long serialVersionUID = -7930580802257379731L;
private transient Session session; private transient Login login;
private URL location; private final URL location;
/** /**
* Create a new {@link AcmeResource}. * Create a new {@link AcmeResource}.
* *
* @param session * @param login
* {@link Session} the resource is bound with * {@link Login} the resource is bound with
*/ */
protected AcmeResource(Session session) { protected AcmeResource(Login login, URL location) {
rebind(session); 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. * Gets the {@link Session} this resource is bound with.
*/ */
protected Session getSession() { protected Session getSession() {
if (session == null) { return getLogin().getSession();
throw new IllegalStateException("Use rebind() for binding this object to a session.");
}
return session;
} }
/** /**
* Sets a new {@link Session}. * Opens a {@link Connection} to the provider.
*/ */
protected void setSession(Session session) { protected Connection connect() {
this.session = Objects.requireNonNull(session, "session"); return getSession().provider().connect();
} }
/** /**
* Sets the resource's location. * Rebinds this resource to a {@link Login}.
*/
protected void setLocation(URL location) {
this.location = Objects.requireNonNull(location, "location");
}
/**
* Rebinds this resource to a {@link Session}.
* <p> * <p>
* 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 * 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 * @param login
* {@link Session} to bind this resource to * {@link Login} to bind this resource to
*/ */
public void rebind(Session session) { public void rebind(Login login) {
if (this.session != null) { if (this.login != null) {
throw new IllegalStateException("Resource is already bound to a session"); throw new IllegalStateException("Resource is already bound to a login");
} }
setSession(session); this.login = login;
} }
/** /**

View File

@ -38,23 +38,8 @@ public class Authorization extends AcmeJsonResource {
private static final long serialVersionUID = -3116928998379417741L; private static final long serialVersionUID = -3116928998379417741L;
private static final Logger LOG = LoggerFactory.getLogger(Authorization.class); private static final Logger LOG = LoggerFactory.getLogger(Authorization.class);
protected Authorization(Session session, URL location) { protected Authorization(Login login, URL location) {
super(session); super(login, location);
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);
} }
/** /**
@ -90,13 +75,13 @@ public class Authorization extends AcmeJsonResource {
* Gets a list of all challenges offered by the server. * Gets a list of all challenges offered by the server.
*/ */
public List<Challenge> getChallenges() { public List<Challenge> getChallenges() {
Session session = getSession(); Login login = getLogin();
return Collections.unmodifiableList(getJSON().get("challenges") return Collections.unmodifiableList(getJSON().get("challenges")
.asArray() .asArray()
.stream() .stream()
.map(Value::asObject) .map(Value::asObject)
.map(session::createChallenge) .map(login::createChallenge)
.collect(toList())); .collect(toList()));
} }
@ -124,11 +109,11 @@ public class Authorization extends AcmeJsonResource {
*/ */
public void deactivate() throws AcmeException { public void deactivate() throws AcmeException {
LOG.debug("deactivate"); LOG.debug("deactivate");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
claims.put("status", "deactivated"); claims.put("status", "deactivated");
conn.sendSignedRequest(getLocation(), claims, getSession()); conn.sendSignedRequest(getLocation(), claims, getLogin());
setJSON(conn.readJsonResponse()); setJSON(conn.readJsonResponse());
} }

View File

@ -17,7 +17,6 @@ import static java.util.Collections.unmodifiableList;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
@ -25,7 +24,6 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.BiFunction;
import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Resource;
@ -47,27 +45,11 @@ public class Certificate extends AcmeResource {
private static final long serialVersionUID = 7381527770159084201L; private static final long serialVersionUID = 7381527770159084201L;
private static final Logger LOG = LoggerFactory.getLogger(Certificate.class); private static final Logger LOG = LoggerFactory.getLogger(Certificate.class);
protected static BiFunction<URI, KeyPair, Session> revokeSessionFactory = Session::new;
private ArrayList<X509Certificate> certChain = null; private ArrayList<X509Certificate> certChain = null;
private ArrayList<URL> alternates = null; private ArrayList<URL> alternates = null;
protected Certificate(Session session, URL certUrl) { protected Certificate(Login login, URL certUrl) {
super(session); super(login, certUrl);
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);
} }
/** /**
@ -79,7 +61,7 @@ public class Certificate extends AcmeResource {
public void download() throws AcmeException { public void download() throws AcmeException {
if (certChain == null) { if (certChain == null) {
LOG.debug("download"); LOG.debug("download");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
conn.sendRequest(getLocation(), getSession()); conn.sendRequest(getLocation(), getSession());
alternates = new ArrayList<>(conn.getLinks("alternate")); alternates = new ArrayList<>(conn.getLinks("alternate"));
certChain = new ArrayList<>(conn.readCertificates()); certChain = new ArrayList<>(conn.readCertificates());
@ -162,14 +144,14 @@ public class Certificate extends AcmeResource {
throw new AcmeException("Server does not allow certificate revocation"); throw new AcmeException("Server does not allow certificate revocation");
} }
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
claims.putBase64("certificate", getCertificate().getEncoded()); claims.putBase64("certificate", getCertificate().getEncoded());
if (reason != null) { if (reason != null) {
claims.put("reason", reason.getReasonCode()); claims.put("reason", reason.getReasonCode());
} }
conn.sendSignedRequest(resUrl, claims, getSession(), true); conn.sendSignedRequest(resUrl, claims, getSession(), getLogin().getKeyPair());
} catch (CertificateEncodingException ex) { } catch (CertificateEncodingException ex) {
throw new AcmeProtocolException("Invalid certificate", 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 * Revoke a certificate. This call is meant to be used for revoking certificates if
* the account's key pair was lost. * the account's key pair was lost.
* *
* @param serverUri * @param session
* {@link URI} of the ACME server * {@link Session} connected to the ACME server
* @param domainKeyPair * @param domainKeyPair
* Key pair the CSR was signed with * Key pair the CSR was signed with
* @param cert * @param cert
@ -190,10 +172,9 @@ public class Certificate extends AcmeResource {
* used when generating OCSP responses and CRLs. {@code null} to give no * used when generating OCSP responses and CRLs. {@code null} to give no
* reason. * 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 { RevocationReason reason) throws AcmeException {
LOG.debug("revoke immediately"); LOG.debug("revoke immediately");
Session session = revokeSessionFactory.apply(serverUri, domainKeyPair);
URL resUrl = session.resourceUrl(Resource.REVOKE_CERT); URL resUrl = session.resourceUrl(Resource.REVOKE_CERT);
if (resUrl == null) { if (resUrl == null) {
@ -207,7 +188,7 @@ public class Certificate extends AcmeResource {
claims.put("reason", reason.getReasonCode()); claims.put("reason", reason.getReasonCode());
} }
conn.sendSignedRequest(resUrl, claims, session, true); conn.sendSignedRequest(resUrl, claims, session, domainKeyPair);
} catch (CertificateEncodingException ex) { } catch (CertificateEncodingException ex) {
throw new AcmeProtocolException("Invalid certificate", ex); throw new AcmeProtocolException("Invalid certificate", ex);
} }

View File

@ -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.
* <p>
* 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");
}
}

View File

@ -34,22 +34,8 @@ public class Order extends AcmeJsonResource {
private static final long serialVersionUID = 5435808648658292177L; private static final long serialVersionUID = 5435808648658292177L;
private static final Logger LOG = LoggerFactory.getLogger(Order.class); private static final Logger LOG = LoggerFactory.getLogger(Order.class);
protected Order(Session session, URL location) { protected Order(Login login, URL location) {
super(session); super(login, location);
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);
} }
/** /**
@ -103,12 +89,12 @@ public class Order extends AcmeJsonResource {
* Gets the {@link Authorization} required for this order. * Gets the {@link Authorization} required for this order.
*/ */
public List<Authorization> getAuthorizations() { public List<Authorization> getAuthorizations() {
Session session = getSession(); Login login = getLogin();
return Collections.unmodifiableList(getJSON().get("authorizations") return Collections.unmodifiableList(getJSON().get("authorizations")
.asArray() .asArray()
.stream() .stream()
.map(Value::asURL) .map(Value::asURL)
.map(loc -> Authorization.bind(session, loc)) .map(login::bindAuthorization)
.collect(toList())); .collect(toList()));
} }
@ -127,7 +113,7 @@ public class Order extends AcmeJsonResource {
public Certificate getCertificate() { public Certificate getCertificate() {
return getJSON().get("certificate").optional() return getJSON().get("certificate").optional()
.map(Value::asURL) .map(Value::asURL)
.map(certUrl -> Certificate.bind(getSession(), certUrl)) .map(getLogin()::bindCertificate)
.orElse(null); .orElse(null);
} }
@ -147,11 +133,11 @@ public class Order extends AcmeJsonResource {
*/ */
public void execute(byte[] csr) throws AcmeException { public void execute(byte[] csr) throws AcmeException {
LOG.debug("finalize"); LOG.debug("finalize");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
claims.putBase64("csr", csr); claims.putBase64("csr", csr);
conn.sendSignedRequest(getFinalizeLocation(), claims, getSession()); conn.sendSignedRequest(getFinalizeLocation(), claims, getLogin());
} }
invalidate(); invalidate();
} }

View File

@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
public class OrderBuilder { public class OrderBuilder {
private static final Logger LOG = LoggerFactory.getLogger(OrderBuilder.class); private static final Logger LOG = LoggerFactory.getLogger(OrderBuilder.class);
private final Session session; private final Login login;
private final Set<String> domainSet = new LinkedHashSet<>(); private final Set<String> domainSet = new LinkedHashSet<>();
private Instant notBefore; private Instant notBefore;
@ -44,10 +44,11 @@ public class OrderBuilder {
/** /**
* Create a new {@link OrderBuilder}. * Create a new {@link OrderBuilder}.
* *
* @param session {@link Session} to bind with * @param login
* {@link Login} to bind with
*/ */
protected OrderBuilder(Session session) { protected OrderBuilder(Login login) {
this.session = session; this.login = login;
} }
/** /**
@ -125,6 +126,8 @@ public class OrderBuilder {
throw new IllegalArgumentException("At least one domain is required"); throw new IllegalArgumentException("At least one domain is required");
} }
Session session = login.getSession();
Object[] identifiers = new Object[domainSet.size()]; Object[] identifiers = new Object[domainSet.size()];
Iterator<String> di = domainSet.iterator(); Iterator<String> di = domainSet.iterator();
for (int ix = 0; ix < identifiers.length; ix++) { for (int ix = 0; ix < identifiers.length; ix++) {
@ -146,9 +149,9 @@ public class OrderBuilder {
claims.put("notAfter", notAfter); 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()); order.setJSON(conn.readJsonResponse());
return order; return order;
} }

View File

@ -26,19 +26,13 @@ import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.provider.AcmeProvider; import org.shredzone.acme4j.provider.AcmeProvider;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
/** /**
* A session stores the ACME server URI and the account's key pair. It also tracks * A session stores the ACME server URI. It also tracks communication parameters.
* communication parameters.
* <p>
* Note that {@link Session} objects are not serializable, as they contain a keypair and
* volatile data.
*/ */
public class Session { public class Session {
private final AtomicReference<Map<Resource, URL>> resourceMap = new AtomicReference<>(); private final AtomicReference<Map<Resource, URL>> resourceMap = new AtomicReference<>();
@ -46,8 +40,6 @@ public class Session {
private final URI serverUri; private final URI serverUri;
private final AcmeProvider provider; private final AcmeProvider provider;
private KeyPair keyPair;
private URL accountLocation;
private byte[] nonce; private byte[] nonce;
private JSON directoryJson; private JSON directoryJson;
private Locale locale = Locale.getDefault(); private Locale locale = Locale.getDefault();
@ -58,11 +50,9 @@ public class Session {
* *
* @param serverUri * @param serverUri
* URI string of the ACME server * URI string of the ACME server
* @param keyPair
* {@link KeyPair} of the ACME account
*/ */
public Session(String serverUri, KeyPair keyPair) { public Session(String serverUri) {
this(URI.create(serverUri), keyPair); this(URI.create(serverUri));
} }
/** /**
@ -70,14 +60,11 @@ public class Session {
* *
* @param serverUri * @param serverUri
* {@link URI} of the ACME server * {@link URI} of the ACME server
* @param keyPair
* {@link KeyPair} of the ACME account
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if no ACME provider was found for the server URI. * 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.serverUri = Objects.requireNonNull(serverUri, "serverUri");
this.keyPair = Objects.requireNonNull(keyPair, "keyPair");
final URI localServerUri = serverUri; final URI localServerUri = serverUri;
@ -93,6 +80,19 @@ public class Session {
.orElseThrow(() -> new IllegalArgumentException("No ACME provider found for " + localServerUri)); .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. * Gets the ACME server {@link URI} of this session.
*/ */
@ -100,34 +100,6 @@ public class Session {
return serverUri; 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. * Gets the last nonce, or {@code null} if the session is new.
*/ */
@ -166,21 +138,6 @@ public class Session {
return provider; 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 * Gets the {@link URL} of the given {@link Resource}. This may involve connecting to
* the server and getting a directory. The result is cached. * the server and getting a directory. The result is cached.

View File

@ -13,13 +13,11 @@
*/ */
package org.shredzone.acme4j.challenge; package org.shredzone.acme4j.challenge;
import java.net.URL;
import java.time.Instant; import java.time.Instant;
import java.util.Objects;
import org.shredzone.acme4j.AcmeJsonResource; import org.shredzone.acme4j.AcmeJsonResource;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Problem;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
@ -51,40 +49,14 @@ public class Challenge extends AcmeJsonResource {
/** /**
* Creates a new generic {@link Challenge} object. * Creates a new generic {@link Challenge} object.
* *
* @param session * @param login
* {@link Session} to bind to. * {@link Login} the resource is bound with
* @param data * @param data
* {@link JSON} challenge data * {@link JSON} challenge data
*/ */
public Challenge(Session session, JSON data) { public Challenge(Login login, JSON data) {
super(session, data); super(login, data.get(KEY_URL).required().asURL());
} setJSON(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 extends Challenge> 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);
}
} }
/** /**
@ -146,7 +118,10 @@ public class Challenge extends AcmeJsonResource {
throw new AcmeProtocolException("incompatible type " + type + " for this challenge"); 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); super.setJSON(json);
} }
@ -161,11 +136,11 @@ public class Challenge extends AcmeJsonResource {
*/ */
public void trigger() throws AcmeException { public void trigger() throws AcmeException {
LOG.debug("trigger"); LOG.debug("trigger");
try (Connection conn = getSession().provider().connect()) { try (Connection conn = connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
prepareResponse(claims); prepareResponse(claims);
conn.sendSignedRequest(getLocation(), claims, getSession()); conn.sendSignedRequest(getLocation(), claims, getLogin());
setJSON(conn.readJsonResponse()); setJSON(conn.readJsonResponse());
} }

View File

@ -15,7 +15,7 @@ package org.shredzone.acme4j.challenge;
import static org.shredzone.acme4j.toolbox.AcmeUtils.*; import static org.shredzone.acme4j.toolbox.AcmeUtils.*;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
/** /**
@ -32,13 +32,13 @@ public class Dns01Challenge extends TokenChallenge {
/** /**
* Creates a new generic {@link Dns01Challenge} object. * Creates a new generic {@link Dns01Challenge} object.
* *
* @param session * @param login
* {@link Session} to bind to. * {@link Login} the resource is bound with
* @param data * @param data
* {@link JSON} challenge data * {@link JSON} challenge data
*/ */
public Dns01Challenge(Session session, JSON data) { public Dns01Challenge(Login login, JSON data) {
super(session, data); super(login, data);
} }
/** /**

View File

@ -13,7 +13,7 @@
*/ */
package org.shredzone.acme4j.challenge; package org.shredzone.acme4j.challenge;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
/** /**
@ -30,13 +30,13 @@ public class Http01Challenge extends TokenChallenge {
/** /**
* Creates a new generic {@link Http01Challenge} object. * Creates a new generic {@link Http01Challenge} object.
* *
* @param session * @param login
* {@link Session} to bind to. * {@link Login} the resource is bound with
* @param data * @param data
* {@link JSON} challenge data * {@link JSON} challenge data
*/ */
public Http01Challenge(Session session, JSON data) { public Http01Challenge(Login login, JSON data) {
super(session, data); super(login, data);
} }
/** /**

View File

@ -19,7 +19,7 @@ import java.security.PublicKey;
import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.lang.JoseException; 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.exception.AcmeProtocolException;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
import org.shredzone.acme4j.toolbox.JSONBuilder; import org.shredzone.acme4j.toolbox.JSONBuilder;
@ -37,13 +37,13 @@ public class TokenChallenge extends Challenge {
/** /**
* Creates a new generic {@link TokenChallenge} object. * Creates a new generic {@link TokenChallenge} object.
* *
* @param session * @param login
* {@link Session} to bind to. * {@link Login} the resource is bound with
* @param data * @param data
* {@link JSON} challenge data * {@link JSON} challenge data
*/ */
public TokenChallenge(Session session, JSON data) { public TokenChallenge(Login login, JSON data) {
super(session, data); super(login, data);
} }
@Override @Override
@ -67,7 +67,7 @@ public class TokenChallenge extends Challenge {
*/ */
protected String getAuthorization() { protected String getAuthorization() {
try { try {
PublicKey pk = getSession().getKeyPair().getPublic(); PublicKey pk = getLogin().getKeyPair().getPublic();
PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(pk); PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(pk);
return getToken() return getToken()
+ '.' + '.'

View File

@ -15,10 +15,12 @@ package org.shredzone.acme4j.connector;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.security.KeyPair;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.exception.AcmeRetryAfterException;
@ -52,23 +54,26 @@ public interface Connection extends AutoCloseable {
void sendRequest(URL url, Session session) throws AcmeException; void sendRequest(URL url, Session session) throws AcmeException;
/** /**
* Sends a signed POST request. Ensures that the session has a KeyIdentifier set, and * Sends a signed POST request. Requires a {@link Login} for the session and
* that the "kid" protected header field is used. * {@link KeyPair}. The {@link Login} account location is sent in a "kid" protected
* header.
* <p>
* If the server does not return a 200 class status code, an {@link AcmeException} is
* raised matching the error.
* *
* @param url * @param url
* {@link URL} to send the request to. * {@link URL} to send the request to.
* @param claims * @param claims
* {@link JSONBuilder} containing claims. Must not be {@code null}. * {@link JSONBuilder} containing claims. Must not be {@code null}.
* @param session * @param login
* {@link Session} instance to be used for signing and tracking * {@link Login} instance to be used for signing and tracking.
* @return HTTP 200 class status that was returned * @return HTTP 200 class status that was returned
*/ */
int sendSignedRequest(URL url, JSONBuilder claims, Session session) int sendSignedRequest(URL url, JSONBuilder claims, Login login) throws AcmeException;
throws AcmeException;
/** /**
* Sends a signed POST request. If the session's KeyIdentifier is set, a "kid" * Sends a signed POST request. Only requires a {@link Session}. The {@link KeyPair}
* protected header field is sent. If not, a "jwk" protected header field is sent. * is sent in a "jwk" protected header field.
* <p> * <p>
* If the server does not return a 200 class status code, an {@link AcmeException} is * If the server does not return a 200 class status code, an {@link AcmeException} is
* raised matching the error. * raised matching the error.
@ -78,14 +83,12 @@ public interface Connection extends AutoCloseable {
* @param claims * @param claims
* {@link JSONBuilder} containing claims. Must not be {@code null}. * {@link JSONBuilder} containing claims. Must not be {@code null}.
* @param session * @param session
* {@link Session} instance to be used for signing and tracking * {@link Session} instance to be used for tracking.
* @param enforceJwk * @param keypair
* {@code true} to enforce a "jwk" header field even if a KeyIdentifier is * {@link KeyPair} to be used for signing.
* set, {@code false} to choose between "kid" and "jwk" header field
* automatically
* @return HTTP 200 class status that was returned * @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; throws AcmeException;
/** /**

View File

@ -41,6 +41,7 @@ import org.jose4j.base64url.Base64Url;
import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jws.JsonWebSignature; import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Problem;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
@ -153,23 +154,29 @@ public class DefaultConnection implements Connection {
} }
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) throws AcmeException { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) throws AcmeException {
return sendSignedRequest(url, claims, session, false); return sendSignedRequest(url, claims, login.getSession(), login.getKeyPair(), login.getAccountLocation());
} }
@Override @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 { throws AcmeException {
Objects.requireNonNull(url, "url"); Objects.requireNonNull(url, "url");
Objects.requireNonNull(claims, "claims"); Objects.requireNonNull(claims, "claims");
Objects.requireNonNull(session, "session"); Objects.requireNonNull(session, "session");
Objects.requireNonNull(keypair, "keypair");
assertConnectionIsClosed(); assertConnectionIsClosed();
AcmeException lastException = null; AcmeException lastException = null;
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
try { try {
return performRequest(url, claims, session, enforceJwk); return performRequest(url, claims, session, keypair, accountLocation);
} catch (AcmeServerException ex) { } catch (AcmeServerException ex) {
if (!BAD_NONCE_ERROR.equals(ex.getType())) { if (!BAD_NONCE_ERROR.equals(ex.getType())) {
throw ex; throw ex;
@ -245,7 +252,7 @@ public class DefaultConnection implements Connection {
String nonceHeader = conn.getHeaderField(REPLAY_NONCE_HEADER); String nonceHeader = conn.getHeaderField(REPLAY_NONCE_HEADER);
if (nonceHeader == null || nonceHeader.trim().isEmpty()) { if (nonceHeader == null || nonceHeader.trim().isEmpty()) {
return null; return null; //NOSONAR: consistent with other getters
} }
if (!BASE64URL_PATTERN.matcher(nonceHeader).matches()) { if (!BASE64URL_PATTERN.matcher(nonceHeader).matches()) {
@ -291,17 +298,16 @@ public class DefaultConnection implements Connection {
* {@link JSONBuilder} containing claims. Must not be {@code null}. * {@link JSONBuilder} containing claims. Must not be {@code null}.
* @param session * @param session
* {@link Session} instance to be used for signing and tracking * {@link Session} instance to be used for signing and tracking
* @param enforceJwk * @param keypair
* {@code true} to enforce a "jwk" header field even if a KeyIdentifier is * {@link KeyPair} to be used for signing
* set, {@code false} to choose between "kid" and "jwk" header field * @param accountLocation
* automatically * 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 * @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 { throws AcmeException {
try { try {
KeyPair keypair = session.getKeyPair();
if (session.getNonce() == null) { if (session.getNonce() == null) {
resetNonce(session); resetNonce(session);
} }
@ -319,10 +325,10 @@ public class DefaultConnection implements Connection {
jws.setPayload(claims.toString()); jws.setPayload(claims.toString());
jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce())); jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce()));
jws.getHeaders().setObjectHeaderValue("url", url); jws.getHeaders().setObjectHeaderValue("url", url);
if (enforceJwk || session.getAccountLocation() == null) { if (accountLocation == null) {
jws.getHeaders().setJwkHeaderValue("jwk", jwk); jws.getHeaders().setJwkHeaderValue("jwk", jwk);
} else { } else {
jws.getHeaders().setObjectHeaderValue("kid", session.getAccountLocation()); jws.getHeaders().setObjectHeaderValue("kid", accountLocation);
} }
jws.setAlgorithmHeaderValue(keyAlgorithm(jwk)); jws.setAlgorithmHeaderValue(keyAlgorithm(jwk));

View File

@ -22,6 +22,7 @@ import java.util.Objects;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.shredzone.acme4j.AcmeResource; import org.shredzone.acme4j.AcmeResource;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeProtocolException;
@ -36,28 +37,28 @@ import org.shredzone.acme4j.toolbox.JSON;
*/ */
public class ResourceIterator<T extends AcmeResource> implements Iterator<T> { public class ResourceIterator<T extends AcmeResource> implements Iterator<T> {
private final Session session; private final Login login;
private final String field; private final String field;
private final Deque<URL> urlList = new ArrayDeque<>(); private final Deque<URL> urlList = new ArrayDeque<>();
private final BiFunction<Session, URL, T> creator; private final BiFunction<Login, URL, T> creator;
private boolean eol = false; private boolean eol = false;
private URL nextUrl; private URL nextUrl;
/** /**
* Creates a new {@link ResourceIterator}. * Creates a new {@link ResourceIterator}.
* *
* @param session * @param login
* {@link Session} to bind this iterator to * {@link Login} to bind this iterator to
* @param field * @param field
* Field name to be used in the JSON response * Field name to be used in the JSON response
* @param start * @param start
* URL of the first JSON array, may be {@code null} for an empty iterator * URL of the first JSON array, may be {@code null} for an empty iterator
* @param creator * @param creator
* Creator for an {@link AcmeResource} that is bound to the given * 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<Session, URL, T> creator) { public ResourceIterator(Login login, String field, URL start, BiFunction<Login, URL, T> creator) {
this.session = Objects.requireNonNull(session, "session"); this.login = Objects.requireNonNull(login, "login");
this.field = Objects.requireNonNull(field, "field"); this.field = Objects.requireNonNull(field, "field");
this.nextUrl = start; this.nextUrl = start;
this.creator = Objects.requireNonNull(creator, "creator"); this.creator = Objects.requireNonNull(creator, "creator");
@ -106,7 +107,7 @@ public class ResourceIterator<T extends AcmeResource> implements Iterator<T> {
throw new NoSuchElementException("no more " + field); throw new NoSuchElementException("no more " + field);
} }
return creator.apply(session, next); return creator.apply(login, next);
} }
/** /**
@ -138,6 +139,7 @@ public class ResourceIterator<T extends AcmeResource> implements Iterator<T> {
* there is a "next" header, it is used for the next batch of URLs. * there is a "next" header, it is used for the next batch of URLs.
*/ */
private void readAndQueue() throws AcmeException { private void readAndQueue() throws AcmeException {
Session session = login.getSession();
try (Connection conn = session.provider().connect()) { try (Connection conn = session.provider().connect()) {
conn.sendRequest(nextUrl, session); conn.sendRequest(nextUrl, session);

View File

@ -20,6 +20,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge;
@ -40,7 +41,7 @@ import org.shredzone.acme4j.toolbox.JSON;
*/ */
public abstract class AbstractAcmeProvider implements AcmeProvider { public abstract class AbstractAcmeProvider implements AcmeProvider {
private static final Map<String, BiFunction<Session, JSON, Challenge>> CHALLENGES = challengeMap(); private static final Map<String, BiFunction<Login, JSON, Challenge>> CHALLENGES = challengeMap();
@Override @Override
public Connection connect() { public Connection connect() {
@ -62,8 +63,8 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
} }
} }
private static Map<String, BiFunction<Session, JSON, Challenge>> challengeMap() { private static Map<String, BiFunction<Login, JSON, Challenge>> challengeMap() {
Map<String, BiFunction<Session, JSON, Challenge>> map = new HashMap<>(); Map<String, BiFunction<Login, JSON, Challenge>> map = new HashMap<>();
map.put(Dns01Challenge.TYPE, Dns01Challenge::new); map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
map.put(Http01Challenge.TYPE, Http01Challenge::new); map.put(Http01Challenge.TYPE, Http01Challenge::new);
@ -81,21 +82,21 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
* are unique to the provider. * are unique to the provider.
*/ */
@Override @Override
public Challenge createChallenge(Session session, JSON data) { public Challenge createChallenge(Login login, JSON data) {
Objects.requireNonNull(session, "session"); Objects.requireNonNull(login, "login");
Objects.requireNonNull(data, "data"); Objects.requireNonNull(data, "data");
String type = data.get("type").required().asString(); String type = data.get("type").required().asString();
BiFunction<Session, JSON, Challenge> constructor = CHALLENGES.get(type); BiFunction<Login, JSON, Challenge> constructor = CHALLENGES.get(type);
if (constructor != null) { if (constructor != null) {
return constructor.apply(session, data); return constructor.apply(login, data);
} }
if (data.contains("token")) { if (data.contains("token")) {
return new TokenChallenge(session, data); return new TokenChallenge(login, data);
} else { } else {
return new Challenge(session, data); return new Challenge(login, data);
} }
} }

View File

@ -17,6 +17,7 @@ import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Connection;
@ -77,13 +78,13 @@ public interface AcmeProvider {
/** /**
* Creates a {@link Challenge} instance for the given challenge data. * Creates a {@link Challenge} instance for the given challenge data.
* *
* @param session * @param login
* {@link Session} to bind the challenge to * {@link Login} to bind the challenge to
* @param data * @param data
* Challenge {@link JSON} data * Challenge {@link JSON} data
* @return {@link Challenge} instance, or {@code null} if this provider is unable to * @return {@link Challenge} instance, or {@code null} if this provider is unable to
* generate a matching {@link Challenge} instance. * generate a matching {@link Challenge} instance.
*/ */
Challenge createChallenge(Session session, JSON data); Challenge createChallenge(Login login, JSON data);
} }

View File

@ -20,6 +20,7 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.security.KeyPair;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
@ -47,12 +48,14 @@ public class AccountBuilderTest {
*/ */
@Test @Test
public void testRegistration() throws Exception { public void testRegistration() throws Exception {
KeyPair accountKey = TestUtils.createKeyPair();
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
private boolean isUpdate; private boolean isUpdate;
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
assertThat(isUpdate, is(false)); assertThat(isUpdate, is(false));
isUpdate = true; isUpdate = true;
@ -60,11 +63,11 @@ public class AccountBuilderTest {
} }
@Override @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(session, is(notNullValue()));
assertThat(url, is(resourceUrl)); assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("newAccount").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("newAccount").toString()));
assertThat(enforceJwk, is(true)); assertThat(keypair, is(accountKey));
isUpdate = false; isUpdate = false;
return HttpURLConnection.HTTP_CREATED; return HttpURLConnection.HTTP_CREATED;
} }
@ -85,22 +88,16 @@ public class AccountBuilderTest {
AccountBuilder builder = new AccountBuilder(); AccountBuilder builder = new AccountBuilder();
builder.addContact("mailto:foo@example.com"); builder.addContact("mailto:foo@example.com");
builder.agreeToTermsOfService(); builder.agreeToTermsOfService();
builder.useKeyPair(accountKey);
Session session = provider.createSession(); 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(account.getTermsOfServiceAgreed(), is(true));
assertThat(session.getAccountLocation(), is(locationUrl)); assertThat(account.getLocation(), is(locationUrl));
try {
AccountBuilder builder2 = new AccountBuilder();
builder2.agreeToTermsOfService();
builder2.create(session);
fail("registered twice on same session");
} catch (IllegalArgumentException ex) {
// expected
}
provider.close(); provider.close();
} }
@ -110,16 +107,17 @@ public class AccountBuilderTest {
*/ */
@Test @Test
public void testRegistrationWithKid() throws Exception { public void testRegistrationWithKid() throws Exception {
KeyPair accountKey = TestUtils.createKeyPair();
String keyIdentifier = "NCC-1701"; String keyIdentifier = "NCC-1701";
SecretKey macKey = TestUtils.createSecretKey("SHA-256"); SecretKey macKey = TestUtils.createSecretKey("SHA-256");
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { public int sendSignedRequest(URL url, JSONBuilder claims, Session session, KeyPair keypair) {
try { try {
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
assertThat(url, is(resourceUrl)); assertThat(url, is(resourceUrl));
assertThat(enforceJwk, is(true)); assertThat(keypair, is(accountKey));
JSON binding = claims.toJSON() JSON binding = claims.toJSON()
.get("externalAccountBinding") .get("externalAccountBinding")
@ -170,12 +168,13 @@ public class AccountBuilderTest {
provider.putTestResource(Resource.NEW_ACCOUNT, resourceUrl); provider.putTestResource(Resource.NEW_ACCOUNT, resourceUrl);
AccountBuilder builder = new AccountBuilder(); 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(); 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(); provider.close();
} }
@ -185,13 +184,15 @@ public class AccountBuilderTest {
*/ */
@Test @Test
public void testOnlyExistingRegistration() throws Exception { public void testOnlyExistingRegistration() throws Exception {
KeyPair accountKey = TestUtils.createKeyPair();
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @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(session, is(notNullValue()));
assertThat(url, is(resourceUrl)); assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("newAccountOnlyExisting").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("newAccountOnlyExisting").toString()));
assertThat(enforceJwk, is(true)); assertThat(keypair, is(accountKey));
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
@ -209,13 +210,13 @@ public class AccountBuilderTest {
provider.putTestResource(Resource.NEW_ACCOUNT, resourceUrl); provider.putTestResource(Resource.NEW_ACCOUNT, resourceUrl);
AccountBuilder builder = new AccountBuilder(); AccountBuilder builder = new AccountBuilder();
builder.useKeyPair(accountKey);
builder.onlyExisting(); builder.onlyExisting();
Session session = provider.createSession(); Session session = provider.createSession();
Account account = builder.create(session); Login login = builder.createLogin(session);
assertThat(account.getLocation(), is(locationUrl)); assertThat(login.getAccountLocation(), is(locationUrl));
assertThat(session.getAccountLocation(), is(locationUrl));
provider.close(); provider.close();
} }

View File

@ -52,7 +52,7 @@ import org.shredzone.acme4j.toolbox.TestUtils;
public class AccountTest { public class AccountTest {
private URL resourceUrl = url("http://example.com/acme/resource"); 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"); private URL agreementUrl = url("http://example.com/agreement.pdf");
/** /**
@ -64,10 +64,10 @@ public class AccountTest {
private JSON jsonResponse; private JSON jsonResponse;
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("updateAccount").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("updateAccount").toString()));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
jsonResponse = getJSON("updateAccountResponse"); jsonResponse = getJSON("updateAccountResponse");
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
@ -97,11 +97,11 @@ public class AccountTest {
} }
}; };
Session session = provider.createSession(); Login login = provider.createLogin();
Account account = new Account(session, locationUrl); Account account = new Account(login);
account.update(); account.update();
assertThat(session.getAccountLocation(), is(locationUrl)); assertThat(login.getAccountLocation(), is(locationUrl));
assertThat(account.getLocation(), is(locationUrl)); assertThat(account.getLocation(), is(locationUrl));
assertThat(account.getTermsOfServiceAgreed(), is(true)); assertThat(account.getTermsOfServiceAgreed(), is(true));
assertThat(account.getContacts(), hasSize(1)); assertThat(account.getContacts(), hasSize(1));
@ -125,7 +125,7 @@ public class AccountTest {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
requestWasSent.set(true); requestWasSent.set(true);
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
return HttpURLConnection.HTTP_OK; 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 // Lazy loading
assertThat(requestWasSent.get(), is(false)); assertThat(requestWasSent.get(), is(false));
@ -173,10 +173,10 @@ public class AccountTest {
public void testPreAuthorizeDomain() throws Exception { public void testPreAuthorizeDomain() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
assertThat(url, is(resourceUrl)); assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
return HttpURLConnection.HTTP_CREATED; 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.putTestResource(Resource.NEW_AUTHZ, resourceUrl);
provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new); provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new);
@ -199,7 +199,7 @@ public class AccountTest {
String domainName = "example.org"; String domainName = "example.org";
Account account = new Account(session, locationUrl); Account account = new Account(login);
Authorization auth = account.preAuthorizeDomain(domainName); Authorization auth = account.preAuthorizeDomain(domainName);
assertThat(auth.getDomain(), is(domainName)); assertThat(auth.getDomain(), is(domainName));
@ -224,21 +224,21 @@ public class AccountTest {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @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(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
Problem problem = TestUtils.createProblem(problemType, problemDetail, resourceUrl); Problem problem = TestUtils.createProblem(problemType, problemDetail, resourceUrl);
throw new AcmeServerException(problem); throw new AcmeServerException(problem);
} }
}; };
Session session = provider.createSession(); Login login = provider.createLogin();
provider.putTestResource(Resource.NEW_AUTHZ, resourceUrl); provider.putTestResource(Resource.NEW_AUTHZ, resourceUrl);
Account account = new Account(session, locationUrl); Account account = new Account(login);
try { try {
account.preAuthorizeDomain("example.org"); account.preAuthorizeDomain("example.org");
@ -260,8 +260,8 @@ public class AccountTest {
// just provide a resource record so the provider returns a directory // just provide a resource record so the provider returns a directory
provider.putTestResource(Resource.NEW_NONCE, resourceUrl); provider.putTestResource(Resource.NEW_NONCE, resourceUrl);
Session session = provider.createSession(); Login login = provider.createLogin();
Account account = Account.bind(session, locationUrl); Account account = login.getAccount();
try { try {
account.preAuthorizeDomain(null); account.preAuthorizeDomain(null);
@ -298,11 +298,11 @@ public class AccountTest {
final TestableConnectionProvider provider = new TestableConnectionProvider() { final TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder payload, Session session) { public int sendSignedRequest(URL url, JSONBuilder payload, Login login) {
try { try {
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
assertThat(session.getKeyPair(), is(sameInstance(oldKeyPair))); assertThat(login.getKeyPair(), is(sameInstance(oldKeyPair)));
JSON json = payload.toJSON(); JSON json = payload.toJSON();
String encodedHeader = json.get("protected").asString(); String encodedHeader = json.get("protected").asString();
@ -319,7 +319,7 @@ public class AccountTest {
StringBuilder expectedPayload = new StringBuilder(); StringBuilder expectedPayload = new StringBuilder();
expectedPayload.append('{'); expectedPayload.append('{');
expectedPayload.append("\"account\":\"").append(resourceUrl).append("\","); expectedPayload.append("\"account\":\"").append(locationUrl).append("\",");
expectedPayload.append("\"newKey\":{"); expectedPayload.append("\"newKey\":{");
expectedPayload.append("\"kty\":\"").append(TestUtils.D_KTY).append("\","); expectedPayload.append("\"kty\":\"").append(TestUtils.D_KTY).append("\",");
expectedPayload.append("\"e\":\"").append(TestUtils.D_E).append("\","); expectedPayload.append("\"e\":\"").append(TestUtils.D_E).append("\",");
@ -341,19 +341,21 @@ public class AccountTest {
provider.putTestResource(Resource.KEY_CHANGE, locationUrl); 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 @Override
public AcmeProvider provider() { public AcmeProvider provider() {
return 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); 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) @Test(expected = IllegalArgumentException.class)
public void testChangeSameKey() throws Exception { public void testChangeSameKey() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider(); TestableConnectionProvider provider = new TestableConnectionProvider();
Session session = provider.createSession(); Login login = provider.createLogin();
Account account = new Account(session, locationUrl); Account account = new Account(login);
account.changeKey(session.getKeyPair()); account.changeKey(login.getKeyPair());
provider.close(); provider.close();
} }
@ -377,11 +379,11 @@ public class AccountTest {
public void testDeactivate() throws Exception { public void testDeactivate() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
JSON json = claims.toJSON(); JSON json = claims.toJSON();
assertThat(json.get("status").asString(), is("deactivated")); assertThat(json.get("status").asString(), is("deactivated"));
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
return HttpURLConnection.HTTP_OK; 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(); account.deactivate();
assertThat(account.getStatus(), is(Status.DEACTIVATED)); assertThat(account.getStatus(), is(Status.DEACTIVATED));
@ -406,10 +408,10 @@ public class AccountTest {
public void testModify() throws Exception { public void testModify() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("modifyAccount").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("modifyAccount").toString()));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
return HttpURLConnection.HTTP_OK; 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")); account.setJSON(getJSON("newAccount"));
EditableAccount editable = account.modify(); EditableAccount editable = account.modify();

View File

@ -15,7 +15,9 @@ package org.shredzone.acme4j;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat; 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.junit.Test;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
@ -28,16 +30,19 @@ import org.shredzone.acme4j.toolbox.TestUtils;
public class AcmeJsonResourceTest { public class AcmeJsonResourceTest {
private static final JSON JSON_DATA = getJSON("newAccountResponse"); 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 @Test
public void testSessionConstructor() throws Exception { public void testLoginConstructor() throws Exception {
Session session = TestUtils.session(); Login login = TestUtils.login();
AcmeJsonResource resource = new DummyJsonResource(session); AcmeJsonResource resource = new DummyJsonResource(login, LOCATION_URL);
assertThat(resource.getSession(), is(session)); assertThat(resource.getLogin(), is(login));
assertThat(resource.getSession(), is(login.getSession()));
assertThat(resource.getLocation(), is(LOCATION_URL));
assertThat(resource.isValid(), is(false)); assertThat(resource.isValid(), is(false));
assertUpdateInvoked(resource, 0); assertUpdateInvoked(resource, 0);
@ -46,30 +51,16 @@ public class AcmeJsonResourceTest {
assertUpdateInvoked(resource, 1); 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 {@link AcmeJsonResource#setJSON(JSON)}.
*/ */
@Test @Test
public void testSetJson() throws Exception { public void testSetJson() throws Exception {
Session session = TestUtils.session(); Login login = TestUtils.login();
JSON jsonData2 = getJSON("requestOrderResponse"); JSON jsonData2 = getJSON("requestOrderResponse");
AcmeJsonResource resource = new DummyJsonResource(session); AcmeJsonResource resource = new DummyJsonResource(login, LOCATION_URL);
assertThat(resource.isValid(), is(false)); assertThat(resource.isValid(), is(false));
assertUpdateInvoked(resource, 0); assertUpdateInvoked(resource, 0);
@ -89,13 +80,9 @@ public class AcmeJsonResourceTest {
*/ */
@Test @Test
public void testInvalidate() throws Exception { public void testInvalidate() throws Exception {
Session session = TestUtils.session(); Login login = TestUtils.login();
AcmeJsonResource resource = new DummyJsonResource(session, JSON_DATA); AcmeJsonResource resource = new DummyJsonResource(login, LOCATION_URL);
assertThat(resource.isValid(), is(true));
assertUpdateInvoked(resource, 0);
resource.invalidate();
assertThat(resource.isValid(), is(false)); assertThat(resource.isValid(), is(false));
assertUpdateInvoked(resource, 0); assertUpdateInvoked(resource, 0);
@ -134,12 +121,13 @@ public class AcmeJsonResourceTest {
private int updateCount = 0; private int updateCount = 0;
public DummyJsonResource(Session session) { public DummyJsonResource(Login login, URL location) {
super(session); super(login, location);
} }
public DummyJsonResource(Session session, JSON json) { public DummyJsonResource(Login login, URL location, JSON json) {
super(session, json); super(login, location);
setJSON(json);
} }
@Override @Override

View File

@ -35,40 +35,19 @@ public class AcmeResourceTest {
*/ */
@Test @Test
public void testConstructor() throws Exception { public void testConstructor() throws Exception {
Session session = TestUtils.session(); Login login = TestUtils.login();
URL location = new URL("http://example.com/acme/resource"); URL location = new URL("http://example.com/acme/resource");
try { try {
new DummyResource(null); new DummyResource(null, null);
fail("Could create resource without session"); fail("Could create resource without login and location");
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// expected // expected
} }
AcmeResource resource = new DummyResource(session); AcmeResource resource = new DummyResource(login, location);
assertThat(resource.getSession(), is(session)); assertThat(resource.getLogin(), is(login));
assertThat(resource.getLocation(), is(nullValue()));
resource.setLocation(location);
assertThat(resource.getLocation(), is(location)); 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 @Test
public void testSerialization() throws Exception { 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 // Create a Challenge for testing
DummyResource challenge = new DummyResource(session); DummyResource challenge = new DummyResource(login, location);
assertThat(challenge.getSession(), is(session)); assertThat(challenge.getLogin(), is(login));
// Serialize it // Serialize it
byte[] serialized = null; byte[] serialized = null;
@ -107,19 +87,19 @@ public class AcmeResourceTest {
} }
assertThat(restored, not(sameInstance(challenge))); 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 { try {
restored.getSession(); restored.getLogin();
fail("was able to retrieve a session"); fail("was able to retrieve a session");
} catch (IllegalStateException ex) { } 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 // Rebind to login
restored.setSession(session); restored.rebind(login);
// Make sure the new session is set // Make sure the new login is set
assertThat(restored.getSession(), is(session)); assertThat(restored.getLogin(), is(login));
} }
/** /**
@ -127,12 +107,14 @@ public class AcmeResourceTest {
*/ */
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void testRebind() throws Exception { public void testRebind() throws Exception {
Session session = TestUtils.session(); Login login = TestUtils.login();
AcmeResource resource = new DummyResource(session); URL location = new URL("http://example.com/acme/resource");
assertThat(resource.getSession(), is(session));
Session session2 = TestUtils.session(); AcmeResource resource = new DummyResource(login, location);
resource.rebind(session2); // fails to rebind to another session 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 class DummyResource extends AcmeResource {
private static final long serialVersionUID = 7188822681353082472L; private static final long serialVersionUID = 7188822681353082472L;
public DummyResource(Session session) { public DummyResource(Login login, URL location) {
super(session); super(login, location);
} }
} }

View File

@ -100,12 +100,12 @@ public class AuthorizationTest {
} }
}; };
Session session = provider.createSession(); Login login = provider.createLogin();
provider.putTestChallenge("http-01", Http01Challenge::new); provider.putTestChallenge("http-01", Http01Challenge::new);
provider.putTestChallenge("dns-01", Dns01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new);
Authorization auth = new Authorization(session, locationUrl); Authorization auth = new Authorization(login, locationUrl);
auth.update(); auth.update();
assertThat(auth.getDomain(), is("example.org")); 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("http-01", Http01Challenge::new);
provider.putTestChallenge("dns-01", Dns01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new);
Authorization auth = new Authorization(session, locationUrl); Authorization auth = new Authorization(login, locationUrl);
// Lazy loading // Lazy loading
assertThat(requestWasSent.get(), is(false)); 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("http-01", Http01Challenge::new);
provider.putTestChallenge("dns-01", Dns01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new);
Authorization auth = new Authorization(session, locationUrl); Authorization auth = new Authorization(login, locationUrl);
try { try {
auth.update(); auth.update();
@ -224,11 +224,11 @@ public class AuthorizationTest {
public void testDeactivate() throws Exception { public void testDeactivate() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
JSON json = claims.toJSON(); JSON json = claims.toJSON();
assertThat(json.get("status").asString(), is("deactivated")); assertThat(json.get("status").asString(), is("deactivated"));
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
return HttpURLConnection.HTTP_OK; 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("http-01", Http01Challenge::new);
provider.putTestChallenge("dns-01", Dns01Challenge::new); provider.putTestChallenge("dns-01", Dns01Challenge::new);
Authorization auth = new Authorization(session, locationUrl); Authorization auth = new Authorization(login, locationUrl);
auth.deactivate(); auth.deactivate();
provider.close(); provider.close();
@ -254,13 +254,13 @@ public class AuthorizationTest {
*/ */
private Authorization createChallengeAuthorization() throws IOException { private Authorization createChallengeAuthorization() throws IOException {
try (TestableConnectionProvider provider = new TestableConnectionProvider()) { try (TestableConnectionProvider provider = new TestableConnectionProvider()) {
Session session = provider.createSession(); Login login = provider.createLogin();
provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new); provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new);
provider.putTestChallenge(Dns01Challenge.TYPE, Dns01Challenge::new); provider.putTestChallenge(Dns01Challenge.TYPE, Dns01Challenge::new);
provider.putTestChallenge(DUPLICATE_TYPE, Challenge::new); provider.putTestChallenge(DUPLICATE_TYPE, Challenge::new);
Authorization authorization = new Authorization(session, locationUrl); Authorization authorization = new Authorization(login, locationUrl);
authorization.setJSON(getJSON("authorizationChallenges")); authorization.setJSON(getJSON("authorizationChallenges"));
return authorization; return authorization;
} }

View File

@ -23,7 +23,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.cert.X509Certificate; 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(); cert.download();
X509Certificate downloadedCert = cert.getCertificate(); X509Certificate downloadedCert = cert.getCertificate();
@ -132,12 +131,11 @@ public class CertificateTest {
} }
@Override @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(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
assertThat(session.getAccountLocation(), is(nullValue())); assertThat(keypair, is(notNullValue()));
assertThat(enforceJwk, is(true));
certRequested = false; certRequested = false;
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
@ -157,7 +155,7 @@ public class CertificateTest {
provider.putTestResource(Resource.REVOKE_CERT, resourceUrl); provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
Certificate cert = new Certificate(provider.createSession(), locationUrl); Certificate cert = new Certificate(provider.createLogin(), locationUrl);
cert.revoke(); cert.revoke();
provider.close(); provider.close();
@ -181,11 +179,11 @@ public class CertificateTest {
} }
@Override @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(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
assertThat(enforceJwk, is(true)); assertThat(keypair, is(notNullValue()));
certRequested = false; certRequested = false;
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
@ -205,7 +203,7 @@ public class CertificateTest {
provider.putTestResource(Resource.REVOKE_CERT, resourceUrl); 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); cert.revoke(RevocationReason.KEY_COMPROMISE);
provider.close(); provider.close();
@ -223,20 +221,17 @@ public class CertificateTest {
* Test that a certificate can be revoked by its domain key pair. * Test that a certificate can be revoked by its domain key pair.
*/ */
@Test @Test
@SuppressWarnings("resource")
public void testRevokeCertificateByKeyPair() throws AcmeException, IOException { public void testRevokeCertificateByKeyPair() throws AcmeException, IOException {
final List<X509Certificate> originalCert = TestUtils.createCertificate(); final List<X509Certificate> originalCert = TestUtils.createCertificate();
final KeyPair certKeyPair = TestUtils.createDomainKeyPair(); final KeyPair certKeyPair = TestUtils.createDomainKeyPair();
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @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 {
assertThat(url, is(resourceUrl)); assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
assertThat(session.getKeyPair(), is(certKeyPair)); assertThat(keypair, is(certKeyPair));
assertThat(enforceJwk, is(true));
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
}; };
@ -244,15 +239,8 @@ public class CertificateTest {
provider.putTestResource(Resource.REVOKE_CERT, resourceUrl); provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
Session session = provider.createSession(); Session session = provider.createSession();
URI serverUri = session.getServerUri();
Certificate.revokeSessionFactory = (uri, keyPair) -> { Certificate.revoke(session, certKeyPair, originalCert.get(0), RevocationReason.KEY_COMPROMISE);
assertThat(uri, is(serverUri));
session.setKeyPair(keyPair);
return session;
};
Certificate.revoke(serverUri, certKeyPair, originalCert.get(0), RevocationReason.KEY_COMPROMISE);
provider.close(); provider.close();
} }

View File

@ -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);
}
}

View File

@ -29,6 +29,7 @@ import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
import org.shredzone.acme4j.toolbox.JSONBuilder; import org.shredzone.acme4j.toolbox.JSONBuilder;
import org.shredzone.acme4j.toolbox.TestUtils;
/** /**
* Unit tests for {@link OrderBuilder}. * Unit tests for {@link OrderBuilder}.
@ -36,7 +37,7 @@ import org.shredzone.acme4j.toolbox.JSONBuilder;
public class OrderBuilderTest { public class OrderBuilderTest {
private URL resourceUrl = url("http://example.com/acme/resource"); 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. * Test that a new {@link Order} can be created.
@ -48,10 +49,10 @@ public class OrderBuilderTest {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
assertThat(url, is(resourceUrl)); assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("requestOrderRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("requestOrderRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
return HttpURLConnection.HTTP_CREATED; 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); provider.putTestResource(Resource.NEW_ORDER, resourceUrl);
Account account = new Account(session, locationUrl); Account account = new Account(login);
Order order = account.newOrder() Order order = account.newOrder()
.domains("example.com", "www.example.com") .domains("example.com", "www.example.com")
.domain("example.org") .domain("example.org")

View File

@ -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(); order.update();
assertThat(order.getStatus(), is(Status.PENDING)); 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 // Lazy loading
assertThat(requestWasSent.get(), is(false)); assertThat(requestWasSent.get(), is(false));
@ -151,10 +151,10 @@ public class OrderTest {
} }
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
assertThat(url, is(finalizeUrl)); assertThat(url, is(finalizeUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("finalizeRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("finalizeRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
isFinalized = true; isFinalized = true;
return HttpURLConnection.HTTP_OK; 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); order.execute(csr);
assertThat(order.getStatus(), is(Status.VALID)); assertThat(order.getStatus(), is(Status.VALID));

View File

@ -26,13 +26,9 @@ import java.time.Instant;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentMatchers; 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.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.provider.AcmeProvider; import org.shredzone.acme4j.provider.AcmeProvider;
import org.shredzone.acme4j.toolbox.JSON;
import org.shredzone.acme4j.toolbox.JSONBuilder;
import org.shredzone.acme4j.toolbox.TestUtils; import org.shredzone.acme4j.toolbox.TestUtils;
/** /**
@ -45,44 +41,25 @@ public class SessionTest {
*/ */
@Test @Test
public void testConstructor() throws IOException { public void testConstructor() throws IOException {
KeyPair keyPair = TestUtils.createKeyPair();
URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI);
try { try {
new Session((URI) null, null); new Session((URI) null);
fail("accepted null parameters in constructor"); fail("accepted null parameters in constructor");
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// expected // expected
} }
try { Session session = new Session(serverUri);
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);
assertThat(session, not(nullValue())); assertThat(session, not(nullValue()));
assertThat(session.getServerUri(), is(serverUri)); 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, not(nullValue()));
assertThat(session2.getServerUri(), is(serverUri)); assertThat(session2.getServerUri(), is(serverUri));
assertThat(session2.getKeyPair(), is(keyPair));
assertThat(session2.getAccountLocation(), is(nullValue()));
try { try {
new Session("#*aBaDuRi*#", keyPair); new Session("#*aBaDuRi*#");
fail("accepted bad URI in constructor"); fail("accepted bad URI in constructor");
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// expected // expected
@ -94,64 +71,34 @@ public class SessionTest {
*/ */
@Test @Test
public void testGettersAndSetters() throws IOException { public void testGettersAndSetters() throws IOException {
KeyPair kp1 = TestUtils.createKeyPair();
KeyPair kp2 = TestUtils.createDomainKeyPair();
URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); 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())); assertThat(session.getNonce(), is(nullValue()));
byte[] data = "foo-nonce-bar".getBytes(); byte[] data = "foo-nonce-bar".getBytes();
session.setNonce(data); session.setNonce(data);
assertThat(session.getNonce(), is(equalTo(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)); assertThat(session.getServerUri(), is(serverUri));
} }
/** /**
* Test if challenges are correctly created via provider. * Test login methods.
*/ */
@Test @Test
public void testCreateChallenge() throws IOException { public void testLogin() throws IOException {
KeyPair keyPair = TestUtils.createKeyPair();
URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI);
String challengeType = Http01Challenge.TYPE; URL accountLocation = url(TestUtils.ACCOUNT_URL);
URL challengeUrl = new URL("https://example.com/acme/authz/0"); KeyPair accountKeyPair = TestUtils.createKeyPair();
JSON data = new JSONBuilder() Session session = new Session(serverUri);
.put("type", challengeType)
.put("url", challengeUrl)
.toJSON();
Http01Challenge mockChallenge = mock(Http01Challenge.class); Login login = session.login(accountLocation, accountKeyPair);
final AcmeProvider mockProvider = mock(AcmeProvider.class); assertThat(login, is(notNullValue()));
assertThat(login.getSession(), is(session));
when(mockProvider.createChallenge( assertThat(login.getAccountLocation(), is(accountLocation));
ArgumentMatchers.any(Session.class), assertThat(login.getKeyPair(), is(accountKeyPair));
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);
} }
/** /**
@ -159,7 +106,6 @@ public class SessionTest {
*/ */
@Test @Test
public void testDirectory() throws AcmeException, IOException { public void testDirectory() throws AcmeException, IOException {
KeyPair keyPair = TestUtils.createKeyPair();
URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI);
final AcmeProvider mockProvider = mock(AcmeProvider.class); final AcmeProvider mockProvider = mock(AcmeProvider.class);
@ -168,7 +114,7 @@ public class SessionTest {
ArgumentMatchers.eq(serverUri))) ArgumentMatchers.eq(serverUri)))
.thenReturn(getJSON("directory")); .thenReturn(getJSON("directory"));
Session session = new Session(serverUri, keyPair) { Session session = new Session(serverUri) {
@Override @Override
public AcmeProvider provider() { public AcmeProvider provider() {
return mockProvider; return mockProvider;
@ -197,7 +143,6 @@ public class SessionTest {
*/ */
@Test @Test
public void testNoMeta() throws AcmeException, IOException { public void testNoMeta() throws AcmeException, IOException {
KeyPair keyPair = TestUtils.createKeyPair();
URI serverUri = URI.create(TestUtils.ACME_SERVER_URI); URI serverUri = URI.create(TestUtils.ACME_SERVER_URI);
final AcmeProvider mockProvider = mock(AcmeProvider.class); final AcmeProvider mockProvider = mock(AcmeProvider.class);
@ -206,7 +151,7 @@ public class SessionTest {
ArgumentMatchers.eq(serverUri))) ArgumentMatchers.eq(serverUri)))
.thenReturn(getJSON("directoryNoMeta")); .thenReturn(getJSON("directoryNoMeta"));
Session session = new Session(serverUri, keyPair) { Session session = new Session(serverUri) {
@Override @Override
public AcmeProvider provider() { public AcmeProvider provider() {
return mockProvider; return mockProvider;

View File

@ -19,7 +19,6 @@ import static org.shredzone.acme4j.toolbox.AcmeUtils.parseTimestamp;
import static org.shredzone.acme4j.toolbox.TestUtils.*; import static org.shredzone.acme4j.toolbox.TestUtils.*;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -28,8 +27,8 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Problem;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
@ -45,51 +44,14 @@ import org.shredzone.acme4j.toolbox.TestUtils;
* Unit tests for {@link Challenge}. * Unit tests for {@link Challenge}.
*/ */
public class ChallengeTest { public class ChallengeTest {
private Session session;
private URL locationUrl = url("https://example.com/acme/some-location"); 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 that after unmarshaling, the challenge properties are set correctly.
*/ */
@Test @Test
public void testUnmarshal() throws URISyntaxException { public void testUnmarshal() throws URISyntaxException {
Challenge challenge = new Challenge(session, getJSON("genericChallenge")); Challenge challenge = new Challenge(TestUtils.login(), getJSON("genericChallenge"));
// Test unmarshalled values // Test unmarshalled values
assertThat(challenge.getType(), is("generic-01")); assertThat(challenge.getType(), is("generic-01"));
@ -113,7 +75,7 @@ public class ChallengeTest {
*/ */
@Test @Test
public void testRespond() throws JoseException { public void testRespond() throws JoseException {
Challenge challenge = new Challenge(session, getJSON("genericChallenge")); Challenge challenge = new Challenge(TestUtils.login(), getJSON("genericChallenge"));
JSONBuilder response = new JSONBuilder(); JSONBuilder response = new JSONBuilder();
challenge.prepareResponse(response); challenge.prepareResponse(response);
@ -126,7 +88,7 @@ public class ChallengeTest {
*/ */
@Test(expected = AcmeProtocolException.class) @Test(expected = AcmeProtocolException.class)
public void testNotAcceptable() throws URISyntaxException { 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 { public void testTrigger() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) { public int sendSignedRequest(URL url, JSONBuilder claims, Login login) {
assertThat(url, is(locationUrl)); assertThat(url, is(locationUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("triggerHttpChallengeRequest").toString())); assertThat(claims.toString(), sameJSONAs(getJSON("triggerHttpChallengeRequest").toString()));
assertThat(session, is(notNullValue())); assertThat(login, is(notNullValue()));
return HttpURLConnection.HTTP_OK; 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(); 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(); 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 { try {
challenge.update(); challenge.update();
@ -237,55 +199,12 @@ public class ChallengeTest {
provider.close(); 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 that unmarshalling something different like a challenge fails.
*/ */
@Test(expected = AcmeProtocolException.class) @Test(expected = AcmeProtocolException.class)
public void testBadUnmarshall() { public void testBadUnmarshall() {
new Challenge(session, getJSON("updateAccountResponse")); new Challenge(TestUtils.login(), getJSON("updateAccountResponse"));
} }
} }

View File

@ -20,9 +20,8 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.IOException; import java.io.IOException;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.toolbox.JSONBuilder; import org.shredzone.acme4j.toolbox.JSONBuilder;
import org.shredzone.acme4j.toolbox.TestUtils; import org.shredzone.acme4j.toolbox.TestUtils;
@ -34,19 +33,14 @@ public class DnsChallengeTest {
private static final String KEY_AUTHORIZATION = private static final String KEY_AUTHORIZATION =
"pNvmJivs0WCko2suV7fhe-59oFqyYx_yB7tx6kIMAyE.HnWjTDnyqlCrm6tZ-6wX-TrEXgRdeNu9G71gqxSO6o0"; "pNvmJivs0WCko2suV7fhe-59oFqyYx_yB7tx6kIMAyE.HnWjTDnyqlCrm6tZ-6wX-TrEXgRdeNu9G71gqxSO6o0";
private static Session session; private Login login = TestUtils.login();
@BeforeClass
public static void setup() throws IOException {
session = TestUtils.session();
}
/** /**
* Test that {@link Dns01Challenge} generates a correct authorization key. * Test that {@link Dns01Challenge} generates a correct authorization key.
*/ */
@Test @Test
public void testDnsChallenge() throws IOException { 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.getType(), is(Dns01Challenge.TYPE));
assertThat(challenge.getStatus(), is(Status.PENDING)); assertThat(challenge.getStatus(), is(Status.PENDING));

View File

@ -20,9 +20,8 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.IOException; import java.io.IOException;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.toolbox.JSONBuilder; import org.shredzone.acme4j.toolbox.JSONBuilder;
@ -37,19 +36,14 @@ public class HttpChallengeTest {
private static final String KEY_AUTHORIZATION = private static final String KEY_AUTHORIZATION =
"rSoI9JpyvFi-ltdnBW0W1DjKstzG7cHixjzcOjwzAEQ.HnWjTDnyqlCrm6tZ-6wX-TrEXgRdeNu9G71gqxSO6o0"; "rSoI9JpyvFi-ltdnBW0W1DjKstzG7cHixjzcOjwzAEQ.HnWjTDnyqlCrm6tZ-6wX-TrEXgRdeNu9G71gqxSO6o0";
private static Session session; private Login login = TestUtils.login();
@BeforeClass
public static void setup() throws IOException {
session = TestUtils.session();
}
/** /**
* Test that {@link Http01Challenge} generates a correct authorization key. * Test that {@link Http01Challenge} generates a correct authorization key.
*/ */
@Test @Test
public void testHttpChallenge() throws IOException { 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.getType(), is(Http01Challenge.TYPE));
assertThat(challenge.getStatus(), is(Status.PENDING)); assertThat(challenge.getStatus(), is(Status.PENDING));
@ -68,7 +62,7 @@ public class HttpChallengeTest {
*/ */
@Test(expected = AcmeProtocolException.class) @Test(expected = AcmeProtocolException.class)
public void testNoTokenSet() { public void testNoTokenSet() {
Http01Challenge challenge = new Http01Challenge(session, getJSON("httpNoTokenChallenge")); Http01Challenge challenge = new Http01Challenge(login, getJSON("httpNoTokenChallenge"));
challenge.getToken(); challenge.getToken();
} }

View File

@ -26,6 +26,7 @@ import java.io.OutputStreamWriter;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.security.KeyPair;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@ -43,6 +44,7 @@ import org.jose4j.jwx.CompactSerializer;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentMatchers; import org.mockito.ArgumentMatchers;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException; import org.shredzone.acme4j.exception.AcmeNetworkException;
@ -64,10 +66,12 @@ import org.shredzone.acme4j.toolbox.TestUtils;
public class DefaultConnectionTest { public class DefaultConnectionTest {
private URL requestUrl = TestUtils.url("http://example.com/acme/"); 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 HttpURLConnection mockUrlConnection;
private HttpConnector mockHttpConnection; private HttpConnector mockHttpConnection;
private Session session; private Session session;
private Login login;
private KeyPair keyPair;
@Before @Before
public void setup() throws AcmeException, IOException { public void setup() throws AcmeException, IOException {
@ -84,11 +88,15 @@ public class DefaultConnectionTest {
session = TestUtils.session(mockProvider); session = TestUtils.session(mockProvider);
session.setLocale(Locale.JAPAN); session.setLocale(Locale.JAPAN);
keyPair = TestUtils.createKeyPair();
login = session.login(accountUrl, keyPair);
} }
/** /**
* Test if {@link DefaultConnection#updateSession(Session)} does nothing if there is * Test that {@link DefaultConnection#getNonce()} returns {@code null} if there is no
* no {@code Replay-Nonce} header. * {@code Replay-Nonce} header.
*/ */
@Test @Test
public void testNoNonceFromHeader() throws AcmeException { public void testNoNonceFromHeader() throws AcmeException {
@ -105,8 +113,8 @@ public class DefaultConnectionTest {
} }
/** /**
* Test that {@link DefaultConnection#updateSession(Session)} extracts a * Test that {@link DefaultConnection#getNonce()} extracts a {@code Replay-Nonce}
* {@code Replay-Nonce} header correctly. * header correctly.
*/ */
@Test @Test
public void testGetNonceFromHeader() { 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. * {@code Replay-Nonce} header.
*/ */
@Test @Test
@ -400,11 +408,10 @@ public class DefaultConnectionTest {
when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(mockUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream()); when(mockUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream());
session.setAccountLocation(accountUrl);
session.setNonce(TestUtils.DUMMY_NONCE); session.setNonce(TestUtils.DUMMY_NONCE);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { 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)); 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.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8")));
when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1"));
session.setAccountLocation(accountUrl);
session.setNonce(TestUtils.DUMMY_NONCE); session.setNonce(TestUtils.DUMMY_NONCE);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); conn.sendSignedRequest(requestUrl, new JSONBuilder(), login);
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeUnauthorizedException ex) { } catch (AcmeUnauthorizedException ex) {
assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:unauthorized"))); 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.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8")));
when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1"));
session.setAccountLocation(accountUrl);
session.setNonce(TestUtils.DUMMY_NONCE); session.setNonce(TestUtils.DUMMY_NONCE);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); conn.sendSignedRequest(requestUrl, new JSONBuilder(), login);
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeUserActionRequiredException ex) { } catch (AcmeUserActionRequiredException ex) {
assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:userActionRequired"))); 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.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8")));
when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1"));
session.setAccountLocation(accountUrl);
session.setNonce(TestUtils.DUMMY_NONCE); session.setNonce(TestUtils.DUMMY_NONCE);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); conn.sendSignedRequest(requestUrl, new JSONBuilder(), login);
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeRateLimitedException ex) { } catch (AcmeRateLimitedException ex) {
assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:rateLimited"))); assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:rateLimited")));
@ -542,7 +546,6 @@ public class DefaultConnectionTest {
when(mockUrlConnection.getOutputStream()) when(mockUrlConnection.getOutputStream())
.thenReturn(new ByteArrayOutputStream()); .thenReturn(new ByteArrayOutputStream());
session.setAccountLocation(accountUrl);
session.setNonce(TestUtils.DUMMY_NONCE); session.setNonce(TestUtils.DUMMY_NONCE);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) {
@ -554,7 +557,7 @@ public class DefaultConnectionTest {
return result.toJSON(); return result.toJSON();
}; };
}) { }) {
conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); conn.sendSignedRequest(requestUrl, new JSONBuilder(), login);
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeServerException ex) { } catch (AcmeServerException ex) {
assertThat(ex.getType(), is(URI.create("urn:zombie:error:apocalypse"))); assertThat(ex.getType(), is(URI.create("urn:zombie:error:apocalypse")));
@ -582,7 +585,6 @@ public class DefaultConnectionTest {
when(mockUrlConnection.getOutputStream()) when(mockUrlConnection.getOutputStream())
.thenReturn(new ByteArrayOutputStream()); .thenReturn(new ByteArrayOutputStream());
session.setAccountLocation(accountUrl);
session.setNonce(TestUtils.DUMMY_NONCE); session.setNonce(TestUtils.DUMMY_NONCE);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) {
@ -591,7 +593,7 @@ public class DefaultConnectionTest {
return JSON.empty(); return JSON.empty();
}; };
}) { }) {
conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); conn.sendSignedRequest(requestUrl, new JSONBuilder(), login);
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeNetworkException ex) { } catch (AcmeNetworkException ex) {
fail("Did not expect an AcmeNetworkException"); fail("Did not expect an AcmeNetworkException");
@ -618,11 +620,10 @@ public class DefaultConnectionTest {
when(mockUrlConnection.getOutputStream()) when(mockUrlConnection.getOutputStream())
.thenReturn(new ByteArrayOutputStream()); .thenReturn(new ByteArrayOutputStream());
session.setAccountLocation(accountUrl);
session.setNonce(TestUtils.DUMMY_NONCE); session.setNonce(TestUtils.DUMMY_NONCE);
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); conn.sendSignedRequest(requestUrl, new JSONBuilder(), login);
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeException ex) { } catch (AcmeException ex) {
assertThat(ex.getMessage(), is("HTTP 500: Infernal Server Error")); assertThat(ex.getMessage(), is("HTTP 500: Infernal Server Error"));
@ -690,8 +691,7 @@ public class DefaultConnectionTest {
}) { }) {
JSONBuilder cb = new JSONBuilder(); JSONBuilder cb = new JSONBuilder();
cb.put("foo", 123).put("bar", "a-string"); cb.put("foo", 123).put("bar", "a-string");
session.setAccountLocation(accountUrl); conn.sendSignedRequest(requestUrl, cb, login);
conn.sendSignedRequest(requestUrl, cb, session);
} }
verify(mockUrlConnection).setRequestMethod("POST"); verify(mockUrlConnection).setRequestMethod("POST");
@ -726,7 +726,7 @@ public class DefaultConnectionTest {
JsonWebSignature jws = new JsonWebSignature(); JsonWebSignature jws = new JsonWebSignature();
jws.setCompactSerialization(CompactSerializer.serialize(encodedHeader, encodedPayload, encodedSignature)); jws.setCompactSerialization(CompactSerializer.serialize(encodedHeader, encodedPayload, encodedSignature));
jws.setKey(session.getKeyPair().getPublic()); jws.setKey(login.getKeyPair().getPublic());
assertThat(jws.verifySignature(), is(true)); assertThat(jws.verifySignature(), is(true));
} }
@ -766,7 +766,7 @@ public class DefaultConnectionTest {
}) { }) {
JSONBuilder cb = new JSONBuilder(); JSONBuilder cb = new JSONBuilder();
cb.put("foo", 123).put("bar", "a-string"); cb.put("foo", 123).put("bar", "a-string");
conn.sendSignedRequest(requestUrl, cb, session, true); conn.sendSignedRequest(requestUrl, cb, session, keyPair);
} }
verify(mockUrlConnection).setRequestMethod("POST"); verify(mockUrlConnection).setRequestMethod("POST");
@ -804,7 +804,7 @@ public class DefaultConnectionTest {
JsonWebSignature jws = new JsonWebSignature(); JsonWebSignature jws = new JsonWebSignature();
jws.setCompactSerialization(CompactSerializer.serialize(encodedHeader, encodedPayload, encodedSignature)); jws.setCompactSerialization(CompactSerializer.serialize(encodedHeader, encodedPayload, encodedSignature));
jws.setKey(session.getKeyPair().getPublic()); jws.setKey(login.getKeyPair().getPublic());
assertThat(jws.verifySignature(), is(true)); assertThat(jws.verifySignature(), is(true));
} }
@ -820,7 +820,7 @@ public class DefaultConnectionTest {
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
JSONBuilder cb = new JSONBuilder(); JSONBuilder cb = new JSONBuilder();
conn.sendSignedRequest(requestUrl, cb, DefaultConnectionTest.this.session, true); conn.sendSignedRequest(requestUrl, cb, DefaultConnectionTest.this.session, DefaultConnectionTest.this.keyPair);
} }
} }

View File

@ -14,10 +14,12 @@
package org.shredzone.acme4j.connector; package org.shredzone.acme4j.connector;
import java.net.URL; import java.net.URL;
import java.security.KeyPair;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
@ -40,13 +42,13 @@ public class DummyConnection implements Connection {
} }
@Override @Override
public int sendSignedRequest(URL url, JSONBuilder claims, Session session) public int sendSignedRequest(URL url, JSONBuilder claims, Login login)
throws AcmeException { throws AcmeException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @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 { throws AcmeException {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -30,6 +30,7 @@ import java.util.NoSuchElementException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Authorization; import org.shredzone.acme4j.Authorization;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
@ -158,11 +159,11 @@ public class ResourceIteratorTest {
} }
}; };
Session session = provider.createSession(); Login login = provider.createLogin();
provider.close(); provider.close();
return new ResourceIterator<>(session, TYPE, first, Authorization::bind); return new ResourceIterator<>(login, TYPE, first, Login::bindAuthorization);
} }
} }

View File

@ -16,20 +16,17 @@ package org.shredzone.acme4j.connector;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.security.KeyPair;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.provider.AcmeProvider; import org.shredzone.acme4j.provider.AcmeProvider;
import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSON;
import org.shredzone.acme4j.toolbox.TestUtils;
/** /**
* Unit tests for {@link Session#provider()}. Requires that both enclosed * Unit tests for {@link Session#provider()}. Requires that both enclosed
@ -38,20 +35,13 @@ import org.shredzone.acme4j.toolbox.TestUtils;
*/ */
public class SessionProviderTest { 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 * There are no testing providers accepting {@code acme://example.org}. Test that
* connecting to this URI will result in an {@link IllegalArgumentException}. * connecting to this URI will result in an {@link IllegalArgumentException}.
*/ */
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testNone() throws Exception { 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 @Test
public void testConnectURI() throws Exception { 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(); AcmeProvider provider = session.provider();
assertThat(provider, is(instanceOf(Provider1.class))); assertThat(provider, is(instanceOf(Provider1.class)));
@ -76,7 +66,7 @@ public class SessionProviderTest {
*/ */
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testDuplicate() throws Exception { 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 { public static class Provider1 implements AcmeProvider {
@ -103,7 +93,7 @@ public class SessionProviderTest {
} }
@Override @Override
public Challenge createChallenge(Session session, JSON data) { public Challenge createChallenge(Login login, JSON data) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }
@ -131,7 +121,7 @@ public class SessionProviderTest {
} }
@Override @Override
public Challenge createChallenge(Session session, JSON data) { public Challenge createChallenge(Login login, JSON data) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
} }

View File

@ -21,7 +21,7 @@ import java.net.URL;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.AcmeResource; import org.shredzone.acme4j.AcmeResource;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.toolbox.TestUtils; import org.shredzone.acme4j.toolbox.TestUtils;
/** /**
@ -33,8 +33,8 @@ public class AcmeLazyLoadingExceptionTest {
@Test @Test
public void testAcmeLazyLoadingException() { public void testAcmeLazyLoadingException() {
Session session = mock(Session.class); Login login = mock(Login.class);
AcmeResource resource = new TestResource(session, resourceUrl); AcmeResource resource = new TestResource(login, resourceUrl);
AcmeException cause = new AcmeException("Something went wrong"); AcmeException cause = new AcmeException("Something went wrong");
@ -50,9 +50,8 @@ public class AcmeLazyLoadingExceptionTest {
private static class TestResource extends AcmeResource { private static class TestResource extends AcmeResource {
private static final long serialVersionUID = 1023419539450677538L; private static final long serialVersionUID = 1023419539450677538L;
public TestResource(Session session, URL location) { public TestResource(Login login, URL location) {
super(session); super(login, location);
setLocation(location);
} }
} }

View File

@ -24,6 +24,7 @@ import java.net.URL;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge;
@ -120,7 +121,7 @@ public class AbstractAcmeProviderTest {
*/ */
@Test @Test
public void testCreateChallenge() { public void testCreateChallenge() {
Session session = mock(Session.class); Login login = mock(Login.class);
AbstractAcmeProvider provider = new AbstractAcmeProvider() { AbstractAcmeProvider provider = new AbstractAcmeProvider() {
@Override @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, not(nullValue()));
assertThat(c1, instanceOf(Http01Challenge.class)); assertThat(c1, instanceOf(Http01Challenge.class));
Challenge c2 = provider.createChallenge(session, getJSON("httpChallenge")); Challenge c2 = provider.createChallenge(login, getJSON("httpChallenge"));
assertThat(c2, not(sameInstance(c1))); 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, not(nullValue()));
assertThat(c3, instanceOf(Dns01Challenge.class)); assertThat(c3, instanceOf(Dns01Challenge.class));
@ -149,7 +150,7 @@ public class AbstractAcmeProviderTest {
.put("type", "foobar-01") .put("type", "foobar-01")
.put("url", "https://example.com/some/challenge") .put("url", "https://example.com/some/challenge")
.toJSON(); .toJSON();
Challenge c6 = provider.createChallenge(session, json6); Challenge c6 = provider.createChallenge(login, json6);
assertThat(c6, not(nullValue())); assertThat(c6, not(nullValue()));
assertThat(c6, instanceOf(Challenge.class)); assertThat(c6, instanceOf(Challenge.class));
@ -158,7 +159,7 @@ public class AbstractAcmeProviderTest {
.put("token", "abc123") .put("token", "abc123")
.put("url", "https://example.com/some/challenge") .put("url", "https://example.com/some/challenge")
.toJSON(); .toJSON();
Challenge c7 = provider.createChallenge(session, json7); Challenge c7 = provider.createChallenge(login, json7);
assertThat(c7, not(nullValue())); assertThat(c7, not(nullValue()));
assertThat(c7, instanceOf(TokenChallenge.class)); assertThat(c7, instanceOf(TokenChallenge.class));
@ -166,14 +167,14 @@ public class AbstractAcmeProviderTest {
JSON json8 = new JSONBuilder() JSON json8 = new JSONBuilder()
.put("url", "https://example.com/some/challenge") .put("url", "https://example.com/some/challenge")
.toJSON(); .toJSON();
provider.createChallenge(session, json8); provider.createChallenge(login, json8);
fail("Challenge without type was accepted"); fail("Challenge without type was accepted");
} catch (AcmeProtocolException ex) { } catch (AcmeProtocolException ex) {
// expected // expected
} }
try { try {
provider.createChallenge(session, null); provider.createChallenge(login, null);
fail("null was accepted"); fail("null was accepted");
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// expected // expected

View File

@ -20,6 +20,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.connector.Connection; 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()}. * of {@link Connection} that is always returned on {@link #connect()}.
*/ */
public class TestableConnectionProvider extends DummyConnection implements AcmeProvider { public class TestableConnectionProvider extends DummyConnection implements AcmeProvider {
private final Map<String, BiFunction<Session, JSON, Challenge>> creatorMap = new HashMap<>(); private final Map<String, BiFunction<Login, JSON, Challenge>> creatorMap = new HashMap<>();
private final Map<String, Challenge> createdMap = new HashMap<>(); private final Map<String, Challenge> createdMap = new HashMap<>();
private final JSONBuilder directory = new JSONBuilder(); private final JSONBuilder directory = new JSONBuilder();
@ -59,7 +60,7 @@ public class TestableConnectionProvider extends DummyConnection implements AcmeP
* @param creator * @param creator
* Creator {@link BiFunction} that creates a matching {@link Challenge} * Creator {@link BiFunction} that creates a matching {@link Challenge}
*/ */
public void putTestChallenge(String type, BiFunction<Session, JSON, Challenge> creator) { public void putTestChallenge(String type, BiFunction<Login, JSON, Challenge> creator) {
creatorMap.put(type, 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}. * Creates a {@link Session} that uses this {@link AcmeProvider}.
*/ */
public Session createSession() throws IOException { public Session createSession() {
return TestUtils.session(this); 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 @Override
public boolean accepts(URI serverUri) { public boolean accepts(URI serverUri) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -108,7 +117,7 @@ public class TestableConnectionProvider extends DummyConnection implements AcmeP
} }
@Override @Override
public Challenge createChallenge(Session session, JSON data) { public Challenge createChallenge(Login login, JSON data) {
if (creatorMap.isEmpty()) { if (creatorMap.isEmpty()) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -117,9 +126,9 @@ public class TestableConnectionProvider extends DummyConnection implements AcmeP
String type = data.get("type").asString(); String type = data.get("type").asString();
if (creatorMap.containsKey(type)) { if (creatorMap.containsKey(type)) {
created = creatorMap.get(type).apply(session, data); created = creatorMap.get(type).apply(login, data);
} else { } else {
created = new Challenge(session, data); created = new Challenge(login, data);
} }
createdMap.put(type, created); createdMap.put(type, created);

View File

@ -51,6 +51,7 @@ import org.jose4j.json.JsonUtil;
import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKey.OutputControlLevel; import org.jose4j.jwk.JsonWebKey.OutputControlLevel;
import org.jose4j.keys.HmacKey; import org.jose4j.keys.HmacKey;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Problem;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.provider.AcmeProvider; 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 D_THUMBPRINT = "0VPbh7-I6swlkBu0TrNKSQp6d69bukzeQA0ksuX3FFs";
public static final String ACME_SERVER_URI = "https://example.com/acme"; 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(); public static final byte[] DUMMY_NONCE = "foo-nonce-foo".getBytes();
private TestUtils() { private TestUtils() {
// utility class without constructor // 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. * Creates a {@link Session} instance. It uses {@link #ACME_SERVER_URI} as server URI.
*/ */
public static Session session() throws IOException { public static Session session() {
KeyPair keyPair = createKeyPair(); return new Session(URI.create(ACME_SERVER_URI));
return new Session(URI.create(ACME_SERVER_URI), keyPair); }
/**
* 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 * @param provider
* {@link AcmeProvider} to be used in this session * {@link AcmeProvider} to be used in this session
*/ */
public static Session session(final AcmeProvider provider) throws IOException { public static Session session(final AcmeProvider provider) {
KeyPair keyPair = createKeyPair(); return new Session(URI.create(ACME_SERVER_URI)) {
return new Session(URI.create(ACME_SERVER_URI), keyPair) {
@Override @Override
public AcmeProvider provider() { public AcmeProvider provider() {
return provider; return provider;

View File

@ -77,11 +77,11 @@ public class ClientTest {
// Create a session for Let's Encrypt. // Create a session for Let's Encrypt.
// Use "acme://letsencrypt.org" for production server // 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. // Get the Account.
// If there is no account yet, create a new one. // 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! // Load or create a key pair for the domains. This should not be the userKeyPair!
KeyPair domainKeyPair = loadOrCreateDomainKeyPair(); KeyPair domainKeyPair = loadOrCreateDomainKeyPair();
@ -179,19 +179,22 @@ public class ClientTest {
* *
* @param session * @param session
* {@link Session} to bind with * {@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. // Ask the user to accept the TOS, if server provides us with a link.
URI tos = session.getMetadata().getTermsOfService(); URI tos = session.getMetadata().getTermsOfService();
if (tos != null) { if (tos != null) {
acceptAgreement(tos); acceptAgreement(tos);
} }
Account acct = new AccountBuilder().agreeToTermsOfService().create(session); Account account = new AccountBuilder()
LOG.info("Registered a new user, URL: " + acct.getLocation()); .agreeToTermsOfService()
.useKeyPair(accountKey)
.create(session);
LOG.info("Registered a new user, URL: " + account.getLocation());
return acct; return account;
} }
/** /**

View File

@ -53,11 +53,12 @@ public class OrderHttpIT {
*/ */
@Test @Test
public void testHttpValidation() throws Exception { public void testHttpValidation() throws Exception {
Session session = new Session(boulderURI());
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
Session session = new Session(boulderURI(), keyPair);
Account account = new AccountBuilder() Account account = new AccountBuilder()
.agreeToTermsOfService() .agreeToTermsOfService()
.useKeyPair(keyPair)
.create(session); .create(session);
KeyPair domainKeyPair = createKeyPair(); KeyPair domainKeyPair = createKeyPair();

View File

@ -24,6 +24,7 @@ import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Account; import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.AccountBuilder; import org.shredzone.acme4j.AccountBuilder;
import org.shredzone.acme4j.Login;
import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
@ -41,25 +42,29 @@ public class AccountIT extends PebbleITBase {
@Test @Test
public void testCreate() throws AcmeException { public void testCreate() throws AcmeException {
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
Session session = new Session(pebbleURI(), keyPair); Session session = new Session(pebbleURI());
// Register a new user // Register a new user
AccountBuilder ab = new AccountBuilder(); Login login = new AccountBuilder()
ab.addContact("mailto:acme@example.com"); .addContact("mailto:acme@example.com")
ab.agreeToTermsOfService(); .agreeToTermsOfService()
.useKeyPair(keyPair)
.createLogin(session);
Account acct = ab.create(session); URL location = login.getAccountLocation();
URL location = acct.getLocation();
assertIsPebbleUrl(location); assertIsPebbleUrl(location);
assertThat(session.getAccountLocation(), is(location));
// Check registered data // Check registered data
Account acct = login.getAccount();
assertThat(acct.getLocation(), is(location));
assertThat(acct.getContacts(), contains(URI.create("mailto:acme@example.com"))); assertThat(acct.getContacts(), contains(URI.create("mailto:acme@example.com")));
assertThat(acct.getStatus(), is(Status.VALID)); assertThat(acct.getStatus(), is(Status.VALID));
// Bind another Account object // Bind another Account object
Session session2 = new Session(pebbleURI(), keyPair); Session session2 = new Session(pebbleURI());
Account acct2 = Account.bind(session2, location); Login login2 = new Login(location, keyPair, session2);
assertThat(login2.getAccountLocation(), is(location));
Account acct2 = login2.getAccount();
assertThat(acct2.getLocation(), is(location)); assertThat(acct2.getLocation(), is(location));
assertThat(acct2.getContacts(), contains(URI.create("mailto:acme@example.com"))); assertThat(acct2.getContacts(), contains(URI.create("mailto:acme@example.com")));
assertThat(acct2.getStatus(), is(Status.VALID)); assertThat(acct2.getStatus(), is(Status.VALID));
@ -73,21 +78,25 @@ public class AccountIT extends PebbleITBase {
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
// Register a new user // Register a new user
Session session1 = new Session(pebbleURI(), keyPair); Session session1 = new Session(pebbleURI());
Account acct1 = new AccountBuilder() Login login1 = new AccountBuilder()
.addContact("mailto:acme@example.com") .addContact("mailto:acme@example.com")
.agreeToTermsOfService() .agreeToTermsOfService()
.create(session1); .useKeyPair(keyPair)
URL location1 = acct1.getLocation(); .createLogin(session1);
URL location1 = login1.getAccountLocation();
assertIsPebbleUrl(location1); assertIsPebbleUrl(location1);
// Try to register the same account again // Try to register the same account again
Session session2 = new Session(pebbleURI(), keyPair); Session session2 = new Session(pebbleURI());
Account acct2 = new AccountBuilder() Login login2 = new AccountBuilder()
.addContact("mailto:acme@example.com") .addContact("mailto:acme@example.com")
.agreeToTermsOfService() .agreeToTermsOfService()
.create(session2); .useKeyPair(keyPair)
URL location2 = acct2.getLocation(); .createLogin(session2);
URL location2 = login2.getAccountLocation();
assertIsPebbleUrl(location2); assertIsPebbleUrl(location2);
assertThat(location1, is(location2)); assertThat(location1, is(location2));
@ -100,21 +109,23 @@ public class AccountIT extends PebbleITBase {
public void testCreateOnlyExisting() throws AcmeException { public void testCreateOnlyExisting() throws AcmeException {
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
Session session1 = new Session(pebbleURI(), keyPair); Session session1 = new Session(pebbleURI());
Account acct1 = new AccountBuilder() Login login1 = new AccountBuilder()
.agreeToTermsOfService() .agreeToTermsOfService()
.create(session1); .useKeyPair(keyPair)
URL location1 = acct1.getLocation(); .createLogin(session1);
assertIsPebbleUrl(location1);
assertThat(session1.getAccountLocation(), is(location1));
Session session2 = new Session(pebbleURI(), keyPair); URL location1 = login1.getAccountLocation();
Account acct2 = new AccountBuilder() assertIsPebbleUrl(location1);
Session session2 = new Session(pebbleURI());
Login login2 = new AccountBuilder()
.onlyExisting() .onlyExisting()
.create(session2); .useKeyPair(keyPair)
URL location2 = acct2.getLocation(); .createLogin(session2);
URL location2 = login2.getAccountLocation();
assertIsPebbleUrl(location2); assertIsPebbleUrl(location2);
assertThat(session2.getAccountLocation(), is(location2));
assertThat(location1, is(location2)); assertThat(location1, is(location2));
} }
@ -127,8 +138,8 @@ public class AccountIT extends PebbleITBase {
public void testNotExisting() throws AcmeException { public void testNotExisting() throws AcmeException {
try { try {
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
Session session = new Session(pebbleURI(), keyPair); Session session = new Session(pebbleURI());
new AccountBuilder().onlyExisting().create(session); new AccountBuilder().onlyExisting().useKeyPair(keyPair).create(session);
fail("onlyExisting flag was ignored"); fail("onlyExisting flag was ignored");
} catch (AcmeServerException ex) { } catch (AcmeServerException ex) {
assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:accountDoesNotExist"))); assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:accountDoesNotExist")));
@ -141,11 +152,12 @@ public class AccountIT extends PebbleITBase {
@Test @Test
public void testModify() throws AcmeException { public void testModify() throws AcmeException {
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
Session session = new Session(pebbleURI(), keyPair); Session session = new Session(pebbleURI());
Account acct = new AccountBuilder() Account acct = new AccountBuilder()
.addContact("mailto:acme@example.com") .addContact("mailto:acme@example.com")
.agreeToTermsOfService() .agreeToTermsOfService()
.useKeyPair(keyPair)
.create(session); .create(session);
URL location = acct.getLocation(); URL location = acct.getLocation();
assertIsPebbleUrl(location); assertIsPebbleUrl(location);
@ -170,24 +182,27 @@ public class AccountIT extends PebbleITBase {
@Ignore // TODO PEBBLE: missing @Ignore // TODO PEBBLE: missing
public void testKeyChange() throws AcmeException { public void testKeyChange() throws AcmeException {
KeyPair keyPair = createKeyPair(); 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(); URL location = acct.getLocation();
KeyPair newKeyPair = createKeyPair(); KeyPair newKeyPair = createKeyPair();
acct.changeKey(newKeyPair); acct.changeKey(newKeyPair);
try { try {
Session sessionOldKey = new Session(pebbleURI(), keyPair); Session sessionOldKey = new Session(pebbleURI());
Account oldAccount = Account.bind(sessionOldKey, location); Account oldAccount = sessionOldKey.login(location, keyPair).getAccount();
oldAccount.update(); oldAccount.update();
} catch (AcmeUnauthorizedException ex) { } catch (AcmeUnauthorizedException ex) {
// Expected // Expected
} }
Session sessionNewKey = new Session(pebbleURI(), newKeyPair); Session sessionNewKey = new Session(pebbleURI());
Account newAccount = Account.bind(sessionNewKey, location); Account newAccount = sessionNewKey.login(location, newKeyPair).getAccount();
assertThat(newAccount.getStatus(), is(Status.VALID)); assertThat(newAccount.getStatus(), is(Status.VALID));
} }
@ -198,15 +213,18 @@ public class AccountIT extends PebbleITBase {
@Ignore // TODO PEBBLE: missing @Ignore // TODO PEBBLE: missing
public void testDeactivate() throws AcmeException { public void testDeactivate() throws AcmeException {
KeyPair keyPair = createKeyPair(); 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(); URL location = acct.getLocation();
acct.deactivate(); acct.deactivate();
Session session2 = new Session(pebbleURI(), keyPair); Session session2 = new Session(pebbleURI());
Account acct2 = Account.bind(session2, location); Account acct2 = session2.login(location, keyPair).getAccount();
assertThat(acct2.getLocation(), is(location)); assertThat(acct2.getLocation(), is(location));
assertThat(acct2.getStatus(), is(Status.DEACTIVATED)); assertThat(acct2.getStatus(), is(Status.DEACTIVATED));
} }

View File

@ -98,10 +98,11 @@ public class OrderIT extends PebbleITBase {
*/ */
private void orderCertificate(String domain, Validator validator) throws Exception { private void orderCertificate(String domain, Validator validator) throws Exception {
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
Session session = new Session(pebbleURI(), keyPair); Session session = new Session(pebbleURI());
Account account = new AccountBuilder() Account account = new AccountBuilder()
.agreeToTermsOfService() .agreeToTermsOfService()
.useKeyPair(keyPair)
.create(session); .create(session);
KeyPair domainKeyPair = createKeyPair(); KeyPair domainKeyPair = createKeyPair();

View File

@ -55,10 +55,11 @@ public class OrderWildcardIT extends PebbleITBase {
public void testDnsValidation() throws Exception { public void testDnsValidation() throws Exception {
BammBammClient client = getBammBammClient(); BammBammClient client = getBammBammClient();
KeyPair keyPair = createKeyPair(); KeyPair keyPair = createKeyPair();
Session session = new Session(pebbleURI(), keyPair); Session session = new Session(pebbleURI());
Account account = new AccountBuilder() Account account = new AccountBuilder()
.agreeToTermsOfService() .agreeToTermsOfService()
.useKeyPair(keyPair)
.create(session); .create(session);
KeyPair domainKeyPair = createKeyPair(); KeyPair domainKeyPair = createKeyPair();

View File

@ -18,7 +18,6 @@ import static org.junit.Assert.assertThat;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.net.URI; import java.net.URI;
import java.security.KeyPair;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Metadata; import org.shredzone.acme4j.Metadata;
@ -31,11 +30,9 @@ import org.shredzone.acme4j.exception.AcmeException;
*/ */
public class SessionIT extends PebbleITBase { public class SessionIT extends PebbleITBase {
private final KeyPair keyPair = createKeyPair();
@Test @Test
public void testNonce() throws AcmeException { public void testNonce() throws AcmeException {
Session session = new Session(pebbleURI(), keyPair); Session session = new Session(pebbleURI());
// No nonce yet on a fresh session // No nonce yet on a fresh session
assertThat(session.getNonce(), is(nullValue())); assertThat(session.getNonce(), is(nullValue()));
@ -50,7 +47,7 @@ public class SessionIT extends PebbleITBase {
@Test @Test
public void testResources() throws AcmeException { 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_ACCOUNT));
assertIsPebbleUrl(session.resourceUrl(Resource.NEW_NONCE)); assertIsPebbleUrl(session.resourceUrl(Resource.NEW_NONCE));
@ -59,7 +56,7 @@ public class SessionIT extends PebbleITBase {
@Test @Test
public void testMetadata() throws AcmeException { public void testMetadata() throws AcmeException {
Session session = new Session(pebbleURI(), keyPair); Session session = new Session(pebbleURI());
Metadata meta = session.getMetadata(); Metadata meta = session.getMetadata();
assertThat(meta, not(nullValue())); assertThat(meta, not(nullValue()));