diff options
Diffstat (limited to 'models/asymkey')
-rw-r--r-- | models/asymkey/error.go | 2 | ||||
-rw-r--r-- | models/asymkey/gpg_key.go | 21 | ||||
-rw-r--r-- | models/asymkey/gpg_key_add.go | 2 | ||||
-rw-r--r-- | models/asymkey/gpg_key_commit_verification.go | 26 | ||||
-rw-r--r-- | models/asymkey/gpg_key_common.go | 9 | ||||
-rw-r--r-- | models/asymkey/gpg_key_verify.go | 111 | ||||
-rw-r--r-- | models/asymkey/ssh_key.go | 103 | ||||
-rw-r--r-- | models/asymkey/ssh_key_commit_verification.go | 80 | ||||
-rw-r--r-- | models/asymkey/ssh_key_deploy.go | 56 | ||||
-rw-r--r-- | models/asymkey/ssh_key_fingerprint.go | 48 | ||||
-rw-r--r-- | models/asymkey/ssh_key_parse.go | 76 | ||||
-rw-r--r-- | models/asymkey/ssh_key_test.go | 32 | ||||
-rw-r--r-- | models/asymkey/ssh_key_verify.go | 60 |
13 files changed, 174 insertions, 452 deletions
diff --git a/models/asymkey/error.go b/models/asymkey/error.go index 1ed6edd71a..b765624579 100644 --- a/models/asymkey/error.go +++ b/models/asymkey/error.go @@ -132,7 +132,7 @@ func IsErrGPGKeyParsing(err error) bool { } func (err ErrGPGKeyParsing) Error() string { - return fmt.Sprintf("failed to parse gpg key %s", err.ParseError.Error()) + return "failed to parse gpg key " + err.ParseError.Error() } // ErrGPGKeyNotExist represents a "GPGKeyNotExist" kind of error. diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index 7f35a96a59..38de7cbda6 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -5,6 +5,7 @@ package asymkey import ( "context" + "errors" "fmt" "strings" "time" @@ -207,7 +208,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified // deleteGPGKey does the actual key deletion func deleteGPGKey(ctx context.Context, keyID string) (int64, error) { if keyID == "" { - return 0, fmt.Errorf("empty KeyId forbidden") // Should never happen but just to be sure + return 0, errors.New("empty KeyId forbidden") // Should never happen but just to be sure } // Delete imported key n, err := db.GetEngine(ctx).Where("key_id=?", keyID).Delete(new(GPGKeyImport)) @@ -227,15 +228,15 @@ func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err err return fmt.Errorf("GetPublicKeyByID: %w", err) } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if _, err = deleteGPGKey(ctx, key.KeyID); err != nil { + return db.WithTx(ctx, func(ctx context.Context) error { + _, err = deleteGPGKey(ctx, key.KeyID) return err - } + }) +} - return committer.Commit() +func FindGPGKeyWithSubKeys(ctx context.Context, keyID string) ([]*GPGKey, error) { + return db.Find[GPGKey](ctx, FindGPGKeyOptions{ + KeyID: keyID, + IncludeSubKeys: true, + }) } diff --git a/models/asymkey/gpg_key_add.go b/models/asymkey/gpg_key_add.go index ec2031088a..1c7d2c1da2 100644 --- a/models/asymkey/gpg_key_add.go +++ b/models/asymkey/gpg_key_add.go @@ -91,7 +91,7 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil) } if err != nil { - log.Error("Unable to validate token signature. Error: %v", err) + log.Debug("AddGPGKey CheckArmoredDetachedSignature failed: %v", err) return nil, ErrGPGInvalidTokenSignature{ ID: ekeys[0].PrimaryKey.KeyIdString(), Wrapped: err, diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index 1591cd5068..b85374e073 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -4,6 +4,7 @@ package asymkey import ( + "errors" "fmt" "hash" @@ -14,25 +15,6 @@ import ( "github.com/ProtonMail/go-crypto/openpgp/packet" ) -// __________________ ________ ____ __. -// / _____/\______ \/ _____/ | |/ _|____ ___.__. -// / \ ___ | ___/ \ ___ | <_/ __ < | | -// \ \_\ \| | \ \_\ \ | | \ ___/\___ | -// \______ /|____| \______ / |____|__ \___ > ____| -// \/ \/ \/ \/\/ -// _________ .__ __ -// \_ ___ \ ____ _____ _____ |__|/ |_ -// / \ \/ / _ \ / \ / \| \ __\ -// \ \___( <_> ) Y Y \ Y Y \ || | -// \______ /\____/|__|_| /__|_| /__||__| -// \/ \/ \/ -// ____ ____ .__ _____.__ __ .__ -// \ \ / /___________|__|/ ____\__| ____ _____ _/ |_|__| ____ ____ -// \ Y // __ \_ __ \ \ __\| |/ ___\\__ \\ __\ |/ _ \ / \ -// \ /\ ___/| | \/ || | | \ \___ / __ \| | | ( <_> ) | \ -// \___/ \___ >__| |__||__| |__|\___ >____ /__| |__|\____/|___| / -// \/ \/ \/ \/ - // This file provides functions relating commit verification // CommitVerification represents a commit validation of signature @@ -40,8 +22,8 @@ type CommitVerification struct { Verified bool Warning bool Reason string - SigningUser *user_model.User - CommittingUser *user_model.User + SigningUser *user_model.User // if Verified, then SigningUser is non-nil + CommittingUser *user_model.User // if Verified, then CommittingUser is non-nil SigningEmail string SigningKey *GPGKey SigningSSHKey *PublicKey @@ -68,7 +50,7 @@ const ( func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error { // Check if key can sign if !k.CanSign { - return fmt.Errorf("key can not sign") + return errors.New("key can not sign") } // Decode key pkey, err := base64DecPubKey(k.Content) diff --git a/models/asymkey/gpg_key_common.go b/models/asymkey/gpg_key_common.go index 1291cbc542..76f52a3ca4 100644 --- a/models/asymkey/gpg_key_common.go +++ b/models/asymkey/gpg_key_common.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto" "encoding/base64" + "errors" "fmt" "hash" "io" @@ -75,7 +76,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) { // Check type pkey, ok := p.(*packet.PublicKey) if !ok { - return nil, fmt.Errorf("key is not a public key") + return nil, errors.New("key is not a public key") } return pkey, nil } @@ -122,15 +123,15 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) { func ExtractSignature(s string) (*packet.Signature, error) { r, err := readArmoredSign(strings.NewReader(s)) if err != nil { - return nil, fmt.Errorf("Failed to read signature armor") + return nil, errors.New("Failed to read signature armor") } p, err := packet.Read(r) if err != nil { - return nil, fmt.Errorf("Failed to read signature packet") + return nil, errors.New("Failed to read signature packet") } sig, ok := p.(*packet.Signature) if !ok { - return nil, fmt.Errorf("Packet is not a signature") + return nil, errors.New("Packet is not a signature") } return sig, nil } diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go index 6eedb5b7ba..55c64973b4 100644 --- a/models/asymkey/gpg_key_verify.go +++ b/models/asymkey/gpg_key_verify.go @@ -14,97 +14,76 @@ import ( "code.gitea.io/gitea/modules/log" ) -// __________________ ________ ____ __. -// / _____/\______ \/ _____/ | |/ _|____ ___.__. -// / \ ___ | ___/ \ ___ | <_/ __ < | | -// \ \_\ \| | \ \_\ \ | | \ ___/\___ | -// \______ /|____| \______ / |____|__ \___ > ____| -// \/ \/ \/ \/\/ -// ____ ____ .__ _____ -// \ \ / /___________|__|/ ____\__.__. -// \ Y // __ \_ __ \ \ __< | | -// \ /\ ___/| | \/ || | \___ | -// \___/ \___ >__| |__||__| / ____| -// \/ \/ - // This file provides functions relating verifying gpg keys // VerifyGPGKey marks a GPG key as verified func VerifyGPGKey(ctx context.Context, ownerID int64, keyID, token, signature string) (string, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return "", err - } - defer committer.Close() - - key := new(GPGKey) - - has, err := db.GetEngine(ctx).Where("owner_id = ? AND key_id = ?", ownerID, keyID).Get(key) - if err != nil { - return "", err - } else if !has { - return "", ErrGPGKeyNotExist{} - } - - if err := key.LoadSubKeys(ctx); err != nil { - return "", err - } + return db.WithTx2(ctx, func(ctx context.Context) (string, error) { + key := new(GPGKey) - sig, err := ExtractSignature(signature) - if err != nil { - return "", ErrGPGInvalidTokenSignature{ - ID: key.KeyID, - Wrapped: err, + has, err := db.GetEngine(ctx).Where("owner_id = ? AND key_id = ?", ownerID, keyID).Get(key) + if err != nil { + return "", err + } else if !has { + return "", ErrGPGKeyNotExist{} } - } - signer, err := hashAndVerifyWithSubKeys(sig, token, key) - if err != nil { - return "", ErrGPGInvalidTokenSignature{ - ID: key.KeyID, - Wrapped: err, + if err := key.LoadSubKeys(ctx); err != nil { + return "", err } - } - if signer == nil { - signer, err = hashAndVerifyWithSubKeys(sig, token+"\n", key) + + sig, err := ExtractSignature(signature) if err != nil { return "", ErrGPGInvalidTokenSignature{ ID: key.KeyID, Wrapped: err, } } - } - if signer == nil { - signer, err = hashAndVerifyWithSubKeys(sig, token+"\n\n", key) + + signer, err := hashAndVerifyWithSubKeys(sig, token, key) if err != nil { return "", ErrGPGInvalidTokenSignature{ ID: key.KeyID, Wrapped: err, } } - } - - if signer == nil { - log.Error("Unable to validate token signature. Error: %v", err) - return "", ErrGPGInvalidTokenSignature{ - ID: key.KeyID, + if signer == nil { + signer, err = hashAndVerifyWithSubKeys(sig, token+"\n", key) + if err != nil { + return "", ErrGPGInvalidTokenSignature{ + ID: key.KeyID, + Wrapped: err, + } + } + } + if signer == nil { + signer, err = hashAndVerifyWithSubKeys(sig, token+"\n\n", key) + if err != nil { + return "", ErrGPGInvalidTokenSignature{ + ID: key.KeyID, + Wrapped: err, + } + } } - } - if signer.PrimaryKeyID != key.KeyID && signer.KeyID != key.KeyID { - return "", ErrGPGKeyNotExist{} - } + if signer == nil { + log.Debug("VerifyGPGKey failed: no signer") + return "", ErrGPGInvalidTokenSignature{ + ID: key.KeyID, + } + } - key.Verified = true - if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil { - return "", err - } + if signer.PrimaryKeyID != key.KeyID && signer.KeyID != key.KeyID { + return "", ErrGPGKeyNotExist{} + } - if err := committer.Commit(); err != nil { - return "", err - } + key.Verified = true + if _, err := db.GetEngine(ctx).ID(key.ID).SetExpr("verified", true).Update(new(GPGKey)); err != nil { + return "", err + } - return key.KeyID, nil + return key.KeyID, nil + }) } // VerificationToken returns token for the user that will be valid in minutes (time) diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index 7a18732c32..87205f0651 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -99,40 +99,36 @@ func AddPublicKey(ctx context.Context, ownerID int64, name, content string, auth return nil, err } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - if err := checkKeyFingerprint(ctx, fingerprint); err != nil { - return nil, err - } + return db.WithTx2(ctx, func(ctx context.Context) (*PublicKey, error) { + if err := checkKeyFingerprint(ctx, fingerprint); err != nil { + return nil, err + } - // Key name of same user cannot be duplicated. - has, err := db.GetEngine(ctx). - Where("owner_id = ? AND name = ?", ownerID, name). - Get(new(PublicKey)) - if err != nil { - return nil, err - } else if has { - return nil, ErrKeyNameAlreadyUsed{ownerID, name} - } + // Key name of same user cannot be duplicated. + has, err := db.GetEngine(ctx). + Where("owner_id = ? AND name = ?", ownerID, name). + Get(new(PublicKey)) + if err != nil { + return nil, err + } else if has { + return nil, ErrKeyNameAlreadyUsed{ownerID, name} + } - key := &PublicKey{ - OwnerID: ownerID, - Name: name, - Fingerprint: fingerprint, - Content: content, - Mode: perm.AccessModeWrite, - Type: KeyTypeUser, - LoginSourceID: authSourceID, - } - if err = addKey(ctx, key); err != nil { - return nil, fmt.Errorf("addKey: %w", err) - } + key := &PublicKey{ + OwnerID: ownerID, + Name: name, + Fingerprint: fingerprint, + Content: content, + Mode: perm.AccessModeWrite, + Type: KeyTypeUser, + LoginSourceID: authSourceID, + } + if err = addKey(ctx, key); err != nil { + return nil, fmt.Errorf("addKey: %w", err) + } - return key, committer.Commit() + return key, nil + }) } // GetPublicKeyByID returns public key by given ID. @@ -288,33 +284,24 @@ func PublicKeyIsExternallyManaged(ctx context.Context, id int64) (bool, error) { // deleteKeysMarkedForDeletion returns true if ssh keys needs update func deleteKeysMarkedForDeletion(ctx context.Context, keys []string) (bool, error) { - // Start session - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return false, err - } - defer committer.Close() - - // Delete keys marked for deletion - var sshKeysNeedUpdate bool - for _, KeyToDelete := range keys { - key, err := SearchPublicKeyByContent(ctx, KeyToDelete) - if err != nil { - log.Error("SearchPublicKeyByContent: %v", err) - continue - } - if _, err = db.DeleteByID[PublicKey](ctx, key.ID); err != nil { - log.Error("DeleteByID[PublicKey]: %v", err) - continue + return db.WithTx2(ctx, func(ctx context.Context) (bool, error) { + // Delete keys marked for deletion + var sshKeysNeedUpdate bool + for _, KeyToDelete := range keys { + key, err := SearchPublicKeyByContent(ctx, KeyToDelete) + if err != nil { + log.Error("SearchPublicKeyByContent: %v", err) + continue + } + if _, err = db.DeleteByID[PublicKey](ctx, key.ID); err != nil { + log.Error("DeleteByID[PublicKey]: %v", err) + continue + } + sshKeysNeedUpdate = true } - sshKeysNeedUpdate = true - } - if err := committer.Commit(); err != nil { - return false, err - } - - return sshKeysNeedUpdate, nil + return sshKeysNeedUpdate, nil + }) } // AddPublicKeysBySource add a users public keys. Returns true if there are changes. @@ -355,13 +342,13 @@ func AddPublicKeysBySource(ctx context.Context, usr *user_model.User, s *auth.So return sshKeysNeedUpdate } -// SynchronizePublicKeys updates a users public keys. Returns true if there are changes. +// SynchronizePublicKeys updates a user's public keys. Returns true if there are changes. func SynchronizePublicKeys(ctx context.Context, usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool { var sshKeysNeedUpdate bool log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name) - // Get Public Keys from DB with current LDAP source + // Get Public Keys from DB with the current auth source var giteaKeys []string keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{ OwnerID: usr.ID, diff --git a/models/asymkey/ssh_key_commit_verification.go b/models/asymkey/ssh_key_commit_verification.go deleted file mode 100644 index 27c6df3578..0000000000 --- a/models/asymkey/ssh_key_commit_verification.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package asymkey - -import ( - "bytes" - "context" - "fmt" - "strings" - - "code.gitea.io/gitea/models/db" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - - "github.com/42wim/sshsig" -) - -// ParseCommitWithSSHSignature check if signature is good against keystore. -func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification { - // Now try to associate the signature with the committer, if present - if committer.ID != 0 { - keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{ - OwnerID: committer.ID, - NotKeytype: KeyTypePrincipal, - }) - if err != nil { // Skipping failed to get ssh keys of user - log.Error("ListPublicKeys: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.failed_retrieval_gpg_keys", - } - } - - committerEmailAddresses, err := user_model.GetEmailAddresses(ctx, committer.ID) - if err != nil { - log.Error("GetEmailAddresses: %v", err) - } - - activated := false - for _, e := range committerEmailAddresses { - if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { - activated = true - break - } - } - - for _, k := range keys { - if k.Verified && activated { - commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email) - if commitVerification != nil { - return commitVerification - } - } - } - } - - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: NoKeyFound, - } -} - -func verifySSHCommitVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *CommitVerification { - if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil { - return nil - } - - return &CommitVerification{ // Everything is ok - CommittingUser: committer, - Verified: true, - Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint), - SigningUser: signer, - SigningSSHKey: k, - SigningEmail: email, - } -} diff --git a/models/asymkey/ssh_key_deploy.go b/models/asymkey/ssh_key_deploy.go index 923c5020ed..4ab84eabcf 100644 --- a/models/asymkey/ssh_key_deploy.go +++ b/models/asymkey/ssh_key_deploy.go @@ -125,39 +125,35 @@ func AddDeployKey(ctx context.Context, repoID int64, name, content string, readO accessMode = perm.AccessModeWrite } - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return nil, err - } - defer committer.Close() - - pkey, exist, err := db.Get[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint}) - if err != nil { - return nil, err - } else if exist { - if pkey.Type != KeyTypeDeploy { - return nil, ErrKeyAlreadyExist{0, fingerprint, ""} - } - } else { - // First time use this deploy key. - pkey = &PublicKey{ - Fingerprint: fingerprint, - Mode: accessMode, - Type: KeyTypeDeploy, - Content: content, - Name: name, + return db.WithTx2(ctx, func(ctx context.Context) (*DeployKey, error) { + pkey, exist, err := db.Get[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint}) + if err != nil { + return nil, err + } else if exist { + if pkey.Type != KeyTypeDeploy { + return nil, ErrKeyAlreadyExist{0, fingerprint, ""} + } + } else { + // First time use this deploy key. + pkey = &PublicKey{ + Fingerprint: fingerprint, + Mode: accessMode, + Type: KeyTypeDeploy, + Content: content, + Name: name, + } + if err = addKey(ctx, pkey); err != nil { + return nil, fmt.Errorf("addKey: %w", err) + } } - if err = addKey(ctx, pkey); err != nil { - return nil, fmt.Errorf("addKey: %w", err) - } - } - key, err := addDeployKey(ctx, pkey.ID, repoID, name, pkey.Fingerprint, accessMode) - if err != nil { - return nil, err - } + key, err := addDeployKey(ctx, pkey.ID, repoID, name, pkey.Fingerprint, accessMode) + if err != nil { + return nil, err + } - return key, committer.Commit() + return key, nil + }) } // GetDeployKeyByID returns deploy key by given ID. diff --git a/models/asymkey/ssh_key_fingerprint.go b/models/asymkey/ssh_key_fingerprint.go index 1ed3b5df2a..b666469ae8 100644 --- a/models/asymkey/ssh_key_fingerprint.go +++ b/models/asymkey/ssh_key_fingerprint.go @@ -6,30 +6,16 @@ package asymkey import ( "context" "fmt" - "strings" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "golang.org/x/crypto/ssh" "xorm.io/builder" ) -// ___________.__ .__ __ -// \_ _____/|__| ____ ____ ________________________|__| _____/ |_ -// | __) | |/ \ / ___\_/ __ \_ __ \____ \_ __ \ |/ \ __\ -// | \ | | | \/ /_/ > ___/| | \/ |_> > | \/ | | \ | -// \___ / |__|___| /\___ / \___ >__| | __/|__| |__|___| /__| -// \/ \//_____/ \/ |__| \/ -// -// This file contains functions for fingerprinting SSH keys -// -// The database is used in checkKeyFingerprint however most of these functions probably belong in a module +// The database is used in checkKeyFingerprint. However, most of these functions probably belong in a module -// checkKeyFingerprint only checks if key fingerprint has been used as public key, +// checkKeyFingerprint only checks if key fingerprint has been used as a public key, // it is OK to use same key as deploy key for multiple repositories/users. func checkKeyFingerprint(ctx context.Context, fingerprint string) error { has, err := db.Exist[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint}) @@ -41,29 +27,6 @@ func checkKeyFingerprint(ctx context.Context, fingerprint string) error { return nil } -func calcFingerprintSSHKeygen(publicKeyContent string) (string, error) { - // Calculate fingerprint. - tmpPath, err := writeTmpKeyFile(publicKeyContent) - if err != nil { - return "", err - } - defer func() { - if err := util.Remove(tmpPath); err != nil { - log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpPath, err) - } - }() - stdout, stderr, err := process.GetManager().Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath) - if err != nil { - if strings.Contains(stderr, "is not a public key file") { - return "", ErrKeyUnableVerify{stderr} - } - return "", util.NewInvalidArgumentErrorf("'ssh-keygen -lf %s' failed with error '%s': %s", tmpPath, err, stderr) - } else if len(stdout) < 2 { - return "", util.NewInvalidArgumentErrorf("not enough output for calculating fingerprint: %s", stdout) - } - return strings.Split(stdout, " ")[1], nil -} - func calcFingerprintNative(publicKeyContent string) (string, error) { // Calculate fingerprint. pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent)) @@ -75,15 +38,12 @@ func calcFingerprintNative(publicKeyContent string) (string, error) { // CalcFingerprint calculate public key's fingerprint func CalcFingerprint(publicKeyContent string) (string, error) { - // Call the method based on configuration - useNative := setting.SSH.KeygenPath == "" - calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen) - fp, err := calcFn(publicKeyContent) + fp, err := calcFingerprintNative(publicKeyContent) if err != nil { if IsErrKeyUnableVerify(err) { return "", err } - return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err) + return "", fmt.Errorf("CalcFingerprint: %w", err) } return fp, nil } diff --git a/models/asymkey/ssh_key_parse.go b/models/asymkey/ssh_key_parse.go index 94b1cf112b..fc39f28624 100644 --- a/models/asymkey/ssh_key_parse.go +++ b/models/asymkey/ssh_key_parse.go @@ -10,14 +10,12 @@ import ( "encoding/base64" "encoding/binary" "encoding/pem" + "errors" "fmt" "math/big" - "os" - "strconv" "strings" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -93,7 +91,7 @@ func parseKeyString(content string) (string, error) { block, _ := pem.Decode([]byte(content)) if block == nil { - return "", fmt.Errorf("failed to parse PEM block containing the public key") + return "", errors.New("failed to parse PEM block containing the public key") } if strings.Contains(block.Type, "PRIVATE") { return "", ErrKeyIsPrivate @@ -174,20 +172,9 @@ func CheckPublicKeyString(content string) (_ string, err error) { return content, nil } - var ( - fnName string - keyType string - length int - ) - if len(setting.SSH.KeygenPath) == 0 { - fnName = "SSHNativeParsePublicKey" - keyType, length, err = SSHNativeParsePublicKey(content) - } else { - fnName = "SSHKeyGenParsePublicKey" - keyType, length, err = SSHKeyGenParsePublicKey(content) - } + keyType, length, err := SSHNativeParsePublicKey(content) if err != nil { - return "", fmt.Errorf("%s: %w", fnName, err) + return "", fmt.Errorf("SSHNativeParsePublicKey: %w", err) } log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length) @@ -221,7 +208,7 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) { // The ssh library can parse the key, so next we find out what key exactly we have. switch pkey.Type() { - case ssh.KeyAlgoDSA: + case ssh.KeyAlgoDSA: //nolint:staticcheck // it's deprecated rawPub := struct { Name string P, Q, G, Y *big.Int @@ -257,56 +244,3 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) { } return "", 0, fmt.Errorf("unsupported key length detection for type: %s", pkey.Type()) } - -// writeTmpKeyFile writes key content to a temporary file -// and returns the name of that file, along with any possible errors. -func writeTmpKeyFile(content string) (string, error) { - tmpFile, err := os.CreateTemp(setting.SSH.KeyTestPath, "gitea_keytest") - if err != nil { - return "", fmt.Errorf("TempFile: %w", err) - } - defer tmpFile.Close() - - if _, err = tmpFile.WriteString(content); err != nil { - return "", fmt.Errorf("WriteString: %w", err) - } - return tmpFile.Name(), nil -} - -// SSHKeyGenParsePublicKey extracts key type and length using ssh-keygen. -func SSHKeyGenParsePublicKey(key string) (string, int, error) { - tmpName, err := writeTmpKeyFile(key) - if err != nil { - return "", 0, fmt.Errorf("writeTmpKeyFile: %w", err) - } - defer func() { - if err := util.Remove(tmpName); err != nil { - log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpName, err) - } - }() - - keygenPath := setting.SSH.KeygenPath - if len(keygenPath) == 0 { - keygenPath = "ssh-keygen" - } - - stdout, stderr, err := process.GetManager().Exec("SSHKeyGenParsePublicKey", keygenPath, "-lf", tmpName) - if err != nil { - return "", 0, fmt.Errorf("fail to parse public key: %s - %s", err, stderr) - } - if strings.Contains(stdout, "is not a public key file") { - return "", 0, ErrKeyUnableVerify{stdout} - } - - fields := strings.Split(stdout, " ") - if len(fields) < 4 { - return "", 0, fmt.Errorf("invalid public key line: %s", stdout) - } - - keyType := strings.Trim(fields[len(fields)-1], "()\r\n") - length, err := strconv.ParseInt(fields[0], 10, 32) - if err != nil { - return "", 0, err - } - return strings.ToLower(keyType), int(length), nil -} diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go index 3650f1892f..21e4ddf62e 100644 --- a/models/asymkey/ssh_key_test.go +++ b/models/asymkey/ssh_key_test.go @@ -42,29 +42,7 @@ func Test_SSHParsePublicKey(t *testing.T) { keyTypeN, lengthN, err := SSHNativeParsePublicKey(tc.content) assert.NoError(t, err) assert.Equal(t, tc.keyType, keyTypeN) - assert.EqualValues(t, tc.length, lengthN) - }) - if tc.skipSSHKeygen { - return - } - t.Run("SSHKeygen", func(t *testing.T) { - keyTypeK, lengthK, err := SSHKeyGenParsePublicKey(tc.content) - if err != nil { - // Some servers do not support ecdsa format. - if !strings.Contains(err.Error(), "line 1 too long:") { - assert.FailNow(t, "%v", err) - } - } - assert.Equal(t, tc.keyType, keyTypeK) - assert.EqualValues(t, tc.length, lengthK) - }) - t.Run("SSHParseKeyNative", func(t *testing.T) { - keyTypeK, lengthK, err := SSHNativeParsePublicKey(tc.content) - if err != nil { - assert.FailNow(t, "%v", err) - } - assert.Equal(t, tc.keyType, keyTypeK) - assert.EqualValues(t, tc.length, lengthK) + assert.Equal(t, tc.length, lengthN) }) }) } @@ -186,14 +164,6 @@ func Test_calcFingerprint(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tc.fp, fpN) }) - if tc.skipSSHKeygen { - return - } - t.Run("SSHKeygen", func(t *testing.T) { - fpK, err := calcFingerprintSSHKeygen(tc.content) - assert.NoError(t, err) - assert.Equal(t, tc.fp, fpK) - }) }) } } diff --git a/models/asymkey/ssh_key_verify.go b/models/asymkey/ssh_key_verify.go index 208288c77b..04917239ee 100644 --- a/models/asymkey/ssh_key_verify.go +++ b/models/asymkey/ssh_key_verify.go @@ -4,8 +4,8 @@ package asymkey import ( - "bytes" "context" + "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" @@ -15,41 +15,33 @@ import ( // VerifySSHKey marks a SSH key as verified func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signature string) (string, error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return "", err - } - defer committer.Close() - - key := new(PublicKey) - - has, err := db.GetEngine(ctx).Where("owner_id = ? AND fingerprint = ?", ownerID, fingerprint).Get(key) - if err != nil { - return "", err - } else if !has { - return "", ErrKeyNotExist{} - } - - err = sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea") - if err != nil { - // edge case for Windows based shells that will add CR LF if piped to ssh-keygen command - // see https://github.com/PowerShell/PowerShell/issues/5974 - if sshsig.Verify(bytes.NewBuffer([]byte(token+"\r\n")), []byte(signature), []byte(key.Content), "gitea") != nil { - log.Error("Unable to validate token signature. Error: %v", err) - return "", ErrSSHInvalidTokenSignature{ - Fingerprint: key.Fingerprint, - } + return db.WithTx2(ctx, func(ctx context.Context) (string, error) { + key := new(PublicKey) + + has, err := db.GetEngine(ctx).Where("owner_id = ? AND fingerprint = ?", ownerID, fingerprint).Get(key) + if err != nil { + return "", err + } else if !has { + return "", ErrKeyNotExist{} } - } - key.Verified = true - if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil { - return "", err - } + err = sshsig.Verify(strings.NewReader(token), []byte(signature), []byte(key.Content), "gitea") + if err != nil { + // edge case for Windows based shells that will add CR LF if piped to ssh-keygen command + // see https://github.com/PowerShell/PowerShell/issues/5974 + if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil { + log.Debug("VerifySSHKey sshsig.Verify failed: %v", err) + return "", ErrSSHInvalidTokenSignature{ + Fingerprint: key.Fingerprint, + } + } + } - if err := committer.Commit(); err != nil { - return "", err - } + key.Verified = true + if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil { + return "", err + } - return key.Fingerprint, nil + return key.Fingerprint, nil + }) } |