Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check signatures/checksums to ensure authenticity #25

Merged
merged 7 commits into from
Feb 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (C) 2015-2017 Akash Manohar J
Copyright (C) 2017 Robin Schneider <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ Node.js plugin for [asdf](https://github.com/asdf-vm/asdf) version manager
asdf plugin-add nodejs https://github.com/asdf-vm/asdf-nodejs.git
```

## Bootstrap trust for signature validation

The plugin properly valides OpenPGP signatures.
All you have to do is to bootstrap the trust once as follows.

You can either import the OpenPGP public keys in your main OpenPGP keyring or use a dedicated keyring (recommended in order to mitigate https://github.com/nodejs/node/issues/9859).
If you decided to do the later, prepare the dedicated keyring and make it temporarily the default one in your current shell:

```Shell
export GNUPGHOME="$HOME/.asdf/keyrings/nodejs" && mkdir -p "$GNUPGHOME" && chmod 0700 "$GNUPGHOME"
```

Then import the OpenPGP public keys of the [Release Team](https://github.com/nodejs/node/#release-team).

For more details, refer to [Verifying Node.js Binaries](https://blog.continuation.io/verifying-node-js-binaries/).
Note that only versions greater or equal to 0.10.0 are checked. Before that version, signatures for SHA2-256 hashes might not be provided (and can not be installed with the `strict` setting for that reason).

This behavior can be influenced by the `NODEJS_CHECK_SIGNATURES` variable which supports the following options:

`no`: Do not check signatures/checksums.
`yes`: Check signatures/checksums if they should be present (enforced for >= 0.10.0).
`strict` (default): Check signatures/checksums and don’t operate on package versions which did not provide signatures/checksums properly (< 0.10.0).

## Use

Check [asdf](https://github.com/asdf-vm/asdf) readme for instructions on how to install & manage versions of Node.js.
Expand Down
230 changes: 161 additions & 69 deletions bin/install
Original file line number Diff line number Diff line change
@@ -1,136 +1,228 @@
#!/usr/bin/env bash

set -o nounset -o pipefail -o errexit

NODEJS_CHECK_SIGNATURES="${NODEJS_CHECK_SIGNATURES:-strict}"

install_nodejs() {
local install_type=$1
local version=$2
local install_path=$3
local install_type="$1"
local version="$2"
local install_path="$3"
local tmp_download_dir="$4"

if [ "$TMPDIR" = "" ]; then
local tmp_download_dir=$(mktemp -d)
else
local tmp_download_dir=$TMPDIR
fi
## Do this first as it is fast but could fail.
download_and_verify_checksums "$install_type" "$version" "$tmp_download_dir"

local source_path=$(get_download_file_path $install_type $version $tmp_download_dir)
download_source_file $install_type $version $source_path
local archive_path
archive_path="${tmp_download_dir}/$(get_archive_file_name "$install_type" "$version")"
download_file "$(get_download_url "$install_type" "$version")" "${archive_path}"

verify_archive "$tmp_download_dir"

# running this in a subshell
# we don't want to disturb current working dir
(
if [ "$install_type" != "version" ]; then
tar zxf $source_path -C $install_path --strip-components=1 || exit 1
cd $install_path
tar zxf "$archive_path" -C "$install_path" --strip-components=1 || exit 1
cd "$install_path" || exit 1

local configure_options="$(construct_configure_options $install_path)"
local configure_options
configure_options="$(construct_configure_options "$install_path")"

# shellcheck disable=SC2086
./configure $configure_options || exit 1
make
make install

if [ $? -ne 0 ]; then
rm -rf $install_path
rm -rf "$install_path"
exit 1
fi
else
tar zxf $source_path -C $install_path --strip-components=1 || exit 1
tar zxf "$archive_path" -C "$install_path" --strip-components=1 || exit 1
fi

mkdir -p $install_path/.npm/lib/node_modules/.hooks
cp $(dirname $(dirname $0))/npm-hooks/* $install_path/.npm/lib/node_modules/.hooks/
chmod +x $install_path/.npm/lib/node_modules/.hooks/*
mkdir -p "$install_path/.npm/lib/node_modules/.hooks"
cp "$(dirname "$(dirname "$0")")"/npm-hooks/* "$install_path/.npm/lib/node_modules/.hooks/"
chmod +x "$install_path"/.npm/lib/node_modules/.hooks/*
)
}


construct_configure_options() {
local install_path=$1
local install_path="$1"

if [ "$NODEJS_CONFIGURE_OPTIONS" = "" ]; then
local configure_options="$(os_based_configure_options) --prefix=$install_path"
if [ -z "${NODEJS_CONFIGURE_OPTIONS:-}" ]; then
local configure_options
configure_options="--dest-cpu=$(get_nodejs_machine_hardware_name)"

if [ "$NODEJS_EXTRA_CONFIGURE_OPTIONS" != "" ]; then
configure_options="$configure_options $NODEJS_EXTRA_CONFIGURE_OPTIONS"
if [ "${NODEJS_EXTRA_CONFIGURE_OPTIONS:-}" != "" ]; then
configure_options="$configure_options ${NODEJS_EXTRA_CONFIGURE_OPTIONS:-}"
fi
else
local configure_options="$NODEJS_CONFIGURE_OPTIONS --prefix=$install_path"
local configure_options="${NODEJS_CONFIGURE_OPTIONS:-}"
fi

configure_options="$configure_options --prefix=$install_path"

echo "$configure_options"
}


os_based_configure_options() {
local operating_system=$(uname -a)
local configure_options=""
get_nodejs_machine_hardware_name() {
local machine_hardware_name
machine_hardware_name="$(uname --machine)"

if [[ "$operating_system" =~ "x86_64" ]]; then
local cpu_type="x64"
else
local cpu_type="x86"
fi
case "$machine_hardware_name" in
'x86_64') local cpu_type="x64";;
'i686') local cpu_type="x86";;
*) local cpu_type="$machine_hardware_name";;
esac

configure_options="$configure_options --dest-cpu=$cpu_type"
echo $configure_options
echo "$cpu_type"
}


download_source_file() {
local install_type=$1
local version=$2
local download_path=$3
local download_url=$(get_download_url $install_type $version)
download_file() {
local download_url="$1"
local download_path="$2"

curl -Lo $download_path -C - $download_url
curl -Lo "$download_path" -C - "$download_url"
}


get_download_file_path() {
local install_type=$1
local version=$2
local tmp_download_dir=$3
get_archive_file_name() {
local install_type="$1"
local version="$2"

local pkg_name

if [ "$install_type" = "version" ]; then
if [[ "$operating_system" =~ "x86_64" ]]; then
local cpu_type="x64"
else
local cpu_type="x86"
fi

if [[ "$operating_system" =~ "Darwin" ]]; then
local pkg_name="node-v${version}-darwin-${cpu_type}"
else # we'll assume it is linux
local pkg_name="node-v${version}-linux-${cpu_type}"
fi
pkg_name="node-v${version}-$(uname --kernel-name | tr '[:upper:]' '[:lower:]')-$(get_nodejs_machine_hardware_name)"
else
local pkg_name="${version}.tar.gz"
pkg_name="${version}"
fi

echo "$tmp_download_dir/$pkg_name"
echo "${pkg_name}.tar.gz"
}


get_download_url() {
local install_type="$1"
local version="$2"

if [ "$install_type" = "version" ]; then
local download_url_base="https://nodejs.org/dist/v${version}"
else
local download_url_base="https://github.com/nodejs/node/archive"
fi

echo "${download_url_base}/$(get_archive_file_name "$install_type" "$version")"
}


get_signed_checksum_download_url() {
local install_type=$1
local version=$2
local operating_system=$(uname -a)

if [ "$install_type" = "version" ]; then
if [[ "$operating_system" =~ "x86_64" ]]; then
local cpu_type="x64"
else
local cpu_type="x86"
echo "https://nodejs.org/dist/v${version}/SHASUMS256.txt.asc"
else
# Not implemented.
exit 1
fi
}


download_and_verify_checksums() {
local install_type="$1"
local version="$2"
local tmp_download_dir="$3"

if [ "${NODEJS_CHECK_SIGNATURES}" == "no" ]; then
return 0
fi

## Seems nodejs.org started with around 0.10.0 to release properly signed SHA2-256 checksum files.
if verlte "0.10.0" "$version"
then
local signed_checksum_file="$tmp_download_dir/SHASUMS256.txt.asc"
local signed_checksum_download_url
signed_checksum_download_url="$(get_signed_checksum_download_url "$install_type" "$version")"
if [ -z "${signed_checksum_download_url}" ]; then
if [ "${NODEJS_CHECK_SIGNATURES}" == "strict" ]; then
echo "$version did not provide signed checksums or support for them has not been implemented and NODEJS_CHECK_SIGNATURES=strict is set. Exiting." >&2
exit 1
else
echo "$version did not provide signed checksums or support for them has not been implemented. Continue without signature checking." >&2
return 0
fi
fi
download_file "${signed_checksum_download_url}" "$signed_checksum_file"

if [[ "$operating_system" =~ "Darwin" ]]; then
echo "https://nodejs.org/dist/v${version}/node-v${version}-darwin-${cpu_type}.tar.gz"
else # we'll assume it is linux
echo "https://nodejs.org/dist/v${version}/node-v${version}-linux-${cpu_type}.tar.gz"
local gnugp_verify_command_name
gnugp_verify_command_name="$(command -v gpg gpg2 | head -n 1)"
if [ -z "${gnugp_verify_command_name}" ]; then
echo "You should install GnuPG to verify the authenticity of the downloaded archives: https://www.gnupg.org/" >&2
exit 1
fi
else
echo "https://github.com/nodejs/node/archive/${version}.tar.gz"

(
if [ -z "${GNUPGHOME:-}" ] && [ -d "$HOME/.asdf/keyrings/nodejs" ]; then
export GNUPGHOME="$HOME/.asdf/keyrings/nodejs"
fi

if ! $gnugp_verify_command_name --verify "$signed_checksum_file"; then
echo "Authenticity of checksum file can not be assured! Please be sure to check the README of asdf-nodejs in case you did not yet bootstrap trust. If you already did that then that is the point to become SUSPICIOUS! There must be a reason why this is failing. If you are installing an older NodeJS version you might need to import OpenPGP keys of previous release managers. Exiting." >&2
exit 1
fi
## Mitigates: https://github.com/nodejs/node/issues/6821
local authentic_checksum_file="$tmp_download_dir/authentic_SHASUMS256.txt"
$gnugp_verify_command_name --output "${authentic_checksum_file}" --decrypt "$signed_checksum_file" 2>/dev/null
)
elif [ "${NODEJS_CHECK_SIGNATURES}" == "strict" ]; then
echo "$version did not provide signed checksums or support for them has not been implemented and NODEJS_CHECK_SIGNATURES=strict is set. Exiting." >&2
exit 1
fi
}


verify_archive() {
local tmp_download_dir="$1"

local authentic_checksum_file="$tmp_download_dir/authentic_SHASUMS256.txt"

if [ "${NODEJS_CHECK_SIGNATURES}" == "no" ]; then
return 0
fi

if [ "${NODEJS_CHECK_SIGNATURES}" == "yes" ] && [ ! -e "${authentic_checksum_file}" ]; then
return 0
fi

if verlte "0.10.0" "$version"
then
local archive_file_name
archive_file_name="$(basename "$(get_download_url "$install_type" "$version")")"

(
cd "${tmp_download_dir}"
if ! sha256sum --check <(grep "\s$archive_file_name$" "${authentic_checksum_file}"); then
echo "Authenticity of package archive can not be assured. Exiting." >&2
exit 1
fi
)
fi
}


install_nodejs $ASDF_INSTALL_TYPE $ASDF_INSTALL_VERSION $ASDF_INSTALL_PATH
verlte() {
## https://stackoverflow.com/questions/4023830/how-compare-two-strings-in-dot-separated-version-format-in-bash/4024263#4024263
[ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ]
}


tmp_download_dir="$(mktemp --directory -t 'asdf_nodejs_XXXXXX')"
trap 'rm -rf "${tmp_download_dir}"' EXIT

install_nodejs "$ASDF_INSTALL_TYPE" "$ASDF_INSTALL_VERSION" "$ASDF_INSTALL_PATH" "$tmp_download_dir"
2 changes: 1 addition & 1 deletion bin/list-all
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

echo $(
curl --silent http://semver.io/node/versions \
curl --silent https://semver.io/node/versions \
| grep -E -v '^0\.([0-9])\.'
)