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}