From 326cfed7cc59af487cdae1d1a75e1e3a8f84cb67 Mon Sep 17 00:00:00 2001 From: Anton Hvornum Date: Wed, 18 May 2022 16:42:28 +0200 Subject: 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". --- .gitlab/ci/build_archiso.sh | 80 ++++++++++++++++++++++++--- AUTHORS.rst | 1 + CHANGELOG.rst | 2 + archiso/mkarchiso | 130 +++++++++++++++++++++++++++++++------------- 4 files changed, 165 insertions(+), 48 deletions(-) diff --git a/.gitlab/ci/build_archiso.sh b/.gitlab/ci/build_archiso.sh index 5250b51..104792a 100755 --- a/.gitlab/ci/build_archiso.sh +++ b/.gitlab/ci/build_archiso.sh @@ -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}/" \ diff --git a/AUTHORS.rst b/AUTHORS.rst index b03b91e..18207eb 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -36,3 +36,4 @@ Archiso Authors * Øyvind Heggstad * plain linen * Pellegrino Prevete +* Anton Hvornum diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 687b413..1d9eeea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ Changelog Added ----- +- The ability to generate rootfs signatures using openssl CMS module if ``-c`` is given. + Changed ------- diff --git a/archiso/mkarchiso b/archiso/mkarchiso index 5f0c79b..2fbbf66 100755 --- a/archiso/mkarchiso +++ b/archiso/mkarchiso @@ -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] Default: '${iso_label}' -P 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 Set the PGP key ID to be used for signing the rootfs image. Passed to gpg as the value for --default-key -G 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 } -- cgit v1.2.3-70-g09d2