Documentationcurrent version
Help us improve the docs by fixing typos and proposing enhancements.

Nikita

Action "java.keystore_add"

Add certificates, private keys and certificate authorities to java keystores and truststores.

CA Cert Chains

In case the CA file reference a chain of certificates, each certificate will be referenced by a unique incremented alias, starting at 0. For example if the alias value is "my-alias", the aliases will be "my-alias-0" then "my-alias-1"...

Relevant Java properties

  • javax.net.ssl.trustStore
  • javax.net.ssl.trustStorePassword
  • javax.net.ssl.keyStore
  • javax.net.ssl.keyStoreType
  • javax.net.ssl.keyStorePassword

Relevant commands

  • View the content of a Java KeyStore (JKS) and Java TrustStore:
    keytool -list -v -keystore $keystore -storepass $storepass
    keytool -list -v -keystore $keystore -storepass $storepass -alias $caname
    Note, alias is optional and may reference a CA or a certificate
  • View the content of a ".pem" certificate:
    openssl x509 -in cert.pem -text
    keytool -printcert -file certs.pem
  • Change the password of a keystore:
    keytool -storepasswd -keystore my.keystore
  • Change the key's password:
    keytool -keypasswd -alias <key_name> -keystore my.keystore

Uploading public and private keys into a keystore

const {$status} = await nikita.java.keystore_add([{
  keystore: java_home + '/lib/security/cacerts',
  storepass: 'changeit',
  caname: 'my_ca_certificate',
  cacert: '/tmp/cacert.pem',
  key: "/tmp/private_key.pem",
  cert: "/tmp/public_cert.pem",
  keypass: 'mypassword',
  name: 'node_1'
})
console.info(`Keystore was updated: ${$status}`)

Uploading a certificate authority

const {$status} = await nikita.java.keystore_add([{
  keystore: java_home + '/lib/security/cacerts',
  storepass: 'changeit',
  caname: 'my_ca_certificate',
  cacert: '/tmp/cacert.pem'
})
console.info(`Keystore was updated: ${$status}`)

Requirements

This action relies on the openssl and keytool commands. If not detected from the path, Nikita will look for "/usr/java/default/bin/keytool" which is the default location of the Oracle JDK installation.

Schema definitions

definitions =
  config:
    type: 'object'
    properties:
      'cacert':
        type: 'string'
        description: '''
        Path to the certificate authority (CA).
        '''
      'cert':
        type: 'string'
        description: '''
        Path to the certificate.
        '''
      'keytool':
        type: 'string'
        default: 'keytool'
        description: '''
        Path to the `keytool` command, detetected from `$PATH` by default.
        '''
      'local':
        type: 'boolean'
        default: false
        description: '''
        Treat the source file (key, cert or cacert) as a local file present on
        the host, only apply with remote actions over SSH.
        '''
      'openssl':
        type: 'string'
        default: 'openssl'
        description: '''
        Path to OpenSSl command line tool.
        '''
      'parent':
        $ref: 'module://@nikitajs/core/lib/actions/fs/mkdir#/definitions/config/properties/parent'
      'keystore':
        type: 'string'
        description: '''
        Path to the keystore.
        '''
      'storepass':
        type: 'string'
        description: '''
        Password to manage the keystore.
        '''
    required: ['keystore', 'storepass']
    dependencies:
      'cacert':
        properties:
          'caname':
            type: 'string'
            description: '''
            Name of the certificate authority (CA).
            '''
        required: ['caname']
      'cert':
        properties:
          'key':
            type: 'string'
            description: '''
            Location of the private key.
            '''
          'keypass':
            type: 'string'
            description: '''
            Password used to protect the certigficate and its key access
            inside the keystore.
            '''
          'name':
            type: 'string'
            description: '''
            Name (aka alias) to reference the certificate inside the keystore.
            '''
        required: ['key', 'keypass', 'name']

Handler

handler = ({config, ssh, metadata: {tmpdir}, tools: {path}}) ->
  # Update paths in case of download
  files =
    cert: if ssh and config.local and config.cert? then "#{tmpdir}/#{path.local.basename config.cert}" else config.cert
    cacert: if ssh and config.local and config.cacert? then "#{tmpdir}/#{path.local.basename config.cacert}" else config.cacert
    key: if ssh and config.local and config.key? then "#{tmpdir}/#{path.local.basename config.key}" else config.key
  # Temporary directory
  # Used to upload certificates and to isolate certificates from their file
  if tmpdir
    await @fs.mkdir
      $shy: true
      target: tmpdir
      mode: 0o0700
  # Upload certificates
  if ssh and config.local and config.cacert
    await @file.download
      $shy: true
      source: config.cacert
      target: files.cacert
      mode: 0o0600
  if ssh and config.local and config.cert
    await @file.download
      $shy: true
      source: config.cert
      target: files.cert
      mode: 0o0600
  if ssh and config.local and config.key
    await @file.download
      $shy: true
      source: config.key
      target: files.key
      mode: 0o0600
  # Prepare parent directory
  await @fs.mkdir
    parent: config.parent
    target: path.dirname config.keystore
  # Deal with key and certificate
  try if !!config.cert
    await @execute
      bash: true
      command: """
      if ! command -v #{config.openssl}; then echo 'OpenSSL command line tool not detected'; exit 4; fi
      # Detect keytool command
      keytoolbin=#{config.keytool}
      command -v $keytoolbin >/dev/null || {
        if [ -x /usr/java/default/bin/keytool ]; then keytoolbin='/usr/java/default/bin/keytool';
        else exit 7; fi
      }
      [ -f #{files.cert} ] || (exit 6)
      user=`#{config.openssl} x509  -noout -in "#{files.cert}" -sha1 -fingerprint | sed 's/\\(.*\\)=\\(.*\\)/\\2/' | cat`
      # We are only retrieving the first certificate found in the chain with `head -n 1`
      keystore=`${keytoolbin} -list -v -keystore #{config.keystore} -storepass #{config.storepass} -alias #{config.name} | grep SHA1: | head -n 1 | sed -E 's/.+SHA1: +(.*)/\\1/'`
      echo "User Certificate: $user"
      echo "Keystore Certificate: $keystore"
      if [ "$user" = "$keystore" ]; then exit 5; fi
      # Create a PKCS12 file that contains key and certificate
      #{config.openssl} pkcs12 -export \
        -in "#{files.cert}" -inkey "#{files.key}" \
        -out "#{tmpdir}/pkcs12" -name #{config.name} \
        -password pass:#{config.keypass}
      # Import PKCS12 into keystore
      ${keytoolbin} -noprompt -importkeystore \
        -destkeystore #{config.keystore} \
        -deststorepass #{config.storepass} \
        -destkeypass #{config.keypass} \
        -srckeystore "#{tmpdir}/pkcs12" -srcstoretype PKCS12 -srcstorepass #{config.keypass} \
        -alias #{config.name}
      """
      trap: true
      code_skipped: 5 # OpenSSL exit 3 if file does not exists
  catch err
    throw Error "OpenSSL command line tool not detected" if err.exit_code is 4
    throw Error "Keystore file does not exists" if err.exit_code is 6
    throw Error "Missing Requirement: command keytool is not detected" if err.exit_code is 6
    throw err
  # Deal with CACert
  try if config.cacert
    await @execute
      bash: true
      command: """
      # Detect keytool command
      keytoolbin=#{config.keytool}
      command -v $keytoolbin >/dev/null || {
        if [ -x /usr/java/default/bin/keytool ]; then keytoolbin='/usr/java/default/bin/keytool';
        else exit 7; fi
      }
      # Check password
      if [ -f #{config.keystore} ] && ! ${keytoolbin} -list -keystore #{config.keystore} -storepass #{config.storepass} >/dev/null; then
        # Keystore password is invalid, change it manually with:
        # keytool -storepasswd -keystore #{config.keystore} -storepass ${old_pasword} -new #{config.storepass}
        exit 2
      fi
      [ -f #{files.cacert} ] || (echo 'CA file doesnt not exists: #{files.cacert} 1>&2'; exit 3)
      # Import CACert
      PEM_FILE=#{files.cacert}
      CERTS=$(grep 'END CERTIFICATE' $PEM_FILE| wc -l)
      code=5
      for N in $(seq 0 $(($CERTS - 1))); do
        if [ $CERTS -eq '1' ]; then
          ALIAS="#{config.caname}"
        else
          ALIAS="#{config.caname}-$N"
        fi
        # Isolate cert into a file
        CACERT_FILE=#{tmpdir}/$ALIAS
        cat $PEM_FILE | awk "n==$N { print }; /END CERTIFICATE/ { n++ }" > $CACERT_FILE
        # Read user CACert signature
        user=`#{config.openssl} x509  -noout -in "$CACERT_FILE" -sha1 -fingerprint | sed 's/\\(.*\\)=\\(.*\\)/\\2/'`
        # Read registered CACert signature
        keystore=`${keytoolbin} -list -v -keystore #{config.keystore} -storepass #{config.storepass} -alias $ALIAS | grep SHA1: | sed -E 's/.+SHA1: +(.*)/\\1/'`
        echo "User CA Cert: $user"
        echo "Keystore CA Cert: $keystore"
        if [ "$user" = "$keystore" ]; then echo 'Identical Signature'; code=5; continue; fi
        # Remove CACert if signature doesnt match
        if [ "$keystore" != "" ]; then
          ${keytoolbin} -delete \
            -keystore #{config.keystore} \
            -storepass #{config.storepass} \
            -alias $ALIAS
        fi
        ${keytoolbin} -noprompt -import -trustcacerts -keystore #{config.keystore} -storepass #{config.storepass} -alias $ALIAS -file #{tmpdir}/$ALIAS
        code=0
      done
      exit $code
      """
      trap: true
      code_skipped: 5
  catch err
    throw Error "CA file does not exist: #{files.cacert}" if err.exit_code is 3
    throw err
  # Ensure ownerships and permissions
  if config.uid? or config.gid?
    await @fs.chown
      target: config.keystore
      uid: config.uid
      gid: config.gid
  if config.mode?
    await @fs.chmod
      target: config.keystore
      mode: config.mode
  undefined

Exports

module.exports =
  handler: handler
  metadata:
    tmpdir: true
    definitions: definitions
Edit on GitHub
Navigate
About

Nikita is an open source project hosted on GitHub and developed by Adaltas.