mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-29 03:40:31 +00:00
250 lines
6.6 KiB
Go
250 lines
6.6 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 (
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
// See [PROTOCOL.agent], section 3.
|
|
const (
|
|
// 3.2 Requests from client to agent for protocol 2 key operations
|
|
agentRequestIdentities = 11
|
|
agentSignRequest = 13
|
|
agentAddIdentity = 17
|
|
agentRemoveIdentity = 18
|
|
agentRemoveAllIdentities = 19
|
|
agentAddIdConstrained = 25
|
|
|
|
// 3.3 Key-type independent requests from client to agent
|
|
agentAddSmartcardKey = 20
|
|
agentRemoveSmartcardKey = 21
|
|
agentLock = 22
|
|
agentUnlock = 23
|
|
agentAddSmartcardKeyConstrained = 26
|
|
|
|
// 3.4 Generic replies from agent to client
|
|
agentFailure = 5
|
|
agentSuccess = 6
|
|
|
|
// 3.6 Replies from agent to client for protocol 2 key operations
|
|
agentIdentitiesAnswer = 12
|
|
agentSignResponse = 14
|
|
|
|
// 3.7 Key constraint identifiers
|
|
agentConstrainLifetime = 1
|
|
agentConstrainConfirm = 2
|
|
)
|
|
|
|
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
|
// is a sanity check, not a limit in the spec.
|
|
const maxAgentResponseBytes = 16 << 20
|
|
|
|
// Agent messages:
|
|
// These structures mirror the wire format of the corresponding ssh agent
|
|
// messages found in [PROTOCOL.agent].
|
|
|
|
type failureAgentMsg struct{}
|
|
|
|
type successAgentMsg struct{}
|
|
|
|
// See [PROTOCOL.agent], section 2.5.2.
|
|
type requestIdentitiesAgentMsg struct{}
|
|
|
|
// See [PROTOCOL.agent], section 2.5.2.
|
|
type identitiesAnswerAgentMsg struct {
|
|
NumKeys uint32
|
|
Keys []byte `ssh:"rest"`
|
|
}
|
|
|
|
// See [PROTOCOL.agent], section 2.6.2.
|
|
type signRequestAgentMsg struct {
|
|
KeyBlob []byte
|
|
Data []byte
|
|
Flags uint32
|
|
}
|
|
|
|
// See [PROTOCOL.agent], section 2.6.2.
|
|
type signResponseAgentMsg struct {
|
|
SigBlob []byte
|
|
}
|
|
|
|
// AgentKey represents a protocol 2 key as defined in [PROTOCOL.agent],
|
|
// section 2.5.2.
|
|
type AgentKey struct {
|
|
blob []byte
|
|
Comment string
|
|
}
|
|
|
|
// String returns the storage form of an agent key with the format, base64
|
|
// encoded serialized key, and the comment if it is not empty.
|
|
func (ak *AgentKey) String() string {
|
|
algo, _, ok := parseString(ak.blob)
|
|
if !ok {
|
|
return "ssh: malformed key"
|
|
}
|
|
|
|
s := string(algo) + " " + base64.StdEncoding.EncodeToString(ak.blob)
|
|
|
|
if ak.Comment != "" {
|
|
s += " " + ak.Comment
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// Key returns an agent's public key as one of the supported key or certificate types.
|
|
func (ak *AgentKey) Key() (PublicKey, error) {
|
|
if key, _, ok := ParsePublicKey(ak.blob); ok {
|
|
return key, nil
|
|
}
|
|
return nil, errors.New("ssh: failed to parse key blob")
|
|
}
|
|
|
|
func parseAgentKey(in []byte) (out *AgentKey, rest []byte, ok bool) {
|
|
ak := new(AgentKey)
|
|
|
|
if ak.blob, in, ok = parseString(in); !ok {
|
|
return
|
|
}
|
|
|
|
comment, in, ok := parseString(in)
|
|
if !ok {
|
|
return
|
|
}
|
|
ak.Comment = string(comment)
|
|
|
|
return ak, in, true
|
|
}
|
|
|
|
// AgentClient provides a means to communicate with an ssh agent process based
|
|
// on the protocol described in [PROTOCOL.agent]?rev=1.6.
|
|
type AgentClient struct {
|
|
// conn is typically represented by using a *net.UnixConn
|
|
conn io.ReadWriter
|
|
// mu is used to prevent concurrent access to the agent
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewAgentClient creates and returns a new *AgentClient using the
|
|
// passed in io.ReadWriter as a connection to a ssh agent.
|
|
func NewAgentClient(rw io.ReadWriter) *AgentClient {
|
|
return &AgentClient{conn: rw}
|
|
}
|
|
|
|
// sendAndReceive sends req to the agent and waits for a reply. On success,
|
|
// the reply is unmarshaled into reply and replyType is set to the first byte of
|
|
// the reply, which contains the type of the message.
|
|
func (ac *AgentClient) sendAndReceive(req []byte) (reply interface{}, replyType uint8, err error) {
|
|
// ac.mu prevents multiple, concurrent requests. Since the agent is typically
|
|
// on the same machine, we don't attempt to pipeline the requests.
|
|
ac.mu.Lock()
|
|
defer ac.mu.Unlock()
|
|
|
|
msg := make([]byte, stringLength(len(req)))
|
|
marshalString(msg, req)
|
|
if _, err = ac.conn.Write(msg); err != nil {
|
|
return
|
|
}
|
|
|
|
var respSizeBuf [4]byte
|
|
if _, err = io.ReadFull(ac.conn, respSizeBuf[:]); err != nil {
|
|
return
|
|
}
|
|
respSize, _, _ := parseUint32(respSizeBuf[:])
|
|
|
|
if respSize > maxAgentResponseBytes {
|
|
err = errors.New("ssh: agent reply too large")
|
|
return
|
|
}
|
|
|
|
buf := make([]byte, respSize)
|
|
if _, err = io.ReadFull(ac.conn, buf); err != nil {
|
|
return
|
|
}
|
|
return unmarshalAgentMsg(buf)
|
|
}
|
|
|
|
// RequestIdentities queries the agent for protocol 2 keys as defined in
|
|
// [PROTOCOL.agent] section 2.5.2.
|
|
func (ac *AgentClient) RequestIdentities() ([]*AgentKey, error) {
|
|
req := marshal(agentRequestIdentities, requestIdentitiesAgentMsg{})
|
|
|
|
msg, msgType, err := ac.sendAndReceive(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch msg := msg.(type) {
|
|
case *identitiesAnswerAgentMsg:
|
|
if msg.NumKeys > maxAgentResponseBytes/8 {
|
|
return nil, errors.New("ssh: too many keys in agent reply")
|
|
}
|
|
keys := make([]*AgentKey, msg.NumKeys)
|
|
data := msg.Keys
|
|
for i := uint32(0); i < msg.NumKeys; i++ {
|
|
var key *AgentKey
|
|
var ok bool
|
|
if key, data, ok = parseAgentKey(data); !ok {
|
|
return nil, ParseError{agentIdentitiesAnswer}
|
|
}
|
|
keys[i] = key
|
|
}
|
|
return keys, nil
|
|
case *failureAgentMsg:
|
|
return nil, errors.New("ssh: failed to list keys")
|
|
}
|
|
return nil, UnexpectedMessageError{agentIdentitiesAnswer, msgType}
|
|
}
|
|
|
|
// SignRequest requests the signing of data by the agent using a protocol 2 key
|
|
// as defined in [PROTOCOL.agent] section 2.6.2.
|
|
func (ac *AgentClient) SignRequest(key PublicKey, data []byte) ([]byte, error) {
|
|
req := marshal(agentSignRequest, signRequestAgentMsg{
|
|
KeyBlob: MarshalPublicKey(key),
|
|
Data: data,
|
|
})
|
|
|
|
msg, msgType, err := ac.sendAndReceive(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch msg := msg.(type) {
|
|
case *signResponseAgentMsg:
|
|
return msg.SigBlob, nil
|
|
case *failureAgentMsg:
|
|
return nil, errors.New("ssh: failed to sign challenge")
|
|
}
|
|
return nil, UnexpectedMessageError{agentSignResponse, msgType}
|
|
}
|
|
|
|
// unmarshalAgentMsg parses an agent message in packet, returning the parsed
|
|
// form and the message type of packet.
|
|
func unmarshalAgentMsg(packet []byte) (interface{}, uint8, error) {
|
|
if len(packet) < 1 {
|
|
return nil, 0, ParseError{0}
|
|
}
|
|
var msg interface{}
|
|
switch packet[0] {
|
|
case agentFailure:
|
|
msg = new(failureAgentMsg)
|
|
case agentSuccess:
|
|
msg = new(successAgentMsg)
|
|
case agentIdentitiesAnswer:
|
|
msg = new(identitiesAnswerAgentMsg)
|
|
case agentSignResponse:
|
|
msg = new(signResponseAgentMsg)
|
|
default:
|
|
return nil, 0, UnexpectedMessageError{0, packet[0]}
|
|
}
|
|
if err := unmarshal(msg, packet, packet[0]); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
return msg, packet[0], nil
|
|
}
|