bashio/lib/pwned.sh
2023-05-20 13:53:39 +02:00

140 lines
4.4 KiB
Bash

#!/usr/bin/env bash
# ==============================================================================
# Home Assistant Community Add-ons: Bashio
# Bashio is a bash function library for use with Home Assistant add-ons.
#
# It contains a set of commonly used operations and can be used
# to be included in add-on scripts to reduce code duplication across add-ons.
# ==============================================================================
# ------------------------------------------------------------------------------
# Checks if a given password is safe to use.
#
# Arguments:
# $1 The password to check
# ------------------------------------------------------------------------------
function bashio::pwned.is_safe_password() {
local password="${1}"
local occurrences
bashio::log.trace "${FUNCNAME[0]}" "<REDACTED PASSWORD>"
if ! occurrences=$(bashio::pwned "${password}"); then
bashio::log.warning "Could not check password, assuming it is safe."
return "${__BASHIO_EXIT_OK}"
fi
if [[ "${occurrences}" -ne 0 ]]; then
return "${__BASHIO_EXIT_NOK}"
fi
return "${__BASHIO_EXIT_OK}"
}
# ------------------------------------------------------------------------------
# Gets the number of occurrences of the password in the list.
#
# Arguments:
# $1 The password to check
# ------------------------------------------------------------------------------
function bashio::pwned.occurances() {
local password="${1}"
local occurrences
bashio::log.trace "${FUNCNAME[0]}" "<REDACTED PASSWORD>"
if ! occurrences=$(bashio::pwned "${password}"); then
occurrences="0"
fi
echo -n "${occurrences}"
return "${__BASHIO_EXIT_OK}"
}
# ------------------------------------------------------------------------------
# Makes a call to the Have I Been Pwned password database
#
# Arguments:
# $1 The password to check
# ------------------------------------------------------------------------------
function bashio::pwned() {
local password="${1}"
local response
local status
local hibp_hash
local count
bashio::log.trace "${FUNCNAME[0]}" "${password//./x}"
# Do not check empty password
if ! bashio::var.has_value "${password}"; then
bashio::log.warning 'Cannot check empty password against HaveIBeenPwned.'
return "${__BASHIO_EXIT_NOK}"
fi
# Hash the password
password=$(echo -n "${password}" \
| sha1sum \
| tr '[:lower:]' '[:upper:]' \
| awk -F' ' '{ print $1 }'
)
bashio::log.debug "Password SHA1: ${password}"
# Check with have I Been Pwned, only send the first 5 chars of the hash
if ! response=$(curl \
--silent \
--show-error \
--write-out '\n%{http_code}' \
--request GET \
"${__BASHIO_HIBP_ENDPOINT}/${password:0:5}"
); then
bashio::log.debug "${response}"
bashio::log.error "Something went wrong contacting the HIBP API"
return "${__BASHIO_EXIT_NOK}"
fi
status=${response##*$'\n'}
response=${response%"$status"}
bashio::log.debug "Requested API resource: ${__BASHIO_HIBP_ENDPOINT}/${password:0:5}"
bashio::log.debug "API HTTP Response code: ${status}"
bashio::log.trace "API Response: ${response}"
if [[ "${status}" -eq 429 ]]; then
bashio::log.error "HIBP Rate limit exceeded."
return "${__BASHIO_EXIT_NOK}"
fi
if [[ "${status}" -eq 503 ]]; then
bashio::log.error "HIBP Service unavailable."
return "${__BASHIO_EXIT_NOK}"
fi
if [[ "${status}" -ne 200 ]]; then
bashio::log.error "Unknown HIBP HTTP error occurred."
return "${__BASHIO_EXIT_NOK}"
fi
# Check the list of returned hashes for a match
for hibp_hash in ${response}; do
if [[ "${password:5:35}" == "${hibp_hash%%:*}" ]]; then
# Found a match! This is bad :(
count=$(echo "${hibp_hash#*:}" | tr -d '\r')
bashio::log.warning \
"Password is in the Have I Been Pwned database!"
bashio::log.warning \
"Password appeared ${count} times!"
echo "${count}"
# Well, at least the execution of this function succeeded.
return "${__BASHIO_EXIT_OK}"
fi
done
# Password was not found
echo "0"
bashio::log.info "Password is NOT in the Have I Been Pwned database! Nice!"
return "${__BASHIO_EXIT_OK}"
}