mirror of https://github.com/shred/acme4j
Add a GenericTokenChallenge. Remove boilerplate code.
parent
ade0207d6d
commit
9b458fb2b6
|
@ -18,15 +18,13 @@ import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.jose4j.base64url.Base64Url;
|
import org.jose4j.base64url.Base64Url;
|
||||||
import org.shredzone.acme4j.Account;
|
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the {@code dns-01} challenge.
|
* Implements the {@code dns-01} challenge.
|
||||||
*
|
*
|
||||||
* @author Richard "Shred" Körber
|
* @author Richard "Shred" Körber
|
||||||
*/
|
*/
|
||||||
public class DnsChallenge extends GenericChallenge {
|
public class DnsChallenge extends GenericTokenChallenge {
|
||||||
private static final long serialVersionUID = 6964687027713533075L;
|
private static final long serialVersionUID = 6964687027713533075L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,27 +32,13 @@ public class DnsChallenge extends GenericChallenge {
|
||||||
*/
|
*/
|
||||||
public static final String TYPE = "dns-01";
|
public static final String TYPE = "dns-01";
|
||||||
|
|
||||||
private String authorization = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes the {@link Challenge} by signing it with an {@link Account}.
|
|
||||||
*
|
|
||||||
* @param account
|
|
||||||
* {@link Account} to sign the challenge with
|
|
||||||
*/
|
|
||||||
public void authorize(Account account) {
|
|
||||||
if (account == null) {
|
|
||||||
throw new NullPointerException("account must not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
authorization = getToken() + '.' + Base64Url.encode(jwkThumbprint(account.getKeyPair().getPublic()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the digest string to be set in the domain's {@code _acme-challenge} TXT
|
* Returns the digest string to be set in the domain's {@code _acme-challenge} TXT
|
||||||
* record.
|
* record.
|
||||||
*/
|
*/
|
||||||
public String getDigest() {
|
public String getDigest() {
|
||||||
|
assertIsAuthorized();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
md.update(getAuthorization().getBytes("UTF-8"));
|
md.update(getAuthorization().getBytes("UTF-8"));
|
||||||
|
@ -66,32 +50,9 @@ public class DnsChallenge extends GenericChallenge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void respond(ClaimBuilder cb) {
|
|
||||||
if (authorization == null) {
|
|
||||||
throw new IllegalStateException("Challenge is not authorized yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
super.respond(cb);
|
|
||||||
cb.put(KEY_TOKEN, getToken());
|
|
||||||
cb.put(KEY_KEY_AUTHORIZATION, getAuthorization());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean acceptable(String type) {
|
protected boolean acceptable(String type) {
|
||||||
return TYPE.equals(type);
|
return TYPE.equals(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getToken() {
|
|
||||||
return get(KEY_TOKEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAuthorization() {
|
|
||||||
if (authorization == null) {
|
|
||||||
throw new IllegalStateException("Challenge is not authorized yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorization;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,6 @@ public class GenericChallenge implements Challenge {
|
||||||
protected static final String KEY_STATUS = "status";
|
protected static final String KEY_STATUS = "status";
|
||||||
protected static final String KEY_URI = "uri";
|
protected static final String KEY_URI = "uri";
|
||||||
protected static final String KEY_VALIDATED = "validated";
|
protected static final String KEY_VALIDATED = "validated";
|
||||||
protected static final String KEY_TOKEN = "token";
|
|
||||||
protected static final String KEY_KEY_AUTHORIZATION = "keyAuthorization";
|
|
||||||
|
|
||||||
private transient Map<String, Object> data = new HashMap<>();
|
private transient Map<String, Object> data = new HashMap<>();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* 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.challenge;
|
||||||
|
|
||||||
|
import org.jose4j.base64url.Base64Url;
|
||||||
|
import org.shredzone.acme4j.Account;
|
||||||
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extension of {@link GenericChallenge} that handles challenges with a {@code token}
|
||||||
|
* and {@code keyAuthorization}.
|
||||||
|
*
|
||||||
|
* @author Richard "Shred" Körber
|
||||||
|
*/
|
||||||
|
public class GenericTokenChallenge extends GenericChallenge {
|
||||||
|
private static final long serialVersionUID = 1634133407432681800L;
|
||||||
|
|
||||||
|
protected static final String KEY_TOKEN = "token";
|
||||||
|
protected static final String KEY_KEY_AUTHORIZATION = "keyAuthorization";
|
||||||
|
|
||||||
|
private String authorization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorizes the {@link Challenge} by signing it with an {@link Account}.
|
||||||
|
*
|
||||||
|
* @param account
|
||||||
|
* {@link Account} to sign the challenge with
|
||||||
|
*/
|
||||||
|
public void authorize(Account account) {
|
||||||
|
if (account == null) {
|
||||||
|
throw new NullPointerException("account must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
authorization = computeAuthorization(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void respond(ClaimBuilder cb) {
|
||||||
|
assertIsAuthorized();
|
||||||
|
|
||||||
|
super.respond(cb);
|
||||||
|
cb.put(KEY_TOKEN, getToken());
|
||||||
|
cb.put(KEY_KEY_AUTHORIZATION, getAuthorization());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the challenge was authorized.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException
|
||||||
|
* if {@link #authorize(Account)} was not invoked.
|
||||||
|
*/
|
||||||
|
protected void assertIsAuthorized() {
|
||||||
|
if (authorization == null) {
|
||||||
|
throw new IllegalStateException("Challenge is not authorized yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the token.
|
||||||
|
*/
|
||||||
|
protected String getToken() {
|
||||||
|
return get(KEY_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the authorization after {@link #authorize(Account)} was invoked.
|
||||||
|
*/
|
||||||
|
protected String getAuthorization() {
|
||||||
|
assertIsAuthorized();
|
||||||
|
return authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the authorization string.
|
||||||
|
* <p>
|
||||||
|
* 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
|
||||||
|
* @return Authorization string
|
||||||
|
*/
|
||||||
|
protected String computeAuthorization(Account account) {
|
||||||
|
return getToken()
|
||||||
|
+ '.'
|
||||||
|
+ Base64Url.encode(jwkThumbprint(account.getKeyPair().getPublic()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,16 +13,13 @@
|
||||||
*/
|
*/
|
||||||
package org.shredzone.acme4j.challenge;
|
package org.shredzone.acme4j.challenge;
|
||||||
|
|
||||||
import org.jose4j.base64url.Base64Url;
|
|
||||||
import org.shredzone.acme4j.Account;
|
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the {@code http-01} challenge.
|
* Implements the {@code http-01} challenge.
|
||||||
*
|
*
|
||||||
* @author Richard "Shred" Körber
|
* @author Richard "Shred" Körber
|
||||||
*/
|
*/
|
||||||
public class HttpChallenge extends GenericChallenge {
|
public class HttpChallenge extends GenericTokenChallenge {
|
||||||
private static final long serialVersionUID = 3322211185872544605L;
|
private static final long serialVersionUID = 3322211185872544605L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,27 +27,12 @@ public class HttpChallenge extends GenericChallenge {
|
||||||
*/
|
*/
|
||||||
public static final String TYPE = "http-01";
|
public static final String TYPE = "http-01";
|
||||||
|
|
||||||
private String authorization = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes the {@link Challenge} by signing it with an {@link Account}.
|
|
||||||
*
|
|
||||||
* @param account
|
|
||||||
* {@link Account} to sign the challenge with
|
|
||||||
*/
|
|
||||||
public void authorize(Account account) {
|
|
||||||
if (account == null) {
|
|
||||||
throw new NullPointerException("account must not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
authorization = getToken() + '.' + Base64Url.encode(jwkThumbprint(account.getKeyPair().getPublic()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the token to be used for this challenge.
|
* Returns the token to be used for this challenge.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String getToken() {
|
public String getToken() {
|
||||||
return get(KEY_TOKEN);
|
return super.getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,22 +42,9 @@ public class HttpChallenge extends GenericChallenge {
|
||||||
* or ASCII encoded). There must not be any other leading or trailing characters
|
* or ASCII encoded). There must not be any other leading or trailing characters
|
||||||
* (like white-spaces or line breaks). Otherwise the challenge will fail.
|
* (like white-spaces or line breaks). Otherwise the challenge will fail.
|
||||||
*/
|
*/
|
||||||
public String getAuthorization() {
|
|
||||||
if (authorization == null) {
|
|
||||||
throw new IllegalStateException("Challenge is not authorized yet");
|
|
||||||
}
|
|
||||||
return authorization;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void respond(ClaimBuilder cb) {
|
public String getAuthorization() {
|
||||||
if (authorization == null) {
|
return super.getAuthorization();
|
||||||
throw new IllegalStateException("Challenge is not authorized yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
super.respond(cb);
|
|
||||||
cb.put(KEY_TOKEN, getToken());
|
|
||||||
cb.put(KEY_KEY_AUTHORIZATION, getAuthorization());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -40,6 +40,9 @@ import org.shredzone.acme4j.util.ValidationBuilder;
|
||||||
public class ProofOfPossessionChallenge extends GenericChallenge {
|
public class ProofOfPossessionChallenge extends GenericChallenge {
|
||||||
private static final long serialVersionUID = 6212440828380185335L;
|
private static final long serialVersionUID = 6212440828380185335L;
|
||||||
|
|
||||||
|
protected static final String KEY_CERTS = "certs";
|
||||||
|
protected static final String KEY_AUTHORIZATION = "authorization";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Challenge type name: {@value}
|
* Challenge type name: {@value}
|
||||||
*/
|
*/
|
||||||
|
@ -96,7 +99,7 @@ public class ProofOfPossessionChallenge extends GenericChallenge {
|
||||||
public void unmarshall(Map<String, Object> map) {
|
public void unmarshall(Map<String, Object> map) {
|
||||||
super.unmarshall(map);
|
super.unmarshall(map);
|
||||||
|
|
||||||
List<String> certData = get("certs");
|
List<String> certData = get(KEY_CERTS);
|
||||||
if (certData != null) {
|
if (certData != null) {
|
||||||
try {
|
try {
|
||||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||||
|
@ -123,7 +126,7 @@ public class ProofOfPossessionChallenge extends GenericChallenge {
|
||||||
super.respond(cb);
|
super.respond(cb);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cb.put("authorization", JsonUtil.parseJson(validation));
|
cb.put(KEY_AUTHORIZATION, JsonUtil.parseJson(validation));
|
||||||
} catch (JoseException ex) {
|
} catch (JoseException ex) {
|
||||||
// should not happen, as the JSON is prevalidated in the setter
|
// should not happen, as the JSON is prevalidated in the setter
|
||||||
throw new IllegalStateException("validation: invalid JSON", ex);
|
throw new IllegalStateException("validation: invalid JSON", ex);
|
||||||
|
|
|
@ -17,16 +17,14 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import org.jose4j.base64url.Base64Url;
|
|
||||||
import org.shredzone.acme4j.Account;
|
import org.shredzone.acme4j.Account;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the {@code tls-sni-01} challenge.
|
* Implements the {@code tls-sni-01} challenge.
|
||||||
*
|
*
|
||||||
* @author Richard "Shred" Körber
|
* @author Richard "Shred" Körber
|
||||||
*/
|
*/
|
||||||
public class TlsSniChallenge extends GenericChallenge {
|
public class TlsSniChallenge extends GenericTokenChallenge {
|
||||||
private static final long serialVersionUID = 7370329525205430573L;
|
private static final long serialVersionUID = 7370329525205430573L;
|
||||||
private static final char[] HEX = "0123456789abcdef".toCharArray();
|
private static final char[] HEX = "0123456789abcdef".toCharArray();
|
||||||
|
|
||||||
|
@ -35,42 +33,21 @@ public class TlsSniChallenge extends GenericChallenge {
|
||||||
*/
|
*/
|
||||||
public static final String TYPE = "tls-sni-01";
|
public static final String TYPE = "tls-sni-01";
|
||||||
|
|
||||||
private String authorization = null;
|
private String subject;
|
||||||
private String subject = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes the {@link Challenge} by signing it with an {@link Account}.
|
|
||||||
*
|
|
||||||
* @param account
|
|
||||||
* {@link Account} to sign the challenge with
|
|
||||||
*/
|
|
||||||
public void authorize(Account account) {
|
|
||||||
if (account == null) {
|
|
||||||
throw new NullPointerException("account must not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
authorization = getToken() + '.' + Base64Url.encode(jwkThumbprint(account.getKeyPair().getPublic()));
|
|
||||||
|
|
||||||
String hash = computeHash(authorization);
|
|
||||||
subject = hash.substring(0, 32) + '.' + hash.substring(32) + ".acme.invalid";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the subject to generate a self-signed certificate for.
|
* Return the subject to generate a self-signed certificate for.
|
||||||
*/
|
*/
|
||||||
public String getSubject() {
|
public String getSubject() {
|
||||||
if (authorization == null) {
|
assertIsAuthorized();
|
||||||
throw new IllegalStateException("Challenge is not authorized yet");
|
|
||||||
}
|
|
||||||
return subject;
|
return subject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void respond(ClaimBuilder cb) {
|
public void authorize(Account account) {
|
||||||
super.respond(cb);
|
super.authorize(account);
|
||||||
cb.put(KEY_TOKEN, getToken());
|
String hash = computeHash(getAuthorization());
|
||||||
cb.put(KEY_KEY_AUTHORIZATION, getAuthorization());
|
subject = hash.substring(0, 32) + '.' + hash.substring(32) + ".acme.invalid";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -103,16 +80,4 @@ public class TlsSniChallenge extends GenericChallenge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getToken() {
|
|
||||||
return get(KEY_TOKEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAuthorization() {
|
|
||||||
if (authorization == null) {
|
|
||||||
throw new IllegalStateException("Challenge is not authorized yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
return authorization;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue