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