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  }