Add a GenericTokenChallenge. Remove boilerplate code.

pull/17/merge
Richard Körber 2015-12-24 16:29:35 +01:00
parent ade0207d6d
commit 9b458fb2b6
6 changed files with 120 additions and 124 deletions

View File

@ -18,15 +18,13 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jose4j.base64url.Base64Url;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.util.ClaimBuilder;
/**
* Implements the {@code dns-01} challenge.
*
* @author Richard "Shred" Körber
*/
public class DnsChallenge extends GenericChallenge {
public class DnsChallenge extends GenericTokenChallenge {
private static final long serialVersionUID = 6964687027713533075L;
/**
@ -34,27 +32,13 @@ public class DnsChallenge extends GenericChallenge {
*/
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
* record.
*/
public String getDigest() {
assertIsAuthorized();
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
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
protected boolean acceptable(String 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;
}
}

View File

@ -50,8 +50,6 @@ public class GenericChallenge implements Challenge {
protected static final String KEY_STATUS = "status";
protected static final String KEY_URI = "uri";
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<>();

View File

@ -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()));
}
}

View File

@ -13,16 +13,13 @@
*/
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.
*
* @author Richard "Shred" Körber
*/
public class HttpChallenge extends GenericChallenge {
public class HttpChallenge extends GenericTokenChallenge {
private static final long serialVersionUID = 3322211185872544605L;
/**
@ -30,27 +27,12 @@ public class HttpChallenge extends GenericChallenge {
*/
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.
*/
@Override
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
* (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
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());
public String getAuthorization() {
return super.getAuthorization();
}
@Override

View File

@ -40,6 +40,9 @@ import org.shredzone.acme4j.util.ValidationBuilder;
public class ProofOfPossessionChallenge extends GenericChallenge {
private static final long serialVersionUID = 6212440828380185335L;
protected static final String KEY_CERTS = "certs";
protected static final String KEY_AUTHORIZATION = "authorization";
/**
* Challenge type name: {@value}
*/
@ -96,7 +99,7 @@ public class ProofOfPossessionChallenge extends GenericChallenge {
public void unmarshall(Map<String, Object> map) {
super.unmarshall(map);
List<String> certData = get("certs");
List<String> certData = get(KEY_CERTS);
if (certData != null) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
@ -123,7 +126,7 @@ public class ProofOfPossessionChallenge extends GenericChallenge {
super.respond(cb);
try {
cb.put("authorization", JsonUtil.parseJson(validation));
cb.put(KEY_AUTHORIZATION, JsonUtil.parseJson(validation));
} catch (JoseException ex) {
// should not happen, as the JSON is prevalidated in the setter
throw new IllegalStateException("validation: invalid JSON", ex);

View File

@ -17,16 +17,14 @@ import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jose4j.base64url.Base64Url;
import org.shredzone.acme4j.Account;
import org.shredzone.acme4j.util.ClaimBuilder;
/**
* Implements the {@code tls-sni-01} challenge.
*
* @author Richard "Shred" Körber
*/
public class TlsSniChallenge extends GenericChallenge {
public class TlsSniChallenge extends GenericTokenChallenge {
private static final long serialVersionUID = 7370329525205430573L;
private static final char[] HEX = "0123456789abcdef".toCharArray();
@ -35,42 +33,21 @@ public class TlsSniChallenge extends GenericChallenge {
*/
public static final String TYPE = "tls-sni-01";
private String authorization = null;
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";
}
private String subject;
/**
* Return the subject to generate a self-signed certificate for.
*/
public String getSubject() {
if (authorization == null) {
throw new IllegalStateException("Challenge is not authorized yet");
}
assertIsAuthorized();
return subject;
}
@Override
public void respond(ClaimBuilder cb) {
super.respond(cb);
cb.put(KEY_TOKEN, getToken());
cb.put(KEY_KEY_AUTHORIZATION, getAuthorization());
public void authorize(Account account) {
super.authorize(account);
String hash = computeHash(getAuthorization());
subject = hash.substring(0, 32) + '.' + hash.substring(32) + ".acme.invalid";
}
@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;
}
}