mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-12-24 22:10:23 +00:00
91774fc936
Uses github.com/libopenstorage/secrets to communicate with Vault. This removes the need for maintaining our own limited Vault APIs. By adding the new dependency, several other packages got updated in the process. Unused indirect dependencies have been removed from go.mod. Signed-off-by: Niels de Vos <ndevos@redhat.com>
1475 lines
37 KiB
Go
1475 lines
37 KiB
Go
// Copyright 2012 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssh
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/md5"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/ed25519"
|
|
"golang.org/x/crypto/ssh/internal/bcrypt_pbkdf"
|
|
)
|
|
|
|
// These constants represent the algorithm names for key types supported by this
|
|
// package.
|
|
const (
|
|
KeyAlgoRSA = "ssh-rsa"
|
|
KeyAlgoDSA = "ssh-dss"
|
|
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
|
|
KeyAlgoSKECDSA256 = "sk-ecdsa-sha2-nistp256@openssh.com"
|
|
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
|
|
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
|
|
KeyAlgoED25519 = "ssh-ed25519"
|
|
KeyAlgoSKED25519 = "sk-ssh-ed25519@openssh.com"
|
|
)
|
|
|
|
// These constants represent non-default signature algorithms that are supported
|
|
// as algorithm parameters to AlgorithmSigner.SignWithAlgorithm methods. See
|
|
// [PROTOCOL.agent] section 4.5.1 and
|
|
// https://tools.ietf.org/html/draft-ietf-curdle-rsa-sha2-10
|
|
const (
|
|
SigAlgoRSA = "ssh-rsa"
|
|
SigAlgoRSASHA2256 = "rsa-sha2-256"
|
|
SigAlgoRSASHA2512 = "rsa-sha2-512"
|
|
)
|
|
|
|
// parsePubKey parses a public key of the given algorithm.
|
|
// Use ParsePublicKey for keys with prepended algorithm.
|
|
func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, err error) {
|
|
switch algo {
|
|
case KeyAlgoRSA:
|
|
return parseRSA(in)
|
|
case KeyAlgoDSA:
|
|
return parseDSA(in)
|
|
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
|
return parseECDSA(in)
|
|
case KeyAlgoSKECDSA256:
|
|
return parseSKECDSA(in)
|
|
case KeyAlgoED25519:
|
|
return parseED25519(in)
|
|
case KeyAlgoSKED25519:
|
|
return parseSKEd25519(in)
|
|
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01, CertAlgoSKECDSA256v01, CertAlgoED25519v01, CertAlgoSKED25519v01:
|
|
cert, err := parseCert(in, certToPrivAlgo(algo))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return cert, nil, nil
|
|
}
|
|
return nil, nil, fmt.Errorf("ssh: unknown key algorithm: %v", algo)
|
|
}
|
|
|
|
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
|
|
// (see sshd(8) manual page) once the options and key type fields have been
|
|
// removed.
|
|
func parseAuthorizedKey(in []byte) (out PublicKey, comment string, err error) {
|
|
in = bytes.TrimSpace(in)
|
|
|
|
i := bytes.IndexAny(in, " \t")
|
|
if i == -1 {
|
|
i = len(in)
|
|
}
|
|
base64Key := in[:i]
|
|
|
|
key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key)))
|
|
n, err := base64.StdEncoding.Decode(key, base64Key)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
key = key[:n]
|
|
out, err = ParsePublicKey(key)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
comment = string(bytes.TrimSpace(in[i:]))
|
|
return out, comment, nil
|
|
}
|
|
|
|
// ParseKnownHosts parses an entry in the format of the known_hosts file.
|
|
//
|
|
// The known_hosts format is documented in the sshd(8) manual page. This
|
|
// function will parse a single entry from in. On successful return, marker
|
|
// will contain the optional marker value (i.e. "cert-authority" or "revoked")
|
|
// or else be empty, hosts will contain the hosts that this entry matches,
|
|
// pubKey will contain the public key and comment will contain any trailing
|
|
// comment at the end of the line. See the sshd(8) manual page for the various
|
|
// forms that a host string can take.
|
|
//
|
|
// The unparsed remainder of the input will be returned in rest. This function
|
|
// can be called repeatedly to parse multiple entries.
|
|
//
|
|
// If no entries were found in the input then err will be io.EOF. Otherwise a
|
|
// non-nil err value indicates a parse error.
|
|
func ParseKnownHosts(in []byte) (marker string, hosts []string, pubKey PublicKey, comment string, rest []byte, err error) {
|
|
for len(in) > 0 {
|
|
end := bytes.IndexByte(in, '\n')
|
|
if end != -1 {
|
|
rest = in[end+1:]
|
|
in = in[:end]
|
|
} else {
|
|
rest = nil
|
|
}
|
|
|
|
end = bytes.IndexByte(in, '\r')
|
|
if end != -1 {
|
|
in = in[:end]
|
|
}
|
|
|
|
in = bytes.TrimSpace(in)
|
|
if len(in) == 0 || in[0] == '#' {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
i := bytes.IndexAny(in, " \t")
|
|
if i == -1 {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
// Strip out the beginning of the known_host key.
|
|
// This is either an optional marker or a (set of) hostname(s).
|
|
keyFields := bytes.Fields(in)
|
|
if len(keyFields) < 3 || len(keyFields) > 5 {
|
|
return "", nil, nil, "", nil, errors.New("ssh: invalid entry in known_hosts data")
|
|
}
|
|
|
|
// keyFields[0] is either "@cert-authority", "@revoked" or a comma separated
|
|
// list of hosts
|
|
marker := ""
|
|
if keyFields[0][0] == '@' {
|
|
marker = string(keyFields[0][1:])
|
|
keyFields = keyFields[1:]
|
|
}
|
|
|
|
hosts := string(keyFields[0])
|
|
// keyFields[1] contains the key type (e.g. “ssh-rsa”).
|
|
// However, that information is duplicated inside the
|
|
// base64-encoded key and so is ignored here.
|
|
|
|
key := bytes.Join(keyFields[2:], []byte(" "))
|
|
if pubKey, comment, err = parseAuthorizedKey(key); err != nil {
|
|
return "", nil, nil, "", nil, err
|
|
}
|
|
|
|
return marker, strings.Split(hosts, ","), pubKey, comment, rest, nil
|
|
}
|
|
|
|
return "", nil, nil, "", nil, io.EOF
|
|
}
|
|
|
|
// ParseAuthorizedKeys parses a public key from an authorized_keys
|
|
// file used in OpenSSH according to the sshd(8) manual page.
|
|
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, err error) {
|
|
for len(in) > 0 {
|
|
end := bytes.IndexByte(in, '\n')
|
|
if end != -1 {
|
|
rest = in[end+1:]
|
|
in = in[:end]
|
|
} else {
|
|
rest = nil
|
|
}
|
|
|
|
end = bytes.IndexByte(in, '\r')
|
|
if end != -1 {
|
|
in = in[:end]
|
|
}
|
|
|
|
in = bytes.TrimSpace(in)
|
|
if len(in) == 0 || in[0] == '#' {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
i := bytes.IndexAny(in, " \t")
|
|
if i == -1 {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
|
return out, comment, options, rest, nil
|
|
}
|
|
|
|
// No key type recognised. Maybe there's an options field at
|
|
// the beginning.
|
|
var b byte
|
|
inQuote := false
|
|
var candidateOptions []string
|
|
optionStart := 0
|
|
for i, b = range in {
|
|
isEnd := !inQuote && (b == ' ' || b == '\t')
|
|
if (b == ',' && !inQuote) || isEnd {
|
|
if i-optionStart > 0 {
|
|
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
|
|
}
|
|
optionStart = i + 1
|
|
}
|
|
if isEnd {
|
|
break
|
|
}
|
|
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
|
|
inQuote = !inQuote
|
|
}
|
|
}
|
|
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
|
|
i++
|
|
}
|
|
if i == len(in) {
|
|
// Invalid line: unmatched quote
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
in = in[i:]
|
|
i = bytes.IndexAny(in, " \t")
|
|
if i == -1 {
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
if out, comment, err = parseAuthorizedKey(in[i:]); err == nil {
|
|
options = candidateOptions
|
|
return out, comment, options, rest, nil
|
|
}
|
|
|
|
in = rest
|
|
continue
|
|
}
|
|
|
|
return nil, "", nil, nil, errors.New("ssh: no key found")
|
|
}
|
|
|
|
// ParsePublicKey parses an SSH public key formatted for use in
|
|
// the SSH wire protocol according to RFC 4253, section 6.6.
|
|
func ParsePublicKey(in []byte) (out PublicKey, err error) {
|
|
algo, in, ok := parseString(in)
|
|
if !ok {
|
|
return nil, errShortRead
|
|
}
|
|
var rest []byte
|
|
out, rest, err = parsePubKey(in, string(algo))
|
|
if len(rest) > 0 {
|
|
return nil, errors.New("ssh: trailing junk in public key")
|
|
}
|
|
|
|
return out, err
|
|
}
|
|
|
|
// MarshalAuthorizedKey serializes key for inclusion in an OpenSSH
|
|
// authorized_keys file. The return value ends with newline.
|
|
func MarshalAuthorizedKey(key PublicKey) []byte {
|
|
b := &bytes.Buffer{}
|
|
b.WriteString(key.Type())
|
|
b.WriteByte(' ')
|
|
e := base64.NewEncoder(base64.StdEncoding, b)
|
|
e.Write(key.Marshal())
|
|
e.Close()
|
|
b.WriteByte('\n')
|
|
return b.Bytes()
|
|
}
|
|
|
|
// PublicKey is an abstraction of different types of public keys.
|
|
type PublicKey interface {
|
|
// Type returns the key's type, e.g. "ssh-rsa".
|
|
Type() string
|
|
|
|
// Marshal returns the serialized key data in SSH wire format,
|
|
// with the name prefix. To unmarshal the returned data, use
|
|
// the ParsePublicKey function.
|
|
Marshal() []byte
|
|
|
|
// Verify that sig is a signature on the given data using this
|
|
// key. This function will hash the data appropriately first.
|
|
Verify(data []byte, sig *Signature) error
|
|
}
|
|
|
|
// CryptoPublicKey, if implemented by a PublicKey,
|
|
// returns the underlying crypto.PublicKey form of the key.
|
|
type CryptoPublicKey interface {
|
|
CryptoPublicKey() crypto.PublicKey
|
|
}
|
|
|
|
// A Signer can create signatures that verify against a public key.
|
|
type Signer interface {
|
|
// PublicKey returns an associated PublicKey instance.
|
|
PublicKey() PublicKey
|
|
|
|
// Sign returns raw signature for the given data. This method
|
|
// will apply the hash specified for the keytype to the data.
|
|
Sign(rand io.Reader, data []byte) (*Signature, error)
|
|
}
|
|
|
|
// A AlgorithmSigner is a Signer that also supports specifying a specific
|
|
// algorithm to use for signing.
|
|
type AlgorithmSigner interface {
|
|
Signer
|
|
|
|
// SignWithAlgorithm is like Signer.Sign, but allows specification of a
|
|
// non-default signing algorithm. See the SigAlgo* constants in this
|
|
// package for signature algorithms supported by this package. Callers may
|
|
// pass an empty string for the algorithm in which case the AlgorithmSigner
|
|
// will use its default algorithm.
|
|
SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error)
|
|
}
|
|
|
|
type rsaPublicKey rsa.PublicKey
|
|
|
|
func (r *rsaPublicKey) Type() string {
|
|
return "ssh-rsa"
|
|
}
|
|
|
|
// parseRSA parses an RSA key according to RFC 4253, section 6.6.
|
|
func parseRSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
E *big.Int
|
|
N *big.Int
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if w.E.BitLen() > 24 {
|
|
return nil, nil, errors.New("ssh: exponent too large")
|
|
}
|
|
e := w.E.Int64()
|
|
if e < 3 || e&1 == 0 {
|
|
return nil, nil, errors.New("ssh: incorrect exponent")
|
|
}
|
|
|
|
var key rsa.PublicKey
|
|
key.E = int(e)
|
|
key.N = w.N
|
|
return (*rsaPublicKey)(&key), w.Rest, nil
|
|
}
|
|
|
|
func (r *rsaPublicKey) Marshal() []byte {
|
|
e := new(big.Int).SetInt64(int64(r.E))
|
|
// RSA publickey struct layout should match the struct used by
|
|
// parseRSACert in the x/crypto/ssh/agent package.
|
|
wirekey := struct {
|
|
Name string
|
|
E *big.Int
|
|
N *big.Int
|
|
}{
|
|
KeyAlgoRSA,
|
|
e,
|
|
r.N,
|
|
}
|
|
return Marshal(&wirekey)
|
|
}
|
|
|
|
func (r *rsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
var hash crypto.Hash
|
|
switch sig.Format {
|
|
case SigAlgoRSA:
|
|
hash = crypto.SHA1
|
|
case SigAlgoRSASHA2256:
|
|
hash = crypto.SHA256
|
|
case SigAlgoRSASHA2512:
|
|
hash = crypto.SHA512
|
|
default:
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, r.Type())
|
|
}
|
|
h := hash.New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), hash, digest, sig.Blob)
|
|
}
|
|
|
|
func (r *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
return (*rsa.PublicKey)(r)
|
|
}
|
|
|
|
type dsaPublicKey dsa.PublicKey
|
|
|
|
func (k *dsaPublicKey) Type() string {
|
|
return "ssh-dss"
|
|
}
|
|
|
|
func checkDSAParams(param *dsa.Parameters) error {
|
|
// SSH specifies FIPS 186-2, which only provided a single size
|
|
// (1024 bits) DSA key. FIPS 186-3 allows for larger key
|
|
// sizes, which would confuse SSH.
|
|
if l := param.P.BitLen(); l != 1024 {
|
|
return fmt.Errorf("ssh: unsupported DSA key size %d", l)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
|
|
func parseDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
P, Q, G, Y *big.Int
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
param := dsa.Parameters{
|
|
P: w.P,
|
|
Q: w.Q,
|
|
G: w.G,
|
|
}
|
|
if err := checkDSAParams(¶m); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
key := &dsaPublicKey{
|
|
Parameters: param,
|
|
Y: w.Y,
|
|
}
|
|
return key, w.Rest, nil
|
|
}
|
|
|
|
func (k *dsaPublicKey) Marshal() []byte {
|
|
// DSA publickey struct layout should match the struct used by
|
|
// parseDSACert in the x/crypto/ssh/agent package.
|
|
w := struct {
|
|
Name string
|
|
P, Q, G, Y *big.Int
|
|
}{
|
|
k.Type(),
|
|
k.P,
|
|
k.Q,
|
|
k.G,
|
|
k.Y,
|
|
}
|
|
|
|
return Marshal(&w)
|
|
}
|
|
|
|
func (k *dsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
if sig.Format != k.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
|
}
|
|
h := crypto.SHA1.New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
|
|
// Per RFC 4253, section 6.6,
|
|
// The value for 'dss_signature_blob' is encoded as a string containing
|
|
// r, followed by s (which are 160-bit integers, without lengths or
|
|
// padding, unsigned, and in network byte order).
|
|
// For DSS purposes, sig.Blob should be exactly 40 bytes in length.
|
|
if len(sig.Blob) != 40 {
|
|
return errors.New("ssh: DSA signature parse error")
|
|
}
|
|
r := new(big.Int).SetBytes(sig.Blob[:20])
|
|
s := new(big.Int).SetBytes(sig.Blob[20:])
|
|
if dsa.Verify((*dsa.PublicKey)(k), digest, r, s) {
|
|
return nil
|
|
}
|
|
return errors.New("ssh: signature did not verify")
|
|
}
|
|
|
|
func (k *dsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
return (*dsa.PublicKey)(k)
|
|
}
|
|
|
|
type dsaPrivateKey struct {
|
|
*dsa.PrivateKey
|
|
}
|
|
|
|
func (k *dsaPrivateKey) PublicKey() PublicKey {
|
|
return (*dsaPublicKey)(&k.PrivateKey.PublicKey)
|
|
}
|
|
|
|
func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|
return k.SignWithAlgorithm(rand, data, "")
|
|
}
|
|
|
|
func (k *dsaPrivateKey) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
|
|
if algorithm != "" && algorithm != k.PublicKey().Type() {
|
|
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
|
|
}
|
|
|
|
h := crypto.SHA1.New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sig := make([]byte, 40)
|
|
rb := r.Bytes()
|
|
sb := s.Bytes()
|
|
|
|
copy(sig[20-len(rb):20], rb)
|
|
copy(sig[40-len(sb):], sb)
|
|
|
|
return &Signature{
|
|
Format: k.PublicKey().Type(),
|
|
Blob: sig,
|
|
}, nil
|
|
}
|
|
|
|
type ecdsaPublicKey ecdsa.PublicKey
|
|
|
|
func (k *ecdsaPublicKey) Type() string {
|
|
return "ecdsa-sha2-" + k.nistID()
|
|
}
|
|
|
|
func (k *ecdsaPublicKey) nistID() string {
|
|
switch k.Params().BitSize {
|
|
case 256:
|
|
return "nistp256"
|
|
case 384:
|
|
return "nistp384"
|
|
case 521:
|
|
return "nistp521"
|
|
}
|
|
panic("ssh: unsupported ecdsa key size")
|
|
}
|
|
|
|
type ed25519PublicKey ed25519.PublicKey
|
|
|
|
func (k ed25519PublicKey) Type() string {
|
|
return KeyAlgoED25519
|
|
}
|
|
|
|
func parseED25519(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
KeyBytes []byte
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if l := len(w.KeyBytes); l != ed25519.PublicKeySize {
|
|
return nil, nil, fmt.Errorf("invalid size %d for Ed25519 public key", l)
|
|
}
|
|
|
|
return ed25519PublicKey(w.KeyBytes), w.Rest, nil
|
|
}
|
|
|
|
func (k ed25519PublicKey) Marshal() []byte {
|
|
w := struct {
|
|
Name string
|
|
KeyBytes []byte
|
|
}{
|
|
KeyAlgoED25519,
|
|
[]byte(k),
|
|
}
|
|
return Marshal(&w)
|
|
}
|
|
|
|
func (k ed25519PublicKey) Verify(b []byte, sig *Signature) error {
|
|
if sig.Format != k.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
|
}
|
|
if l := len(k); l != ed25519.PublicKeySize {
|
|
return fmt.Errorf("ssh: invalid size %d for Ed25519 public key", l)
|
|
}
|
|
|
|
if ok := ed25519.Verify(ed25519.PublicKey(k), b, sig.Blob); !ok {
|
|
return errors.New("ssh: signature did not verify")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (k ed25519PublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
return ed25519.PublicKey(k)
|
|
}
|
|
|
|
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
|
return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521()
|
|
}
|
|
|
|
// ecHash returns the hash to match the given elliptic curve, see RFC
|
|
// 5656, section 6.2.1
|
|
func ecHash(curve elliptic.Curve) crypto.Hash {
|
|
bitSize := curve.Params().BitSize
|
|
switch {
|
|
case bitSize <= 256:
|
|
return crypto.SHA256
|
|
case bitSize <= 384:
|
|
return crypto.SHA384
|
|
}
|
|
return crypto.SHA512
|
|
}
|
|
|
|
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
|
|
func parseECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
Curve string
|
|
KeyBytes []byte
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
key := new(ecdsa.PublicKey)
|
|
|
|
switch w.Curve {
|
|
case "nistp256":
|
|
key.Curve = elliptic.P256()
|
|
case "nistp384":
|
|
key.Curve = elliptic.P384()
|
|
case "nistp521":
|
|
key.Curve = elliptic.P521()
|
|
default:
|
|
return nil, nil, errors.New("ssh: unsupported curve")
|
|
}
|
|
|
|
key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes)
|
|
if key.X == nil || key.Y == nil {
|
|
return nil, nil, errors.New("ssh: invalid curve point")
|
|
}
|
|
return (*ecdsaPublicKey)(key), w.Rest, nil
|
|
}
|
|
|
|
func (k *ecdsaPublicKey) Marshal() []byte {
|
|
// See RFC 5656, section 3.1.
|
|
keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y)
|
|
// ECDSA publickey struct layout should match the struct used by
|
|
// parseECDSACert in the x/crypto/ssh/agent package.
|
|
w := struct {
|
|
Name string
|
|
ID string
|
|
Key []byte
|
|
}{
|
|
k.Type(),
|
|
k.nistID(),
|
|
keyBytes,
|
|
}
|
|
|
|
return Marshal(&w)
|
|
}
|
|
|
|
func (k *ecdsaPublicKey) Verify(data []byte, sig *Signature) error {
|
|
if sig.Format != k.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
|
}
|
|
|
|
h := ecHash(k.Curve).New()
|
|
h.Write(data)
|
|
digest := h.Sum(nil)
|
|
|
|
// Per RFC 5656, section 3.1.2,
|
|
// The ecdsa_signature_blob value has the following specific encoding:
|
|
// mpint r
|
|
// mpint s
|
|
var ecSig struct {
|
|
R *big.Int
|
|
S *big.Int
|
|
}
|
|
|
|
if err := Unmarshal(sig.Blob, &ecSig); err != nil {
|
|
return err
|
|
}
|
|
|
|
if ecdsa.Verify((*ecdsa.PublicKey)(k), digest, ecSig.R, ecSig.S) {
|
|
return nil
|
|
}
|
|
return errors.New("ssh: signature did not verify")
|
|
}
|
|
|
|
func (k *ecdsaPublicKey) CryptoPublicKey() crypto.PublicKey {
|
|
return (*ecdsa.PublicKey)(k)
|
|
}
|
|
|
|
// skFields holds the additional fields present in U2F/FIDO2 signatures.
|
|
// See openssh/PROTOCOL.u2f 'SSH U2F Signatures' for details.
|
|
type skFields struct {
|
|
// Flags contains U2F/FIDO2 flags such as 'user present'
|
|
Flags byte
|
|
// Counter is a monotonic signature counter which can be
|
|
// used to detect concurrent use of a private key, should
|
|
// it be extracted from hardware.
|
|
Counter uint32
|
|
}
|
|
|
|
type skECDSAPublicKey struct {
|
|
// application is a URL-like string, typically "ssh:" for SSH.
|
|
// see openssh/PROTOCOL.u2f for details.
|
|
application string
|
|
ecdsa.PublicKey
|
|
}
|
|
|
|
func (k *skECDSAPublicKey) Type() string {
|
|
return KeyAlgoSKECDSA256
|
|
}
|
|
|
|
func (k *skECDSAPublicKey) nistID() string {
|
|
return "nistp256"
|
|
}
|
|
|
|
func parseSKECDSA(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
Curve string
|
|
KeyBytes []byte
|
|
Application string
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
key := new(skECDSAPublicKey)
|
|
key.application = w.Application
|
|
|
|
if w.Curve != "nistp256" {
|
|
return nil, nil, errors.New("ssh: unsupported curve")
|
|
}
|
|
key.Curve = elliptic.P256()
|
|
|
|
key.X, key.Y = elliptic.Unmarshal(key.Curve, w.KeyBytes)
|
|
if key.X == nil || key.Y == nil {
|
|
return nil, nil, errors.New("ssh: invalid curve point")
|
|
}
|
|
|
|
return key, w.Rest, nil
|
|
}
|
|
|
|
func (k *skECDSAPublicKey) Marshal() []byte {
|
|
// See RFC 5656, section 3.1.
|
|
keyBytes := elliptic.Marshal(k.Curve, k.X, k.Y)
|
|
w := struct {
|
|
Name string
|
|
ID string
|
|
Key []byte
|
|
Application string
|
|
}{
|
|
k.Type(),
|
|
k.nistID(),
|
|
keyBytes,
|
|
k.application,
|
|
}
|
|
|
|
return Marshal(&w)
|
|
}
|
|
|
|
func (k *skECDSAPublicKey) Verify(data []byte, sig *Signature) error {
|
|
if sig.Format != k.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
|
}
|
|
|
|
h := ecHash(k.Curve).New()
|
|
h.Write([]byte(k.application))
|
|
appDigest := h.Sum(nil)
|
|
|
|
h.Reset()
|
|
h.Write(data)
|
|
dataDigest := h.Sum(nil)
|
|
|
|
var ecSig struct {
|
|
R *big.Int
|
|
S *big.Int
|
|
}
|
|
if err := Unmarshal(sig.Blob, &ecSig); err != nil {
|
|
return err
|
|
}
|
|
|
|
var skf skFields
|
|
if err := Unmarshal(sig.Rest, &skf); err != nil {
|
|
return err
|
|
}
|
|
|
|
blob := struct {
|
|
ApplicationDigest []byte `ssh:"rest"`
|
|
Flags byte
|
|
Counter uint32
|
|
MessageDigest []byte `ssh:"rest"`
|
|
}{
|
|
appDigest,
|
|
skf.Flags,
|
|
skf.Counter,
|
|
dataDigest,
|
|
}
|
|
|
|
original := Marshal(blob)
|
|
|
|
h.Reset()
|
|
h.Write(original)
|
|
digest := h.Sum(nil)
|
|
|
|
if ecdsa.Verify((*ecdsa.PublicKey)(&k.PublicKey), digest, ecSig.R, ecSig.S) {
|
|
return nil
|
|
}
|
|
return errors.New("ssh: signature did not verify")
|
|
}
|
|
|
|
type skEd25519PublicKey struct {
|
|
// application is a URL-like string, typically "ssh:" for SSH.
|
|
// see openssh/PROTOCOL.u2f for details.
|
|
application string
|
|
ed25519.PublicKey
|
|
}
|
|
|
|
func (k *skEd25519PublicKey) Type() string {
|
|
return KeyAlgoSKED25519
|
|
}
|
|
|
|
func parseSKEd25519(in []byte) (out PublicKey, rest []byte, err error) {
|
|
var w struct {
|
|
KeyBytes []byte
|
|
Application string
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := Unmarshal(in, &w); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if l := len(w.KeyBytes); l != ed25519.PublicKeySize {
|
|
return nil, nil, fmt.Errorf("invalid size %d for Ed25519 public key", l)
|
|
}
|
|
|
|
key := new(skEd25519PublicKey)
|
|
key.application = w.Application
|
|
key.PublicKey = ed25519.PublicKey(w.KeyBytes)
|
|
|
|
return key, w.Rest, nil
|
|
}
|
|
|
|
func (k *skEd25519PublicKey) Marshal() []byte {
|
|
w := struct {
|
|
Name string
|
|
KeyBytes []byte
|
|
Application string
|
|
}{
|
|
KeyAlgoSKED25519,
|
|
[]byte(k.PublicKey),
|
|
k.application,
|
|
}
|
|
return Marshal(&w)
|
|
}
|
|
|
|
func (k *skEd25519PublicKey) Verify(data []byte, sig *Signature) error {
|
|
if sig.Format != k.Type() {
|
|
return fmt.Errorf("ssh: signature type %s for key type %s", sig.Format, k.Type())
|
|
}
|
|
if l := len(k.PublicKey); l != ed25519.PublicKeySize {
|
|
return fmt.Errorf("invalid size %d for Ed25519 public key", l)
|
|
}
|
|
|
|
h := sha256.New()
|
|
h.Write([]byte(k.application))
|
|
appDigest := h.Sum(nil)
|
|
|
|
h.Reset()
|
|
h.Write(data)
|
|
dataDigest := h.Sum(nil)
|
|
|
|
var edSig struct {
|
|
Signature []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := Unmarshal(sig.Blob, &edSig); err != nil {
|
|
return err
|
|
}
|
|
|
|
var skf skFields
|
|
if err := Unmarshal(sig.Rest, &skf); err != nil {
|
|
return err
|
|
}
|
|
|
|
blob := struct {
|
|
ApplicationDigest []byte `ssh:"rest"`
|
|
Flags byte
|
|
Counter uint32
|
|
MessageDigest []byte `ssh:"rest"`
|
|
}{
|
|
appDigest,
|
|
skf.Flags,
|
|
skf.Counter,
|
|
dataDigest,
|
|
}
|
|
|
|
original := Marshal(blob)
|
|
|
|
if ok := ed25519.Verify(k.PublicKey, original, edSig.Signature); !ok {
|
|
return errors.New("ssh: signature did not verify")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewSignerFromKey takes an *rsa.PrivateKey, *dsa.PrivateKey,
|
|
// *ecdsa.PrivateKey or any other crypto.Signer and returns a
|
|
// corresponding Signer instance. ECDSA keys must use P-256, P-384 or
|
|
// P-521. DSA keys must use parameter size L1024N160.
|
|
func NewSignerFromKey(key interface{}) (Signer, error) {
|
|
switch key := key.(type) {
|
|
case crypto.Signer:
|
|
return NewSignerFromSigner(key)
|
|
case *dsa.PrivateKey:
|
|
return newDSAPrivateKey(key)
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
}
|
|
}
|
|
|
|
func newDSAPrivateKey(key *dsa.PrivateKey) (Signer, error) {
|
|
if err := checkDSAParams(&key.PublicKey.Parameters); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &dsaPrivateKey{key}, nil
|
|
}
|
|
|
|
type wrappedSigner struct {
|
|
signer crypto.Signer
|
|
pubKey PublicKey
|
|
}
|
|
|
|
// NewSignerFromSigner takes any crypto.Signer implementation and
|
|
// returns a corresponding Signer interface. This can be used, for
|
|
// example, with keys kept in hardware modules.
|
|
func NewSignerFromSigner(signer crypto.Signer) (Signer, error) {
|
|
pubKey, err := NewPublicKey(signer.Public())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &wrappedSigner{signer, pubKey}, nil
|
|
}
|
|
|
|
func (s *wrappedSigner) PublicKey() PublicKey {
|
|
return s.pubKey
|
|
}
|
|
|
|
func (s *wrappedSigner) Sign(rand io.Reader, data []byte) (*Signature, error) {
|
|
return s.SignWithAlgorithm(rand, data, "")
|
|
}
|
|
|
|
func (s *wrappedSigner) SignWithAlgorithm(rand io.Reader, data []byte, algorithm string) (*Signature, error) {
|
|
var hashFunc crypto.Hash
|
|
|
|
if _, ok := s.pubKey.(*rsaPublicKey); ok {
|
|
// RSA keys support a few hash functions determined by the requested signature algorithm
|
|
switch algorithm {
|
|
case "", SigAlgoRSA:
|
|
algorithm = SigAlgoRSA
|
|
hashFunc = crypto.SHA1
|
|
case SigAlgoRSASHA2256:
|
|
hashFunc = crypto.SHA256
|
|
case SigAlgoRSASHA2512:
|
|
hashFunc = crypto.SHA512
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
|
|
}
|
|
} else {
|
|
// The only supported algorithm for all other key types is the same as the type of the key
|
|
if algorithm == "" {
|
|
algorithm = s.pubKey.Type()
|
|
} else if algorithm != s.pubKey.Type() {
|
|
return nil, fmt.Errorf("ssh: unsupported signature algorithm %s", algorithm)
|
|
}
|
|
|
|
switch key := s.pubKey.(type) {
|
|
case *dsaPublicKey:
|
|
hashFunc = crypto.SHA1
|
|
case *ecdsaPublicKey:
|
|
hashFunc = ecHash(key.Curve)
|
|
case ed25519PublicKey:
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
}
|
|
}
|
|
|
|
var digest []byte
|
|
if hashFunc != 0 {
|
|
h := hashFunc.New()
|
|
h.Write(data)
|
|
digest = h.Sum(nil)
|
|
} else {
|
|
digest = data
|
|
}
|
|
|
|
signature, err := s.signer.Sign(rand, digest, hashFunc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// crypto.Signer.Sign is expected to return an ASN.1-encoded signature
|
|
// for ECDSA and DSA, but that's not the encoding expected by SSH, so
|
|
// re-encode.
|
|
switch s.pubKey.(type) {
|
|
case *ecdsaPublicKey, *dsaPublicKey:
|
|
type asn1Signature struct {
|
|
R, S *big.Int
|
|
}
|
|
asn1Sig := new(asn1Signature)
|
|
_, err := asn1.Unmarshal(signature, asn1Sig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch s.pubKey.(type) {
|
|
case *ecdsaPublicKey:
|
|
signature = Marshal(asn1Sig)
|
|
|
|
case *dsaPublicKey:
|
|
signature = make([]byte, 40)
|
|
r := asn1Sig.R.Bytes()
|
|
s := asn1Sig.S.Bytes()
|
|
copy(signature[20-len(r):20], r)
|
|
copy(signature[40-len(s):40], s)
|
|
}
|
|
}
|
|
|
|
return &Signature{
|
|
Format: algorithm,
|
|
Blob: signature,
|
|
}, nil
|
|
}
|
|
|
|
// NewPublicKey takes an *rsa.PublicKey, *dsa.PublicKey, *ecdsa.PublicKey,
|
|
// or ed25519.PublicKey returns a corresponding PublicKey instance.
|
|
// ECDSA keys must use P-256, P-384 or P-521.
|
|
func NewPublicKey(key interface{}) (PublicKey, error) {
|
|
switch key := key.(type) {
|
|
case *rsa.PublicKey:
|
|
return (*rsaPublicKey)(key), nil
|
|
case *ecdsa.PublicKey:
|
|
if !supportedEllipticCurve(key.Curve) {
|
|
return nil, errors.New("ssh: only P-256, P-384 and P-521 EC keys are supported")
|
|
}
|
|
return (*ecdsaPublicKey)(key), nil
|
|
case *dsa.PublicKey:
|
|
return (*dsaPublicKey)(key), nil
|
|
case ed25519.PublicKey:
|
|
if l := len(key); l != ed25519.PublicKeySize {
|
|
return nil, fmt.Errorf("ssh: invalid size %d for Ed25519 public key", l)
|
|
}
|
|
return ed25519PublicKey(key), nil
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %T", key)
|
|
}
|
|
}
|
|
|
|
// ParsePrivateKey returns a Signer from a PEM encoded private key. It supports
|
|
// the same keys as ParseRawPrivateKey. If the private key is encrypted, it
|
|
// will return a PassphraseMissingError.
|
|
func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
|
key, err := ParseRawPrivateKey(pemBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewSignerFromKey(key)
|
|
}
|
|
|
|
// ParsePrivateKeyWithPassphrase returns a Signer from a PEM encoded private
|
|
// key and passphrase. It supports the same keys as
|
|
// ParseRawPrivateKeyWithPassphrase.
|
|
func ParsePrivateKeyWithPassphrase(pemBytes, passphrase []byte) (Signer, error) {
|
|
key, err := ParseRawPrivateKeyWithPassphrase(pemBytes, passphrase)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return NewSignerFromKey(key)
|
|
}
|
|
|
|
// encryptedBlock tells whether a private key is
|
|
// encrypted by examining its Proc-Type header
|
|
// for a mention of ENCRYPTED
|
|
// according to RFC 1421 Section 4.6.1.1.
|
|
func encryptedBlock(block *pem.Block) bool {
|
|
return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED")
|
|
}
|
|
|
|
// A PassphraseMissingError indicates that parsing this private key requires a
|
|
// passphrase. Use ParsePrivateKeyWithPassphrase.
|
|
type PassphraseMissingError struct {
|
|
// PublicKey will be set if the private key format includes an unencrypted
|
|
// public key along with the encrypted private key.
|
|
PublicKey PublicKey
|
|
}
|
|
|
|
func (*PassphraseMissingError) Error() string {
|
|
return "ssh: this private key is passphrase protected"
|
|
}
|
|
|
|
// ParseRawPrivateKey returns a private key from a PEM encoded private key. It
|
|
// supports RSA (PKCS#1), PKCS#8, DSA (OpenSSL), and ECDSA private keys. If the
|
|
// private key is encrypted, it will return a PassphraseMissingError.
|
|
func ParseRawPrivateKey(pemBytes []byte) (interface{}, error) {
|
|
block, _ := pem.Decode(pemBytes)
|
|
if block == nil {
|
|
return nil, errors.New("ssh: no key found")
|
|
}
|
|
|
|
if encryptedBlock(block) {
|
|
return nil, &PassphraseMissingError{}
|
|
}
|
|
|
|
switch block.Type {
|
|
case "RSA PRIVATE KEY":
|
|
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
// RFC5208 - https://tools.ietf.org/html/rfc5208
|
|
case "PRIVATE KEY":
|
|
return x509.ParsePKCS8PrivateKey(block.Bytes)
|
|
case "EC PRIVATE KEY":
|
|
return x509.ParseECPrivateKey(block.Bytes)
|
|
case "DSA PRIVATE KEY":
|
|
return ParseDSAPrivateKey(block.Bytes)
|
|
case "OPENSSH PRIVATE KEY":
|
|
return parseOpenSSHPrivateKey(block.Bytes, unencryptedOpenSSHKey)
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
|
}
|
|
}
|
|
|
|
// ParseRawPrivateKeyWithPassphrase returns a private key decrypted with
|
|
// passphrase from a PEM encoded private key. If the passphrase is wrong, it
|
|
// will return x509.IncorrectPasswordError.
|
|
func ParseRawPrivateKeyWithPassphrase(pemBytes, passphrase []byte) (interface{}, error) {
|
|
block, _ := pem.Decode(pemBytes)
|
|
if block == nil {
|
|
return nil, errors.New("ssh: no key found")
|
|
}
|
|
|
|
if block.Type == "OPENSSH PRIVATE KEY" {
|
|
return parseOpenSSHPrivateKey(block.Bytes, passphraseProtectedOpenSSHKey(passphrase))
|
|
}
|
|
|
|
if !encryptedBlock(block) || !x509.IsEncryptedPEMBlock(block) {
|
|
return nil, errors.New("ssh: not an encrypted key")
|
|
}
|
|
|
|
buf, err := x509.DecryptPEMBlock(block, passphrase)
|
|
if err != nil {
|
|
if err == x509.IncorrectPasswordError {
|
|
return nil, err
|
|
}
|
|
return nil, fmt.Errorf("ssh: cannot decode encrypted private keys: %v", err)
|
|
}
|
|
|
|
switch block.Type {
|
|
case "RSA PRIVATE KEY":
|
|
return x509.ParsePKCS1PrivateKey(buf)
|
|
case "EC PRIVATE KEY":
|
|
return x509.ParseECPrivateKey(buf)
|
|
case "DSA PRIVATE KEY":
|
|
return ParseDSAPrivateKey(buf)
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
|
}
|
|
}
|
|
|
|
// ParseDSAPrivateKey returns a DSA private key from its ASN.1 DER encoding, as
|
|
// specified by the OpenSSL DSA man page.
|
|
func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) {
|
|
var k struct {
|
|
Version int
|
|
P *big.Int
|
|
Q *big.Int
|
|
G *big.Int
|
|
Pub *big.Int
|
|
Priv *big.Int
|
|
}
|
|
rest, err := asn1.Unmarshal(der, &k)
|
|
if err != nil {
|
|
return nil, errors.New("ssh: failed to parse DSA key: " + err.Error())
|
|
}
|
|
if len(rest) > 0 {
|
|
return nil, errors.New("ssh: garbage after DSA key")
|
|
}
|
|
|
|
return &dsa.PrivateKey{
|
|
PublicKey: dsa.PublicKey{
|
|
Parameters: dsa.Parameters{
|
|
P: k.P,
|
|
Q: k.Q,
|
|
G: k.G,
|
|
},
|
|
Y: k.Pub,
|
|
},
|
|
X: k.Priv,
|
|
}, nil
|
|
}
|
|
|
|
func unencryptedOpenSSHKey(cipherName, kdfName, kdfOpts string, privKeyBlock []byte) ([]byte, error) {
|
|
if kdfName != "none" || cipherName != "none" {
|
|
return nil, &PassphraseMissingError{}
|
|
}
|
|
if kdfOpts != "" {
|
|
return nil, errors.New("ssh: invalid openssh private key")
|
|
}
|
|
return privKeyBlock, nil
|
|
}
|
|
|
|
func passphraseProtectedOpenSSHKey(passphrase []byte) openSSHDecryptFunc {
|
|
return func(cipherName, kdfName, kdfOpts string, privKeyBlock []byte) ([]byte, error) {
|
|
if kdfName == "none" || cipherName == "none" {
|
|
return nil, errors.New("ssh: key is not password protected")
|
|
}
|
|
if kdfName != "bcrypt" {
|
|
return nil, fmt.Errorf("ssh: unknown KDF %q, only supports %q", kdfName, "bcrypt")
|
|
}
|
|
|
|
var opts struct {
|
|
Salt string
|
|
Rounds uint32
|
|
}
|
|
if err := Unmarshal([]byte(kdfOpts), &opts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
k, err := bcrypt_pbkdf.Key(passphrase, []byte(opts.Salt), int(opts.Rounds), 32+16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
key, iv := k[:32], k[32:]
|
|
|
|
c, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
switch cipherName {
|
|
case "aes256-ctr":
|
|
ctr := cipher.NewCTR(c, iv)
|
|
ctr.XORKeyStream(privKeyBlock, privKeyBlock)
|
|
case "aes256-cbc":
|
|
if len(privKeyBlock)%c.BlockSize() != 0 {
|
|
return nil, fmt.Errorf("ssh: invalid encrypted private key length, not a multiple of the block size")
|
|
}
|
|
cbc := cipher.NewCBCDecrypter(c, iv)
|
|
cbc.CryptBlocks(privKeyBlock, privKeyBlock)
|
|
default:
|
|
return nil, fmt.Errorf("ssh: unknown cipher %q, only supports %q or %q", cipherName, "aes256-ctr", "aes256-cbc")
|
|
}
|
|
|
|
return privKeyBlock, nil
|
|
}
|
|
}
|
|
|
|
type openSSHDecryptFunc func(CipherName, KdfName, KdfOpts string, PrivKeyBlock []byte) ([]byte, error)
|
|
|
|
// parseOpenSSHPrivateKey parses an OpenSSH private key, using the decrypt
|
|
// function to unwrap the encrypted portion. unencryptedOpenSSHKey can be used
|
|
// as the decrypt function to parse an unencrypted private key. See
|
|
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key.
|
|
func parseOpenSSHPrivateKey(key []byte, decrypt openSSHDecryptFunc) (crypto.PrivateKey, error) {
|
|
const magic = "openssh-key-v1\x00"
|
|
if len(key) < len(magic) || string(key[:len(magic)]) != magic {
|
|
return nil, errors.New("ssh: invalid openssh private key format")
|
|
}
|
|
remaining := key[len(magic):]
|
|
|
|
var w struct {
|
|
CipherName string
|
|
KdfName string
|
|
KdfOpts string
|
|
NumKeys uint32
|
|
PubKey []byte
|
|
PrivKeyBlock []byte
|
|
}
|
|
|
|
if err := Unmarshal(remaining, &w); err != nil {
|
|
return nil, err
|
|
}
|
|
if w.NumKeys != 1 {
|
|
// We only support single key files, and so does OpenSSH.
|
|
// https://github.com/openssh/openssh-portable/blob/4103a3ec7/sshkey.c#L4171
|
|
return nil, errors.New("ssh: multi-key files are not supported")
|
|
}
|
|
|
|
privKeyBlock, err := decrypt(w.CipherName, w.KdfName, w.KdfOpts, w.PrivKeyBlock)
|
|
if err != nil {
|
|
if err, ok := err.(*PassphraseMissingError); ok {
|
|
pub, errPub := ParsePublicKey(w.PubKey)
|
|
if errPub != nil {
|
|
return nil, fmt.Errorf("ssh: failed to parse embedded public key: %v", errPub)
|
|
}
|
|
err.PublicKey = pub
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
pk1 := struct {
|
|
Check1 uint32
|
|
Check2 uint32
|
|
Keytype string
|
|
Rest []byte `ssh:"rest"`
|
|
}{}
|
|
|
|
if err := Unmarshal(privKeyBlock, &pk1); err != nil || pk1.Check1 != pk1.Check2 {
|
|
if w.CipherName != "none" {
|
|
return nil, x509.IncorrectPasswordError
|
|
}
|
|
return nil, errors.New("ssh: malformed OpenSSH key")
|
|
}
|
|
|
|
switch pk1.Keytype {
|
|
case KeyAlgoRSA:
|
|
// https://github.com/openssh/openssh-portable/blob/master/sshkey.c#L2760-L2773
|
|
key := struct {
|
|
N *big.Int
|
|
E *big.Int
|
|
D *big.Int
|
|
Iqmp *big.Int
|
|
P *big.Int
|
|
Q *big.Int
|
|
Comment string
|
|
Pad []byte `ssh:"rest"`
|
|
}{}
|
|
|
|
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := checkOpenSSHKeyPadding(key.Pad); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pk := &rsa.PrivateKey{
|
|
PublicKey: rsa.PublicKey{
|
|
N: key.N,
|
|
E: int(key.E.Int64()),
|
|
},
|
|
D: key.D,
|
|
Primes: []*big.Int{key.P, key.Q},
|
|
}
|
|
|
|
if err := pk.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pk.Precompute()
|
|
|
|
return pk, nil
|
|
case KeyAlgoED25519:
|
|
key := struct {
|
|
Pub []byte
|
|
Priv []byte
|
|
Comment string
|
|
Pad []byte `ssh:"rest"`
|
|
}{}
|
|
|
|
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(key.Priv) != ed25519.PrivateKeySize {
|
|
return nil, errors.New("ssh: private key unexpected length")
|
|
}
|
|
|
|
if err := checkOpenSSHKeyPadding(key.Pad); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pk := ed25519.PrivateKey(make([]byte, ed25519.PrivateKeySize))
|
|
copy(pk, key.Priv)
|
|
return &pk, nil
|
|
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
|
key := struct {
|
|
Curve string
|
|
Pub []byte
|
|
D *big.Int
|
|
Comment string
|
|
Pad []byte `ssh:"rest"`
|
|
}{}
|
|
|
|
if err := Unmarshal(pk1.Rest, &key); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := checkOpenSSHKeyPadding(key.Pad); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var curve elliptic.Curve
|
|
switch key.Curve {
|
|
case "nistp256":
|
|
curve = elliptic.P256()
|
|
case "nistp384":
|
|
curve = elliptic.P384()
|
|
case "nistp521":
|
|
curve = elliptic.P521()
|
|
default:
|
|
return nil, errors.New("ssh: unhandled elliptic curve: " + key.Curve)
|
|
}
|
|
|
|
X, Y := elliptic.Unmarshal(curve, key.Pub)
|
|
if X == nil || Y == nil {
|
|
return nil, errors.New("ssh: failed to unmarshal public key")
|
|
}
|
|
|
|
if key.D.Cmp(curve.Params().N) >= 0 {
|
|
return nil, errors.New("ssh: scalar is out of range")
|
|
}
|
|
|
|
x, y := curve.ScalarBaseMult(key.D.Bytes())
|
|
if x.Cmp(X) != 0 || y.Cmp(Y) != 0 {
|
|
return nil, errors.New("ssh: public key does not match private key")
|
|
}
|
|
|
|
return &ecdsa.PrivateKey{
|
|
PublicKey: ecdsa.PublicKey{
|
|
Curve: curve,
|
|
X: X,
|
|
Y: Y,
|
|
},
|
|
D: key.D,
|
|
}, nil
|
|
default:
|
|
return nil, errors.New("ssh: unhandled key type")
|
|
}
|
|
}
|
|
|
|
func checkOpenSSHKeyPadding(pad []byte) error {
|
|
for i, b := range pad {
|
|
if int(b) != i+1 {
|
|
return errors.New("ssh: padding not as expected")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FingerprintLegacyMD5 returns the user presentation of the key's
|
|
// fingerprint as described by RFC 4716 section 4.
|
|
func FingerprintLegacyMD5(pubKey PublicKey) string {
|
|
md5sum := md5.Sum(pubKey.Marshal())
|
|
hexarray := make([]string, len(md5sum))
|
|
for i, c := range md5sum {
|
|
hexarray[i] = hex.EncodeToString([]byte{c})
|
|
}
|
|
return strings.Join(hexarray, ":")
|
|
}
|
|
|
|
// FingerprintSHA256 returns the user presentation of the key's
|
|
// fingerprint as unpadded base64 encoded sha256 hash.
|
|
// This format was introduced from OpenSSH 6.8.
|
|
// https://www.openssh.com/txt/release-6.8
|
|
// https://tools.ietf.org/html/rfc4648#section-3.2 (unpadded base64 encoding)
|
|
func FingerprintSHA256(pubKey PublicKey) string {
|
|
sha256sum := sha256.Sum256(pubKey.Marshal())
|
|
hash := base64.RawStdEncoding.EncodeToString(sha256sum[:])
|
|
return "SHA256:" + hash
|
|
}
|