@@ -7,8 +7,12 @@ package models
7
7
8
8
import (
9
9
"bufio"
10
+ "crypto/rsa"
11
+ "crypto/x509"
12
+ "encoding/asn1"
10
13
"encoding/base64"
11
14
"encoding/binary"
15
+ "encoding/pem"
12
16
"errors"
13
17
"fmt"
14
18
"io/ioutil"
@@ -94,14 +98,68 @@ func extractTypeFromBase64Key(key string) (string, error) {
94
98
return string (b [4 : 4 + keyLength ]), nil
95
99
}
96
100
101
+ const ssh2keyStart = "---- BEGIN SSH2 PUBLIC KEY ----"
102
+
97
103
// parseKeyString parses any key string in OpenSSH or SSH2 format to clean OpenSSH string (RFC4253).
98
104
func parseKeyString (content string ) (string , error ) {
99
105
// remove whitespace at start and end
100
106
content = strings .TrimSpace (content )
101
107
102
108
var keyType , keyContent , keyComment string
103
109
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
+ }
105
163
// Parse OpenSSH format.
106
164
107
165
// Remove all newlines
@@ -132,32 +190,11 @@ func parseKeyString(content string) (string, error) {
132
190
} else if keyType != t {
133
191
return "" , fmt .Errorf ("key type and content does not match: %s - %s" , keyType , t )
134
192
}
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 )
161
198
}
162
199
return keyType + " " + keyContent + " " + keyComment , nil
163
200
}
0 commit comments