code:
#!/usr/bin/perl -w

# This script reads the ChangeLog.txt file and outputs it to stdout
# in the format for the blog post.

use strict;
use FindBin;

sub version_type {
  return $_[0] =~ 'a' ? 'alpha' : 'release';
}

my ($changelog, $current_tbversion, $last_tbversion);

if (!open(CHANGELOG, '<', "$FindBin::Bin/../ChangeLog.txt")) {
  print STDERR "Error opening changelog file\n";
  exit 1;
}

foreach (<CHANGELOG>) {
  if (m/^Tor Browser ([^\s]+) -/) {
    if ($current_tbversion) {
      $last_tbversion = $1;
      last if version_type($current_tbversion) eq version_type($last_tbversion);
      next;
    }
    $current_tbversion = $1;
    next;
  }

  next if $last_tbversion;

  # Remove one space at the begining of all lines
  s/^\s//;

  # Replace '*' by '-'
  s/^(\s*)\*/$1-/;

  s/&/&/; s/</</; s/>/>/;

  # Change bug numbers to links
  s|Bug (\d+): ([^\[]+) \[([^\]]+)\]|[Bug $3#$1](https://gitlab.torproject.org/tpo/applications/$3/-/issues/$1): $2|;

  $changelog .= $_;
}

my $changelog_branch = 'main';
if (! ( $current_tbversion =~ m/a/ ) ) {
  my @v = split(/\./, $current_tbversion);
  $changelog_branch = "maint-$v[0].$v[1]";
}
print "The full changelog since [Tor Browser $last_tbversion](https://gitweb.torproject.org/builders/tor-browser-build.git/plain/projects/tor-browser/Bundle-Data/Docs/ChangeLog.txt?h=$changelog_branch) is:\n\n";
print $changelog;

path: tools/changelog-format-blog-post


code:
#!/usr/bin/perl -w
use strict;
use IO::CaptureOutput qw(capture_exec);
use FindBin;
use lib "$FindBin::Bin/../rbm/lib";
use RBM;
use File::Slurp qw(read_dir write_file read_file);
use Getopt::Long;

my %options;
my @options_list = qw(dry-run! list-used-files=s project=s target=s@);
Getopt::Long::GetOptionsFromArray(\@ARGV, \%options, @options_list) || exit 1;

sub clean_file {
    my ($file, $used_files) = @_;
    return if $used_files->{$file};
    if (-d $file) {
        my @l = read_dir($file);
        foreach my $subfile (@l) {
            clean_file("$file/$subfile", $used_files);
        }
        @l = read_dir($file);
        rmdir $file unless @l;
    } else {
        print "Removing file $file\n";
        unlink $file unless $options{'dry-run'};
    }
}

sub get_project_input_files {
    my ($project, @targets) = @_;
    print "Getting input files for $project ", join(' ', @targets), "\n";
    $RBM::config->{run}{target} = \@targets;
    my $res = RBM::project_config($project, 'input_files_paths',
                                        {error_if_undef => 1});
    return ref $res eq 'ARRAY' ? @$res : ();
}

sub get_input_files {
    my ($c) = @_;
    my @files;
    foreach my $p (@$c) {
        my $tmp = File::Temp->new();
        my @args = ("$FindBin::Bin/$FindBin::Script", '--list-used-files', $tmp,
                    '--project', $p->{project});
        foreach my $target (@{$p->{target}}) {
            push @args, ('--target', $target);
        }
        RBM::exit_error 'Error getting used files' unless system(@args) == 0;
        push @files, map { chomp; $_ } read_file($tmp);
    }
    return @files;
}

RBM::load_config;
RBM::load_system_config;
RBM::load_local_config;
RBM::set_default_env;

if ($options{'list-used-files'}) {
    if (!$options{project} || !$options{target}) {
        RBM::exit_error 'Missing option project or target';
    }
    my @files = get_project_input_files($options{project}, @{$options{target}});
    write_file($options{'list-used-files'}, join("\n", @files));
    exit 0;
}

my $clean = RBM::project_config('clean', 'var/clean');
if (!$clean) {
    print STDERR "Clean configuration is missing. ",
                 "You should add it to rbm.local.conf.\n";
    exit 1;
}

my @files = get_input_files($clean->{HEAD}) if $clean->{HEAD};

foreach my $branch (keys %$clean) {
    next if $branch eq 'HEAD';
    print "Checking out $branch branch\n";
    RBM::exit_error("Error checking out $branch branch")
        unless system('git', 'checkout', $branch) == 0;
    RBM::exit_error("Error running git submodule update --init")
        unless system('git', 'submodule', 'update', '--init') == 0;
    push @files, get_input_files($clean->{$branch});
    RBM::exit_error('Error checking out @{-1}')
        unless system('git', 'checkout', '@{-1}') == 0;
    RBM::exit_error("Error running git submodule update --init")
        unless system('git', 'submodule', 'update', '--init') == 0;
}
my %used_files = map { $_ => 1 } @files;
my $outdir = $RBM::config->{basedir} . '/out';
clean_file($outdir, \%used_files);

repo_name: JeremyRand/tor

path: tools/clean-old

license: mit


code:
#!/usr/bin/perl -w
#
# This script converts all dmg files from the current directory and
# listed in the sha256sums-unsigned-build.txt file to full update
# mar files. After code signing the dmg files, this script can be used
# to update the mar files.
#
# A recent version of p7zip is required to extract the dmg files, such
# as 15.14. The version in Debian Jessie (9.20) is not recent enough.
# It is possible to install the p7zip-full package from Debian testing,
# or build p7zip from sources:
#  $ p7zipdir=/some_directory/p7zip
#  $ mkdir $p7zipdir
#  $ cd $p7zipdir
#  $ wget http://snapshot.debian.org/archive/debian/20160417T044336Z/pool/main/p/p7zip/p7zip_15.14.1%2Bdfsg.orig.tar.xz
#  $ echo 'e9e696e2fa77b00445a4d85fa07506debeae01943fdc1bee1472152d7d1386af p7zip_15.14.1+dfsg.orig.tar.xz' | sha256sum -c
#  $ wget http://snapshot.debian.org/archive/debian/20160515T161830Z/pool/main/p/p7zip/p7zip_15.14.1%2Bdfsg-2.debian.tar.xz
#  $ echo 'f4db6803535fc30b6ae9db5aabfd9f57a851c6773d72073847ec5e3731b7af37  p7zip_15.14.1+dfsg-2.debian.tar.xz' | sha256sum -c
#  $ tar xvf p7zip_15.14.1+dfsg-2.debian.tar.xz
#  $ tar xvf p7zip_15.14.1+dfsg.orig.tar.xz
#  $ cd p7zip_15.14.1/
#  $ for patch in $(cat ../debian/patches/series ); do patch -p1 < ../debian/patches/$patch; done
#  $ make 7z
#  $ mkdir $p7zipdir/bin
#  $ echo '#!/bin/sh' > $p7zipdir/bin/7z
#  $ echo "export LD_LIBRARY_PATH=$PWD/bin" >> $p7zipdir/bin/7z
#  $ echo "exec $PWD/bin/7z "'"$@"' >> $p7zipdir/bin/7z
#  $ chmod +x $p7zipdir/bin/7z
#  $ export "PATH=$p7zipdir/bin:$PATH"

use strict;
use IO::CaptureOutput qw(capture_exec);
use File::Slurp;
use File::Find;
use Parallel::ForkManager;
use Cwd;

# If the application is not TorBrowser (for instance, TorMessenger)
# set the application name in the TOR_APPNAME_BUNDLE_OSX,
# TOR_APPNAME_DMGFILE and TOR_APPNAME_MARFILE environment variables
my $appname = $ENV{TOR_APPNAME_BUNDLE_OSX} // 'Tor Browser';
my $appname_dmg = $ENV{TOR_APPNAME_DMGFILE} // 'TorBrowser';
my $appname_mar = $ENV{TOR_APPNAME_MARFILE} // 'tor-browser';

sub exit_error {
    print STDERR "Error: ", $_[0], "\n";
    chdir '/';
    exit (exists $_[1] ? $_[1] : 1);
}

sub osname {
    my ($osname) = capture_exec('uname', '-s');
    my ($arch) = capture_exec('uname', '-m');
    chomp($osname, $arch);
    if ($osname eq 'Linux' && $arch eq 'x86_64') {
        return 'linux64';
    }
    if ($osname eq 'Linux' && $arch =~ m/^i.86$/) {
        return 'linux32';
    }
    exit_error 'Unknown OS';
}

my $martools_tmpdir;
sub extract_martools {
    my $osname = osname;
    my $marzip = getcwd . "/mar-tools-$osname.zip";
    $martools_tmpdir = File::Temp->newdir();
    my $old_cwd = getcwd;
    chdir $martools_tmpdir;
    my (undef, undef, $success) = capture_exec('unzip', $marzip);
    chdir $old_cwd;
    exit_error "Error extracting $marzip" unless $success;
    $ENV{PATH} = "$martools_tmpdir/mar-tools:$ENV{PATH}";
    if ($ENV{LD_LIBRARY_PATH}) {
        $ENV{LD_LIBRARY_PATH} .= ":$martools_tmpdir/mar-tools";
    } else {
        $ENV{LD_LIBRARY_PATH} = "$martools_tmpdir/mar-tools";
    }
    $ENV{MAR} = "$martools_tmpdir/mar-tools/mar";
    $ENV{MSBDIFF} = "$martools_tmpdir/mar-tools/mbsdiff";
}

sub get_nbprocs {
    return $ENV{NUM_PROCS} if defined $ENV{NUM_PROCS};
    if (-f '/proc/cpuinfo') {
        return scalar grep { m/^processor\s+:\s/ } read_file '/proc/cpuinfo';
    }
    return 4;
}

sub get_dmg_files_from_sha256sums {
    exit_error "Missing sha256sums-unsigned-build.txt file"
        unless -f 'sha256sums-unsigned-build.txt';
    my @files;
    foreach my $line (read_file('sha256sums-unsigned-build.txt')) {
        my (undef, $filename) = split '  ', $line;
        chomp $filename;
        next unless $filename =~ m/^$appname_dmg-(.+)-osx64_(.+)\.dmg$/;
        push @files, { filename => $filename, version => $1, lang => $2 };
    }
    return @files;
}

sub convert_files {
    my ($channel) = @_;
    my $pm = Parallel::ForkManager->new(get_nbprocs);
    $pm->run_on_finish(sub { print "Finished $_[2]\n" });
    foreach my $file (get_dmg_files_from_sha256sums) {
        # The 'ja' locale is a special case: it is called 'ja-JP-mac'
        # internally on OSX, but the dmg file still uses 'ja' to avoid
        # confusing users.
        my $mar_lang = $file->{lang} eq 'ja' ? 'ja-JP-mac' : $file->{lang};
        my $output = "$appname_mar-osx64-$file->{version}_$mar_lang.mar";
        my $step_name = "$file->{filename} -> $output";
        print "Starting $step_name\n";
        $pm->start($step_name) and next;
        my $tmpdir = File::Temp->newdir();
        my (undef, $err, $success) = capture_exec('7z', 'x', "-o$tmpdir",
                                                        $file->{filename});
        exit_error "Error extracting $file->{filename}: $err" unless $success;

        # 7z does not currently extract file permissions from the dmg files
        # so we also extract the old mar file to copy the permissions
        # https://trac.torproject.org/projects/tor/ticket/20210
        my $tmpdir_oldmar = File::Temp->newdir();
        my $oldmar = getcwd . '/' . $output;
        exit_error "Error extracting $output"
                unless system('mar', '-C', $tmpdir_oldmar, '-x', $oldmar) == 0;
        my $wanted = sub {
            my $file = $File::Find::name;
            $file =~ s{^$tmpdir/$appname\.app/}{};
            if (-f "$tmpdir_oldmar/$file") {
                my (undef, undef, $mode) = stat("$tmpdir_oldmar/$file");
                chmod $mode, $File::Find::name;
                return;
            }
            chmod 0644, $File::Find::name if -f $File::Find::name;
            chmod 0755, $File::Find::name if -d $File::Find::name;
        };
        find($wanted, "$tmpdir/$appname.app");

        unlink $output;
        local $ENV{MOZ_PRODUCT_VERSION} = $file->{version};
        local $ENV{MAR_CHANNEL_ID} = "torbrowser-torproject-$channel";
        local $ENV{TMPDIR} = $tmpdir;
        (undef, $err, $success) =  capture_exec('make_full_update.sh', '-q',
                                        $output, "$tmpdir/$appname.app");
        exit_error "Error updating $output: $err" unless $success;
        exit_error "make_full_update.sh failed. $output does not exist."
            unless -f $output;
        $pm->finish;
    }
    $pm->wait_all_children;
}

sub remove_incremental_mars {
    exit_error "Missing sha256sums-unsigned-build.incrementals.txt file"
        unless -f 'sha256sums-unsigned-build.incrementals.txt';
    foreach my $line (read_file('sha256sums-unsigned-build.incrementals.txt')) {
        my (undef, $filename) = split '  ', $line;
        chomp $filename;
        next unless $filename =~ m/^$appname_mar-osx64.+\.incremental\.mar$/;
        next unless -f $filename;
        print "Removing $filename\n";
        unlink $filename;
    }
}

# Set LC_ALL=C to avoid reproducibility issues when creating mar files
$ENV{LC_ALL} = 'C';

exit_error "Please specify update channel" unless @ARGV == 1;
my $channel = $ARGV[0];

extract_martools;
convert_files $channel;
remove_incremental_mars;


code:
#!/usr/bin/env python3

import urllib.request
import json
import base64
import sys

def fetch(x):
  with urllib.request.urlopen(x) as response:
    return response.read()

def find_addon(addons, addon_id):
  results = addons['results']
  for x in results:
    addon = x['addon']
    if addon['guid'] == addon_id:
      return addon
  sys.exit("Error: cannot find addon " + addon_id)

def fetch_and_embed_icons(addons):
  results = addons['results']
  for x in results:
    addon = x['addon']
    icon_data = fetch(addon['icon_url'])
    addon['icon_url'] = 'data:image/png;base64,' + str(base64.b64encode(icon_data), 'utf8')

def patch_https_everywhere(addons):
  addon = find_addon(addons, 'https-everywhere@eff.org')
  addon['guid'] = 'https-everywhere-eff@eff.org'
  addon['url'] = 'https://www.eff.org/https-everywhere'

def main(argv):
  amo_collection = argv[0] if argv else '83a9cccfe6e24a34bd7b155ff9ee32'
  url = 'https://services.addons.mozilla.org/api/v4/accounts/account/mozilla/collections/' + amo_collection + '/addons/'
  data = json.loads(fetch(url))
  fetch_and_embed_icons(data)
  patch_https_everywhere(data)
  data['results'].sort(key=lambda x: x['addon']['guid'])
  find_addon(data, '{73a6fe31-595d-460b-a920-fcc0f8843232}') # Check that NoScript is present
  print(json.dumps(data, indent=2, ensure_ascii=False))

if __name__ == "__main__":
   main(sys.argv[1:])


code:
The keyring/ directory contains some gpg keyring files that we use
during the build to verify gpg signatures on downloaded files, or git
tags. In order to be able to continue to use a git tag even after the
key or sub-key that signed it expired (which is common when one is
rotating sub-keys frequently), we configured gpg to ignore key expirations
when verifying git tag signatures. However this also means that we should
make sure that our keyring files do not contain expired keys or subkeys
that are not supposed to be used anymore.

This directory contains some scripts that can help clean the keyring
files.

The complete process for cleaning keyring files starts with:

 - Run `list-all-keyrings` to see if we include any expired key or sub-key.

Then for each expired key or sub-key:

 - Check if the expiration is expected, and do nothing in that case.

 - Check if the owner of that key or sub-key extended it, and in that
   case add the updated key or sub-key.

 - If a key is not needed anymore (but other keys in the keyring are
   still needed), remove it with `gpg --delete-keys <key>`.

 - If a sub-key is not needed anymore, but the main key still contains
   at least one other valid sub-key, use `drop-expired-sub-keys` to
   remove the expired sub-key.

path: tools/keyring/README


code:
#!/bin/bash

# Drop expired and revoked sub-keys from a keyring file
#
# usage: drop-expired-sub-keys <keyring-file>
#
# Note: this script only handles the case where all expired and revoked
# sub-keys should be removed, so it cannot be used in the cases where
# some of the expired sub-keys need to be kept. It is also only handling
# one small part of the process to clean the keyring files and is not
# supposed to be run on all keyring files.
#
# See the README file for the complete process for cleaning keyring files.

set -e
keyring="$1"
test -f "$keyring"
tmpfile=$(mktemp)
gpg --no-auto-check-trustdb --no-default-keyring --keyring "$keyring" --export-options export-clean --export-filter 'drop-subkey=expired -t || revoked -t' --export > "$tmpfile"
mv -f "$tmpfile" "$keyring"

path: tools/keyring/drop-expired-sub-keys


code:
#!/bin/sh

# List all keys included in our keyring files, including expired sub-keys.

set -e
cd $(dirname "$0")/../..
for keyring in ./keyring/*.gpg
do
    gpg --no-auto-check-trustdb --list-options show-unusable-subkeys,show-keyring --no-default-keyring --list-keys --keyring "$keyring"
done

path: tools/keyring/list-all-keyrings


code:
#!/bin/sh

# Copyright (c) 2020, The Tor Project, Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#
#     * Neither the names of the copyright owners nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# Usage:
# 1) Let SIGNMAR point to your signmar binary
# 2) Let LD_LIBRARY_PATH point to the mar-tools directory
# 3) Let NSS_DB_DIR point to the directory containing the database with the
#    signing certificate to check against.
# 4) Let CHANNEL be the expected update channel
#
#    To create the database to use for signature checking import the
#    release*.der certificate of your choice found in
#    toolkit/mozapps/update/updater by issuing the following commands:
#
#    mkdir nssdb
#    certutil -d nssdb -N --empty-password
#    certutil -A -n "marsigner" -t,, -d nssdb -i /path/to/.der
#
# 4) Change into the directory containing the MAR files and the
#    sha256sums-unsigned-build.txt/sha256sums-unsigned-build.incrementals.txt.
# 5) Run /path/to/marsigning_check.sh

if [ -z "$SIGNMAR" ]
then
  echo "The path to your signmar binary is missing!"
  exit 1
fi

if [ -z "$LD_LIBRARY_PATH" ]
then
  echo "The library search path to your mar-tools directory is missing!"
  exit 1
fi

if [ -z "$NSS_DB_DIR" ]
then
  echo "The path to your nssdb directory is missing!"
  exit 1
fi

if [ -z "$CHANNEL" ]
then
  echo "The update channel is missing! ([nightly|alpha|release])"
  exit 1
fi

unsigned_mars=0
badsigned_mars=0
not_reproduced_mars=0
# XXX: Stripping the signature of signed macOS MAR files is currently not
# expected to be reproducible, see: #20254.
not_reproduced_mars_expected=0

mkdir tmp

for f in *.mar; do
  case $f in
    *.incremental.mar) sha256_txt=$(grep "$f" \
      sha256sums-unsigned-build.incrementals.txt);;
    *) sha256_txt=$(grep "$f" sha256sums-unsigned-build.txt);;
  esac

  # Test 1: Is the MAR file correctly signed?
  echo "Verifying the MAR signature of $f..."
  if ! $SIGNMAR -d "$NSS_DB_DIR" -n marsigner -v "$f"
  then
    # Something went wrong. Let's figure out what.
    if [ "$sha256_txt" = "$(sha256sum "$f")" ]
    then
      echo "$f has still the SHA-256 sum of the unsigned MAR file!"
      unsigned_mars=$((unsigned_mars + 1))
    else
      echo "$f is either signed with the wrong key or the signature is" \
           "corrupted!"
      badsigned_mars=$((badsigned_mars +1))
    fi
  fi

  # Test 1.5: Is the MAR file correctly signed by the correct channel key?
  if [ ! "$($SIGNMAR -T "$f" | grep "MAR channel name")" = "    - MAR channel name: torbrowser-torproject-${CHANNEL}" ]; then
      echo "$f contains wrong update channel!"
  fi

  # Test 2: Do we get the old SHA-256 sum after stripping the MAR signature? We
  # want to have a test for that to be sure we've the signed MAR files in front
  # of us which we actually want to ship to our users.
  if [ "$unsigned_mars" = "0" ] && [ "$badsigned_mars" = "0" ]
  then
    # At least we seem to have succeeded in signing the MAR file. Let's see if
    # it is the expected one.
    echo "Checking the SHA-256 sum of the stripped $f..."
    ${SIGNMAR} -r "$f" tmp/"$f"
    cd tmp || exit 1
    if ! [ "$sha256_txt" = "$(sha256sum "$f")" ]
    then
      not_reproduced_mars=$((not_reproduced_mars + 1))
      case "$f" in
        *osx64*)
          not_reproduced_mars_expected=$((not_reproduced_mars_expected + 1))
          ;;
        *) echo "$f does not have the SHA-256 sum of the unsigned MAR file!"
          ;;
      esac
    fi
    rm "$f"
    cd ..
  fi
  echo ""
done

rm -rf tmp/

if ! [ "$unsigned_mars" = "0" ] || ! [ "$badsigned_mars" = "0" ]
then
  echo "We got:"
  if ! [ "$unsigned_mars" = "0" ]
  then
    echo "$unsigned_mars unsigned MAR file(s)"
  fi
  if ! [ "$badsigned_mars" = "0" ]
  then
    echo "$badsigned_mars badly signed MAR file(s)"
  fi
  echo "exiting..."
  exit 1
fi

if ! [ "$not_reproduced_mars" = "0" ]
then
  echo "We got $not_reproduced_mars non-matching, signed MAR files."
  if [ "$not_reproduced_mars" -eq "$not_reproduced_mars_expected" ]
  then
    echo "This is currently expected as we got the same amount of" \
         "non-matching macOS MAR files."
    echo "The signatures and non-macOS MAR files are fine."
    exit 0
  else
    echo "This is currently unexpected as we only got" \
         "$not_reproduced_mars_expected non-matching macOS MAR files," \
         "exiting..."
    exit 1
  fi
else
  echo "The signatures and MAR files are fine."
  exit 0
fi


code:
#!/usr/bin/perl -w

# Copyright (c) 2019, The Tor Project, Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#
#     * Neither the names of the copyright owners nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# 'prune-old-builds' is a script to prune old builds.
#
#
# Usage:
#  $ ./prune-old-builds [options] <directory>
#
#
# Available options:
#
# --dry-run
#         Don't delete anything, but say what would be deleted.
#
# --prefix <prefix>
#         Prefix of the directories to be removed. Default is 'tbb-nightly.'.
#
# --separator <c>
#         Separator character to separate the year, month, day in the directory
#         names. Default is '.'.
#
# --days <n>
#         Number of days that we should keep. Default is 6.
#
# --weeks <n>
#         Number of monday builds that we should keep. Default is 3.
#
# --months <n>
#         Number of 1st day of the month builds that we should keep.
#         Default is 3.

use strict;
use Getopt::Long;
use DateTime;
use DateTime::Duration;
use File::Path qw(remove_tree);

my %options = (
    days   => 6,
    weeks  => 3,
    months => 3,
    prefix => 'tbb-nightly.',
    separator => '.',
);

sub keep_builds {
    my %res;

    my $day = DateTime::Duration->new(days => 1);
    my $week = DateTime::Duration->new(weeks => 1);
    my $month = DateTime::Duration->new(months => 1);

    my $n = $options{days};
    my $dt = DateTime->now;
    while ($n) {
        $res{ $options{prefix} . $dt->ymd($options{separator}) } = 1;
        $dt = $dt - $day;
        $n--;
    }

    my $w = $options{weeks};
    while ($dt->day_of_week != 1) {
        $dt = $dt - $day;
    }
    while ($w) {
        $res{ $options{prefix} . $dt->ymd($options{separator}) } = 1;
        $dt = $dt - $week;
        $w--;
    }

    my $m = $options{months};
    $dt = DateTime->now;
    while ($dt->day != 1) {
        $dt = $dt - $day;
    }
    while ($m) {
        $res{ $options{prefix} . $dt->ymd($options{separator}) } = 1;
        $dt = $dt - $month;
        $m--;
    }

    return \%res;
}

sub clean_directory {
    my ($directory) = @_;
    my $k = keep_builds;
    chdir $directory || die "Error entering $directory";
    foreach my $file (glob "$options{prefix}*") {
        next unless $file =~ m/^$options{prefix}\d{4}$options{separator}\d{2}$options{separator}\d{2}$/;
        next if $k->{$file};
        if ($options{'dry-run'}) {
            print "Would remove $file\n";
        } else {
            remove_tree($file);
        }
    }
}

my @opts = qw(days=i weeks=i months=i prefix=s dry-run!);
Getopt::Long::GetOptions(\%options, @opts);
die "Missing argument: directory to clean" unless @ARGV;
foreach my $dir (@ARGV) {
    clean_directory($dir);
}

path: tools/prune-old-builds


code:
#!/bin/bash

# This script allows you to repackage a Tor Browser bundle using an
# obj-x86_64-pc-linux-gnu directory from a local tor-browser.git build.
#
# This script will download the current Tor Browser version (using
# var/torbrowser_version from rbm config, or an optional second argument)
# and repackage it with the specified obj directory.
#
# The new repackaged bundle can be found in the _repackaged directory.

set -e

display_usage() {
	echo -e "\\nUsage: $0 firefox_obj_path [torbrowser-version]\\n"
}
if [ $# -lt 1 ] || [ $# -gt 2 ]
then
    display_usage
    exit 1
fi

DIRNAME="$( cd "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
OBJ_PATH=$1
if [ $# -eq 2 ]
then
  TOR_VERSION="$2"
else
  TOR_VERSION=$("$DIRNAME"/../rbm/rbm showconf tor-browser var/torbrowser_version)
fi
TOR_FILENAME=tor-browser-linux64-${TOR_VERSION}_en-US.tar.xz
TOR_BROWSER_URL=https://dist.torproject.org/torbrowser/"${TOR_VERSION}"/"${TOR_FILENAME}"
TMPDIR="$(mktemp -d)"

(
cd "$TMPDIR"
wget "$TOR_BROWSER_URL"
wget "$TOR_BROWSER_URL".asc
gpg --no-default-keyring --keyring "$DIRNAME"/../keyring/torbrowser.gpg --verify "${TOR_FILENAME}".asc "${TOR_FILENAME}"

# From projects/firefox/build: replace firefox binary by the wrapper and strip libraries/binaries
tar xf "${TOR_FILENAME}"
cp -r "${OBJ_PATH}"/dist/firefox .
rm firefox/firefox-bin
mv firefox/firefox firefox/firefox.real
for LIB in firefox/*.so firefox/gtk2/*.so firefox/firefox.real firefox/plugin-container firefox/updater
do
    strip "$LIB"
done

# Repackage https-everywhere extension
mkdir _omni/
unzip tor-browser_en-US/Browser/omni.ja -d _omni/
cd _omni/
zip -Xmr ../firefox/omni.ja chrome/torbutton/content/extensions/https-everywhere/
cd ..
rm -rf _omni/

# Overwrite extracted tor-browser with locally built files and move to _repackaged folder
cp -r firefox/* tor-browser_en-US/Browser
rm -rf firefox "${TOR_FILENAME}"
REPACKAGED_DIR="$DIRNAME"/_repackaged/
mkdir -p "$REPACKAGED_DIR"
mv tor-browser_en-US "$REPACKAGED_DIR"/tor-browser-"$(date '+%Y%m%d%H%M%S')"
rm -rf "$TMPDIR"
)


code:
The files in this directory are a large part of what we use when signing
releases. The scripts are meant to be templates, though, at the moment
omitting specific paths and credential information.

Additionally, when starting to use them for your own signing setup don't forget
to adapt the locale list if needed. The entitlement files, however, are kept
up-to-date.

The scripts in the nightly/ directory are used to sign and publish the
nightly updates.

path: tools/signing/README


code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
     Entitlements to apply to the .app bundle and all executable files
     contained within it during codesigning of production channel builds that
     will be notarized. These entitlements enable hardened runtime protections
     to the extent possible for Firefox. Some supporting binaries within the
     bundle could use more restrictive entitlements, but they are launched by
     the main Firefox process and therefore inherit the parent process
     entitlements.
-->
<plist version="1.0">
  <dict>
    <!-- Firefox does not use MAP_JIT for executable mappings -->
    <key>com.apple.security.cs.allow-jit</key><false/>

    <!-- Firefox needs to create executable pages (without MAP_JIT) -->
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>

    <!-- Code paged in from disk should match the signature at page in-time -->
    <key>com.apple.security.cs.disable-executable-page-protection</key><false/>

    <!-- Allow dyld environment variables. Needed because Firefox uses
         dyld variables to load libaries from within the .app bundle. -->
    <key>com.apple.security.cs.allow-dyld-environment-variables</key><true/>

    <!-- Don't allow debugging of the executable. Debuggers will be prevented
         from attaching to running executables. Notarization does not permit
         access to get-task-allow (as documented by Apple) so this must be
         disabled on notarized builds. -->
    <key>com.apple.security.get-task-allow</key><false/>

    <!-- Firefox needs to access the microphone on sites the user allows -->
    <key>com.apple.security.device.audio-input</key><true/>

    <!-- Firefox needs to access the camera on sites the user allows -->
    <key>com.apple.security.device.camera</key><true/>

    <!-- Firefox needs to access the location on sites the user allows -->
    <key>com.apple.security.personal-information.location</key><true/>

    <!-- Allow Firefox to send Apple events to other applications. Needed
         for native messaging webextension helper applications launched by
         Firefox which rely on Apple Events to signal other processes. -->
    <key>com.apple.security.automation.apple-events</key><true/>

    <!-- For SmartCardServices(7) -->
    <key>com.apple.security.smartcard</key><true/>
  </dict>
</plist>


code:
#!/bin/bash

# Sign apk for each target architecture.
# This script requires two command line arguments.
# Usage: android-signing <version> <path/to/signing/key>

# In addition, hard-coding the path to an Android SDK build-tools version, as
# BUILD_TOOLS, is required.

set -x
set -e

VERSION=$1
SIGNING_KEY_PATH=$2

# TODO set correctly.
BUILD_TOOLS=/path/to/build-tools/version
export PATH="${BUILD_TOOLS}:${PATH}"

ARCHS="armv7 aarch64 x86 x86_64"

# Sign individual apk
sign_apk() {
    INPUTAPK="$1"

    # https://developer.android.com/studio/publish/app-signing#sign-manually
    # After running `gradlew assembleRelease`, creates an unsigned-unaligned apk

    # Aligning ensures that all uncompressed data starts with a particular byte
    # alignment relative to the start of the file, which may reduce the amount
    # of RAM consumed by an app.
    # zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
    echo Aligning and signing ${INPUTAPK}

    # Append the different stages of signing
    UNSIGNED_UNALIGNED_APK=`echo "${INPUTAPK}" | sed 's/\.apk/-unsigned-unaligned.apk/'`
    UNSIGNED_APK=`echo "${UNSIGNED_UNALIGNED_APK}" | sed 's/-unaligned//'`
    SIGNED_APK=`echo "${UNSIGNED_APK}" | sed 's/-unsigned//'`

    cp "${INPUTAPK}" "${UNSIGNED_UNALIGNED_APK}"

    # Step 1: Align
    zipalign -v -p 4 "${UNSIGNED_UNALIGNED_APK}" "${UNSIGNED_APK}"
    if [ ! $? = 0 ]; then
        echo "zipalign failed"
        exit 1
    fi
    echo zipalign succeeded

    # Step 2: Verify alignment
    zipalign -vc 4 "${UNSIGNED_APK}"
    if [ ! $? = 0 ]; then
        echo "zipalign verify failed"
        exit 1
    fi
    echo zipalign verify succeeded

    # Step 3: Sign
    # Use this command if reading key from file
    apksigner sign --verbose -ks ${SIGNING_KEY_PATH} --ks-type pkcs12 --ks-pass env:KSPASS --debuggable-apk-permitted=false --out "${SIGNED_APK}" "${UNSIGNED_APK}"

    # Or, use below command if using a hardware token
    # apksigner sign --verbose --provider-class sun.security.pkcs11.SunPKCS11 --provider-arg pkcs11_java.cfg --ks NONE --ks-type PKCS11 --debuggable-apk-permitted=false --out "${SIGNED_APK}" "${UNSIGNED_APK}"

    if [ ! $? = 0 ]; then
        echo "apksigner sign failed"
        exit 1
    fi
    echo apksigner sign succeeded

    # Step 4: Verify signature
    apksigner verify --verbose "${SIGNED_APK}"
    if [ ! $? = 0 ]; then
        echo "apksigner verify failed"
        exit 1
    fi

    echo apksigner verify succeeded
}

# Rename and verify signing certificate
finalize() {
  for arch in ${ARCHS}; do
      mv tor-browser-${VERSION}-android-${arch}-multi{-qa,}.apk
  done

  for arch in ${ARCHS}; do
      verified=`apksigner verify --print-certs --verbose tor-browser-${VERSION}-android-${arch}-multi.apk`
      scheme_v1=
      scheme_v2=
      cert_digest=
      pubkey_digest=

      # Verify the expected signing key was used, Alpha verses Release based on the filename.
      if `echo ${VERSION} | grep -q a`; then
          scheme_v1="Verified using v1 scheme (JAR signing): true"
          scheme_v2="Verified using v2 scheme (APK Signature Scheme v2): true"
          cert_digest="Signer #1 certificate SHA-256 digest: 15f760b41acbe4783e667102c9f67119be2af62fab07763f9d57f01e5e1074e1"
          pubkey_digest="Signer #1 public key SHA-256 digest: 4e617e6516f81123ca58e718d617a704ac8365c575bd9e7a731ba5dd0476869d"
      else
          scheme_v1="Verified using v1 scheme (JAR signing): true"
          scheme_v2="Verified using v2 scheme (APK Signature Scheme v2): true"
          cert_digest="Signer #1 certificate SHA-256 digest: 20061f045e737c67375c17794cfedb436a03cec6bacb7cb9f96642205ca2cec8"
          pubkey_digest="Signer #1 public key SHA-256 digest: 343ca8a2e5452670bdc335a181a4baed909f868937d68c4653e44ef84de8dfc6"
      fi
      for digest in "${scheme_v1}" "${scheme_v2}" "${cert_digest}" "${pubkey_digest}"; do
          if ! `echo "${verified}" | grep -q "${digest}"`; then
              echo "Expected digest not found:"
              echo ${digest}
              echo "in:"
              echo ${verified}
              exit 1
          fi
      done
  done

  echo Done.
}

if [ -z "$VERSION" ]; then
    echo Provide version number
    exit
fi

if [ -z "${SIGNING_KEY_PATH}" ]; then
    echo Provide the path to the signing key: release or alpha
    exit
fi

if [ -z "$KSPASS" ]; then
    echo "Enter keystore passphrase"
    stty -echo; read KSPASS; stty echo
    export KSPASS
fi

# Sign all packages
for arch in ${ARCHS}; do
    sign_apk tor-browser-${VERSION}-android-${arch}-multi-qa.apk
done

finalize

path: tools/signing/android-signing


code:
#!/bin/bash

# Copyright (c) 2019, The Tor Project, Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:

#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#
#     * Neither the names of the copyright owners nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

osslsigncode_file="$script_dir/../../out/osslsigncode/osslsigncode-e72a1937d1a1-25066d.tar.gz"

test -f "$osslsigncode_file" ||
  exit_error "$osslsigncode_file is missing." \
             "You can build it with:" \
             "  ./rbm/rbm build osslsigncode" \
             "See var/deps in projects/osslsigncode/config for the list of build dependencies"

which rename > /dev/null 2>&1 ||
  exit_error '`rename` is missing.'

tmp_dir="$signed_dir/$tbb_version/tmp-timestamp"
mkdir "$tmp_dir"
tar -C "$tmp_dir" -xf "$osslsigncode_file"
export PATH="$PATH:$tmp_dir/osslsigncode/bin"

cd "$signed_dir/$tbb_version"
COUNT=0
for i in `find . -name "*.exe" -print`
do
  osslsigncode add \
                 -t http://timestamp.digicert.com \
                 -p socks://127.0.0.1:9050 \
                 $i $i-timestamped
  COUNT=$((COUNT + 1))

done
echo "Timestamped $COUNT .exe files, now renaming"
rename -f 's/-timestamped//' *-timestamped

rm -Rf "$tmp_dir"


code:
#!/bin/bash

#set -x
#set -e

VERSION=$1
LANG_COUNT=$2
INCREMENTAL_VERSIONS="$3"
SIGNERS="$4"

if [ "$#" -ne 4 ]; then
    echo "<version> <lang_count> <incrementals> <signers>"
    exit
fi

INSTALL_PLATFORMS="tor-browser-linux32-${VERSION}_\*.tar.xz tor-browser-linux64-${VERSION}_\*.tar.xz torbrowser-install-${VERSION}_\*.exe torbrowser-install-win64-${VERSION}_\*.exe TorBrowser-${VERSION}-osx64_\*.dmg"

MAR_PLATFORMS="linux32 linux64 win32 win64 osx64"
MAR_TOOLS_PLATFORMS="linux32 linux64 win32 win64 mac64"

total_count=0
remaining_files=$(ls)

for p in ${INSTALL_PLATFORMS}; do
  expand_p=$(echo "${p}" | sed 's/\\\*/*/g')
  test "$(ls ${expand_p} 2>/dev/null | wc -l)" = "${LANG_COUNT}" || echo "${p} not ${LANG_COUNT}"
  total_count=$(( total_count + LANG_COUNT ))
  for f in ${expand_p}; do
    remaining_files=$(echo "${remaining_files}" | sed 's/ '"${f}"' / /')
  done
done

for p in ${INSTALL_PLATFORMS}; do
  expand_p="$(echo "${p}" | sed 's/\\\*/*/g')"
  test "$(ls ${expand_p}.asc 2>/dev/null | wc -l)" = "${LANG_COUNT}" || echo "${p}.asc not ${LANG_COUNT}"
  total_count=$(( total_count + LANG_COUNT ))
  for f in ${expand_p}; do
    remaining_files=$(echo "${remaining_files}" | sed 's/ '"${f}.asc"' / /')
  done
done

p=tor-browser-"${VERSION}"-android-\*-multi\*.apk
expand_p="$(echo "${p}" | sed 's/\\\\\*/*/g')"
test "$(ls ${expand_p} 2>/dev/null | wc -l)" = 8 || echo "${p} not 8"
total_count=$(( total_count + 8 ))
for f in ${expand_p}; do
  remaining_files=$(echo "${remaining_files}" | sed 's/ '"${f}"' / /')
done
test "$(ls ${expand_p}.asc 2>/dev/null | wc -l)" = 8 || echo "${p}.asc not 8"
total_count=$(( total_count + 8 ))
for f in ${expand_p}; do
  remaining_files=$(echo "${remaining_files}" | sed 's/ '"${f}.asc"' / /')
done

for p in ${MAR_PLATFORMS}; do
  count=$(ls tor-browser-"${p}"-"${VERSION}"_*.mar 2>/dev/null | wc -l)
  test "${count}" -eq "${LANG_COUNT}" || echo "${p} not ${LANG_COUNT} (found $count)"
  total_count=$(( total_count + count ))
  for f in tor-browser-"${p}"-"${VERSION}"_*.mar; do
    remaining_files=$(echo "${remaining_files}" | sed 's/ '"${f}"' / /')
  done
done

for p in ${MAR_TOOLS_PLATFORMS}; do
  test -f mar-tools-"${p}".zip || echo mar-tools-"${p}".zip does not exit
  total_count=$(( total_count + 1 ))
  remaining_files=$(echo "${remaining_files}" | sed 's/ 'mar-tools-"${p}".zip' / /')
done

for p in ${MAR_TOOLS_PLATFORMS}; do
  test -f mar-tools-"${p}".zip.asc || echo mar-tools-"${p}".zip.asc does not exit
  total_count=$(( total_count + 1 ))
  remaining_files=$(echo "${remaining_files}" | sed 's/ 'mar-tools-"${p}".zip.asc' / /')
done

for p in ${MAR_PLATFORMS}; do
  for i in ${INCREMENTAL_VERSIONS}; do
    count="$(ls tor-browser-"${p}"-"${i}"-"${VERSION}"_*.mar 2>/dev/null | wc -l)"
    test "${count}" -eq "${LANG_COUNT}" || echo "${p} ${i} incrementals not ${LANG_COUNT} (found $count)"
    total_count=$(( total_count + count ))
    for f in tor-browser-"${p}"-"${i}"-"${VERSION}"_*.mar; do
      remaining_files=$(echo "${remaining_files}" | sed 's/ '"${f}"' / /')
    done
  done
done

for f in tor-win32-*.zip tor-win64-*.zip; do
  test -f "${f}" || echo "${f} does not exist"
  test -f "${f}.asc" || echo "${f}.asc does not exist"
  total_count=$(( total_count + 2 ))
  remaining_files=$(echo "${remaining_files}" | sed 's/[ ]*'"${f}"'[ ]*/ /')
  remaining_files=$(echo "${remaining_files}" | sed 's/[ ]*'"${f}.asc"'[ ]*/ /')
done

for f in sha256sums-unsigned-build.txt sha256sums-unsigned-build.incrementals.txt sha256sums-signed-build.txt sha256sums-signed-build.incrementals.txt; do
  test -f ${f} || echo ${f} does not exist
  test -f ${f}.asc || echo ${f}.asc does not exist
  total_count=$(( total_count + 2 ))
  #remaining_files=$(echo ${remaining_files} | sed 's/ '${f}' / /')
  remaining_files=$(echo "${remaining_files}" | sed 's/ '${f}' / /')
  remaining_files=$(echo "${remaining_files}" | sed 's/ '${f}.asc' / /')
done

for s in ${SIGNERS}; do
  for f in sha256sums-unsigned-build.txt sha256sums-unsigned-build.incrementals.txt; do
    test -f "${f}.asc-${s}" || echo "${f}.asc-${s} does not exist"
    total_count=$(( total_count + 1 ))
    remaining_files="$(echo "${remaining_files}" | sed 's/ '"${f}.asc-${s}"' / /')"
  done
done

for f in sha256sums-unsigned-build.txt sha256sums-unsigned-build.incrementals.txt; do
  for s in "${f}".asc-*; do
    gpg2 --quiet --verify "${s}" ${f}
  done
done

for f in sha256sums-signed-build.txt sha256sums-signed-build.incrementals.txt; do
  gpg2 --quiet --verify ${f}.asc ${f}
done

for f in sha256sums-signed-build.txt sha256sums-signed-build.incrementals.txt; do
  sha256sum --quiet -c $f
done

f=geckodriver-linux64.tar.xz
test -f ${f} || echo ${f} does not exist
test -f ${f}.asc || echo ${f}.asc does not exist
total_count=$(( total_count + 2 ))
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}"'[ ]*/ /')"
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}.asc"'[ ]*/ /')"

for f in tor-browser-linux64-debug.tar.xz tor-linux32-debug.tar.xz tor-linux64-debug.tar.xz; do
  test -f ${f} || echo ${f} does not exist
  test -f ${f}.asc || echo ${f}.asc does not exist
  total_count=$(( total_count + 2 ))
  remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}"'[ ]*/ /')"
  remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}.asc"'[ ]*/ /')"
done

test "$(ls src-firefox-tor-browser-*.tar.xz 2>/dev/null | wc -l)" = 1 || echo src-firefox-tor-browser-*.tar.xz is wrong
test "$(ls src-firefox-tor-browser-*.tar.xz.asc 2>/dev/null | wc -l)" = 1 || echo src-firefox-tor-browser-*.tar.xz.asc is wrong
total_count=$(( total_count + 2 ))
f="$(ls src-firefox-tor-browser-*.tar.xz)"
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}"'[ ]*/ /')"
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}.asc"'[ ]*/ /')"

test "$(ls src-tor-launcher-*.tar.xz 2>/dev/null | wc -l)" = 1 || echo src-tor-launcher-*.tar.xz is wrong
test "$(ls src-tor-launcher-*.tar.xz.asc 2>/dev/null | wc -l)" = 1 || echo src-tor-launcher-*.tar.xz.asc is wrong
total_count=$(( total_count + 2 ))
f="$(ls src-tor-launcher-*.tar.xz)"
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}"'[ ]*/ /')"
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}.asc"'[ ]*/ /')"

test "$(ls langpacks-tor-browser-linux64-*.tar.xz 2>/dev/null | wc -l)" = 1 || echo langpacks-tor-browser-linux64-*.tar.xz is wrong
test "$(ls langpacks-tor-browser-linux64-*.tar.xz.asc 2>/dev/null | wc -l)" = 1 || echo langpacks-tor-browser-linux64-*.tar.xz.asc is wrong
total_count=$(( total_count + 2 ))
f="$(ls langpacks-tor-browser-linux64-*.tar.xz)"
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}"'[ ]*/ /')"
remaining_files="$(echo "${remaining_files}" | sed 's/[ ]*'"${f}.asc"'[ ]*/ /')"

# Expected file endings
file_count_by_ending="$(ls ./*.tar.xz{,.asc} ./*.zip{,.asc} ./*.exe{,.asc} ./*.mar ./*.dmg{,.asc} ./*.apk{,.asc} ./*.txt{,.asc} ./*.txt.asc-* | wc -l)"
test "${file_count_by_ending}" -eq ${total_count} || echo "Unexpected file endings: counted ${file_count_by_ending} vs ${total_count}"

test "$(ls | wc -l)" -eq ${total_count} || echo "wrong total count: $(ls | wc -l) vs ${total_count}"
echo "${remaining_files}"
echo done.


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"
source "$script_dir/set-config.blog"

var_is_defined blog_publish_user blog_directory

content_dir="$blog_directory/content/blog"
test -d "$content_dir" || exit_error "$content_dir is not a direcotry"

blog_dir="$content_dir/new-release-tor-browser-"$(echo $tbb_version | sed 's/\.//g')

test -d "$blog_dir" && exit_error "$blog_dir already exists"

mkdir "$blog_dir"
echo "Created directory $blog_dir"

if test "$tbb_version_type" = "release"
then
  lead=../../../assets/static/images/blog/tor-browser-11.jpg
else
  lead=../../../assets/static/images/blog/tor-browser_0_0.png
fi
ln -s "$lead" "$blog_dir/lead.jpg"
echo "Created $blog_dir/lead.jpg -> $lead"

if test "$tbb_version_type" = "release"
then
  title="New Release: Tor Browser $tbb_version"
  download_page='https://www.torproject.org/download/'
else
  title="New Alpha Release: Tor Browser $tbb_version"
  download_page='https://www.torproject.org/download/alpha/'
fi

contents_lr="$blog_dir/contents.lr"
cat > "$contents_lr" << EOF
title: $title
---
pub_date: $(date +%Y-%m-%d)
---
author: $blog_publish_user
---
categories:

applications
releases
---
summary: Tor Browser $tbb_version is now available from the Tor Browser download page and also from our distribution directory.
---
body:
Tor Browser $tbb_version is now available from the [Tor Browser download page]($download_page) and also from our [distribution directory](https://www.torproject.org/dist/torbrowser/$tbb_version/).

This version includes important [security updates](https://www.mozilla.org/en-US/security/advisories/) to Firefox.

EOF

$script_dir/../changelog-format-blog-post >> "$contents_lr"
echo "Created $contents_lr"

path: tools/signing/create-blog-post


code:
#!/bin/bash

# This script is called from gatekeeper-bundling.sh, and creates a dmg
# file from a directory
#
# Usage:
#   ddmg.sh <dmg-file> <src-directory>

set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

dest_file="$1"
src_dir="$2"

set +e
find $src_dir -executable -exec chmod 0755 {} \; 2> /dev/null
find $src_dir ! -executable -exec chmod 0644 {} \; 2> /dev/null

find $src_dir -exec touch -m -t 200001010101 {} \; 2> /dev/null
set -e

dmg_tmpdir=$(mktemp -d)
filelist="$dmg_tmpdir/filelist.txt"
cd $src_dir
find . -type f | sed -e 's/^\.\///' | sort | xargs -i echo "{}={}" > $filelist
find . -type l | sed -e 's/^\.\///' | sort | xargs -i echo "{}={}" >> $filelist

export LD_PRELOAD=$faketime_path
export FAKETIME="2000-01-01 01:01:01"

echo "Starting: " $(basename $dest_file)

genisoimage -D -V "Tor Browser" -no-pad -R -apple -o "$dmg_tmpdir/tbb-uncompressed.dmg" -path-list $filelist -graft-points -gid 20 -dir-mode 0755 -new-dir-mode 0755

dmg dmg "$dmg_tmpdir/tbb-uncompressed.dmg" "$dest_file"

echo "Finished: " $(basename $dest_file)

rm -Rf "$dmg_tmpdir"


code:
#!/bin/bash

# This script runs `make dmg2mar-release` or `make dmg2mar-alpha`, after
# moving the signed dmg files from the $tbb_version-macos-signed directory
# to the normal signed directory.
# It should be run after `gatekeeper-bundling.sh`.

set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

cd "$script_dir/../.."

version=$(./rbm/rbm showconf tor-browser --target alpha --target torbrowser-linux-x86_64 var/torbrowser_version)
test "$version" = "$tbb_version" || \
  exit_error "Incorrect tor browser version: $version != $tbb_version"

test -d "$macos_signed_dir" || \
  exit_error "$macos_signed_dir does not exist"

nb_locales=$(echo $bundle_locales | wc -w)
nb_bundles=$(ls -1 "$macos_signed_dir"/TorBrowser-*.dmg | wc -l)
test "$nb_locales" -eq "$nb_bundles" || \
  exit_error "Wrong number of bundles: $nb_locales != $nb_bundles"

mv -vf "$macos_signed_dir"/TorBrowser-*.dmg "$signed_version_dir"/

make dmg2mar-$tbb_version_type


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

NON_INTERACTIVE=1
steps_dir="$signed_version_dir.steps"
test -d "$steps_dir" || mkdir -p "$steps_dir"

test -f "$steps_dir/macos-signer-gatekeeper-signing.done" ||
  read -sp "Enter macos keychain passphrase: " KEYCHAIN_PW
echo
test -f "$steps_dir/macos-signer-notarization.done" ||
  read -sp "Enter macos notarization passphrase: " NOTARIZATION_PW
echo
test -f "$steps_dir/linux-signer-signmars.done" ||
  read -sp "Enter nssdb7 (mar signing) passphrase: " NSSPASS
echo
test -f "$steps_dir/linux-signer-authenticode-signing.done" ||
  read -sp "Enter windows authenticode (yubihsm) passphrase: " YUBIPASS
echo
test -f "$steps_dir/linux-signer-gpg-sign.done" ||
  read -sp "Enter gpg passphrase: " GPG_PASS
echo

function wait-for-finished-build {
  "$script_dir/wait-for-finished-build"
}

function sync-builder-unsigned-to-local-signed {
  "$script_dir/sync-builder-unsigned-to-local-signed"
}

function macos-signer-directory-not-present {
  # To avoid doing two signings at the same time, or to avoid unknowingly
  # using parts from a previous signing attempt, we check that the
  # directory does not exist yet (see tor-browser-build#40497)
  if ssh "$ssh_host_macos_signer" "test -d $tbb_version"
  then
    echo "The directory $tbb_version already exists on $ssh_host_macos_signer"
    return 1
  fi
  return 0
}

function sync-scripts-to-macos-signer {
  "$script_dir/sync-scripts-to-macos-signer"
}

function macos-signer-enable-network-proxy-settings {
  ssh "$ssh_host_macos_signer" 'networksetup -setsecurewebproxystate Ethernet on'
}

function macos-signer-gatekeeper-signing {
  "$script_dir/sync-macos-local-to-macos-signer"
  ssh "$ssh_host_macos_signer" 'bash -s' << EOF
  export KEYCHAIN_PW=$KEYCHAIN_PW
  ~/signing-$tbb_version_type/macos-signer-gatekeeper-signing
EOF
  unset KEYCHAIN_PW
}

function macos-signer-notarization {
  ssh "$ssh_host_macos_signer" 'bash -s' << EOF
  export PW=$NOTARIZATION_PW
  ~/signing-$tbb_version_type/macos-signer-notarization
EOF
  unset NOTARIZATION_PW
}

function macos-signer-stapler {
  ssh "$ssh_host_macos_signer" "~/signing-$tbb_version_type/macos-signer-stapler"
  "$script_dir/sync-macos-signer-stapled-to-macos-local-stapled"
}

function macos-signer-disable-network-proxy-settings {
  ssh "$ssh_host_macos_signer" 'networksetup -setsecurewebproxystate Ethernet off'
}

function gatekeeper-bundling {
  "$script_dir/gatekeeper-bundling.sh"
}

function dmg2mar {
  "$script_dir/dmg2mar"
}

function sync-scripts-to-linux-signer {
  "$script_dir/sync-scripts-to-linux-signer"
}

function linux-signer-signmars {
  "$script_dir/sync-local-to-linux-signer"
  ssh "$ssh_host_linux_signer" 'bash -s' << EOF
  export NSSPASS=$NSSPASS
  ~/signing-$tbb_version_type/linux-signer-signmars
EOF
  unset NSSPASS
}

function sync-after-signmars {
  "$script_dir/sync-linux-signer-to-local"
}

function download-unsigned-sha256sums-gpg-signatures-from-people-tpo {
  "$script_dir/download-unsigned-sha256sums-gpg-signatures-from-people-tpo"
}

function linux-signer-authenticode-signing {
  ssh "$ssh_host_linux_signer" 'bash -s' << EOF
  export YUBIPASS='$YUBIPASS'
  ~/signing-$tbb_version_type/linux-signer-authenticode-signing
EOF
  unset YUBIPASS
}

function sync-after-authenticode-signing {
  "$script_dir/sync-linux-signer-to-local"
}

function authenticode-timestamping {
  "$script_dir/authenticode-timestamping.sh"
}

function sync-after-authenticode-timestamping {
  "$script_dir/sync-local-to-linux-signer"
}

function hash_signed_bundles {
  "$script_dir/hash_signed_bundles.sh"
}

function sync-after-hash {
  "$script_dir/sync-local-to-linux-signer"
}

function linux-signer-gpg-sign {
  ssh "$ssh_host_linux_signer" 'bash -s' << EOF
  export GPG_PASS=$GPG_PASS
  ~/signing-$tbb_version_type/linux-signer-gpg-sign
EOF
}

function sync-after-gpg-sign {
  "$script_dir/sync-linux-signer-to-local"
}

function sync-local-to-staticiforme {
  "$script_dir/sync-local-to-staticiforme"
}

function sync-scripts-to-staticiforme {
  "$script_dir/sync-scripts-to-staticiforme"
}

function staticiforme-prepare-cdn-dist-upload {
  ssh "$ssh_host_staticiforme" "signing-$tbb_version_type/staticiforme-prepare-cdn-dist-upload"
}

function upload-update_responses-to-staticiforme {
  "$script_dir/upload-update_responses-to-staticiforme"
}

function finished-signing-clean-macos-signer {
  "$script_dir/finished-signing-clean-macos-signer"
}

function finished-signing-clean-linux-signer {
  "$script_dir/finished-signing-clean-linux-signer"
}

function do_step {
  test -f "$steps_dir/$1.done" && return 0
  echo "$(date -Iseconds) - Starting step: $1"
  $1 | tee "$steps_dir/$1.log" 2>&1
  test ${PIPESTATUS[0]} -eq 0
  touch "$steps_dir/$1.done"
  echo "$(date -Iseconds) - Finished step: $1"
}

do_step wait-for-finished-build
do_step sync-builder-unsigned-to-local-signed
do_step macos-signer-directory-not-present
do_step sync-scripts-to-macos-signer
do_step macos-signer-enable-network-proxy-settings
do_step macos-signer-gatekeeper-signing
do_step macos-signer-notarization
do_step macos-signer-stapler
do_step macos-signer-disable-network-proxy-settings
do_step gatekeeper-bundling
do_step dmg2mar
do_step sync-scripts-to-linux-signer
do_step linux-signer-signmars
do_step sync-after-signmars
do_step linux-signer-authenticode-signing
do_step sync-after-authenticode-signing
do_step authenticode-timestamping
do_step sync-after-authenticode-timestamping
do_step hash_signed_bundles
do_step sync-after-hash
do_step linux-signer-gpg-sign
do_step sync-after-gpg-sign
do_step download-unsigned-sha256sums-gpg-signatures-from-people-tpo
do_step sync-local-to-staticiforme
do_step sync-scripts-to-staticiforme
do_step staticiforme-prepare-cdn-dist-upload
do_step upload-update_responses-to-staticiforme
do_step finished-signing-clean-macos-signer
do_step finished-signing-clean-linux-signer

path: tools/signing/do-all-signing


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

for builder in $tb_builders
do
  for file in sha256sums-unsigned-build.txt.asc sha256sums-unsigned-build.incrementals.txt.asc
  do
    tmpfile=$(mktemp)
    chmod 644 "$tmpfile"
    wget -q -O "$tmpfile" "https://people.torproject.org/~$builder/builds/$tbb_version-build$tbb_version_build/$file" || \
      wget -q -O "$tmpfile" "https://people.torproject.org/~$builder/builds/tor-browser/$tbb_version-build$tbb_version_build/$file" || \
      wget -q -O "$tmpfile" "https://tb-build-03.torproject.org/~$builder/builds/tor-browser/$tbb_version_type/unsigned/$tbb_version-build$tbb_version_build/$file" && \
      mv "$tmpfile" "$signed_version_dir/$file-$builder" && echo "Added $file-$builder"
  done
done
exit 0


code:
#!/bin/bash

# Remove current tbb version from linux-signer. You should run this
# when all signing has been done.

set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_linux_signer tbb_version

ssh "$ssh_host_linux_signer" 'bash -s' << EOF
  test -n "$tbb_version" && rm -Rfv ~/"$tbb_version"
EOF


code:
#!/bin/bash

# Remove current tbb version from macos-signer. You should run this
# when all signing has been done.

set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_macos_signer tbb_version

ssh "$ssh_host_macos_signer" 'bash -s' << EOF
  test -n "$tbb_version" && rm -Rfv ~/"$tbb_version"
EOF


code:
function exit_error {
  for msg in "$@"
  do
    echo "$msg" > /dev/stderr
  done
  exit 1
}

function var_is_defined {
  for var in "$@"
  do
    test -n "${!var}" || exit_error "$var is not defined (see set-config* files)"
  done
}

function check_torbrowser_version_var {
  local tbver=$("$script_dir/../../rbm/rbm" showconf tor-browser var/torbrowser_version)
  test "$tbver" != "$tbb_version" && exit_error "Wrong tbb_version: $tbver != $tbb_version"
  return 0
}

. "$script_dir/set-config"

path: tools/signing/functions


code:
#!/bin/bash

# Copyright (c) 2019, The Tor Project, Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:

#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#
#     * Neither the names of the copyright owners nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

which genisoimage > /dev/null || \
  exit_error 'genisoimage is missing. You should install the genisoimage package.'
test -f $faketime_path || \
  exit_error "$faketime_path is missing"
test -d $macos_stapled_dir || \
  exit_error "The stapled macos zip files should be placed in directory $macos_stapled_dir"
libdmg_file="$script_dir/../../out/libdmg-hfsplus/libdmg-hfsplus-dfd5e5cc3dc1-c9296e.tar.gz"
test -f "$libdmg_file" || \
  exit_error "$libdmg_file is missing." \
             "You can build it with:" \
             "  ./rbm/rbm build --target no_containers libdmg-hfsplus" \
             "See var/deps in projects/libdmg-hfsplus/config for the list of build dependencies"

test -d "$macos_signed_dir" || mkdir "$macos_signed_dir"
tmpdir="$macos_stapled_dir/tmp"
rm -Rf "$tmpdir"
mkdir "$tmpdir"
cp -rT "$script_dir/../../projects/tor-browser/Bundle-Data/mac-applications.dmg" "$tmpdir/dmg"

tar -C "$tmpdir" -xf "$libdmg_file"
export PATH="$PATH:$tmpdir/libdmg-hfsplus"

for lang in $bundle_locales
do
  cd $tmpdir/dmg
  unzip -q $macos_stapled_dir/tb-${tbb_version}_$lang-stapled.zip
  cd ..
  $script_dir/ddmg.sh $macos_signed_dir/TorBrowser-${tbb_version}-osx64_$lang.dmg $tmpdir/dmg/
  rm -rf 'dmg/Tor Browser.app'
done

rm -Rf "$tmpdir"


code:
#!/bin/bash

# Copyright (c) 2018, The Tor Project, Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#
#     * Neither the names of the copyright owners nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# This script will generate sha256sums-signed-build.txt and
# sha256sums-signed-build.incrementals.txt files in the signed directory.

set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

export LC_ALL=C

cd "$signed_version_dir"

rm -f sha256sums-signed-build.txt sha256sums-signed-build.incrementals.txt
sha256sum `ls -1 | grep -v '\.incremental\.mar$' | grep -v '^sha256sums*' | \
  sort` > sha256sums-signed-build.txt

if ls -1 | grep -q '\.incremental\.mar$'
then
  sha256sum `ls -1 | grep '\.incremental\.mar$' | \
    sort` > sha256sums-signed-build.incrementals.txt
fi

echo "Hashing the signed bundles done."
exit 0


code:
#!/bin/bash
set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

cd ~/"$tbb_version"

test -n "${YUBIPASS:-}" || read -s -p "Authenticode (yubihsm) password:" YUBIPASS
echo

tmpdir=$(mktemp -d)
chgrp yubihsm "$tmpdir"
chmod g+rwx "$tmpdir"

cwd=$(pwd)
for i in `find . -name "*.exe" -print`
do
  echo "Signing $i"
  echo export 'YUBIHSM_PKCS11_CONF=~/yubihsm_pkcs11.conf' \; \
       /home/yubihsm/osslsigncode/osslsigncode \
                 -pkcs11engine /usr/lib/engines/engine_pkcs11.so \
                 -pkcs11module /usr/local/lib/yubihsm_pkcs11.so \
                 -pass "'$YUBIPASS'" \
                 -h sha256 \
                 -certs /home/yubihsm/tpo-cert.crt \
                 -key 1c40 \
                 "$cwd/$i" "$tmpdir/$i" \
                 | sudo su - yubihsm
  mv -vf "$tmpdir/$i" "$cwd/$i"
done

unset YUBIPASS
rmdir "$tmpdir"


code:
#!/bin/bash
set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

cd ~/"$tbb_version"

test -n "$GPG_PASS" || read -sp "Enter gpg passphrase: " GPG_PASS
for i in `find . -name "*.dmg" -o -name "*.exe" -o -name "*.tar.xz" -o -name "*.txt" -o -name "*.zip" -o -name "*.tar.gz" -o -name "*.apk" | sort`
do
  if test -f "$i.asc"
  then
    echo "Removing $i.asc"
    rm -f "$i.asc"
  fi
  echo "Signing $i"
  echo "$GPG_PASS" | gpg -absu 0xe53d989a9e2d47bf! --batch --no-tty --passphrase-fd 0 $i
done

path: tools/signing/linux-signer-gpg-sign


code:
#!/bin/bash
#
#
# You may set NSS_DB_DIR and/or NSS_CERTNAME before invoking this script
# (if you don't want to use the default values).

set -e
set -u

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

if [ -z "${NSS_DB_DIR+x}" ]; then
  NSS_DB_DIR=/home/boklm/marsigning/nssdb7
fi

if [ -z "${NSS_CERTNAME+x}" ]; then
  NSS_CERTNAME=marsigner
fi

export LC_ALL=C

# Check some prerequisites.
if [ ! -r "$NSS_DB_DIR/cert9.db" ]; then
  >&2 echo "Please create and populate the $NSS_DB_DIR directory"
  exit 2
fi

# Extract the MAR tools so we can use the signmar program.
MARTOOLS_TMP_DIR=$(mktemp -d)
trap "rm -rf $MARTOOLS_TMP_DIR" EXIT
MARTOOLS_ZIP=~/gitian-builder/inputs/mar-tools-new-linux32.zip
unzip -d "$MARTOOLS_TMP_DIR" -q "$MARTOOLS_ZIP"
export PATH="$MARTOOLS_TMP_DIR/mar-tools:$PATH"
if [ -z "${LD_LIBRARY_PATH+x}" ]; then
  export LD_LIBRARY_PATH="$MARTOOLS_TMP_DIR/mar-tools"
else
  export LD_LIBRARY_PATH="$MARTOOLS_TMP_DIR/mar-tools:$LD_LIBRARY_PATH"
fi

# Prompt for the NSS password.
# TODO: Test that the entered NSS password is correct.  But how?  Unfortunately,
# both certutil and signmar keep trying to read a new password when they are
# given an incorrect one.
test -n "${NSSPASS:-}" || read -s -p "NSS password:" NSSPASS
echo ""

COUNT=0
cd ~/"$tbb_version"
for marfile in *.mar; do
  if [ ! -f "$marfile" ]; then
    continue;
  fi

  # First, we check for an existing signature.  The signmar -T output will
  # include a line like "Signature block found with N signatures".
  SIGINFO_PREFIX="Signature block found with "
  SIGINFO=$(signmar -T "$marfile" | grep "^${SIGINFO_PREFIX}")
  SIGCOUNT=0
  if [ ! -z "$SIGINFO" ]; then
    SIGCOUNT=$(echo $SIGINFO | sed -e "s/${SIGINFO_PREFIX}//" -e 's/\([0-9]*\).*$/\1/')
  fi
  if [ $SIGCOUNT -ne 0 ]; then
    echo "Skipping $marfile (already signed)"
    continue;
  fi

  echo "$NSSPASS" | signmar -d "$NSS_DB_DIR" -n "$NSS_CERTNAME" -s \
    "$marfile" tmp.mar
  mv -f tmp.mar "$marfile"
  COUNT=$((COUNT + 1))
  echo "Signed MAR file $COUNT ($marfile)"
done

echo "$COUNT MAR files have been signed."

path: tools/signing/linux-signer-signmars


code:
#!/bin/bash
set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

ENTITLEMENTS="$script_dir/$tbb_version_type.entitlements.xml"

function check_signature() {
  LANG=$1
  UNZIP=$2
  local failed_open=0
  local failed_exec=0
  if [ ${UNZIP} -eq 1 ]
  then
    test -d test_${LANG} && rm -r test_${LANG}
    unzip -d test_${LANG} -q tb-${tbb_version}_$LANG.zip
    pushd test_${LANG}
  fi
  echo "Checking $LANG..."
  spctl -vvvv --assess --type open --context context:primary-signature 'Tor Browser.app/'
  if [ $? -ne 3 ]; then
    echo tb-${tbb_version}_$LANG.zip not signed correctly. Failed open.
    failed_open=1
  fi
  spctl -vvvv --assess --type exec --context context:primary-signature 'Tor Browser.app/'
  if [ $? -ne 0 ]; then
    echo tb-${tbb_version}_$LANG.zip not signed correctly. Failed exec.
    failed_exec=1
  fi
  if [ ${UNZIP} -eq 1 ]
  then
    popd
    rm -r test_${LANG}
  fi
  if [ ${failed_open} -ne 0 -o ${failed_exec} -ne 0 ]
  then
    return 1
  fi
}

cd ~/${tbb_version}

if test -n "$KEYCHAIN_PW"
then
  KPW="-p $KEYCHAIN_PW"
fi

security unlock $KPW /Users/torbrowser/Library/Keychains/tbb-signing-alpha.keychain
security unlock $KPW /Users/torbrowser/Library/Keychains/tbb-signing-2021.keychain

unset KPW KEYCHAIN_PW

for LANG in $bundle_locales
do
  if [ -f tb-${tbb_version}_${LANG}.zip ]
  then
    echo "Deleting tb-${tbb_version}_${LANG}.zip"
    rm tb-${tbb_version}_${LANG}.zip
  fi
  if [ -d "Tor Browser.app" ]
  then
    echo "Deleting Tor Browser.app"
    rm -r "Tor Browser.app"
  fi
  if [ -d '/Volumes/Tor Browser' ]; then
    echo "DMG already mounted. Please correct."
    exit 1
  fi
  hdiutil attach TorBrowser-${tbb_version}-osx64_$LANG.dmg
  cp -rf "/Volumes/Tor Browser/Tor Browser.app" "Tor Browser.app"
  echo "Signing Tor Browser_$LANG.app"
  codesign -vvv --deep -o runtime --entitlements="$ENTITLEMENTS" --timestamp -f -s "Developer ID Application: The Tor Project, Inc (MADPSAYN6T)" "Tor Browser.app/"
  echo "codesign exit code: $?"
  set +e
  check_signature $LANG 0
  if [ $? -eq 1 ]
  then
    echo Signature verification failed.
    rm -r "Tor Browser.app"
    hdiutil detach "/Volumes/Tor Browser"
    exit 1
  fi
  set -e
  echo "Zipping up tb-${tbb_version}_${LANG}.zip"
  zip -qr tb-${tbb_version}_${LANG}.zip "Tor Browser.app"
  rm -rf "Tor Browser.app"
  hdiutil detach "/Volumes/Tor Browser"
  set +e
  check_signature $LANG 1
  if [ $? -eq 1 ]
  then
    echo "Signature verification failed (${LANG})".
    rm -r "Tor Browser.app"
    exit 1
  fi
  set -e
done


code:
#!/bin/bash
set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"
source "$script_dir/set-config.macos-notarization"

ALTOOL=~/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/Frameworks/AppStoreService.framework/Versions/A/Support/altool

cd ~/${tbb_version}

if [ -z "${PW}" ]; then
  echo "Please enter notarization password:"
  stty -echo; read PW; stty echo; export PW
fi

for LANG in $bundle_locales
do
  if test -f ${LANG}/tb-${tbb_version}_$LANG.zip.uuid
  then
    echo "Skipping ${LANG}/tb-${tbb_version}_$LANG.zip"
    continue;
  fi
  if test -d ${LANG}; then
    mv ${LANG}/tb-${tbb_version}_$LANG.zip ./
    rm -r ${LANG}/
  fi
  mkdir $LANG
  cd $LANG
  mv ../tb-${tbb_version}_$LANG.zip .
  unzip -q tb-${tbb_version}_$LANG.zip
  echo "Notarizing $LANG..."
  $ALTOOL --notarize-app --verbose -t osx -f tb-${tbb_version}_$LANG.zip --primary-bundle-id org.torproject.torbrowser -u "$macos_notarization_user" -p @env:PW --output-format xml | tee tb-${tbb_version}_$LANG.zip.log 2>&1

  request_uuid=`grep -A1 RequestUUID tb-${tbb_version}_$LANG.zip.log | grep -o '[0-9a-f]\+[0-9a-f-]\+'`
  if [ -z "${request_uuid}" ]; then
    echo "Request UUID not present. Notarization failed"
    exit 1
  fi
  echo ${request_uuid} > tb-${tbb_version}_$LANG.zip.uuid
  echo "Notarization done for $LANG."

  cd ..
done

path: tools/signing/macos-signer-notarization


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

ssh -R :1080 "$ssh_host_macos_signer" 'python ~/proxy.py --port 8443'

path: tools/signing/macos-signer-proxy


code:
#!/bin/bash
set -e

script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

STAPLER=/Users/torbrowser/Xcode.app/Contents//Developer/usr/bin/stapler

cd ~/${tbb_version}

for LANG in $bundle_locales
do
  echo "Stapling $LANG..."
  cd $LANG
  $STAPLER staple Tor\ Browser.app
  zip -qr ../tb-${tbb_version}_$LANG-stapled.zip Tor\ Browser.app
  cd ..
done

path: tools/signing/macos-signer-stapler


code:
---
martools_version: 9.0.2
martools_url: https://archive.torproject.org/tor-package-archive/torbrowser/
martools_gpg_keyring: keyring/torbrowser.gpg
builds_url: /srv/tbb-nightlies-master.torproject.org/htdocs/nightly-builds/tor-browser-builds
builds_url_auth_basic_username: tor-guest
builds_url_auth_basic_password: tor-guest
publish_dirs:
    - nightly-linux-x86_64
    - nightly-linux-i686
    - nightly-windows-x86_64
    - nightly-windows-i686
    - nightly-osx-x86_64
nss_db_dir: nssdb
nss_certname: nightly-marsigner
gpg_keyring: keyring/torbrowser-nightly.gpg
rsync_dest: /srv/tbb-nightlies-master.torproject.org/htdocs/nightly-updates/
post_rsync_cmd: 'unset LD_PRELOAD && /usr/local/bin/static-update-component nightlies.tbb.torproject.org'
tmp_dir: /home/tbb-nightlies/tmp


code:
#!/bin/bash
set -e
nssdb="$(dirname "$0")/nssdb"
if test -d $nssdb
then
    echo "Error: $nssdb already exists" >&2
    exit 1
fi
mkdir -p $nssdb
chmod 700 $nssdb
certutil -d $nssdb -N --empty-password
certutil -d $nssdb -S -x -g 4096 -Z SHA384 -n nightly-marsigner -s "CN=Tor Browser Nightly MAR signing key" -t,,
certutil -d $nssdb -L -r -n nightly-marsigner -o $nssdb/nightly-marsigner.der


code:
#!/usr/bin/perl -w
use strict;
use FindBin;
use YAML::XS qw(LoadFile DumpFile);
use POSIX qw(setlocale LC_ALL);
use File::Path qw(make_path);
use File::Temp;
use File::Copy;
use File::Copy::Recursive qw(dirmove);
use File::Basename;
use LWP::Simple;
use Path::Tiny;
use DateTime;
use Digest::SHA qw(sha256_hex);
use JSON;

# Set umask and locale to provide a consistent environment
umask(0022);
$ENV{"LC_ALL"} = "C";
setlocale(LC_ALL, "C");
# autoflush STDOUT to have logs in the right order (see bug #40203)
STDOUT->autoflush(1);

sub exit_error {
    print STDERR "Error: ", $_[0], "\n";
    chdir '/';
    unlink "$FindBin::Bin/lock";
    exit (exists $_[1] ? $_[1] : 1);
}

exit_error "Missing config file: $FindBin::Bin/config.yml"
           unless -f "$FindBin::Bin/config.yml";
my $config = LoadFile("$FindBin::Bin/config.yml");
my $topdir = "$FindBin::Bin/../../..";

{
    no warnings 'redefine';
    sub LWP::UserAgent::get_basic_credentials {
        if ($config->{builds_url_auth_basic_username}
            && $config->{builds_url_auth_basic_password}) {
            return ( $config->{builds_url_auth_basic_username},
                     $config->{builds_url_auth_basic_password} );
        }
        return ();
    }
}

sub print_time {
    my $dt = DateTime->now;
    print $dt->ymd, " ", $dt->hms, " - ", @_;
}

END {
    print_time "Exiting sign-nightly (pid: $$)\n";
}

sub run_alone {
    my $pidfile = "$FindBin::Bin/lock";
    if (-f $pidfile) {
        my $pid = path($pidfile)->slurp_utf8;
        if (kill(0, $pid)) {
            print STDERR "Script is already running ($pid). Exiting.\n";
            exit 0;
        }
    }
    path($pidfile)->spew_utf8($$);
}

END {
    unlink "$FindBin::Bin/lock";
}

sub get_tmpdir {
    my ($config) = @_;
    return File::Temp->newdir($config->{tmp_dir} ?
                                (DIR => $config->{tmp_dir})
                                : ());
}

sub basedir_path {
    my ($path, $basedir) = @_;
    return ( $path =~ m|^/| ) ? $path : "$basedir/$path";
}

sub get_last_build_version {
    my ($config, $publish_dir) = @_;
    my $today = 'tbb-nightly.' . DateTime->now->ymd('.');
    my $dt_yesterday = DateTime->now - DateTime::Duration->new(days => 1);
    my $yesterday = 'tbb-nightly.' . $dt_yesterday->ymd('.');
    for my $version ($today, $yesterday) {
        my $url = "$config->{builds_url}/$version/$publish_dir/sha256sums-unsigned-build.incrementals.txt";
        if ($url =~ m|^/|) {
            return $version if -f $url;
        } else {
            return $version if get($url);
        }
    }
    return undef;
}

sub get_current_version {
    my ($publish_dir) = @_;
    my $file = "$topdir/nightly/$publish_dir-current-version.txt";
    return undef unless -f $file;
    return path($file)->slurp_utf8;
}

sub set_current_version {
    my ($publish_dir, $version) = @_;
    my $file = "$topdir/nightly/$publish_dir-current-version.txt";
    path($file)->spew_utf8($version);
}

sub get_new_version {
    my ($config, $publish_dir) = @_;
    my $today = 'tbb-nightly.' . DateTime->now->ymd('.');
    my $current_ver = get_current_version($publish_dir);
    my $last_ver = get_last_build_version($config, $publish_dir);
    return $last_ver unless defined($current_ver);
    return undef if $current_ver eq $today;
    return undef unless defined($last_ver);
    return undef if $current_ver eq $last_ver;
    return $last_ver;
}

sub get_file {
    my ($url, $file) = @_;
    return copy($url, $file) if $url =~ m|^/|;
    return getstore($url, $file) == 200;
}

sub get_file_sha256sum {
    my ($url, $file, $sha256sum) = @_;
    my $retries = $url =~ m|^/| ? 1 : 5;
    while ($retries > 0) {
        $retries--;
        print "Getting $url\n";
        next unless get_file($url, "$file.tmp");
        next unless $sha256sum eq sha256_hex(path("$file.tmp")->slurp_raw);
        move("$file.tmp", $file);
        return 1;
    }
    exit_error "Error getting $url";
}

sub fetch_version {
    my ($config, $publish_dir, $version) = @_;
    my $tmpdir = get_tmpdir($config);
    my $urldir = "$config->{builds_url}/$version/$publish_dir";
    my $destdir = "$topdir/nightly/$publish_dir/$version";

    return if -d $destdir;
    my $gpg_keyring = basedir_path($config->{gpg_keyring}, $topdir);
    for my $file (qw/sha256sums-unsigned-build.txt sha256sums-unsigned-build.incrementals.txt/) {
        my $url = "$urldir/$file";
        exit_error "Error getting $url"
                unless get_file($url, "$tmpdir/$file");
        exit_error "Error getting $url.asc"
                unless get_file("$url.asc", "$tmpdir/$file.asc");
        exit_error "Error checking gpg signature for $url"
                if system('gpg', '--no-default-keyring', '--keyring', $gpg_keyring,
                          '--verify', "$tmpdir/$file.asc",
                          "$tmpdir/$file");
    }
    my %sums = map { chomp; reverse split '  ', $_ }
        (
            path("$tmpdir/sha256sums-unsigned-build.txt")->lines_utf8,
            path("$tmpdir/sha256sums-unsigned-build.incrementals.txt")->lines_utf8,
        );
    my @build_infos_file = grep { $_ =~ m/build-infos-.*\.json/ } keys %sums;
    exit_error "Missing build-infos.json in $urldir" unless @build_infos_file;
    get_file_sha256sum("$urldir/$build_infos_file[0]",
                  "$tmpdir/build-infos.json", $sums{$build_infos_file[0]});
    foreach my $file (sort grep { $_ =~ m/\.mar$/ } keys %sums) {
        get_file_sha256sum("$urldir/$file", "$tmpdir/$file", $sums{$file});
    }
    make_path("$topdir/nightly/$publish_dir");
    dirmove($tmpdir, $destdir)
        or exit_error "Error moving $tmpdir to $destdir: $!";
    chmod 0755, $destdir;
}

sub setup_martools {
    my ($config) = @_;
    my $martools_dir = "$FindBin::Bin/mar-tools-$config->{martools_version}";
    if (! -d $martools_dir) {
        my $file = "mar-tools-linux64.zip";
        my $url = "$config->{martools_url}/$config->{martools_version}/$file";
        my $tmpdir = get_tmpdir($config);
        exit_error "Error downloading $url"
                unless getstore($url, "$tmpdir/$file") == 200;
        exit_error "Error downloading $url.asc"
                unless getstore("$url.asc", "$tmpdir/$file.asc") == 200;
        my $gpg_keyring = basedir_path($config->{martools_gpg_keyring}, $topdir);
        exit_error "Error checking gpg signature for $url"
                if system('gpg', '--no-default-keyring', '--keyring', $gpg_keyring,
                          '--verify', "$tmpdir/$file.asc",
                          "$tmpdir/$file");
        exit_error "Error extracting martools"
                unless system('unzip', '-d', $martools_dir, '-x',
                                       "$tmpdir/$file") == 0;
    }
    $ENV{PATH} = "$martools_dir/mar-tools:$ENV{PATH}";
    if ($ENV{LD_LIBRARY_PATH}) {
        $ENV{LD_LIBRARY_PATH} = "$martools_dir/mar-tools:$ENV{LD_LIBRARY_PATH}";
    } else {
        $ENV{LD_LIBRARY_PATH} = "$martools_dir/mar-tools";
    }
}

sub sign_version {
    my ($config, $publish_dir, $version) = @_;
    setup_martools($config);
    my $nss_db_dir = basedir_path($config->{nss_db_dir}, $FindBin::Bin);
    for my $marfile (path("$topdir/nightly/$publish_dir/$version")->children(qr/\.mar$/)) {
        print "Signing $marfile\n";
        exit_error "Error signing $marfile"
          unless system('signmar', '-d', $nss_db_dir, '-n',
                        $config->{nss_certname}, '-s', $marfile,
                        "$marfile-signed") == 0;
        move("$marfile-signed", $marfile);
    }
}

sub get_buildinfos {
    my ($filename) = @_;
    exit_error "$filename does not exist" unless -f $filename;
    return decode_json(path($filename)->slurp_utf8);
}

sub update_responses {
    my ($config, $publish_dir, $version) = @_;
    my $ur_config = LoadFile("$FindBin::Bin/update-responses-base-config.yml");
    $ur_config->{download}{mars_url} .= "/$publish_dir";
    $ur_config->{releases_dir} = "$topdir/nightly/$publish_dir";
    $ur_config->{channels}->{nightly} = $version;
    $ur_config->{versions}->{$version} = $ur_config->{versions}->{nightly_version};
    my $buildinfos = get_buildinfos("$topdir/nightly/$publish_dir/$version/build-infos.json");
    $ur_config->{versions}->{$version}{platformVersion} = $buildinfos->{firefox_platform_version};
    $ur_config->{versions}->{$version}{buildID} = $buildinfos->{firefox_buildid};
    DumpFile("$topdir/tools/update-responses/config.yml", $ur_config);
    my $htdocsdir = "$topdir/tools/update-responses/htdocs/nightly";
    path($htdocsdir)->remove_tree({ safe => 0 });
    exit_error "Error running update_responses for $publish_dir" unless
       system("$topdir/tools/update-responses/update_responses") == 0;
    path("$topdir/nightly/updates/$publish_dir/nightly")->remove_tree({ safe => 0 });
    make_path("$topdir/nightly/updates/$publish_dir");
    dirmove($htdocsdir, "$topdir/nightly/updates/$publish_dir/nightly")
        or exit_error "Error moving $htdocsdir to $topdir/nightly/updates/$publish_dir/nightly: $!";
}

sub remove_oldversions {
    my ($config, $publish_dir, $version) = @_;
    for my $dir (path("$topdir/nightly/$publish_dir")->children) {
        my ($filename) = fileparse($dir);
        next if $filename eq $version;
        path($dir)->remove_tree({ safe => 0 });
    }
}

sub sync_dest {
    my ($config) = @_;
    exit_error "Error running rsync"
        if system('rsync', '-aH', '--delete-after',
                  "$topdir/nightly/", "$config->{rsync_dest}/");
    if ($config->{post_rsync_cmd}) {
        exit_error "Error running $config->{post_rsync_cmd}"
                if system($config->{post_rsync_cmd});
    }
}

print_time "Starting sign-nightly (pid: $$)\n";
run_alone;
my $some_updates = 0;
foreach my $publish_dir (@{$config->{publish_dirs}}) {
    my $new_version = get_new_version($config, $publish_dir);
    next unless $new_version;
    fetch_version($config, $publish_dir, $new_version);
    sign_version($config, $publish_dir, $new_version);
    update_responses($config, $publish_dir, $new_version);
    set_current_version($publish_dir, $new_version);
    remove_oldversions($config, $publish_dir, $new_version);
    $some_updates = 1;
}
sync_dest($config) if $some_updates;

path: tools/signing/nightly/sign-nightly


code:
---
download:
    mars_url: https://nightlies.tbb.torproject.org/nightly-updates
appname_marfile: tor-browser
build_targets:
    linux32: Linux_x86-gcc3
    linux64: Linux_x86_64-gcc3
    win32:
        - WINNT_x86-gcc3
        - WINNT_x86-gcc3-x86
        - WINNT_x86-gcc3-x64
    win64: WINNT_x86_64-gcc3-x64
    osx32: Darwin_x86-gcc3
    osx64: Darwin_x86_64-gcc3
channels:
    nightly: nightly_version
versions:
    nightly_version:
        platformVersion: 68.2.0
        detailsURL: http://f4amtbsowhix7rrf.onion/
        migrate_langs:
            pt-PT: pt-BR
        minSupportedInstructionSet: SSE2
        osx32:
            minSupportedOSVersion: 13.0.0
        osx64:
            minSupportedOSVersion: 13.0.0
        win32:
            minSupportedOSVersion: 6.1
        win64:
            minSupportedOSVersion: 6.1
mar_compression: xz


code:
name = OpenSC-PKCS11
description = SunPKCS11 via OpenSC
library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
slotListIndex = 0


code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
     Entitlements to apply to the .app bundle and all executable files
     contained within it during codesigning of production channel builds that
     will be notarized. These entitlements enable hardened runtime protections
     to the extent possible for Firefox. Some supporting binaries within the
     bundle could use more restrictive entitlements, but they are launched by
     the main Firefox process and therefore inherit the parent process
     entitlements.
-->
<plist version="1.0">
  <dict>
    <!-- Firefox does not use MAP_JIT for executable mappings -->
    <key>com.apple.security.cs.allow-jit</key><false/>

    <!-- Firefox needs to create executable pages (without MAP_JIT) -->
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>

    <!-- Code paged in from disk should match the signature at page in-time -->
    <key>com.apple.security.cs.disable-executable-page-protection</key><false/>

    <!-- Allow dyld environment variables. Needed because Firefox uses
         dyld variables to load libaries from within the .app bundle. -->
    <key>com.apple.security.cs.allow-dyld-environment-variables</key><true/>

    <!-- Don't allow debugging of the executable. Debuggers will be prevented
         from attaching to running executables. Notarization does not permit
         access to get-task-allow (as documented by Apple) so this must be
         disabled on notarized builds. -->
    <key>com.apple.security.get-task-allow</key><false/>

    <!-- Firefox needs to access the microphone on sites the user allows -->
    <key>com.apple.security.device.audio-input</key><true/>

    <!-- Firefox needs to access the camera on sites the user allows -->
    <key>com.apple.security.device.camera</key><true/>

    <!-- Firefox needs to access the location on sites the user allows -->
    <key>com.apple.security.personal-information.location</key><true/>

    <!-- Allow Firefox to send Apple events to other applications. Needed
         for native messaging webextension helper applications launched by
         Firefox which rely on Apple Events to signal other processes. -->
    <key>com.apple.security.automation.apple-events</key><true/>

    <!-- For SmartCardServices(7) -->
    <key>com.apple.security.smartcard</key><true/>
  </dict>
</plist>


code:
. "$script_dir/set-config.tbb-version"
. "$script_dir/set-config.hosts"

bundle_locales="ar ca cs da de el en-US es-AR es-ES fa fr ga-IE he hu id is it ja ka ko lt mk ms my nb-NO nl pl pt-BR ro ru sv-SE th tr vi zh-CN zh-TW"

signed_dir="$script_dir/../../$tbb_version_type/signed"
signed_version_dir="$signed_dir/$tbb_version"
macos_stapled_dir="$signed_dir/$tbb_version-macos-stapled"
macos_signed_dir="$signed_dir/$tbb_version-macos-signed"

faketime_path=/usr/lib/x86_64-linux-gnu/faketime/libfaketime.so.1

echo "${BASH_ARGV0:-}" | grep -q '\.dry-run$' && DRY_RUN='--dry-run'
test -z "${NON_INTERACTIVE:-}" || rsync_progress="--progress"
rsync_options="-avH ${rsync_progress:-} ${DRY_RUN:-}"

tb_builders='boklm gk pierov richard sysrqb'

path: tools/signing/set-config


code:
# You should uncomment the following 2 lines:

#blog_directory=/path/to/blog.git
#blog_publish_user=user


code:
# The following line should be uncommented and updated:

#macos_notarization_user='user@email'

var_is_defined macos_notarization_user


code:
# The following 3 lines should be uncommented and updated:

#tbb_version=11.5a4
#tbb_version_build=1
#tbb_version_type=alpha

var_is_defined tbb_version tbb_version_build tbb_version_type


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

dist_dir="/srv/dist-master.torproject.org/htdocs/torbrowser/$tbb_version"
cat > "$dist_dir/.htaccess" << 'EOF'
RewriteEngine On
RewriteRule ^sha256sums.txt$ sha256sums-unsigned-build.txt
RewriteRule ^sha256sums.txt.asc$ sha256sums-unsigned-build.txt.asc
RewriteRule ^sha256sums.incrementals.txt$ sha256sums-unsigned-build.incrementals.txt
RewriteRule ^sha256sums.incrementals.txt.asc$ sha256sums-unsigned-build.incrementals.txt.asc
EOF
chgrp -R tb-release "$dist_dir"
chmod 775 "$dist_dir"
chmod 664 "$dist_dir"/*
chmod 664 "$dist_dir/.htaccess"

cdn_dir="/srv/cdn-master.torproject.org/htdocs/aus1/torbrowser/$tbb_version"
if test -d "$cdn_dir"
then
  echo "Error: $cdn_dir already exists" >&2
  exit 1
fi
mkdir "$cdn_dir"
chgrp tb-release "$cdn_dir"
chmod 775 "$cdn_dir"
cd "$cdn_dir"
for marfile in "$dist_dir"/*.mar; do
  ln -f "$marfile" .
done

echo "$tbb_version is ready to upload to cdn.tpo and dist.tpo"
echo "You should remove the old version(s) before starting the upload with:"
echo '  static-update-component cdn.torproject.org && static-update-component dist.torproject.org'
echo '(preferably using screen or tmux)'


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_builder builder_tor_browser_build_dir

rsync $rsync_options "$ssh_host_builder:$builder_tor_browser_build_dir/$tbb_version_type/signed/$tbb_version/" "$signed_version_dir/"

path: tools/signing/sync-builder-to-local


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_builder builder_tor_browser_build_dir

rsync $rsync_options "$ssh_host_builder:$builder_tor_browser_build_dir/$tbb_version_type/unsigned/$tbb_version-build$tbb_version_build/" "$signed_version_dir/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_linux_signer

rsync $rsync_options "$ssh_host_linux_signer:$tbb_version/" "$signed_version_dir/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_builder builder_tor_browser_build_dir

rsync $rsync_options "$signed_version_dir/" "$ssh_host_builder:$builder_tor_browser_build_dir/$tbb_version_type/signed/$tbb_version/"

path: tools/signing/sync-local-to-builder


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_linux_signer

rsync $rsync_options "$signed_version_dir/" "$ssh_host_linux_signer:$tbb_version/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

rsync $rsync_options "$signed_version_dir/" "$ssh_host_staticiforme:/srv/dist-master.torproject.org/htdocs/torbrowser/$tbb_version/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_macos_signer

rsync $rsync_options "$signed_version_dir"/*.dmg "$ssh_host_macos_signer:$tbb_version/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_macos_signer

rsync $rsync_options "$ssh_host_macos_signer:$tbb_version/*-stapled.zip" "$macos_stapled_dir/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_linux_signer

rsync $rsync_options "$script_dir/" "$ssh_host_linux_signer:signing-$tbb_version_type/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_macos_signer

rsync $rsync_options "$script_dir/" "$ssh_host_macos_signer:signing-$tbb_version_type/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_staticiforme

rsync $rsync_options "$script_dir/" "$ssh_host_staticiforme:signing-$tbb_version_type/"


code:
#!/bin/bash
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

check_torbrowser_version_var

update_responses_tar_filename="update-responses-$tbb_version_type-$tbb_version.tar"
update_responses_tar="$script_dir/../../$tbb_version_type/update-responses/$update_responses_tar_filename"
if test -f "$update_responses_tar"
then
  echo "$update_responses_tar_filename already exists: not running 'make update_responses-$tbb_version_type'"
else
  echo "Running 'make update_responses-$tbb_version_type'"
  pushd "$script_dir/../.." > /dev/null
  make update_responses-$tbb_version_type
  popd > /dev/null
fi

update_dir=/srv/aus1-master.torproject.org/htdocs/torbrowser/update_3
deploy_script=$(mktemp)
trap "rm -Rf $deploy_script" EXIT
cat << EOF > "$deploy_script"
#!/bin/bash
set -e

tmpdir="\$(mktemp -d)"

trap "rm -Rf \$tmpdir" EXIT

rm -Rf "$update_dir/$tbb_version_type.old"
test -d "$update_dir/$tbb_version_type" && \\
  mv -v "$update_dir/$tbb_version_type" "$update_dir/$tbb_version_type.old"

tar -C "\$tmpdir" -xf ~/$update_responses_tar_filename
chmod 775 "\$tmpdir"/$tbb_version_type
chmod 664 "\$tmpdir"/$tbb_version_type/* "\$tmpdir"/$tbb_version_type/.htaccess
chgrp -R torwww "\$tmpdir"/$tbb_version_type
mv -v "\$tmpdir"/$tbb_version_type "$update_dir/$tbb_version_type"

static-update-component aus1.torproject.org
EOF

chmod +x $deploy_script
scp -p "$update_responses_tar" "$ssh_host_staticiforme:"
scp -p $deploy_script $ssh_host_staticiforme:deploy_update_responses-$tbb_version_type.sh

echo 'To enable updates you can now run:'
echo "  ssh $ssh_host_staticiforme ./deploy_update_responses-$tbb_version_type.sh"


code:
#!/bin/bash
#
# This script will wait until a build is finished on ssh_host_builder.
#
# You can use this to start downloading a build immediately after it
# finished with something like:
#  $ ./wait-for-finished-build && ./sync-builder-unsigned-to-local-signed
#
set -e
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
source "$script_dir/functions"

var_is_defined ssh_host_builder builder_tor_browser_build_dir

while true
do
  ssh "$ssh_host_builder" test -f "$builder_tor_browser_build_dir/$tbb_version_type/unsigned/$tbb_version-build$tbb_version_build/sha256sums-unsigned-build.incrementals.txt" && \
    exit 0
  echo "$(date -Iseconds): Waiting for build to finish..."
  sleep 5m
done

path: tools/signing/wait-for-finished-build


code:
Tor Browser Update Responses script
===================================

This directory contains a script to generate xml update responses and
incremental mar files for the Tor Browser updater.

See ticket [#12622](https://gitlab.torproject.org/legacy/trac/-/issues/12622)
for details.

Dependencies
------------

The following perl modules need to be installed to run the script:
  FindBin YAML::XS File::Slurp Digest::SHA XML::Writer File::Temp
  IO::CaptureOutput Parallel::ForkManager XML::LibXML LWP JSON

On Debian / Ubuntu you can install them with:

```
  # apt-get install libfindbin-libs-perl libyaml-libyaml-perl \
                    libfile-slurp-perl libdigest-sha-perl libxml-writer-perl \
                    libio-captureoutput-perl libparallel-forkmanager-perl \
                    libxml-libxml-perl libwww-perl libjson-perl
```

On Red Hat / Fedora you can install them with:

```
  # for module in FindBin YAML::XS File::Slurp Digest::SHA XML::Writer \
                  File::Temp IO::CaptureOutput Parallel::ForkManager \
                  XML::LibXML LWP JSON
    do yum install "perl($module)"; done
```

Running the script
------------------

We usually don't run the script directly. Instead we run one of the
following make commands:

 - make update_responses-alpha or make update_responses-release to
   generate update responses files for an alpha or stable release.

 - make incrementals-alpha or make incrementals-release to generate
   incremental mar files for an alpha or stable release.

 - make dmg2mar-alpha or make dmg2mar-release to generate updated mar
   files for the macOS bundles, from their dmg files, for an alpha or
   stable release.

In addition to running the script, those make commands will generate
the configuration file for the script, in tools/update-responses/config.yml,
from a template in projects/release/update_responses_config.yml.

URL Format
----------

The generated update responses files expect the following URL format:
  https://something/$channel/$build_target/$tb_version/$lang?force=1

'build_target' is the OS for which the browser was built. The
correspondance between the build target and the OS name that we use in
archive files is defined in the config.yml file.

'tb_version' is the Tor Browser version.

'lang' is the locale.