Add the ability to generate rootfs signatures using openssl CMS module if `-c` is given.

(gitlab ci)

Added a CA structure to the codesigning certificates.
This to test the functionality of optional CA being in the signing message.

(mkarchiso)
Removed the ``sign_netboot_artifacts`` variable and instead
we'll now rely on ``if [[ -v cert_list ]]; then``.

Added ``ARCHISO_TLS_FD`` and ``ARCHISO_TLSCA_FD`` environment variables
to override the certificates used. This is so that third party CA's can
be used during building in a meaningful way without distrupting the
CA trust that is shipped by default.

_cms_sign_artifact() was added which signs the rootfs using OpenSSL CMS.
The files will be saved as "${artifact}.cms.sig". That would be for instance
"${isofs_dir}/${install_dir}/${arch}/airootfs.sfs.cms.sig".
This commit is contained in:
Anton Hvornum 2022-05-18 16:42:28 +02:00 committed by David Runge
parent 5f135b4342
commit 326cfed7cc
No known key found for this signature in database
GPG Key ID: 139B09DA5BF0D338
4 changed files with 165 additions and 48 deletions

View File

@ -30,6 +30,8 @@ gnupg_homedir=""
codesigning_dir="" codesigning_dir=""
codesigning_cert="" codesigning_cert=""
codesigning_key="" codesigning_key=""
ca_cert=""
ca_key=""
pgp_key_id="" pgp_key_id=""
print_section_start() { print_section_start() {
@ -204,43 +206,103 @@ EOF
print_section_end "ephemeral_pgp_key" print_section_end "ephemeral_pgp_key"
} }
create_ephemeral_codesigning_key() { create_ephemeral_codesigning_keys() {
# create ephemeral certificates used for codesigning # create ephemeral certificates used for codesigning
print_section_start "ephemeral_codesigning_key" "Creating ephemeral codesigning key" print_section_start "ephemeral_codesigning_key" "Creating ephemeral codesigning keys"
# The exact steps in creating a CA with Codesigning being signed was taken from
# https://jamielinux.com/docs/openssl-certificate-authority/introduction.html
# (slight modifications to the process to not disturb default values of /etc/ssl/openssl.cnf)
codesigning_dir="${tmpdir}/.codesigning/" codesigning_dir="${tmpdir}/.codesigning/"
local codesigning_conf="${codesigning_dir}/openssl.cnf" local ca_dir="${codesigning_dir}/ca/"
local ca_conf="${ca_dir}/certificate_authority.cnf"
local ca_subj="/C=DE/ST=Berlin/L=Berlin/O=Arch Linux/OU=Release Engineering/CN=archlinux.org"
ca_cert="${ca_dir}/cacert.pem"
ca_key="${ca_dir}/private/cakey.pem"
local codesigning_conf="${codesigning_dir}/code_signing.cnf"
local codesigning_subj="/C=DE/ST=Berlin/L=Berlin/O=Arch Linux/OU=Release Engineering/CN=archlinux.org" local codesigning_subj="/C=DE/ST=Berlin/L=Berlin/O=Arch Linux/OU=Release Engineering/CN=archlinux.org"
codesigning_cert="${codesigning_dir}/codesign.crt" codesigning_cert="${codesigning_dir}/codesign.crt"
codesigning_key="${codesigning_dir}/codesign.key" codesigning_key="${codesigning_dir}/codesign.key"
mkdir -p "${ca_dir}/"{private,newcerts,crl}
mkdir -p "${codesigning_dir}" mkdir -p "${codesigning_dir}"
cp -- /etc/ssl/openssl.cnf "${codesigning_conf}" cp -- /etc/ssl/openssl.cnf "${codesigning_conf}"
printf "\n[codesigning]\nkeyUsage=digitalSignature\nextendedKeyUsage=codeSigning\n" >> "${codesigning_conf}" cp -- /etc/ssl/openssl.cnf "${ca_conf}"
touch "${ca_dir}/index.txt"
echo "1000" > "${ca_dir}/serial"
# Prepare the ca configuration for the change in directory
sed -i "s#/etc/ssl#${ca_dir}#g" "${ca_conf}"
# Create the Certificate Authority
openssl req \
-newkey rsa:4096 \
-sha256 \
-nodes \
-x509 \
-new \
-sha256 \
-keyout "${ca_key}" \
-config "${ca_conf}" \
-subj "${ca_subj}" \
-out "${ca_cert}"
cat << EOF >> "${ca_conf}"
[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA ('man x509v3_config').
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
EOF
cat << EOF >> "${codesigning_conf}"
[codesigning]
keyUsage=digitalSignature
extendedKeyUsage=codeSigning, clientAuth, emailProtection
EOF
openssl req \ openssl req \
-newkey rsa:4096 \ -newkey rsa:4096 \
-keyout "${codesigning_key}" \ -keyout "${codesigning_key}" \
-nodes \ -nodes \
-sha256 \ -sha256 \
-x509 \ -out "${codesigning_cert}.csr" \
-days 365 \
-out "${codesigning_cert}" \
-config "${codesigning_conf}" \ -config "${codesigning_conf}" \
-subj "${codesigning_subj}" \ -subj "${codesigning_subj}" \
-extensions codesigning -extensions codesigning
# Sign the code signing certificate with the CA
openssl ca \
-batch \
-config "${ca_conf}" \
-extensions v3_intermediate_ca \
-days 3650 \
-notext \
-md sha256 \
-in "${codesigning_cert}.csr" \
-out "${codesigning_cert}"
print_section_end "ephemeral_codesigning_key" print_section_end "ephemeral_codesigning_key"
} }
run_mkarchiso() { run_mkarchiso() {
# run mkarchiso # run mkarchiso
create_ephemeral_pgp_key create_ephemeral_pgp_key
create_ephemeral_codesigning_key create_ephemeral_codesigning_keys
print_section_start "mkarchiso" "Running mkarchiso" print_section_start "mkarchiso" "Running mkarchiso"
mkdir -p "${output}/" "${tmpdir}/" mkdir -p "${output}/" "${tmpdir}/"
GNUPGHOME="${gnupg_homedir}" ./archiso/mkarchiso \ GNUPGHOME="${gnupg_homedir}" ./archiso/mkarchiso \
-D "${install_dir}" \ -D "${install_dir}" \
-c "${codesigning_cert} ${codesigning_key}" \ -c "${codesigning_cert} ${codesigning_key} ${ca_cert}" \
-g "${pgp_key_id}" \ -g "${pgp_key_id}" \
-G "${pgp_sender}" \ -G "${pgp_sender}" \
-o "${output}/" \ -o "${output}/" \

View File

@ -36,3 +36,4 @@ Archiso Authors
* Øyvind Heggstad <heggstad@gmail.com> * Øyvind Heggstad <heggstad@gmail.com>
* plain linen <bcdedit@hotmail.com> * plain linen <bcdedit@hotmail.com>
* Pellegrino Prevete <pellegrinoprevete@gmail.com> * Pellegrino Prevete <pellegrinoprevete@gmail.com>
* Anton Hvornum <anton@hvornum.se>

View File

@ -8,6 +8,8 @@ Changelog
Added Added
----- -----
- The ability to generate rootfs signatures using openssl CMS module if ``-c`` is given.
Changed Changed
------- -------

View File

@ -43,7 +43,6 @@ bootmodes=()
airootfs_image_type="" airootfs_image_type=""
airootfs_image_tool_options=() airootfs_image_tool_options=()
cert_list=() cert_list=()
sign_netboot_artifacts=""
declare -A file_permissions=() declare -A file_permissions=()
efibootimg="" efibootimg=""
efiboot_files=() efiboot_files=()
@ -94,10 +93,11 @@ usage: ${app_name} [options] <profile_dir>
Default: '${iso_label}' Default: '${iso_label}'
-P <publisher> Set the ISO publisher -P <publisher> Set the ISO publisher
Default: '${iso_publisher}' Default: '${iso_publisher}'
-c [cert ..] Provide certificates for codesigning of netboot artifacts -c [cert ..] Provide certificates for codesigning of netboot artifacts as
well as the rootfs artifact.
Multiple files are provided as quoted, space delimited list. Multiple files are provided as quoted, space delimited list.
The first file is considered as the signing certificate, The first file is considered as the signing certificate,
the second as the key. the second as the key and the third as the optional certificate authority.
-g <gpg_key> Set the PGP key ID to be used for signing the rootfs image. -g <gpg_key> Set the PGP key ID to be used for signing the rootfs image.
Passed to gpg as the value for --default-key Passed to gpg as the value for --default-key
-G <mbox> Set the PGP signer (must include an email address) -G <mbox> Set the PGP signer (must include an email address)
@ -250,14 +250,11 @@ _mkchecksum() {
} }
# GPG sign the root file system image. # GPG sign the root file system image.
_mksignature() { _mk_pgp_signature() {
local airootfs_image_filename gpg_options=() local gpg_options=()
_msg_info "Signing rootfs image..." local airootfs_image_filename="${1}"
if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then _msg_info "Signing rootfs image using GPG..."
airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs"
elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs"
fi
rm -f -- "${airootfs_image_filename}.sig" rm -f -- "${airootfs_image_filename}.sig"
# Add gpg sender option if the value is provided # Add gpg sender option if the value is provided
[[ -z "${gpg_sender}" ]] || gpg_options+=('--sender' "${gpg_sender}") [[ -z "${gpg_sender}" ]] || gpg_options+=('--sender' "${gpg_sender}")
@ -342,6 +339,15 @@ _make_packages() {
exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg" exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg"
export ARCHISO_GNUPG_FD export ARCHISO_GNUPG_FD
fi fi
if [[ -v cert_list[0] ]]; then
exec {ARCHISO_TLS_FD}<>"${cert_list[0]}"
export ARCHISO_TLS_FD
fi
if [[ -v cert_list[2] ]]; then
exec {ARCHISO_TLSCA_FD}<>"${cert_list[2]}"
export ARCHISO_TLSCA_FD
fi
# Unset TMPDIR to work around https://bugs.archlinux.org/task/70580 # Unset TMPDIR to work around https://bugs.archlinux.org/task/70580
if [[ "${quiet}" = "y" ]]; then if [[ "${quiet}" = "y" ]]; then
@ -350,6 +356,14 @@ _make_packages() {
env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}"
fi fi
if [[ -v cert_list[0] ]]; then
exec {ARCHISO_TLS_FD}<&-
unset ARCHISO_TLS_FD
fi
if [[ -v cert_list[2] ]]; then
exec {ARCHISO_TLSCA_FD}<&-
unset ARCHISO_TLSCA_FD
fi
if [[ -n "${gpg_key}" ]]; then if [[ -n "${gpg_key}" ]]; then
exec {ARCHISO_GNUPG_FD}<&- exec {ARCHISO_GNUPG_FD}<&-
unset ARCHISO_GNUPG_FD unset ARCHISO_GNUPG_FD
@ -998,8 +1012,18 @@ _validate_requirements_bootmode_uefi-x64.grub.eltorito() {
_prepare_airootfs_image() { _prepare_airootfs_image() {
_run_once "_mkairootfs_${airootfs_image_type}" _run_once "_mkairootfs_${airootfs_image_type}"
_mkchecksum _mkchecksum
if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs"
elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs"
fi
if [[ -n "${gpg_key}" ]]; then if [[ -n "${gpg_key}" ]]; then
_mksignature _mk_pgp_signature "${airootfs_image_filename}"
fi
if [[ -v cert_list ]]; then
_cms_sign_artifact "${airootfs_image_filename}"
fi fi
} }
@ -1012,6 +1036,32 @@ _export_netboot_artifacts() {
du -hs -- "${out_dir}/${install_dir}" du -hs -- "${out_dir}/${install_dir}"
} }
_cms_sign_artifact() {
local artifact="${1}"
local openssl_flags=(
"-sign"
"-binary"
"-nocerts"
"-noattr"
"-outform" "DER" "-out" "${artifact}.cms.sig"
"-in" "${artifact}"
"-signer" "${cert_list[0]}"
"-inkey" "${cert_list[1]}"
)
if (( ${#cert_list[@]} > 2 )); then
openssl_flags+=("-certfile" "${cert_list[2]}")
fi
_msg_info "Signing ${artifact} image using openssl cms..."
rm -f -- "${artifact}.cms.sig"
openssl cms "${openssl_flags[@]}"
_msg_info "Done!"
}
# sign build artifacts for netboot # sign build artifacts for netboot
_sign_netboot_artifacts() { _sign_netboot_artifacts() {
local _file _dir local _file _dir
@ -1115,6 +1165,26 @@ _validate_common_requirements_buildmode_iso_netboot() {
_msg_error "Packages file '${packages}' does not exist." 0 _msg_error "Packages file '${packages}' does not exist." 0
fi fi
if [[ -v cert_list ]]; then
# Check if the certificate files exist
for _cert in "${cert_list[@]}"; do
if [[ ! -e "${_cert}" ]]; then
(( validation_error=validation_error+1 ))
_msg_error "File '${_cert}' does not exist." 0
fi
done
# Check if there are at least three certificate files to sign netboot and rootfs.
if (( ${#cert_list[@]} < 2 )); then
(( validation_error=validation_error+1 ))
_msg_error "Two certificates are required for codesigning netboot artifacts, but '${cert_list[*]}' is provided." 0
fi
if ! command -v openssl &> /dev/null; then
(( validation_error=validation_error+1 ))
_msg_error "Validating build mode '${_buildmode}': openssl is not available on this host. Install 'openssl'!" 0
fi
fi
# Check if the specified airootfs_image_type is supported # Check if the specified airootfs_image_type is supported
if typeset -f "_mkairootfs_${airootfs_image_type}" &> /dev/null; then if typeset -f "_mkairootfs_${airootfs_image_type}" &> /dev/null; then
if typeset -f "_validate_requirements_airootfs_image_type_${airootfs_image_type}" &> /dev/null; then if typeset -f "_validate_requirements_airootfs_image_type_${airootfs_image_type}" &> /dev/null; then
@ -1156,31 +1226,8 @@ _validate_requirements_buildmode_iso() {
} }
_validate_requirements_buildmode_netboot() { _validate_requirements_buildmode_netboot() {
local _override_cert_list=()
if [[ "${sign_netboot_artifacts}" == "y" ]]; then
# Check if the certificate files exist
for _cert in "${cert_list[@]}"; do
if [[ -e "${_cert}" ]]; then
_override_cert_list+=("$(realpath -- "${_cert}")")
else
(( validation_error=validation_error+1 ))
_msg_error "File '${_cert}' does not exist." 0
fi
done
cert_list=("${_override_cert_list[@]}")
# Check if there are at least two certificate files
if (( ${#cert_list[@]} < 2 )); then
(( validation_error=validation_error+1 ))
_msg_error "Two certificates are required for codesigning, but '${cert_list[*]}' is provided." 0
fi
fi
_validate_common_requirements_buildmode_iso_netboot _validate_common_requirements_buildmode_iso_netboot
_validate_common_requirements_buildmode_all _validate_common_requirements_buildmode_all
if ! command -v openssl &> /dev/null; then
(( validation_error=validation_error+1 ))
_msg_error "Validating build mode '${_buildmode}': openssl is not available on this host. Install 'openssl'!" 0
fi
} }
# SYSLINUX El Torito # SYSLINUX El Torito
@ -1541,10 +1588,7 @@ _set_overrides() {
fi fi
[[ ! -v override_gpg_key ]] || gpg_key="$override_gpg_key" [[ ! -v override_gpg_key ]] || gpg_key="$override_gpg_key"
[[ ! -v override_gpg_sender ]] || gpg_sender="$override_gpg_sender" [[ ! -v override_gpg_sender ]] || gpg_sender="$override_gpg_sender"
if [[ -v override_cert_list ]]; then [[ ! -v override_cert_list ]] || mapfile -t cert_list < <(realpath -- "${override_cert_list[@]}")
sign_netboot_artifacts="y"
fi
[[ ! -v override_cert_list ]] || cert_list+=("${override_cert_list[@]}")
if [[ -v override_quiet ]]; then if [[ -v override_quiet ]]; then
quiet="$override_quiet" quiet="$override_quiet"
elif [[ -z "$quiet" ]]; then elif [[ -z "$quiet" ]]; then
@ -1675,8 +1719,16 @@ _build_buildmode_netboot() {
local run_once_mode="${buildmode}" local run_once_mode="${buildmode}"
_build_iso_base _build_iso_base
if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs"
elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
airootfs_image_filename="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs"
fi
if [[ -v cert_list ]]; then if [[ -v cert_list ]]; then
_run_once _sign_netboot_artifacts _run_once _sign_netboot_artifacts
_cms_sign_artifact "${airootfs_image_filename}"
fi fi
_run_once _export_netboot_artifacts _run_once _export_netboot_artifacts
} }