Merge Account and Registration, simplify API

pull/17/merge
Richard Körber 2016-01-28 23:55:09 +01:00
parent d48f7ee0f8
commit 1b83115892
33 changed files with 274 additions and 353 deletions

View File

@ -1,52 +0,0 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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 java.io.Serializable;
import java.security.KeyPair;
/**
* Represents an account at the ACME server.
* <p>
* An account is identified by its {@link KeyPair}.
*
* @author Richard "Shred" Körber
*/
public class Account implements Serializable {
private static final long serialVersionUID = 1064105289226118702L;
private final KeyPair keyPair;
/**
* Creates a new {@link Account} instance.
*
* @param keyPair
* {@link KeyPair} that identifies the account.
*/
public Account(KeyPair keyPair) {
if (keyPair == null) {
throw new NullPointerException("keypair must not be null");
}
this.keyPair = keyPair;
}
/**
* The {@link KeyPair} that belongs to this account.
*/
public KeyPair getKeyPair() {
return keyPair;
}
}

View File

@ -32,62 +32,55 @@ public interface AcmeClient {
/**
* Registers a new account.
*
* @param account
* {@link Account} to register
* @param registration
* {@link Registration} containing registration data
*/
void newRegistration(Account account, Registration registration) throws AcmeException;
void newRegistration(Registration registration) throws AcmeException;
/**
* Modifies an existing account.
*
* @param account
* {@link Account} that is registered
* @param registration
* {@link Registration} containing updated registration data and the
* account location URI
*/
void modifyRegistration(Account account, Registration registration) throws AcmeException;
void modifyRegistration(Registration registration) throws AcmeException;
/**
* Modifies the account so it is identified with the new {@link KeyPair}.
* <p>
* Starting from the next call, {@link Account} must use the new {@link KeyPair} for
* identification.
* Starting from the next call, {@link Registration} must use the new {@link KeyPair}
* for identification.
*
* @param account
* {@link Account} to change the identification key pair of
* @param registration
* {@link Registration} containing the account location URI. Other
* properties are ignored.
* @param newKeyPair
* new {@link KeyPair} to be used for identifying this account
*/
void changeRegistrationKey(Account account, Registration registration, KeyPair newKeyPair)
void changeRegistrationKey(Registration registration, KeyPair newKeyPair)
throws AcmeException;
/**
* Recovers an account by contact-based recovery. The server starts an out-of-band
* recovery process by using one of the contact addresses given at account creation.
*
* @param account
* <em>New</em> {@link Account} to associate the recovered account with
* @param registration
* {@link Registration}, with the account location URI set
* {@link Registration}, with the new key pair and the account location URI
* set
* @throws AcmeException
*/
void recoverRegistration(Account account, Registration registration) throws AcmeException;
void recoverRegistration(Registration registration) throws AcmeException;
/**
* Creates a new {@link Authorization} for a domain.
*
* @param account
* {@link Account} the authorization is related to
* @param registration
* {@link Registration} the authorization is related to
* @param auth
* {@link Authorization} containing the domain name
*/
void newAuthorization(Account account, Authorization auth) throws AcmeException;
void newAuthorization(Registration registration, Authorization auth) throws AcmeException;
/**
* Updates an {@link Authorization} to the current server state.
@ -101,12 +94,12 @@ public interface AcmeClient {
* Triggers a {@link Challenge}. The ACME server is requested to validate the
* response. Note that the validation is performed asynchronously.
*
* @param account
* {@link Account} to be used for conversation
* @param registration
* {@link Registration} to be used for conversation
* @param challenge
* {@link Challenge} to trigger
*/
void triggerChallenge(Account account, Challenge challenge) throws AcmeException;
void triggerChallenge(Registration registration, Challenge challenge) throws AcmeException;
/**
* Updates the {@link Challenge} instance. It contains the current state.
@ -130,13 +123,13 @@ public interface AcmeClient {
/**
* Requests a certificate.
*
* @param account
* {@link Account} to be used for conversation
* @param registration
* {@link Registration} to be used for conversation
* @param csr
* PKCS#10 Certificate Signing Request to be sent to the server
* @return {@link URI} the certificate can be downloaded from
*/
URI requestCertificate(Account account, byte[] csr) throws AcmeException;
URI requestCertificate(Registration registration, byte[] csr) throws AcmeException;
/**
* Downloads a certificate.
@ -150,11 +143,12 @@ public interface AcmeClient {
/**
* Revokes a certificate.
*
* @param account
* {@link Account} to be used for conversation
* @param registration
* {@link Registration} to be used for conversation
* @param certificate
* Certificate to revoke
*/
void revokeCertificate(Account account, X509Certificate certificate) throws AcmeException;
void revokeCertificate(Registration registration, X509Certificate certificate)
throws AcmeException;
}

View File

@ -16,6 +16,7 @@ package org.shredzone.acme4j;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.List;
@ -27,24 +28,47 @@ import java.util.List;
public class Registration implements Serializable {
private static final long serialVersionUID = -8177333806740391140L;
private final KeyPair keyPair;
private List<URI> contacts = new ArrayList<>();
private URI agreement;
private URI location;
/**
* Create an empty {@link Registration}.
* Creates a {@link Registration} with no location URI set. This is only useful for
* new registrations.
*
* @param keyPair
* Account key pair
*/
public Registration() {
// default constructor
public Registration(KeyPair keyPair) {
if (keyPair == null) {
throw new NullPointerException("keypair must not be null");
}
this.keyPair = keyPair;
}
/**
* Create a {@link Registration} for the given location URI.
* Creates a {@link Registration} with a location URI set. This is useful for
* modifications to the registration.
*
* @param keyPair
* Account key pair
* @param location
* Registration location URI
*/
public Registration(URI location) {
public Registration(KeyPair keyPair, URI location) {
this(keyPair);
this.location = location;
}
/**
* The {@link KeyPair} that belongs to this account.
*/
public KeyPair getKeyPair() {
return keyPair;
}
/**
* Returns the URI of the agreement document the user is required to accept.
*/

View File

@ -14,7 +14,7 @@
package org.shredzone.acme4j.challenge;
import org.jose4j.base64url.Base64Url;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.util.ClaimBuilder;
/**
@ -32,17 +32,17 @@ public class GenericTokenChallenge extends GenericChallenge {
private String authorization;
/**
* Authorizes the {@link Challenge} by signing it with an {@link Account}.
* Authorizes the {@link Challenge} by signing it with a {@link Registration}.
*
* @param account
* {@link Account} to sign the challenge with
* @param registration
* {@link Registration} to sign the challenge with
*/
public void authorize(Account account) {
if (account == null) {
throw new NullPointerException("account must not be null");
public void authorize(Registration registration) {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
authorization = computeAuthorization(account);
authorization = computeAuthorization(registration);
}
@Override
@ -58,7 +58,7 @@ public class GenericTokenChallenge extends GenericChallenge {
* Asserts that the challenge was authorized.
*
* @throws IllegalStateException
* if {@link #authorize(Account)} was not invoked.
* if {@link #authorize(Registration)} was not invoked.
*/
protected void assertIsAuthorized() {
if (authorization == null) {
@ -74,7 +74,7 @@ public class GenericTokenChallenge extends GenericChallenge {
}
/**
* Gets the authorization after {@link #authorize(Account)} was invoked.
* Gets the authorization after {@link #authorize(Registration)} was invoked.
*/
protected String getAuthorization() {
assertIsAuthorized();
@ -87,14 +87,14 @@ public class GenericTokenChallenge extends GenericChallenge {
* The default is {@code token + '.' + base64url(jwkThumbprint)}. Subclasses may
* override this method if a different algorithm is used.
*
* @param account
* {@link Account} to authorize with
* @param registration
* {@link Registration} to authorize with
* @return Authorization string
*/
protected String computeAuthorization(Account account) {
protected String computeAuthorization(Registration registration) {
return getToken()
+ '.'
+ Base64Url.encode(jwkThumbprint(account.getKeyPair().getPublic()));
+ Base64Url.encode(jwkThumbprint(registration.getKeyPair().getPublic()));
}
}

View File

@ -28,7 +28,7 @@ import java.util.Map;
import org.jose4j.base64url.Base64Url;
import org.jose4j.json.JsonUtil;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.ValidationBuilder;
@ -59,20 +59,20 @@ public class ProofOfPossessionChallenge extends GenericChallenge {
}
/**
* Authorizes the challenge by signing it with the {@link Account} of the current
* Authorizes the challenge by signing it with the {@link Registration} of the current
* domain owner.
*
* @param ownerAccount
* {@link Account} of the certificate holder
* @param ownerRegistration
* {@link Registration} of the certificate holder
* @param domainKeypair
* {@link KeyPair} matching one of the requested certificates
* @param domains
* Domains to validate
*/
public void authorize(Account ownerAccount, KeyPair domainKeypair, String... domains) {
public void authorize(Registration ownerRegistration, KeyPair domainKeypair, String... domains) {
importValidation(new ValidationBuilder()
.domains(domains)
.sign(ownerAccount, domainKeypair));
.sign(ownerRegistration, domainKeypair));
}
/**

View File

@ -17,7 +17,7 @@ import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
/**
* Implements the {@code tls-sni-01} challenge.
@ -44,8 +44,8 @@ public class TlsSniChallenge extends GenericTokenChallenge {
}
@Override
public void authorize(Account account) {
super.authorize(account);
public void authorize(Registration registration) {
super.authorize(registration);
String hash = computeHash(getAuthorization());
subject = hash.substring(0, 32) + '.' + hash.substring(32) + ".acme.invalid";
}

View File

@ -17,7 +17,7 @@ import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.Map;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.util.ClaimBuilder;
@ -47,11 +47,12 @@ public interface Connection extends AutoCloseable {
* {@link ClaimBuilder} containing claims. Must not be {@code null}.
* @param session
* {@link Session} instance to be used for tracking
* @param account
* {@link Account} to be used for signing the request
* @param registration
* {@link Registration} to be used for signing the request
* @return HTTP response code
*/
int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException;
int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration)
throws AcmeException;
/**
* Reads a server response as JSON data.

View File

@ -15,13 +15,12 @@ package org.shredzone.acme4j.exception;
import java.net.URI;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.AcmeClient;
import org.shredzone.acme4j.Registration;
/**
* An exception that is thrown when there is a conflict with the request. For example,
* this exception is thrown when {@link AcmeClient#newRegistration(Account, Registration)}
* this exception is thrown when {@link AcmeClient#newRegistration(Registration)}
* is invoked, but the registration already exists.
*
* @author Richard "Shred" Körber

View File

@ -27,7 +27,6 @@ import java.util.Map;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.AcmeClient;
import org.shredzone.acme4j.Authorization;
import org.shredzone.acme4j.Registration;
@ -88,10 +87,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public void newRegistration(Account account, Registration registration) throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
}
public void newRegistration(Registration registration) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
@ -110,7 +106,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.put("contact", registration.getContacts());
}
int rc = conn.sendSignedRequest(resourceUri(Resource.NEW_REG), claims, session, account);
int rc = conn.sendSignedRequest(resourceUri(Resource.NEW_REG), claims, session, registration);
if (rc != HttpURLConnection.HTTP_CREATED && rc != HttpURLConnection.HTTP_CONFLICT) {
conn.throwAcmeException();
}
@ -132,10 +128,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public void modifyRegistration(Account account, Registration registration) throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
}
public void modifyRegistration(Registration registration) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
@ -154,7 +147,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.put("agreement", registration.getAgreement());
}
int rc = conn.sendSignedRequest(registration.getLocation(), claims, session, account);
int rc = conn.sendSignedRequest(registration.getLocation(), claims, session, registration);
if (rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
@ -172,11 +165,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public void changeRegistrationKey(Account account, Registration registration, KeyPair newKeyPair)
throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
}
public void changeRegistrationKey(Registration registration, KeyPair newKeyPair) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
@ -186,7 +175,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
if (newKeyPair == null) {
throw new NullPointerException("newKeyPair must not be null");
}
if (Arrays.equals(account.getKeyPair().getPrivate().getEncoded(),
if (Arrays.equals(registration.getKeyPair().getPrivate().getEncoded(),
newKeyPair.getPrivate().getEncoded())) {
throw new IllegalArgumentException("newKeyPair must actually be a new key pair");
}
@ -195,7 +184,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
try {
ClaimBuilder oldKeyClaim = new ClaimBuilder();
oldKeyClaim.putResource("reg");
oldKeyClaim.putKey("oldKey", account.getKeyPair().getPublic());
oldKeyClaim.putKey("oldKey", registration.getKeyPair().getPublic());
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(oldKeyClaim.toString());
@ -214,7 +203,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.putResource("reg");
claims.put("newKey", newKey);
int rc = conn.sendSignedRequest(registration.getLocation(), claims, session, account);
int rc = conn.sendSignedRequest(registration.getLocation(), claims, session, registration);
if (rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
@ -222,10 +211,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public void recoverRegistration(Account account, Registration registration) throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
}
public void recoverRegistration(Registration registration) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
@ -243,7 +229,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.put("contact", registration.getContacts());
}
int rc = conn.sendSignedRequest(resourceUri(Resource.RECOVER_REG), claims, session, account);
int rc = conn.sendSignedRequest(resourceUri(Resource.RECOVER_REG), claims, session, registration);
if (rc != HttpURLConnection.HTTP_CREATED) {
conn.throwAcmeException();
}
@ -258,9 +244,9 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public void newAuthorization(Account account, Authorization auth) throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
public void newAuthorization(Registration registration, Authorization auth) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
if (auth == null) {
throw new NullPointerException("auth must not be null");
@ -277,7 +263,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
.put("type", "dns")
.put("value", auth.getDomain());
int rc = conn.sendSignedRequest(resourceUri(Resource.NEW_AUTHZ), claims, session, account);
int rc = conn.sendSignedRequest(resourceUri(Resource.NEW_AUTHZ), claims, session, registration);
if (rc != HttpURLConnection.HTTP_CREATED) {
conn.throwAcmeException();
}
@ -313,9 +299,9 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public void triggerChallenge(Account account, Challenge challenge) throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
public void triggerChallenge(Registration registration, Challenge challenge) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
if (challenge == null) {
throw new NullPointerException("challenge must not be null");
@ -330,7 +316,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.putResource("challenge");
challenge.respond(claims);
int rc = conn.sendSignedRequest(challenge.getLocation(), claims, session, account);
int rc = conn.sendSignedRequest(challenge.getLocation(), claims, session, registration);
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
@ -383,9 +369,9 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public URI requestCertificate(Account account, byte[] csr) throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
public URI requestCertificate(Registration registration, byte[] csr) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
if (csr == null) {
throw new NullPointerException("csr must not be null");
@ -397,7 +383,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.putResource(Resource.NEW_CERT);
claims.putBase64("csr", csr);
int rc = conn.sendSignedRequest(resourceUri(Resource.NEW_CERT), claims, session, account);
int rc = conn.sendSignedRequest(resourceUri(Resource.NEW_CERT), claims, session, registration);
if (rc != HttpURLConnection.HTTP_CREATED && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
@ -429,9 +415,9 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
@Override
public void revokeCertificate(Account account, X509Certificate certificate) throws AcmeException {
if (account == null) {
throw new NullPointerException("account must not be null");
public void revokeCertificate(Registration registration, X509Certificate certificate) throws AcmeException {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
if (certificate == null) {
throw new NullPointerException("certificate must not be null");
@ -448,7 +434,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.putResource(Resource.REVOKE_CERT);
claims.putBase64("certificate", certificate.getEncoded());
int rc = conn.sendSignedRequest(resUri, claims, session, account);
int rc = conn.sendSignedRequest(resUri, claims, session, registration);
if (rc != HttpURLConnection.HTTP_OK) {
conn.throwAcmeException();
}

View File

@ -37,7 +37,7 @@ import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.HttpConnector;
import org.shredzone.acme4j.connector.Resource;
@ -99,7 +99,8 @@ public class DefaultConnection implements Connection {
}
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration)
throws AcmeException {
if (uri == null) {
throw new NullPointerException("uri must not be null");
}
@ -109,15 +110,15 @@ public class DefaultConnection implements Connection {
if (session == null) {
throw new NullPointerException("session must not be null");
}
if (account == null) {
throw new NullPointerException("account must not be null");
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
if (conn != null) {
throw new IllegalStateException("Connection was not closed. Race condition?");
}
try {
KeyPair keypair = account.getKeyPair();
KeyPair keypair = registration.getKeyPair();
if (session.getNonce() == null) {
LOG.debug("Getting initial nonce, HEAD {}", uri);

View File

@ -24,7 +24,7 @@ import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.challenge.ProofOfPossessionChallenge;
/**
@ -87,15 +87,15 @@ public class ValidationBuilder {
* Signs with the given {@link KeyPair} and returns a signed JSON Web Signature
* structure that can be used for validation.
*
* @param account
* {@link Account} of the current domain owner
* @param registration
* {@link Registration} of the current domain owner
* @param keypair
* One of the {@link KeyPair} requested by the challenge
* @return JWS validation object
*/
public String sign(Account account, KeyPair keypair) {
if (account == null) {
throw new NullPointerException("account must not be null");
public String sign(Registration registration, KeyPair keypair) {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
if (keypair == null) {
throw new NullPointerException("keypair must not be null");
@ -105,7 +105,7 @@ public class ValidationBuilder {
ClaimBuilder claims = new ClaimBuilder();
claims.put("type", ProofOfPossessionChallenge.TYPE);
claims.array("identifiers", identifiers.toArray());
claims.putKey("accountKey", account.getKeyPair().getPublic());
claims.putKey("accountKey", registration.getKeyPair().getPublic());
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(keypair.getPublic());

View File

@ -1,52 +0,0 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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 java.io.IOException;
import java.security.KeyPair;
import org.junit.Test;
import org.shredzone.acme4j.util.TestUtils;
/**
* Unit tests for {@link Account}.
*
* @author Richard "Shred" Körber
*/
public class AccountTest {
/**
* Test getters and setters.
*/
@Test
public void testGetterAndSetter() throws IOException {
KeyPair keypair = TestUtils.createKeyPair();
Account account = new Account(keypair);
assertThat(account.getKeyPair(), is(sameInstance(keypair)));
}
/**
* Test null values.
*/
@Test(expected = NullPointerException.class)
public void testNull() {
new Account(null);
}
}

View File

@ -16,10 +16,13 @@ package org.shredzone.acme4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyPair;
import org.junit.Test;
import org.shredzone.acme4j.util.TestUtils;
/**
* Unit tests for {@link Registration}.
@ -32,12 +35,14 @@ public class RegistrationTest {
* Test getters and setters.
*/
@Test
public void testGetterAndSetter() throws URISyntaxException {
Registration registration = new Registration();
public void testGetterAndSetter() throws IOException, URISyntaxException {
KeyPair keypair = TestUtils.createKeyPair();
Registration registration = new Registration(keypair);
assertThat(registration.getAgreement(), is(nullValue()));
assertThat(registration.getLocation(), is(nullValue()));
assertThat(registration.getContacts(), is(empty()));
assertThat(registration.getKeyPair(), is(sameInstance(keypair)));
registration.setAgreement(new URI("http://example.com/agreement.pdf"));
registration.setLocation(new URI("http://example.com/acme/12345"));
@ -56,12 +61,16 @@ public class RegistrationTest {
* Test constructors.
*/
@Test
public void testConstructor() throws URISyntaxException {
Registration registration1 = new Registration();
assertThat(registration1.getLocation(), is(nullValue()));
public void testConstructor() throws IOException, URISyntaxException {
KeyPair keypair = TestUtils.createKeyPair();
Registration registration2 = new Registration(new URI("http://example.com/acme/12345"));
Registration registration1 = new Registration(keypair);
assertThat(registration1.getLocation(), is(nullValue()));
assertThat(registration1.getKeyPair(), is(sameInstance(keypair)));
Registration registration2 = new Registration(keypair, new URI("http://example.com/acme/12345"));
assertThat(registration2.getLocation(), is(new URI("http://example.com/acme/12345")));
assertThat(registration2.getKeyPair(), is(sameInstance(keypair)));
}
}

View File

@ -21,7 +21,7 @@ import java.io.IOException;
import java.security.KeyPair;
import org.junit.Test;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils;
@ -42,7 +42,7 @@ public class DnsChallengeTest {
@Test
public void testDnsChallenge() throws IOException {
KeyPair keypair = TestUtils.createKeyPair();
Account account = new Account(keypair);
Registration reg = new Registration(keypair);
DnsChallenge challenge = new DnsChallenge();
challenge.unmarshall(TestUtils.getJsonAsMap("dnsChallenge"));
@ -57,7 +57,7 @@ public class DnsChallengeTest {
// expected
}
challenge.authorize(account);
challenge.authorize(reg);
assertThat(challenge.getDigest(), is("rzMmotrIgsithyBYc0vgiLUEEKYx0WetQRgEF2JIozA"));

View File

@ -21,7 +21,7 @@ import java.io.IOException;
import java.security.KeyPair;
import org.junit.Test;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils;
@ -44,7 +44,7 @@ public class HttpChallengeTest {
@Test
public void testHttpChallenge() throws IOException {
KeyPair keypair = TestUtils.createKeyPair();
Account account = new Account(keypair);
Registration reg = new Registration(keypair);
HttpChallenge challenge = new HttpChallenge();
challenge.unmarshall(TestUtils.getJsonAsMap("httpChallenge"));
@ -59,7 +59,7 @@ public class HttpChallengeTest {
// expected
}
challenge.authorize(account);
challenge.authorize(reg);
assertThat(challenge.getToken(), is(TOKEN));
assertThat(challenge.getAuthorization(), is(KEY_AUTHORIZATION));

View File

@ -22,7 +22,7 @@ import java.security.KeyPair;
import java.security.cert.X509Certificate;
import org.junit.Test;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils;
@ -42,7 +42,7 @@ public class ProofOfPossessionChallengeTest {
public void testProofOfPossessionChallenge() throws IOException {
X509Certificate cert = TestUtils.createCertificate();
KeyPair keypair = TestUtils.createKeyPair();
Account account = new Account(keypair);
Registration reg = new Registration(keypair);
KeyPair domainKeyPair = TestUtils.createDomainKeyPair();
ProofOfPossessionChallenge challenge = new ProofOfPossessionChallenge();
@ -60,14 +60,14 @@ public class ProofOfPossessionChallengeTest {
// expected
}
challenge.authorize(account, domainKeyPair, "example.org");
challenge.authorize(reg, domainKeyPair, "example.org");
ClaimBuilder cb = new ClaimBuilder();
challenge.respond(cb);
assertThat(cb.toString(), sameJSONAs("{\"type\"=\""
+ ProofOfPossessionChallenge.TYPE + "\",\"authorization\"="
+ new ValidationBuilder().domain("example.org").sign(account, domainKeyPair)
+ new ValidationBuilder().domain("example.org").sign(reg, domainKeyPair)
+ "}"));
}
@ -78,12 +78,12 @@ public class ProofOfPossessionChallengeTest {
@Test
public void testImportValidation() throws IOException {
KeyPair keypair = TestUtils.createKeyPair();
Account account = new Account(keypair);
Registration reg = new Registration(keypair);
KeyPair domainKeyPair = TestUtils.createDomainKeyPair();
String validation = new ValidationBuilder()
.domain("example.org")
.sign(account, domainKeyPair);
.sign(reg, domainKeyPair);
ProofOfPossessionChallenge challenge = new ProofOfPossessionChallenge();
challenge.unmarshall(TestUtils.getJsonAsMap("proofOfPossessionChallenge"));

View File

@ -21,7 +21,7 @@ import java.io.IOException;
import java.security.KeyPair;
import org.junit.Test;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils;
@ -42,7 +42,7 @@ public class TlsSniChallengeTest {
@Test
public void testTlsSniChallenge() throws IOException {
KeyPair keypair = TestUtils.createKeyPair();
Account account = new Account(keypair);
Registration reg = new Registration(keypair);
TlsSniChallenge challenge = new TlsSniChallenge();
challenge.unmarshall(TestUtils.getJsonAsMap("tlsSniChallenge"));
@ -57,7 +57,7 @@ public class TlsSniChallengeTest {
// expected
}
challenge.authorize(account);
challenge.authorize(reg);
assertThat(challenge.getSubject(), is("14e2350a04434f93c2e0b6012968d99d.ed459b6a7a019d9695609b8514f9d63d.acme.invalid"));

View File

@ -31,7 +31,6 @@ import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.junit.Before;
import org.junit.Test;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Authorization;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.Status;
@ -54,11 +53,12 @@ import org.shredzone.acme4j.util.TimestampParser;
*/
public class AbstractAcmeClientTest {
private Account testAccount;
private URI resourceUri;
private URI locationUri;
private URI anotherLocationUri;
private URI agreementUri;
private KeyPair accountKeyPair;
private Registration testRegistration;
@Before
public void setup() throws IOException, URISyntaxException {
@ -66,7 +66,8 @@ public class AbstractAcmeClientTest {
locationUri = new URI("https://example.com/acme/some-location");
anotherLocationUri = new URI("https://example.com/acme/another-location");
agreementUri = new URI("http://example.com/agreement.pdf");
testAccount = new Account(TestUtils.createKeyPair());
accountKeyPair = TestUtils.createKeyPair();
testRegistration = new Registration(accountKeyPair);
}
/**
@ -74,16 +75,16 @@ public class AbstractAcmeClientTest {
*/
@Test
public void testNewRegistration() throws AcmeException {
Registration registration = new Registration();
Registration registration = new Registration(accountKeyPair);
registration.addContact("mailto:foo@example.com");
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("newRegistration")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_CREATED;
}
@ -104,7 +105,7 @@ public class AbstractAcmeClientTest {
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.putTestResource(Resource.NEW_REG, resourceUri);
client.newRegistration(testAccount, registration);
client.newRegistration(registration);
assertThat(registration.getLocation(), is(locationUri));
assertThat(registration.getAgreement(), is(agreementUri));
@ -115,18 +116,18 @@ public class AbstractAcmeClientTest {
*/
@Test
public void testModifyRegistration() throws AcmeException {
Registration registration = new Registration();
Registration registration = new Registration(accountKeyPair);
registration.setAgreement(agreementUri);
registration.addContact("mailto:foo2@example.com");
registration.setLocation(locationUri);
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
assertThat(uri, is(locationUri));
assertThat(claims.toString(), sameJSONAs(getJson("modifyRegistration")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -146,7 +147,7 @@ public class AbstractAcmeClientTest {
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.modifyRegistration(testAccount, registration);
client.modifyRegistration(registration);
assertThat(registration.getLocation(), is(locationUri));
assertThat(registration.getAgreement(), is(agreementUri));
@ -157,14 +158,14 @@ public class AbstractAcmeClientTest {
*/
@Test
public void testChangeRegistrationKey() throws AcmeException, IOException {
Registration registration = new Registration();
Registration registration = new Registration(accountKeyPair);
registration.setLocation(locationUri);
final KeyPair newKeyPair = TestUtils.createDomainKeyPair();
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
Map<String, Object> claimMap = claims.toMap();
assertThat(claimMap.get("resource"), is((Object) "reg"));
assertThat(claimMap.get("newKey"), not(nullValue()));
@ -189,7 +190,7 @@ public class AbstractAcmeClientTest {
assertThat(uri, is(locationUri));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -201,7 +202,7 @@ public class AbstractAcmeClientTest {
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.changeRegistrationKey(testAccount, registration, newKeyPair);
client.changeRegistrationKey(registration, newKeyPair);
}
/**
@ -209,13 +210,13 @@ public class AbstractAcmeClientTest {
*/
@Test(expected = IllegalArgumentException.class)
public void testChangeRegistrationSameKey() throws AcmeException, IOException {
Registration registration = new Registration();
Registration registration = new Registration(accountKeyPair);
registration.setLocation(locationUri);
Connection connection = new DummyConnection();
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.changeRegistrationKey(testAccount, registration, testAccount.getKeyPair());
client.changeRegistrationKey(registration, registration.getKeyPair());
}
/**
@ -223,17 +224,17 @@ public class AbstractAcmeClientTest {
*/
@Test
public void testRecoverRegistration() throws AcmeException {
Registration registration = new Registration();
Registration registration = new Registration(accountKeyPair);
registration.addContact("mailto:foo@example.com");
registration.setLocation(locationUri);
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("recoverRegistration")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_CREATED;
}
@ -254,7 +255,7 @@ public class AbstractAcmeClientTest {
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.putTestResource(Resource.RECOVER_REG, resourceUri);
client.recoverRegistration(testAccount, registration);
client.recoverRegistration(registration);
assertThat(registration.getLocation(), is(anotherLocationUri));
assertThat(registration.getAgreement(), is(agreementUri));
@ -270,11 +271,11 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("newAuthorizationRequest")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_CREATED;
}
@ -297,7 +298,7 @@ public class AbstractAcmeClientTest {
client.putTestChallenge("http-01", httpChallenge);
client.putTestChallenge("dns-01", dnsChallenge);
client.newAuthorization(testAccount, auth);
client.newAuthorization(testRegistration, auth);
assertThat(auth.getDomain(), is("example.org"));
assertThat(auth.getStatus(), is(Status.PENDING));
@ -365,11 +366,11 @@ public class AbstractAcmeClientTest {
public void testTriggerChallenge() throws AcmeException {
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("triggerHttpChallengeRequest")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_ACCEPTED;
}
@ -383,9 +384,9 @@ public class AbstractAcmeClientTest {
HttpChallenge challenge = new HttpChallenge();
challenge.unmarshall(getJsonAsMap("triggerHttpChallenge"));
challenge.authorize(testAccount);
challenge.authorize(testRegistration);
client.triggerChallenge(testAccount, challenge);
client.triggerChallenge(testRegistration, challenge);
assertThat(challenge.getStatus(), is(Status.PENDING));
assertThat(challenge.getLocation(), is(locationUri));
@ -451,11 +452,11 @@ public class AbstractAcmeClientTest {
public void testRequestCertificate() throws AcmeException, IOException {
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("requestCertificateRequest")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_CREATED;
}
@ -469,7 +470,7 @@ public class AbstractAcmeClientTest {
client.putTestResource(Resource.NEW_CERT, resourceUri);
byte[] csr = TestUtils.getResourceAsByteArray("/csr.der");
URI certUri = client.requestCertificate(testAccount, csr);
URI certUri = client.requestCertificate(testRegistration, csr);
assertThat(certUri, is(locationUri));
}
@ -509,11 +510,11 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() {
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
assertThat(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateRequest")));
assertThat(session, is(notNullValue()));
assertThat(account, is(sameInstance(testAccount)));
assertThat(registration, is(sameInstance(testRegistration)));
return HttpURLConnection.HTTP_OK;
}
};
@ -521,7 +522,7 @@ public class AbstractAcmeClientTest {
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.putTestResource(Resource.REVOKE_CERT, resourceUri);
client.revokeCertificate(testAccount, cert);
client.revokeCertificate(testRegistration, cert);
}
/**

View File

@ -35,7 +35,7 @@ import org.jose4j.base64url.Base64Url;
import org.jose4j.jwx.CompactSerializer;
import org.junit.Before;
import org.junit.Test;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.HttpConnector;
import org.shredzone.acme4j.connector.Resource;
@ -295,9 +295,9 @@ public class DefaultConnectionTest {
cb.put("foo", 123).put("bar", "a-string");
KeyPair keypair = TestUtils.createKeyPair();
Account account = new Account(keypair);
Registration reg = new Registration(keypair);
conn.sendSignedRequest(requestUri, cb, testSession, account);
conn.sendSignedRequest(requestUri, cb, testSession, reg);
}
verify(mockUrlConnection).setRequestMethod("HEAD");

View File

@ -17,7 +17,7 @@ import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.Map;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.connector.Session;
@ -38,7 +38,7 @@ public class DummyConnection implements Connection {
}
@Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Account account) throws AcmeException {
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException {
throw new UnsupportedOperationException();
}

View File

@ -26,7 +26,7 @@ import org.jose4j.base64url.Base64Url;
import org.jose4j.json.JsonUtil;
import org.jose4j.lang.JoseException;
import org.junit.Test;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.Registration;
/**
* Unit test for {@link ValidationBuilder}.
@ -40,16 +40,16 @@ public class ValidationBuilderTest {
*/
@Test
public void testValidationBuilder() throws IOException, JoseException {
Account account = new Account(TestUtils.createKeyPair());
Registration reg = new Registration(TestUtils.createKeyPair());
KeyPair domainKeyPair = TestUtils.createDomainKeyPair();
assertThat(account.getKeyPair(), not(domainKeyPair));
assertThat(reg.getKeyPair(), not(domainKeyPair));
ValidationBuilder vb = new ValidationBuilder();
vb.domain("abc.de").domain("ef.gh");
vb.domains("ijk.lm", "no.pq", "rst.uv");
vb.domains(Arrays.asList("w.x", "y.z"));
String json = vb.sign(account, domainKeyPair);
String json = vb.sign(reg, domainKeyPair);
Map<String, Object> data = JsonUtil.parseJson(json);

View File

@ -80,17 +80,14 @@ public class ClientTest {
createdNewKeyPair = true;
}
// Create an Account instance for the user
Account account = new Account(userKeyPair);
// Create an AcmeClient for Let's Encrypt
// Use "acme://letsencrypt.org" for production server
AcmeClient client = AcmeClientFactory.connect("acme://letsencrypt.org/staging");
// Register a new user
Registration reg = new Registration();
Registration reg = new Registration(userKeyPair);
try {
client.newRegistration(account, reg);
client.newRegistration(reg);
LOG.info("Registered a new user, URI: " + reg.getLocation());
} catch (AcmeConflictException ex) {
LOG.info("Account does already exist, URI: " + reg.getLocation());
@ -99,40 +96,41 @@ public class ClientTest {
LOG.info("Terms of Service: " + reg.getAgreement());
if (createdNewKeyPair) {
boolean accepted = acceptAgreement(client, account, reg);
boolean accepted = acceptAgreement(client, reg);
if (!accepted) {
return;
}
}
for (String domain : domains) {
// Create a new authorization
Authorization auth = new Authorization();
auth.setDomain(domain);
try {
client.newAuthorization(account, auth);
client.newAuthorization(reg, auth);
} catch (AcmeUnauthorizedException ex) {
// Maybe there are new T&C to accept?
boolean accepted = acceptAgreement(client, account, reg);
boolean accepted = acceptAgreement(client, reg);
if (!accepted) {
return;
}
// Then try again...
client.newAuthorization(account, auth);
client.newAuthorization(reg, auth);
}
LOG.info("New authorization for domain " + domain);
// Uncomment a challenge...
Challenge challenge = httpChallenge(auth, account, domain);
// Challenge challenge = dnsChallenge(auth, account, domain);
// Challenge challenge = tlsSniChallenge(auth, account, domain);
Challenge challenge = httpChallenge(auth, reg, domain);
// Challenge challenge = dnsChallenge(auth, reg, domain);
// Challenge challenge = tlsSniChallenge(auth, reg, domain);
if (challenge == null) {
return;
}
// Trigger the challenge
client.triggerChallenge(account, challenge);
client.triggerChallenge(reg, challenge);
// Poll for the challenge to complete
int attempts = 10;
@ -177,7 +175,7 @@ public class ClientTest {
}
// Request a signed certificate
URI certificateUri = client.requestCertificate(account, csrb.getEncoded());
URI certificateUri = client.requestCertificate(reg, csrb.getEncoded());
LOG.info("Success! The certificate for domains " + domains + " has been generated!");
LOG.info("Certificate URI: " + certificateUri);
@ -188,13 +186,13 @@ public class ClientTest {
}
// Revoke the certificate (uncomment if needed...)
// client.revokeCertificate(account, cert);
// client.revokeCertificate(reg, cert);
}
/**
* Prepares HTTP challenge.
*/
public Challenge httpChallenge(Authorization auth, Account account, String domain) throws AcmeException {
public Challenge httpChallenge(Authorization auth, Registration reg, String domain) throws AcmeException {
// Find a single http-01 challenge
HttpChallenge challenge = auth.findChallenge(HttpChallenge.TYPE);
if (challenge == null) {
@ -203,7 +201,7 @@ public class ClientTest {
}
// Authorize the challenge
challenge.authorize(account);
challenge.authorize(reg);
// Output the challenge, wait for acknowledge...
LOG.info("Please create a file in your web server's base directory.");
@ -233,7 +231,7 @@ public class ClientTest {
/**
* Prepares DNS challenge.
*/
public Challenge dnsChallenge(Authorization auth, Account account, String domain) throws AcmeException {
public Challenge dnsChallenge(Authorization auth, Registration reg, String domain) throws AcmeException {
// Find a single dns-01 challenge
DnsChallenge challenge = auth.findChallenge(DnsChallenge.TYPE);
if (challenge == null) {
@ -242,7 +240,7 @@ public class ClientTest {
}
// Authorize the challenge
challenge.authorize(account);
challenge.authorize(reg);
// Output the challenge, wait for acknowledge...
LOG.info("Please create a TXT record:");
@ -267,7 +265,7 @@ public class ClientTest {
/**
* Prepares TLS-SNI challenge.
*/
public Challenge tlsSniChallenge(Authorization auth, Account account, String domain) throws AcmeException {
public Challenge tlsSniChallenge(Authorization auth, Registration reg, String domain) throws AcmeException {
// Find a single tls-sni-01 challenge
TlsSniChallenge challenge = auth.findChallenge(TlsSniChallenge.TYPE);
if (challenge == null) {
@ -276,7 +274,7 @@ public class ClientTest {
}
// Authorize the challenge
challenge.authorize(account);
challenge.authorize(reg);
// Get the Subject
String subject = challenge.getSubject();
@ -327,13 +325,11 @@ public class ClientTest {
*
* @param client
* {@link AcmeClient} to send confirmation to
* @param account
* {@link Account} User's account
* @param reg
* {@link Registration} User's registration, containing the Agreement URI
* @return {@code true}: User confirmed, {@code false} user rejected
*/
public boolean acceptAgreement(AcmeClient client, Account account, Registration reg)
public boolean acceptAgreement(AcmeClient client, Registration reg)
throws AcmeException {
int option = JOptionPane.showConfirmDialog(null,
"Do you accept the Terms of Service?\n\n" + reg.getAgreement(),
@ -344,7 +340,7 @@ public class ClientTest {
return false;
}
client.modifyRegistration(account, reg);
client.modifyRegistration(reg);
LOG.info("Updated user's ToS");
return true;

View File

@ -6,7 +6,7 @@ After authorizing the challenge, `DnsChallenge` provides a digest string:
```java
DnsChallenge challenge = auth.findChallenge(DnsChallenge.TYPE);
challenge.authorize(account);
challenge.authorize(registration);
String digest = challenge.getDigest();
```

View File

@ -6,7 +6,7 @@ After authorizing the challenge, `HttpChallenge` provides two strings:
```java
HttpChallenge challenge = auth.findChallenge(HttpChallenge.TYPE);
challenge.authorize(account);
challenge.authorize(registration);
String token = challenge.getToken();
String content = challenge.getAuthorization();

View File

@ -13,14 +13,14 @@ Collection<X509Certificate> certificates = challenge.getCertificates();
In the next step, the _current owner of the domain_ authorizes the challenge, by signing it with a key pair that corresponds to one of the `certificates`:
```java
Account ownerAccount = ... // Account of the domain owner
Registration ownerRegistration = ... // Registration of the domain owner
KeyPair domainKeyPair = ... // Key pair matching a certificate
String domain = ... // Domain to authorize
challenge.authorize(ownerAccount, domainKeyPair, domain);
challenge.authorize(ownerRegistration, domainKeyPair, domain);
```
The challenge is completed when the domain is associated with the account of the `ownerAccount`, and the `domainKeyPair` matches one of the `certificates`.
The challenge is completed when the domain is associated with the account of the `ownerRegistration`, and the `domainKeyPair` matches one of the `certificates`.
## Importing a Validation
@ -31,12 +31,12 @@ There is a way to prepare the validation externally, and import a validation doc
_acme4j_ offers a `ValidationBuilder` class for generating the validation document:
```java
Account ownerAccount = ... // Account of the domain owner
Registration ownerRegistration = ... // Registration of the domain owner
KeyPair domainKeyPair = ... // Key pair matching a certificates
ValidationBuilder vb = new ValidationBuilder();
vb.domain("example.org");
String json = vb.sign(ownerAccount, domainKeyPair);
String json = vb.sign(ownerRegistration, domainKeyPair);
```
This `json` string can be transported (e.g. via email) and then imported into the challenge:

View File

@ -6,7 +6,7 @@ After authorizing the challenge, `TlsSniChallenge` provides a subject:
```java
TlsSniChallenge challenge = auth.findChallenge(TlsSniChallenge.TYPE);
challenge.authorize(account);
challenge.authorize(registration);
String subject = challenge.getSubject();
```
@ -17,7 +17,7 @@ The `subject` is basically a domain name formed like in this example:
30c452b9bd088cdbc2c4094947025d7c.7364ea602ac325a1b55ceaae024fbe29.acme.invalid
```
You need to create a self-signed certificate with the subject set as _Subject Alternative Name_. After that, configure your web server so it will use this certificate on a SNI request to the `subject`.
You need to create a self-signed certificate with the subject set as _Subject Alternative Name_. After that, configure your web server so it will use this certificate on a SNI request to the `subject`.
The `TlsSniChallenge` class does not generate a self-signed certificate, as it would require _Bouncy Castle_. However, there is a utility method in the _acme4j-utils_ module for this use case:

View File

@ -6,7 +6,7 @@ Once you have your account set up, you need to associate your domain with it. Th
Authorization auth = new Authorization();
auth.setDomain("example.org");
client.newAuthorization(account, auth);
client.newAuthorization(registration, auth);
```
When `newAuthorization()` returns successfully, the `Authorization` instance contains further details about how you can prove ownership of your domain. An ACME server offers combinations of different authorization methods, called `Challenge`s.
@ -30,10 +30,10 @@ HttpChallenge challenge = auth.findChallenge(HttpChallenge.TYPE);
It returns a properly casted `Challenge` object, or `null` if your challenge type was not acceptable.
After you have found a challenge, you need to sign it first:
After you have found a challenge, you need to sign it with your `Registration` first:
```java
challenge.authorize(account);
challenge.authorize(registration);
```
After signing the challenge, it provides the necessary data for a successful response to the challenge. The kind of response depends on the challenge type (see the [documentation of challenges](../challenge/index.html)). Some types may also require more data for authorizing the challenge.
@ -41,7 +41,7 @@ After signing the challenge, it provides the necessary data for a successful res
After you have performed the necessary steps to set up the response to the challenge, the ACME server is told to test your response:
```java
client.triggerChallenge(account, challenge);
client.triggerChallenge(registration, challenge);
```
Again, the call completes the `Challenge` transfer object with server side data like the current challenge status and a challenge URI.
@ -51,7 +51,7 @@ Now you have to wait for the server to test your response and set the challenge
```java
while (challenge.getStatus() != Challenge.Status.VALID) {
Thread.sleep(3000L);
client.updateChallenge(account, challenge);
client.updateChallenge(registration, challenge);
}
```
@ -61,8 +61,6 @@ As soon as the challenge is `VALID`, you have successfully associated the domain
If your final certificate contains further domains or subdomains, repeat the authorization run with each of them.
Note that wildcard certificates are not currently supported.
## Update an Authorization
For each authorization, the server provides an URI where the status of the authorization can be queried. It can be retrieved from `Authorization.getLocation()` after `newAuthorization()` returned.
@ -88,11 +86,11 @@ Challenge originalChallenge = ... // some Challenge instance
URI challengeUri = originalChallenge.getLocation();
```
Later, you pass this `challengeUri` to `recreateChallenge()`:
Later, you pass this `challengeUri` to `restoreChallenge()`:
```java
URI challengeUri = ... // challenge URI
Challenge restoredChallenge = client.restoreChallenge(account, challengeUri);
Challenge restoredChallenge = client.restoreChallenge(registration, challengeUri);
```
The `restoredChallenge` already reflects the current state of the challenge.

View File

@ -45,7 +45,7 @@ To revoke a certificate, just pass the it to the respective method:
```java
X509Certificate cert = ... // certificate to be revoked
client.revokeCertificate(account, cert);
client.revokeCertificate(registration, cert);
```
As an exception, ACME servers also accept the domain's key pair for revoking a certificate. _acme4j_ does not directly support this way of revocation. However, you can do so with this tiny hack:
@ -53,7 +53,7 @@ As an exception, ACME servers also accept the domain's key pair for revoking a c
```java
KeyPair domainKeyPair = ... // KeyPair to be used for HTTPS encryption
X509Certificate cert = ... // certificate to be revoked
client.revokeCertificate(new Account(domainKeyPair), cert);
client.revokeCertificate(new Registration(domainKeyPair), cert);
```
If you have the choice, you should always prefer to use your account key.

View File

@ -17,4 +17,4 @@ For this reason, special ACME URIs should be preferred:
AcmeClient client = AcmeClientFactory.connect("acme://letsencrypt.org/staging");
```
Instead of a generic provider, this call uses a special _Let's Encrypt_ provider that also accepts the _Let's Encrypt_ certificate.
Instead of a generic provider, this call uses a special _Let's Encrypt_ provider that also accepts the _Let's Encrypt_ certificate.

View File

@ -2,16 +2,28 @@
_acme4j_ is a client library that helps connecting to ACME servers without worrying about specification details.
Central part of the communication is an [`Account`](../apidocs/org/shredzone/acme4j/Account.html) object, which contains a key pair. The ACME server identifies your account by the public key, and verifies that your requests are signed with your private key. For this reason, you should keep the key pair in a safe place. If you should lose it, you would need to recover access to your account.
Central part of the communication is a [`Registration`](../apidocs/org/shredzone/acme4j/Registration.html) object, which contains a key pair. The ACME server identifies your account by the public key, and verifies that your requests are signed with your private key. For this reason, you should keep the key pair in a safe place. If you should lose it, you would need to recover access to your account.
The first step is to create a Java `KeyPair`, save it somewhere, and then pass it to the constructor of `Account`:
The first step is to create a Java `KeyPair`, save it somewhere, and then pass it to the constructor of `Registration`:
```java
KeyPair keypair = ... // your key pair
Account account = new Account(keypair);
Registration registration = new Registration(keypair);
```
You need this `Account` instance as identifier for almost all API calls.
You need this `Registration` instance as identifier for almost all API calls.
Some calls additionally need the registration location URI to be set. You can either set it after construction, or use the constructor that also accepts the location URI:
```java
KeyPair keypair = ... // your key pair
URI accountLocationUri = ... // your account's URI, as returned by newRegistration()
Registration registration1 = new Registration(keypair, accountLocationUri);
Registration registration2 = new Registration(keypair);
registration2.setLocation(accountLocationUri);
```
To get a certificate, these steps need to be performed:

View File

@ -12,16 +12,18 @@ Individual CAs may offer further ways of recovery, which are not part of this do
On this recovery method, the CA contacts the account owner via one of the contact addresses given on account creation. The owner is asked to take some action (e.g. clicking on a link in an email). If it was successful, the account data is transferred to the new account.
To initiate contact-based recovery, you first need to create a new key pair and an `Account` object. Then create a `Registration` object by passing the location URI of your _old_ account to the constructor. Finally, start the recovery process by invoking `recoverRegistration()`:
To initiate contact-based recovery, you first need to create a new account key pair. Then create a `Registration` object by passing the _new_ key pair and location URI of your _old_ account to the constructor. Finally, start the recovery process by invoking `recoverRegistration()`:
```java
Account account = ... // your new account
KeyPair newKeyPair = ... // your new account KeyPair
URI oldAccountUri = ... // location of your old account
Registration reg = new Registration(oldAccountUri);
client.recoverRegistration(account, reg);
Registration reg = new Registration(newKeyPair, oldAccountUri);
client.recoverRegistration(reg);
URI newAccountUri = reg.getLocation();
```
`newAccountUri` is the location URI of your _new_ account.
`newAccountUri` is the location URI of your _new_ account. Store it and use it in connection with your new key pair.
The old location URI is now invalid and can be deleted.

View File

@ -1,43 +1,43 @@
# Register an Account
The first thing to do is to register your `Account` with the CA.
The first thing to do is to register your account key with the CA.
You need a `Registration` instance that serves as a data transfer object, and fill the object with details of your account. The `AcmeClient.newRegistration()` call then completes the data transfer object with server side account data.
This code fragment registers your account with the CA. Optionally you can add contact URIs (like email addresses or phone numbers) to the registration, which will help the CA getting in contact with you.
```java
Registration reg = new Registration();
KeyPair keyPair = ... // your account KeyPair
Registration reg = new Registration(keypair);
reg.addContact("mailto:acme@example.com"); // optional
client.newRegistration(account, reg);
client.newRegistration(reg);
URI accountLocationUri = reg.getLocation(); // your account's server URI
```
After invocating `newRegistration()`, the `location` property contains the URI of your newly created account on server side.
After invocating `newRegistration()`, the `location` property contains the URI of your newly created account on server side. You should copy the `location` to a safe place. You will need it again if you need to [update your registration](#Update_your_Registration), or if you need to [recover](./recovery.html) access to your account after you have lost your account key. Unlike your key pair, the `location` is a public information that does not need security precautions.
`newRegistration()` may fail and throw an `AcmeException` for various reasons. When your public key was already registered with the CA, an `AcmeConflictException` is thrown, but the `location` property will still hold your account URI after the call. This may be helpful if you forgot your account URI and need to recover it.
You should always copy the `location` to a safe place. If you should lose your key pair, you will need it to [recover](./recovery.html) access to your account. Unlike your key pair, the `location` is an information that does not need security precautions.
## Update your Registration
## Update an Account
At some point, you may want to update your registration. For example your contact address might have changed, or you were asked by the CA to accept the latest terms of service.
At some point, you may want to update your account. For example your contact address might have changed, or you were asked by the CA to accept the current terms of service.
To do so, create a `Registration` object again, and set the `location` property to the URI that you previously got via `newRegistration()`. Also set whatever you like to change to your account.
To do so, create a `Registration` object again, this time by passing in the account key pair and the `location` property that you previously got via `newRegistration()`. Also set whatever you like to change to your account.
The following example accepts the terms of service by explicitly setting the URL to the agreement document.
```java
KeyPair keyPair = ... // your account KeyPair
URI accountLocationUri = ... // your account's URI
URI agreementUri = ... // TAC link provided by the CA
Registration reg = new Registration();
reg.setLocation(accountLocationUri);
Registration reg = new Registration(keyPair, accountLocationUri);
reg.setAgreement(agreementUri);
client.modifyRegistration(account, reg);
client.modifyRegistration(reg);
```
## Account Key Roll-Over
@ -47,12 +47,14 @@ It is also possible to change the key pair that is associated with your account,
The following example changes the key pair:
```java
Registration reg = new Registration();
reg.setLocation(accountLocationUri);
KeyPair oldKeyPair = ... // your old KeyPair that is to be replaced
URI accountLocationUri = ... // your account's URI
Registration reg = new Registration(oldKeyPair, accountLocationUri);
KeyPair newKeyPair = ... // new KeyPair to be used
client.changeRegistrationKey(account, reg, newKeyPair);
client.changeRegistrationKey(reg, newKeyPair);
```
All subsequent calls must now use an `Account` instance with the new key. The old key can be disposed.
All subsequent calls must now use the new key pair. The old key pair can be disposed.

View File

@ -31,7 +31,7 @@
<item name="Introduction" href="index.html"/>
<item name="How to Use" href="usage/index.html">
<item name="Connection" href="usage/connect.html"/>
<item name="Account" href="usage/register.html"/>
<item name="Registration" href="usage/register.html"/>
<item name="Authorization" href="usage/authorization.html"/>
<item name="Certificate" href="usage/certificate.html"/>
<item name="Recovery" href="usage/recovery.html"/>