4

Context

After looking at various methods to add a self-signed, public root certificate (ca.crt) to (snap) Firefox, I experienced some difficulties.

Other solutions

Firefox does not look at the system root ca's by default.

  • However, one answer found a hacky workaround to make it do that anyways.
  • Another answer says the certificates first need to be imported by hand.
  • And the most recent answer, is from 2021, and suggest using the policies.json. That works for the apt version. However, for the snap version, the policies.json file is recognised (and used/imported) by Firefox, but the root ca named ca.crt is not added to the trusted root certificate authorities of snap Firefox (see "snap Firefox" section below for details).

Question

How can one, reliably, and automatically, using Bash, add a root ca named ca.crt to a snap Firefox installation?

Current Solution

The current solution adds the root ca to the apt (and snap) Firefox installation using the following script:

#!/bin/bash
# Adds the root ca certificate named ca.crt to the apt or snap installation of Firefox.

add_self_signed_root_cert_to_firefox() {

  local policies_filepath
  policies_filepath=$(get_firefox_policies_path)

  local root_ca_filepath="$UBUNTU_CERTIFICATE_DIR$CA_PUBLIC_CERT_FILENAME"

  if [ "$(file_exists "$policies_filepath")" == "FOUND" ]; then

    if [ "$(file_contains_string "$root_ca_filepath" "$policies_filepath")" == "NOTFOUND" ]; then

      # Create a backup of the existing policies.
      sudo rm backups/policies.json
      sudo cp "$policies_filepath" backups/policies.json

      # Generate content to put in policies.json.
      local new_json_content
      # shellcheck disable=SC2086
      new_json_content=$(jq '.policies.Certificates += {
                    "Install": ["'$root_ca_filepath'"]
               }' $policies_filepath)

      # Append the content
      echo "$new_json_content" | sudo tee "$policies_filepath" >/dev/null

    else
      red_msg "Your certificate is already added to Firefox."
      exit 6
    fi
  else
    new_json_content="$(create_policies_content_to_add_root_ca "$root_ca_filepath")"
    echo "$new_json_content" | sudo tee "$policies_filepath" >/dev/null
  fi

  # Assert the policy is in the file.
  if [ "$(file_contains_string "$root_ca_filepath" "$policies_filepath")" == "NOTFOUND" ]; then

    red_msg "Error, policy was not found in file:$policies_filepath" "true"
    exit 5
  fi

}

has_added_self_signed_root_ca_cert_to_firefox() {
  local policies_filepath
  policies_filepath=$(get_firefox_policies_path)

  local root_ca_filepath="$UBUNTU_CERTIFICATE_DIR$CA_PUBLIC_CERT_FILENAME"
  read -p "root_ca_filepath=$root_ca_filepath"
  read -p "policies_filepath=$policies_filepath"

  # Assert the root project for this run/these services is created.
  if [ "$(file_exists "certificates/root/$CA_PUBLIC_CERT_FILENAME")" != "FOUND" ]; then
    echo "NOTFOUND"
    return 0
  fi
  if [ "$(file_exists "$UBUNTU_CERTIFICATE_DIR$CA_PUBLIC_CERT_FILENAME")" != "FOUND" ]; then
    echo "NOTFOUND"
    return 0
  fi
  # Assert the root ca hash is as expected.
  if [[ "$(md5sum_is_identical "$UBUNTU_CERTIFICATE_DIR$CA_PUBLIC_CERT_FILENAME" "certificates/root/$CA_PUBLIC_CERT_FILENAME")" != "FOUND" ]]; then
    echo "NOTFOUND"
    return 0
  fi

  if [ "$(file_exists "$policies_filepath")" == "FOUND" ]; then
    if [ "$(file_contains_string "$root_ca_filepath" "$policies_filepath")" == "NOTFOUND" ]; then
      echo "NOTFOUND"
      return 0
    elif [ "$(file_contains_string "$root_ca_filepath" "$policies_filepath")" == "FOUND" ]; then
      echo "FOUND"
      return 0
    fi
  else
    echo "NOTFOUND"
  fi
}

assert_has_added_self_signed_root_ca_cert_to_firefox() {
  if [[ "$(has_added_self_signed_root_ca_cert_to_firefox)" != "FOUND" ]]; then
    echo "Error, root ca certificate was not added to apt Firefox."
    exit 6
  fi
}

firefox_is_installed() {
  if [[ "$(firefox_is_installed_with_apt)" == "FOUND" ]]; then
    echo "FOUND"
  elif [[ "$(firefox_is_installed_with_snap)" == "FOUND" ]]; then
    echo "FOUND"
  else
    echo "NOTFOUND"
  fi
}

firefox_is_installed_with_apt() {
  if dpkg -l firefox &>/dev/null; then
    echo "FOUND"
  else
    echo "NOTFOUND"
  fi
}

firefox_is_installed_with_snap() {
  if snap list | grep -v firefox &>/dev/null; then
    echo "FOUND"
  else
    echo "NOTFOUND"
  fi
}

get_firefox_policies_path() {
  local policies_filepath

  #elif snap list | grep -v firefox &>/dev/null; then
  if [ "$(firefox_via_snap)" == "FOUND" ]; then
    # policies_filepath="/snap/firefox/current/distribution/policies.json"
    sudo mkdir -p "/etc/firefox/policies"
    policies_filepath="/etc/firefox/policies/policies.json"
  # TODO: prevent False positive on apt package if snap Firefox is installed.
  elif [[ "$(apt_package_is_installed "Firefox")" != "FOUND" ]]; then
    #if dpkg -l firefox &>/dev/null; then
    policies_filepath="/etc/firefox/policies/policies.json"
  else
    echo "Error, firefox installation was not found."
    exit 6
  fi
  echo "$policies_filepath"
}

create_policies_content_to_add_root_ca() {
  local root_ca_filepath="$1"
  local inner
  inner=$(
    jq -n --argjson Install '["'"$root_ca_filepath"'"]' \
      '$ARGS.named'
  )

  local medium
  medium=$(
    jq -n --argjson Certificates "$inner" \
      '$ARGS.named'
  )

  local final
  final=$(
    jq -n --argjson policies "$medium" \
      '$ARGS.named'
  )

  echo "$final"
  # Desired output (created with jq as exercise, and for modularity):
  # {
  # "policies": {
  # "Certificates": {
  #     "Install": [
  #                "/usr/local/share/ca-certificates/ca.crt"
  #                ]
  #          }
  #     }
  # }
}

close_restart_close_firefox() {
  # Close firefox.
  pkill firefox >>/dev/null 2>&1

  green_msg "Opening and closing Firefox, please wait 3 seconds." "true"
  # TODO: Verify no errors occur when running Firefox.
  # TODO: Verify Firefox was started successfully.
  nohup firefox >/dev/null 2>&1 &
  sleep 3

  pkill firefox >>/dev/null 2>&1
  green_msg "Proceeding with script." "true"
}

This works for the apt verion of Firefox. However, it does not work for the snap version of Firefox, details are in the next subsection.

Snap Firefox Issue

For the standard installation on Ubuntu (snap Firefox), the content from the /etc/firefox/policies/policies.json is loaded. However, the certificate is not recognised. The link to the ca.crt is recognised: enter image description here

However, it is not listed in the about:preferences > certificates: enter image description here

On the apt version, the certificates are added and shown in that list.

Note

For completeness, I also have a script to swap the snap installation to apt, however, I am looking for a less invasive method that preserves the Firefox settings of the user.

a.t.
  • 2,002
  • 3
  • 26
  • 66

1 Answers1

0

By default snap uses strict confinment

from the docs:

Strict confinement uses security features of the Linux kernel, including AppArmor, seccomp and namespaces, to prevent applications and services accessing the wider system.

It means that by default firefox or whatever process running via files installed by snap won't have access to any files on its host OS which are not part of the snap package.

The solution to that problem is the system-files interface which is used by firefox.

See:

$ sudo snap connections firefox | grep system-files
system-files              firefox:etc-firefox             :system-files                    -

Which means that in order for firefox which was installed by snap to access any file (e.g: policy.json or ca.crt) it must be located under the /etc/firefox directory

ofirule
  • 4,233
  • 2
  • 26
  • 40