forked from NYANDEV/forgejo
Implement #798 Flexible ssh-key input
It is now possible to input ssh keys in a number of formats: openssh, SSH2 or just the base64 encoded key.
This commit is contained in:
parent
9b0858b1ad
commit
6251626de4
2 changed files with 90 additions and 4 deletions
|
@ -6,6 +6,8 @@ package models
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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.
|
||||
func CheckPublicKeyString(content string) (bool, error) {
|
||||
content = strings.TrimRight(content, "\n\r")
|
||||
|
|
|
@ -325,10 +325,15 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
|||
return
|
||||
}
|
||||
|
||||
// Remove newline characters from form.KeyContent
|
||||
cleanContent := strings.Replace(form.Content, "\n", "", -1)
|
||||
// Parse openssh style string from form content
|
||||
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 {
|
||||
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
|
||||
} else {
|
||||
|
@ -341,7 +346,7 @@ func SettingsSSHKeysPost(ctx *middleware.Context, form auth.AddSSHKeyForm) {
|
|||
k := &models.PublicKey{
|
||||
OwnerId: ctx.User.Id,
|
||||
Name: form.SSHTitle,
|
||||
Content: cleanContent,
|
||||
Content: content,
|
||||
}
|
||||
if err := models.AddPublicKey(k); err != nil {
|
||||
if err == models.ErrKeyAlreadyExist {
|
||||
|
|
Loading…
Reference in a new issue