Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 1 | // Copyright 2012 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | package ssh |
| 6 | |
| 7 | import ( |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 8 | "bytes" |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 9 | "crypto" |
Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 10 | "crypto/dsa" |
Jonathan Pittman | 94c9f92 | 2012-12-14 05:52:19 +1100 | [diff] [blame] | 11 | "crypto/ecdsa" |
| 12 | "crypto/elliptic" |
Ryuzo Yamamoto | ede567c | 2016-11-04 15:41:44 -0400 | [diff] [blame] | 13 | "crypto/md5" |
Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 14 | "crypto/rsa" |
Ryuzo Yamamoto | ede567c | 2016-11-04 15:41:44 -0400 | [diff] [blame] | 15 | "crypto/sha256" |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 16 | "crypto/x509" |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 17 | "encoding/asn1" |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 18 | "encoding/base64" |
Ryuzo Yamamoto | ede567c | 2016-11-04 15:41:44 -0400 | [diff] [blame] | 19 | "encoding/hex" |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 20 | "encoding/pem" |
| 21 | "errors" |
| 22 | "fmt" |
| 23 | "io" |
Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 24 | "math/big" |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 25 | "strings" |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 26 | |
| 27 | "golang.org/x/crypto/ed25519" |
Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 28 | ) |
| 29 | |
Jonathan Pittman | d95b283 | 2012-12-16 10:47:10 -0500 | [diff] [blame] | 30 | // These constants represent the algorithm names for key types supported by this |
| 31 | // package. |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 32 | const ( |
Jonathan Pittman | 54c65ae | 2012-12-14 10:11:06 -0500 | [diff] [blame] | 33 | KeyAlgoRSA = "ssh-rsa" |
| 34 | KeyAlgoDSA = "ssh-dss" |
| 35 | KeyAlgoECDSA256 = "ecdsa-sha2-nistp256" |
| 36 | KeyAlgoECDSA384 = "ecdsa-sha2-nistp384" |
| 37 | KeyAlgoECDSA521 = "ecdsa-sha2-nistp521" |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 38 | KeyAlgoED25519 = "ssh-ed25519" |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 39 | ) |
| 40 | |
JP Sugarbroad | 15d8abf | 2013-10-09 12:56:09 -0400 | [diff] [blame] | 41 | // parsePubKey parses a public key of the given algorithm. |
| 42 | // Use ParsePublicKey for keys with prepended algorithm. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 43 | func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) { |
JP Sugarbroad | 15d8abf | 2013-10-09 12:56:09 -0400 | [diff] [blame] | 44 | switch algo { |
Jonathan Pittman | 54c65ae | 2012-12-14 10:11:06 -0500 | [diff] [blame] | 45 | case KeyAlgoRSA: |
Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 46 | return parseRSA(in) |
Jonathan Pittman | 54c65ae | 2012-12-14 10:11:06 -0500 | [diff] [blame] | 47 | case KeyAlgoDSA: |
Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 48 | return parseDSA(in) |
Jonathan Pittman | 54c65ae | 2012-12-14 10:11:06 -0500 | [diff] [blame] | 49 | case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521: |
Jonathan Pittman | 94c9f92 | 2012-12-14 05:52:19 +1100 | [diff] [blame] | 50 | return parseECDSA(in) |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 51 | case KeyAlgoED25519: |
| 52 | return parseED25519(in) |
| 53 | case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoED25519v01: |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 54 | cert, err := parseCert(in, certToPrivAlgo(algo)) |
| 55 | if err != nil { |
| 56 | return nil, nil, err |
| 57 | } |
| 58 | return cert, nil, nil |
Jonathan Pittman | 9b05c27 | 2012-02-24 12:52:06 -0500 | [diff] [blame] | 59 | } |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 60 | return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo) |
Jonathan Pittman | 94c9f92 | 2012-12-14 05:52:19 +1100 | [diff] [blame] | 61 | } |
| 62 | |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 63 | // parseAuthorizedKey parses a public key in OpenSSH authorized_keys format |
| 64 | // (see sshd(8) manual page) once the options and key type fields have been |
| 65 | // removed. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 66 | func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) { |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 67 | in = bytes.TrimSpace(in) |
| 68 | |
| 69 | i := bytes.IndexAny(in, " \t") |
| 70 | if i == -1 { |
| 71 | i = len(in) |
| 72 | } |
| 73 | base64Key := in[:i] |
| 74 | |
| 75 | key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key))) |
| 76 | n, err := base64.StdEncoding.Decode(key, base64Key) |
| 77 | if err != nil { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 78 | return nil, "", err |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 79 | } |
| 80 | key = key[:n] |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 81 | out, err = ParsePublicKey(key) |
| 82 | if err != nil { |
| 83 | return nil, "", err |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 84 | } |
| 85 | comment = string(bytes.TrimSpace(in[i:])) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 86 | return out, comment, nil |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 87 | } |
| 88 | |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 89 | // ParseKnownHosts parses an entry in the format of the known_hosts file. |
| 90 | // |
| 91 | // The known_hosts format is documented in the sshd(8) manual page. This |
| 92 | // function will parse a single entry from in. On successful return, marker |
| 93 | // will contain the optional marker value (i.e. "cert-authority" or "revoked") |
| 94 | // or else be empty, hosts will contain the hosts that this entry matches, |
| 95 | // pubKey will contain the public key and comment will contain any trailing |
| 96 | // comment at the end of the line. See the sshd(8) manual page for the various |
| 97 | // forms that a host string can take. |
| 98 | // |
| 99 | // The unparsed remainder of the input will be returned in rest. This function |
| 100 | // can be called repeatedly to parse multiple entries. |
| 101 | // |
| 102 | // If no entries were found in the input then err will be io.EOF. Otherwise a |
| 103 | // non-nil err value indicates a parse error. |
| 104 | func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey, comment string, rest []byte, err error) { |
| 105 | for len(in) > 0 { |
| 106 | end := bytes.IndexByte(in, '\n') |
| 107 | if end != -1 { |
| 108 | rest = in[end+1:] |
| 109 | in = in[:end] |
| 110 | } else { |
| 111 | rest = nil |
| 112 | } |
| 113 | |
| 114 | end = bytes.IndexByte(in, '\r') |
| 115 | if end != -1 { |
| 116 | in = in[:end] |
| 117 | } |
| 118 | |
| 119 | in = bytes.TrimSpace(in) |
| 120 | if len(in) == 0 || in[0] == '#' { |
| 121 | in = rest |
| 122 | continue |
| 123 | } |
| 124 | |
| 125 | i := bytes.IndexAny(in, " \t") |
| 126 | if i == -1 { |
| 127 | in = rest |
| 128 | continue |
| 129 | } |
| 130 | |
Martin Hamrle | 077efaa | 2016-07-06 09:13:05 +0200 | [diff] [blame] | 131 | // Strip out the beginning of the known_host key. |
Peter Moody | 3760e01 | 2015-12-23 13:51:54 -0800 | [diff] [blame] | 132 | // This is either an optional marker or a (set of) hostname(s). |
| 133 | keyFields := bytes.Fields(in) |
| 134 | if len(keyFields) < 3 || len(keyFields) > 5 { |
| 135 | return "", nil, nil, "", nil, errors.New("ssh: invalid entry in known_hosts data") |
| 136 | } |
| 137 | |
| 138 | // keyFields[0] is either "@cert-authority", "@revoked" or a comma separated |
| 139 | // list of hosts |
| 140 | marker := "" |
| 141 | if keyFields[0][0] == '@' { |
| 142 | marker = string(keyFields[0][1:]) |
| 143 | keyFields = keyFields[1:] |
| 144 | } |
| 145 | |
| 146 | hosts := string(keyFields[0]) |
| 147 | // keyFields[1] contains the key type (e.g. “ssh-rsa”). |
| 148 | // However, that information is duplicated inside the |
| 149 | // base64-encoded key and so is ignored here. |
| 150 | |
| 151 | key := bytes.Join(keyFields[2:], []byte(" ")) |
| 152 | if pubKey, comment, err = parseAuthorizedKey(key); err != nil { |
| 153 | return "", nil, nil, "", nil, err |
| 154 | } |
| 155 | |
| 156 | return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil |
| 157 | } |
| 158 | |
| 159 | return "", nil, nil, "", nil, io.EOF |
| 160 | } |
| 161 | |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 162 | // ParseAuthorizedKeys parses a public key from an authorized_keys |
| 163 | // file used in OpenSSH according to the sshd(8) manual page. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 164 | func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) { |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 165 | for len(in) > 0 { |
| 166 | end := bytes.IndexByte(in, '\n') |
| 167 | if end != -1 { |
| 168 | rest = in[end+1:] |
| 169 | in = in[:end] |
| 170 | } else { |
| 171 | rest = nil |
| 172 | } |
| 173 | |
| 174 | end = bytes.IndexByte(in, '\r') |
| 175 | if end != -1 { |
| 176 | in = in[:end] |
| 177 | } |
| 178 | |
| 179 | in = bytes.TrimSpace(in) |
| 180 | if len(in) == 0 || in[0] == '#' { |
| 181 | in = rest |
| 182 | continue |
| 183 | } |
| 184 | |
| 185 | i := bytes.IndexAny(in, " \t") |
| 186 | if i == -1 { |
| 187 | in = rest |
| 188 | continue |
| 189 | } |
| 190 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 191 | if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { |
| 192 | return out, comment, options, rest, nil |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 193 | } |
| 194 | |
| 195 | // No key type recognised. Maybe there's an options field at |
| 196 | // the beginning. |
| 197 | var b byte |
| 198 | inQuote := false |
| 199 | var candidateOptions []string |
| 200 | optionStart := 0 |
| 201 | for i, b = range in { |
| 202 | isEnd := !inQuote && (b == ' ' || b == '\t') |
| 203 | if (b == ',' && !inQuote) || isEnd { |
| 204 | if i-optionStart > 0 { |
| 205 | candidateOptions = append(candidateOptions, string(in[optionStart:i])) |
| 206 | } |
| 207 | optionStart = i + 1 |
| 208 | } |
| 209 | if isEnd { |
| 210 | break |
| 211 | } |
| 212 | if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) { |
| 213 | inQuote = !inQuote |
| 214 | } |
| 215 | } |
| 216 | for i < len(in) && (in[i] == ' ' || in[i] == '\t') { |
| 217 | i++ |
| 218 | } |
| 219 | if i == len(in) { |
| 220 | // Invalid line: unmatched quote |
| 221 | in = rest |
| 222 | continue |
| 223 | } |
| 224 | |
| 225 | in = in[i:] |
| 226 | i = bytes.IndexAny(in, " \t") |
| 227 | if i == -1 { |
| 228 | in = rest |
| 229 | continue |
| 230 | } |
| 231 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 232 | if out, comment, err = parseAuthorizedKey(in[i:]); err == nil { |
Jonathan Pittman | 9112f50 | 2013-10-22 15:12:41 -0400 | [diff] [blame] | 233 | options = candidateOptions |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 234 | return out, comment, options, rest, nil |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 235 | } |
| 236 | |
| 237 | in = rest |
| 238 | continue |
| 239 | } |
| 240 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 241 | return nil, "", nil, nil, errors.New("ssh: no key found") |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 242 | } |
| 243 | |
| 244 | // ParsePublicKey parses an SSH public key formatted for use in |
JP Sugarbroad | 15d8abf | 2013-10-09 12:56:09 -0400 | [diff] [blame] | 245 | // the SSH wire protocol according to RFC 4253, section 6.6. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 246 | func ParsePublicKey(in []byte) (out PublicKey, err error) { |
JP Sugarbroad | 15d8abf | 2013-10-09 12:56:09 -0400 | [diff] [blame] | 247 | algo, in, ok := parseString(in) |
| 248 | if !ok { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 249 | return nil, errShortRead |
| 250 | } |
| 251 | var rest []byte |
| 252 | out, rest, err = parsePubKey(in, string(algo)) |
| 253 | if len(rest) > 0 { |
| 254 | return nil, errors.New("ssh: trailing junk in public key") |
JP Sugarbroad | 15d8abf | 2013-10-09 12:56:09 -0400 | [diff] [blame] | 255 | } |
| 256 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 257 | return out, err |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 258 | } |
| 259 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 260 | // MarshalAuthorizedKey serializes key for inclusion in an OpenSSH |
| 261 | // authorized_keys file. The return value ends with newline. |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 262 | func MarshalAuthorizedKey(key PublicKey) []byte { |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 263 | b := &bytes.Buffer{} |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 264 | b.WriteString(key.Type()) |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 265 | b.WriteByte(' ') |
| 266 | e := base64.NewEncoder(base64.StdEncoding, b) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 267 | e.Write(key.Marshal()) |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 268 | e.Close() |
| 269 | b.WriteByte('\n') |
| 270 | return b.Bytes() |
| 271 | } |
| 272 | |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 273 | // PublicKey is an abstraction of different types of public keys. |
| 274 | type PublicKey interface { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 275 | // Type returns the key's type, e.g. "ssh-rsa". |
| 276 | Type() string |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 277 | |
| 278 | // Marshal returns the serialized key data in SSH wire format, |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 279 | // with the name prefix. |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 280 | Marshal() []byte |
| 281 | |
| 282 | // Verify that sig is a signature on the given data using this |
| 283 | // key. This function will hash the data appropriately first. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 284 | Verify(data []byte, sig *Signature) error |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 285 | } |
| 286 | |
Martin Garton | 69267d2 | 2016-06-10 08:32:25 +0100 | [diff] [blame] | 287 | // CryptoPublicKey, if implemented by a PublicKey, |
| 288 | // returns the underlying crypto.PublicKey form of the key. |
| 289 | type CryptoPublicKey interface { |
| 290 | CryptoPublicKey() crypto.PublicKey |
| 291 | } |
| 292 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 293 | // A Signer can create signatures that verify against a public key. |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 294 | type Signer interface { |
| 295 | // PublicKey returns an associated PublicKey instance. |
| 296 | PublicKey() PublicKey |
| 297 | |
| 298 | // Sign returns raw signature for the given data. This method |
| 299 | // will apply the hash specified for the keytype to the data. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 300 | Sign(rand io.Reader, data []byte) (*Signature, error) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 301 | } |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 302 | |
| 303 | type rsaPublicKey rsa.PublicKey |
| 304 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 305 | func (r *rsaPublicKey) Type() string { |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 306 | return "ssh-rsa" |
| 307 | } |
| 308 | |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 309 | // parseRSA parses an RSA key according to RFC 4253, section 6.6. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 310 | func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { |
| 311 | var w struct { |
| 312 | E *big.Int |
| 313 | N *big.Int |
| 314 | Rest []byte `ssh:"rest"` |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 315 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 316 | if err := Unmarshal(in, &w); err != nil { |
| 317 | return nil, nil, err |
| 318 | } |
| 319 | |
| 320 | if w.E.BitLen() > 24 { |
| 321 | return nil, nil, errors.New("ssh: exponent too large") |
| 322 | } |
| 323 | e := w.E.Int64() |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 324 | if e < 3 || e&1 == 0 { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 325 | return nil, nil, errors.New("ssh: incorrect exponent") |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 326 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 327 | |
| 328 | var key rsa.PublicKey |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 329 | key.E = int(e) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 330 | key.N = w.N |
| 331 | return (*rsaPublicKey)(&key), w.Rest, nil |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 332 | } |
| 333 | |
| 334 | func (r *rsaPublicKey) Marshal() []byte { |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 335 | e := new(big.Int).SetInt64(int64(r.E)) |
Peter Moody | e84a34b | 2016-04-19 09:49:29 -0700 | [diff] [blame] | 336 | // RSA publickey struct layout should match the struct used by |
| 337 | // parseRSACert in the x/crypto/ssh/agent package. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 338 | wirekey := struct { |
| 339 | Name string |
| 340 | E *big.Int |
| 341 | N *big.Int |
| 342 | }{ |
| 343 | KeyAlgoRSA, |
| 344 | e, |
| 345 | r.N, |
| 346 | } |
| 347 | return Marshal(&wirekey) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 348 | } |
| 349 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 350 | func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error { |
| 351 | if sig.Format != r.Type() { |
| 352 | return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type()) |
| 353 | } |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 354 | h := crypto.SHA1.New() |
| 355 | h.Write(data) |
| 356 | digest := h.Sum(nil) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 357 | return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig.Blob) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 358 | } |
| 359 | |
Martin Garton | 69267d2 | 2016-06-10 08:32:25 +0100 | [diff] [blame] | 360 | func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey { |
| 361 | return (*rsa.PublicKey)(r) |
| 362 | } |
| 363 | |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 364 | type dsaPublicKey dsa.PublicKey |
| 365 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 366 | func (r *dsaPublicKey) Type() string { |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 367 | return "ssh-dss" |
| 368 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 369 | |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 370 | // parseDSA parses an DSA key according to RFC 4253, section 6.6. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 371 | func parseDSA(in []byte) (out PublicKey, rest []byte, err error) { |
| 372 | var w struct { |
| 373 | P, Q, G, Y *big.Int |
| 374 | Rest []byte `ssh:"rest"` |
| 375 | } |
| 376 | if err := Unmarshal(in, &w); err != nil { |
| 377 | return nil, nil, err |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 378 | } |
| 379 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 380 | key := &dsaPublicKey{ |
| 381 | Parameters: dsa.Parameters{ |
| 382 | P: w.P, |
| 383 | Q: w.Q, |
| 384 | G: w.G, |
| 385 | }, |
| 386 | Y: w.Y, |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 387 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 388 | return key, w.Rest, nil |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 389 | } |
| 390 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 391 | func (k *dsaPublicKey) Marshal() []byte { |
Peter Moody | e84a34b | 2016-04-19 09:49:29 -0700 | [diff] [blame] | 392 | // DSA publickey struct layout should match the struct used by |
| 393 | // parseDSACert in the x/crypto/ssh/agent package. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 394 | w := struct { |
| 395 | Name string |
| 396 | P, Q, G, Y *big.Int |
| 397 | }{ |
| 398 | k.Type(), |
| 399 | k.P, |
| 400 | k.Q, |
| 401 | k.G, |
| 402 | k.Y, |
| 403 | } |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 404 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 405 | return Marshal(&w) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 406 | } |
| 407 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 408 | func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error { |
| 409 | if sig.Format != k.Type() { |
| 410 | return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type()) |
| 411 | } |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 412 | h := crypto.SHA1.New() |
| 413 | h.Write(data) |
| 414 | digest := h.Sum(nil) |
| 415 | |
| 416 | // Per RFC 4253, section 6.6, |
| 417 | // The value for 'dss_signature_blob' is encoded as a string containing |
| 418 | // r, followed by s (which are 160-bit integers, without lengths or |
| 419 | // padding, unsigned, and in network byte order). |
| 420 | // For DSS purposes, sig.Blob should be exactly 40 bytes in length. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 421 | if len(sig.Blob) != 40 { |
| 422 | return errors.New("ssh: DSA signature parse error") |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 423 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 424 | r := new(big.Int).SetBytes(sig.Blob[:20]) |
| 425 | s := new(big.Int).SetBytes(sig.Blob[20:]) |
| 426 | if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) { |
| 427 | return nil |
| 428 | } |
| 429 | return errors.New("ssh: signature did not verify") |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 430 | } |
| 431 | |
Martin Garton | 69267d2 | 2016-06-10 08:32:25 +0100 | [diff] [blame] | 432 | func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey { |
| 433 | return (*dsa.PublicKey)(k) |
| 434 | } |
| 435 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 436 | type dsaPrivateKey struct { |
| 437 | *dsa.PrivateKey |
| 438 | } |
| 439 | |
| 440 | func (k *dsaPrivateKey) PublicKey() PublicKey { |
| 441 | return (*dsaPublicKey)(&k.PrivateKey.PublicKey) |
| 442 | } |
| 443 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 444 | func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 445 | h := crypto.SHA1.New() |
| 446 | h.Write(data) |
| 447 | digest := h.Sum(nil) |
| 448 | r, s, err := dsa.Sign(rand, k.PrivateKey, digest) |
| 449 | if err != nil { |
| 450 | return nil, err |
| 451 | } |
| 452 | |
| 453 | sig := make([]byte, 40) |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 454 | rb := r.Bytes() |
| 455 | sb := s.Bytes() |
| 456 | |
| 457 | copy(sig[20-len(rb):20], rb) |
| 458 | copy(sig[40-len(sb):], sb) |
| 459 | |
| 460 | return &Signature{ |
| 461 | Format: k.PublicKey().Type(), |
| 462 | Blob: sig, |
| 463 | }, nil |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 464 | } |
| 465 | |
| 466 | type ecdsaPublicKey ecdsa.PublicKey |
| 467 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 468 | func (key *ecdsaPublicKey) Type() string { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 469 | return "ecdsa-sha2-" + key.nistID() |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | func (key *ecdsaPublicKey) nistID() string { |
| 473 | switch key.Params().BitSize { |
| 474 | case 256: |
| 475 | return "nistp256" |
| 476 | case 384: |
| 477 | return "nistp384" |
| 478 | case 521: |
| 479 | return "nistp521" |
| 480 | } |
| 481 | panic("ssh: unsupported ecdsa key size") |
| 482 | } |
| 483 | |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 484 | type ed25519PublicKey ed25519.PublicKey |
| 485 | |
| 486 | func (key ed25519PublicKey) Type() string { |
| 487 | return KeyAlgoED25519 |
| 488 | } |
| 489 | |
| 490 | func parseED25519(in []byte) (out PublicKey, rest []byte, err error) { |
| 491 | var w struct { |
| 492 | KeyBytes []byte |
| 493 | Rest []byte `ssh:"rest"` |
| 494 | } |
| 495 | |
| 496 | if err := Unmarshal(in, &w); err != nil { |
| 497 | return nil, nil, err |
| 498 | } |
| 499 | |
| 500 | key := ed25519.PublicKey(w.KeyBytes) |
| 501 | |
| 502 | return (ed25519PublicKey)(key), w.Rest, nil |
| 503 | } |
| 504 | |
| 505 | func (key ed25519PublicKey) Marshal() []byte { |
| 506 | w := struct { |
| 507 | Name string |
| 508 | KeyBytes []byte |
| 509 | }{ |
| 510 | KeyAlgoED25519, |
| 511 | []byte(key), |
| 512 | } |
| 513 | return Marshal(&w) |
| 514 | } |
| 515 | |
| 516 | func (key ed25519PublicKey) Verify(b []byte, sig *Signature) error { |
| 517 | if sig.Format != key.Type() { |
| 518 | return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type()) |
| 519 | } |
| 520 | |
| 521 | edKey := (ed25519.PublicKey)(key) |
| 522 | if ok := ed25519.Verify(edKey, b, sig.Blob); !ok { |
| 523 | return errors.New("ssh: signature did not verify") |
| 524 | } |
| 525 | |
| 526 | return nil |
| 527 | } |
| 528 | |
Martin Garton | 69267d2 | 2016-06-10 08:32:25 +0100 | [diff] [blame] | 529 | func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey { |
| 530 | return ed25519.PublicKey(k) |
| 531 | } |
| 532 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 533 | func supportedEllipticCurve(curve elliptic.Curve) bool { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 534 | return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521() |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 535 | } |
| 536 | |
| 537 | // ecHash returns the hash to match the given elliptic curve, see RFC |
| 538 | // 5656, section 6.2.1 |
| 539 | func ecHash(curve elliptic.Curve) crypto.Hash { |
| 540 | bitSize := curve.Params().BitSize |
| 541 | switch { |
| 542 | case bitSize <= 256: |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 543 | return crypto.SHA256 |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 544 | case bitSize <= 384: |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 545 | return crypto.SHA384 |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 546 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 547 | return crypto.SHA512 |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 548 | } |
| 549 | |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 550 | // parseECDSA parses an ECDSA key according to RFC 5656, section 3.1. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 551 | func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) { |
Matt Bostock | d5c5f17 | 2015-08-16 22:25:54 +0100 | [diff] [blame] | 552 | var w struct { |
| 553 | Curve string |
| 554 | KeyBytes []byte |
| 555 | Rest []byte `ssh:"rest"` |
| 556 | } |
| 557 | |
| 558 | if err := Unmarshal(in, &w); err != nil { |
| 559 | return nil, nil, err |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 560 | } |
| 561 | |
| 562 | key := new(ecdsa.PublicKey) |
| 563 | |
Matt Bostock | d5c5f17 | 2015-08-16 22:25:54 +0100 | [diff] [blame] | 564 | switch w.Curve { |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 565 | case "nistp256": |
| 566 | key.Curve = elliptic.P256() |
| 567 | case "nistp384": |
| 568 | key.Curve = elliptic.P384() |
| 569 | case "nistp521": |
| 570 | key.Curve = elliptic.P521() |
| 571 | default: |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 572 | return nil, nil, errors.New("ssh: unsupported curve") |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 573 | } |
| 574 | |
Matt Bostock | d5c5f17 | 2015-08-16 22:25:54 +0100 | [diff] [blame] | 575 | key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 576 | if key.X == nil || key.Y == nil { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 577 | return nil, nil, errors.New("ssh: invalid curve point") |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 578 | } |
Matt Bostock | d5c5f17 | 2015-08-16 22:25:54 +0100 | [diff] [blame] | 579 | return (*ecdsaPublicKey)(key), w.Rest, nil |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 580 | } |
| 581 | |
| 582 | func (key *ecdsaPublicKey) Marshal() []byte { |
| 583 | // See RFC 5656, section 3.1. |
| 584 | keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y) |
Peter Moody | e84a34b | 2016-04-19 09:49:29 -0700 | [diff] [blame] | 585 | // ECDSA publickey struct layout should match the struct used by |
| 586 | // parseECDSACert in the x/crypto/ssh/agent package. |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 587 | w := struct { |
| 588 | Name string |
| 589 | ID string |
| 590 | Key []byte |
| 591 | }{ |
| 592 | key.Type(), |
| 593 | key.nistID(), |
| 594 | keyBytes, |
| 595 | } |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 596 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 597 | return Marshal(&w) |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 598 | } |
| 599 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 600 | func (key *ecdsaPublicKey) Verify(data []byte, sig *Signature) error { |
| 601 | if sig.Format != key.Type() { |
| 602 | return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, key.Type()) |
| 603 | } |
| 604 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 605 | h := ecHash(key.Curve).New() |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 606 | h.Write(data) |
| 607 | digest := h.Sum(nil) |
| 608 | |
| 609 | // Per RFC 5656, section 3.1.2, |
| 610 | // The ecdsa_signature_blob value has the following specific encoding: |
| 611 | // mpint r |
| 612 | // mpint s |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 613 | var ecSig struct { |
| 614 | R *big.Int |
| 615 | S *big.Int |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 616 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 617 | |
| 618 | if err := Unmarshal(sig.Blob, &ecSig); err != nil { |
| 619 | return err |
Han-Wen Nienhuys | e62b2ae | 2013-09-13 14:25:14 -0400 | [diff] [blame] | 620 | } |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 621 | |
| 622 | if ecdsa.Verify((*ecdsa.PublicKey)(key), digest, ecSig.R, ecSig.S) { |
| 623 | return nil |
| 624 | } |
| 625 | return errors.New("ssh: signature did not verify") |
Shawn Ledbetter | 887809b | 2012-12-10 17:43:09 -0500 | [diff] [blame] | 626 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 627 | |
Martin Garton | 69267d2 | 2016-06-10 08:32:25 +0100 | [diff] [blame] | 628 | func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey { |
| 629 | return (*ecdsa.PublicKey)(k) |
| 630 | } |
| 631 | |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 632 | // NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey, |
| 633 | // *ecdsa.PrivateKey or any other crypto.Signer and returns a corresponding |
| 634 | // Signer instance. ECDSA keys must use P-256, P-384 or P-521. |
| 635 | func NewSignerFromKey(key interface{}) (Signer, error) { |
| 636 | switch key := key.(type) { |
| 637 | case crypto.Signer: |
| 638 | return NewSignerFromSigner(key) |
| 639 | case *dsa.PrivateKey: |
| 640 | return &dsaPrivateKey{key}, nil |
| 641 | default: |
| 642 | return nil, fmt.Errorf("ssh: unsupported key type %T", key) |
| 643 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 644 | } |
| 645 | |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 646 | type wrappedSigner struct { |
| 647 | signer crypto.Signer |
| 648 | pubKey PublicKey |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 649 | } |
| 650 | |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 651 | // NewSignerFromSigner takes any crypto.Signer implementation and |
| 652 | // returns a corresponding Signer interface. This can be used, for |
| 653 | // example, with keys kept in hardware modules. |
| 654 | func NewSignerFromSigner(signer crypto.Signer) (Signer, error) { |
| 655 | pubKey, err := NewPublicKey(signer.Public()) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 656 | if err != nil { |
| 657 | return nil, err |
| 658 | } |
| 659 | |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 660 | return &wrappedSigner{signer, pubKey}, nil |
| 661 | } |
| 662 | |
| 663 | func (s *wrappedSigner) PublicKey() PublicKey { |
| 664 | return s.pubKey |
| 665 | } |
| 666 | |
| 667 | func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) { |
| 668 | var hashFunc crypto.Hash |
| 669 | |
| 670 | switch key := s.pubKey.(type) { |
| 671 | case *rsaPublicKey, *dsaPublicKey: |
| 672 | hashFunc = crypto.SHA1 |
| 673 | case *ecdsaPublicKey: |
| 674 | hashFunc = ecHash(key.Curve) |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 675 | case ed25519PublicKey: |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 676 | default: |
| 677 | return nil, fmt.Errorf("ssh: unsupported key type %T", key) |
| 678 | } |
| 679 | |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 680 | var digest []byte |
| 681 | if hashFunc != 0 { |
| 682 | h := hashFunc.New() |
| 683 | h.Write(data) |
| 684 | digest = h.Sum(nil) |
| 685 | } else { |
| 686 | digest = data |
| 687 | } |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 688 | |
| 689 | signature, err := s.signer.Sign(rand, digest, hashFunc) |
| 690 | if err != nil { |
| 691 | return nil, err |
| 692 | } |
| 693 | |
| 694 | // crypto.Signer.Sign is expected to return an ASN.1-encoded signature |
| 695 | // for ECDSA and DSA, but that's not the encoding expected by SSH, so |
| 696 | // re-encode. |
| 697 | switch s.pubKey.(type) { |
| 698 | case *ecdsaPublicKey, *dsaPublicKey: |
| 699 | type asn1Signature struct { |
| 700 | R, S *big.Int |
| 701 | } |
| 702 | asn1Sig := new(asn1Signature) |
| 703 | _, err := asn1.Unmarshal(signature, asn1Sig) |
| 704 | if err != nil { |
| 705 | return nil, err |
| 706 | } |
| 707 | |
| 708 | switch s.pubKey.(type) { |
| 709 | case *ecdsaPublicKey: |
| 710 | signature = Marshal(asn1Sig) |
| 711 | |
| 712 | case *dsaPublicKey: |
| 713 | signature = make([]byte, 40) |
| 714 | r := asn1Sig.R.Bytes() |
| 715 | s := asn1Sig.S.Bytes() |
| 716 | copy(signature[20-len(r):20], r) |
| 717 | copy(signature[40-len(s):40], s) |
| 718 | } |
| 719 | } |
| 720 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 721 | return &Signature{ |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 722 | Format: s.pubKey.Type(), |
| 723 | Blob: signature, |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 724 | }, nil |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 725 | } |
| 726 | |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 727 | // NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey, |
Dmitry Savintsev | 4cd25d6 | 2016-07-26 15:00:17 +0200 | [diff] [blame] | 728 | // or ed25519.PublicKey returns a corresponding PublicKey instance. |
| 729 | // ECDSA keys must use P-256, P-384 or P-521. |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 730 | func NewPublicKey(key interface{}) (PublicKey, error) { |
| 731 | switch key := key.(type) { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 732 | case *rsa.PublicKey: |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 733 | return (*rsaPublicKey)(key), nil |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 734 | case *ecdsa.PublicKey: |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 735 | if !supportedEllipticCurve(key.Curve) { |
| 736 | return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported.") |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 737 | } |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 738 | return (*ecdsaPublicKey)(key), nil |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 739 | case *dsa.PublicKey: |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 740 | return (*dsaPublicKey)(key), nil |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 741 | case ed25519.PublicKey: |
| 742 | return (ed25519PublicKey)(key), nil |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 743 | default: |
Evan Broder | e74b035 | 2015-06-12 19:11:01 +0200 | [diff] [blame] | 744 | return nil, fmt.Errorf("ssh: unsupported key type %T", key) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 745 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 746 | } |
| 747 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 748 | // ParsePrivateKey returns a Signer from a PEM encoded private key. It supports |
| 749 | // the same keys as ParseRawPrivateKey. |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 750 | func ParsePrivateKey(pemBytes []byte) (Signer, error) { |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 751 | key, err := ParseRawPrivateKey(pemBytes) |
| 752 | if err != nil { |
| 753 | return nil, err |
| 754 | } |
| 755 | |
| 756 | return NewSignerFromKey(key) |
| 757 | } |
| 758 | |
Emmanuel Odeke | a20de3f | 2016-09-23 01:39:13 -0700 | [diff] [blame] | 759 | // encryptedBlock tells whether a private key is |
| 760 | // encrypted by examining its Proc-Type header |
| 761 | // for a mention of ENCRYPTED |
| 762 | // according to RFC 1421 Section 4.6.1.1. |
| 763 | func encryptedBlock(block *pem.Block) bool { |
| 764 | return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED") |
| 765 | } |
| 766 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 767 | // ParseRawPrivateKey returns a private key from a PEM encoded private key. It |
| 768 | // supports RSA (PKCS#1), DSA (OpenSSL), and ECDSA private keys. |
| 769 | func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) { |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 770 | block, _ := pem.Decode(pemBytes) |
| 771 | if block == nil { |
| 772 | return nil, errors.New("ssh: no key found") |
| 773 | } |
| 774 | |
Emmanuel Odeke | a20de3f | 2016-09-23 01:39:13 -0700 | [diff] [blame] | 775 | if encryptedBlock(block) { |
| 776 | return nil, errors.New("ssh: cannot decode encrypted private keys") |
| 777 | } |
| 778 | |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 779 | switch block.Type { |
| 780 | case "RSA PRIVATE KEY": |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 781 | return x509.ParsePKCS1PrivateKey(block.Bytes) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 782 | case "EC PRIVATE KEY": |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 783 | return x509.ParseECPrivateKey(block.Bytes) |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 784 | case "DSA PRIVATE KEY": |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 785 | return ParseDSAPrivateKey(block.Bytes) |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 786 | case "OPENSSH PRIVATE KEY": |
| 787 | return parseOpenSSHPrivateKey(block.Bytes) |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 788 | default: |
| 789 | return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type) |
| 790 | } |
Han-Wen Nienhuys | 934c14f | 2013-09-19 14:45:31 -0400 | [diff] [blame] | 791 | } |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 792 | |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 793 | // ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as |
| 794 | // specified by the OpenSSL DSA man page. |
| 795 | func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) { |
| 796 | var k struct { |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 797 | Version int |
| 798 | P *big.Int |
| 799 | Q *big.Int |
| 800 | G *big.Int |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 801 | Pub *big.Int |
Paul Querna | b822463 | 2017-01-17 17:10:46 -0800 | [diff] [blame] | 802 | Priv *big.Int |
Adam Langley | fa50e74 | 2014-04-09 13:57:52 -0700 | [diff] [blame] | 803 | } |
| 804 | rest, err := asn1.Unmarshal(der, &k) |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 805 | if err != nil { |
| 806 | return nil, errors.New("ssh: failed to parse DSA key: " + err.Error()) |
| 807 | } |
| 808 | if len(rest) > 0 { |
| 809 | return nil, errors.New("ssh: garbage after DSA key") |
| 810 | } |
| 811 | |
| 812 | return &dsa.PrivateKey{ |
| 813 | PublicKey: dsa.PublicKey{ |
| 814 | Parameters: dsa.Parameters{ |
| 815 | P: k.P, |
| 816 | Q: k.Q, |
| 817 | G: k.G, |
| 818 | }, |
Paul Querna | b822463 | 2017-01-17 17:10:46 -0800 | [diff] [blame] | 819 | Y: k.Pub, |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 820 | }, |
Paul Querna | b822463 | 2017-01-17 17:10:46 -0800 | [diff] [blame] | 821 | X: k.Priv, |
Han-Wen Nienhuys | 4e05812 | 2013-09-26 11:17:52 -0400 | [diff] [blame] | 822 | }, nil |
| 823 | } |
Martin Garton | 1e61df8 | 2016-04-30 22:10:58 +0100 | [diff] [blame] | 824 | |
| 825 | // Implemented based on the documentation at |
| 826 | // https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key |
| 827 | func parseOpenSSHPrivateKey(key []byte) (*ed25519.PrivateKey, error) { |
| 828 | magic := append([]byte("openssh-key-v1"), 0) |
| 829 | if !bytes.Equal(magic, key[0:len(magic)]) { |
| 830 | return nil, errors.New("ssh: invalid openssh private key format") |
| 831 | } |
| 832 | remaining := key[len(magic):] |
| 833 | |
| 834 | var w struct { |
| 835 | CipherName string |
| 836 | KdfName string |
| 837 | KdfOpts string |
| 838 | NumKeys uint32 |
| 839 | PubKey []byte |
| 840 | PrivKeyBlock []byte |
| 841 | } |
| 842 | |
| 843 | if err := Unmarshal(remaining, &w); err != nil { |
| 844 | return nil, err |
| 845 | } |
| 846 | |
| 847 | pk1 := struct { |
| 848 | Check1 uint32 |
| 849 | Check2 uint32 |
| 850 | Keytype string |
| 851 | Pub []byte |
| 852 | Priv []byte |
| 853 | Comment string |
| 854 | Pad []byte `ssh:"rest"` |
| 855 | }{} |
| 856 | |
| 857 | if err := Unmarshal(w.PrivKeyBlock, &pk1); err != nil { |
| 858 | return nil, err |
| 859 | } |
| 860 | |
| 861 | if pk1.Check1 != pk1.Check2 { |
| 862 | return nil, errors.New("ssh: checkint mismatch") |
| 863 | } |
| 864 | |
| 865 | // we only handle ed25519 keys currently |
| 866 | if pk1.Keytype != KeyAlgoED25519 { |
| 867 | return nil, errors.New("ssh: unhandled key type") |
| 868 | } |
| 869 | |
| 870 | for i, b := range pk1.Pad { |
| 871 | if int(b) != i+1 { |
| 872 | return nil, errors.New("ssh: padding not as expected") |
| 873 | } |
| 874 | } |
| 875 | |
| 876 | if len(pk1.Priv) != ed25519.PrivateKeySize { |
| 877 | return nil, errors.New("ssh: private key unexpected length") |
| 878 | } |
| 879 | |
| 880 | pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize)) |
| 881 | copy(pk, pk1.Priv) |
| 882 | return &pk, nil |
| 883 | } |
Ryuzo Yamamoto | ede567c | 2016-11-04 15:41:44 -0400 | [diff] [blame] | 884 | |
| 885 | // FingerprintLegacyMD5 returns the user presentation of the key's |
| 886 | // fingerprint as described by RFC 4716 section 4. |
| 887 | func FingerprintLegacyMD5(pubKey PublicKey) string { |
| 888 | md5sum := md5.Sum(pubKey.Marshal()) |
| 889 | hexarray := make([]string, len(md5sum)) |
| 890 | for i, c := range md5sum { |
| 891 | hexarray[i] = hex.EncodeToString([]byte{c}) |
| 892 | } |
| 893 | return strings.Join(hexarray, ":") |
| 894 | } |
| 895 | |
| 896 | // FingerprintSHA256 returns the user presentation of the key's |
| 897 | // fingerprint as unpadded base64 encoded sha256 hash. |
| 898 | // This format was introduced from OpenSSH 6.8. |
| 899 | // https://www.openssh.com/txt/release-6.8 |
| 900 | // https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding) |
| 901 | func FingerprintSHA256(pubKey PublicKey) string { |
| 902 | sha256sum := sha256.Sum256(pubKey.Marshal()) |
| 903 | hash := base64.RawStdEncoding.EncodeToString(sha256sum[:]) |
| 904 | return "SHA256:" + hash |
| 905 | } |