25

How to import multiple certificates in a single file with keytool [to cert store]?

keytool -importcert only imports the first one.

Fakrudeen
  • 5,778
  • 7
  • 44
  • 70

10 Answers10

37

A bash script that will import all certificates from a PEM file:

#!/bin/bash
PEM_FILE=$1
PASSWORD=$2
KEYSTORE=$3
# number of certs in the PEM file
CERTS=$(grep 'END CERTIFICATE' $PEM_FILE| wc -l)

# For every cert in the PEM file, extract it and import into the JKS keystore
# awk command: step 1, if line is in the desired cert, print the line
#              step 2, increment counter when last line of cert is found
for N in $(seq 0 $(($CERTS - 1))); do
  ALIAS="${PEM_FILE%.*}-$N"
  cat $PEM_FILE |
    awk "n==$N { print }; /END CERTIFICATE/ { n++ }" |
    keytool -noprompt -import -trustcacerts \
            -alias $ALIAS -keystore $KEYSTORE -storepass $PASSWORD
done

For example:

./jks_import_pem TrustedCAs.PEM changeit truststore.jks
cmcginty
  • 113,384
  • 42
  • 163
  • 163
  • I had to add "-storetype JKS" to the keytool line in this script, otherwise I was getting the error: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty. After that, everything has worked perfectly. – karezza Jun 30 '22 at 23:43
13

If you want to include the CA certificates you should add the -trustcacerts option.

If you have multiple certificate chains in one PEM file you will have to split the file.

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
bowmore
  • 10,842
  • 1
  • 35
  • 43
3

I wanted to do the same thing, but apparently it's only possible if you're importing the key as well:

There are two types of entries- key entries and trusted cert entries, and only the key entry can contain a "chain" of certificates, attached to it. The trusted cert entries are all single cert entries.

(https://www.java.net/node/674524#comment-709695)

I even tried converting to PKCS#7 format first, but it didn't work, either because of the above reason or because my version of keytool was too old.

So had to first split the file into separate certs:

cat certchain.pem | awk 'split_after==1{n++;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > ("cert" n ".pem")}'

(https://serverfault.com/q/391396/58568)

Then import each one individually.

Community
  • 1
  • 1
bmaupin
  • 14,427
  • 5
  • 89
  • 94
  • 1
    Note that to be strictly correct, you'll need parentheses around the string concatenation: `("cert" n ".pem")`. Without them, some versions of awk will get confused (OS X, anyway). – eaj Oct 26 '15 at 19:17
3

You can simply use the free and easy to use GUI Tool Keystore Explorer to import and manage multiple certificates.

Ruwen
  • 3,008
  • 1
  • 19
  • 16
1

You can use the p11-kit tool that can does this very quicky. The only constraint is that it reads the certificates from /etc/pki/ca-trust/source/

/usr/bin/p11-kit extract --format=java-cacerts --filter=ca-anchors \
                --overwrite --purpose server-auth $DEST/java/cacerts
Akram Ben Aissi
  • 360
  • 3
  • 6
1

Importing certs with proper key alias name:

#!/bin/bash
PEM_FILE=$1
PASSWORD=$2
KEYSTORE=$3
# number of certs in the PEM file
CERTS=$(grep 'END CERTIFICATE' $PEM_FILE| wc -l)

# For every cert in the PEM file, extract it and import into the JKS keystore
# awk command: step 1, if line is in the desired cert, print the line
#              step 2, increment counter when last line of cert is found
for N in $(seq 0 $(($CERTS - 1))); do


    rm -f tmp.pem
    cat $PEM_FILE |
    awk "n==$N { print }; /END CERTIFICATE/ { n++ }" > tmp.pem
   

   ALIAS=$(openssl x509 -in tmp.pem -noout -text  | grep Subject: | sed 's/.*CN=//')

   #Optional: adding index to alias name
   ALIAS=$(echo "${N}_${ALIAS}")
    echo $ALIAS

    cat $PEM_FILE |
    awk "n==$N { print }; /END CERTIFICATE/ { n++ }" |
    keytool -noprompt -import -trustcacerts \
           -alias "${ALIAS}" -keystore $KEYSTORE -storepass $PASSWORD

done
1

I slightly improved the top answer to retain the alias names:

#!/bin/bash
PEM_FILE=$1
PASSWORD=$2
KEYSTORE=$3
# number of certs in the PEM file
CERTS=$(grep 'END CERTIFICATE' $PEM_FILE| wc -l)

# For every cert in the PEM file, extract it and import into the JKS keystore
# awk command: step 1, if line is in the desired cert, print the line
#              step 2, increment counter when last line of cert is found
for N in $(seq 0 $(($CERTS - 1))); do
  ALIAS=`cat $PEM_FILE |  awk "n==$N { print }; /END CERTIFICATE/ { n++ }" | grep '#' | cut -c3-`
  # echo "$ALIAS"
  cat $PEM_FILE |
    awk "n==$N { print }; /END CERTIFICATE/ { n++ }" |
    keytool -noprompt -import -trustcacerts \
            -alias "$ALIAS" -keystore $KEYSTORE -storepass $PASSWORD
done
tobiger
  • 103
  • 1
  • 12
0

You can import multiple CA certificates within one pem file. Ensure the root CA is first, followed by the rest.

Paul
  • 15
  • 7
-1

The given answers are not really Ansible solutions, more like alternatives.

What i wrote below works for the 1st certificate, but it's not looping. Any ideas?

    java_install_keystore_cert: true
    java_keystore_certs: "{{ apps.jira.keystore_certs }}"
    java_keystore_cert_alias: test
apps:
  jira:
    keystore_certs:
      - certName: xyz.xxx.com
        certFileName: xyz.xxx.com.pem
      - certName: xxx.com
        certFileName: xxx.com.pem
- name: Copy SSL certificate to remote server
  copy:
    src: "{{ java_keystore_certs[0].certFileName }}"
    #src: "{{ java_keystore_cert_file }}"
    dest: /tmp/
  when: java_install_keystore_cert|default(false)

- name: Determine Java cacerts keystore location
  find:
    paths: "{{ java_home }}/"
    patterns: 'cacerts'
    recurse: yes
  register: cacerts_file
  when: java_install_keystore_cert|default(false)

- name: Import SSL certificate to Java cacerts keystore
  java_cert:
    cert_alias: "{{ java_keystore_cert_alias }}"
    #cert_path: "/tmp/{{ java_keystore_cert_file }}"
    cert_path: "/tmp/{{ java_keystore_certs[0].certFileName }}"
    keystore_path: "{{ cacerts_file.files[0].path }}"
    keystore_pass: changeit
    executable: "{{ java_home }}/bin/keytool"
    state: present
  when: java_install_keystore_cert|default(false) and cacerts_file is defined
Tommy
  • 81
  • 1
  • 1
  • 7
-1

I also switched to a not-just Ansible solution....

  copy:
    src: "{{ java_keystore_cert_file }}"
    dest: /tmp/
  when: java_install_keystore_cert|default(false)

- name: Determine Java keystore (cacerts) location
  find:
    paths: "{{ java_home }}/"
    patterns: 'cacerts'
    recurse: yes
  register: cacerts_file
  when: java_install_keystore_cert|default(false)

# Not using the java_cert module (anymore) since that imports the first certificate only

# Always use .pem (simply rename .crt or .cert to .pem if needed)
# The .pem file should contain one or more public certificates, no private key(s) or chain
- name: Transfer the import certificate script
  copy:
    src: files/scripts/importcert.sh
    dest: /tmp/importcert.sh
    mode: 0700
  when: java_install_keystore_cert|default(false) and cacerts_file is defined

- name: Import certificate to Java keystore
  command: sh /tmp/importcert.sh "/tmp/{{ java_keystore_cert_file }}" "{{ java_home }}/bin/keytool" changeit "{{ cacerts_file.files[0].path }}"
  when: java_install_keystore_cert|default(false) and cacerts_file is defined
#!/bin/bash
PEM_FILE=$1
KEYTOOL=$2
PASSWORD=$3
KEYSTORE=$4
# number of certs in the PEM file
CERTS=$(grep 'END CERTIFICATE' $PEM_FILE| wc -l)

# For every cert in the PEM file, extract it and import into the JKS keystore
# awk command: step 1, if line is in the desired cert, print the line
#              step 2, increment counter when last line of cert is found
for N in $(seq 0 $(($CERTS - 1))); do
  ALIAS="${PEM_FILE%.*}-$N"
  cat $PEM_FILE |
    awk "n==$N { print }; /END CERTIFICATE/ { n++ }" |
    $KEYTOOL -noprompt -import -trustcacerts \
            -alias $ALIAS -keystore $KEYSTORE -storepass $PASSWORD
done
Tommy
  • 81
  • 1
  • 1
  • 7