Skip to content

Commit

Permalink
Merge pull request #2 from galaxy4public/master
Browse files Browse the repository at this point in the history
The first release of BootUnlock
  • Loading branch information
galaxy4public authored Jan 16, 2019
2 parents 3e9083f + 29fc0ce commit 6858473
Show file tree
Hide file tree
Showing 14 changed files with 1,052 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
all:
@build/build.sh
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# BootUnlock
A helper script that unlocks macOS'es encrypted APFS volumes before login

To build an macOS package you can either use "make" (if you have Xcode
installed) or just run "build/build.sh" (if you do not want to install Xcode).
The result will be the same: a package is going to be created in the "out"
directory.

To install the package just open it in Finder and follow the installation prompts.
24 changes: 24 additions & 0 deletions build/Distribution.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<installer-gui-script minSpecVersion="2">
<title>BootUnlock</title>
<product id="au.com.openwall.BootUnlock" version="1.0" />
<background file="background.png" scaling="proportional" alignment="bottomleft"/>
<welcome file="welcome.rtf"/>
<readme file="readme.rtf"/>
<license file="license.rtf"/>
<conclusion file="installed.rtf"/>
<options customize="never" rootVolumeOnly="true" require-scripts="false" />
<!--
<domains enable_anywhere="false" enable_currentUserHome="false" enable_localSystem="true" />
-->
<allowed-os-versions>
<os-version min="10.6.6" />
</allowed-os-versions>
<choices-outline>
<line choice="au.com.openwall.BootUnlock" />
</choices-outline>
<choice id="au.com.openwall.BootUnlock" visible="false">
<pkg-ref id="au.com.openwall.BootUnlock"/>
</choice>
<pkg-ref id="au.com.openwall.BootUnlock">BootUnlock-1.0-dist.pkg</pkg-ref>
</installer-gui-script>
41 changes: 41 additions & 0 deletions build/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# Name of the package.
NAME="BootUnlock"

# Once installed the identifier is used as the filename for a receipt files in /var/db/receipts/.
IDENTIFIER="au.com.openwall.$NAME"

# Package version number.
VERSION="1.0"

# The location to copy the contents of files.
INSTALL_LOCATION="/Library/PrivilegedHelperTools/$IDENTIFIER"

set -eu -o pipefail

WORK_DIR="${0%/*}"
[ -z "$WORK_DIR" -o "$WORK_DIR" == "$0" ] && WORK_DIR="$(pwd)" ||:

mkdir -p "$WORK_DIR/../out" ||:

# pkgbuild need proper permissions on the source files
chmod 0755 "$WORK_DIR/../files/"*.sh
chmod 0644 "$WORK_DIR/../files/"*.xsl

# Build package.
/usr/bin/pkgbuild \
--identifier "$IDENTIFIER" \
--version "$VERSION" \
--install-location "$INSTALL_LOCATION" \
--root "$WORK_DIR/../files" \
--scripts "$WORK_DIR/../scripts" \
"$WORK_DIR/../out/$NAME-$VERSION-dist.pkg"

/usr/bin/productbuild \
--distribution "$WORK_DIR/Distribution.xml" \
--package-path "$WORK_DIR/../out" \
--resources "$WORK_DIR/../resources" \
"$WORK_DIR/../out/$NAME-$VERSION.pkg"

rm "$WORK_DIR/../out/$NAME-$VERSION-dist.pkg"
21 changes: 21 additions & 0 deletions files/diskutil.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="/plist/dict[key = 'Containers']/array/dict[key = 'Volumes']/array/dict[key = 'Encryption']"/>
</xsl:template>
<xsl:template match="dict">
<xsl:apply-templates match="key|string|true|false" mode="dict" select="string[preceding-sibling::key[1]/text() = 'Name']"/>
<xsl:text>:</xsl:text>
<xsl:apply-templates match="key|string|true|false" mode="dict" select="string[preceding-sibling::key[1]/text() = 'APFSVolumeUUID']"/>
<xsl:text>:</xsl:text>
<xsl:apply-templates match="key|string|true|false" mode="dict" select="string[preceding-sibling::key[1]/text() = 'DeviceIdentifier']"/>
<xsl:text>:</xsl:text>
<xsl:apply-templates match="true|false" mode="dict" select="true[preceding-sibling::key[1]/text() = 'Encryption']"/>
<xsl:text>:</xsl:text>
<xsl:apply-templates match="true|false" mode="dict" select="true[preceding-sibling::key[1]/text() = 'Locked']"/>
<xsl:text>&#xA;</xsl:text>
</xsl:template>
<xsl:template match="true|false" mode="dict">
<xsl:value-of select="name()"/>
</xsl:template>
</xsl:stylesheet>
30 changes: 30 additions & 0 deletions files/helper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

set -eu -o pipefail

PATH=/sbin:/bin:/usr/sbin:/usr/bin

echo "=== $(date) ==="
diskutil apfs list -plist \
| xsltproc --novalid "${0%/*}/diskutil.xsl" - \
| grep -E ':true:true$' \
| cut -f1-3 -d':' \
| while IFS=: read NAME UUID DEVICE ; do
printf 'Trying to unlock volume "%s" with UUID %s ...\n' "$NAME" "$UUID"
if ! PASSPHRASE=$(${0%/*}/BootUnlock find-generic-password \
-D 'Encrypted Volume Password' \
-a "$UUID" -s "$UUID" -w); then
echo 'NOTICE: could not find the secret on the System keychain, skipping the volume.' >&2
continue
fi
if ! printf '%s' "$PASSPHRASE" | diskutil apfs unlock "$DEVICE" -stdinpassphrase ; then
if [ -z "${PASSPHRASE//[[:digit:][a-fA-F]}" ]; then # This may be a hexadecimal string
echo 'NOTICE: the passphrase looks like a hexdecimal string, re-trying ...' >&2
if printf '%s' "$PASSPHRASE" | xxd -r -p | diskutil apfs unlock "$DEVICE" -stdinpassphrase; then
continue
fi
fi
echo "ERROR: could not unlock volume '$NAME', skipping the volume." >&2
continue
fi
done
111 changes: 111 additions & 0 deletions files/update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/bin/bash

set -eu -o pipefail

PATH=/sbin:/bin:/usr/sbin:/usr/bin

[ "$(id -un)" = root ] \
|| exec -a "$0" osascript -e "
do shell script quoted form of \"$0\" & space & quoted form of \"$*\" with prompt \"BootUnlock Configurator requires administrative privileges to work with volumes and to update the System keychain.\" with administrator privileges" \
|| exit 1

# Location of the log file
LOG_FILE=/var/log/BootUnlock.log

# Determine the cannonical location of this script
WORK_DIR="${BASH_SOURCE%/*}"
[ "$WORK_DIR" != "$BASH_SOURCE" ] || WORK_DIR=.
pushd "$WORK_DIR" &>/dev/null
WORK_DIR=$(pwd -P)
popd &>/dev/null
SELF="$WORK_DIR/${0##*/}"

printf 'Redirecting standard output and errors to "%s" ...\n' "$LOG_FILE"
exec >>"$LOG_FILE" 2>&1
printf '===[ update.sh: %s ]===\n' "$(date)"

# A quick and dirty way of determining the root device :)
ROOT_DEVICE=$(df -l / | grep -E '^/dev/' | cut -f1 -d' ' | head -1 | cut -f3- -d/)

# Get the list of volumes with the encryption enabled
IFS=$'\n' VOLUME=($(diskutil apfs list -plist \
| xsltproc --novalid "${0%/*}/diskutil.xsl" - \
| grep -E ':true:(true)?$' \
| cut -f1-3 -d':' \
))

# Generate a list of volumes for GUI
VOLUME_LIST=
for V in "${VOLUME[@]}" ; do
NAME="${V%%:*}"; V="${V#*:}"
UUID="${V%%:*}"; V="${V#*:}"
DEVICE="${V%%:*}"; V="${V#*:}"

# macOS can automatically mount the system volume, so there is no point
# of presenting that choice to the user
[ "$DEVICE" != "$ROOT_DEVICE" ] || continue

VOLUME_LIST="$VOLUME_LIST${VOLUME_LIST:+, }\"$DEVICE > $NAME\""
done

RESPONSE=$(osascript -e "
set volumeList to { $VOLUME_LIST }
set unlockList to choose from list volumeList with title \"BootUnlock\" with prompt \"
Please select volume(s) you want to be automatically unlocked during the boot (you can select multiple volumes by holding the Option key)\" multiple selections allowed true empty selection allowed true
")

if [ -z "$RESPONSE" -o "$RESPONSE" = false ]; then
osascript -e "display alert \"BootUnlock\" message \"
You did not select any volumes, so BootUnlock is not going to do anything at the system boot up time.
If you reconsider and will want to enable unlocking of a particular volume you can re-run the '$SELF' script at a later time
\" as critical" &>/dev/null
exit 0
fi

RESPONSE="$(printf '%s' "$RESPONSE" | tr ',' '\n' | sed 's,^[[:space:]]*,,;s,>.*$,,;s,[[:space:]]*$,,')"

# Real work starts here :)
for V in "${VOLUME[@]}" ; do
NAME="${V%%:*}"; V="${V#*:}"
UUID="${V%%:*}"; V="${V#*:}"
DEVICE="${V%%:*}"; V="${V#*:}"

# We are going to work only on the volumes selected by the user
printf '%s' "$RESPONSE" | grep -E "^$DEVICE\$" &>/dev/null || continue

while : ; do # This is a wrapper in case user provides a wrong password
PASSPHRASE=$(osascript -e "
display dialog \"Please provide the passphrase for volume '$NAME':\" with title \"BootUnlock\" buttons { \"Skip\", \"Unlock\" } default button \"Unlock\" with icon file ((path to \"apps\" as text) & \"Utilities:Disk Utility.app:Contents:Resources:AppIcon.icns\") default answer \"\" hidden answer true
")

PASSPHRASE=$(printf '%s' "$PASSPHRASE" | cut -f3- -d:)

if printf '%s' "$PASSPHRASE" | diskutil apfs unlock "$DEVICE" -stdinpassphrase -verify -user "$UUID"; then
printf 'Adding password for volume "%s" with UUID %s to the System keychain...\n' "$NAME" "$UUID"
if sudo /usr/bin/security add-generic-password \
-a "$UUID" -s "$UUID" -l "$NAME" \
-D 'Encrypted Volume Password' \
-T '' -T "$WORK_DIR/BootUnlock" \
-w "$PASSPHRASE" \
-U \
/Library/Keychains/System.keychain; then
break
else
osascript -e "display alert \"BootUnlock\" message \"
BootUnlock experienced an internal error while adding password to the System keychain.
Please check the /var/log/BootUnlock.log log file for more information.
\" as critical" &>/dev/null
exit 1
fi
else
osascript -e "display alert \"BootUnlock\" message \"
The specified password for volume '$NAME' does not seem to be valid. BootUnlock will prompt you for the password again.
If you believe that you are providing the correct password, yet it is not recognised, please check the /var/log/BootUnlock.log log file for more information.
\" as critical" &>/dev/null
fi
done
done

Binary file added resources/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions resources/installed.rtf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf200
{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Menlo-Regular;
}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
{\*\expandedcolortbl;;\csgray\c0;}
\paperw11900\paperh16840\margl1440\margr1440\vieww17240\viewh12860\viewkind0
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc\partightenfactor0

\f0\b\fs28 \cf0 BootUnlock\
\
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qj\partightenfactor0
\f1\b0 \cf0 It seems that you have successfully installed
\f0\b BootUnlock
\f1\b0 to your system. Now, to test that it works properly it is recommended to reboot the system and try to log in as your normal user account. If everything works as expected you will be able to login and see the volumes you selected earlier to be automatically unlocked.\
\
If you decide that BootUnlock is not for you, then you can completely clean it out of your system by removing the
\f2 /Library/PrivilegedHelperTools/au.com.openwall.BootUnlock
\f1 directory and the
\f2 \cf2 \CocoaLigature0 /Library/LaunchDaemons/au.com.openwall.BootUnlock.plist
\f1 file.}
Loading

0 comments on commit 6858473

Please sign in to comment.