mirror of
https://github.com/Luzifer/cloudkeys-go.git
synced 2024-11-14 17:02:43 +00:00
Knut Ahlers
a1df72edc5
commitf0db1ff1f8
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:19:56 2017 +0100 Mark option as deprecated Signed-off-by: Knut Ahlers <knut@ahlers.me> commit9891df2a16
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:11:56 2017 +0100 Fix: Typo Signed-off-by: Knut Ahlers <knut@ahlers.me> commit836006de64
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:04:20 2017 +0100 Add new dependencies Signed-off-by: Knut Ahlers <knut@ahlers.me> commitd64fee60c8
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 11:55:52 2017 +0100 Replace insecure password hashing Prior this commit passwords were hashed with a static salt and using the SHA1 hashing function. This could lead to passwords being attackable in case someone gets access to the raw data stored inside the database. This commit introduces password hashing using bcrypt hashing function which addresses this issue. Old passwords are not automatically re-hashed as they are unknown. Replacing the old password scheme is not that easy and needs #10 to be solved. Therefore the old hashing scheme is kept for compatibility reason. Signed-off-by: Knut Ahlers <knut@ahlers.me> Signed-off-by: Knut Ahlers <knut@ahlers.me> closes #14 closes #15
523 lines
12 KiB
Go
523 lines
12 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 agent
|
|
|
|
import (
|
|
"crypto/dsa"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rsa"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"math/big"
|
|
|
|
"golang.org/x/crypto/ed25519"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// Server wraps an Agent and uses it to implement the agent side of
|
|
// the SSH-agent, wire protocol.
|
|
type server struct {
|
|
agent Agent
|
|
}
|
|
|
|
func (s *server) processRequestBytes(reqData []byte) []byte {
|
|
rep, err := s.processRequest(reqData)
|
|
if err != nil {
|
|
if err != errLocked {
|
|
// TODO(hanwen): provide better logging interface?
|
|
log.Printf("agent %d: %v", reqData[0], err)
|
|
}
|
|
return []byte{agentFailure}
|
|
}
|
|
|
|
if err == nil && rep == nil {
|
|
return []byte{agentSuccess}
|
|
}
|
|
|
|
return ssh.Marshal(rep)
|
|
}
|
|
|
|
func marshalKey(k *Key) []byte {
|
|
var record struct {
|
|
Blob []byte
|
|
Comment string
|
|
}
|
|
record.Blob = k.Marshal()
|
|
record.Comment = k.Comment
|
|
|
|
return ssh.Marshal(&record)
|
|
}
|
|
|
|
// See [PROTOCOL.agent], section 2.5.1.
|
|
const agentV1IdentitiesAnswer = 2
|
|
|
|
type agentV1IdentityMsg struct {
|
|
Numkeys uint32 `sshtype:"2"`
|
|
}
|
|
|
|
type agentRemoveIdentityMsg struct {
|
|
KeyBlob []byte `sshtype:"18"`
|
|
}
|
|
|
|
type agentLockMsg struct {
|
|
Passphrase []byte `sshtype:"22"`
|
|
}
|
|
|
|
type agentUnlockMsg struct {
|
|
Passphrase []byte `sshtype:"23"`
|
|
}
|
|
|
|
func (s *server) processRequest(data []byte) (interface{}, error) {
|
|
switch data[0] {
|
|
case agentRequestV1Identities:
|
|
return &agentV1IdentityMsg{0}, nil
|
|
|
|
case agentRemoveAllV1Identities:
|
|
return nil, nil
|
|
|
|
case agentRemoveIdentity:
|
|
var req agentRemoveIdentityMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var wk wireKey
|
|
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, s.agent.Remove(&Key{Format: wk.Format, Blob: req.KeyBlob})
|
|
|
|
case agentRemoveAllIdentities:
|
|
return nil, s.agent.RemoveAll()
|
|
|
|
case agentLock:
|
|
var req agentLockMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, s.agent.Lock(req.Passphrase)
|
|
|
|
case agentUnlock:
|
|
var req agentUnlockMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, s.agent.Unlock(req.Passphrase)
|
|
|
|
case agentSignRequest:
|
|
var req signRequestAgentMsg
|
|
if err := ssh.Unmarshal(data, &req); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var wk wireKey
|
|
if err := ssh.Unmarshal(req.KeyBlob, &wk); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
k := &Key{
|
|
Format: wk.Format,
|
|
Blob: req.KeyBlob,
|
|
}
|
|
|
|
sig, err := s.agent.Sign(k, req.Data) // TODO(hanwen): flags.
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &signResponseAgentMsg{SigBlob: ssh.Marshal(sig)}, nil
|
|
|
|
case agentRequestIdentities:
|
|
keys, err := s.agent.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rep := identitiesAnswerAgentMsg{
|
|
NumKeys: uint32(len(keys)),
|
|
}
|
|
for _, k := range keys {
|
|
rep.Keys = append(rep.Keys, marshalKey(k)...)
|
|
}
|
|
return rep, nil
|
|
|
|
case agentAddIDConstrained, agentAddIdentity:
|
|
return nil, s.insertIdentity(data)
|
|
}
|
|
|
|
return nil, fmt.Errorf("unknown opcode %d", data[0])
|
|
}
|
|
|
|
func parseConstraints(constraints []byte) (lifetimeSecs uint32, confirmBeforeUse bool, extensions []ConstraintExtension, err error) {
|
|
for len(constraints) != 0 {
|
|
switch constraints[0] {
|
|
case agentConstrainLifetime:
|
|
lifetimeSecs = binary.BigEndian.Uint32(constraints[1:5])
|
|
constraints = constraints[5:]
|
|
case agentConstrainConfirm:
|
|
confirmBeforeUse = true
|
|
constraints = constraints[1:]
|
|
case agentConstrainExtension:
|
|
var msg constrainExtensionAgentMsg
|
|
if err = ssh.Unmarshal(constraints, &msg); err != nil {
|
|
return 0, false, nil, err
|
|
}
|
|
extensions = append(extensions, ConstraintExtension{
|
|
ExtensionName: msg.ExtensionName,
|
|
ExtensionDetails: msg.ExtensionDetails,
|
|
})
|
|
constraints = msg.Rest
|
|
default:
|
|
return 0, false, nil, fmt.Errorf("unknown constraint type: %d", constraints[0])
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func setConstraints(key *AddedKey, constraintBytes []byte) error {
|
|
lifetimeSecs, confirmBeforeUse, constraintExtensions, err := parseConstraints(constraintBytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
key.LifetimeSecs = lifetimeSecs
|
|
key.ConfirmBeforeUse = confirmBeforeUse
|
|
key.ConstraintExtensions = constraintExtensions
|
|
return nil
|
|
}
|
|
|
|
func parseRSAKey(req []byte) (*AddedKey, error) {
|
|
var k rsaKeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
if k.E.BitLen() > 30 {
|
|
return nil, errors.New("agent: RSA public exponent too large")
|
|
}
|
|
priv := &rsa.PrivateKey{
|
|
PublicKey: rsa.PublicKey{
|
|
E: int(k.E.Int64()),
|
|
N: k.N,
|
|
},
|
|
D: k.D,
|
|
Primes: []*big.Int{k.P, k.Q},
|
|
}
|
|
priv.Precompute()
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseEd25519Key(req []byte) (*AddedKey, error) {
|
|
var k ed25519KeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
priv := ed25519.PrivateKey(k.Priv)
|
|
|
|
addedKey := &AddedKey{PrivateKey: &priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseDSAKey(req []byte) (*AddedKey, error) {
|
|
var k dsaKeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
priv := &dsa.PrivateKey{
|
|
PublicKey: dsa.PublicKey{
|
|
Parameters: dsa.Parameters{
|
|
P: k.P,
|
|
Q: k.Q,
|
|
G: k.G,
|
|
},
|
|
Y: k.Y,
|
|
},
|
|
X: k.X,
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func unmarshalECDSA(curveName string, keyBytes []byte, privScalar *big.Int) (priv *ecdsa.PrivateKey, err error) {
|
|
priv = &ecdsa.PrivateKey{
|
|
D: privScalar,
|
|
}
|
|
|
|
switch curveName {
|
|
case "nistp256":
|
|
priv.Curve = elliptic.P256()
|
|
case "nistp384":
|
|
priv.Curve = elliptic.P384()
|
|
case "nistp521":
|
|
priv.Curve = elliptic.P521()
|
|
default:
|
|
return nil, fmt.Errorf("agent: unknown curve %q", curveName)
|
|
}
|
|
|
|
priv.X, priv.Y = elliptic.Unmarshal(priv.Curve, keyBytes)
|
|
if priv.X == nil || priv.Y == nil {
|
|
return nil, errors.New("agent: point not on curve")
|
|
}
|
|
|
|
return priv, nil
|
|
}
|
|
|
|
func parseEd25519Cert(req []byte) (*AddedKey, error) {
|
|
var k ed25519CertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
priv := ed25519.PrivateKey(k.Priv)
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad ED25519 certificate")
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseECDSAKey(req []byte) (*AddedKey, error) {
|
|
var k ecdsaKeyMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
priv, err := unmarshalECDSA(k.Curve, k.KeyBytes, k.D)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseRSACert(req []byte) (*AddedKey, error) {
|
|
var k rsaCertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad RSA certificate")
|
|
}
|
|
|
|
// An RSA publickey as marshaled by rsaPublicKey.Marshal() in keys.go
|
|
var rsaPub struct {
|
|
Name string
|
|
E *big.Int
|
|
N *big.Int
|
|
}
|
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &rsaPub); err != nil {
|
|
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
|
}
|
|
|
|
if rsaPub.E.BitLen() > 30 {
|
|
return nil, errors.New("agent: RSA public exponent too large")
|
|
}
|
|
|
|
priv := rsa.PrivateKey{
|
|
PublicKey: rsa.PublicKey{
|
|
E: int(rsaPub.E.Int64()),
|
|
N: rsaPub.N,
|
|
},
|
|
D: k.D,
|
|
Primes: []*big.Int{k.Q, k.P},
|
|
}
|
|
priv.Precompute()
|
|
|
|
addedKey := &AddedKey{PrivateKey: &priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseDSACert(req []byte) (*AddedKey, error) {
|
|
var k dsaCertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad DSA certificate")
|
|
}
|
|
|
|
// A DSA publickey as marshaled by dsaPublicKey.Marshal() in keys.go
|
|
var w struct {
|
|
Name string
|
|
P, Q, G, Y *big.Int
|
|
}
|
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &w); err != nil {
|
|
return nil, fmt.Errorf("agent: Unmarshal failed to parse public key: %v", err)
|
|
}
|
|
|
|
priv := &dsa.PrivateKey{
|
|
PublicKey: dsa.PublicKey{
|
|
Parameters: dsa.Parameters{
|
|
P: w.P,
|
|
Q: w.Q,
|
|
G: w.G,
|
|
},
|
|
Y: w.Y,
|
|
},
|
|
X: k.X,
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func parseECDSACert(req []byte) (*AddedKey, error) {
|
|
var k ecdsaCertMsg
|
|
if err := ssh.Unmarshal(req, &k); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pubKey, err := ssh.ParsePublicKey(k.CertBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cert, ok := pubKey.(*ssh.Certificate)
|
|
if !ok {
|
|
return nil, errors.New("agent: bad ECDSA certificate")
|
|
}
|
|
|
|
// An ECDSA publickey as marshaled by ecdsaPublicKey.Marshal() in keys.go
|
|
var ecdsaPub struct {
|
|
Name string
|
|
ID string
|
|
Key []byte
|
|
}
|
|
if err := ssh.Unmarshal(cert.Key.Marshal(), &ecdsaPub); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
priv, err := unmarshalECDSA(ecdsaPub.ID, ecdsaPub.Key, k.D)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
addedKey := &AddedKey{PrivateKey: priv, Certificate: cert, Comment: k.Comments}
|
|
if err := setConstraints(addedKey, k.Constraints); err != nil {
|
|
return nil, err
|
|
}
|
|
return addedKey, nil
|
|
}
|
|
|
|
func (s *server) insertIdentity(req []byte) error {
|
|
var record struct {
|
|
Type string `sshtype:"17|25"`
|
|
Rest []byte `ssh:"rest"`
|
|
}
|
|
|
|
if err := ssh.Unmarshal(req, &record); err != nil {
|
|
return err
|
|
}
|
|
|
|
var addedKey *AddedKey
|
|
var err error
|
|
|
|
switch record.Type {
|
|
case ssh.KeyAlgoRSA:
|
|
addedKey, err = parseRSAKey(req)
|
|
case ssh.KeyAlgoDSA:
|
|
addedKey, err = parseDSAKey(req)
|
|
case ssh.KeyAlgoECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521:
|
|
addedKey, err = parseECDSAKey(req)
|
|
case ssh.KeyAlgoED25519:
|
|
addedKey, err = parseEd25519Key(req)
|
|
case ssh.CertAlgoRSAv01:
|
|
addedKey, err = parseRSACert(req)
|
|
case ssh.CertAlgoDSAv01:
|
|
addedKey, err = parseDSACert(req)
|
|
case ssh.CertAlgoECDSA256v01, ssh.CertAlgoECDSA384v01, ssh.CertAlgoECDSA521v01:
|
|
addedKey, err = parseECDSACert(req)
|
|
case ssh.CertAlgoED25519v01:
|
|
addedKey, err = parseEd25519Cert(req)
|
|
default:
|
|
return fmt.Errorf("agent: not implemented: %q", record.Type)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.agent.Add(*addedKey)
|
|
}
|
|
|
|
// ServeAgent serves the agent protocol on the given connection. It
|
|
// returns when an I/O error occurs.
|
|
func ServeAgent(agent Agent, c io.ReadWriter) error {
|
|
s := &server{agent}
|
|
|
|
var length [4]byte
|
|
for {
|
|
if _, err := io.ReadFull(c, length[:]); err != nil {
|
|
return err
|
|
}
|
|
l := binary.BigEndian.Uint32(length[:])
|
|
if l > maxAgentResponseBytes {
|
|
// We also cap requests.
|
|
return fmt.Errorf("agent: request too large: %d", l)
|
|
}
|
|
|
|
req := make([]byte, l)
|
|
if _, err := io.ReadFull(c, req); err != nil {
|
|
return err
|
|
}
|
|
|
|
repData := s.processRequestBytes(req)
|
|
if len(repData) > maxAgentResponseBytes {
|
|
return fmt.Errorf("agent: reply too large: %d bytes", len(repData))
|
|
}
|
|
|
|
binary.BigEndian.PutUint32(length[:], uint32(len(repData)))
|
|
if _, err := c.Write(length[:]); err != nil {
|
|
return err
|
|
}
|
|
if _, err := c.Write(repData); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|