Lazily generate san string for certificates

The san string used by certificate generation for ssl tests currently
runs at gradle configuration time. This takes several seconds, and
significantly slows down gradle configuration on every invocation.
This change wraps the code into a lazy evaluator that will be invoked at
runtime, and cache the string once it is generated.

Original commit: elastic/x-pack-elasticsearch@812036f416
This commit is contained in:
Ryan Ernst 2016-03-14 17:22:05 -07:00
parent 7d377a5b7f
commit 1b21f20e5b
1 changed files with 138 additions and 123 deletions

View File

@ -8,7 +8,7 @@ dependencies {
} }
// needed to be consistent with ssl host checking // needed to be consistent with ssl host checking
String san = getSubjectAlternativeNameString() Object san = new SanEvaluator()
// location of generated keystores and certificates // location of generated keystores and certificates
File keystoreDir = new File(project.buildDir, 'keystore') File keystoreDir = new File(project.buildDir, 'keystore')
@ -200,144 +200,159 @@ processTestResources {
} }
} }
/** A lazy evaluator to find the san to use for certificate generation. */
class SanEvaluator {
// Code stolen from NetworkUtils/InetAddresses/NetworkAddress to support SAN private static String san = null
/** Return all interfaces (and subinterfaces) on the system */
static List<NetworkInterface> getInterfaces() throws SocketException { String toString() {
List<NetworkInterface> all = new ArrayList<>(); synchronized (SanEvaluator.class) {
addAllInterfaces(all, Collections.list(NetworkInterface.getNetworkInterfaces())); if (san == null) {
Collections.sort(all, new Comparator<NetworkInterface>() { san = getSubjectAlternativeNameString()
@Override }
public int compare(NetworkInterface left, NetworkInterface right) {
return Integer.compare(left.getIndex(), right.getIndex());
} }
}); return san
return all; }
}
/** Helper for getInterfaces, recursively adds subinterfaces to {@code target} */ // Code stolen from NetworkUtils/InetAddresses/NetworkAddress to support SAN
private static void addAllInterfaces(List<NetworkInterface> target, List<NetworkInterface> level) { /** Return all interfaces (and subinterfaces) on the system */
if (!level.isEmpty()) { private static List<NetworkInterface> getInterfaces() throws SocketException {
target.addAll(level); List<NetworkInterface> all = new ArrayList<>();
for (NetworkInterface intf : level) { addAllInterfaces(all, Collections.list(NetworkInterface.getNetworkInterfaces()));
addAllInterfaces(target, Collections.list(intf.getSubInterfaces())); Collections.sort(all, new Comparator<NetworkInterface>() {
@Override
public int compare(NetworkInterface left, NetworkInterface right) {
return Integer.compare(left.getIndex(), right.getIndex());
}
});
return all;
}
/** Helper for getInterfaces, recursively adds subinterfaces to {@code target} */
private static void addAllInterfaces(List<NetworkInterface> target, List<NetworkInterface> level) {
if (!level.isEmpty()) {
target.addAll(level);
for (NetworkInterface intf : level) {
addAllInterfaces(target, Collections.list(intf.getSubInterfaces()));
}
} }
} }
}
private static String getSubjectAlternativeNameString() { private static String getSubjectAlternativeNameString() {
List<InetAddress> list = new ArrayList<>(); List<InetAddress> list = new ArrayList<>();
for (NetworkInterface intf : getInterfaces()) { for (NetworkInterface intf : getInterfaces()) {
if (intf.isUp()) { if (intf.isUp()) {
// NOTE: some operating systems (e.g. BSD stack) assign a link local address to the loopback interface // NOTE: some operating systems (e.g. BSD stack) assign a link local address to the loopback interface
// while technically not a loopback address, some of these treat them as one (e.g. OS X "localhost") so we must too, // while technically not a loopback address, some of these treat them as one (e.g. OS X "localhost") so we must too,
// otherwise things just won't work out of box. So we include all addresses from loopback interfaces. // otherwise things just won't work out of box. So we include all addresses from loopback interfaces.
for (InetAddress address : Collections.list(intf.getInetAddresses())) { for (InetAddress address : Collections.list(intf.getInetAddresses())) {
if (intf.isLoopback() || address.isLoopbackAddress()) { if (intf.isLoopback() || address.isLoopbackAddress()) {
list.add(address); list.add(address);
}
} }
} }
} }
} if (list.isEmpty()) {
if (list.isEmpty()) { throw new IllegalArgumentException("no up-and-running loopback addresses found, got " + getInterfaces());
throw new IllegalArgumentException("no up-and-running loopback addresses found, got " + getInterfaces());
}
StringBuilder builder = new StringBuilder("san=");
for (int i = 0; i < list.size(); i++) {
InetAddress address = list.get(i);
String hostAddress;
if (address instanceof Inet6Address) {
hostAddress = compressedIPV6Address((Inet6Address)address);
} else {
hostAddress = address.getHostAddress();
}
builder.append("ip:").append(hostAddress);
String hostname = address.getHostName();
if (hostname.equals(address.getHostAddress()) == false) {
builder.append(",dns:").append(hostname);
} }
if (i != (list.size() - 1)) { StringBuilder builder = new StringBuilder("san=");
builder.append(","); for (int i = 0; i < list.size(); i++) {
} InetAddress address = list.get(i);
} String hostAddress;
if (address instanceof Inet6Address) {
return builder.toString(); hostAddress = compressedIPV6Address((Inet6Address)address);
} } else {
hostAddress = address.getHostAddress();
private static String compressedIPV6Address(Inet6Address inet6Address) {
byte[] bytes = inet6Address.getAddress();
int[] hextets = new int[8];
for (int i = 0; i < hextets.length; i++) {
hextets[i] = (bytes[2 * i] & 255) << 8 | bytes[2 * i + 1] & 255;
}
compressLongestRunOfZeroes(hextets);
return hextetsToIPv6String(hextets);
}
/**
* Identify and mark the longest run of zeroes in an IPv6 address.
*
* <p>Only runs of two or more hextets are considered. In case of a tie, the
* leftmost run wins. If a qualifying run is found, its hextets are replaced
* by the sentinel value -1.
*
* @param hextets {@code int[]} mutable array of eight 16-bit hextets
*/
private static void compressLongestRunOfZeroes(int[] hextets) {
int bestRunStart = -1;
int bestRunLength = -1;
int runStart = -1;
for (int i = 0; i < hextets.length + 1; i++) {
if (i < hextets.length && hextets[i] == 0) {
if (runStart < 0) {
runStart = i;
} }
} else if (runStart >= 0) { builder.append("ip:").append(hostAddress);
int runLength = i - runStart; String hostname = address.getHostName();
if (runLength > bestRunLength) { if (hostname.equals(address.getHostAddress()) == false) {
bestRunStart = runStart; builder.append(",dns:").append(hostname);
bestRunLength = runLength;
} }
runStart = -1;
}
}
if (bestRunLength >= 2) {
Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1);
}
}
/** if (i != (list.size() - 1)) {
* Convert a list of hextets into a human-readable IPv6 address. builder.append(",");
* }
* <p>In order for "::" compression to work, the input should contain negative }
* sentinel values in place of the elided zeroes.
* return builder.toString();
* @param hextets {@code int[]} array of eight 16-bit hextets, or -1s }
*/
private static String hextetsToIPv6String(int[] hextets) { private static String compressedIPV6Address(Inet6Address inet6Address) {
/* byte[] bytes = inet6Address.getAddress();
* While scanning the array, handle these state transitions: int[] hextets = new int[8];
* start->num => "num" start->gap => "::" for (int i = 0; i < hextets.length; i++) {
* num->num => ":num" num->gap => "::" hextets[i] = (bytes[2 * i] & 255) << 8 | bytes[2 * i + 1] & 255;
* gap->num => "num" gap->gap => "" }
compressLongestRunOfZeroes(hextets);
return hextetsToIPv6String(hextets);
}
/**
* Identify and mark the longest run of zeroes in an IPv6 address.
*
* <p>Only runs of two or more hextets are considered. In case of a tie, the
* leftmost run wins. If a qualifying run is found, its hextets are replaced
* by the sentinel value -1.
*
* @param hextets {@code int[]} mutable array of eight 16-bit hextets
*/ */
StringBuilder buf = new StringBuilder(39); private static void compressLongestRunOfZeroes(int[] hextets) {
boolean lastWasNumber = false; int bestRunStart = -1;
for (int i = 0; i < hextets.length; i++) { int bestRunLength = -1;
boolean thisIsNumber = hextets[i] >= 0; int runStart = -1;
if (thisIsNumber) { for (int i = 0; i < hextets.length + 1; i++) {
if (lastWasNumber) { if (i < hextets.length && hextets[i] == 0) {
buf.append(':'); if (runStart < 0) {
} runStart = i;
buf.append(Integer.toHexString(hextets[i])); }
} else { } else if (runStart >= 0) {
if (i == 0 || lastWasNumber) { int runLength = i - runStart;
buf.append("::"); if (runLength > bestRunLength) {
bestRunStart = runStart;
bestRunLength = runLength;
}
runStart = -1;
} }
} }
lastWasNumber = thisIsNumber; if (bestRunLength >= 2) {
Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1);
}
}
/**
* Convert a list of hextets into a human-readable IPv6 address.
*
* <p>In order for "::" compression to work, the input should contain negative
* sentinel values in place of the elided zeroes.
*
* @param hextets {@code int[]} array of eight 16-bit hextets, or -1s
*/
private static String hextetsToIPv6String(int[] hextets) {
/*
* While scanning the array, handle these state transitions:
* start->num => "num" start->gap => "::"
* num->num => ":num" num->gap => "::"
* gap->num => "num" gap->gap => ""
*/
StringBuilder buf = new StringBuilder(39);
boolean lastWasNumber = false;
for (int i = 0; i < hextets.length; i++) {
boolean thisIsNumber = hextets[i] >= 0;
if (thisIsNumber) {
if (lastWasNumber) {
buf.append(':');
}
buf.append(Integer.toHexString(hextets[i]));
} else {
if (i == 0 || lastWasNumber) {
buf.append("::");
}
}
lastWasNumber = thisIsNumber;
}
return buf.toString();
} }
return buf.toString();
} }