ssh: support SSH agent signature flags and custom extensions

This commit implements two new features. To preserve backwards
compatibility the new methods are added to an `ExtendedAgent` interface
which extends `Agent`. The client code implements `ExtendedAgent`
(which extends Agent) so you can call these additional methods against
SSH agents such as the OpenSSH agent. The ServeAgent method still
accepts Agent but will attempt to upcast the agent to `ExtendedAgent`
as needed, so if you supply an ExtendedAgent implementation you can
implement these additional methods (which keyring does).

The first feature is supporting the standard flags that can be passed to
SSH Sign requests, requesting that RSA signatures use SHA-256 or
SHA-512. See section 4.5.1 of the SSH agent protocol draft:
https://tools.ietf.org/html/draft-miller-ssh-agent-02

The second feature is supporting calling custom extensions from clients
and implementing custom extensions from servers. See section 4.7 of the
SSH agent protocol draft:
https://tools.ietf.org/html/draft-miller-ssh-agent-02

Change-Id: I0f74feb893762c27e921ec37604d3a46434ee6ef
GitHub-Last-Rev: 2e23fd01c0e95b664e8507682f0bd5bd61d4c146
GitHub-Pull-Request: golang/crypto#53
Reviewed-on: https://go-review.googlesource.com/c/123955
Reviewed-by: Han-Wen Nienhuys <hanwen@google.com>
Run-TryBot: Han-Wen Nienhuys <hanwen@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Ian Haken
2018-10-29 10:30:14 +00:00
committed by Han-Wen Nienhuys
parent 45a5f77698
commit dab2b1051b
7 changed files with 382 additions and 26 deletions
+63 -5
View File
@@ -20,7 +20,7 @@ import (
)
// startOpenSSHAgent executes ssh-agent, and returns an Agent interface to it.
func startOpenSSHAgent(t *testing.T) (client Agent, socket string, cleanup func()) {
func startOpenSSHAgent(t *testing.T) (client ExtendedAgent, socket string, cleanup func()) {
if testing.Short() {
// ssh-agent is not always available, and the key
// types supported vary by platform.
@@ -79,13 +79,12 @@ func startOpenSSHAgent(t *testing.T) (client Agent, socket string, cleanup func(
}
}
// startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client.
func startKeyringAgent(t *testing.T) (client Agent, cleanup func()) {
func startAgent(t *testing.T, agent Agent) (client ExtendedAgent, cleanup func()) {
c1, c2, err := netPipe()
if err != nil {
t.Fatalf("netPipe: %v", err)
}
go ServeAgent(NewKeyring(), c2)
go ServeAgent(agent, c2)
return NewClient(c1), func() {
c1.Close()
@@ -93,6 +92,11 @@ func startKeyringAgent(t *testing.T) (client Agent, cleanup func()) {
}
}
// startKeyringAgent uses Keyring to simulate a ssh-agent Server and returns a client.
func startKeyringAgent(t *testing.T) (client ExtendedAgent, cleanup func()) {
return startAgent(t, NewKeyring())
}
func testOpenSSHAgent(t *testing.T, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
agent, _, cleanup := startOpenSSHAgent(t)
defer cleanup()
@@ -107,7 +111,7 @@ func testKeyringAgent(t *testing.T, key interface{}, cert *ssh.Certificate, life
testAgentInterface(t, agent, key, cert, lifetimeSecs)
}
func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
func testAgentInterface(t *testing.T, agent ExtendedAgent, key interface{}, cert *ssh.Certificate, lifetimeSecs uint32) {
signer, err := ssh.NewSignerFromKey(key)
if err != nil {
t.Fatalf("NewSignerFromKey(%T): %v", key, err)
@@ -159,6 +163,25 @@ func testAgentInterface(t *testing.T, agent Agent, key interface{}, cert *ssh.Ce
t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
}
// For tests on RSA keys, try signing with SHA-256 and SHA-512 flags
if pubKey.Type() == "ssh-rsa" {
sshFlagTest := func(flag SignatureFlags, expectedSigFormat string) {
sig, err = agent.SignWithFlags(pubKey, data, flag)
if err != nil {
t.Fatalf("SignWithFlags(%s): %v", pubKey.Type(), err)
}
if sig.Format != expectedSigFormat {
t.Fatalf("Signature format didn't match expected value: %s != %s", sig.Format, expectedSigFormat)
}
if err := pubKey.Verify(data, sig); err != nil {
t.Fatalf("Verify(%s): %v", pubKey.Type(), err)
}
}
sshFlagTest(0, ssh.SigAlgoRSA)
sshFlagTest(SignatureFlagRsaSha256, ssh.SigAlgoRSASHA2256)
sshFlagTest(SignatureFlagRsaSha512, ssh.SigAlgoRSASHA2512)
}
// If the key has a lifetime, is it removed when it should be?
if lifetimeSecs > 0 {
time.Sleep(time.Second*time.Duration(lifetimeSecs) + 100*time.Millisecond)
@@ -377,3 +400,38 @@ func testAgentLifetime(t *testing.T, agent Agent) {
t.Errorf("Want 0 keys, got %v", len(keys))
}
}
type keyringExtended struct {
*keyring
}
func (r *keyringExtended) Extension(extensionType string, contents []byte) ([]byte, error) {
if extensionType != "my-extension@example.com" {
return []byte{agentExtensionFailure}, nil
}
return append([]byte{agentSuccess}, contents...), nil
}
func TestAgentExtensions(t *testing.T) {
agent, _, cleanup := startOpenSSHAgent(t)
defer cleanup()
result, err := agent.Extension("my-extension@example.com", []byte{0x00, 0x01, 0x02})
if err == nil {
t.Fatal("should have gotten agent extension failure")
}
agent, cleanup = startAgent(t, &keyringExtended{})
defer cleanup()
result, err = agent.Extension("my-extension@example.com", []byte{0x00, 0x01, 0x02})
if err != nil {
t.Fatalf("agent extension failure: %v", err)
}
if len(result) != 4 || !bytes.Equal(result, []byte{agentSuccess, 0x00, 0x01, 0x02}) {
t.Fatalf("agent extension result invalid: %v", result)
}
result, err = agent.Extension("bad-extension@example.com", []byte{0x00, 0x01, 0x02})
if err == nil {
t.Fatal("should have gotten agent extension failure")
}
}