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 }