mirror of
https://github.com/google/nomulus.git
synced 2025-05-13 16:07:15 +02:00
Daggerize TMCH/signed mark util classes
This allows them to support injectable configuration. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=143709052
This commit is contained in:
parent
534e3ba01c
commit
c05424b947
19 changed files with 242 additions and 165 deletions
|
@ -48,7 +48,12 @@ import org.xml.sax.SAXException;
|
|||
/** TMCH utility functions for domain flows. */
|
||||
public final class DomainFlowTmchUtils {
|
||||
|
||||
@Inject public DomainFlowTmchUtils() {}
|
||||
private final TmchXmlSignature tmchXmlSignature;
|
||||
|
||||
@Inject
|
||||
public DomainFlowTmchUtils(TmchXmlSignature tmchXmlSignature) {
|
||||
this.tmchXmlSignature = tmchXmlSignature;
|
||||
}
|
||||
|
||||
public SignedMark verifySignedMarks(
|
||||
ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, DateTime now)
|
||||
|
@ -86,7 +91,7 @@ public final class DomainFlowTmchUtils {
|
|||
}
|
||||
|
||||
try {
|
||||
TmchXmlSignature.verify(signedMarkData);
|
||||
tmchXmlSignature.verify(signedMarkData);
|
||||
} catch (CertificateExpiredException e) {
|
||||
throw new SignedMarkCertificateExpiredException();
|
||||
} catch (CertificateNotYetValidException e) {
|
||||
|
|
|
@ -37,6 +37,7 @@ public final class TmchCrl extends CrossTldSingleton {
|
|||
|
||||
String crl;
|
||||
DateTime updated;
|
||||
String url;
|
||||
|
||||
/** Returns the singleton instance of this entity, without memoization. */
|
||||
@Nullable
|
||||
|
@ -50,13 +51,14 @@ public final class TmchCrl extends CrossTldSingleton {
|
|||
* <p>Please do not call this function unless your CRL is properly formatted, signed by the root,
|
||||
* and actually newer than the one currently in the datastore.
|
||||
*/
|
||||
public static void set(final String crl) {
|
||||
public static void set(final String crl, final String url) {
|
||||
ofy().transactNew(new VoidWork() {
|
||||
@Override
|
||||
public void vrun() {
|
||||
TmchCrl tmchCrl = new TmchCrl();
|
||||
tmchCrl.updated = ofy().getTransactionTime();
|
||||
tmchCrl.crl = checkNotNull(crl, "crl");
|
||||
tmchCrl.url = checkNotNull(url, "url");
|
||||
ofy().saveWithoutBackup().entity(tmchCrl);
|
||||
}});
|
||||
}
|
||||
|
@ -66,7 +68,12 @@ public final class TmchCrl extends CrossTldSingleton {
|
|||
return crl;
|
||||
}
|
||||
|
||||
/** Time we last updated the datastore with a newer ICANN CRL. */
|
||||
/** Returns the URL that the CRL was downloaded from. */
|
||||
public final String getUrl() {
|
||||
return crl;
|
||||
}
|
||||
|
||||
/** Time we last updated the Datastore with a newer ICANN CRL. */
|
||||
public final DateTime getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
|
|
@ -14,13 +14,13 @@
|
|||
|
||||
package google.registry.tmch;
|
||||
|
||||
import static com.google.common.base.Throwables.propagateIfInstanceOf;
|
||||
import static google.registry.util.CacheUtils.memoizeWithLongExpiration;
|
||||
import static google.registry.util.CacheUtils.memoizeWithShortExpiration;
|
||||
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||
import static google.registry.util.X509Utils.loadCrl;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import google.registry.config.ConfigModule.Config;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.tmch.TmchCrl;
|
||||
import google.registry.util.Clock;
|
||||
|
@ -30,10 +30,18 @@ import google.registry.util.X509Utils;
|
|||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Datastore singleton for ICANN's TMCH root certificate and revocation list. */
|
||||
/**
|
||||
* Helper methods for accessing ICANN's TMCH root certificate and revocation list.
|
||||
*
|
||||
* <p>There are two CRLs, a real one for the production environment and a testing one for
|
||||
* non-production environments. The Datastore singleton {@link TmchCrl} entity is used to cache this
|
||||
* CRL once loaded and will always contain the proper one corresponding to the environment.
|
||||
*/
|
||||
@Immutable
|
||||
@ThreadSafe
|
||||
public final class TmchCertificateAuthority {
|
||||
|
@ -45,45 +53,70 @@ public final class TmchCertificateAuthority {
|
|||
private static final String CRL_FILE = "icann-tmch.crl";
|
||||
private static final String TEST_CRL_FILE = "icann-tmch-test.crl";
|
||||
|
||||
/**
|
||||
* A cached supplier that loads the crl from datastore or chooses a default value.
|
||||
*
|
||||
* <p>We keep the cache here rather than caching TmchCrl in the model, because loading the crl
|
||||
* string into an X509CRL instance is expensive and should itself be cached.
|
||||
*/
|
||||
private static final Supplier<X509CRL> CRL_CACHE =
|
||||
memoizeWithShortExpiration(new Supplier<X509CRL>() {
|
||||
@Override
|
||||
public X509CRL get() {
|
||||
TmchCrl storedCrl = TmchCrl.get();
|
||||
try {
|
||||
X509CRL crl = loadCrl((storedCrl == null)
|
||||
? readResourceUtf8(
|
||||
TmchCertificateAuthority.class,
|
||||
ENVIRONMENT.config().getTmchCaTestingMode() ? TEST_CRL_FILE : CRL_FILE)
|
||||
: storedCrl.getCrl());
|
||||
crl.verify(getRoot().getPublicKey());
|
||||
return crl;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}});
|
||||
private boolean tmchCaTestingMode;
|
||||
|
||||
/** A cached function that loads the crt from a jar resource. */
|
||||
private static final Supplier<X509Certificate> ROOT_CACHE =
|
||||
memoizeWithLongExpiration(new Supplier<X509Certificate>() {
|
||||
@Override
|
||||
public X509Certificate get() {
|
||||
try {
|
||||
X509Certificate root = X509Utils.loadCertificate(readResourceUtf8(
|
||||
TmchCertificateAuthority.class,
|
||||
ENVIRONMENT.config().getTmchCaTestingMode() ? TEST_ROOT_CRT_FILE : ROOT_CRT_FILE));
|
||||
root.checkValidity(clock.nowUtc().toDate());
|
||||
return root;
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}});
|
||||
public @Inject TmchCertificateAuthority(@Config("tmchCaTestingMode") boolean tmchCaTestingMode) {
|
||||
this.tmchCaTestingMode = tmchCaTestingMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* A cached supplier that loads the CRL from Datastore or chooses a default value.
|
||||
*
|
||||
* <p>We keep the cache here rather than caching TmchCrl in the model, because loading the CRL
|
||||
* string into an X509CRL instance is expensive and should itself be cached.
|
||||
*
|
||||
* <p>Note that the stored CRL won't exist for tests, and on deployed environments will always
|
||||
* correspond to the correct CRL for the given testing mode because {@link TmchCrlAction} can
|
||||
* only persist the correct one for this given environment.
|
||||
*/
|
||||
private static final LoadingCache<Boolean, X509CRL> CRL_CACHE =
|
||||
CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(
|
||||
ENVIRONMENT.config().getSingletonCacheRefreshDuration().getMillis(), MILLISECONDS)
|
||||
.build(
|
||||
new CacheLoader<Boolean, X509CRL>() {
|
||||
@Override
|
||||
public X509CRL load(final Boolean tmchCaTestingMode)
|
||||
throws GeneralSecurityException {
|
||||
TmchCrl storedCrl = TmchCrl.get();
|
||||
try {
|
||||
String crlContents;
|
||||
if (storedCrl == null) {
|
||||
String file = tmchCaTestingMode.booleanValue() ? TEST_CRL_FILE : CRL_FILE;
|
||||
crlContents = readResourceUtf8(TmchCertificateAuthority.class, file);
|
||||
} else {
|
||||
crlContents = storedCrl.getCrl();
|
||||
}
|
||||
X509CRL crl = X509Utils.loadCrl(crlContents);
|
||||
crl.verify(ROOT_CACHE.get(tmchCaTestingMode).getPublicKey());
|
||||
return crl;
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof GeneralSecurityException) {
|
||||
throw (GeneralSecurityException) e.getCause();
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected exception while loading CRL", e);
|
||||
}
|
||||
}
|
||||
}});
|
||||
|
||||
/** A cached function that loads the CRT from a jar resource. */
|
||||
private static final LoadingCache<Boolean, X509Certificate> ROOT_CACHE =
|
||||
CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(
|
||||
ENVIRONMENT.config().getSingletonCachePersistDuration().getMillis(), MILLISECONDS)
|
||||
.build(
|
||||
new CacheLoader<Boolean, X509Certificate>() {
|
||||
@Override
|
||||
public X509Certificate load(final Boolean tmchCaTestingMode)
|
||||
throws GeneralSecurityException {
|
||||
String file =
|
||||
tmchCaTestingMode.booleanValue() ? TEST_ROOT_CRT_FILE : ROOT_CRT_FILE;
|
||||
X509Certificate root =
|
||||
X509Utils.loadCertificate(
|
||||
readResourceUtf8(TmchCertificateAuthority.class, file));
|
||||
root.checkValidity(clock.nowUtc().toDate());
|
||||
return root;
|
||||
}});
|
||||
|
||||
@NonFinalForTesting
|
||||
private static Clock clock = new SystemClock();
|
||||
|
@ -97,14 +130,14 @@ public final class TmchCertificateAuthority {
|
|||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* @see X509Utils#verifyCertificate
|
||||
*/
|
||||
public static void verify(X509Certificate cert) throws GeneralSecurityException {
|
||||
public void verify(X509Certificate cert) throws GeneralSecurityException {
|
||||
synchronized (TmchCertificateAuthority.class) {
|
||||
X509Utils.verifyCertificate(getRoot(), getCrl(), cert, clock.nowUtc().toDate());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update to the latest TMCH X.509 certificate revocation list and save to the datastore.
|
||||
* Update to the latest TMCH X.509 certificate revocation list and save it to Datastore.
|
||||
*
|
||||
* <p>Your ASCII-armored CRL must be signed by the current ICANN root certificate.
|
||||
*
|
||||
|
@ -115,27 +148,35 @@ public final class TmchCertificateAuthority {
|
|||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* @see X509Utils#verifyCrl
|
||||
*/
|
||||
public static void updateCrl(String asciiCrl) throws GeneralSecurityException {
|
||||
public void updateCrl(String asciiCrl, String url) throws GeneralSecurityException {
|
||||
X509CRL crl = X509Utils.loadCrl(asciiCrl);
|
||||
X509Utils.verifyCrl(getRoot(), getCrl(), crl, clock.nowUtc().toDate());
|
||||
TmchCrl.set(asciiCrl);
|
||||
TmchCrl.set(asciiCrl, url);
|
||||
}
|
||||
|
||||
public static X509Certificate getRoot() throws GeneralSecurityException {
|
||||
public X509Certificate getRoot() throws GeneralSecurityException {
|
||||
try {
|
||||
return ROOT_CACHE.get();
|
||||
} catch (RuntimeException e) {
|
||||
propagateIfInstanceOf(e.getCause(), GeneralSecurityException.class);
|
||||
throw e;
|
||||
return ROOT_CACHE.get(tmchCaTestingMode);
|
||||
} catch (Exception e) {
|
||||
if (e.getCause() instanceof GeneralSecurityException) {
|
||||
throw (GeneralSecurityException) e.getCause();
|
||||
} else if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static X509CRL getCrl() throws GeneralSecurityException {
|
||||
public X509CRL getCrl() throws GeneralSecurityException {
|
||||
try {
|
||||
return CRL_CACHE.get();
|
||||
} catch (RuntimeException e) {
|
||||
propagateIfInstanceOf(e.getCause(), GeneralSecurityException.class);
|
||||
throw e;
|
||||
return CRL_CACHE.get(tmchCaTestingMode);
|
||||
} catch (Exception e) {
|
||||
if (e.getCause() instanceof GeneralSecurityException) {
|
||||
throw (GeneralSecurityException) e.getCause();
|
||||
} else if (e instanceof RuntimeException) {
|
||||
throw (RuntimeException) e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,14 +31,16 @@ public final class TmchCrlAction implements Runnable {
|
|||
|
||||
@Inject Marksdb marksdb;
|
||||
@Inject @Config("tmchCrlUrl") URL tmchCrlUrl;
|
||||
@Inject TmchCertificateAuthority tmchCertificateAuthority;
|
||||
@Inject TmchCrlAction() {}
|
||||
|
||||
/** Synchronously fetches latest ICANN TMCH CRL and saves it to datastore. */
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
TmchCertificateAuthority
|
||||
.updateCrl(new String(marksdb.fetch(tmchCrlUrl, Optional.<String>absent()), UTF_8));
|
||||
tmchCertificateAuthority.updateCrl(
|
||||
new String(marksdb.fetch(tmchCrlUrl, Optional.<String>absent()), UTF_8),
|
||||
tmchCrlUrl.toString());
|
||||
} catch (IOException | GeneralSecurityException e) {
|
||||
throw new RuntimeException("Failed to update ICANN TMCH CRL.", e);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import static com.google.common.base.Throwables.getRootCause;
|
|||
import static com.google.common.base.Throwables.propagateIfInstanceOf;
|
||||
import static google.registry.xml.XmlTransformer.loadXmlSchemas;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
@ -32,6 +33,7 @@ import java.security.cert.X509Certificate;
|
|||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.crypto.AlgorithmMethod;
|
||||
import javax.xml.crypto.KeySelector;
|
||||
import javax.xml.crypto.KeySelectorException;
|
||||
|
@ -56,6 +58,14 @@ import org.xml.sax.SAXException;
|
|||
@ThreadSafe
|
||||
public final class TmchXmlSignature {
|
||||
|
||||
@VisibleForTesting
|
||||
final TmchCertificateAuthority tmchCertificateAuthority;
|
||||
|
||||
@Inject
|
||||
public TmchXmlSignature(TmchCertificateAuthority tmchCertificateAuthority) {
|
||||
this.tmchCertificateAuthority = tmchCertificateAuthority;
|
||||
}
|
||||
|
||||
private static final Schema SCHEMA =
|
||||
loadXmlSchemas(ImmutableList.of("mark.xsd", "dsig.xsd", "smd.xsd"));
|
||||
|
||||
|
@ -66,19 +76,15 @@ public final class TmchXmlSignature {
|
|||
* cryptographic stuff.
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* @throws IOException
|
||||
* @throws MarshalException
|
||||
* @throws ParserConfigurationException
|
||||
* @throws SAXException
|
||||
*/
|
||||
public static void verify(byte[] smdXml)
|
||||
throws GeneralSecurityException,
|
||||
IOException,
|
||||
MarshalException,
|
||||
ParserConfigurationException,
|
||||
SAXException,
|
||||
XMLSignatureException {
|
||||
public void verify(byte[] smdXml)
|
||||
throws GeneralSecurityException, IOException, MarshalException, ParserConfigurationException,
|
||||
SAXException, XMLSignatureException {
|
||||
checkArgument(smdXml.length > 0);
|
||||
Document doc = parseSmdDocument(new ByteArrayInputStream(smdXml));
|
||||
|
||||
|
@ -87,7 +93,7 @@ public final class TmchXmlSignature {
|
|||
throw new XMLSignatureException("Expected exactly one <ds:Signature> element.");
|
||||
}
|
||||
XMLSignatureFactory factory = XMLSignatureFactory.getInstance("DOM");
|
||||
KeyValueKeySelector selector = new KeyValueKeySelector();
|
||||
KeyValueKeySelector selector = new KeyValueKeySelector(tmchCertificateAuthority);
|
||||
DOMValidateContext context = new DOMValidateContext(selector, signatureNodes.item(0));
|
||||
XMLSignature signature = factory.unmarshalXMLSignature(context);
|
||||
|
||||
|
@ -134,13 +140,21 @@ public final class TmchXmlSignature {
|
|||
|
||||
/** Callback class for DOM validator checks validity of {@code <ds:KeyInfo>} elements. */
|
||||
private static final class KeyValueKeySelector extends KeySelector {
|
||||
|
||||
private final TmchCertificateAuthority tmchCertificateAuthority;
|
||||
|
||||
KeyValueKeySelector(TmchCertificateAuthority tmchCertificateAuthority) {
|
||||
this.tmchCertificateAuthority = tmchCertificateAuthority;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public KeySelectorResult select(
|
||||
@Nullable KeyInfo keyInfo,
|
||||
KeySelector.Purpose purpose,
|
||||
AlgorithmMethod method,
|
||||
XMLCryptoContext context) throws KeySelectorException {
|
||||
XMLCryptoContext context)
|
||||
throws KeySelectorException {
|
||||
if (keyInfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -151,7 +165,7 @@ public final class TmchXmlSignature {
|
|||
if (x509DataChild instanceof X509Certificate) {
|
||||
X509Certificate cert = (X509Certificate) x509DataChild;
|
||||
try {
|
||||
TmchCertificateAuthority.verify(cert);
|
||||
tmchCertificateAuthority.verify(cert);
|
||||
} catch (SignatureException e) {
|
||||
throw new KeySelectorException(new CertificateSignatureException(e.getMessage()));
|
||||
} catch (GeneralSecurityException e) {
|
||||
|
|
|
@ -71,8 +71,8 @@ final class GenerateApplicationsReportCommand implements RemoteApiCommand {
|
|||
validateWith = PathParameter.OutputFile.class)
|
||||
private Path output = Paths.get("/dev/stdout");
|
||||
|
||||
@Inject
|
||||
Clock clock;
|
||||
@Inject Clock clock;
|
||||
@Inject TmchXmlSignature tmchXmlSignature;
|
||||
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
|
@ -143,7 +143,7 @@ final class GenerateApplicationsReportCommand implements RemoteApiCommand {
|
|||
}
|
||||
|
||||
try {
|
||||
TmchXmlSignature.verify(signedMarkData);
|
||||
tmchXmlSignature.verify(signedMarkData);
|
||||
} catch (Exception e) {
|
||||
return Optional.of(
|
||||
makeLine(domainApplication, String.format("Invalid SMD (%s)", e.getMessage())));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue