#!/bin/bash set -euo pipefail # if container log level is trace: # print commands and their arguments as they are executed container log level eq trace && set -x # shellcheck disable=SC1091 source /tmp/openldap-test-lib.sh TEST_DIR=$(mktemp -d) trap 'rm -rf "${TEST_DIR:?}"' EXIT URI="ldap://${TEST_NODES}:3890" USER_DN="uid=password-helper,${TEST_SUFFIX}" # shellcheck disable=SC2016 PASSWORD=' Test " $HOME $(touch injected) `touch injected` \ end ' PASSWORD+="'" PASSWORD_FILE="${TEST_DIR}/secret ' \$(touch injected)" printf '%s' "${PASSWORD}" > "${PASSWORD_FILE}" chmod 400 "${PASSWORD_FILE}" ln -s "${PASSWORD_FILE}" "${TEST_DIR}/projected-secret" eventually has_entry "${URI}" "${TEST_SUFFIX}" expect_rc 0 bind_modify "${URI}" "${ADMIN_DN}" -a < "${TEST_DIR}/trace" 2>&1 else container --log-level trace run -- openldap-ctl password hash --stdin < "${PASSWORD_FILE}" > "${TEST_DIR}/trace" 2>&1 fi HASH=$(sed -n '/^{ARGON2}/p' "${TEST_DIR}/trace") check_hash "${HASH}" '{ARGON2}' if grep -Fq -- "${PASSWORD}" "${TEST_DIR}/trace"; then fail "${mode} password leaked in trace output" fi ok "${mode} hashes exact special characters without exposing the secret in trace output" done # A custom backend must receive a source path, never the file/stdin secret as an argument. cat > "${TEST_DIR}/hasher.sh" <<'SH' #!/bin/bash set -euo pipefail [ "$#" -eq 2 ] && [ "$1" = -T ] || exit 1 exec slappasswd -h '{SSHA}' "$@" SH export OPENLDAP_CTL_PASSWORD_HASH_CMD="bash '${TEST_DIR}/hasher.sh'" HASH=$(container run -- openldap-ctl password hash --file "${PASSWORD_FILE}") check_hash "${HASH}" '{SSHA}' HASH=$(container run -- openldap-ctl password hash --stdin < "${PASSWORD_FILE}") check_hash "${HASH}" '{SSHA}' unset OPENLDAP_CTL_PASSWORD_HASH_CMD ok 'custom hash commands receive the password source without secret arguments' # Trailing newlines are part of the password for both input sources. PASSWORD+=$'\n' chmod 600 "${PASSWORD_FILE}" printf '%s' "${PASSWORD}" > "${PASSWORD_FILE}" HASH=$(container run -- openldap-ctl password hash --file "${PASSWORD_FILE}") check_hash "${HASH}" '{ARGON2}' HASH=$(container run -- openldap-ctl password hash --stdin < "${PASSWORD_FILE}") check_hash "${HASH}" '{ARGON2}' ok 'file and stdin preserve trailing newlines' for PASSWORD in --file --stdin --; do HASH=$(container run -- openldap-ctl password hash -- "${PASSWORD}") check_hash "${HASH}" '{ARGON2}' done ok 'reserved option names can still be hashed as literal passwords' expect_fail container run -- openldap-ctl password hash expect_output 'Usage: openldap-ctl password hash' expect_fail container run -- openldap-ctl password hash '' expect_fail container run -- openldap-ctl password hash --file expect_fail container run -- openldap-ctl password hash --file "${PASSWORD_FILE}" extra expect_fail container run -- openldap-ctl password hash --stdin extra expect_fail container run -- openldap-ctl password hash --file "${TEST_DIR}/missing" expect_fail container run -- openldap-ctl password hash --file "${TEST_DIR}" touch "${TEST_DIR}/empty" expect_fail container run -- openldap-ctl password hash --file "${TEST_DIR}/empty" expect_fail container run -- openldap-ctl password hash --stdin < "${TEST_DIR}/empty" chmod 000 "${PASSWORD_FILE}" expect_fail container run -- openldap-ctl password hash --file "${PASSWORD_FILE}" ok 'invalid arguments and missing, unreadable or empty inputs are rejected'