mirror of https://github.com/shred/acme4j
Get unique identifier according to draft-ietf-acme-ari-03
parent
edb7ec83b6
commit
6a4770c23a
|
@ -16,21 +16,29 @@ package org.shredzone.acme4j.toolbox;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UncheckedIOException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.net.IDN;
|
import java.net.IDN;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import edu.umd.cs.findbugs.annotations.Nullable;
|
import edu.umd.cs.findbugs.annotations.Nullable;
|
||||||
|
import org.bouncycastle.asn1.ASN1Integer;
|
||||||
|
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
|
||||||
|
import org.bouncycastle.asn1.x509.Certificate;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -323,4 +331,57 @@ public final class AcmeUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the certificate's unique identifier for renewal according to
|
||||||
|
* draft-ietf-acme-ari-03.
|
||||||
|
*
|
||||||
|
* @param certificate
|
||||||
|
* Certificate to get the unique identifier for.
|
||||||
|
* @return Unique identifier
|
||||||
|
* @throws AcmeProtocolException
|
||||||
|
* if the certificate is invalid or does not provide the necessary
|
||||||
|
* information.
|
||||||
|
*/
|
||||||
|
public static String getRenewalUniqueIdentifier(X509Certificate certificate) {
|
||||||
|
try {
|
||||||
|
var cert = new X509CertificateHolder(certificate.getEncoded());
|
||||||
|
|
||||||
|
var aki = Optional.of(cert)
|
||||||
|
.map(X509CertificateHolder::getExtensions)
|
||||||
|
.map(AuthorityKeyIdentifier::fromExtensions)
|
||||||
|
.map(AuthorityKeyIdentifier::getKeyIdentifier)
|
||||||
|
.map(AcmeUtils::base64UrlEncode)
|
||||||
|
.orElseThrow(() -> new AcmeProtocolException("Missing or invalid Authority Key Identifier"));
|
||||||
|
|
||||||
|
var sn = Optional.of(cert)
|
||||||
|
.map(X509CertificateHolder::toASN1Structure)
|
||||||
|
.map(Certificate::getSerialNumber)
|
||||||
|
.map(AcmeUtils::getRawInteger)
|
||||||
|
.map(AcmeUtils::base64UrlEncode)
|
||||||
|
.orElseThrow(() -> new AcmeProtocolException("Missing or invalid serial number"));
|
||||||
|
|
||||||
|
return aki + '.' + sn;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new AcmeProtocolException("Invalid certificate", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the raw integer array from ASN1Integer. This is done by encoding it to a byte
|
||||||
|
* array, and then skipping the INTEGER identifier. Other methods of ASN1Integer only
|
||||||
|
* deliver a parsed integer value that might have been mangled.
|
||||||
|
*
|
||||||
|
* @param integer
|
||||||
|
* ASN1Integer to convert to raw
|
||||||
|
* @return Byte array of the raw integer
|
||||||
|
*/
|
||||||
|
private static byte[] getRawInteger(ASN1Integer integer) {
|
||||||
|
try {
|
||||||
|
var encoded = integer.getEncoded();
|
||||||
|
return Arrays.copyOfRange(encoded, 2, encoded.length);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new UncheckedIOException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBQzCB66ADAgECAgUAh2VDITAKBggqhkjOPQQDAjAVMRMwEQYDVQQDEwpFeGFt
|
||||||
|
cGxlIENBMCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMBYxFDAS
|
||||||
|
BgNVBAMTC2V4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeBZu
|
||||||
|
7cbpAYNXZLbbh8rNIzuOoqOOtmxA1v7cRm//AwyMwWxyHz4zfwmBhcSrf47NUAFf
|
||||||
|
qzLQ2PPQxdTXREYEnKMjMCEwHwYDVR0jBBgwFoAUaYhba4dGQEHhs3uEe6CuLN4B
|
||||||
|
yNQwCgYIKoZIzj0EAwIDRwAwRAIge09+S5TZAlw5tgtiVvuERV6cT4mfutXIlwTb
|
||||||
|
+FYN/8oCIClDsqBklhB9KAelFiYt9+6FDj3z4KGVelYM5MdsO3pK
|
||||||
|
-----END CERTIFICATE-----
|
Loading…
Reference in New Issue