Support for reading passwords from a specified fd

This initial work adds the -pfd[N] flag to the 7z command so that an
alternate file descriptor (fd) may be specified for reading the password
instead of standard input (stdin).

By adding this flag it becomes possible for 7z to accept data from stdin
for use with the -si flag while also being being able to decrypt a
password protected achieve without revealing the password on the
command line.

For example, generating a secret key and storing it in an encrypted archive
without the need to expose any of the data to a filesystem:

    age-keygen | 7z a -pfd9 9< <(pass show archive) -siid.age archive.7z

As a side effect the password is not echoed to the terminal, however
this PR should not conflict with the work in #33.

Note that the -p flag is necessary if the archive does not exist but
should not be used if it does.
This commit is contained in:
Earnestly 2026-02-07 22:15:56 +00:00
parent 5e96a82794
commit 2c37f6565d
10 changed files with 67 additions and 2 deletions

View file

@ -120,6 +120,15 @@ static bool StringToUInt32(const wchar_t *s, UInt32 &v)
return *end == 0;
}
static bool StringToInt32(const wchar_t *s, Int32 &v)
{
if (*s == 0)
return false;
const wchar_t *end;
v = ConvertStringToInt32(s, &end);
return *end == 0;
}
namespace NKey {
enum Enum
@ -209,6 +218,7 @@ enum Enum
#ifndef Z7_NO_CRYPTO
, kPassword
, kPasswordFd
#endif
};
@ -360,6 +370,7 @@ static const CSwitchForm kSwitchForms[] =
#ifndef Z7_NO_CRYPTO
, { "p", SWFRM_STRING }
, { "pfd", SWFRM_STRING }
#endif
};
@ -1460,6 +1471,22 @@ void CArcCmdLineParser::Parse2(CArcCmdLineOptions &options)
options.PasswordEnabled = parser[NKey::kPassword].ThereIs;
if (options.PasswordEnabled)
options.Password = parser[NKey::kPassword].PostStrings[0];
options.PasswordFd = 0;
if (parser[NKey::kPasswordFd].ThereIs)
{
const UString &s = parser[NKey::kPasswordFd].PostStrings[0];
if (s.IsEmpty())
throw CArcCmdLineException("No file descriptor given to -pfd", s);
else
{
Int32 v;
if (!StringToInt32(s, v))
throw CArcCmdLineException("A file descriptor is required for -pfd", s);
options.PasswordFd = (int)v;
}
}
#endif
options.ShowDialog = parser[NKey::kShowDialog].ThereIs;

View file

@ -89,6 +89,7 @@ struct CArcCmdLineOptions
#ifndef Z7_NO_CRYPTO
bool PasswordEnabled;
UString Password;
int PasswordFd;
#endif
UStringVector HashMethods;

View file

@ -1081,7 +1081,7 @@ HRESULT ListArchives(
const NWildcard::CCensorNode &wildcardCensor,
bool enableHeaders, bool techMode,
#ifndef Z7_NO_CRYPTO
bool &passwordEnabled, UString &password,
bool &passwordEnabled, UString &password, int &passwordFd,
#endif
#ifndef Z7_SFX
const CObjectVector<CProperty> *props,
@ -1161,6 +1161,7 @@ HRESULT ListArchives(
openCallback.PasswordIsDefined = passwordEnabled;
openCallback.Password = password;
openCallback.PasswordFd = passwordFd;
#endif

View file

@ -31,7 +31,7 @@ HRESULT ListArchives(
const NWildcard::CCensorNode &wildcardCensor,
bool enableHeaders, bool techMode,
#ifndef Z7_NO_CRYPTO
bool &passwordEnabled, UString &password,
bool &passwordEnabled, UString &password, int &passwordFd,
#endif
#ifndef Z7_SFX
const CObjectVector<CProperty> *props,

View file

@ -163,6 +163,7 @@ static const char * const kHelpString =
" -o{Directory} : set Output directory\n"
#ifndef Z7_NO_CRYPTO
" -p{Password} : set Password\n"
" -pfd{N} : read Password from fd\n"
#endif
" -r[-|0] : Recurse subdirectories for name search\n"
" -sa{a|e|s} : set Archive name mode\n"
@ -1333,6 +1334,7 @@ int Main2(
#ifndef Z7_NO_CRYPTO
ecs->PasswordIsDefined = options.PasswordEnabled;
ecs->Password = options.Password;
ecs->PasswordFd = options.PasswordFd;
#endif
ecs->Init(g_StdStream, g_ErrStream, percentsStream, options.DisablePercents);
@ -1517,6 +1519,7 @@ int Main2(
#ifndef Z7_NO_CRYPTO
options.PasswordEnabled,
options.Password,
options.PasswordFd,
#endif
&options.Properties,
numErrors, numWarnings);
@ -1551,6 +1554,7 @@ int Main2(
(options.PasswordEnabled && !options.Password.IsEmpty());
openCallback.PasswordIsDefined = passwordIsDefined;
openCallback.Password = options.Password;
openCallback.PasswordFd = options.PasswordFd;
#endif
CUpdateCallbackConsole callback;
@ -1564,6 +1568,7 @@ int Main2(
callback.PasswordIsDefined = passwordIsDefined;
callback.AskPassword = (options.PasswordEnabled && options.Password.IsEmpty());
callback.Password = options.Password;
callback.PasswordFd = options.PasswordFd;
#endif
callback.StdOutMode = uo.StdOutMode;

View file

@ -87,6 +87,15 @@ HRESULT COpenCallbackConsole::Open_CryptoGetTextPassword(BSTR *password)
if (!PasswordIsDefined)
{
ClosePercents();
if (PasswordFd) {
FILE *_file = fdopen(PasswordFd, "r");
if (!_file)
return S_FALSE;
g_StdIn = CStdInStream(_file);
}
RINOK(GetPassword_HRESULT(_so, Password))
PasswordIsDefined = true;
}

View file

@ -67,6 +67,7 @@ public:
bool PasswordIsDefined;
// bool PasswordWasAsked;
UString Password;
int PasswordFd;
#endif
};

View file

@ -828,6 +828,16 @@ HRESULT CUpdateCallbackConsole::CryptoGetTextPassword2(Int32 *passwordIsDefined,
if (!PasswordIsDefined)
{
if (PasswordFd) {
FILE *_file = fdopen(PasswordFd, "r");
if (!_file)
return S_FALSE;
g_StdIn = CStdInStream(_file);
}
if (AskPassword)
{
RINOK(GetPassword_HRESULT(_so, Password))
@ -857,6 +867,15 @@ HRESULT CUpdateCallbackConsole::CryptoGetTextPassword(BSTR *password)
if (!PasswordIsDefined)
{
{
if (PasswordFd) {
FILE *_file = fdopen(PasswordFd, "r");
if (!_file)
return S_FALSE;
g_StdIn = CStdInStream(_file);
}
RINOK(GetPassword_HRESULT(_so, Password))
PasswordIsDefined = true;
}

View file

@ -122,6 +122,7 @@ public:
bool PasswordIsDefined;
bool AskPassword;
UString Password;
int PasswordFd;
#endif
CUpdateCallbackConsole():

View file

@ -3,6 +3,7 @@
#ifndef ZIP7_INC_USER_INPUT_UTILS_H
#define ZIP7_INC_USER_INPUT_UTILS_H
#include "../../../Common/StdInStream.h"
#include "../../../Common/StdOutStream.h"
namespace NUserAnswerMode {