1package ssh2pem 2 3import ( 4 "bufio" 5 "bytes" 6 "crypto/rsa" 7 "crypto/x509" 8 "encoding/base64" 9 "encoding/binary" 10 "encoding/pem" 11 "fmt" 12 "io/ioutil" 13 "math/big" 14 "os" 15 "os/exec" 16 "strings" 17) 18 19func readLength(data []byte) ([]byte, uint32, error) { 20 l_buf := data[0:4] 21 22 buf := bytes.NewBuffer(l_buf) 23 24 var length uint32 25 26 err := binary.Read(buf, binary.BigEndian, &length) 27 if err != nil { 28 return nil, 0, err 29 } 30 31 return data[4:], length, nil 32} 33 34func readBigInt(data []byte, length uint32) ([]byte, *big.Int, error) { 35 var bigint = new(big.Int) 36 bigint.SetBytes(data[0:length]) 37 return data[length:], bigint, nil 38} 39 40func getRsaValues(data []byte) (format string, e, n *big.Int, err error) { 41 data, length, err := readLength(data) 42 if err != nil { 43 return 44 } 45 46 format = string(data[0:length]) 47 data = data[length:] 48 49 data, length, err = readLength(data) 50 if err != nil { 51 return 52 } 53 54 data, e, err = readBigInt(data, length) 55 if err != nil { 56 return 57 } 58 59 data, length, err = readLength(data) 60 if err != nil { 61 return 62 } 63 64 data, n, err = readBigInt(data, length) 65 if err != nil { 66 return 67 } 68 69 return 70} 71 72// DecodePublicKey return *rsa.PublicKey interface 73func DecodePublicKey(str string) (interface{}, error) { 74 tokens := strings.Split(str, " ") 75 76 if len(tokens) < 2 { 77 return nil, fmt.Errorf("Invalid key format") 78 } 79 80 key_type := tokens[0] 81 data, err := base64.StdEncoding.DecodeString(tokens[1]) 82 if err != nil { 83 return nil, err 84 } 85 86 format, e, n, err := getRsaValues(data) 87 if format != key_type { 88 return nil, fmt.Errorf("Key type mismatch %s != %s", key_type, format) 89 } 90 91 pubKey := &rsa.PublicKey{ 92 N: n, 93 E: int(e.Int64()), 94 } 95 96 return pubKey, nil 97} 98 99// GetPem return ssh-rsa public key in PEM PKCS8 100func GetPem(key string) ([]byte, error) { 101 f, err := os.Open(key) 102 if err != nil { 103 return nil, err 104 } 105 defer f.Close() 106 107 var b bytes.Buffer 108 109 // remove empty lines from file 110 scanner := bufio.NewScanner(f) 111 for scanner.Scan() { 112 if len(bytes.TrimSpace(scanner.Bytes())) > 0 { 113 b.WriteString(fmt.Sprintf("%s\n", scanner.Text())) 114 } 115 } 116 117 // check if ssh key is the private key 118 // TODO find a way of doing this not depending on ssh-keygen 119 block, r := pem.Decode(b.Bytes()) 120 if len(r) == 0 { 121 if strings.HasSuffix(block.Type, "PRIVATE KEY") { 122 tmpKey, err := ioutil.TempFile("", "trimCR") 123 if err != nil { 124 return nil, err 125 } 126 defer os.Remove(tmpKey.Name()) 127 if _, err := tmpKey.Write(b.Bytes()); err != nil { 128 return nil, err 129 } 130 if err := tmpKey.Close(); err != nil { 131 return nil, err 132 } 133 out, err := exec.Command("ssh-keygen", 134 "-yf", 135 tmpKey.Name(), 136 "-e", 137 "-m", 138 "PKCS8").Output() 139 if err != nil { 140 return out, fmt.Errorf("Verify private key permissions, try chmod 0400 %s, %s", key, err) 141 } 142 return out, err 143 } 144 } 145 146 pubKey, err := GetPublicKeyPem(b.String()) 147 if err != nil { 148 return nil, err 149 } 150 return pubKey, nil 151} 152 153// GetPublicKeyPem return the public key in PEM PKCS8 154func GetPublicKeyPem(key string) ([]byte, error) { 155 pubKey, err := DecodePublicKey(key) 156 if err != nil { 157 return nil, fmt.Errorf("Use a public ssh key: %s", err) 158 } 159 160 pkix, err := x509.MarshalPKIXPublicKey(pubKey) 161 if err != nil { 162 return nil, err 163 } 164 165 return pem.EncodeToMemory(&pem.Block{ 166 Type: "RSA PUBLIC KEY", 167 Bytes: pkix, 168 }), nil 169} 170