private String getAccountUrl(AcmeAccount account, boolean staging) throws AcmeException { String accountUrl = account.getAccountUrl(); if (accountUrl == null) { createAccount(account, staging, true); accountUrl = account.getAccountUrl(); if (accountUrl == null) { acme.acmeAccountDoesNotExist(); } } return accountUrl; }
} else { payloadBuilder.add(TERMS_OF_SERVICE_AGREED, account.isTermsOfServiceAgreed()); if (account.getContactUrls() != null && !(account.getContactUrls().length == 0)) { JsonArrayBuilder contactBuilder = Json.createArrayBuilder(); for (String contactUrl : account.getContactUrls()) { contactBuilder.add(contactUrl); account.setAccountUrl(getLocation(connection)); try { return connection.getResponseCode() == HttpURLConnection.HTTP_CREATED;
/** * Change the key that is associated with the given ACME account. * * @param account the ACME account information to use (must not be {@code null}) * @param staging whether or not the staging server URL should be used * @throws AcmeException if an error occurs while attempting to change the key that is associated with the given ACME account */ public void changeAccountKey(AcmeAccount account, boolean staging) throws AcmeException { Assert.checkNotNullParam("account", account); SelfSignedX509CertificateAndSigningKey newCertificateAndSigningKey = SelfSignedX509CertificateAndSigningKey.builder() .setKeySize(account.getKeySize()) .setKeyAlgorithmName(account.getKeyAlgorithmName()) .setDn(account.getDn()) .build(); changeAccountKey(account, staging, newCertificateAndSigningKey.getSelfSignedCertificate(), newCertificateAndSigningKey.getSigningKey()); }
private String getEncodedProtectedHeader(boolean useJwk, String resourceUrl, AcmeAccount account, boolean staging) throws AcmeException { JsonObjectBuilder protectedHeaderBuilder = Json.createObjectBuilder().add(ALG, account.getAlgHeader()); if (useJwk) { protectedHeaderBuilder.add(JWK, getJwk(account.getPublicKey(), account.getAlgHeader())); } else { protectedHeaderBuilder.add(KID, getAccountUrl(account, staging)); } protectedHeaderBuilder .add(NONCE, base64UrlEncode(getNonce(account, staging))) .add(URL, resourceUrl); return getEncodedJson(protectedHeaderBuilder.build()); }
for (int i = 0; i < MAX_RETRIES; i++) { String encodedProtectedHeader = getEncodedProtectedHeader(useJwk, resourceUrl, account, staging); String encodedSignature = getEncodedSignature(account.getPrivateKey(), account.getSignature(), encodedProtectedHeader, encodedPayload); JsonObject jws = getJws(encodedProtectedHeader, encodedPayload, encodedSignature); connection = (HttpURLConnection) url.openConnection(); account.setNonce(getReplayNonce(connection)); // update the account nonce
static AcmeAccount resetAcmeAccount(AcmeAccount acmeAccount, boolean staging) { String accountUrl = acmeAccount.getAccountUrl(); if (accountUrl != null) { String stagingEndpoint = acmeAccount.getStagingServerUrl().substring(0, acmeAccount.getStagingServerUrl().indexOf("/" + DIRECTORY)); if ((accountUrl.startsWith(stagingEndpoint) && ! staging) || (! accountUrl.startsWith(stagingEndpoint) && staging)) { // need to reset the account information so it will get populated with the correct staging / non-staging account URL AcmeAccount.Builder acmeAccountBuilder = AcmeAccount.builder(); acmeAccountBuilder .setServerUrl(acmeAccount.getServerUrl()) .setStagingServerUrl(acmeAccount.getStagingServerUrl()) .setDn(acmeAccount.getDn()) .setKey(acmeAccount.getCertificate(), acmeAccount.getPrivateKey()) .setTermsOfServiceAgreed(acmeAccount.isTermsOfServiceAgreed()); if (acmeAccount.getContactUrls() != null) { acmeAccountBuilder.setContactUrls(acmeAccount.getContactUrls()); } return acmeAccountBuilder.build(); } } return acmeAccount; }
/** * Change the key that is associated with the given ACME account. * * @param account the ACME account information to use (must not be {@code null}) * @param staging whether or not the staging server URL should be used * @param certificate the new certificate to associate with the given ACME account (must not be {@code null}) * @param privateKey the new private key to associate with the given ACME account (must not be {@code null}) * @throws AcmeException if an error occurs while attempting to change the key that is associated with the given ACME account */ public void changeAccountKey(AcmeAccount account, boolean staging, X509Certificate certificate, PrivateKey privateKey) throws AcmeException { Assert.checkNotNullParam("account", account); Assert.checkNotNullParam("certificate", certificate); Assert.checkNotNullParam("privateKey", privateKey); final String keyChangeUrl = getResourceUrl(account, AcmeResource.KEY_CHANGE, staging).toString(); final String signatureAlgorithm = getDefaultCompatibleSignatureAlgorithmName(privateKey); final String algHeader = getAlgHeaderFromSignatureAlgorithm(signatureAlgorithm); final String innerEncodedProtectedHeader = getEncodedProtectedHeader(algHeader, certificate.getPublicKey(), keyChangeUrl); JsonObjectBuilder innerPayloadBuilder = Json.createObjectBuilder() .add(ACCOUNT, getAccountUrl(account, staging)) .add(OLD_KEY, getJwk(account.getPublicKey(), account.getAlgHeader())); final String innerEncodedPayload = getEncodedJson(innerPayloadBuilder.build()); final String innerEncodedSignature = getEncodedSignature(privateKey, signatureAlgorithm, innerEncodedProtectedHeader, innerEncodedPayload); final String outerEncodedPayload = getEncodedJson(getJws(innerEncodedProtectedHeader, innerEncodedPayload, innerEncodedSignature)); sendPostRequestWithRetries(account, staging, keyChangeUrl, false, outerEncodedPayload, HttpURLConnection.HTTP_OK); account.changeCertificateAndPrivateKey(certificate, privateKey); // update account info }
final AcmeAccount acmeAccount = getAcmeAccount(context, certificateAuthorityAccountName, staging); if (agreeToTermsOfService != null) { acmeAccount.setTermsOfServiceAgreed(agreeToTermsOfService); if (acmeAccount.getAccountUrl() == null) { created = acmeClient.createAccount(acmeAccount, staging); acmeClient.updateAccount(acmeAccount, staging, acmeAccount.isTermsOfServiceAgreed(), acmeAccount.getContactUrls());
/** * Get the resource URLs needed to perform operations from the ACME server. * * @param account the ACME account information to use (must not be {@code null}) * @param staging whether or not the staging server URL should be used * @return a map of ACME resources to URLs * @throws AcmeException if an error occurs while attempting to get the resource URLs from the ACME server */ public Map<AcmeResource, URL> getResourceUrls(AcmeAccount account, boolean staging) throws AcmeException { Assert.checkNotNullParam("account", account); final Map<AcmeResource, URL> resourceUrls = account.getResourceUrls(staging); if (resourceUrls.isEmpty()) { HttpURLConnection connection = sendGetRequest(account.getServerUrl(staging), HttpURLConnection.HTTP_OK, JSON_CONTENT_TYPE); JsonObject directoryJson = getJsonResponse(connection); try { for (AcmeResource resource : AcmeResource.values()) { String resourceUrl = getOptionalJsonString(directoryJson, resource.getValue()); URL url = resourceUrl != null ? new URL(resourceUrl) : null; resourceUrls.put(resource, url); } } catch (MalformedURLException e) { throw acme.unableToRetrieveAcmeServerDirectoryUrls(e); } } return resourceUrls; }
/** * Update an account with an ACME server using the given account information. * * @param account the ACME account information to use (must not be {@code null}) * @param staging whether or not the staging server URL should be used * @param termsOfServiceAgreed the new value for whether or not the terms of service have been agreed to * @param contactUrls the new account contact URLs * @throws AcmeException if an error occurs while attempting to update the account */ public void updateAccount(AcmeAccount account, boolean staging, boolean termsOfServiceAgreed, String[] contactUrls) throws AcmeException { Assert.checkNotNullParam("account", account); JsonObjectBuilder payloadBuilder = Json.createObjectBuilder() .add(TERMS_OF_SERVICE_AGREED, termsOfServiceAgreed); if (contactUrls != null && ! (contactUrls.length == 0)) { JsonArrayBuilder contactBuilder = Json.createArrayBuilder(); for (String contactUrl : contactUrls) { contactBuilder.add(contactUrl); } payloadBuilder.add(CONTACT, contactBuilder.build()); } sendPostRequestWithRetries(account, staging, getAccountUrl(account, staging), false, getEncodedJson(payloadBuilder.build()), HttpURLConnection.HTTP_OK); account.setTermsOfServiceAgreed(termsOfServiceAgreed); if (contactUrls != null && ! (contactUrls.length == 0)) { account.setContactUrls(contactUrls); } }
/** * Update the contact URLs for an account with an ACME server. * * @param account the ACME account information to use (must not be {@code null}) * @param staging whether or not the staging server URL should be used * @param contactUrls the new account contact URLs * @throws AcmeException if an error occurs while attempting to update the account */ public void updateAccount(AcmeAccount account, boolean staging, String[] contactUrls) throws AcmeException { updateAccount(account, staging, account.isTermsOfServiceAgreed(), contactUrls); }
HttpURLConnection connection = sendGetRequest(account.getServerUrl(staging), HttpURLConnection.HTTP_OK, JSON_CONTENT_TYPE); JsonObject directoryJson = getJsonResponse(connection); JsonObject metadata = directoryJson.getJsonObject(META);
return new AcmeAccount(this);
private void saveCertificateAuthorityAccountKey(ModifiableKeyStoreService keyStoreService, char[] keyPassword) throws OperationFailedException { KeyStore modifiableAccountkeyStore = keyStoreService.getModifiableValue(); try { modifiableAccountkeyStore.setKeyEntry(alias, acmeAccount.getPrivateKey(), keyPassword, new X509Certificate[]{ acmeAccount.getCertificate() }); } catch (KeyStoreException e) { throw ROOT_LOGGER.unableToUpdateCertificateAuthorityAccountKeyStore(e, e.getLocalizedMessage()); } ((KeyStoreService) keyStoreService).save(); } }
@Override protected void executeRuntimeStep(final OperationContext context, final ModelNode operation) throws OperationFailedException { Boolean agreeToTermsOfService = UPDATE_AGREE_TO_TERMS_OF_SERVICE.resolveModelAttribute(context, operation).asBooleanOrNull(); boolean staging = STAGING.resolveModelAttribute(context, operation).asBoolean(); AcmeAccount acmeAccount = getAcmeAccount(context, staging); try { if (agreeToTermsOfService != null) { acmeClient.updateAccount(acmeAccount, staging, agreeToTermsOfService.booleanValue(), acmeAccount.getContactUrls()); } else { acmeClient.updateAccount(acmeAccount, staging, acmeAccount.getContactUrls()); } } catch (AcmeException e) { throw ROOT_LOGGER.unableToUpdateAccountWithCertificateAuthority(e, e.getLocalizedMessage()); } } }
@Override protected void executeRuntimeStep(final OperationContext context, final ModelNode operation) throws OperationFailedException { boolean agreeToTermsOfService = AGREE_TO_TERMS_OF_SERVICE.resolveModelAttribute(context, operation).asBoolean(); boolean staging = STAGING.resolveModelAttribute(context, operation).asBoolean(); AcmeAccount acmeAccount = getAcmeAccount(context, staging); try { acmeAccount.setTermsOfServiceAgreed(agreeToTermsOfService); boolean created = acmeClient.createAccount(acmeAccount, staging); if (! created) { throw ROOT_LOGGER.certificateAuthorityAccountAlreadyExists(ElytronDescriptionConstants.UPDATE_ACCOUNT, ElytronDescriptionConstants.CHANGE_ACCOUNT_KEY); } } catch (AcmeException e) { throw ROOT_LOGGER.unableToCreateAccountWithCertificateAuthority(e, e.getLocalizedMessage()); } } }
for (int i = 0; i < MAX_RETRIES; i++) { String encodedProtectedHeader = getEncodedProtectedHeader(useJwk, resourceUrl, account, staging); String encodedSignature = getEncodedSignature(account.getPrivateKey(), account.getSignature(), encodedProtectedHeader, encodedPayload); JsonObject jws = getJws(encodedProtectedHeader, encodedPayload, encodedSignature); connection = (HttpURLConnection) url.openConnection(); account.setNonce(getReplayNonce(connection)); // update the account nonce
/** * Change the key that is associated with the given ACME account. * * @param account the ACME account information to use (must not be {@code null}) * @param staging whether or not the staging server URL should be used * @param certificate the new certificate to associate with the given ACME account (must not be {@code null}) * @param privateKey the new private key to associate with the given ACME account (must not be {@code null}) * @throws AcmeException if an error occurs while attempting to change the key that is associated with the given ACME account */ public void changeAccountKey(AcmeAccount account, boolean staging, X509Certificate certificate, PrivateKey privateKey) throws AcmeException { Assert.checkNotNullParam("account", account); Assert.checkNotNullParam("certificate", certificate); Assert.checkNotNullParam("privateKey", privateKey); final String keyChangeUrl = getResourceUrl(account, AcmeResource.KEY_CHANGE, staging).toString(); final String signatureAlgorithm = getDefaultCompatibleSignatureAlgorithmName(privateKey); final String algHeader = getAlgHeaderFromSignatureAlgorithm(signatureAlgorithm); final String innerEncodedProtectedHeader = getEncodedProtectedHeader(algHeader, certificate.getPublicKey(), keyChangeUrl); JsonObjectBuilder innerPayloadBuilder = Json.createObjectBuilder() .add(ACCOUNT, getAccountUrl(account, staging)) .add(OLD_KEY, getJwk(account.getPublicKey(), account.getAlgHeader())); final String innerEncodedPayload = getEncodedJson(innerPayloadBuilder.build()); final String innerEncodedSignature = getEncodedSignature(privateKey, signatureAlgorithm, innerEncodedProtectedHeader, innerEncodedPayload); final String outerEncodedPayload = getEncodedJson(getJws(innerEncodedProtectedHeader, innerEncodedPayload, innerEncodedSignature)); sendPostRequestWithRetries(account, staging, keyChangeUrl, false, outerEncodedPayload, HttpURLConnection.HTTP_OK); account.changeCertificateAndPrivateKey(certificate, privateKey); // update account info }
/** * Get the key authorization string for this challenge. * * @param account the ACME account information to use (must not be {@code null}) * @return the key authorization string for this challenge * @throws AcmeException if the key authorization string cannot be determined */ public String getKeyAuthorization(AcmeAccount account) throws AcmeException { Assert.checkNotNullParam("account", account); JsonObject jwk = getJwk(account.getPublicKey(), account.getAlgHeader()); byte[] jwkWithoutWhitespace = CodePointIterator.ofString(jwk.toString()).skip(Character::isWhitespace).skipCrLf().asUtf8().drain(); try { MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(jwkWithoutWhitespace); byte[] jwkThumbprint = messageDigest.digest(); return token + "." + base64UrlEncode(jwkThumbprint); } catch (NoSuchAlgorithmException e) { throw acme.unableToDetermineKeyAuthorizationString(e); } }
/** * Get the resource URLs needed to perform operations from the ACME server. * * @param account the ACME account information to use (must not be {@code null}) * @param staging whether or not the staging server URL should be used * @return a map of ACME resources to URLs * @throws AcmeException if an error occurs while attempting to get the resource URLs from the ACME server */ public Map<AcmeResource, URL> getResourceUrls(AcmeAccount account, boolean staging) throws AcmeException { Assert.checkNotNullParam("account", account); final Map<AcmeResource, URL> resourceUrls = account.getResourceUrls(staging); if (resourceUrls.isEmpty()) { HttpURLConnection connection = sendGetRequest(account.getServerUrl(staging), HttpURLConnection.HTTP_OK, JSON_CONTENT_TYPE); JsonObject directoryJson = getJsonResponse(connection); try { for (AcmeResource resource : AcmeResource.values()) { String resourceUrl = getOptionalJsonString(directoryJson, resource.getValue()); URL url = resourceUrl != null ? new URL(resourceUrl) : null; resourceUrls.put(resource, url); } } catch (MalformedURLException e) { throw acme.unableToRetrieveAcmeServerDirectoryUrls(e); } } return resourceUrls; }