Skip to content

Commit 18c0e9c

Browse files
authored
Make PKCS8, PEM and SSH2 keys work (#7600)
* Make PEM and SSH2 keys work * add ssh2 testcases and PEM cases - and fix PEM * Add final test to parse the proposed key
1 parent 6485962 commit 18c0e9c

File tree

2 files changed

+125
-27
lines changed

2 files changed

+125
-27
lines changed

models/ssh_key.go

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ package models
77

88
import (
99
"bufio"
10+
"crypto/rsa"
11+
"crypto/x509"
12+
"encoding/asn1"
1013
"encoding/base64"
1114
"encoding/binary"
15+
"encoding/pem"
1216
"errors"
1317
"fmt"
1418
"io/ioutil"
@@ -94,14 +98,68 @@ func extractTypeFromBase64Key(key string) (string, error) {
9498
return string(b[4 : 4+keyLength]), nil
9599
}
96100

101+
const ssh2keyStart = "---- BEGIN SSH2 PUBLIC KEY ----"
102+
97103
// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253).
98104
func parseKeyString(content string) (string, error) {
99105
// remove whitespace at start and end
100106
content = strings.TrimSpace(content)
101107

102108
var keyType, keyContent, keyComment string
103109

104-
if !strings.Contains(content, "-----BEGIN") {
110+
if content[:len(ssh2keyStart)] == ssh2keyStart {
111+
// Parse SSH2 file format.
112+
113+
// Transform all legal line endings to a single "\n".
114+
content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
115+
116+
lines := strings.Split(content, "\n")
117+
continuationLine := false
118+
119+
for _, line := range lines {
120+
// Skip lines that:
121+
// 1) are a continuation of the previous line,
122+
// 2) contain ":" as that are comment lines
123+
// 3) contain "-" as that are begin and end tags
124+
if continuationLine || strings.ContainsAny(line, ":-") {
125+
continuationLine = strings.HasSuffix(line, "\\")
126+
} else {
127+
keyContent += line
128+
}
129+
}
130+
131+
t, err := extractTypeFromBase64Key(keyContent)
132+
if err != nil {
133+
return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
134+
}
135+
keyType = t
136+
} else {
137+
if strings.Contains(content, "-----BEGIN") {
138+
// Convert PEM Keys to OpenSSH format
139+
// Transform all legal line endings to a single "\n".
140+
content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
141+
142+
block, _ := pem.Decode([]byte(content))
143+
if block == nil {
144+
return "", fmt.Errorf("failed to parse PEM block containing the public key")
145+
}
146+
147+
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
148+
if err != nil {
149+
var pk rsa.PublicKey
150+
_, err2 := asn1.Unmarshal(block.Bytes, &pk)
151+
if err2 != nil {
152+
return "", fmt.Errorf("failed to parse DER encoded public key as either PKIX or PEM RSA Key: %v %v", err, err2)
153+
}
154+
pub = &pk
155+
}
156+
157+
sshKey, err := ssh.NewPublicKey(pub)
158+
if err != nil {
159+
return "", fmt.Errorf("unable to convert to ssh public key: %v", err)
160+
}
161+
content = string(ssh.MarshalAuthorizedKey(sshKey))
162+
}
105163
// Parse OpenSSH format.
106164

107165
// Remove all newlines
@@ -132,32 +190,11 @@ func parseKeyString(content string) (string, error) {
132190
} else if keyType != t {
133191
return "", fmt.Errorf("key type and content does not match: %s - %s", keyType, t)
134192
}
135-
} else {
136-
// Parse SSH2 file format.
137-
138-
// Transform all legal line endings to a single "\n".
139-
content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
140-
141-
lines := strings.Split(content, "\n")
142-
continuationLine := false
143-
144-
for _, line := range lines {
145-
// Skip lines that:
146-
// 1) are a continuation of the previous line,
147-
// 2) contain ":" as that are comment lines
148-
// 3) contain "-" as that are begin and end tags
149-
if continuationLine || strings.ContainsAny(line, ":-") {
150-
continuationLine = strings.HasSuffix(line, "\\")
151-
} else {
152-
keyContent += line
153-
}
154-
}
155-
156-
t, err := extractTypeFromBase64Key(keyContent)
157-
if err != nil {
158-
return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
159-
}
160-
keyType = t
193+
}
194+
// Finally we need to check whether we can actually read the proposed key:
195+
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyType + " " + keyContent + " " + keyComment))
196+
if err != nil {
197+
return "", fmt.Errorf("invalid ssh public key: %v", err)
161198
}
162199
return keyType + " " + keyContent + " " + keyComment, nil
163200
}

models/ssh_key_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,67 @@ func Test_CheckPublicKeyString(t *testing.T) {
6666
{"ssh-rsa AAAAB3NzaC1yc2EA\r\nAAADAQABAAAAgQDAu7tvI\nvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+\r\nBZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvW\nqIwC4prx/WVk2wLTJjzBAhyNx\r\nfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\r\n\r\n"},
6767
{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf"},
6868
{"\r\nssh-ed25519 \r\nAAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf\r\n\r\n"},
69+
{`---- BEGIN SSH2 PUBLIC KEY ----
70+
Comment: "1024-bit DSA, converted by andrew@phaedra from OpenSSH"
71+
AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3
72+
ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/
73+
YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL
74+
+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8
75+
A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb
76+
0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgP
77+
aguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxc
78+
Ns4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd6429
79+
82daopE7zQ/NPAnJfag=
80+
---- END SSH2 PUBLIC KEY ----
81+
`},
82+
{`---- BEGIN SSH2 PUBLIC KEY ----
83+
Comment: "1024-bit RSA, converted by andrew@phaedra from OpenSSH"
84+
AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxB
85+
cQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIV
86+
j0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ==
87+
---- END SSH2 PUBLIC KEY ----
88+
`},
89+
{`-----BEGIN RSA PUBLIC KEY-----
90+
MIGJAoGBAMC7u28i9fpketFe5k1+RHdcsdKy4Ir1mfdfnyXEFxDO6jnFmAHq9HDC
91+
b9C0m4X7Nk+1jmGxAgsEuYX4FnlakpmnWMF5KMfYbuXF632Rtwf6QhWPS08USjIo
92+
j3C9aojALimvH9ZWTbAtMmPMECHI3F8SrsL0J6Jf2lARsSol+QoJAgMBAAE=
93+
-----END RSA PUBLIC KEY-----
94+
`},
95+
{`-----BEGIN PUBLIC KEY-----
96+
MIIBtzCCASsGByqGSM44BAEwggEeAoGBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn5
97+
9NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczW
98+
OVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQse
99+
cdKktISwTakzAhUAsyrDtiYTSpS/sMMCxjnC336AJpMCgYBpK7/3xvduajLBD/9v
100+
ASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g
101+
+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTL
102+
zIyMtkHf/IrPCwlM+pV/M/96YgOBhQACgYEAqQcGn9CKgzgPaguIZooTAOQdvBLM
103+
I5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2
104+
PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982da
105+
opE7zQ/NPAnJfag=
106+
-----END PUBLIC KEY-----
107+
`},
108+
{`-----BEGIN PUBLIC KEY-----
109+
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAu7tvIvX6ZHrRXuZNfkR3XLHS
110+
suCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jB
111+
eSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C
112+
9CeiX9pQEbEqJfkKCQIDAQAB
113+
-----END PUBLIC KEY-----
114+
`},
115+
{`-----BEGIN PUBLIC KEY-----
116+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGV4ftTgVMEh/Q+OcE2s
117+
RK0CDfSKAvcZezCiZKr077+juUUfWFvyCvRW3414F7KaWBobAmaNYRTjrFxzJ3zj
118+
karv8TA8eMj7sryqcOC3jxHIOEw4qWgxbsW1jqnPwVGUWXF7uNUAFnwy6yJ8LJbV
119+
mR0nhu4Y4aWnJeBa1b/VdaUujnOUNTccRM087jS0v/HYma05v2AEEP/gfps1iN8x
120+
LReJomY4wJY1ndS0wT71Nt3dvQ3AZphWoXGeONV2bE3gMBsRv0Oo/DYDV4/VsTHl
121+
sMV1do3gF/xAUqWawlZQkNcibME+sQqfE7gZ04hlmDATU2zmbzwuHtFiNv8mVv7O
122+
RQIDAQAB
123+
-----END PUBLIC KEY-----
124+
`},
125+
{`---- BEGIN SSH2 PUBLIC KEY ----
126+
Comment: "256-bit ED25519, converted by andrew@phaedra from OpenSSH"
127+
AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf
128+
---- END SSH2 PUBLIC KEY ----
129+
`},
69130
} {
70131
_, err := CheckPublicKeyString(test.content)
71132
assert.NoError(t, err)

0 commit comments

Comments
 (0)