Skip to content

Commit

Permalink
make signing work with sops decryption
Browse files Browse the repository at this point in the history
works by setting up a temp directory off of /dev/shm, the same way we
were already doing for the release keys. except now, it's safe to
include your encrypted keys in the repo with the rest of your config.
just don't forget to --extra-sandbox-paths <path to key file> for your
private key.

example config is included.
  • Loading branch information
cassandracomar committed Apr 7, 2023
1 parent 1f5825d commit 32b23df
Show file tree
Hide file tree
Showing 35 changed files with 633 additions and 52 deletions.
3 changes: 3 additions & 0 deletions .sops.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
creation_rules:
- path_regex: test-keys/.*
age: "age18xp0rzq0520kt7jfcrf0ky6lc3s0f4953cfgtjf4nrs7gp0dm43qh9lzm7"
1 change: 0 additions & 1 deletion check.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ let
in {
signing.enable = true;
signing.keyStorePath = builtins.toString snakeoilKeys;
signing.buildTimeKeyStorePath = "${snakeoilKeys}";
};
in {
check = lib.recurseIntoAttrs (lib.mapAttrs (name: c: (robotnix c).config.build.checkAndroid) configs);
Expand Down
10 changes: 10 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@
flavor = "grapheneos";
apv.enable = false;
adevtool.hash = "sha256-aA54o2FPfI+9iDLiUaGJAqMzUuNyWwCuWOoa1lADKuM=";
signing = {
enable = true;
keyStorePath = ./test-keys;
sopsDecrypt = {
enable = true;
sopsConfig = ./.sops.yaml;
key = ./test-keys/keystore-private-keys.txt;
keyType = "age";
};
};
};
}) [ "bluejay" "panther" "cheetah" ]));
};
Expand Down
21 changes: 11 additions & 10 deletions modules/apps/prebuilt.nix
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,12 @@ let
};
deviceCertificates = lib.attrNames defaultDeviceCertFingerprints;

_keyPath = keyStorePath: name:
keyPath = name:
if builtins.elem name deviceCertificates
then (if config.signing.enable
then "${keyStorePath}/${config.device}/${name}"
else "${keyStorePath}/${lib.replaceStrings ["releasekey"] ["testkey"] name}") # If not signing.enable, use test keys from AOSP
else "${keyStorePath}/${name}";
evalTimeKeyPath = name: _keyPath config.signing.keyStorePath name;
buildTimeKeyPath = name: _keyPath config.signing.buildTimeKeyStorePath name;
then "${config.device}/${name}"
else "${lib.replaceStrings ["releasekey"] ["testkey"] name}") # If not signing.enable, use test keys from AOSP
else "${name}";

putInStore = path: if (lib.hasPrefix builtins.storeDir path) then path else (/. + path);

Expand Down Expand Up @@ -207,17 +205,20 @@ in
inherit (config) apk;
keyPath =
if _config.signing.enable
then buildTimeKeyPath config.certificate
# $KEYSDIR is set by the withKeys wrapper
then "$KEYSDIR/${keyPath config.certificate}"
else "${config.snakeoilKeyPath}/${config.certificate}";
keysFun = _config.build.signing.withKeys _config.signing.keyStorePath;
}));

fingerprint = let
snakeoilFingerprint = pkgs.robotnix.certFingerprint "${config.snakeoilKeyPath}/${config.certificate}.x509.pem";
snakeoilFingerprint = pkgs.robotnix.certFingerprint (s: s) "${config.snakeoilKeyPath}/${config.certificate}.x509.pem";
in mkDefault (
if config.certificate == "PRESIGNED"
then pkgs.robotnix.apkFingerprint config.signedApk # TODO: IFD
else if _config.signing.enable
then pkgs.robotnix.certFingerprint (putInStore "${evalTimeKeyPath config.certificate}.x509.pem") # TODO: IFD
# $KEYSDIR is set by the withKeys wrapper
then pkgs.robotnix.certFingerprint (_config.build.signing.withKeys _config.signing.keyStorePath) "$KEYSDIR/${keyPath config.certificate}.x509.pem" # TODO: IFD
else # !_config.signing.enable
defaultDeviceCertFingerprints.${name} or (
builtins.trace ''
Expand Down Expand Up @@ -247,7 +248,7 @@ in
mkdir -p $out/
cp ${config.certificate}.{pk8,x509.pem} $out/
'';
};
};
}));
};
};
Expand Down
3 changes: 2 additions & 1 deletion modules/base.nix
Original file line number Diff line number Diff line change
Expand Up @@ -506,13 +506,14 @@ in
dontBuild = true;

installPhase = ''
mkdir -p $out/unmodified
cp --reflink=auto -r * $out/unmodified
for file in bin/*; do
isELF "$file" || continue
bash ${../scripts/patchelf-prefix.sh} "$file" "${pkgs.stdenv.cc.bintools.dynamicLinker}" || continue
done
'' + ''
set -x
mkdir -p $out
cp --reflink=auto -r * $out/
'' + lib.optionalString (config.androidVersion <= 10) ''
ln -s $out/releasetools/sign_target_files_apks.py $out/bin/sign_target_files_apks
Expand Down
29 changes: 7 additions & 22 deletions modules/release.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,16 @@ let
[ otaTools openssl jre zip unzip pkgs.getopt which toybox vboot_reference utillinux
python3 # ota_from_target_files invokes, brillo_update_payload which has "truncate_file" which invokes python
];
in ''
in config.build.signing.withKeys keysDir ''
export PATH=${lib.makeBinPath deps}:$PATH
export EXT2FS_NO_MTAB_OK=yes
# build-tools releasetools/common.py hilariously tries to modify the
# permissions of the source file in ZipWrite. Since signing uses this
# function with a key, we need to make a temporary copy of our keys so the
# sandbox doesn't complain if it doesn't have permissions to do so.
export KEYSDIR=${keysDir}
if [[ "$KEYSDIR" ]]; then
if [[ ! -d "$KEYSDIR" ]]; then
echo 'Missing KEYSDIR directory, did you use "--option extra-sandbox-paths /keys=..." ?'
exit 1
fi
${lib.optionalString config.signing.enable "${config.build.verifyKeysScript} \"$KEYSDIR\" || exit 1"}
NEW_KEYSDIR=$(mktemp -d /dev/shm/robotnix_keys.XXXXXXXXXX)
trap "rm -rf \"$NEW_KEYSDIR\"" EXIT
cp -r "$KEYSDIR"/* "$NEW_KEYSDIR"
chmod u+w -R "$NEW_KEYSDIR"
KEYSDIR=$NEW_KEYSDIR
fi
${commands}
'';

runWrappedCommand = name: script: args: pkgs.runCommand "${config.device}-${name}-${config.buildNumber}.zip" {} (wrapScript {
commands = script (args // {out="$out";});
keysDir = config.signing.buildTimeKeyStorePath;
keysDir = config.signing.keyStorePath;
});

signedTargetFilesScript = { targetFiles, out }: ''
Expand All @@ -60,6 +42,7 @@ let
'';
imgScript = { targetFiles, out }: ''img_from_target_files ${targetFiles} ${out}'';
factoryImgScript = { targetFiles, img, out }: ''
set -x
ln -s ${targetFiles} ${config.device}-target_files-${config.buildNumber}.zip || true
ln -s ${img} ${config.device}-img-${config.buildNumber}.zip || true
Expand All @@ -69,15 +52,17 @@ let
export VERSION=${lib.toLower config.buildNumber}
get_radio_image() {
${lib.getBin pkgs.unzip}/bin/unzip -p ${targetFiles} OTA/android-info.txt \
${lib.getBin pkgs.libarchive}/bin/bsdtar xvf ${targetFiles} -O OTA/android-info.txt \
| grep "require version-$1" | cut -d'=' -f2 | tr '[:upper:]' '[:lower:]' || exit 1
}
export BOOTLOADER=$(get_radio_image bootloader)
export RADIO=$(get_radio_image baseband)
export PATH=${lib.getBin pkgs.zip}/bin:${lib.getBin pkgs.unzip}/bin:$PATH
${pkgs.runtimeShell} ${config.source.dirs."device/common".src}/generate-factory-images-common.sh
mv *-factory-*.zip ${out}
ls -l
dd if=${config.device}-factory-${config.buildNumber}.zip of=${out}
ls -l $out
'';
in
{
Expand Down
87 changes: 73 additions & 14 deletions modules/signing.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ let
cfg = config.signing;

# TODO: Find a better way to do this?
putInStore = path: if (lib.hasPrefix builtins.storeDir path) then path else (/. + path);
putInStore = path: path;

keysToGenerate = lib.unique (lib.flatten (
map (key: "${config.device}/${key}") [ "releasekey" "platform" "shared" "media" ]
Expand Down Expand Up @@ -83,19 +83,48 @@ in
};

keyStorePath = mkOption {
type = types.str;
type = types.either types.str types.path;
description = ''
String containing absolute path to generated keys for signing.
This must be a _string_ and not a "nix path" to ensure that your secret keys are not imported into the public `/nix/store`.
String containing absolute path to generated keys for signing or relative path if your keys are encrypted with sops.
This must be a _string_ and not a "nix path" to ensure that your plain-text secret keys are not imported into the public `/nix/store`,
if you do not enable sops encryption. Read the documentation for sopsDecrypt.keyType for more details.
If this value is an absolute path, make sure to add this path to extra-sandbox-paths in your nix config or pass --extra-sandbox-paths
to the nix cli so that these keys are available.
'';
example = "/var/secrets/android-keys";
};

buildTimeKeyStorePath = mkOption {
type = with types; either str path;
description = ''
Path to generated keys for signing to use at build-time, as opposed to keyStorePath, which is used at evaluation-time.
'';
sopsDecrypt = {
enable = mkEnableOption "decrypt key files using sops";
keyType = {
type = types.enum ["age" "pgp_public" "pgp"];
description = ''
denotes the kind of key passed to this module:
* age - the key refers to an age keys.txt file that contains the age key(s), one line per key
* pgp - the key refers to a pgp public key and the private key will need to be read from config.signing.sopsDecrypt.gpgHome
make sure to add `--extra-sandbox-paths "$GPG_HOME"` to the nix cli invocation to ensure this path is readable.
'';
};
key = {
type = types.str;
description = "see keyType";
};
sopsConfig = {
type = types.path;
description = ''
config file for sops to use to choose a private key from those provided -- refer to https://github.com/mozilla/sops/ for details on how to provide this file.
'';
};
gpgHome = {
type = types.str;
description = "see keyType";
};
gpgTTY = {
type = types.str;
description = "if your pgp key must be read from a card, you will need to set this var so GPG_TTY gets set correctly";
};
};
};
};
Expand All @@ -111,10 +140,6 @@ in
];

signing.keyStorePath = mkIf (!config.signing.enable) (mkDefault testKeysStorePath);
signing.buildTimeKeyStorePath = mkMerge [
(mkIf config.signing.enable (mkDefault "/keys"))
(mkIf (!config.signing.enable) (mkDefault testKeysStorePath))
];
signing.avb.fingerprint = mkIf config.signing.enable (mkOptionDefault
(pkgs.robotnix.sha256Fingerprint (putInStore "${config.signing.keyStorePath}/${config.device}/avb_pkmd.bin"))
);
Expand Down Expand Up @@ -185,6 +210,10 @@ in
"packages/modules/Wifi/service/ServiceWifiResources/resources-certs/com.android.wifi.resources" = "com.android.wifi.resources";
"packages/modules/Connectivity/service/ServiceConnectivityResources/resources-certs/com.android.connectivity.resources" = "com.android.connectivity.resources";
}
// lib.optionalAttrs (config.androidVersion >= 13) {
"build/make/target/product/security/bluetooth" = "${config.device}/bluetooth";
"build/make/target/product/security/sdk_sandbox" = "${config.device}/sdk_sandbox";
}
# App-specific keys
// lib.mapAttrs'
(name: prebuilt: lib.nameValuePair "robotnix/prebuilt/${prebuilt.name}/${prebuilt.certificate}" prebuilt.certificate)
Expand Down Expand Up @@ -212,7 +241,7 @@ in
${config.source.dirs."system/extras".src}/verity/generate_verity_key.c \
${config.source.dirs."system/core".src}/libcrypto_utils/android_pubkey.c${lib.optionalString (config.androidVersion >= 12) "pp"} \
-I ${config.source.dirs."system/core".src}/libcrypto_utils/include/ \
-I ${pkgs.boringssl}/include ${pkgs.boringssl}/lib/libssl.a ${pkgs.boringssl}/lib/libcrypto.a -lpthread
-I ${pkgs.boringssl.dev}/include ${pkgs.boringssl}/lib/libssl.a ${pkgs.boringssl}/lib/libcrypto.a -lpthread
cp ${config.source.dirs."external/avb".src}/avbtool $out/bin/avbtool
Expand Down Expand Up @@ -330,6 +359,36 @@ in
fi
exit $RETVAL
'';

build.signing.withKeys = keysDir: script: ''
set -x
export KEYSDIR=${keysDir}
if [[ "$KEYSDIR" ]]; then
if [[ ! -d "$KEYSDIR" ]]; then
echo 'Missing KEYSDIR directory, did you use "--option extra-sandbox-paths /keys=..." ?'
exit 1
fi
${lib.optionalString config.signing.enable "${config.build.verifyKeysScript} \"$KEYSDIR\" || exit 1"}
NEW_KEYSDIR=$(mktemp -d /dev/shm/robotnix_keys.XXXXXXXXXX)
trap "rm -rf \"$NEW_KEYSDIR\"" EXIT
# copy the keys over
SOPS_AGE_FILE=${lib.optionalString (config.signing.sopsDecrypt.keyType == "age") config.signing.sopsDecrypt.key};
SOPS_PGP_KEY=${lib.optionalString (config.signing.sopsDecrypt.keyType == "pgp") config.signing.sopsDecrypt.key};
GPG_HOME=${lib.optionalString (config.signing.sopsDecrypt.keyType == "pgp") config.signing.sopsDecrypt.gpgHome};
GPG_TTY=${lib.optionalString (config.signing.sopsDecrypt.keyType == "pgp" && builtins.isString config.signing.sopsDecrypt.gpgTTY) config.signing.sopsDecrypt.gpgTTY};
cd $KEYSDIR
for f in `find . -type f`; do
mkdir -p $(dirname $NEW_KEYSDIR/''${f#./})
${if config.signing.sopsDecrypt.enable then "sops -c ${config.signing.sopsDecrypt.sopsConfig} -d $f --output" else "cp $f"} $NEW_KEYSDIR/''${f#./}
done
# now set the new KEYSDIR and run the script
KEYSDIR=$NEW_KEYSDIR
chmod u+w -R "$NEW_KEYSDIR"
${script}
fi
'';
};

imports = [
Expand Down
8 changes: 4 additions & 4 deletions pkgs/robotnix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ let
echo "\"$fingerprint\"" > $out
'');

certFingerprint = cert: (import (runCommand "cert-fingerprint" {} ''
certFingerprint = keysFun: cert: (import (runCommand "cert-fingerprint" {} (keysFun ''
${openssl}/bin/openssl x509 -noout -fingerprint -sha256 -in ${cert} | awk -F"=" '{print "\"" $2 "\"" }' | sed 's/://g' > $out
''));
'')));

sha256Fingerprint = file: lib.toUpper (builtins.hashFile "sha256" file);

Expand All @@ -39,10 +39,10 @@ let
--add-flags "-jar ${build-tools}/lib/apksigner.jar"
'';

signApk = { apk, keyPath, name ? (getName "signApk" apk) + "-signed.apk" }: runCommand name {} ''
signApk = { apk, keyPath, keysFun, name ? (getName "signApk" apk) + "-signed.apk" }: runCommand name {} (keysFun ''
cp ${apk} $out
${apksigner}/bin/apksigner sign --key ${keyPath}.pk8 --cert ${keyPath}.x509.pem $out
'';
'');

# Currently only supports 1 signer.
verifyApk = { apk, sha256, name ? (getName "verifyApk" apk) + ".apk" }: runCommand name {} ''
Expand Down
20 changes: 20 additions & 0 deletions test-keys/com.android.connectivity.resources.pk8
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"data": "ENC[AES256_GCM,data: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,iv:my0acZPyeRnGu08JPgZM3UhLHPBIrg0x7Klst4tfpR4=,tag:68s3oZoARLET4f8MvkhjTA==,type:str]",
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age18xp0rzq0520kt7jfcrf0ky6lc3s0f4953cfgtjf4nrs7gp0dm43qh9lzm7",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWTVZjbzFoc3VhQ2NyeWM3\nS295UXM2Y21FQWJJNWtWMVpJdEorYWptazJFCjl5VnlIYVhZTXNKU2ZLdW11YU4w\nQndjbzNobjVURUppdEQwR3VmRFRtNGcKLS0tIHpsUzUvVFlnalQ0c25KNzVIWHZB\nelcxZWZmQmJKMjRqOWdsUExYNVdSSjQKBLGWZDDUmImXDL+S0je4khEN6GgUjrcb\nsjJIjtbhPxOToNobo/+ykw6LkrMjGbj6b9kOGuuE/gROKIdGttRzXQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2023-04-07T18:49:57Z",
"mac": "ENC[AES256_GCM,data:H/2VJSKzPeLP2n9czIdyoIddhjcwKIEBDtQVzsbiewJFhH21ul8JB7nsmQBm5OopcjGMBxYgF8MQTK38lCqNRzqeINEakPgS3ktOvn3HAHjE3t+qRpW0x8eLxlW/cgdIyska0sHcNIZcimq6J/i361GPSTfhuAj7nDJQ8rrKplM=,iv:Za8XkIErwWMbxs5aZhd1cUBJjAEaXUs9aSNJhNXBMBs=,tag:N0hAFu+QlQyggHofhQCLjg==,type:str]",
"pgp": null,
"unencrypted_suffix": "_unencrypted",
"version": "3.7.3"
}
}
Loading

0 comments on commit 32b23df

Please sign in to comment.