Merge pull request #825 from phsmit/ssh2_keys
Implement #798 Flexible ssh-key input
This commit is contained in:
commit
161774d4fb
|
@ -6,6 +6,8 @@ package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
@ -111,6 +113,85 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func extractTypeFromBase64Key(key string) (string, error) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(key)
|
||||||
|
if err != nil || len(b) < 4 {
|
||||||
|
return "", errors.New("Invalid key format")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyLength := int(binary.BigEndian.Uint32(b))
|
||||||
|
|
||||||
|
if len(b) < 4+keyLength {
|
||||||
|
return "", errors.New("Invalid key format")
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b[4 : 4+keyLength]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse any key string in openssh or ssh2 format to clean openssh string (rfc4253)
|
||||||
|
func ParseKeyString(content string) (string, error) {
|
||||||
|
|
||||||
|
// Transform all legal line endings to a single "\n"
|
||||||
|
s := strings.Replace(strings.Replace(strings.TrimSpace(content), "\r\n", "\n", -1), "\r", "\n", -1)
|
||||||
|
|
||||||
|
lines := strings.Split(s, "\n")
|
||||||
|
|
||||||
|
var keyType, keyContent, keyComment string
|
||||||
|
|
||||||
|
if len(lines) == 1 {
|
||||||
|
// Parse openssh format
|
||||||
|
parts := strings.Fields(lines[0])
|
||||||
|
switch len(parts) {
|
||||||
|
case 0:
|
||||||
|
return "", errors.New("Empty key")
|
||||||
|
case 1:
|
||||||
|
keyContent = parts[0]
|
||||||
|
case 2:
|
||||||
|
keyType = parts[0]
|
||||||
|
keyContent = parts[1]
|
||||||
|
default:
|
||||||
|
keyType = parts[0]
|
||||||
|
keyContent = parts[1]
|
||||||
|
keyComment = parts[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If keyType is not given, extract it from content. If given, validate it
|
||||||
|
if len(keyType) == 0 {
|
||||||
|
if t, err := extractTypeFromBase64Key(keyContent); err == nil {
|
||||||
|
keyType = t
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if t, err := extractTypeFromBase64Key(keyContent); err != nil || keyType != t {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Parse SSH2 file format.
|
||||||
|
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 = keyContent + line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, err := extractTypeFromBase64Key(keyContent); err == nil {
|
||||||
|
keyType = t
|
||||||
|
} else {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keyType + " " + keyContent + " " + keyComment, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
|
// CheckPublicKeyString checks if the given public key string is recognized by SSH.
|
||||||
func CheckPublicKeyString(content string) (bool, error) {
|
func CheckPublicKeyString(content string) (bool, error) {
|
||||||
content = strings.TrimRight(content, "\n\r")
|
content = strings.TrimRight(content, "\n\r")
|
||||||
|
|
|
@ -325,10 +325,15 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove newline characters from form.KeyContent
|
// Parse openssh style string from form content
|
||||||
cleanContent := strings.Replace(form.Content, "\n", "", -1)
|
content, err := models.ParseKeyString(form.Content)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
|
||||||
|
ctx.Redirect(setting.AppSubUrl + "/user/settings/ssh")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ok, err := models.CheckPublicKeyString(cleanContent); !ok {
|
if ok, err := models.CheckPublicKeyString(content); !ok {
|
||||||
if err == models.ErrKeyUnableVerify {
|
if err == models.ErrKeyUnableVerify {
|
||||||
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
||||||
} else {
|
} else {
|
||||||
|
@ -341,7 +346,7 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
||||||
k := &models.PublicKey{
|
k := &models.PublicKey{
|
||||||
OwnerId: ctx.User.Id,
|
OwnerId: ctx.User.Id,
|
||||||
Name: form.SSHTitle,
|
Name: form.SSHTitle,
|
||||||
Content: cleanContent,
|
Content: content,
|
||||||
}
|
}
|
||||||
if err := models.AddPublicKey(k); err != nil {
|
if err := models.AddPublicKey(k); err != nil {
|
||||||
if err == models.ErrKeyAlreadyExist {
|
if err == models.ErrKeyAlreadyExist {
|
||||||
|
|
Loading…
Reference in New Issue