1+ #! /bin/sh
2+ set -euo pipefail
3+
4+ ts () { date -u +' %Y-%m-%dT%H:%M:%SZ' ; }
5+
6+ APP_ROOT=" ${APP_ROOT:-/ app} "
7+ CERT_DIR=" ${APP_ROOT} /certs"
8+ TMP_DIR=" ${APP_ROOT} /tmp"
9+
10+ CA_PEM=" ${CERT_DIR} /ca.crt"
11+ SERVER_CRT=" ${CERT_DIR} /server.crt"
12+ SERVER_KEY=" ${CERT_DIR} /server.key"
13+ CHAIN_PEM=" ${CERT_DIR} /chain.crt" # optional
14+
15+ TRUSTSTORE_PATH=" ${CERT_DIR} /truststore.bcfks"
16+ KEYSTORE_PATH=" ${CERT_DIR} /keystore.bcfks"
17+ SERVER_PK8=" ${CERT_DIR} /server.pk8"
18+
19+ BC_DIR=" /usr/share/java/bouncycastle-fips"
20+ BC_JAR=" ${BC_DIR} /bc-fips.jar"
21+
22+ mkdir -p " ${CERT_DIR} " " ${TMP_DIR} "
23+
24+ # Providers/module-path only; no java.security append/override
25+ export JDK_JAVA_OPTIONS=" --module-path=${BC_DIR} --add-modules=jdk.crypto.ec,jdk.crypto.cryptoki ${JDK_JAVA_OPTIONS:- } "
26+ export JAVA_TOOL_OPTIONS=" ${JAVA_TOOL_OPTIONS:- } "
27+
28+ # Optional: enforce approved-only if set via env
29+ if [ -n " ${FIPS_APPROVED_ONLY:- } " ]; then
30+ export JAVA_OPTS=" ${JAVA_OPTS:- } -Dorg.bouncycastle.fips.approved_only=true"
31+ fi
32+
33+ need_openssl () { command -v openssl > /dev/null 2>&1 || { echo " $( ts) ERROR: openssl not found" ; exit 1; }; }
34+ need_javac () { command -v javac > /dev/null 2>&1 || { echo " $( ts) ERROR: javac not found" ; exit 1; }; }
35+ need_java () { command -v java > /dev/null 2>&1 || { echo " $( ts) ERROR: java not found" ; exit 1; }; }
36+
37+ # Strip inherited javax.net.ssl pins to avoid conflicts
38+ _strip_ssl_flags () { sed -E ' s/-Djavax\.net\.ssl\.(trust|key)Store(Type|Provider|Password)=[^ ]+//g' ; }
39+ [ -n " ${JDK_JAVA_OPTIONS:- } " ] && export JDK_JAVA_OPTIONS=" $( printf ' %s' " ${JDK_JAVA_OPTIONS} " | _strip_ssl_flags) "
40+ [ -n " ${JAVA_TOOL_OPTIONS:- } " ] && export JAVA_TOOL_OPTIONS=" $( printf ' %s' " ${JAVA_TOOL_OPTIONS} " | _strip_ssl_flags) "
41+ [ -n " ${JAVA_OPTS:- } " ] && export JAVA_OPTS=" $( printf ' %s' " ${JAVA_OPTS} " | _strip_ssl_flags) "
42+
43+ # --- Truststore plan ---
44+ TRUSTSTORE_PASSWORD=" ${TRUSTSTORE_PASSWORD:- } "
45+ if [ -f " ${CA_PEM} " ] && { [ " ${FORCE_REGEN:- 0} " = " 1" ] || [ ! -f " ${TRUSTSTORE_PATH} " ]; }; then
46+ echo " $( ts) Creating BCFKS truststore (CA only) at ${TRUSTSTORE_PATH} "
47+ : " ${TRUSTSTORE_PASSWORD:= $(LC_ALL=C tr -dc ' A-Za-z0-9' </ dev/ urandom | head -c 32 || true)} "
48+ export TRUSTSTORE_PASSWORD
49+ :> " ${TMP_DIR} /.need_makebcfks"
50+ elif [ ! -f " ${CA_PEM} " ]; then
51+ echo " $( ts) WARN: ${CA_PEM} not found; no custom CA will be trusted."
52+ fi
53+
54+ # --- Keystore plan ---
55+ KEY_PASSWORD=" ${KEY_PASSWORD:- } "
56+ if [ -f " ${SERVER_CRT} " ] && [ -f " ${SERVER_KEY} " ] && { [ " ${FORCE_REGEN:- 0} " = " 1" ] || [ ! -f " ${KEYSTORE_PATH} " ]; }; then
57+ echo " $( ts) Creating BCFKS keystore (server cert+key) at ${KEYSTORE_PATH} "
58+ : " ${KEY_PASSWORD:= $(LC_ALL=C tr -dc ' A-Za-z0-9' </ dev/ urandom | head -c 32 || true)} "
59+ export KEY_PASSWORD
60+ need_openssl
61+ if ! grep -q " BEGIN PRIVATE KEY" " ${SERVER_KEY} " ; then
62+ openssl pkcs8 -topk8 -nocrypt -in " ${SERVER_KEY} " -out " ${SERVER_PK8} " ${PASSPHRASE: +-passin pass: " ${PASSPHRASE} " } 2> /dev/null \
63+ || { echo " $( ts) ERROR: Failed to convert server.key to PKCS#8; check PASSPHRASE" ; exit 1; }
64+ else
65+ cp -f " ${SERVER_KEY} " " ${SERVER_PK8} "
66+ fi
67+ :> " ${TMP_DIR} /.need_makebcfks"
68+ fi
69+
70+ # --- Write .bcfks if needed ---
71+ if [ -f " ${TMP_DIR} /.need_makebcfks" ]; then
72+ need_javac; need_java
73+ cat > " ${TMP_DIR} /MakeBcfks.java" << 'EOF '
74+ import java.io.InputStream; import java.io.OutputStream; import java.io.IOException;
75+ import java.nio.file.*; import java.security.*; import java.security.cert.*; import java.security.spec.PKCS8EncodedKeySpec;
76+ import java.util.*; public class MakeBcfks {
77+ static final String PROV_NAME="BCFIPS";
78+ static byte[] readAll(String p)throws IOException{return Files.readAllBytes(Paths.get(p));}
79+ static byte[] decodePem(byte[] in,String type){String s=new String(in);String b="-----BEGIN "+type+"-----",e="-----END "+type+"-----";
80+ int i=s.indexOf(b),j=s.indexOf(e); if(i>=0&&j>i){String b64=s.substring(i+b.length(),j).replaceAll("[\\r\\n\\s]","");return Base64.getDecoder().decode(b64);} return in;}
81+ static PrivateKey readPkcs8Key(String path)throws Exception{
82+ byte[] der=decodePem(readAll(path),"PRIVATE KEY"); PKCS8EncodedKeySpec spec=new PKCS8EncodedKeySpec(der);
83+ GeneralSecurityException last=null; for(String alg:new String[]{"RSA","EC","Ed25519","Ed448"}){
84+ try{return KeyFactory.getInstance(alg,PROV_NAME).generatePrivate(spec);}catch(GeneralSecurityException e){last=e;}}
85+ throw (last!=null)?last:new GeneralSecurityException("Unsupported key algorithm");}
86+ static KeyStore newBcfks(char[]pwd)throws Exception{KeyStore ks=KeyStore.getInstance("BCFKS",PROV_NAME); ks.load(null,pwd); return ks;}
87+ public static void main(String[] a)throws Exception{
88+ if(Security.getProvider(PROV_NAME)==null){
89+ Security.addProvider((Provider)Class.forName("org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider").getDeclaredConstructor().newInstance());}
90+ String ca=System.getenv("CA_PEM"), leaf=System.getenv("SERVER_CRT"), chain=System.getenv("CHAIN_PEM"), pk8=System.getenv("SERVER_PK8"),
91+ ts=System.getenv("TRUSTSTORE_PATH"), ks=System.getenv("KEYSTORE_PATH");
92+ char[] tsp=Optional.ofNullable(System.getenv("TRUSTSTORE_PASSWORD")).orElse("").toCharArray();
93+ char[] ksp=Optional.ofNullable(System.getenv("KEY_PASSWORD")).orElse("").toCharArray();
94+ if(ca!=null && Files.isRegularFile(Paths.get(ca)) && !Files.isRegularFile(Paths.get(ts))){
95+ KeyStore T=newBcfks(tsp); CertificateFactory cf=CertificateFactory.getInstance("X.509");
96+ try(InputStream is=Files.newInputStream(Paths.get(ca))){int i=0; for(java.security.cert.Certificate c: cf.generateCertificates(is)){T.setCertificateEntry("ca-"+(i++),c);}}
97+ try(OutputStream os=Files.newOutputStream(Paths.get(ts))){T.store(os,tsp);} System.out.println("Wrote truststore: "+ts);
98+ }
99+ if(leaf!=null && Files.isRegularFile(Paths.get(leaf)) && pk8!=null && Files.isRegularFile(Paths.get(pk8)) && !Files.isRegularFile(Paths.get(ks))){
100+ PrivateKey key=readPkcs8Key(pk8); List<java.security.cert.Certificate> list=new ArrayList<>(); CertificateFactory cf=CertificateFactory.getInstance("X.509");
101+ try(InputStream is=Files.newInputStream(Paths.get(leaf))){list.addAll((Collection<? extends java.security.cert.Certificate>)cf.generateCertificates(is));}
102+ if(chain!=null && Files.isRegularFile(Paths.get(chain))){try(InputStream cs=Files.newInputStream(Paths.get(chain))){list.addAll((Collection<? extends java.security.cert.Certificate>)cf.generateCertificates(cs));}}
103+ KeyStore K=newBcfks(ksp); K.setKeyEntry("app-server",key,ksp,list.toArray(new java.security.cert.Certificate[0]));
104+ try(OutputStream os=Files.newOutputStream(Paths.get(ks))){K.store(os,ksp);} System.out.println("Wrote keystore: "+ks);
105+ }
106+ }
107+ }
108+ EOF
109+ javac -cp " ${BC_JAR} " " ${TMP_DIR} /MakeBcfks.java"
110+ CA_PEM=" ${CA_PEM} " SERVER_CRT=" ${SERVER_CRT} " CHAIN_PEM=" ${CHAIN_PEM} " \
111+ SERVER_PK8=" ${SERVER_PK8} " TRUSTSTORE_PATH=" ${TRUSTSTORE_PATH} " KEYSTORE_PATH=" ${KEYSTORE_PATH} " \
112+ TRUSTSTORE_PASSWORD=" ${TRUSTSTORE_PASSWORD:- } " KEY_PASSWORD=" ${KEY_PASSWORD:- } " \
113+ java -cp " ${TMP_DIR} :${BC_JAR} " MakeBcfks
114+ rm -f " ${TMP_DIR} /.need_makebcfks" " ${SERVER_PK8} " || true
115+ fi
116+
117+ # Pin BCFKS stores at runtime (only if they exist + passwords set)
118+ RUNTIME_PROPS=" "
119+ [ -f " ${TRUSTSTORE_PATH} " ] && [ -n " ${TRUSTSTORE_PASSWORD:- } " ] && RUNTIME_PROPS=" ${RUNTIME_PROPS} -Djavax.net.ssl.trustStore=${TRUSTSTORE_PATH} -Djavax.net.ssl.trustStoreType=BCFKS -Djavax.net.ssl.trustStoreProvider=BCFIPS -Djavax.net.ssl.trustStorePassword=${TRUSTSTORE_PASSWORD} "
120+ [ -f " ${KEYSTORE_PATH} " ] && [ -n " ${KEY_PASSWORD:- } " ] && RUNTIME_PROPS=" ${RUNTIME_PROPS} -Djavax.net.ssl.keyStore=${KEYSTORE_PATH} -Djavax.net.ssl.keyStoreType=BCFKS -Djavax.net.ssl.keyStoreProvider=BCFIPS -Djavax.net.ssl.keyStorePassword=${KEY_PASSWORD} "
121+
122+ echo " $( ts) JAVA_TOOL_OPTIONS=${JAVA_TOOL_OPTIONS} "
123+ [ -n " ${TRUSTSTORE_PASSWORD:- } " ] && echo " $( ts) truststore: ${TRUSTSTORE_PATH} (pwd len: ${# TRUSTSTORE_PASSWORD} )"
124+ [ -n " ${KEY_PASSWORD:- } " ] && echo " $( ts) keystore: ${KEYSTORE_PATH} (pwd len: ${# KEY_PASSWORD} )"
125+
126+ APP_JAR=" ${APP_JAR:- ${APP_ROOT} / lib/ app.jar} "
127+ if [ -f " $APP_JAR " ]; then
128+ echo " $( ts) Launching Java app: ${APP_JAR} "
129+ exec java ${JAVA_OPTS:- } ${RUNTIME_PROPS} -jar " ${APP_JAR} "
130+ else
131+ echo " [WARN] No ${APP_JAR} ; sleeping for debug"
132+ exec sleep 600
133+ fi
0 commit comments