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:
parent
98f53adcfb
commit
3552c21404
3 changed files with 86 additions and 53 deletions
|
|
@ -7,7 +7,6 @@ import java.security.KeyPair;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
@ -65,29 +64,11 @@ public class AcmeClient {
|
||||||
this.acmeServerUrl = acmeServerUrl;
|
this.acmeServerUrl = acmeServerUrl;
|
||||||
|
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
// Read in keys
|
// Start user authentication process
|
||||||
// ------------------------------------------------
|
|
||||||
|
|
||||||
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
|
|
||||||
// ------------------------------------------------
|
// ------------------------------------------------
|
||||||
|
|
||||||
Session session = new Session(acmeServerUrl);
|
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);
|
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.
|
// Generate one "Certificate Signing Request" for all the domains, and sign it with the domain key pair.
|
||||||
CSRBuilder csrBuilder = new CSRBuilder();
|
CSRBuilder csrBuilder = new CSRBuilder();
|
||||||
csrBuilder.addDomains(domains);
|
csrBuilder.addDomains(domains);
|
||||||
csrBuilder.sign(dataStore.loadDomainKeyPair());
|
csrBuilder.sign(domainKeyPair);
|
||||||
|
|
||||||
order.execute(csrBuilder.getEncoded()); // Order the certificate
|
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.");
|
logger.info("The certificate for domains '" + StringUtil.join(",", domains) + "' has been successfully generated.");
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
|
||||||
order = null;
|
order = null;
|
||||||
challenges.clear();
|
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
|
* 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
|
* public key. If your key is not known to the server yet, a new account will be
|
||||||
* created.
|
* 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
|
* @param session {@link Session} to bind with
|
||||||
* @return {@link Account}
|
* @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.
|
// 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) {
|
||||||
logger.info("By using this service you accept the Terms of Service: " + tos);
|
logger.info("By using this service you accept the Terms of Service: " + tos);
|
||||||
}
|
}
|
||||||
|
|
||||||
Account account = new AccountBuilder()
|
// Create a new account or login existing user
|
||||||
.agreeToTermsOfService()
|
|
||||||
.useKeyPair(accountKey)
|
|
||||||
.create(session);
|
|
||||||
logger.info("Registered a new user, URL: " + account.getLocation());
|
|
||||||
|
|
||||||
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
|
// 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));
|
logger.fine("Challenge not yet completed, sleeping for: " + StringUtil.formatTimeToString(sleep));
|
||||||
Thread.sleep(sleep);
|
Thread.sleep(sleep);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,38 @@
|
||||||
package zutil.net.acme;
|
package zutil.net.acme;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
|
||||||
public interface AcmeDataStore {
|
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.
|
* 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.
|
* Stores a domain key pair for later usage.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package zutil.net.acme;
|
package zutil.net.acme;
|
||||||
|
|
||||||
import org.shredzone.acme4j.util.KeyPairUtils;
|
import org.shredzone.acme4j.util.KeyPairUtils;
|
||||||
|
import zutil.io.file.FileUtil;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.net.URL;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
|
||||||
public class AcmeFileDataStore implements AcmeDataStore {
|
public class AcmeFileDataStore implements AcmeDataStore {
|
||||||
|
|
||||||
private final File userKeyFile;
|
private final File accountLocationFile;
|
||||||
|
private final File accountKeyFile;
|
||||||
private final File domainKeyFile;
|
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.
|
* @param folder is the folder there the different files should be stored in.
|
||||||
*/
|
*/
|
||||||
public AcmeFileDataStore(File folder) {
|
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");
|
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()) {
|
if (domainKeyFile.exists()) {
|
||||||
try (FileReader fr = new FileReader(domainKeyFile)) {
|
try (FileReader fr = new FileReader(domainKeyFile)) {
|
||||||
return KeyPairUtils.readKeyPair(fr);
|
return KeyPairUtils.readKeyPair(fr);
|
||||||
|
|
@ -32,17 +48,20 @@ public class AcmeFileDataStore implements AcmeDataStore {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
public void storeUserKeyPair(KeyPair keyPair){
|
|
||||||
try (FileWriter fw = new FileWriter(userKeyFile)) {
|
public void storeAccountKeyPair(URL accountLocation, KeyPair accounKeyPair){
|
||||||
KeyPairUtils.writeKeyPair(keyPair, fw);
|
try (FileWriter fw = new FileWriter(accountKeyFile)) {
|
||||||
|
FileUtil.setContent(accountLocationFile, accountLocation.toString());
|
||||||
|
KeyPairUtils.writeKeyPair(accounKeyPair, fw);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public KeyPair loadDomainKeyPair() {
|
|
||||||
if (userKeyFile.exists()) {
|
public KeyPair getDomainKeyPair() {
|
||||||
try (FileReader fr = new FileReader(userKeyFile)) {
|
if (accountKeyFile.exists()) {
|
||||||
|
try (FileReader fr = new FileReader(accountKeyFile)) {
|
||||||
return KeyPairUtils.readKeyPair(fr);
|
return KeyPairUtils.readKeyPair(fr);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
@ -50,8 +69,9 @@ public class AcmeFileDataStore implements AcmeDataStore {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void storeDomainKeyPair(KeyPair keyPair){
|
public void storeDomainKeyPair(KeyPair keyPair){
|
||||||
try (FileWriter fw = new FileWriter(userKeyFile)) {
|
try (FileWriter fw = new FileWriter(accountKeyFile)) {
|
||||||
KeyPairUtils.writeKeyPair(keyPair, fw);
|
KeyPairUtils.writeKeyPair(keyPair, fw);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue