Skip to content

Commit 29fc0ce

Browse files
committed
The first release of BootUnlock
1 parent 3e9083f commit 29fc0ce

File tree

14 files changed

+1052
-0
lines changed

14 files changed

+1052
-0
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
all:
2+
@build/build.sh

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
# BootUnlock
22
A helper script that unlocks macOS'es encrypted APFS volumes before login
3+
4+
To build an macOS package you can either use "make" (if you have Xcode
5+
installed) or just run "build/build.sh" (if you do not want to install Xcode).
6+
The result will be the same: a package is going to be created in the "out"
7+
directory.
8+
9+
To install the package just open it in Finder and follow the installation prompts.

build/Distribution.xml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
2+
<installer-gui-script minSpecVersion="2">
3+
<title>BootUnlock</title>
4+
<product id="au.com.openwall.BootUnlock" version="1.0" />
5+
<background file="background.png" scaling="proportional" alignment="bottomleft"/>
6+
<welcome file="welcome.rtf"/>
7+
<readme file="readme.rtf"/>
8+
<license file="license.rtf"/>
9+
<conclusion file="installed.rtf"/>
10+
<options customize="never" rootVolumeOnly="true" require-scripts="false" />
11+
<!--
12+
<domains enable_anywhere="false" enable_currentUserHome="false" enable_localSystem="true" />
13+
-->
14+
<allowed-os-versions>
15+
<os-version min="10.6.6" />
16+
</allowed-os-versions>
17+
<choices-outline>
18+
<line choice="au.com.openwall.BootUnlock" />
19+
</choices-outline>
20+
<choice id="au.com.openwall.BootUnlock" visible="false">
21+
<pkg-ref id="au.com.openwall.BootUnlock"/>
22+
</choice>
23+
<pkg-ref id="au.com.openwall.BootUnlock">BootUnlock-1.0-dist.pkg</pkg-ref>
24+
</installer-gui-script>

build/build.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
3+
# Name of the package.
4+
NAME="BootUnlock"
5+
6+
# Once installed the identifier is used as the filename for a receipt files in /var/db/receipts/.
7+
IDENTIFIER="au.com.openwall.$NAME"
8+
9+
# Package version number.
10+
VERSION="1.0"
11+
12+
# The location to copy the contents of files.
13+
INSTALL_LOCATION="/Library/PrivilegedHelperTools/$IDENTIFIER"
14+
15+
set -eu -o pipefail
16+
17+
WORK_DIR="${0%/*}"
18+
[ -z "$WORK_DIR" -o "$WORK_DIR" == "$0" ] && WORK_DIR="$(pwd)" ||:
19+
20+
mkdir -p "$WORK_DIR/../out" ||:
21+
22+
# pkgbuild need proper permissions on the source files
23+
chmod 0755 "$WORK_DIR/../files/"*.sh
24+
chmod 0644 "$WORK_DIR/../files/"*.xsl
25+
26+
# Build package.
27+
/usr/bin/pkgbuild \
28+
--identifier "$IDENTIFIER" \
29+
--version "$VERSION" \
30+
--install-location "$INSTALL_LOCATION" \
31+
--root "$WORK_DIR/../files" \
32+
--scripts "$WORK_DIR/../scripts" \
33+
"$WORK_DIR/../out/$NAME-$VERSION-dist.pkg"
34+
35+
/usr/bin/productbuild \
36+
--distribution "$WORK_DIR/Distribution.xml" \
37+
--package-path "$WORK_DIR/../out" \
38+
--resources "$WORK_DIR/../resources" \
39+
"$WORK_DIR/../out/$NAME-$VERSION.pkg"
40+
41+
rm "$WORK_DIR/../out/$NAME-$VERSION-dist.pkg"

files/diskutil.xsl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
2+
<xsl:output method="text"/>
3+
<xsl:template match="/">
4+
<xsl:apply-templates select="/plist/dict[key = 'Containers']/array/dict[key = 'Volumes']/array/dict[key = 'Encryption']"/>
5+
</xsl:template>
6+
<xsl:template match="dict">
7+
<xsl:apply-templates match="key|string|true|false" mode="dict" select="string[preceding-sibling::key[1]/text() = 'Name']"/>
8+
<xsl:text>:</xsl:text>
9+
<xsl:apply-templates match="key|string|true|false" mode="dict" select="string[preceding-sibling::key[1]/text() = 'APFSVolumeUUID']"/>
10+
<xsl:text>:</xsl:text>
11+
<xsl:apply-templates match="key|string|true|false" mode="dict" select="string[preceding-sibling::key[1]/text() = 'DeviceIdentifier']"/>
12+
<xsl:text>:</xsl:text>
13+
<xsl:apply-templates match="true|false" mode="dict" select="true[preceding-sibling::key[1]/text() = 'Encryption']"/>
14+
<xsl:text>:</xsl:text>
15+
<xsl:apply-templates match="true|false" mode="dict" select="true[preceding-sibling::key[1]/text() = 'Locked']"/>
16+
<xsl:text>&#xA;</xsl:text>
17+
</xsl:template>
18+
<xsl:template match="true|false" mode="dict">
19+
<xsl:value-of select="name()"/>
20+
</xsl:template>
21+
</xsl:stylesheet>

files/helper.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
3+
set -eu -o pipefail
4+
5+
PATH=/sbin:/bin:/usr/sbin:/usr/bin
6+
7+
echo "=== $(date) ==="
8+
diskutil apfs list -plist \
9+
| xsltproc --novalid "${0%/*}/diskutil.xsl" - \
10+
| grep -E ':true:true$' \
11+
| cut -f1-3 -d':' \
12+
| while IFS=: read NAME UUID DEVICE ; do
13+
printf 'Trying to unlock volume "%s" with UUID %s ...\n' "$NAME" "$UUID"
14+
if ! PASSPHRASE=$(${0%/*}/BootUnlock find-generic-password \
15+
-D 'Encrypted Volume Password' \
16+
-a "$UUID" -s "$UUID" -w); then
17+
echo 'NOTICE: could not find the secret on the System keychain, skipping the volume.' >&2
18+
continue
19+
fi
20+
if ! printf '%s' "$PASSPHRASE" | diskutil apfs unlock "$DEVICE" -stdinpassphrase ; then
21+
if [ -z "${PASSPHRASE//[[:digit:][a-fA-F]}" ]; then # This may be a hexadecimal string
22+
echo 'NOTICE: the passphrase looks like a hexdecimal string, re-trying ...' >&2
23+
if printf '%s' "$PASSPHRASE" | xxd -r -p | diskutil apfs unlock "$DEVICE" -stdinpassphrase; then
24+
continue
25+
fi
26+
fi
27+
echo "ERROR: could not unlock volume '$NAME', skipping the volume." >&2
28+
continue
29+
fi
30+
done

files/update.sh

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/bin/bash
2+
3+
set -eu -o pipefail
4+
5+
PATH=/sbin:/bin:/usr/sbin:/usr/bin
6+
7+
[ "$(id -un)" = root ] \
8+
|| exec -a "$0" osascript -e "
9+
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" \
10+
|| exit 1
11+
12+
# Location of the log file
13+
LOG_FILE=/var/log/BootUnlock.log
14+
15+
# Determine the cannonical location of this script
16+
WORK_DIR="${BASH_SOURCE%/*}"
17+
[ "$WORK_DIR" != "$BASH_SOURCE" ] || WORK_DIR=.
18+
pushd "$WORK_DIR" &>/dev/null
19+
WORK_DIR=$(pwd -P)
20+
popd &>/dev/null
21+
SELF="$WORK_DIR/${0##*/}"
22+
23+
printf 'Redirecting standard output and errors to "%s" ...\n' "$LOG_FILE"
24+
exec >>"$LOG_FILE" 2>&1
25+
printf '===[ update.sh: %s ]===\n' "$(date)"
26+
27+
# A quick and dirty way of determining the root device :)
28+
ROOT_DEVICE=$(df -l / | grep -E '^/dev/' | cut -f1 -d' ' | head -1 | cut -f3- -d/)
29+
30+
# Get the list of volumes with the encryption enabled
31+
IFS=$'\n' VOLUME=($(diskutil apfs list -plist \
32+
| xsltproc --novalid "${0%/*}/diskutil.xsl" - \
33+
| grep -E ':true:(true)?$' \
34+
| cut -f1-3 -d':' \
35+
))
36+
37+
# Generate a list of volumes for GUI
38+
VOLUME_LIST=
39+
for V in "${VOLUME[@]}" ; do
40+
NAME="${V%%:*}"; V="${V#*:}"
41+
UUID="${V%%:*}"; V="${V#*:}"
42+
DEVICE="${V%%:*}"; V="${V#*:}"
43+
44+
# macOS can automatically mount the system volume, so there is no point
45+
# of presenting that choice to the user
46+
[ "$DEVICE" != "$ROOT_DEVICE" ] || continue
47+
48+
VOLUME_LIST="$VOLUME_LIST${VOLUME_LIST:+, }\"$DEVICE > $NAME\""
49+
done
50+
51+
RESPONSE=$(osascript -e "
52+
set volumeList to { $VOLUME_LIST }
53+
set unlockList to choose from list volumeList with title \"BootUnlock\" with prompt \"
54+
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
55+
")
56+
57+
if [ -z "$RESPONSE" -o "$RESPONSE" = false ]; then
58+
osascript -e "display alert \"BootUnlock\" message \"
59+
You did not select any volumes, so BootUnlock is not going to do anything at the system boot up time.
60+
61+
If you reconsider and will want to enable unlocking of a particular volume you can re-run the '$SELF' script at a later time
62+
\" as critical" &>/dev/null
63+
exit 0
64+
fi
65+
66+
RESPONSE="$(printf '%s' "$RESPONSE" | tr ',' '\n' | sed 's,^[[:space:]]*,,;s,>.*$,,;s,[[:space:]]*$,,')"
67+
68+
# Real work starts here :)
69+
for V in "${VOLUME[@]}" ; do
70+
NAME="${V%%:*}"; V="${V#*:}"
71+
UUID="${V%%:*}"; V="${V#*:}"
72+
DEVICE="${V%%:*}"; V="${V#*:}"
73+
74+
# We are going to work only on the volumes selected by the user
75+
printf '%s' "$RESPONSE" | grep -E "^$DEVICE\$" &>/dev/null || continue
76+
77+
while : ; do # This is a wrapper in case user provides a wrong password
78+
PASSPHRASE=$(osascript -e "
79+
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
80+
")
81+
82+
PASSPHRASE=$(printf '%s' "$PASSPHRASE" | cut -f3- -d:)
83+
84+
if printf '%s' "$PASSPHRASE" | diskutil apfs unlock "$DEVICE" -stdinpassphrase -verify -user "$UUID"; then
85+
printf 'Adding password for volume "%s" with UUID %s to the System keychain...\n' "$NAME" "$UUID"
86+
if sudo /usr/bin/security add-generic-password \
87+
-a "$UUID" -s "$UUID" -l "$NAME" \
88+
-D 'Encrypted Volume Password' \
89+
-T '' -T "$WORK_DIR/BootUnlock" \
90+
-w "$PASSPHRASE" \
91+
-U \
92+
/Library/Keychains/System.keychain; then
93+
break
94+
else
95+
osascript -e "display alert \"BootUnlock\" message \"
96+
BootUnlock experienced an internal error while adding password to the System keychain.
97+
98+
Please check the /var/log/BootUnlock.log log file for more information.
99+
\" as critical" &>/dev/null
100+
exit 1
101+
fi
102+
else
103+
osascript -e "display alert \"BootUnlock\" message \"
104+
The specified password for volume '$NAME' does not seem to be valid. BootUnlock will prompt you for the password again.
105+
106+
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.
107+
\" as critical" &>/dev/null
108+
fi
109+
done
110+
done
111+

resources/background.png

26 KB
Loading

resources/installed.rtf

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf200
2+
{\fonttbl\f0\fswiss\fcharset0 Helvetica-Bold;\f1\fswiss\fcharset0 Helvetica;\f2\fnil\fcharset0 Menlo-Regular;
3+
}
4+
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
5+
{\*\expandedcolortbl;;\csgray\c0;}
6+
\paperw11900\paperh16840\margl1440\margr1440\vieww17240\viewh12860\viewkind0
7+
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qc\partightenfactor0
8+
9+
\f0\b\fs28 \cf0 BootUnlock\
10+
\
11+
\pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\qj\partightenfactor0
12+
13+
\f1\b0 \cf0 It seems that you have successfully installed
14+
\f0\b BootUnlock
15+
\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.\
16+
\
17+
If you decide that BootUnlock is not for you, then you can completely clean it out of your system by removing the
18+
\f2 /Library/PrivilegedHelperTools/au.com.openwall.BootUnlock
19+
\f1 directory and the
20+
\f2 \cf2 \CocoaLigature0 /Library/LaunchDaemons/au.com.openwall.BootUnlock.plist
21+
\f1 file.}

0 commit comments

Comments
 (0)