mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-11-07 10:49:06 +01:00
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
This commit is contained in:
parent
6485962dd5
commit
18c0e9c2a9
2 changed files with 125 additions and 27 deletions
|
@ -7,8 +7,12 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/asn1"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -94,6 +98,8 @@ func extractTypeFromBase64Key(key string) (string, error) {
|
||||||
return string(b[4 : 4+keyLength]), nil
|
return string(b[4 : 4+keyLength]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ssh2keyStart = "---- BEGIN SSH2 PUBLIC KEY ----"
|
||||||
|
|
||||||
// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253).
|
// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253).
|
||||||
func parseKeyString(content string) (string, error) {
|
func parseKeyString(content string) (string, error) {
|
||||||
// remove whitespace at start and end
|
// remove whitespace at start and end
|
||||||
|
@ -101,7 +107,59 @@ func parseKeyString(content string) (string, error) {
|
||||||
|
|
||||||
var keyType, keyContent, keyComment string
|
var keyType, keyContent, keyComment string
|
||||||
|
|
||||||
if !strings.Contains(content, "-----BEGIN") {
|
if content[:len(ssh2keyStart)] == ssh2keyStart {
|
||||||
|
// Parse SSH2 file format.
|
||||||
|
|
||||||
|
// Transform all legal line endings to a single "\n".
|
||||||
|
content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
|
||||||
|
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
continuationLine := false
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
// Skip lines that:
|
||||||
|
// 1) are a continuation of the previous line,
|
||||||
|
// 2) contain ":" as that are comment lines
|
||||||
|
// 3) contain "-" as that are begin and end tags
|
||||||
|
if continuationLine || strings.ContainsAny(line, ":-") {
|
||||||
|
continuationLine = strings.HasSuffix(line, "\\")
|
||||||
|
} else {
|
||||||
|
keyContent += line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := extractTypeFromBase64Key(keyContent)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
|
||||||
|
}
|
||||||
|
keyType = t
|
||||||
|
} else {
|
||||||
|
if strings.Contains(content, "-----BEGIN") {
|
||||||
|
// Convert PEM Keys to OpenSSH format
|
||||||
|
// Transform all legal line endings to a single "\n".
|
||||||
|
content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
|
||||||
|
|
||||||
|
block, _ := pem.Decode([]byte(content))
|
||||||
|
if block == nil {
|
||||||
|
return "", fmt.Errorf("failed to parse PEM block containing the public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
var pk rsa.PublicKey
|
||||||
|
_, err2 := asn1.Unmarshal(block.Bytes, &pk)
|
||||||
|
if err2 != nil {
|
||||||
|
return "", fmt.Errorf("failed to parse DER encoded public key as either PKIX or PEM RSA Key: %v %v", err, err2)
|
||||||
|
}
|
||||||
|
pub = &pk
|
||||||
|
}
|
||||||
|
|
||||||
|
sshKey, err := ssh.NewPublicKey(pub)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to convert to ssh public key: %v", err)
|
||||||
|
}
|
||||||
|
content = string(ssh.MarshalAuthorizedKey(sshKey))
|
||||||
|
}
|
||||||
// Parse OpenSSH format.
|
// Parse OpenSSH format.
|
||||||
|
|
||||||
// Remove all newlines
|
// Remove all newlines
|
||||||
|
@ -132,32 +190,11 @@ func parseKeyString(content string) (string, error) {
|
||||||
} else if keyType != t {
|
} else if keyType != t {
|
||||||
return "", fmt.Errorf("key type and content does not match: %s - %s", keyType, t)
|
return "", fmt.Errorf("key type and content does not match: %s - %s", keyType, t)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Parse SSH2 file format.
|
|
||||||
|
|
||||||
// Transform all legal line endings to a single "\n".
|
|
||||||
content = strings.NewReplacer("\r\n", "\n", "\r", "\n").Replace(content)
|
|
||||||
|
|
||||||
lines := strings.Split(content, "\n")
|
|
||||||
continuationLine := false
|
|
||||||
|
|
||||||
for _, line := range lines {
|
|
||||||
// Skip lines that:
|
|
||||||
// 1) are a continuation of the previous line,
|
|
||||||
// 2) contain ":" as that are comment lines
|
|
||||||
// 3) contain "-" as that are begin and end tags
|
|
||||||
if continuationLine || strings.ContainsAny(line, ":-") {
|
|
||||||
continuationLine = strings.HasSuffix(line, "\\")
|
|
||||||
} else {
|
|
||||||
keyContent += line
|
|
||||||
}
|
}
|
||||||
}
|
// Finally we need to check whether we can actually read the proposed key:
|
||||||
|
_, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyType + " " + keyContent + " " + keyComment))
|
||||||
t, err := extractTypeFromBase64Key(keyContent)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("extractTypeFromBase64Key: %v", err)
|
return "", fmt.Errorf("invalid ssh public key: %v", err)
|
||||||
}
|
|
||||||
keyType = t
|
|
||||||
}
|
}
|
||||||
return keyType + " " + keyContent + " " + keyComment, nil
|
return keyType + " " + keyContent + " " + keyComment, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,67 @@ func Test_CheckPublicKeyString(t *testing.T) {
|
||||||
{"ssh-rsa AAAAB3NzaC1yc2EA\r\nAAADAQABAAAAgQDAu7tvI\nvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+\r\nBZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvW\nqIwC4prx/WVk2wLTJjzBAhyNx\r\nfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\r\n\r\n"},
|
{"ssh-rsa AAAAB3NzaC1yc2EA\r\nAAADAQABAAAAgQDAu7tvI\nvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+\r\nBZ5WpKZp1jBeSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvW\nqIwC4prx/WVk2wLTJjzBAhyNx\r\nfEq7C9CeiX9pQEbEqJfkKCQ== nocomment\r\n\r\n"},
|
||||||
{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf"},
|
{"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf"},
|
||||||
{"\r\nssh-ed25519 \r\nAAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf\r\n\r\n"},
|
{"\r\nssh-ed25519 \r\nAAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf\r\n\r\n"},
|
||||||
|
{`---- BEGIN SSH2 PUBLIC KEY ----
|
||||||
|
Comment: "1024-bit DSA, converted by andrew@phaedra from OpenSSH"
|
||||||
|
AAAAB3NzaC1kc3MAAACBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn59NriyboW2x/DZuYAz3
|
||||||
|
ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczWOVsaszBZSl90q8UnWlSH6P+/
|
||||||
|
YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQsecdKktISwTakzAAAAFQCzKsO2JhNKlL
|
||||||
|
+wwwLGOcLffoAmkwAAAIBpK7/3xvduajLBD/9vASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8
|
||||||
|
A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb
|
||||||
|
0N6s08NZysLzvj0N+ZC/FnhKTLzIyMtkHf/IrPCwlM+pV/M/96YgAAAIEAqQcGn9CKgzgP
|
||||||
|
aguIZooTAOQdvBLMI5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxc
|
||||||
|
Ns4BeVKhy2PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd6429
|
||||||
|
82daopE7zQ/NPAnJfag=
|
||||||
|
---- END SSH2 PUBLIC KEY ----
|
||||||
|
`},
|
||||||
|
{`---- BEGIN SSH2 PUBLIC KEY ----
|
||||||
|
Comment: "1024-bit RSA, converted by andrew@phaedra from OpenSSH"
|
||||||
|
AAAAB3NzaC1yc2EAAAADAQABAAAAgQDAu7tvIvX6ZHrRXuZNfkR3XLHSsuCK9Zn3X58lxB
|
||||||
|
cQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jBeSjH2G7lxet9kbcH+kIV
|
||||||
|
j0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C9CeiX9pQEbEqJfkKCQ==
|
||||||
|
---- END SSH2 PUBLIC KEY ----
|
||||||
|
`},
|
||||||
|
{`-----BEGIN RSA PUBLIC KEY-----
|
||||||
|
MIGJAoGBAMC7u28i9fpketFe5k1+RHdcsdKy4Ir1mfdfnyXEFxDO6jnFmAHq9HDC
|
||||||
|
b9C0m4X7Nk+1jmGxAgsEuYX4FnlakpmnWMF5KMfYbuXF632Rtwf6QhWPS08USjIo
|
||||||
|
j3C9aojALimvH9ZWTbAtMmPMECHI3F8SrsL0J6Jf2lARsSol+QoJAgMBAAE=
|
||||||
|
-----END RSA PUBLIC KEY-----
|
||||||
|
`},
|
||||||
|
{`-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBtzCCASsGByqGSM44BAEwggEeAoGBAOChCC7lf6Uo9n7BmZ6M8St19PZf4Tn5
|
||||||
|
9NriyboW2x/DZuYAz3ibZ2OkQ3S0SqDIa0HXSEJ1zaExQdmbO+Ux/wsytWZmCczW
|
||||||
|
OVsaszBZSl90q8UnWlSH6P+/YA+RWJm5SFtuV9PtGIhyZgoNuz5kBQ7K139wuQse
|
||||||
|
cdKktISwTakzAhUAsyrDtiYTSpS/sMMCxjnC336AJpMCgYBpK7/3xvduajLBD/9v
|
||||||
|
ASqBQIHrgK2J+wiQnIb/Wzy0UsVmvfn8A+udRbBo+csM8xrSnlnlJnjkJS3qiM5g
|
||||||
|
+eTwsLIV1IdKPEwmwB+VcP53Cw6lSyWyJcvhFb0N6s08NZysLzvj0N+ZC/FnhKTL
|
||||||
|
zIyMtkHf/IrPCwlM+pV/M/96YgOBhQACgYEAqQcGn9CKgzgPaguIZooTAOQdvBLM
|
||||||
|
I5y0bQjOW6734XOpqQGf/Kra90wpoasLKZjSYKNPjE+FRUOrStLrxcNs4BeVKhy2
|
||||||
|
PYTRnybfYVk1/dmKgH6P1YSRONsGKvTsH6c5IyCRG0ncCgYeF8tXppyd642982da
|
||||||
|
opE7zQ/NPAnJfag=
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`},
|
||||||
|
{`-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAu7tvIvX6ZHrRXuZNfkR3XLHS
|
||||||
|
suCK9Zn3X58lxBcQzuo5xZgB6vRwwm/QtJuF+zZPtY5hsQILBLmF+BZ5WpKZp1jB
|
||||||
|
eSjH2G7lxet9kbcH+kIVj0tPFEoyKI9wvWqIwC4prx/WVk2wLTJjzBAhyNxfEq7C
|
||||||
|
9CeiX9pQEbEqJfkKCQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`},
|
||||||
|
{`-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzGV4ftTgVMEh/Q+OcE2s
|
||||||
|
RK0CDfSKAvcZezCiZKr077+juUUfWFvyCvRW3414F7KaWBobAmaNYRTjrFxzJ3zj
|
||||||
|
karv8TA8eMj7sryqcOC3jxHIOEw4qWgxbsW1jqnPwVGUWXF7uNUAFnwy6yJ8LJbV
|
||||||
|
mR0nhu4Y4aWnJeBa1b/VdaUujnOUNTccRM087jS0v/HYma05v2AEEP/gfps1iN8x
|
||||||
|
LReJomY4wJY1ndS0wT71Nt3dvQ3AZphWoXGeONV2bE3gMBsRv0Oo/DYDV4/VsTHl
|
||||||
|
sMV1do3gF/xAUqWawlZQkNcibME+sQqfE7gZ04hlmDATU2zmbzwuHtFiNv8mVv7O
|
||||||
|
RQIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`},
|
||||||
|
{`---- BEGIN SSH2 PUBLIC KEY ----
|
||||||
|
Comment: "256-bit ED25519, converted by andrew@phaedra from OpenSSH"
|
||||||
|
AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf
|
||||||
|
---- END SSH2 PUBLIC KEY ----
|
||||||
|
`},
|
||||||
} {
|
} {
|
||||||
_, err := CheckPublicKeyString(test.content)
|
_, err := CheckPublicKeyString(test.content)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
Loading…
Reference in a new issue