#!/bin/bash # Usage: # ./create-truststore.sh [CA_CERT [TRUSTSTORE [ALIAS]]] # ./create-truststore.sh --verify [CA_CERT [TRUSTSTORE [ALIAS]]] # Defaults may be overridden with CERT_DIR, CA_CERT, TRUSTSTORE, CA_ALIAS, JAVA_BIN. # Requires Java 8+; javac is required on Java 8-10. # Java 11+ supports source-file mode when the jdk.compiler module is available. set -euo pipefail CERT_DIR="${CERT_DIR:-/site/var/spring/key}" CA_CERT="${CA_CERT:-${CERT_DIR}/local-root-ca.crt}" TRUSTSTORE="${TRUSTSTORE:-${CERT_DIR}/truststore-empty.jks}" CA_ALIAS="${CA_ALIAS:-local-root-ca}" MODE=create if [[ "${1:-}" == --verify ]]; then MODE=verify shift fi CA_CERT="${1:-${CA_CERT}}" TRUSTSTORE="${2:-${TRUSTSTORE}}" CA_ALIAS="${3:-${CA_ALIAS}}" if (( $# > 3 )); then echo 'Usage: create-truststore.sh [--verify] [CA_CERT [TRUSTSTORE [ALIAS]]]' >&2 exit 2 fi if [[ ! -r "${CA_CERT}" ]]; then echo "ERROR: CA certificate not readable: ${CA_CERT}" >&2 exit 1 fi if [[ "${MODE}" == create && -e "${TRUSTSTORE}" ]]; then echo "ERROR: TrustStore already exists: ${TRUSTSTORE}" >&2 exit 1 fi if [[ "${MODE}" == verify && ! -r "${TRUSTSTORE}" ]]; then echo "ERROR: TrustStore not readable: ${TRUSTSTORE}" >&2 exit 1 fi JAVA_BIN="${JAVA_BIN:-$(which java 2>/dev/null || true)}" if [[ -z "${JAVA_BIN}" || ! -x "${JAVA_BIN}" ]]; then echo 'ERROR: java not found (set JAVA_BIN or add java to PATH)' >&2 exit 1 fi JAVA_MAJOR="$("${JAVA_BIN}" -version 2>&1 | awk -F '"' '/version/ {split($2, v, "."); print (v[1] == "1" ? v[2] : v[1]); exit}')" if [[ ! "${JAVA_MAJOR}" =~ ^[0-9]+$ || "${JAVA_MAJOR}" -lt 8 ]]; then echo 'ERROR: cannot determine Java version or Java is older than 8' >&2 exit 1 fi WORK_DIR="$(mktemp -d)" trap 'rm -rf -- "${WORK_DIR}"' EXIT SOURCE="${WORK_DIR}/CreateTrustStore.java" cat > "${SOURCE}" <<'JAVA' import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.security.KeyStore; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; public class CreateTrustStore { public static void main(String[] args) throws Exception { String mode = args[0]; String certPath = args[1]; String storePath = args[2]; String alias = args[3]; char[] password = new char[0]; Certificate ca; try (InputStream in = Files.newInputStream(Paths.get(certPath))) { ca = CertificateFactory.getInstance("X.509").generateCertificate(in); } KeyStore store = KeyStore.getInstance("JKS"); if ("create".equals(mode)) { store.load(null, password); store.setCertificateEntry(alias, ca); try (OutputStream out = Files.newOutputStream( Paths.get(storePath), StandardOpenOption.CREATE_NEW)) { store.store(out, password); } } else if (!"verify".equals(mode)) { throw new IllegalArgumentException("Unknown mode: " + mode); } try (InputStream in = Files.newInputStream(Paths.get(storePath))) { store.load(in, password); } if (!store.isCertificateEntry(alias) || !ca.equals(store.getCertificate(alias))) { throw new IllegalStateException("CA certificate/alias mismatch"); } System.out.println("OK: JKS verified with empty password; alias=" + alias); } } JAVA umask 077 # Prefer javac from the selected Java installation to avoid mixing JDK versions. # For Java 11+ source-file mode is preferred, especially on JRE-only systems. resolve_javac() { local java_real java_dir candidate java_real="$(readlink -f -- "${JAVA_BIN}" 2>/dev/null || printf '%s' "${JAVA_BIN}")" java_dir="$(dirname -- "${java_real}")" candidate="${JAVAC_BIN:-${java_dir}/javac}" if [[ -x "${candidate}" ]]; then printf '%s\n' "${candidate}" return 0 fi # A java executable in PATH may be a wrapper; javac from PATH is usable # only when it belongs to the same major Java version. if [[ -z "${JAVAC_BIN:-}" ]]; then candidate="$(which javac 2>/dev/null || true)" if [[ -n "${candidate}" && -x "${candidate}" ]]; then local compiler_major compiler_major="$("${candidate}" -version 2>&1 | awk '{split($2,v,"."); print (v[1] == "1" ? v[2] : v[1])}')" if [[ "${compiler_major}" == "${JAVA_MAJOR}" ]]; then printf '%s\n' "${candidate}" return 0 fi fi fi return 1 } run_compiled() { local compiler compiler="$(resolve_javac)" || { echo "ERROR: Java ${JAVA_MAJOR}: javac from the matching JDK is required" >&2 return 1 } "${compiler}" -d "${WORK_DIR}" "${SOURCE}" "${JAVA_BIN}" -cp "${WORK_DIR}" CreateTrustStore "${MODE}" "${CA_CERT}" "${TRUSTSTORE}" "${CA_ALIAS}" } if (( JAVA_MAJOR >= 11 )); then # A runtime image can omit jdk.compiler: fall back to javac if available. if "${JAVA_BIN}" --list-modules 2>/dev/null | grep -q '^jdk.compiler@'; then "${JAVA_BIN}" "${SOURCE}" "${MODE}" "${CA_CERT}" "${TRUSTSTORE}" "${CA_ALIAS}" else run_compiled fi else run_compiled fi if [[ "${MODE}" == create ]]; then chmod 640 -- "${TRUSTSTORE}" echo "TrustStore created: ${TRUSTSTORE}" fi