Skip to content

Commit

Permalink
Bug 1873: Support for OpenSSH certificates for user authentication
Browse files Browse the repository at this point in the history
https://winscp.net/tracker/1873

Source commit: fc13a226065092a102456a27242ac69151cb1d15
  • Loading branch information
martinprikryl committed Dec 14, 2022
1 parent 89d744f commit 48106ee
Show file tree
Hide file tree
Showing 20 changed files with 140 additions and 34 deletions.
17 changes: 12 additions & 5 deletions source/core/PuttyIntf.cpp
Expand Up @@ -259,8 +259,12 @@ static void connection_fatal(Seat * seat, const char * message)
SeatPromptResult confirm_ssh_host_key(Seat * seat, const char * host, int port, const char * keytype,
char * keystr, SeatDialogText *, HelpCtx,
void (*DebugUsedArg(callback))(void *ctx, SeatPromptResult result), void * DebugUsedArg(ctx),
char **key_fingerprints)
char **key_fingerprints, bool is_certificate)
{
if (DebugAlwaysFalse(is_certificate))
{
NotImplemented();
}
UnicodeString FingerprintSHA256, FingerprintMD5;
if (key_fingerprints[SSH_FPTYPE_SHA256] != NULL)
{
Expand Down Expand Up @@ -863,7 +867,8 @@ void FreeKey(TPrivateKey * PrivateKey)
sfree(Ssh2Key);
}
//---------------------------------------------------------------------------
RawByteString LoadPublicKey(const UnicodeString & FileName, UnicodeString & Algorithm, UnicodeString & Comment)
RawByteString LoadPublicKey(
const UnicodeString & FileName, UnicodeString & Algorithm, UnicodeString & Comment, bool & HasCertificate)
{
RawByteString Result;
UTF8String UtfFileName = UTF8String(FileName);
Expand All @@ -880,6 +885,8 @@ RawByteString LoadPublicKey(const UnicodeString & FileName, UnicodeString & Algo
throw Exception(Error);
}
Algorithm = UnicodeString(AnsiString(AlgorithmStr));
const ssh_keyalg * KeyAlg = find_pubkey_alg(AlgorithmStr);
HasCertificate = (KeyAlg != NULL) && KeyAlg->is_certificate;
sfree(AlgorithmStr);
Comment = UnicodeString(AnsiString(CommentStr));
sfree(CommentStr);
Expand All @@ -893,10 +900,10 @@ RawByteString LoadPublicKey(const UnicodeString & FileName, UnicodeString & Algo
return Result;
}
//---------------------------------------------------------------------------
UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment)
UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment, bool & HasCertificate)
{
UnicodeString Algorithm;
RawByteString PublicKey = LoadPublicKey(FileName, Algorithm, Comment);
RawByteString PublicKey = LoadPublicKey(FileName, Algorithm, Comment, HasCertificate);
UnicodeString PublicKeyBase64 = EncodeBase64(PublicKey.c_str(), PublicKey.Length());
PublicKeyBase64 = ReplaceStr(PublicKeyBase64, L"\r", L"");
PublicKeyBase64 = ReplaceStr(PublicKeyBase64, L"\n", L"");
Expand Down Expand Up @@ -1043,7 +1050,7 @@ UnicodeString __fastcall ParseOpenSshPubLine(const UnicodeString & Line, const s
{
try
{
Algorithm = find_pubkey_alg(AlgorithmName);
Algorithm = find_pubkey_alg_winscp_host(AlgorithmName);
if (Algorithm == NULL)
{
throw Exception(FORMAT(L"Unknown public key algorithm \"%s\".", (AlgorithmName)));
Expand Down
2 changes: 1 addition & 1 deletion source/core/PuttyTools.h
Expand Up @@ -20,7 +20,7 @@ void ChangeKeyComment(TPrivateKey * PrivateKey, const UnicodeString & Comment);
void SaveKey(TKeyType KeyType, const UnicodeString & FileName,
const UnicodeString & Passphrase, TPrivateKey * PrivateKey);
void FreeKey(TPrivateKey * PrivateKey);
UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment);
UnicodeString GetPublicKeyLine(const UnicodeString & FileName, UnicodeString & Comment, bool & HasCertificate);
extern const UnicodeString PuttyKeyExt;
//---------------------------------------------------------------------------
bool __fastcall HasGSSAPI(UnicodeString CustomPath);
Expand Down
10 changes: 7 additions & 3 deletions source/core/SecureShell.cpp
Expand Up @@ -242,9 +242,13 @@ Conf * __fastcall TSecureShell::StoreToConfig(TSessionData * Data, bool Simple)
conf_set_filename(conf, CONF_ssh_gss_custom, GssLibCustomFileName);
filename_free(GssLibCustomFileName);

Filename * KeyFileFileName = filename_from_str(UTF8String(Data->ResolvePublicKeyFile()).c_str());
conf_set_filename(conf, CONF_keyfile, KeyFileFileName);
filename_free(KeyFileFileName);
Filename * AFileName = filename_from_str(UTF8String(Data->ResolvePublicKeyFile()).c_str());
conf_set_filename(conf, CONF_keyfile, AFileName);
filename_free(AFileName);

AFileName = filename_from_str(UTF8String(ExpandEnvironmentVariables(Data->DetachedCertificate)).c_str());
conf_set_filename(conf, CONF_detached_cert, AFileName);
filename_free(AFileName);

conf_set_bool(conf, CONF_ssh2_des_cbc, Data->Ssh2DES);
conf_set_bool(conf, CONF_ssh_no_userauth, Data->SshNoUserAuth);
Expand Down
21 changes: 17 additions & 4 deletions source/core/SessionData.cpp
Expand Up @@ -191,7 +191,8 @@ void __fastcall TSessionData::DefaultSettings()
GssLib[Index] = DefaultGssLibList[Index];
}
GssLibCustom = L"";
PublicKeyFile = L"";
PublicKeyFile = EmptyStr;
DetachedCertificate = EmptyStr;
Passphrase = L"";
FPuttyProtocol = L"";
TcpNoDelay = false;
Expand Down Expand Up @@ -356,6 +357,7 @@ void __fastcall TSessionData::NonPersistant()
PROPERTY(UserName); \
PROPERTY_HANDLER(Password, F); \
PROPERTY(PublicKeyFile); \
PROPERTY(DetachedCertificate); \
PROPERTY_HANDLER(Passphrase, F); \
PROPERTY(FSProtocol); \
PROPERTY(Ftps); \
Expand Down Expand Up @@ -727,6 +729,7 @@ void __fastcall TSessionData::DoLoad(THierarchicalStorage * Storage, bool PuttyI
}
GssLibCustom = Storage->ReadString(L"GSSCustom", GssLibCustom);
PublicKeyFile = Storage->ReadString(L"PublicKeyFile", PublicKeyFile);
DetachedCertificate = Storage->ReadString(L"DetachedCertificate", DetachedCertificate);
AddressFamily = static_cast<TAddressFamily>
(Storage->ReadInteger(L"AddressFamily", AddressFamily));
RekeyData = Storage->ReadString(L"RekeyBytes", RekeyData);
Expand Down Expand Up @@ -1046,11 +1049,13 @@ void __fastcall TSessionData::DoSave(THierarchicalStorage * Storage,
// PuTTY is started in its binary directory to allow relative paths when opening PuTTY's own stored session.
// To allow relative paths in our sessions, we have to expand them for PuTTY.
WRITE_DATA_EX(StringRaw, L"PublicKeyFile", PublicKeyFile, ExpandFileName);
WRITE_DATA_EX(StringRaw, L"DetachedCertificate", DetachedCertificate, ExpandFileName);
}
else
{
WRITE_DATA(String, UserName);
WRITE_DATA(String, PublicKeyFile);
WRITE_DATA(String, DetachedCertificate);
WRITE_DATA(Integer, FSProtocol);
WRITE_DATA(String, LocalDirectory);
WRITE_DATA(String, OtherLocalDirectory);
Expand Down Expand Up @@ -2553,6 +2558,7 @@ TSessionData * TSessionData::CreateTunnelData(int TunnelLocalPortNumber)
TunnelData->UserName = TunnelUserName;
TunnelData->Password = TunnelPassword;
TunnelData->PublicKeyFile = TunnelPublicKeyFile;
TunnelData->DetachedCertificate = EmptyStr;
TunnelData->Passphrase = TunnelPassphrase;
UnicodeString AHostName = HostNameExpanded;
if (IsIPv6Literal(AHostName))
Expand Down Expand Up @@ -2599,6 +2605,7 @@ void __fastcall TSessionData::ExpandEnvironmentVariables()
HostName = HostNameExpanded;
UserName = UserNameExpanded;
PublicKeyFile = ::ExpandEnvironmentVariables(PublicKeyFile);
DetachedCertificate = ::ExpandEnvironmentVariables(DetachedCertificate);
}
//---------------------------------------------------------------------
void __fastcall TSessionData::ValidatePath(const UnicodeString Path)
Expand Down Expand Up @@ -3148,6 +3155,11 @@ void __fastcall TSessionData::SetPublicKeyFile(UnicodeString value)
}
}
//---------------------------------------------------------------------
void __fastcall TSessionData::SetDetachedCertificate(UnicodeString value)
{
SET_SESSION_PROPERTY(DetachedCertificate);
}
//---------------------------------------------------------------------
UnicodeString TSessionData::ResolvePublicKeyFile()
{
UnicodeString Result = PublicKeyFile;
Expand Down Expand Up @@ -4599,9 +4611,10 @@ void __fastcall TSessionData::DisableAuthentationsExceptPassword()
AuthKIPassword = false;
AuthGSSAPI = false;
AuthGSSAPIKEX = false;
PublicKeyFile = L"";
TlsCertificateFile = L"";
Passphrase = L"";
PublicKeyFile = EmptyStr;
DetachedCertificate = EmptyStr;
TlsCertificateFile = EmptyStr;
Passphrase = EmptyStr;
TryAgent = false;
}
//---------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions source/core/SessionData.h
Expand Up @@ -134,6 +134,7 @@ friend class TStoredSessionList;
bool FVMSAllRevisions;
UnicodeString FPublicKeyFile;
UnicodeString FPassphrase;
UnicodeString FDetachedCertificate;
UnicodeString FPuttyProtocol;
TFSProtocol FFSProtocol;
bool FModified;
Expand Down Expand Up @@ -285,6 +286,7 @@ friend class TStoredSessionList;
void __fastcall SetPublicKeyFile(UnicodeString value);
UnicodeString __fastcall GetPassphrase() const;
void __fastcall SetPassphrase(UnicodeString value);
void __fastcall SetDetachedCertificate(UnicodeString value);

void __fastcall SetPuttyProtocol(UnicodeString value);
bool __fastcall GetCanLogin();
Expand Down Expand Up @@ -580,6 +582,7 @@ friend class TStoredSessionList;
__property UnicodeString GssLibCustom = { read=FGssLibCustom, write=SetGssLibCustom };
__property UnicodeString PublicKeyFile = { read=FPublicKeyFile, write=SetPublicKeyFile };
__property UnicodeString Passphrase = { read=GetPassphrase, write=SetPassphrase };
__property UnicodeString DetachedCertificate = { read=FDetachedCertificate, write=SetDetachedCertificate };
__property UnicodeString PuttyProtocol = { read=FPuttyProtocol, write=SetPuttyProtocol };
__property TFSProtocol FSProtocol = { read=FFSProtocol, write=SetFSProtocol };
__property UnicodeString FSProtocolStr = { read=GetFSProtocolStr };
Expand Down
3 changes: 2 additions & 1 deletion source/core/Terminal.cpp
Expand Up @@ -8423,7 +8423,8 @@ UnicodeString TTerminal::UploadPublicKey(const UnicodeString & FileName)
Log->AddSeparator();

UnicodeString Comment;
UnicodeString Line = GetPublicKeyLine(FileName, Comment);
bool UnusedHasCertificate;
UnicodeString Line = GetPublicKeyLine(FileName, Comment, UnusedHasCertificate);

LogEvent(FORMAT(L"Adding public key line to \"%s\" file:\n%s", (AuthorizedKeysFilePath, Line)));

Expand Down
32 changes: 30 additions & 2 deletions source/forms/SiteAdvanced.cpp
Expand Up @@ -51,6 +51,7 @@ __fastcall TSiteAdvancedDialog::TSiteAdvancedDialog(TComponent * AOwner) :
TForm(AOwner)
{
NoUpdate = 0;
FKeyHasCertificate = false;

// we need to make sure that window procedure is set asap
// (so that CM_SHOWINGCHANGED handling is applied)
Expand Down Expand Up @@ -248,6 +249,7 @@ void __fastcall TSiteAdvancedDialog::LoadSession()
GSSAPIFwdTGTCheck->Checked = FSessionData->GSSAPIFwdTGT;
AgentFwdCheck->Checked = FSessionData->AgentFwd;
PrivateKeyEdit3->Text = FSessionData->PublicKeyFile;
DetachedCertificateEdit->Text = FSessionData->DetachedCertificate;

// SSH page
Ssh2LegacyDESCheck->Checked = FSessionData->Ssh2DES;
Expand Down Expand Up @@ -474,6 +476,9 @@ void __fastcall TSiteAdvancedDialog::SaveSession(TSessionData * SessionData)
SessionData->GSSAPIFwdTGT = GSSAPIFwdTGTCheck->Checked;
SessionData->AgentFwd = AgentFwdCheck->Checked;
SessionData->PublicKeyFile = PrivateKeyEdit3->Text;
// When user selectes key with certificate, s/he cannot clear the certificate box anymore as it is disabled,
// so let's not save it, in case it causes troubles in PuTTY code.
SessionData->DetachedCertificate = !FKeyHasCertificate ? DetachedCertificateEdit->Text : EmptyStr;

// Connection page
SessionData->FtpPasvMode = FtpPasvModeCheck->Checked;
Expand Down Expand Up @@ -855,7 +860,25 @@ void __fastcall TSiteAdvancedDialog::UpdateControls()
AuthenticationGroup->Enabled && (AuthKICheck->Enabled && AuthKICheck->Checked));
EnableControl(AuthenticationParamsGroup, AuthenticationGroup->Enabled);
EnableControl(AgentFwdCheck, AuthenticationParamsGroup->Enabled && TryAgentCheck->Checked);
if (PrivateKeyEdit3->Text != FLastPrivateKey)
{
FLastPrivateKey = PrivateKeyEdit3->Text;
FKeyHasCertificate = false;
if (PrivateKeyEdit3->Enabled && !FLastPrivateKey.IsEmpty())
{
try
{
UnicodeString UnusedComment;
GetPublicKeyLine(FLastPrivateKey, UnusedComment, FKeyHasCertificate);
}
catch (...)
{
}
}
}
EnableControl(PrivateKeyViewButton, PrivateKeyEdit3->Enabled && !PrivateKeyEdit3->Text.IsEmpty());
EnableControl(DetachedCertificateEdit, PrivateKeyViewButton->Enabled && !FKeyHasCertificate);
EnableControl(DetachedCertificateLabel, DetachedCertificateEdit->Enabled);
EnableControl(AuthGSSAPICheck3, AuthenticationGroup->Enabled);
EnableControl(GSSAPIFwdTGTCheck,
AuthGSSAPICheck3->Enabled && AuthGSSAPICheck3->Checked);
Expand Down Expand Up @@ -1336,6 +1359,10 @@ void __fastcall TSiteAdvancedDialog::FormCloseQuery(TObject * /*Sender*/,
void __fastcall TSiteAdvancedDialog::PathEditBeforeDialog(TObject * /*Sender*/,
UnicodeString & Name, bool & /*Action*/)
{
// Reset key stats, even if new key is not select, this way the clicking browse button gives the user chance to
// reload the "certificate" state in case the key file is recreated with certificate
FLastPrivateKey = EmptyStr;

FBeforeDialogPath = Name;
Name = ExpandEnvironmentVariables(Name);
}
Expand Down Expand Up @@ -1609,7 +1636,8 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyViewButtonClick(TObject * /*Sende
VerifyAndConvertKey(FileName, false);
PrivateKeyEdit3->Text = FileName;
UnicodeString CommentDummy;
UnicodeString Line = GetPublicKeyLine(FileName, CommentDummy);
bool HasCertificate;
UnicodeString Line = GetPublicKeyLine(FileName, CommentDummy, HasCertificate);
std::unique_ptr<TStrings> Messages(TextToStringList(Line));

TClipboardHandler ClipboardHandler;
Expand All @@ -1623,7 +1651,7 @@ void __fastcall TSiteAdvancedDialog::PrivateKeyViewButtonClick(TObject * /*Sende
Params.Aliases = Aliases;
Params.AliasesCount = LENOF(Aliases);

UnicodeString Message = LoadStr(LOGIN_AUTHORIZED_KEYS);
UnicodeString Message = LoadStr(HasCertificate ? LOGIN_KEY_WITH_CERTIFICATE : LOGIN_AUTHORIZED_KEYS);
int Answers = qaOK | qaRetry;
MoreMessageDialog(Message, Messages.get(), qtInformation, Answers, HELP_LOGIN_AUTHORIZED_KEYS, &Params);
}
Expand Down
31 changes: 28 additions & 3 deletions source/forms/SiteAdvanced.dfm
Expand Up @@ -2288,13 +2288,13 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
Left = 0
Top = 132
Width = 393
Height = 120
Height = 164
Anchors = [akLeft, akTop, akRight]
Caption = 'Authentication parameters'
TabOrder = 2
DesignSize = (
393
120)
164)
object PrivateKeyLabel: TLabel
Left = 12
Top = 42
Expand All @@ -2303,6 +2303,14 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
Caption = 'Private &key file:'
FocusControl = PrivateKeyEdit3
end
object DetachedCertificateLabel: TLabel
Left = 12
Top = 117
Width = 186
Height = 13
Caption = 'Certificate to use with the private key:'
FocusControl = DetachedCertificateEdit
end
object AgentFwdCheck: TCheckBox
Left = 12
Top = 19
Expand Down Expand Up @@ -2350,10 +2358,27 @@ object SiteAdvancedDialog: TSiteAdvancedDialog
TabOrder = 2
OnClick = PrivateKeyViewButtonClick
end
object DetachedCertificateEdit: TFilenameEdit
Left = 12
Top = 133
Width = 372
Height = 21
AcceptFiles = True
OnBeforeDialog = PathEditBeforeDialog
OnAfterDialog = PrivateKeyEdit3AfterDialog
Filter = 'Public key files (*.pub)|*.pub|All Files (*.*)|*.*'
DialogOptions = [ofReadOnly, ofPathMustExist, ofFileMustExist]
DialogTitle = 'Select certificate file'
ClickKey = 16397
Anchors = [akLeft, akTop, akRight]
TabOrder = 4
Text = 'DetachedCertificateEdit'
OnChange = DataChange
end
end
object GSSAPIGroup: TGroupBox
Left = 0
Top = 258
Top = 302
Width = 393
Height = 71
Anchors = [akLeft, akTop, akRight]
Expand Down
4 changes: 4 additions & 0 deletions source/forms/SiteAdvanced.h
Expand Up @@ -281,6 +281,8 @@ class TSiteAdvancedDialog : public TForm
TCheckBox *VMSAllRevisionsCheck;
TLabel *Label5;
TComboBox *SFTPRealPathCombo;
TLabel *DetachedCertificateLabel;
TFilenameEdit *DetachedCertificateEdit;
void __fastcall DataChange(TObject *Sender);
void __fastcall FormShow(TObject *Sender);
void __fastcall PageControlChange(TObject *Sender);
Expand Down Expand Up @@ -346,6 +348,8 @@ class TSiteAdvancedDialog : public TForm
TFSProtocol FFSProtocol;
TSessionData * FSessionData;
TColor FColor;
UnicodeString FLastPrivateKey;
bool FKeyHasCertificate;
std::unique_ptr<TPopupMenu> FColorPopupMenu;
std::unique_ptr<TObjectList> FPrivateKeyMonitors;
std::unique_ptr<TStrings> FPuttyRegSettings;
Expand Down
3 changes: 1 addition & 2 deletions source/putty/crypto/openssh-certs.c
@@ -1,4 +1,3 @@
#ifndef WINSCP
/*
* Public key type for OpenSSH certificates.
*/
Expand Down Expand Up @@ -725,6 +724,7 @@ static key_components *opensshcert_components(ssh_key *key)
static SeatDialogText *opensshcert_cert_info(ssh_key *key)
{
#ifdef WINSCP
assert(false);
return NULL;
#else
opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
Expand Down Expand Up @@ -1223,4 +1223,3 @@ static void opensshcert_sign(ssh_key *key, ptrlen data, unsigned flags,
opensshcert_key *ck = container_of(key, opensshcert_key, sshk);
ssh_key_sign(ck->basekey, data, flags, bs);
}
#endif

0 comments on commit 48106ee

Please sign in to comment.