github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/pki/ssh/format.go (about)

     1  // Copyright 2022 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package ssh
     5  
     6  import (
     7  	"crypto"
     8  	"crypto/ecdsa"
     9  	"crypto/ed25519"
    10  	"crypto/elliptic"
    11  	"crypto/rsa"
    12  	"crypto/x509"
    13  	"encoding/pem"
    14  	"fmt"
    15  	"reflect"
    16  	"strings"
    17  
    18  	"github.com/juju/errors"
    19  	cryptossh "golang.org/x/crypto/ssh"
    20  )
    21  
    22  func encodePrivateKey(pk crypto.PrivateKey) (string, error) {
    23  	pkcs, err := x509.MarshalPKCS8PrivateKey(pk)
    24  	if err != nil {
    25  		return "", err
    26  	}
    27  	block := &pem.Block{
    28  		Type:  "PRIVATE KEY",
    29  		Bytes: pkcs,
    30  	}
    31  	return string(pem.EncodeToMemory(block)), nil
    32  }
    33  
    34  func encodePublicKey(pk crypto.PublicKey, comment string) (string, error) {
    35  	sshPublicKey, err := cryptossh.NewPublicKey(pk)
    36  	if err != nil {
    37  		return "", errors.NewNotValid(err, "public key")
    38  	}
    39  	encoded := string(cryptossh.MarshalAuthorizedKey(sshPublicKey))
    40  	if comment != "" {
    41  		encoded = fmt.Sprintf("%s %s\n", strings.TrimRight(encoded, "\n"), comment)
    42  	}
    43  	return encoded, nil
    44  }
    45  
    46  type privateKey interface {
    47  	Public() crypto.PublicKey
    48  }
    49  
    50  // FormatKey formats the crypto key into PKCS8 encoded private key and openssh public keyline.
    51  func FormatKey(pk crypto.PrivateKey, comment string) (private string, public string, keyAlgorithm string, err error) {
    52  	sshPrivateKey, _ := pk.(privateKey)
    53  	if sshPrivateKey == nil {
    54  		return "", "", "", errors.NotValidf("private key")
    55  	}
    56  	private, err = encodePrivateKey(sshPrivateKey)
    57  	if err != nil {
    58  		err = errors.Annotate(err, "cannot encode private key")
    59  		return
    60  	}
    61  	public, err = encodePublicKey(sshPrivateKey.Public(), comment)
    62  	if err != nil {
    63  		err = errors.Annotate(err, "cannot encode public key")
    64  		return
    65  	}
    66  	switch k := pk.(type) {
    67  	case *ecdsa.PrivateKey:
    68  		switch k.Curve {
    69  		case elliptic.P256():
    70  			keyAlgorithm = cryptossh.KeyAlgoECDSA256
    71  		case elliptic.P384():
    72  			keyAlgorithm = cryptossh.KeyAlgoECDSA384
    73  		case elliptic.P521():
    74  			keyAlgorithm = cryptossh.KeyAlgoECDSA521
    75  		}
    76  	case *ed25519.PrivateKey, ed25519.PrivateKey:
    77  		keyAlgorithm = cryptossh.KeyAlgoED25519
    78  	case *rsa.PrivateKey:
    79  		keyAlgorithm = cryptossh.KeyAlgoRSA
    80  	}
    81  	if keyAlgorithm == "" {
    82  		err = errors.Errorf("unknown private key type %v", reflect.TypeOf(pk))
    83  	}
    84  	return
    85  }