@@ -7,8 +7,12 @@ package models
77
88import (
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).
98104func 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}
0 commit comments