ACME client will now reuse existing accounts instead of creating new one every time and renamed user variables to account.

This commit is contained in:
Ziver Koc 2021-08-21 22:56:50 +02:00
parent 98f53adcfb
commit 3552c21404
3 changed files with 86 additions and 53 deletions

View file

@ -7,7 +7,6 @@ import java.security.KeyPair;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -65,29 +64,11 @@ public class AcmeClient {
this.acmeServerUrl = acmeServerUrl;
// ------------------------------------------------
// Read in keys
// ------------------------------------------------
KeyPair userKeyPair = dataStore.loadUserKeyPair(); // Load the user key file. If there is no key file, create a new one.
KeyPair domainKeyPair = dataStore.loadDomainKeyPair(); // Load or create a key pair for the domains. This should not be same as the userKeyPair!
if (userKeyPair == null) {
logger.fine("Creating new user keys.");
userKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
dataStore.storeUserKeyPair(userKeyPair);
}
if (domainKeyPair == null) {
logger.fine("Creating new domain keys.");
domainKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
dataStore.storeDomainKeyPair(domainKeyPair);
}
// ------------------------------------------------
// Start user authorization process
// Start user authentication process
// ------------------------------------------------
Session session = new Session(acmeServerUrl);
acmeAccount = getAccount(session, userKeyPair); // Get the Account. If there is no account yet, create a new one.
acmeAccount = getAccount(session); // Get the Account. If there is no account yet, create a new one.
}
@ -138,10 +119,19 @@ public class AcmeClient {
execDomainChallenge(challenge);
}
// Load or create a key pair for the domains. This should not be same as the userKeyPair!
KeyPair domainKeyPair = dataStore.getDomainKeyPair();
if (domainKeyPair == null) {
logger.fine("Creating new domain keys.");
domainKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
dataStore.storeDomainKeyPair(domainKeyPair);
}
// Generate one "Certificate Signing Request" for all the domains, and sign it with the domain key pair.
CSRBuilder csrBuilder = new CSRBuilder();
csrBuilder.addDomains(domains);
csrBuilder.sign(dataStore.loadDomainKeyPair());
csrBuilder.sign(domainKeyPair);
order.execute(csrBuilder.getEncoded()); // Order the certificate
@ -173,6 +163,7 @@ public class AcmeClient {
logger.info("The certificate for domains '" + StringUtil.join(",", domains) + "' has been successfully generated.");
// Cleanup
order = null;
challenges.clear();
@ -184,29 +175,42 @@ public class AcmeClient {
* Finds your {@link Account} at the ACME server. It will be found by your user's
* public key. If your key is not known to the server yet, a new account will be
* created.
* <p>
* This is a simple way of finding your {@link Account}. A better way is to get the
* URL of your new account with {@link Account#getLocation()} and store it somewhere.
* If you need to get access to your account later, reconnect to it via {@link
* Session#login(URL, KeyPair)} by using the stored location.
*
* @param session {@link Session} to bind with
* @return {@link Account}
*/
private Account getAccount(Session session, KeyPair accountKey) throws AcmeException {
private Account getAccount(Session session) throws AcmeException {
// Load the account key pair
URL accountLocation = dataStore.getAccountLocation();
KeyPair accountKeyPair = dataStore.getAccountKeyPair();
// Ask the user to accept the TOS, if server provides us with a link.
URI tos = session.getMetadata().getTermsOfService();
if (tos != null) {
logger.info("By using this service you accept the Terms of Service: " + tos);
}
Account account = new AccountBuilder()
.agreeToTermsOfService()
.useKeyPair(accountKey)
.create(session);
logger.info("Registered a new user, URL: " + account.getLocation());
// Create a new account or login existing user
return account;
if (accountLocation == null || accountKeyPair == null) {
logger.fine("Creating new account keys.");
accountKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);
Account account = new AccountBuilder()
.agreeToTermsOfService()
.useKeyPair(accountKeyPair)
.create(session);
logger.info("Successfully registered new account, URL: " + account.getLocation());
dataStore.storeAccountKeyPair(account.getLocation(), accountKeyPair);
return account;
} else {
logger.info("Logging in existing account: " + accountLocation);
Login login = session.login(accountLocation, accountKeyPair);
return login.getAccount();
}
}
@ -238,7 +242,7 @@ public class AcmeClient {
}
// Wait for a few seconds
long sleep = 100L + 5000L * attempts;
long sleep = 100L + 1000L * attempts;
logger.fine("Challenge not yet completed, sleeping for: " + StringUtil.formatTimeToString(sleep));
Thread.sleep(sleep);

View file

@ -1,29 +1,38 @@
package zutil.net.acme;
import java.net.URL;
import java.security.KeyPair;
public interface AcmeDataStore {
/**
* Loads a user key pair.
* Loads an accounts key pair.
*
* @return a KeyPair for the user account, null if no KeyPar was found.
* @return a KeyPair for the account, null if no KeyPair is found.
*/
KeyPair loadUserKeyPair();
URL getAccountLocation();
/**
* Stores a user key pair for later usage.
* Retrieve an account key pair.
*
* @param keyPair the keys for the user account
* @return a KeyPair object for the account, null if no KeyPair is found.
*/
void storeUserKeyPair(KeyPair keyPair);
KeyPair getAccountKeyPair();
/**
* Stores an accounts key pair for later usage.
*
* @param accountLocation the URL to the account profile, this is used as an identifier to the ACME service.
* @param accountKeyPair the keys for the user account
*/
void storeAccountKeyPair(URL accountLocation, KeyPair accountKeyPair);
/**
* Loads a domain key pair.
*
* @return a KeyPair for the domains, null if no KeyPar was found.
* @return a KeyPair object for the domains, null if no KeyPar was found.
*/
KeyPair loadDomainKeyPair();
KeyPair getDomainKeyPair();
/**
* Stores a domain key pair for later usage.

View file

@ -1,13 +1,16 @@
package zutil.net.acme;
import org.shredzone.acme4j.util.KeyPairUtils;
import zutil.io.file.FileUtil;
import java.io.*;
import java.net.URL;
import java.security.KeyPair;
public class AcmeFileDataStore implements AcmeDataStore {
private final File userKeyFile;
private final File accountLocationFile;
private final File accountKeyFile;
private final File domainKeyFile;
@ -17,12 +20,25 @@ public class AcmeFileDataStore implements AcmeDataStore {
* @param folder is the folder there the different files should be stored in.
*/
public AcmeFileDataStore(File folder) {
this.userKeyFile = new File(folder, "user.key");
this.accountLocationFile = new File(folder, "accountLocation.cfg");
this.accountKeyFile = new File(folder, "account.key");
this.domainKeyFile = new File(folder, "domain.key");
}
public KeyPair loadUserKeyPair(){
@Override
public URL getAccountLocation() {
if (accountKeyFile.exists()) {
try {
return new URL(FileUtil.getContent(accountKeyFile));
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
public KeyPair getAccountKeyPair(){
if (domainKeyFile.exists()) {
try (FileReader fr = new FileReader(domainKeyFile)) {
return KeyPairUtils.readKeyPair(fr);
@ -32,17 +48,20 @@ public class AcmeFileDataStore implements AcmeDataStore {
}
return null;
}
public void storeUserKeyPair(KeyPair keyPair){
try (FileWriter fw = new FileWriter(userKeyFile)) {
KeyPairUtils.writeKeyPair(keyPair, fw);
public void storeAccountKeyPair(URL accountLocation, KeyPair accounKeyPair){
try (FileWriter fw = new FileWriter(accountKeyFile)) {
FileUtil.setContent(accountLocationFile, accountLocation.toString());
KeyPairUtils.writeKeyPair(accounKeyPair, fw);
} catch (IOException e) {
e.printStackTrace();
}
}
public KeyPair loadDomainKeyPair() {
if (userKeyFile.exists()) {
try (FileReader fr = new FileReader(userKeyFile)) {
public KeyPair getDomainKeyPair() {
if (accountKeyFile.exists()) {
try (FileReader fr = new FileReader(accountKeyFile)) {
return KeyPairUtils.readKeyPair(fr);
} catch (IOException e) {
e.printStackTrace();
@ -50,8 +69,9 @@ public class AcmeFileDataStore implements AcmeDataStore {
}
return null;
}
public void storeDomainKeyPair(KeyPair keyPair){
try (FileWriter fw = new FileWriter(userKeyFile)) {
try (FileWriter fw = new FileWriter(accountKeyFile)) {
KeyPairUtils.writeKeyPair(keyPair, fw);
} catch (IOException e) {
e.printStackTrace();