7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-18 19:59:18 +00:00

Handle SSH keys better

OpenSSH will iterate through a series of keys.  Additionally, there may
be a key specified in the ssh/config file that we need to account for.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18864
This commit is contained in:
Seth Hillbrand 2025-01-21 15:57:45 -08:00
parent 4fe05bfe47
commit 5a57e5cfd4
8 changed files with 206 additions and 47 deletions

View File

@ -63,6 +63,7 @@ bool GIT_CLONE_HANDLER::PerformClone()
cloneOptions.fetch_opts.callbacks.payload = this;
m_testedTypes = 0;
ResetNextKey();
if( git_clone( &m_repo, m_URL.ToStdString().c_str(), m_clonePath.ToStdString().c_str(),
&cloneOptions ) != 0 )

View File

@ -29,7 +29,7 @@
#include <iostream>
#include <time.h>
GIT_PULL_HANDLER::GIT_PULL_HANDLER( git_repository* aRepo ) : KIGIT_COMMON( aRepo )
GIT_PULL_HANDLER::GIT_PULL_HANDLER( KIGIT_COMMON* aRepo ) : KIGIT_COMMON( *aRepo )
{}
@ -55,6 +55,9 @@ bool GIT_PULL_HANDLER::PerformFetch()
remoteCallbacks.credentials = credentials_cb;
remoteCallbacks.payload = this;
m_testedTypes = 0;
ResetNextKey();
if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) )
{
git_remote_free( remote );

View File

@ -42,14 +42,14 @@ struct CommitDetails
std::string m_date;
};
// Enum for result codes
enum class PullResult
// Enum for result codes, error codes are negative, success codes are positive
enum class PullResult : int
{
Success,
Error,
MergeFailed = -2,
Error = -1,
Success = 0,
UpToDate,
FastForward,
MergeFailed
FastForward
};
struct ConflictData
@ -68,7 +68,7 @@ struct ConflictData
class GIT_PULL_HANDLER : public KIGIT_COMMON, public GIT_PROGRESS
{
public:
GIT_PULL_HANDLER( git_repository* aRepo );
GIT_PULL_HANDLER( KIGIT_COMMON* aCommon );
~GIT_PULL_HANDLER();
PullResult PerformPull();

View File

@ -26,7 +26,7 @@
#include <iostream>
GIT_PUSH_HANDLER::GIT_PUSH_HANDLER( git_repository* aRepo ) : KIGIT_COMMON( aRepo )
GIT_PUSH_HANDLER::GIT_PUSH_HANDLER( KIGIT_COMMON* aRepo ) : KIGIT_COMMON( *aRepo )
{}

View File

@ -43,7 +43,7 @@ enum class PushResult
class GIT_PUSH_HANDLER : public KIGIT_COMMON, public GIT_PROGRESS
{
public:
GIT_PUSH_HANDLER( git_repository* aRepo );
GIT_PUSH_HANDLER( KIGIT_COMMON* aRepo );
~GIT_PUSH_HANDLER();
PushResult PerformPush();

View File

@ -27,6 +27,8 @@
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/textfile.h>
#include <wx/utils.h>
#include <map>
#include <vector>
@ -416,6 +418,90 @@ wxString KIGIT_COMMON::GetRemotename() const
return retval;
}
void KIGIT_COMMON::SetSSHKey( const wxString& aKey )
{
auto it = std::find( m_publicKeys.begin(), m_publicKeys.end(), aKey );
if( it != m_publicKeys.end() )
m_publicKeys.erase( it );
m_publicKeys.insert( m_publicKeys.begin(), aKey );
}
void KIGIT_COMMON::updatePublicKeys()
{
m_publicKeys.clear();
wxFileName keyFile( wxGetHomeDir(), wxEmptyString );
keyFile.AppendDir( ".ssh" );
keyFile.SetFullName( "id_rsa" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
keyFile.SetFullName( "id_dsa" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
keyFile.SetFullName( "id_ecdsa" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
keyFile.SetFullName( "id_ed25519" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
// Parse SSH config file for hostname information
wxFileName sshConfig( wxGetHomeDir(), wxEmptyString );
sshConfig.AppendDir( ".ssh" );
sshConfig.SetFullName( "config" );
if( sshConfig.FileExists() )
{
wxTextFile configFile( sshConfig.GetFullPath() );
configFile.Open();
wxString lastIdentityFile;
bool match = false;
for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() )
{
line.Trim( false ).Trim( true );
if( line.StartsWith( "Host " ) )
{
lastIdentityFile.Clear();
match = false;
}
// The difference here is that we are matching either "Hostname" or "Host" to get the
// match. This is because in the absence of a "Hostname" line, the "Host" line is used
if( line.StartsWith( "Host" ) && line.Contains( m_hostname ) )
match = true;
if( match && line.StartsWith( "IdentityFile" ) )
{
wxString keyPath = line.AfterFirst( ' ' ).Trim( false ).Trim( true );
// Expand ~ to home directory if present
if( keyPath.StartsWith( "~" ) )
keyPath.Replace( "~", wxGetHomeDir(), false );
// Add the public key to the beginning of the list
if( wxFileName::FileExists( keyPath ) )
{
SetSSHKey( keyPath );
}
}
}
configFile.Close();
}
}
void KIGIT_COMMON::UpdateCurrentBranchInfo()
{
@ -437,6 +523,77 @@ void KIGIT_COMMON::UpdateCurrentBranchInfo()
// Find the stored password if it exists
KIPLATFORM::SECRETS::GetSecret( m_remote, m_username, m_password );
updateConnectionType();
updatePublicKeys();
}
void KIGIT_COMMON::updateConnectionType()
{
if( m_remote.StartsWith( "https://" ) || m_remote.StartsWith( "http://" ) )
m_connType = GIT_CONN_TYPE::GIT_CONN_HTTPS;
else if( m_remote.StartsWith( "ssh://" ) || m_remote.StartsWith( "git@" ) || m_remote.StartsWith( "git+ssh://" ) )
m_connType = GIT_CONN_TYPE::GIT_CONN_SSH;
else
m_connType = GIT_CONN_TYPE::GIT_CONN_LOCAL;
if( m_connType != GIT_CONN_TYPE::GIT_CONN_LOCAL )
{
wxString uri = m_remote;
size_t atPos = uri.find( '@' );
if( atPos != wxString::npos )
{
size_t protoEnd = uri.find( "//" );
if( protoEnd != wxString::npos )
{
wxString credentials = uri.Mid( protoEnd + 2, atPos - protoEnd - 2 );
size_t colonPos = credentials.find( ':' );
if( colonPos != wxString::npos )
{
m_username = credentials.Left( colonPos );
m_password = credentials.Mid( colonPos + 1, credentials.Length() - colonPos - 1 );
}
else
{
m_username = credentials;
}
}
else
{
m_username = uri.Left( atPos );
}
}
if( m_remote.StartsWith( "git@" ) )
{
// SSH format: git@hostname:path
size_t colonPos = m_remote.find( ':' );
if( colonPos != wxString::npos )
m_hostname = m_remote.Mid( 4, colonPos - 4 );
}
else
{
// other URL format: proto://[user@]hostname/path
size_t hostStart = m_remote.find( "://" ) + 2;
size_t hostEnd = m_remote.find( '/', hostStart );
wxString host;
if( hostEnd != wxString::npos )
host = m_remote.Mid( hostStart, hostEnd - hostStart );
else
host = m_remote.Mid( hostStart );
atPos = host.find( '@' );
if( atPos != wxString::npos )
m_hostname = host.Mid( atPos + 1 );
else
m_hostname = host;
}
}
}
@ -585,7 +742,14 @@ extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aU
&& !( parent->TestedTypes() & GIT_CREDTYPE_SSH_KEY ) )
{
// SSH key authentication
wxString sshKey = parent->GetSSHKey();
wxString sshKey = parent->GetNextPublicKey();
if( sshKey.IsEmpty() )
{
parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY;
return GIT_PASSTHROUGH;
}
wxString sshPubKey = sshKey + ".pub";
wxString username = parent->GetUsername().Trim().Trim( false );
wxString password = parent->GetPassword().Trim().Trim( false );
@ -594,7 +758,6 @@ extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aU
sshPubKey.ToStdString().c_str(),
sshKey.ToStdString().c_str(),
password.ToStdString().c_str() );
parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY;
}
else
{

View File

@ -89,12 +89,11 @@ public:
wxString GetUsername() const { return m_username; }
wxString GetPassword() const { return m_password; }
wxString GetSSHKey() const { return m_sshKey; }
GIT_CONN_TYPE GetConnType() const { return m_connType; }
void SetUsername( const wxString& aUsername ) { m_username = aUsername; }
void SetPassword( const wxString& aPassword ) { m_password = aPassword; }
void SetSSHKey( const wxString& aSSHKey ) { m_sshKey = aSSHKey; }
void SetSSHKey( const wxString& aSSHKey );
void SetConnType( GIT_CONN_TYPE aConnType ) { m_connType = aConnType; }
void SetConnType( unsigned aConnType )
@ -119,17 +118,34 @@ public:
wxString GetRemotename() const;
void ResetNextKey() { m_nextPublicKey = 0; }
wxString GetNextPublicKey()
{
if( m_nextPublicKey >= static_cast<int>( m_publicKeys.size() ) )
return wxEmptyString;
return m_publicKeys[m_nextPublicKey++];
}
protected:
git_repository* m_repo;
GIT_CONN_TYPE m_connType;
wxString m_remote;
wxString m_remote; // This is the full connection string
wxString m_hostname; // This is just the hostname without the protocol, username, or password
wxString m_username;
wxString m_password;
wxString m_sshKey;
unsigned m_testedTypes;
private:
void updatePublicKeys();
void updateConnectionType();
std::vector<wxString> m_publicKeys;
int m_nextPublicKey;
};

View File

@ -641,15 +641,6 @@ void PROJECT_TREE_PANE::ReCreateTreePrj()
m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername );
m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey );
m_TreeProject->GitCommon()->UpdateCurrentBranchInfo();
wxString conn_type = Prj().GetLocalSettings().m_GitRepoType;
if( conn_type == "https" )
m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS );
else if( conn_type == "ssh" )
m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH );
else
m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL );
}
// We may have opened a legacy project, in which case GetProjectFileName will return the
@ -1694,12 +1685,7 @@ void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
m_gitLastError = GIT_ERROR_NONE;
GIT_PULL_HANDLER handler( repo );
handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() );
handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() );
handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() );
handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() );
handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
_( "Fetching Remote" ),
@ -1733,18 +1719,13 @@ void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent )
if( !repo )
return;
GIT_PULL_HANDLER handler( repo );
handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() );
handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() );
handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() );
handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() );
handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
_( "Fetching Remote" ),
1 ) );
if( handler.PerformPull() != PullResult::Success )
if( handler.PerformPull() < PullResult::Success )
{
wxString errorMessage = handler.GetErrorString();
@ -1760,12 +1741,7 @@ void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
if( !repo )
return;
GIT_PUSH_HANDLER handler( repo );
handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() );
handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() );
handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() );
handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() );
GIT_PUSH_HANDLER handler( m_TreeProject->GitCommon() );
handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this,
_( "Fetching Remote" ),
@ -2410,12 +2386,12 @@ void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent )
void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent )
{
git_repository* repo = m_TreeProject->GetGitRepo();
KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon();
if( !repo )
if( !gitCommon )
return;
GIT_PULL_HANDLER handler( repo );
GIT_PULL_HANDLER handler( gitCommon );
handler.PerformFetch();
}