code.gitea.io/gitea@v1.22.3/models/asymkey/ssh_key_fingerprint.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package asymkey 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/modules/log" 13 "code.gitea.io/gitea/modules/process" 14 "code.gitea.io/gitea/modules/setting" 15 "code.gitea.io/gitea/modules/util" 16 17 "golang.org/x/crypto/ssh" 18 "xorm.io/builder" 19 ) 20 21 // ___________.__ .__ __ 22 // \_ _____/|__| ____ ____ ________________________|__| _____/ |_ 23 // | __) | |/ \ / ___\_/ __ \_ __ \____ \_ __ \ |/ \ __\ 24 // | \ | | | \/ /_/ > ___/| | \/ |_> > | \/ | | \ | 25 // \___ / |__|___| /\___ / \___ >__| | __/|__| |__|___| /__| 26 // \/ \//_____/ \/ |__| \/ 27 // 28 // This file contains functions for fingerprinting SSH keys 29 // 30 // The database is used in checkKeyFingerprint however most of these functions probably belong in a module 31 32 // checkKeyFingerprint only checks if key fingerprint has been used as public key, 33 // it is OK to use same key as deploy key for multiple repositories/users. 34 func checkKeyFingerprint(ctx context.Context, fingerprint string) error { 35 has, err := db.Exist[PublicKey](ctx, builder.Eq{"fingerprint": fingerprint}) 36 if err != nil { 37 return err 38 } else if has { 39 return ErrKeyAlreadyExist{0, fingerprint, ""} 40 } 41 return nil 42 } 43 44 func calcFingerprintSSHKeygen(publicKeyContent string) (string, error) { 45 // Calculate fingerprint. 46 tmpPath, err := writeTmpKeyFile(publicKeyContent) 47 if err != nil { 48 return "", err 49 } 50 defer func() { 51 if err := util.Remove(tmpPath); err != nil { 52 log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpPath, err) 53 } 54 }() 55 stdout, stderr, err := process.GetManager().Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath) 56 if err != nil { 57 if strings.Contains(stderr, "is not a public key file") { 58 return "", ErrKeyUnableVerify{stderr} 59 } 60 return "", util.NewInvalidArgumentErrorf("'ssh-keygen -lf %s' failed with error '%s': %s", tmpPath, err, stderr) 61 } else if len(stdout) < 2 { 62 return "", util.NewInvalidArgumentErrorf("not enough output for calculating fingerprint: %s", stdout) 63 } 64 return strings.Split(stdout, " ")[1], nil 65 } 66 67 func calcFingerprintNative(publicKeyContent string) (string, error) { 68 // Calculate fingerprint. 69 pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent)) 70 if err != nil { 71 return "", err 72 } 73 return ssh.FingerprintSHA256(pk), nil 74 } 75 76 // CalcFingerprint calculate public key's fingerprint 77 func CalcFingerprint(publicKeyContent string) (string, error) { 78 // Call the method based on configuration 79 useNative := setting.SSH.KeygenPath == "" 80 calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen) 81 fp, err := calcFn(publicKeyContent) 82 if err != nil { 83 if IsErrKeyUnableVerify(err) { 84 return "", err 85 } 86 return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err) 87 } 88 return fp, nil 89 }