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_cert=""
codesigning_key=""
ca_cert=""
ca_key=""
pgp_key_id=""
print_section_start() {
@ -204,43 +206,103 @@ EOF
print_section_end "ephemeral_pgp_key"
}
create_ephemeral_codesigning_key() {
create_ephemeral_codesigning_keys() {
# 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/"
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"
codesigning_cert="${codesigning_dir}/codesign.crt"
codesigning_key="${codesigning_dir}/codesign.key"
mkdir -p "${ca_dir}/"{private,newcerts,crl}
mkdir -p "${codesigning_dir}"
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 \
-newkey rsa:4096 \
-keyout "${codesigning_key}" \
-nodes \
-sha256 \
-x509 \
-days 365 \
-out "${codesigning_cert}" \
-out "${codesigning_cert}.csr" \
-config "${codesigning_conf}" \
-subj "${codesigning_subj}" \
-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"
}
run_mkarchiso() {
# run mkarchiso
create_ephemeral_pgp_key
create_ephemeral_codesigning_key
create_ephemeral_codesigning_keys
print_section_start "mkarchiso" "Running mkarchiso"
mkdir -p "${output}/" "${tmpdir}/"
GNUPGHOME="${gnupg_homedir}" ./archiso/mkarchiso \
-D "${install_dir}" \
-c "${codesigning_cert} ${codesigning_key}" \
-c "${codesigning_cert} ${codesigning_key} ${ca_cert}" \
-g "${pgp_key_id}" \
-G "${pgp_sender}" \
-o "${output}/" \

View file

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

View file

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

View file

@ -43,7 +43,6 @@ bootmodes=()
airootfs_image_type=""
airootfs_image_tool_options=()
cert_list=()
sign_netboot_artifacts=""
declare -A file_permissions=()
efibootimg=""
efiboot_files=()
@ -94,10 +93,11 @@ usage: ${app_name} [options] <profile_dir>
Default: '${iso_label}'
-P <publisher> Set the 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.
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.
Passed to gpg as the value for --default-key
-G <mbox> Set the PGP signer (must include an email address)
@ -250,14 +250,11 @@ _mkchecksum() {
}
# GPG sign the root file system image.
_mksignature() {
local airootfs_image_filename gpg_options=()
_msg_info "Signing rootfs image..."
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
_mk_pgp_signature() {
local gpg_options=()
local airootfs_image_filename="${1}"
_msg_info "Signing rootfs image using GPG..."
rm -f -- "${airootfs_image_filename}.sig"
# Add gpg sender option if the value is provided
[[ -z "${gpg_sender}" ]] || gpg_options+=('--sender' "${gpg_sender}")
@ -342,6 +339,15 @@ _make_packages() {
exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg"
export ARCHISO_GNUPG_FD
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
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[@]}"
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
exec {ARCHISO_GNUPG_FD}<&-
unset ARCHISO_GNUPG_FD
@ -998,8 +1012,18 @@ _validate_requirements_bootmode_uefi-x64.grub.eltorito() {
_prepare_airootfs_image() {
_run_once "_mkairootfs_${airootfs_image_type}"
_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
_mksignature
_mk_pgp_signature "${airootfs_image_filename}"
fi
if [[ -v cert_list ]]; then
_cms_sign_artifact "${airootfs_image_filename}"
fi
}
@ -1012,6 +1036,32 @@ _export_netboot_artifacts() {
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_netboot_artifacts() {
local _file _dir
@ -1115,6 +1165,26 @@ _validate_common_requirements_buildmode_iso_netboot() {
_msg_error "Packages file '${packages}' does not exist." 0
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
if typeset -f "_mkairootfs_${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() {
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_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
@ -1541,10 +1588,7 @@ _set_overrides() {
fi
[[ ! -v override_gpg_key ]] || gpg_key="$override_gpg_key"
[[ ! -v override_gpg_sender ]] || gpg_sender="$override_gpg_sender"
if [[ -v override_cert_list ]]; then
sign_netboot_artifacts="y"
fi
[[ ! -v override_cert_list ]] || cert_list+=("${override_cert_list[@]}")
[[ ! -v override_cert_list ]] || mapfile -t cert_list < <(realpath -- "${override_cert_list[@]}")
if [[ -v override_quiet ]]; then
quiet="$override_quiet"
elif [[ -z "$quiet" ]]; then
@ -1675,8 +1719,16 @@ _build_buildmode_netboot() {
local run_once_mode="${buildmode}"
_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
_run_once _sign_netboot_artifacts
_cms_sign_artifact "${airootfs_image_filename}"
fi
_run_once _export_netboot_artifacts
}