Skip to content

Commit

Permalink
Add a GenericTokenChallenge. Remove boilerplate code.
Browse files Browse the repository at this point in the history
  • Loading branch information
shred committed Dec 24, 2015
1 parent ade0207 commit 9b458fb
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,27 @@
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;

/**
* Challenge type name: {@value}
*/
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"));
Expand All @@ -66,32 +50,9 @@ public String getDigest() {
}
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<>();

Expand Down
Original file line number Diff line number Diff line change
@@ -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()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,44 +13,26 @@
*/
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;

/**
* Challenge type name: {@value}
*/
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();
}

/**
Expand All @@ -60,22 +42,9 @@ public String getToken() {
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
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}
*/
Expand Down Expand Up @@ -96,7 +99,7 @@ public void importValidation(String validation) {
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");
Expand All @@ -123,7 +126,7 @@ public void respond(ClaimBuilder cb) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,14 @@
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();

Expand All @@ -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
Expand Down Expand Up @@ -103,16 +80,4 @@ private String computeHash(String z) {
}
}

private String getToken() {
return get(KEY_TOKEN);
}

private String getAuthorization() {
if (authorization == null) {
throw new IllegalStateException("Challenge is not authorized yet");
}

return authorization;
}

}

0 comments on commit 9b458fb

Please sign in to comment.