Closed tmikaeld closed 11 months ago
This is an issue that I couldn't solve yet. We can't just call the brew commands with sudo because it will give this error for a good reason:
Error: Running Homebrew as root is extremely dangerous and no longer supported.
As Homebrew does not drop privileges on installation you would be giving all
build scripts full access to your system.
So we would have to inject the password safely into the running shell somehow.
This works flawlessly for me. Un/-installing Edge asks for authorization, and I can either use Touch ID or my password.
What do you mean by "this"? Calling brew with sudo?
No, when I press install in Applite, it automatically ask me to enter my credentials when I install an app, e.g. Edge that requires sudo.
I think this one works because you bound your sudo to fido/keychain/passkeys. normally it asks for password which it can't get.
You mean like this:
The problem here is that although the app is installed and working, Applite can't open it.
Hmm... I don't really understand this then. Can you explain this further: "bound your sudo to fido/keychain/passkeys"? I don't know that much about this.
@tmikaeld are you referring to this:
/etc/pam.d/sudo
# sudo: auth account password session
auth sufficient pam_tid.so
auth sufficient pam_smartcard.so
auth required pam_opendirectory.so
account required pam_permit.so
password required pam_deny.so
session required pam_permit.so
I added the pam_tid.so
to allow sudo with Touch ID. But if I would not have added this, the it should have symply asked me for the password.
Just checked it. Indeed, it is this line I added auth sufficient pam_tid.so
I just checked it too, I changed it and it works.
@milanvarady I guess you need to write a Privileged Helper Extension.
So we would have to inject the password safely into the running shell somehow.
PINEntry is a passphrase entry dialog from GPGTools which utilizes the Assuan protocol. So if you add a dependency on pinentry-mac
in homebrew/core (depends_on formula: "pinentry-mac"
), then you can set SUDO_ASKPASS
[1] to have brew
use PINEntry to query the user for a password. You just need to call it in the correct way to interact with the Assuan protocol. For example,
SUDO_ASKPASS=Resources/call-pinentry brew upgrade
where Resources/call-pinentry
is the path to:
#! /bin/ksh -p
typeset PATH="/opt/homebrew/bin:/usr/local/bin"
# Minimal permissions are that:
# 1. The calling user is the owner or in the owning group of the file and
# 2. Can read and execute the file.
# sudo chown root:staff <file>; sudo chmod 550 <file>
printf "%s\n" "SETOK OK" "SETCANCEL Cancel" "SETDESC Planned Update" "SETPROMPT Enter Password:" "SETTITLE Software Upgrade via Applite" "GETPIN" | "$(brew --prefix)/bin/pinentry-mac" --no-global-grab | /usr/bin/awk '/^D / {print substr($0, index($0, $2))}'
# NOTE: The printf format string is applied to each argument.
Your call-pinentry script will return the entered text (password). This is just an example. For instance, SETOK, SETCANCEL and SETDESC could all be omitted.
Hope this helps. It might be a challenge to provide localized text in the dialog box that pinentry-mac displays[2], but it should be secure.
[1] https://github.com/Homebrew/brew/blob/3c8b4949baefb1f8166749ff1a3f0665afadedda/docs/Manpage.md?plain=1#L2369-L2370
[2] https://github.com/GPGTools/pinentry/blob/master/doc/HACKING#string-translation
I added the
pam_tid.so
to allow sudo with Touch ID.
Note that /private/etc/pam.d/sudo will be overwritten when macOS receives an update, and it will be necessary to re-edit /private/etc/pam.d/sudo after each such macOS update. Something like this might work.
cat <<\EOF > com.toobuntu.launchd.update_pam.d_sudo.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.toobuntu.launchd.update_pam.d_sudo</string>
<key>ProgramArguments</key>
<array>
<string>/Library/com.toobuntu.launchd/update_pam.d_sudo.ksh</string>
</array>
<key>StandardOutPath</key>
<string>/Library/com.toobuntu.launchd/update_pam.d_sudo_out.log</string>
<key>StandardErrorPath</key>
<string>/Library/com.toobuntu.launchd/update_pam.d_sudo_err.log</string>
<key>WatchPaths</key>
<array>
<string>/private/etc/pam.d/sudo</string>
</array>
</dict>
</plist>
EOF
cat <<\EOF > update_pam.d_sudo.ksh
#! /bin/ksh -p
if /usr/bin/grep --quiet --fixed-strings pam_smartcard.so /private/etc/pam.d/sudo &&
! /usr/bin/grep --quiet --fixed-strings pam_tid.so /private/etc/pam.d/sudo; then
{
/usr/bin/sed -i '.orig' -e '/pam_smartcard.so/ i\
auth sufficient pam_tid.so # Touch ID for sudo
' /private/etc/pam.d/sudo
}
else
printf 1>&2 "%s\n" "Warning: The PAM Smartcard module was not detected or the PAM Touch ID module" "was already configured."
fi
EOF
sudo install -m 0644 -o root com.toobuntu.launchd.update_pam.d_sudo.plist /Library/LaunchDaemons/
sudo install -d /Library/com.toobuntu.launchd/
sudo install -m 0755 -o root -g wheel update_pam.d_sudo.ksh /Library/com.toobuntu.launchd/
sudo launchctl bootstrap 'system' /Library/LaunchDaemons/com.toobuntu.launchd.update_pam.d_sudo.plist
open 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'
# Add /bin/ksh to the list of applications granted Full Disk Access for all users on this Mac.
And if you are on the CLI and use tmux
, then you also need pam_reattach.so
[1].
@toobuntu thanks for your detailed answers, now I understand things better. I have a question. Does using PINEntry
have any advantage, or would a simple script like this work too?
#!/bin/sh
osascript <<EOT
tell application "System Events"
activate
set mypassword to text returned of (display dialog "Please enter your password:" default answer "" with hidden answer)
end tell
mypassword
EOT
Alternatively, I've been looking for a way to prompt the user with the default password prompt that also allows Touch ID, but couldn't find a solution. Do you know of a method that would allow this?
Does using
PINEntry
have any advantage, or would a simple script like this work too?
Yes, a simple AppleScript dialog will work. But, even Apple admits it is not an ideal solution for obtaining passwords[1]:
Protect potentially sensitive information from prying eyes by using the display dialog command’s default answer parameter in conjunction with the hidden answer parameter to show bullets instead of plain text in the dialog’s text field. * Always be cautious when requesting sensitive data, such as passwords. Hidden text is returned by the display dialog command as plain, unencrypted text, so this command offers limited security.
By the way, the AppleScript can be simplified to[2]:
osascript -e 'text returned of (display dialog "Enter password:" default answer "" with hidden answer)' 2> /dev/null
Using pam_tid.so is a nicety when invoking sudo in the terminal and I know which commands sudo wants to run. But it does have limitations. The prompt indicates only that sudo is trying to execute a command as administrator, and nothing about the command which sudo is trying to run[3]. There are also people who refuse to enable Touch ID, though pam_tid.so at least falls back to asking the user to input a password in this case.
Another consideration is NIST recommendations to allow unmasking (bottom of page 14). AppleScript does not. PINEntry does.
Enter pinentry[4]:
pinentry is a small collection of dialog programs that allow GnuPG to read passphrases and PIN numbers in a secure manner.
In browsing the source[5], it seems to try to minimize the chance that the passphrase is saved in memory and that memory is exposed to an attacker by initializing the secure memory subsystem and dropping privileges. Debian has manpages for the various pinentries, which sum it up (I have replaced the toolkit with an asterisk because the remainder of the paragraph is identical regardless of whether it is pinentry-tty or pinentry-gnome3, etc.):
pinentry-* is a program that allows for secure entry of PINs or pass phrases. That means it tries to take care that the entered information is not swapped to disk or temporarily stored anywhere. This functionality is particularly useful for entering pass phrases when using encryption software such as GnuPG or e-mail clients using the same. It uses an open protocol and is therefore not tied to particular software.
Similar to pinentry from GnuPG, ssh-askpass is an X11-based passphrase dialog for use with OpenSSH. But it is not relevant in macOS since Apple deprecated the askpass UI and provided an option to integrate ssh passphrases into the Keychain. However, the brew cask for an unofficial (that is, not provided by OpenSSH and available only in a private tap instead of in homebrew/cask) macOS variant of ssh-askpass is just an AppleScript dialog. There is also ssh-askpass-mac written in Swift, which stores the passphrase in the macOS Keychain. And so it is really better suited for ssh which has many passphrases instead of sudo which requires the login passphrase. It also has this security caveat:
The passhprase is temporarily stored in the memory area of the ssh-askpass-mac app and with the Swift programming language it is not possible to ensure that the memory area is overwritten.
Alternatively, I've been looking for a way to prompt the user with the default password prompt that also allows Touch ID, but couldn't find a solution. Do you know of a method that would allow this?
I do not. The problem here is that brew
is a command line program. It passes SUDO_ASKPASS
when sudo
is needed by a cask's install script. How would one use Touch ID with GUI password entry fallback, like pam_tid.so
or DeviceOwnerAuthentication
, and instead of responding "authenticated" actually return a password to sudo
on a command line, and as invoked by brew
? I don't know whether that's possible in Swift (or any other way of accessing Touch ID).
I would be remiss if I failed to mention sudo-touchid for the concept. It hasn't been updated in seven years. It is a fork of sudo which has Touch ID built in. You could theoretically do the same, with a current sudo, and tell brew to use that instead of the system sudo. But brew hardcodes the path to sudo so that wouldn't work anyway. It seems heavy-handed to forcibly edit /private/etc/pam.d/sudo to add Touch ID, and maintain that configuration, especially if the user doesn't even want Touch ID enabled or wasn't asked.
It occurred to me that, on every brew upgrade
, you could grep
/private/etc/pam.d/sudo to see whether it contains pam_tid.so in an uncommented line. If so, don't set SUDO_ASKPASS. If not, set SUDO_ASKPASS. That way, your power users who already configured sudo with Touch ID can keep it and the others will get a GUI dialog.
[1] https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/PromptforText.html#//apple_ref/doc/uid/TP40016239-CH80-SW1 [2] https://scriptingosx.com/2020/09/avoiding-applescript-security-and-privacy-requests/, Section: Don’t send to other Processes [3] https://github.com/Homebrew/homebrew-autoupdate/issues/40#issuecomment-949786673 [4] https://gnupg.org/related_software/pinentry/index.html [5] https://git.gnupg.org/cgi-bin/gitweb.cgi?p=pinentry.git;a=tree;h=refs/heads/master;hb=refs/heads/master
It came to my attention that Homebrew has multiple packages that provide pinentry-mac: The most popular seems to be the formula pinentry-mac
. There are also the GPGTools casks, specifically gpg-suite-pinentry
. The paths to pinentry-mac are different for each. The formula installs to "$(brew --prefix)/bin/pinentry-mac"
, while the cask installs to /usr/local/MacGPG2/libexec/pinentry-mac.app/Contents/MacOS/pinentry-mac
(because GPGTools / MacGPG2 always installs into /usr/local). The actual executable, at this time, is basically the same no matter if installed via the formula or cask; they are both currently at version 1.1.1.1. The formula installs a binary for the CPU architecture (Intel or ARM) and the cask installs a universal binary (for both), but otherwise they are the same.
Thanks to the help of @toobuntu, I finally implemented this feature. The solution was to use the pinentry-mac package and pass it to brew with the SUDO_ASKPASS
parameter.
The app installs pinentry during the setup, and also checks every time the app is opened or a .pkg app is downloaded, and fixes the installation if needed. Pinentry is called from a script called pinentry.ksh
, and to add an extra layer of security, the file is checked against an md5 hash to make sure it hasn't been modified.
See changes in this commit: ba987547c7ea7637ca60f1942614404fc0f32dcc. This feature will be released in the next update soon.
The feature is now live in the v1.2 update.
For example, when installing microsoft-edge cask