func parseOpenSSHPrivateKey()

in ssh/keys.go [1286:1452]


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")
	}
}