Raw
 1package openssh
 2
 3import (
 4	"bytes"
 5	"crypto/rsa"
 6	"strings"
 7
 8	"golang.org/x/crypto/ssh"
 9
10	"tug/internal/platform/sshkey"
11)
12
13func Parse(text string) (sshkey.Key, error) {
14	line := strings.TrimSpace(text)
15	switch {
16	case len(line) > sshkey.MaxLineBytes:
17		return sshkey.Key{}, sshkey.ErrNotAKey
18	case strings.ContainsAny(line, "\r\n"):
19		return sshkey.Key{}, sshkey.ErrNotOneLine
20	case line == "" || line[0] == '#':
21		return sshkey.Key{}, sshkey.ErrNotAKey
22	}
23	pub, comment, options, rest, err := ssh.ParseAuthorizedKey([]byte(line))
24	switch {
25	case err != nil:
26		return sshkey.Key{}, sshkey.ErrNotAKey
27	case len(rest) > 0:
28		return sshkey.Key{}, sshkey.ErrNotOneLine
29	case len(options) > 0:
30		return sshkey.Key{}, sshkey.ErrOptions
31	}
32	k, err := accept(pub)
33	if err != nil {
34		return sshkey.Key{}, err
35	}
36	k.Comment = comment
37	return k, nil
38}
39
40func FromData(keyType string, data []byte) (sshkey.Key, error) {
41	pub, err := ssh.ParsePublicKey(data)
42	if err != nil || pub.Type() != keyType || !bytes.Equal(pub.Marshal(), data) {
43		return sshkey.Key{}, sshkey.ErrNotAKey
44	}
45	return accept(pub)
46}
47
48func accept(pub ssh.PublicKey) (sshkey.Key, error) {
49	if _, ok := pub.(*ssh.Certificate); ok || strings.HasSuffix(pub.Type(), "-cert-v01@openssh.com") {
50		return sshkey.Key{}, sshkey.ErrCertificate
51	}
52	data := pub.Marshal()
53	if err := sshkey.Check(pub.Type(), data); err != nil {
54		return sshkey.Key{}, err
55	}
56	if pub.Type() == ssh.KeyAlgoRSA {
57		crypto, ok := pub.(ssh.CryptoPublicKey)
58		if !ok {
59			return sshkey.Key{}, sshkey.ErrNotAKey
60		}
61		rsaKey, ok := crypto.CryptoPublicKey().(*rsa.PublicKey)
62		if !ok {
63			return sshkey.Key{}, sshkey.ErrNotAKey
64		}
65		if rsaKey.N.BitLen() < sshkey.MinRSABits {
66			return sshkey.Key{}, sshkey.ErrShortRSA
67		}
68	}
69	return sshkey.Key{Type: pub.Type(), Data: data, Fingerprint: sshkey.Fingerprint(data)}, nil
70}