├── create_keystore.sh ├── LICENSE ├── rc.local └── README.md /create_keystore.sh: -------------------------------------------------------------------------------- 1 | if [[ ${PWD:0:7} = "/media/" ]]; then 2 | keytool -genkey -v -keystore my-key.keystore -keyalg RSA -keysize 2048 -validity 10000 3 | 4 | echo "Make sure to move your keystore file to your secure storage" 5 | echo "Make sure to (manually) create a textfile called alias.txt, ks-pass.txt and key-pass.txt with your password on your secure storage" 6 | echo "!!!Make sure you have multiple copies of these files on various secure devices!!!" 7 | else 8 | echo "Run this script directly on your USB device" 9 | fi 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 AirGap.it 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rc.local: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # 3 | # rc.local 4 | # 5 | # This script is executed at the end of each multiuser runlevel. 6 | # Make sure that the script will "exit 0" on success or any other 7 | # value on error. 8 | # 9 | # In order to enable or disable this script just change the execution 10 | # bits. 11 | # 12 | # By default this script does nothing. 13 | 14 | # Print the IP address 15 | _IP=$(hostname -I) || true 16 | if [ "$_IP" ]; then 17 | printf "My IP address is %s\n" "$_IP" 18 | fi 19 | 20 | # Disable wifi 21 | sudo iwconfig wlan0 txpower off 22 | 23 | # Disable ethernet 24 | sudo ip link set eth0 down 25 | 26 | # Watch for mounted devices 27 | cd /media/ 28 | 29 | for i in `seq 1 100`; 30 | do 31 | # Search for *.apk 32 | APK="$(find $PWD -type f -name "*.apk" -not -path "*/\.*" | head -n 1)" 33 | 34 | # Search for *.keystore 35 | KEYSTORE="$(find $PWD -type f -name "*.keystore" | head -n 1)" 36 | KEYPASS="$(find $PWD -type f -name "key-pass.txt" | head -n 1)" 37 | KSPASS="$(find $PWD -type f -name "ks-pass.txt" | head -n 1)" 38 | ALIAS="$(find $PWD -type f -name "alias.txt" | head -n 1)" 39 | 40 | if [ -z "$APK" ] || [ -z "$KEYSTORE" ] || [ -z "$KEYPASS" ] || [ -z "$KSPASS" ] || [ -z "$ALIAS" ]; then 41 | echo "One of the following is missing: *.apk, *.keystore, key-pass.txt, ks-pass.txt or alias.txt $i/100" 42 | else 43 | APKS=$(find $PWD -type f -name "*.apk" -not -path "*/\.*") 44 | for i in $APKS ; do 45 | echo "Everything found, start signing..." 46 | BASE="$(dirname $i)" 47 | FILENAME="$(basename $i)" 48 | FILENAME_NO_EXTENSION="${FILENAME%.*}" 49 | OUTFILE="$BASE/$FILENAME_NO_EXTENSION-signed.apk" 50 | /home/pi/android-sdk-linux/build-tools/28.0.0/apksigner sign --key-pass pass:$(cat "$KEYPASS") --ks-pass pass:$(cat "$KSPASS") --ks $KEYSTORE --ks-key-alias $(cat "$ALIAS") --out $OUTFILE $i 51 | echo "Created file:" 52 | echo $OUTFILE 53 | done 54 | 55 | echo "Shutdown..." 56 | sudo shutdown -h now 57 | fi 58 | sleep 1; 59 | done; 60 | 61 | exit 0 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # airgap-raspberry-apk-signer 2 | 3 | Modified Raspberry Pi image to sign your Android APK the most secure way (airgapped). 4 | 5 | ## Idea / Motivation 6 | 7 | During the development process of AirGap, we came to the conlusion that probably a lot of devices will receive the app through alternative channels such as sideloading or sending the file through QR codes to the airgapped device. As this process requires the phone to be configured to allow installtions from unknown sources, it's crucial to make sure that the user does not install a malicious application. Android prevents this through application signatures. If the application was installed once, it will fail if the user tries to update the application with a newer version but signed by a different developer. This is fine, as long as the developer can guarantee that his signing certificate has never been compromised. 8 | 9 | As our team has a strong focus on security, we knew this signing step can only be safe, if it's done properly. We also knew, the only way to achieve the highest level of security is if we airgap this step as well. 10 | 11 | ## Solution 12 | 13 | ### Requirements 14 | 15 | - Raspberry Pi3 16 | - Two USB Sticks, one of them with Hardware encryption (we use an iStorage datashur - https://istorage-uk.com/product/datashur/) 17 | - This Raspberry distribution 18 | 19 | ### Setup 20 | 21 | You have to connect your raspberry pi to the internet for the initial setup. You then have to do the following steps that we will explain in more detail below: 22 | 23 | 1. Install Java 24 | 2. Install android-build-tools 25 | 3. Install auto-mount 26 | 4. Setup boot script 27 | 5. Generate a secure keystore 28 | 29 | #### 1. Install Java 30 | 31 | ``` 32 | sudo apt-get update 33 | 34 | sudo apt-get install oracle-java8-jdk 35 | ``` 36 | 37 | #### 2. Download android SDK and apksigner 38 | 39 | ``` 40 | cd $HOME 41 | mkdir android-sdk-linux 42 | cd android-sdk-linux 43 | mkdir build-tools 44 | cd build-tools 45 | wget https://dl.google.com/android/repository/build-tools_r28-linux.zip 46 | unzip build-tools_r28-linux.zip 47 | mv android-9 28.0.0 48 | rm build-tools_r28-linux.zip 49 | ``` 50 | 51 | #### 3. Install auto-mount 52 | 53 | ``` 54 | sudo apt-get install usbmount 55 | ``` 56 | 57 | Then you need to set a flag from 58 | 59 | `MountFlags=slave` to `MountFlags=shared` 60 | 61 | in the following file 62 | 63 | ``` 64 | sudo nano /lib/systemd/system/systemd-udevd.service 65 | ``` 66 | 67 | #### 4. Setup boot script 68 | 69 | Copy the `rc.local` from this repository to `/etc/rc.local` 70 | 71 | The easiest way to do that is to copy the file to the root of a usb stick and then plug it into the raspberry pi. It should automatically mount the usb-stick in `/media/usb0/`. 72 | 73 | So you can copy the file using this command: 74 | 75 | ``` 76 | sudo cp /media/usb0/rc.local /etc/ 77 | ``` 78 | 79 | ### 5. Generate a secure keystore 80 | 81 | 1. Attach a monitor and a keyboard to your Raspberry. 82 | 2. Wait until all signing attempts (100) passed. 83 | 3. Login with the user pi (password raspberry) 84 | 4. Copy the file .create_keystore.sh to your encrypted usb drive 85 | 5. Run the script 86 | 6. Create the following files on your encrypted usb drive: 87 | - alias.txt (containing your alias) 88 | - ks-pass.txt (containing keystore password) 89 | - key-pass.txt (containing your key alias password) 90 | 7. Done 91 | 92 | ## Usage 93 | 94 | ### Sign an APK 95 | 96 | As soon as you have your keystore on the secure USB storage, signing an APK is easy, **no keyboard or monitor is required**. 97 | 98 | 1. Boot the Raspberry 99 | 2. Connect both of your USB sticks to the Raspberry 100 | 3. Wait a few minutes, usually the indicators on the USB will stop blinking. 101 | 4. Done, if the Raspberry lights are not blinking anymore. 102 | 103 | It will automatically find the apk and your key, no interaction required. A file called android-release.apk will be written to the same folder where the apk file was found. 104 | 105 | ## About 106 | 107 | We use a base raspbian image, with additional installed android sdk signing tools. Beside the create_keystore.sh file, we also adjusted the rc.local (bootup) file to automatically sign without the need of a monitor attached. 108 | 109 | ## Remarks 110 | 111 | - Only use the SD card for this single purpose 112 | - If you are super paranoid (like we are), only use the Raspberry for this single purpose 113 | - Even though we disabled all networking (ethernet, bluetooth, wifi) best practive is to not connect an ethernet cable to the device. 114 | - **Make sure that you always create a backup of your keystore file. If the file is lost, you are not longer able to sign and therefore provide updates for your existing applications!** 115 | --------------------------------------------------------------------------------