Skip to content

Instantly share code, notes, and snippets.

@nyg
Last active April 2, 2024 11:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nyg/feedae9fd75fee0f27f788b2dc155633 to your computer and use it in GitHub Desktop.
Save nyg/feedae9fd75fee0f27f788b2dc155633 to your computer and use it in GitHub Desktop.
List all JCA security provider services and export them to a CSV file.
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.security.Provider;
import java.security.Security;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
/**
* Dependencies: slf4j, log4j2, lombok, bouncycastle
* JVM requires: --add-opens=java.base/java.security=ALL-UNNAMED
*
* https://git.sr.ht/~nyg/example-java-jca
*/
@Slf4j
public class AllServices {
public static void main(String[] args) throws IOException {
Security.addProvider(new BouncyCastleProvider());
List<String> services = Arrays.stream(Security.getProviders())
.map(Provider::getServices)
.flatMap(Collection::stream)
.map(ServiceDescription::new)
.map(ServiceDescription::toCsv)
.sorted()
.toList();
services.forEach(s -> log.info("{}", s));
log.info("{} services found", services.size());
Files.createDirectories(Path.of("output"));
Files.write(Path.of("output", "services.csv"), services, StandardCharsets.UTF_8, StandardOpenOption.CREATE);
}
@Getter
@ToString
public static class ServiceDescription implements Comparable<ServiceDescription> {
private static final Field SERVICE_ALIASES;
private static final Field SERVICE_ATTRIBUTES;
static {
try {
SERVICE_ALIASES = Provider.Service.class.getDeclaredField("aliases");
SERVICE_ALIASES.setAccessible(true);
SERVICE_ATTRIBUTES = Provider.Service.class.getDeclaredField("attributes");
SERVICE_ATTRIBUTES.setAccessible(true);
}
catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
private final String provider;
private final String providerVersion;
private final String type;
private final String algorithm;
private final String className;
private final List<String> aliases;
private final Map<Object, String> attributes;
public ServiceDescription(Provider.Service service) {
this.provider = service.getProvider().getName();
this.providerVersion = service.getProvider().getVersionStr();
this.type = service.getType();
this.algorithm = service.getAlgorithm();
this.className = service.getClassName();
try {
this.aliases = (List<String>) SERVICE_ALIASES.get(service);
this.attributes = (Map<Object, String>) SERVICE_ATTRIBUTES.get(service);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public String toCsv() {
return "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\""
.formatted(type, algorithm, provider, providerVersion, attributes, aliases, className);
}
@Override
public int compareTo(ServiceDescription other) {
return Comparator
.comparing(ServiceDescription::getType, String.CASE_INSENSITIVE_ORDER)
.thenComparing(ServiceDescription::getAlgorithm, String.CASE_INSENSITIVE_ORDER)
.thenComparing(ServiceDescription::getProvider, String.CASE_INSENSITIVE_ORDER)
.thenComparing(ServiceDescription::getProviderVersion, String.CASE_INSENSITIVE_ORDER)
.compare(this, other);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment