# Installing OpenAI's ChatGPT Desktop without trusting the download. Sourced, # not run. # # OpenAI signs both its packages and its repository metadata, with one key, and # publishes neither that key nor its fingerprint anywhere a first install could # fetch them. The documented instructions are "download this RPM and install # it" -- and the RPM's own root scriptlet is what writes the repository file and # drops the key into /etc/pki/rpm-gpg. Following them means handing an # unverified download to root and letting it decide afterwards what to trust, # which is the one thing this repository will not do with a network response. # # So the key is pinned here instead. setup/keys/ carries a copy and records # where it came from; this verifies that copy's fingerprint, installs it, and # writes the repository itself with gpgcheck on. dnf then checks the metadata # signature and the package signature against that key before anything runs as # root, and every later upgrade goes through the same repository and the same # key. # # Two callers, which is why this is a library: install-packages, for a machine # being built, and the migration that replaces the community codex-desktop # build on machines that predate the official package. # The key that signs the packages and the repository metadata. Pinned, so a # substituted key is a failure here rather than a silent change of publisher. CHATGPT_KEY_FINGERPRINT="3BFA0E4AE8B8CC16A2D9BA684A3B4A566C4660E4" # `$basearch` stays literal: dnf expands it, and this is the same base URL the # package's own scriptlet configures. CHATGPT_REPO_BASEURL="https://persistent.oaistatic.com/codex-app-prod/linux/rpm/\$basearch" CHATGPT_REPO_FILE="/etc/yum.repos.d/chatgpt.repo" CHATGPT_KEY_FILE="/etc/pki/rpm-gpg/RPM-GPG-KEY-chatgpt" chatgpt_pinned_key() { printf '%s/setup/keys/RPM-GPG-KEY-chatgpt' "${PANAMA_PATH:-$HOME/.local/share/Panama}" } # The fingerprint of the pinned copy. Nonzero when it cannot be read at all, # which the caller reports differently from a key that reads but is the wrong # one. chatgpt_pinned_fingerprint() { local key key="$(chatgpt_pinned_key)" [[ -r "$key" ]] || return 1 gpg --show-keys --with-colons "$key" 2>/dev/null \ | awk -F: '$1 == "fpr" { print $10; exit }' } # Fails without touching anything when the pinned key is missing, unreadable, # or not the key this repository says it is. Everything below assumes it passed. chatgpt_verify_pinned_key() { local found if ! command -v gpg >/dev/null 2>&1; then printf 'gpg is missing, so the pinned ChatGPT signing key cannot be verified.\n' >&2 return 1 fi if ! found="$(chatgpt_pinned_fingerprint)"; then printf 'The pinned ChatGPT signing key is missing: %s\n' "$(chatgpt_pinned_key)" >&2 return 1 fi if [[ "$found" != "$CHATGPT_KEY_FINGERPRINT" ]]; then printf 'The pinned ChatGPT signing key is %s, not the expected %s.\n' \ "${found:-unreadable}" "$CHATGPT_KEY_FINGERPRINT" >&2 return 1 fi } # Installs the verified key and the repository that names it, so the install # after this one is a signature check rather than an act of faith. # # Takes the command that gets root, because the two callers ask for it # differently: plain `sudo` from the installer, which authenticated once at the # top of the run, and `panama-sudo --reason ...` from a migration, whose prompt # has to say which repair it is for. chatgpt_install_repository() { local -a sudo_cmd=("$@") (( ${#sudo_cmd[@]} > 0 )) || sudo_cmd=(sudo) chatgpt_verify_pinned_key || return 1 "${sudo_cmd[@]}" install -D -m 0644 "$(chatgpt_pinned_key)" "$CHATGPT_KEY_FILE" || return 1 "${sudo_cmd[@]}" rpmkeys --import "$CHATGPT_KEY_FILE" || return 1 # Written here rather than left to the package's scriptlet, because the # point of it is to exist -- with gpgcheck on and this key named -- before # the first install rather than after it. Same base URL and same key the # scriptlet writes, so it finds nothing to change later. printf '%s\n' \ '[openai-chatgpt]' \ 'name=ChatGPT' \ "baseurl=$CHATGPT_REPO_BASEURL" \ 'enabled=1' \ 'type=rpm-md' \ 'gpgcheck=1' \ 'repo_gpgcheck=1' \ "gpgkey=file://$CHATGPT_KEY_FILE" \ | "${sudo_cmd[@]}" tee "$CHATGPT_REPO_FILE" >/dev/null || return 1 }