LEFT | RIGHT |
(no file at all) | |
1 // Copyright 2012 The Go Authors. All rights reserved. | 1 // Copyright 2012 The Go Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style | 2 // Use of this source code is governed by a BSD-style |
3 // license that can be found in the LICENSE file. | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 package ssh | 5 package agent |
6 | 6 |
7 import ( | 7 import ( |
8 "bytes" | 8 "bytes" |
| 9 "crypto/rsa" |
| 10 "crypto/x509" |
| 11 "encoding/pem" |
9 "net" | 12 "net" |
10 "os" | 13 "os" |
11 "os/exec" | 14 "os/exec" |
12 "strconv" | 15 "strconv" |
13 "testing" | 16 "testing" |
| 17 |
| 18 "code.google.com/p/gosshnew/ssh" |
14 ) | 19 ) |
15 | 20 |
16 func startAgent(t *testing.T) (address string, cleanup func()) { | 21 const rsaKeySerialized = `-----BEGIN RSA PRIVATE KEY----- |
| 22 MIIBOwIBAAJBALdGZxkXDAjsYk10ihwU6Id2KeILz1TAJuoq4tOgDWxEEGeTrcld |
| 23 r/ZwVaFzjWzxaf6zQIJbfaSEAhqD5yo72+sCAwEAAQJBAK8PEVU23Wj8mV0QjwcJ |
| 24 tZ4GcTUYQL7cF4+ezTCE9a1NrGnCP2RuQkHEKxuTVrxXt+6OF15/1/fuXnxKjmJC |
| 25 nxkCIQDaXvPPBi0c7vAxGwNY9726x01/dNbHCE0CBtcotobxpwIhANbbQbh3JHVW |
| 26 2haQh4fAG5mhesZKAGcxTyv4mQ7uMSQdAiAj+4dzMpJWdSzQ+qGHlHMIBvVHLkqB |
| 27 y2VdEyF7DPCZewIhAI7GOI/6LDIFOvtPo6Bj2nNmyQ1HU6k/LRtNIXi4c9NJAiAr |
| 28 rrxx26itVhJmcvoUhOjwuzSlP2bE5VHAvkGB352YBg== |
| 29 -----END RSA PRIVATE KEY-----` |
| 30 |
| 31 var rsaKey ssh.Signer |
| 32 var rawRSAKey *rsa.PrivateKey |
| 33 |
| 34 func init() { |
| 35 » block, _ := pem.Decode([]byte(rsaKeySerialized)) |
| 36 » if block == nil { |
| 37 » » panic("ssh: no key found") |
| 38 » } |
| 39 |
| 40 » rawRSAKey, _ = x509.ParsePKCS1PrivateKey(block.Bytes) |
| 41 » rsaKey, _ = ssh.NewSignerFromKey(rawRSAKey) |
| 42 } |
| 43 |
| 44 func startAgent(t *testing.T) (client *AgentClient, cleanup func()) { |
17 bin, err := exec.LookPath("ssh-agent") | 45 bin, err := exec.LookPath("ssh-agent") |
18 if err != nil { | 46 if err != nil { |
19 t.Skip("could not find ssh-agent") | 47 t.Skip("could not find ssh-agent") |
20 } | 48 } |
21 | 49 |
22 cmd := exec.Command(bin, "-s") | 50 cmd := exec.Command(bin, "-s") |
23 out, err := cmd.Output() | 51 out, err := cmd.Output() |
24 if err != nil { | 52 if err != nil { |
25 t.Fatalf("cmd.Output: %v", err) | 53 t.Fatalf("cmd.Output: %v", err) |
26 } | 54 } |
27 | 55 |
28 /* Output looks like: | 56 /* Output looks like: |
29 | 57 |
30 SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_A
UTH_SOCK; | 58 SSH_AUTH_SOCK=/tmp/ssh-P65gpcqArqvH/agent.15541; export SSH_A
UTH_SOCK; |
31 SSH_AGENT_PID=15542; export SSH_AGENT_PID; | 59 SSH_AGENT_PID=15542; export SSH_AGENT_PID; |
32 echo Agent pid 15542; | 60 echo Agent pid 15542; |
33 */ | 61 */ |
34 fields := bytes.Split(out, []byte(";")) | 62 fields := bytes.Split(out, []byte(";")) |
35 line := bytes.SplitN(fields[0], []byte("="), 2) | 63 line := bytes.SplitN(fields[0], []byte("="), 2) |
36 socket := line[1] | 64 socket := line[1] |
37 | 65 |
38 line = bytes.SplitN(fields[2], []byte("="), 2) | 66 line = bytes.SplitN(fields[2], []byte("="), 2) |
39 pidStr := line[1] | 67 pidStr := line[1] |
40 pid, err := strconv.Atoi(string(pidStr)) | 68 pid, err := strconv.Atoi(string(pidStr)) |
41 if err != nil { | 69 if err != nil { |
42 t.Fatalf("Atoi(%q): %v", pidStr, err) | 70 t.Fatalf("Atoi(%q): %v", pidStr, err) |
43 } | 71 } |
44 » return string(socket), func() { | 72 |
| 73 » conn, err := net.Dial("unix", string(socket)) |
| 74 » if err != nil { |
| 75 » » t.Fatalf("net.Dial: %v", err) |
| 76 » } |
| 77 |
| 78 » ac := NewAgentClient(conn) |
| 79 » return ac, func() { |
45 proc, _ := os.FindProcess(pid) | 80 proc, _ := os.FindProcess(pid) |
46 if proc != nil { | 81 if proc != nil { |
47 proc.Kill() | 82 proc.Kill() |
48 } | 83 } |
| 84 ac.Close() |
49 } | 85 } |
50 } | 86 } |
51 | 87 |
52 func TestAgent(t *testing.T) { | 88 func TestAgent(t *testing.T) { |
53 » socket, cleanup := startAgent(t) | 89 » agent, cleanup := startAgent(t) |
54 defer cleanup() | 90 defer cleanup() |
55 » conn, err := net.Dial("unix", socket) | 91 » keys, err := agent.List() |
56 » if err != nil { | |
57 » » t.Fatalf("net.Dial: %v", err) | |
58 » } | |
59 » defer conn.Close() | |
60 | |
61 » ac := NewAgentClient(conn) | |
62 » keys, err := ac.RequestIdentities() | |
63 if err != nil { | 92 if err != nil { |
64 t.Fatalf("RequestIdentities: %v", err) | 93 t.Fatalf("RequestIdentities: %v", err) |
65 } | 94 } |
66 if len(keys) > 0 { | 95 if len(keys) > 0 { |
67 t.Errorf("got %d keys, want 0", len(keys)) | 96 t.Errorf("got %d keys, want 0", len(keys)) |
68 } | 97 } |
69 » if err := ac.insert(rsaKey, "comment"); err != nil { | 98 » if err := agent.Insert(rawRSAKey, "comment"); err != nil { |
70 t.Fatalf("insert: %v", err) | 99 t.Fatalf("insert: %v", err) |
71 } | 100 } |
72 » if keys, _ := ac.RequestIdentities(); len(keys) != 1 || keys[0].Comment
!= "comment" { | 101 » if keys, _ := agent.List(); len(keys) != 1 || keys[0].Comment != "commen
t" { |
73 t.Fatalf("got %v, want 1 key with comment `comment`", keys) | 102 t.Fatalf("got %v, want 1 key with comment `comment`", keys) |
74 } | 103 } |
75 data := []byte("hello") | 104 data := []byte("hello") |
76 » sigBytes, err := ac.SignRequest(rsaKey.PublicKey(), data) | 105 » sigBytes, err := agent.Sign(rsaKey.PublicKey(), data) |
77 if err != nil { | 106 if err != nil { |
78 » » t.Fatalf("SignRequest: %v", err) | 107 » » t.Fatalf("Sign: %v", err) |
79 } | 108 } |
80 » sig, _, ok := parseSignatureBody(sigBytes) | 109 |
81 » if !ok { | 110 » type Sig struct { |
82 » » t.Fatalf("parseSignatureBody(%q) failed", sig) | 111 » » Algo string |
| 112 » » Blob []byte |
83 } | 113 } |
| 114 sig := Sig{} |
| 115 if err := ssh.Unmarshal(sigBytes, &sig); err != nil { |
| 116 t.Fatalf("parseSignatureBody(%q) failed", sigBytes) |
| 117 } |
| 118 |
84 if !rsaKey.PublicKey().Verify(data, sig.Blob) { | 119 if !rsaKey.PublicKey().Verify(data, sig.Blob) { |
85 t.Fatalf("key signature does not validate") | 120 t.Fatalf("key signature does not validate") |
86 } | 121 } |
87 } | 122 } |
| 123 |
| 124 // netPipe is analogous to net.Pipe, but it uses a real net.Conn, and |
| 125 // therefore is buffered (net.Pipe deadlocks if both sides start with |
| 126 // a write.) |
| 127 func netPipe() (net.Conn, net.Conn, error) { |
| 128 listener, err := net.Listen("tcp", "127.0.0.1:0") |
| 129 if err != nil { |
| 130 return nil, nil, err |
| 131 } |
| 132 defer listener.Close() |
| 133 c1, err := net.Dial("tcp", listener.Addr().String()) |
| 134 if err != nil { |
| 135 return nil, nil, err |
| 136 } |
| 137 |
| 138 c2, err := listener.Accept() |
| 139 if err != nil { |
| 140 c1.Close() |
| 141 return nil, nil, err |
| 142 } |
| 143 |
| 144 return c1, c2, nil |
| 145 } |
| 146 |
| 147 func TestAuth(t *testing.T) { |
| 148 a, b, err := netPipe() |
| 149 if err != nil { |
| 150 t.Fatalf("netPipe: %v", err) |
| 151 } |
| 152 |
| 153 defer a.Close() |
| 154 defer b.Close() |
| 155 |
| 156 agent, cleanup := startAgent(t) |
| 157 defer cleanup() |
| 158 |
| 159 if err := agent.Insert(rawRSAKey, "comment"); err != nil { |
| 160 t.Errorf("Insert: %v", err) |
| 161 } |
| 162 |
| 163 serverConf := ssh.ServerConfig{} |
| 164 serverConf.AddHostKey(rsaKey) |
| 165 serverConf.PublicKeyCallback = func(c ssh.ConnMetadata, algo string, pub
key []byte) bool { |
| 166 return bytes.Equal(pubkey, ssh.MarshalPublicKey(rsaKey.PublicKey
())) |
| 167 } |
| 168 |
| 169 go func() { |
| 170 _, err := ssh.Server(a, &serverConf) |
| 171 if err != nil { |
| 172 t.Fatalf("Server: %v", err) |
| 173 } |
| 174 }() |
| 175 |
| 176 conf := ssh.ClientConfig{} |
| 177 conf.Auth = append(conf.Auth, ssh.ClientAuthKeyring(agent)) |
| 178 if _, err := ssh.Client(b, &conf); err != nil { |
| 179 t.Fatalf("Client: %v", err) |
| 180 } |
| 181 } |
LEFT | RIGHT |