mirror of https://github.com/shred/acme4j
Permit to globally register additional challenge types
parent
24af42b6b9
commit
2f2e59fd36
|
@ -25,6 +25,7 @@ module org.shredzone.acme4j {
|
||||||
exports org.shredzone.acme4j.toolbox;
|
exports org.shredzone.acme4j.toolbox;
|
||||||
|
|
||||||
uses org.shredzone.acme4j.provider.AcmeProvider;
|
uses org.shredzone.acme4j.provider.AcmeProvider;
|
||||||
|
uses org.shredzone.acme4j.provider.ChallengeProvider;
|
||||||
|
|
||||||
provides org.shredzone.acme4j.provider.AcmeProvider
|
provides org.shredzone.acme4j.provider.AcmeProvider
|
||||||
with org.shredzone.acme4j.provider.GenericAcmeProvider,
|
with org.shredzone.acme4j.provider.GenericAcmeProvider,
|
||||||
|
|
|
@ -56,14 +56,30 @@ public class TokenChallenge extends Challenge {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the authorization string.
|
* Computes the key authorization for the given token.
|
||||||
* <p>
|
* <p>
|
||||||
* The default is {@code token + '.' + base64url(jwkThumbprint)}. Subclasses may
|
* The default is {@code token + '.' + base64url(jwkThumbprint)}. Subclasses may
|
||||||
* override this method if a different algorithm is used.
|
* override this method if a different algorithm is used.
|
||||||
|
*
|
||||||
|
* @param token
|
||||||
|
* Token to be used
|
||||||
|
* @return Key Authorization string for that token
|
||||||
|
* @since 2.12
|
||||||
|
*/
|
||||||
|
protected String keyAuthorizationFor(String token) {
|
||||||
|
PublicKey pk = getLogin().getKeyPair().getPublic();
|
||||||
|
return token + '.' + base64UrlEncode(JoseUtils.thumbprint(pk));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authorization string.
|
||||||
|
* <p>
|
||||||
|
* The default uses {@link #keyAuthorizationFor(String)} to compute the key
|
||||||
|
* authorization of {@link #getToken()}. Subclasses may override this method if a
|
||||||
|
* different algorithm is used.
|
||||||
*/
|
*/
|
||||||
public String getAuthorization() {
|
public String getAuthorization() {
|
||||||
PublicKey pk = getLogin().getKeyPair().getPublic();
|
return keyAuthorizationFor(getToken());
|
||||||
return getToken() + '.' + base64UrlEncode(JoseUtils.thumbprint(pk));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.BiFunction;
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
import org.shredzone.acme4j.Login;
|
import org.shredzone.acme4j.Login;
|
||||||
import org.shredzone.acme4j.Session;
|
import org.shredzone.acme4j.Session;
|
||||||
|
@ -44,7 +44,7 @@ import org.shredzone.acme4j.toolbox.JSON;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractAcmeProvider implements AcmeProvider {
|
public abstract class AbstractAcmeProvider implements AcmeProvider {
|
||||||
|
|
||||||
private static final Map<String, BiFunction<Login, JSON, Challenge>> CHALLENGES = challengeMap();
|
private static final Map<String, ChallengeProvider> CHALLENGES = challengeMap();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Connection connect(URI serverUri) {
|
public Connection connect(URI serverUri) {
|
||||||
|
@ -81,13 +81,35 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Map<String, BiFunction<Login, JSON, Challenge>> challengeMap() {
|
private static Map<String, ChallengeProvider> challengeMap() {
|
||||||
Map<String, BiFunction<Login, JSON, Challenge>> map = new HashMap<>();
|
Map<String, ChallengeProvider> map = new HashMap<>();
|
||||||
|
|
||||||
map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
|
map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
|
||||||
map.put(Http01Challenge.TYPE, Http01Challenge::new);
|
map.put(Http01Challenge.TYPE, Http01Challenge::new);
|
||||||
map.put(TlsAlpn01Challenge.TYPE, TlsAlpn01Challenge::new);
|
map.put(TlsAlpn01Challenge.TYPE, TlsAlpn01Challenge::new);
|
||||||
|
|
||||||
|
for (ChallengeProvider provider : ServiceLoader.load(ChallengeProvider.class)) {
|
||||||
|
ChallengeType typeAnno = provider.getClass().getAnnotation(ChallengeType.class);
|
||||||
|
if (typeAnno == null) {
|
||||||
|
throw new IllegalStateException("ChallengeProvider "
|
||||||
|
+ provider.getClass().getName()
|
||||||
|
+ " has no @ChallengeType annotation");
|
||||||
|
}
|
||||||
|
String type = typeAnno.value();
|
||||||
|
if (type == null || type.trim().isEmpty()) {
|
||||||
|
throw new IllegalStateException("ChallengeProvider "
|
||||||
|
+ provider.getClass().getName()
|
||||||
|
+ ": type must not be null or empty");
|
||||||
|
}
|
||||||
|
if (map.containsKey(type)) {
|
||||||
|
throw new IllegalStateException("ChallengeProvider "
|
||||||
|
+ provider.getClass().getName()
|
||||||
|
+ ": there is already a provider for challenge type "
|
||||||
|
+ type);
|
||||||
|
}
|
||||||
|
map.put(type, provider);
|
||||||
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableMap(map);
|
return Collections.unmodifiableMap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +129,9 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
|
||||||
|
|
||||||
String type = data.get("type").asString();
|
String type = data.get("type").asString();
|
||||||
|
|
||||||
BiFunction<Login, JSON, Challenge> constructor = CHALLENGES.get(type);
|
ChallengeProvider constructor = CHALLENGES.get(type);
|
||||||
if (constructor != null) {
|
if (constructor != null) {
|
||||||
return constructor.apply(login, data);
|
return constructor.create(login, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.contains("token")) {
|
if (data.contains("token")) {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* acme4j - Java ACME client
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 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.provider;
|
||||||
|
|
||||||
|
import org.shredzone.acme4j.Login;
|
||||||
|
import org.shredzone.acme4j.challenge.Challenge;
|
||||||
|
import org.shredzone.acme4j.toolbox.JSON;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A provider that creates a Challenge from a matching JSON.
|
||||||
|
*
|
||||||
|
* @since 2.12
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ChallengeProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Challenge.
|
||||||
|
*
|
||||||
|
* @param login
|
||||||
|
* {@link Login} of the user's account
|
||||||
|
* @param data
|
||||||
|
* {@link JSON} of the challenge as sent by the CA
|
||||||
|
* @return Created and initialized {@link Challenge}. It must match the JSON type.
|
||||||
|
*/
|
||||||
|
Challenge create(Login login, JSON data);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* acme4j - Java ACME client
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 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.provider;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annotates the challenge type that is generated by the {@link ChallengeProvider}.
|
||||||
|
*
|
||||||
|
* @since 2.12
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface ChallengeType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Challenge type.
|
||||||
|
*/
|
||||||
|
String value();
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue