[gcr/wip/dueno/ssh-agent: 4/5] gcr: port ssh-agent tests from gnome-keyring




commit 05b47bf2f8834e745a9d01574ccedb92953b4c23
Author: Daiki Ueno <dueno src gnome org>
Date:   Thu Feb 11 16:18:32 2021 +0100

    gcr: port ssh-agent tests from gnome-keyring
    
    This copies most of the tests and fixtures from gnome-keyring,
    exercising ssh-agent functionality.  Currently
    test-gkd-ssh-agent-interaction has not been ported yet, because the
    new implementation interacts with secret-service.

 gcr/fixtures/ssh-agent/id_dsa_encrypted       |  15 +
 gcr/fixtures/ssh-agent/id_dsa_encrypted.pub   |   1 +
 gcr/fixtures/ssh-agent/id_dsa_plain           |  12 +
 gcr/fixtures/ssh-agent/id_dsa_plain.pub       |   1 +
 gcr/fixtures/ssh-agent/id_dsa_test.pub        |   7 +
 gcr/fixtures/ssh-agent/id_ecdsa_384           |   6 +
 gcr/fixtures/ssh-agent/id_ecdsa_384.pub       |   1 +
 gcr/fixtures/ssh-agent/id_ecdsa_521           |   7 +
 gcr/fixtures/ssh-agent/id_ecdsa_521.pub       |   1 +
 gcr/fixtures/ssh-agent/id_ecdsa_encrypted     |   8 +
 gcr/fixtures/ssh-agent/id_ecdsa_encrypted.pub |   1 +
 gcr/fixtures/ssh-agent/id_ecdsa_plain         |   5 +
 gcr/fixtures/ssh-agent/id_ecdsa_plain.pub     |   1 +
 gcr/fixtures/ssh-agent/id_ecdsa_test.pub      |   1 +
 gcr/fixtures/ssh-agent/id_rsa_encrypted       |  30 ++
 gcr/fixtures/ssh-agent/id_rsa_encrypted.pub   |   1 +
 gcr/fixtures/ssh-agent/id_rsa_plain           |  27 ++
 gcr/fixtures/ssh-agent/id_rsa_plain.pub       |   1 +
 gcr/fixtures/ssh-agent/id_rsa_test.pub        |   1 +
 gcr/fixtures/ssh-agent/identity.pub           |   1 +
 gcr/gcr-ssh-agent-service.c                   |  19 +-
 gcr/gcr-ssh-agent-test.c                      | 334 ++++++++++++++
 gcr/gcr-ssh-agent-test.h                      |  94 ++++
 gcr/meson.build                               |  43 +-
 gcr/test-ssh-agent-common.c                   | 334 ++++++++++++++
 gcr/test-ssh-agent-common.h                   |  94 ++++
 gcr/test-ssh-agent-preload.c                  | 166 +++++++
 gcr/test-ssh-agent-process.c                  | 219 +++++++++
 gcr/test-ssh-agent-service.c                  | 618 ++++++++++++++++++++++++++
 gcr/test-ssh-agent-util.c                     | 119 +++++
 30 files changed, 2163 insertions(+), 5 deletions(-)
---
diff --git a/gcr/fixtures/ssh-agent/id_dsa_encrypted b/gcr/fixtures/ssh-agent/id_dsa_encrypted
new file mode 100644
index 0000000..c17fb80
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_dsa_encrypted
@@ -0,0 +1,15 @@
+-----BEGIN DSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,1ED7C6189634877B
+
+lex6pFMAspyvnqSktXA1NX/x80/Ebb9BGFaIcJAPvLduCrDR02fgwc3oE+dwSwwe
+PXZ8vJK8+AP+Z8Rkedya/3OT4vaAuSENZFCfJA+P6lXglVQplA57R4U5/P9580Ee
+l/VTumnh5Gz84hJ5TuEspDOLrq8atkc31qFbEJ+zjMLygGNGbIOzecLou2pBt1Ol
+ncx1MPIznoJl2b1NQt7rgPpcbqzCHo+/qgLgvGs7osIg8xzzp+E2ifWuwCnY4NmW
+dxLRABi4I97q7kShH7OblBQLKxDreg28sojJQ0h0y0fd6xVcoscFCvfanFh8xx/D
+rI+JV3HCRCrlB9YS6U3zB5vpbc1UQ1EaE4AxSmrSLdKsvrPGc7M+grwy/DjYerqO
+WGwFWnz/OrlXruJG9Nwyltq/YmXAxFGoSWrunkm42xUxYs8RElddQOFC0ZyfVWOi
+IOS2Bv2HkNW+lMTRoR/RIbDc90wzO0HL0Xx4v6LYSbVBZIcaOJU+stoNLeE8Fu53
+G47YU+Fd7WswdlIdXtrPjyyiWapf6+xNdhRrqB+40JJQRi2mL1NyYZ2bZkjEd0Or
+DsfFH/+DlZrjEdlqWTK2ow==
+-----END DSA PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_dsa_encrypted.pub b/gcr/fixtures/ssh-agent/id_dsa_encrypted.pub
new file mode 100644
index 0000000..a64acac
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_dsa_encrypted.pub
@@ -0,0 +1 @@
+ssh-dss 
AAAAB3NzaC1kc3MAAACBANHNmw2YHEodUj4Ae27i8Rm8uoLnpS68QEiCJx8bv9P1o0AaD0w55sH+TBzlo7vtAEDlAzIOBY3PMpy5WarELTIeXmFPzKfHL8tuxMbOPaN/wDkDZNnJZsqlyRwlQKStPcAlvLBNuMjA53u2ndMTVghtUHXETQzwxKhXf7TmvfLBAAAAFQDnF/Y8MgFCP0PpRC5ZAQo1dyDEwwAAAIEAr4iOpTeZx8i1QgQpRl+dmbBAtHTXbPiophzNJBge9lixqF0T3egN2B9wGGnumIXmnst9RPPjuu+cHCLfxhXHzLlW8MLwoiF6ZQOx9M8WcfWIl5oiGyr2e969woRf5OcMGQPOQBdws6MEtemRqq5gu6dqDqVl3xfhSZSP9LpqAI8AAACAUjiuQ3qGErsCz++qd0qrR++QA185XGXAPZqQEHcr4iKSlO17hSUYA03kOWtDaeRtJOlxjIjl9iLo3juKGFgxUfo2StScOSO2saTWFGjA4MybHCK1+mIYXRcYrq314yK2Tmbql/UGDWpcCCGXLWpSFHTaXTbJjPd6VL+TO9/8tFk=
diff --git a/gcr/fixtures/ssh-agent/id_dsa_plain b/gcr/fixtures/ssh-agent/id_dsa_plain
new file mode 100644
index 0000000..2687623
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_dsa_plain
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQDCyZij6148cb6Gp2XazVJr+zrcfSkbN1MyeR5hCgACTqcn1D4R
+huf79uWe7lvyYuPyXNedfde0iFP7Ff9k1T9i3thyYjs1scdwpbqxnAVnmN4SNv/v
+fFXPp6w0EDNqmAOwg+Pqwue7juiPVJyPBcsSPZZKTOMDCS/2fVibFCXgswIVAOW6
+ngNCg6d4Cn+GHQtEbNVtZpRdAoGAP2LhEH+zUCYri0xi32l9bMnlqDCBdw2zOMbe
+G2XDRt40W4ObiE1+PPNp/CDFuYrOjD4s7Cl4s/U+iop4DKsPcLwbdr3CoWE6hEuO
+dH0jkzcvt8kZ5Ymtm3OiqEW2ARi8rUwVJ9+bRbkfRerNrDeH/PQzwCWf7Lzp9lPT
+NVjCDtsCgYANW+HtuJWVmfXRRDJ1goMK+GXixDvBLBbFSDf74kYInt3vUBm4MKpr
+HbmC27TLRymb6IOH1ENpYT3MffZJusQTqqZJKPba5nwLvPP9lzN60bJAtqiWUsqf
+wHEh6Jx+qoMggm8i6ogJJO2zDlki5Twf1ilN+tinHdOL/2CmPPRMgAIVALKfUS61
+xqFTOY5fG1+rLwlPGvL9
+-----END DSA PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_dsa_plain.pub b/gcr/fixtures/ssh-agent/id_dsa_plain.pub
new file mode 100644
index 0000000..3b4717d
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_dsa_plain.pub
@@ -0,0 +1 @@
+ssh-dss 
AAAAB3NzaC1kc3MAAACBAMLJmKPrXjxxvoanZdrNUmv7Otx9KRs3UzJ5HmEKAAJOpyfUPhGG5/v25Z7uW/Ji4/Jc151917SIU/sV/2TVP2Le2HJiOzWxx3ClurGcBWeY3hI2/+98Vc+nrDQQM2qYA7CD4+rC57uO6I9UnI8FyxI9lkpM4wMJL/Z9WJsUJeCzAAAAFQDlup4DQoOneAp/hh0LRGzVbWaUXQAAAIA/YuEQf7NQJiuLTGLfaX1syeWoMIF3DbM4xt4bZcNG3jRbg5uITX4882n8IMW5is6MPizsKXiz9T6KingMqw9wvBt2vcKhYTqES450fSOTNy+3yRnlia2bc6KoRbYBGLytTBUn35tFuR9F6s2sN4f89DPAJZ/svOn2U9M1WMIO2wAAAIANW+HtuJWVmfXRRDJ1goMK+GXixDvBLBbFSDf74kYInt3vUBm4MKprHbmC27TLRymb6IOH1ENpYT3MffZJusQTqqZJKPba5nwLvPP9lzN60bJAtqiWUsqfwHEh6Jx+qoMggm8i6ogJJO2zDlki5Twf1ilN+tinHdOL/2CmPPRMgA==
diff --git a/gcr/fixtures/ssh-agent/id_dsa_test.pub b/gcr/fixtures/ssh-agent/id_dsa_test.pub
new file mode 100644
index 0000000..33653b2
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_dsa_test.pub
@@ -0,0 +1,7 @@
+# A comment should be ignored
+
+# Blank lines should be ignored
+
+ssh-dss 
AAAAB3NzaC1kc3MAAACBANHNmw2YHEodUj4Ae27i8Rm8uoLnpS68QEiCJx8bv9P1o0AaD0w55sH+TBzlo7vtAEDlAzIOBY3PMpy5WarELTIeXmFPzKfHL8tuxMbOPaN/wDkDZNnJZsqlyRwlQKStPcAlvLBNuMjA53u2ndMTVghtUHXETQzwxKhXf7TmvfLBAAAAFQDnF/Y8MgFCP0PpRC5ZAQo1dyDEwwAAAIEAr4iOpTeZx8i1QgQpRl+dmbBAtHTXbPiophzNJBge9lixqF0T3egN2B9wGGnumIXmnst9RPPjuu+cHCLfxhXHzLlW8MLwoiF6ZQOx9M8WcfWIl5oiGyr2e969woRf5OcMGQPOQBdws6MEtemRqq5gu6dqDqVl3xfhSZSP9LpqAI8AAACAUjiuQ3qGErsCz++qd0qrR++QA185XGXAPZqQEHcr4iKSlO17hSUYA03kOWtDaeRtJOlxjIjl9iLo3juKGFgxUfo2StScOSO2saTWFGjA4MybHCK1+mIYXRcYrq314yK2Tmbql/UGDWpcCCGXLWpSFHTaXTbJjPd6VL+TO9/8tFk=
 A public key comment
+
+# blah blah
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_384 b/gcr/fixtures/ssh-agent/id_ecdsa_384
new file mode 100644
index 0000000..7d12859
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_384
@@ -0,0 +1,6 @@
+-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDBAcQcqfgI1Oc9lkcuvD7tQUVmjI2w1E122EO7olpF560alvAk9/hhu
+eTZpAgl3HRugBwYFK4EEACKhZANiAARoa4Enyu7wC6QYrwPrOkhTljfmen/pF2x7
+Lf+SlCpAX5w69KAXcbNPiDncXpckecfQusf/KA9KAMFQXa/eQmXkyZOjhiWkFKTz
+4TklDF2ehBhE83riZFl+JAlaQOcFka0=
+-----END EC PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_384.pub b/gcr/fixtures/ssh-agent/id_ecdsa_384.pub
new file mode 100644
index 0000000..0ca36a4
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_384.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp384 
AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBGhrgSfK7vALpBivA+s6SFOWN+Z6f+kXbHst/5KUKkBfnDr0oBdxs0+IOdxelyR5x9C6x/8oD0oAwVBdr95CZeTJk6OGJaQUpPPhOSUMXZ6EGETzeuJkWX4kCVpA5wWRrQ==
 A public key comment
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_521 b/gcr/fixtures/ssh-agent/id_ecdsa_521
new file mode 100644
index 0000000..32f2251
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_521
@@ -0,0 +1,7 @@
+-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIBLLaP4NDe6/pO69bC8xRzKcRKZ/HFebSnl6IYerSBv1uXSuIwhK5s
+6xhFUfecUCrImZYaDwFoeB8pa5D66siqWsygBwYFK4EEACOhgYkDgYYABAEuCDfR
+6i7TTI99PeX85cbIhzaO3ho/s9QIdAIeut5ybrTUTgDepo3A+PxHLgAwIxMgtkB6
+0HVSE+NL47ewKUWSOAAQP16XBWjpJHtTZrsy3he+aUw5728tCjI4/TPqF6ANdRw0
+FjAj7Q0SQvTQl9CqBW68beETcBXK9y8YtO+pHnVmYA==
+-----END EC PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_521.pub b/gcr/fixtures/ssh-agent/id_ecdsa_521.pub
new file mode 100644
index 0000000..e57832d
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_521.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp521 
AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAEuCDfR6i7TTI99PeX85cbIhzaO3ho/s9QIdAIeut5ybrTUTgDepo3A+PxHLgAwIxMgtkB60HVSE+NL47ewKUWSOAAQP16XBWjpJHtTZrsy3he+aUw5728tCjI4/TPqF6ANdRw0FjAj7Q0SQvTQl9CqBW68beETcBXK9y8YtO+pHnVmYA==
 A public key comment
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_encrypted b/gcr/fixtures/ssh-agent/id_ecdsa_encrypted
new file mode 100644
index 0000000..3955d0f
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_encrypted
@@ -0,0 +1,8 @@
+-----BEGIN EC PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,739A7A7A93FC8D8932F08886D4E00A28
+
+R993QL3hLEDT9eav5CGOEAfpp87WXwwfUQBDX6tQLT9AfKawIx9ywWEVDP858YvH
+7DVftJjfMEJPTprMunH/b+lzG20C5UkXFJSV6Mjb1vfYh55tMn5BkzUswzMtHiXw
+C8T+HqpuKI5jTGDbW9m5A0VvFHvWgktj6BYCprpldtI=
+-----END EC PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_encrypted.pub b/gcr/fixtures/ssh-agent/id_ecdsa_encrypted.pub
new file mode 100644
index 0000000..d589462
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_encrypted.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNgFMnVkKhrkaU7FhRLHKstw6YrXHZ5o0hnoKu32Zup0JCegz9GGARlSDf8shWYNi+jGHJ+cIkgvmi92x2QNiAo=
 A public key comment
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_plain b/gcr/fixtures/ssh-agent/id_ecdsa_plain
new file mode 100644
index 0000000..6423c0d
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_plain
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMYWoyDjg5vGlG5DLo6EmnzXK4OGfnA+2GrL9p3xfvu+oAoGCCqGSM49
+AwEHoUQDQgAEqOtZpbYB2DmsI3PDGXRArS3XLf4GhOQr4VxXJHIv7L8Ow2dWlc79
+nR2GSnS2QsXGRVkBOAPH5ZdfvVLrI1zLnA==
+-----END EC PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_plain.pub b/gcr/fixtures/ssh-agent/id_ecdsa_plain.pub
new file mode 100644
index 0000000..7d4b2d4
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_plain.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKjrWaW2Adg5rCNzwxl0QK0t1y3+BoTkK+FcVyRyL+y/DsNnVpXO/Z0dhkp0tkLFxkVZATgDx+WXX71S6yNcy5w=
 A public key comment
diff --git a/gcr/fixtures/ssh-agent/id_ecdsa_test.pub b/gcr/fixtures/ssh-agent/id_ecdsa_test.pub
new file mode 100644
index 0000000..7d4b2d4
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_ecdsa_test.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 
AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKjrWaW2Adg5rCNzwxl0QK0t1y3+BoTkK+FcVyRyL+y/DsNnVpXO/Z0dhkp0tkLFxkVZATgDx+WXX71S6yNcy5w=
 A public key comment
diff --git a/gcr/fixtures/ssh-agent/id_rsa_encrypted b/gcr/fixtures/ssh-agent/id_rsa_encrypted
new file mode 100644
index 0000000..2c72860
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_rsa_encrypted
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,C7CA72E4022D0E83
+
+me0oewaJEzC9jiS1EaEN1dgXwc6QpVa9YeZLOmuhWbKhJI7B4eB7ZzdCRZv07wKv
+kCSkM+/rk6Env7QjkRhREF2Uc1jGwZAh5SttP/eGlWe4tcJ0o246rNgtPmyrlmh4
+7jESkZICnv7kv9nam4W0GU/4UHTGZsx4z6l0XUnbI5mDwUC4QL7mF9Bu3tZ3+hlY
+V4Pr7OklILzopGuoaYaJABhbu/EtU3R8+g9gXJZFkLJUo41Q53BjBlZb1XEKSBE/
+3TL+sDVklBOZlqgIiBnN7buGeC/0VFzmworpMvti1SIBtfKCuYGYcrqoeUmP8+CK
+lAOpfFGWuh89YpVsWG57pJg6YJo8SrqwmUO0ksv3SA2Lf4hoecX3Ikp6vNKYlZ27
+uKWtmWV79gtsOKARh2v72ZFChRS6AQFkU1ZMA56rKEaW/Mvlm/7HF9G4k7SEftR1
+r06FUKp5uUCbvWOfyYM/69HdB8aZROU24zUPkYu49kZ/QCl7q7LxG+ODJIybyklR
+RxZVO0qcb14umRP0iC+9jNw3J/hECS6JzHW7NbCplzi/AEbXZGUrhfzpri3CTs81
+l9WSywbZINUyesYO4TfttfmCgBuLRc0x2C/wrn8MG09Gl7c2aw2pxhaMPGF3j/G8
+shV7FwTGsKm8G+eMSiFXkm58Biz/nAITimAqe36VsmkcqjAuX0YVAiLVkux9YmQ2
+5hmCoBaZIx88YGgCwsigwPQPGQrXlZinlFfYluG6trA42pl2BD/6U/AJEZ8RUsD0
+OshlvRxxR0/zCqMufRWxBb1BUftglKunfiF6iHgTshIoOUmvLblzaEDB8Jf5UMS1
+tZMH0RQSLH3gQ+kp7/2mW2jGbdIhusru729yykb/9iinoGMEgdsfFr62nDj5zPYb
+kltEUwjVWnuL/O/38exZjrIZkf1uaFkjLXqrHIz3MuoHSoVd/AVBeEU4BvKO4aoM
+mgcT/Nc+jNWhOOewDtvtYxcPjrvmg69huLG0XoCgQ8P2GQJD2rSVc2MTe3hqmJj8
+SKqSUdlq4Sb+8K/hh++gCQkrJb5q7zIdXxULA7xf8PD2yhrVgO/h9mzaDxR77zBJ
+lab2dSxEH9vdGQKqGT6ZndEZo6Gopn2pXtn7bYVN2K0GojYa2MiLNbuhqxtZvg5Z
+Y49acItEd49objc40IU6WAAwz5w6jl6lybTq6EiNSMsmtekugUwyFQCOpTfYdOwZ
+9zrbmzwiWWAiQQTYuPESS1FpQEvAilzoSmwmbNkaAbMZfguX429+cSW1Kdu0/AWq
+SGx7AFpZpbKLd7hdKy25ZRIyRqu0goTsqA/r28faLKhjhKMF2Dl+Pgtkny7t/k1Z
+ZJXGmt++mHIck1UvGZR37thqpqd5IF9e96Pos1nQPfkV3cxXOT6mMOf9F3yCYW6d
+VclJPaM0lTEobbt2q/cDIM86Mo7G3cYHtfe7O+ard5Uhz+KVXhczduXeiwR4XNmP
+xQj/mgGTGCZIEWMJ32576B/vjEThY1RkA/wvdiMmV4gskXBbu/rhcX/RX51yBalP
+FU0DCN56t7vrXacEcdilMJIWLFZiqQCvYFHigNhwVsfBhNZHDxRODg==
+-----END RSA PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_rsa_encrypted.pub b/gcr/fixtures/ssh-agent/id_rsa_encrypted.pub
new file mode 100644
index 0000000..5883b32
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_rsa_encrypted.pub
@@ -0,0 +1 @@
+ssh-rsa 
AAAAB3NzaC1yc2EAAAABIwAAAQEA4+W0j6ubMFOkdaQ+bSZ5UQCa/HztFKajLjAmJBjiQ2HxMkYTyVhgel1+VXzEMF0mFJx+FML5kF2sf7GA6EXCz7f1A/UiUBfFkD2BYI8WE6o71iFUQ0UK7q8gnPfckSunGdUbb3MZUz+04K6ovR8XQKaOel0EP5rZ4qF457JRg4/w7jrQU9wOuL9m05aaFbPUemACa2M0Yw49PKu6pO8RcjQ3pKokFHkigqTtoRtbk186Rponw4btFt1bQfxxyDWMA1PHz4Gts+EHgOoLfwEN/IErfpYMamgff+1O74qamOTb9LnEkgnWh0UVyf3LcMJLB/fJaEEnoqX0mzf2yon28w==
diff --git a/gcr/fixtures/ssh-agent/id_rsa_plain b/gcr/fixtures/ssh-agent/id_rsa_plain
new file mode 100644
index 0000000..2e184e4
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_rsa_plain
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAoD6VKqkhay6pKHSRjAGWWfFPU8xfsi2gnOwP/B1UHDoztx3c
+zhO+py/fTlhCnSP1jsjkrVIZcnzah2fUNFFRgS4+jROBtvbgHsS72V1E6+ZogV+m
+BJWWAhw0iPrmQ3Kvm38D3PByo5Y7yKO5kIG2LloYLjosJ5F4sx2xh0uz2wXNtnY1
+b5xhe2+VEksm9OB+FXaUkZC2fQrTNo8ZGFJQSFd8kUhIfbUDJmlYuZ+vvHM+A3Lc
+9rHyW4IPaRyxFQciRmb+ZQqU2uSdOXAhg17lskuX/q8yCI5Hy5eDicC222oUMdJT
+tYgwX4dQCU8TICWhxb3x4RCV+g7D99+tkIvv+wIBIwKCAQEAm6qCRrLtQ4xpz4cu
+TX3ig0Ivzb84Y/HPOVpYq8S4G2u9NYq5PTfAhScwsoGfy9nJ+GthzPCrAYCZxV2M
+UBR7D8aF6CGT85C8dakrgrJgNZ3/HpduExT4H06Zbx+d2x7zu5/1H8UBpjorcnMp
+SoyiWOnOWMrLzqqSghzYV4teQnkhDmMTDgP/L39k9oLh/gvDHkxQET/IirrM3iT3
+rpZsXjsA+vAOrDrrsauPP9uAswaRGOH1O+xdAc/QH6/j2RK6ew/uICl0V9xYddSw
+9LSkk0gre2sdd7zz/r2t1oMFFsq+MaQ5Uynz0zmwpe/wyQjWY1ILy/wcIdOpLyOS
+PUaMSwKBgQDMULEsX+QChX3Od9gnwfbu4it7KYOV8V495al1YsaEyZcmcPQNKGrG
+iHyjDTWjj9w0THhrzF2ZfkWw3+N3SHfYqRx0+bzMgttEEJbaACOqBJPMmOwmi30I
+9ILcmsSMyOk+W9bHKPQ4OjwIVruiyvsFoLfhcFm0hispibWCKnlhUQKBgQDIx+aT
+kFnnVBvPnLAHgDfN32X0KR5Kk3PRe0cdNoeJHb/VHgLC0SuzZwdl+bzLdEyDaKht
+MGiPtblEhrjeTvwCHpwFOyMb33lYc1En8L2DNDjL0CASzRoHbvcKkin/L78wKmkV
+TY5uFyZ7Q/5S0YNlGSKL02+XURE/F/4FzKRJiwKBgQC6zW7JfD4uMOgNOl86SvBl
+YRHPsO1V4/5kejSIlNLCjGzv8j436nBAfMm5pa1iV6TCGgezA/3OKk5Yki8N+R0d
+3HHKCOchYbKHX5/dQfS4s7pUqRJsYkZulmkENbsEX+sxsw2C4511PI6oidAfz4Zr
+i6DOHZPYQCd2b0bdaKl9iwKBgQCD8RPdUDsUW9fC8fAE7duHSall7yKIx0wb8ezn
+T7gm56nycs6dR6Bf8z9gRQcJWw21cKkxz1qnlKzyhGrbOzfkIr5MlJNqFoLw+1KW
+luLv0dwuKa6tRPPY/8bpsIH/dyXd0roVUDkGhD3b+Xs9vOFREIRqg6EaSzc4FxSk
+ua3J7QKBgBVAzKSD3ya+VYKFD3E8GaiLQoClJF2t9Zkzr3yyJ657CwugA/2uU2/x
+3YNU3vK9hyypTXulbtteifRceSLDxEBQ67f0F3gvBqU6ZU2FmD7YTTv82Jvl0Ue2
+49ouxRjON9nXmr+6qe/yr5vIRlcRjKlfaIxDL7V6OTgwedUwqCuY
+-----END RSA PRIVATE KEY-----
diff --git a/gcr/fixtures/ssh-agent/id_rsa_plain.pub b/gcr/fixtures/ssh-agent/id_rsa_plain.pub
new file mode 100644
index 0000000..a00a1e9
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_rsa_plain.pub
@@ -0,0 +1 @@
+ssh-rsa 
AAAAB3NzaC1yc2EAAAABIwAAAQEAoD6VKqkhay6pKHSRjAGWWfFPU8xfsi2gnOwP/B1UHDoztx3czhO+py/fTlhCnSP1jsjkrVIZcnzah2fUNFFRgS4+jROBtvbgHsS72V1E6+ZogV+mBJWWAhw0iPrmQ3Kvm38D3PByo5Y7yKO5kIG2LloYLjosJ5F4sx2xh0uz2wXNtnY1b5xhe2+VEksm9OB+FXaUkZC2fQrTNo8ZGFJQSFd8kUhIfbUDJmlYuZ+vvHM+A3Lc9rHyW4IPaRyxFQciRmb+ZQqU2uSdOXAhg17lskuX/q8yCI5Hy5eDicC222oUMdJTtYgwX4dQCU8TICWhxb3x4RCV+g7D99+tkIvv+w==
diff --git a/gcr/fixtures/ssh-agent/id_rsa_test.pub b/gcr/fixtures/ssh-agent/id_rsa_test.pub
new file mode 100644
index 0000000..61870b3
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/id_rsa_test.pub
@@ -0,0 +1 @@
+ssh-rsa   
AAAAB3NzaC1yc2EAAAABIwAAAQEAoD6VKqkhay6pKHSRjAGWWfFPU8xfsi2gnOwP/B1UHDoztx3czhO+py/fTlhCnSP1jsjkrVIZcnzah2fUNFFRgS4+jROBtvbgHsS72V1E6+ZogV+mBJWWAhw0iPrmQ3Kvm38D3PByo5Y7yKO5kIG2LloYLjosJ5F4sx2xh0uz2wXNtnY1b5xhe2+VEksm9OB+FXaUkZC2fQrTNo8ZGFJQSFd8kUhIfbUDJmlYuZ+vvHM+A3Lc9rHyW4IPaRyxFQciRmb+ZQqU2uSdOXAhg17lskuX/q8yCI5Hy5eDicC222oUMdJTtYgwX4dQCU8TICWhxb3x4RCV+g7D99+tkIvv+w==
   A public key comment
diff --git a/gcr/fixtures/ssh-agent/identity.pub b/gcr/fixtures/ssh-agent/identity.pub
new file mode 100644
index 0000000..64f0092
--- /dev/null
+++ b/gcr/fixtures/ssh-agent/identity.pub
@@ -0,0 +1 @@
+2048 65537 
24441362561658402203833950446201855344021432187363502135808306611576487614688480997306138918965620875403280637443583435371977323903269172664531450582985454519164856071918921465780900234446084102021505343856321702549268936741458077623280683630954129182173904049746029183391563757599728238653932509919193037170161958394083453355955428766928687086643512804493287104481453268463186578945838089020355657752804348513609803384640933423383046464181883782074377289700592413463972335302845592904197813072122904513007851760502305134812488745575825428292149365221963416490618044666955554129518253460898036309102204336193271317821
 miles@centos7
diff --git a/gcr/gcr-ssh-agent-service.c b/gcr/gcr-ssh-agent-service.c
index f343400..d677726 100644
--- a/gcr/gcr-ssh-agent-service.c
+++ b/gcr/gcr-ssh-agent-service.c
@@ -49,7 +49,8 @@ static const GcrSshAgentOperation operations[GCR_SSH_OP_MAX];
 enum {
        PROP_0,
        PROP_PATH,
-       PROP_PRELOAD
+       PROP_PRELOAD,
+       PROP_INTERACTION
 };
 
 struct _GcrSshAgentService
@@ -58,6 +59,8 @@ struct _GcrSshAgentService
        gchar *path;
        GcrSshAgentPreload *preload;
        GcrSshAgentProcess *process;
+       /* for mocking */
+       GTlsInteraction *interaction;
        GSocketAddress *address;
        GSocketListener *listener;
        GHashTable *keys;
@@ -106,6 +109,9 @@ gcr_ssh_agent_service_set_property (GObject *object,
        case PROP_PRELOAD:
                self->preload = g_value_dup_object (value);
                break;
+       case PROP_INTERACTION:
+               self->interaction = g_value_dup_object (value);
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                break;
@@ -119,6 +125,7 @@ gcr_ssh_agent_service_finalize (GObject *object)
 
        g_free (self->path);
        g_object_unref (self->preload);
+       g_clear_object (&self->interaction);
 
        g_object_unref (self->process);
        g_object_unref (self->listener);
@@ -145,6 +152,10 @@ gcr_ssh_agent_service_class_init (GcrSshAgentServiceClass *klass)
                 g_param_spec_object ("preload", "Preload", "Preload",
                                      GCR_TYPE_SSH_AGENT_PRELOAD,
                                      G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+       g_object_class_install_property (gobject_class, PROP_INTERACTION,
+                g_param_spec_object ("interaction", "Interaction", "Interaction",
+                                     G_TYPE_TLS_INTERACTION,
+                                     G_PARAM_WRITABLE));
 }
 
 static gboolean
@@ -243,7 +254,11 @@ ensure_key (GcrSshAgentService *self,
 
        label = info->comment[0] != '\0' ? info->comment : _("Unnamed");
 
-       interaction = gcr_ssh_agent_interaction_new (NULL, label, fields);
+       if (self->interaction) {
+               interaction = g_object_ref (self->interaction);
+       } else {
+               interaction = gcr_ssh_agent_interaction_new (NULL, label, fields);
+       }
        askpass = gcr_ssh_askpass_new (interaction);
        g_object_unref (interaction);
 
diff --git a/gcr/gcr-ssh-agent-test.c b/gcr/gcr-ssh-agent-test.c
new file mode 100644
index 0000000..6196e8f
--- /dev/null
+++ b/gcr/gcr-ssh-agent-test.c
@@ -0,0 +1,334 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-test.h"
+
+#include "gcr-ssh-agent-private.h"
+#include "gcr-ssh-agent-util.h"
+
+/* RSA private key blob decoded from fixtures/ssh-agent/id_rsa_plain */
+static const guint8 private_blob[4*6 + 0x101 + 0x1 + 0x101 + 0x80 + 0x81 + 0x81] = {
+       /* n */
+       0x00, 0x00, 0x01, 0x01, 0x00, 0xa0, 0x3e, 0x95, 0x2a, 0xa9, 0x21, 0x6b,
+       0x2e, 0xa9, 0x28, 0x74, 0x91, 0x8c, 0x01, 0x96, 0x59, 0xf1, 0x4f, 0x53,
+       0xcc, 0x5f, 0xb2, 0x2d, 0xa0, 0x9c, 0xec, 0x0f, 0xfc, 0x1d, 0x54, 0x1c,
+       0x3a, 0x33, 0xb7, 0x1d, 0xdc, 0xce, 0x13, 0xbe, 0xa7, 0x2f, 0xdf, 0x4e,
+       0x58, 0x42, 0x9d, 0x23, 0xf5, 0x8e, 0xc8, 0xe4, 0xad, 0x52, 0x19, 0x72,
+       0x7c, 0xda, 0x87, 0x67, 0xd4, 0x34, 0x51, 0x51, 0x81, 0x2e, 0x3e, 0x8d,
+       0x13, 0x81, 0xb6, 0xf6, 0xe0, 0x1e, 0xc4, 0xbb, 0xd9, 0x5d, 0x44, 0xeb,
+       0xe6, 0x68, 0x81, 0x5f, 0xa6, 0x04, 0x95, 0x96, 0x02, 0x1c, 0x34, 0x88,
+       0xfa, 0xe6, 0x43, 0x72, 0xaf, 0x9b, 0x7f, 0x03, 0xdc, 0xf0, 0x72, 0xa3,
+       0x96, 0x3b, 0xc8, 0xa3, 0xb9, 0x90, 0x81, 0xb6, 0x2e, 0x5a, 0x18, 0x2e,
+       0x3a, 0x2c, 0x27, 0x91, 0x78, 0xb3, 0x1d, 0xb1, 0x87, 0x4b, 0xb3, 0xdb,
+       0x05, 0xcd, 0xb6, 0x76, 0x35, 0x6f, 0x9c, 0x61, 0x7b, 0x6f, 0x95, 0x12,
+       0x4b, 0x26, 0xf4, 0xe0, 0x7e, 0x15, 0x76, 0x94, 0x91, 0x90, 0xb6, 0x7d,
+       0x0a, 0xd3, 0x36, 0x8f, 0x19, 0x18, 0x52, 0x50, 0x48, 0x57, 0x7c, 0x91,
+       0x48, 0x48, 0x7d, 0xb5, 0x03, 0x26, 0x69, 0x58, 0xb9, 0x9f, 0xaf, 0xbc,
+       0x73, 0x3e, 0x03, 0x72, 0xdc, 0xf6, 0xb1, 0xf2, 0x5b, 0x82, 0x0f, 0x69,
+       0x1c, 0xb1, 0x15, 0x07, 0x22, 0x46, 0x66, 0xfe, 0x65, 0x0a, 0x94, 0xda,
+       0xe4, 0x9d, 0x39, 0x70, 0x21, 0x83, 0x5e, 0xe5, 0xb2, 0x4b, 0x97, 0xfe,
+       0xaf, 0x32, 0x08, 0x8e, 0x47, 0xcb, 0x97, 0x83, 0x89, 0xc0, 0xb6, 0xdb,
+       0x6a, 0x14, 0x31, 0xd2, 0x53, 0xb5, 0x88, 0x30, 0x5f, 0x87, 0x50, 0x09,
+       0x4f, 0x13, 0x20, 0x25, 0xa1, 0xc5, 0xbd, 0xf1, 0xe1, 0x10, 0x95, 0xfa,
+       0x0e, 0xc3, 0xf7, 0xdf, 0xad, 0x90, 0x8b, 0xef, 0xfb,
+       /* e */
+       0x00, 0x00, 0x00, 0x01, 0x23,
+       /* d */
+       0x00, 0x00, 0x01, 0x01, 0x00, 0x9b, 0xaa, 0x82, 0x46, 0xb2, 0xed, 0x43,
+       0x8c, 0x69, 0xcf, 0x87, 0x2e, 0x4d, 0x7d, 0xe2, 0x83, 0x42, 0x2f, 0xcd,
+       0xbf, 0x38, 0x63, 0xf1, 0xcf, 0x39, 0x5a, 0x58, 0xab, 0xc4, 0xb8, 0x1b,
+       0x6b, 0xbd, 0x35, 0x8a, 0xb9, 0x3d, 0x37, 0xc0, 0x85, 0x27, 0x30, 0xb2,
+       0x81, 0x9f, 0xcb, 0xd9, 0xc9, 0xf8, 0x6b, 0x61, 0xcc, 0xf0, 0xab, 0x01,
+       0x80, 0x99, 0xc5, 0x5d, 0x8c, 0x50, 0x14, 0x7b, 0x0f, 0xc6, 0x85, 0xe8,
+       0x21, 0x93, 0xf3, 0x90, 0xbc, 0x75, 0xa9, 0x2b, 0x82, 0xb2, 0x60, 0x35,
+       0x9d, 0xff, 0x1e, 0x97, 0x6e, 0x13, 0x14, 0xf8, 0x1f, 0x4e, 0x99, 0x6f,
+       0x1f, 0x9d, 0xdb, 0x1e, 0xf3, 0xbb, 0x9f, 0xf5, 0x1f, 0xc5, 0x01, 0xa6,
+       0x3a, 0x2b, 0x72, 0x73, 0x29, 0x4a, 0x8c, 0xa2, 0x58, 0xe9, 0xce, 0x58,
+       0xca, 0xcb, 0xce, 0xaa, 0x92, 0x82, 0x1c, 0xd8, 0x57, 0x8b, 0x5e, 0x42,
+       0x79, 0x21, 0x0e, 0x63, 0x13, 0x0e, 0x03, 0xff, 0x2f, 0x7f, 0x64, 0xf6,
+       0x82, 0xe1, 0xfe, 0x0b, 0xc3, 0x1e, 0x4c, 0x50, 0x11, 0x3f, 0xc8, 0x8a,
+       0xba, 0xcc, 0xde, 0x24, 0xf7, 0xae, 0x96, 0x6c, 0x5e, 0x3b, 0x00, 0xfa,
+       0xf0, 0x0e, 0xac, 0x3a, 0xeb, 0xb1, 0xab, 0x8f, 0x3f, 0xdb, 0x80, 0xb3,
+       0x06, 0x91, 0x18, 0xe1, 0xf5, 0x3b, 0xec, 0x5d, 0x01, 0xcf, 0xd0, 0x1f,
+       0xaf, 0xe3, 0xd9, 0x12, 0xba, 0x7b, 0x0f, 0xee, 0x20, 0x29, 0x74, 0x57,
+       0xdc, 0x58, 0x75, 0xd4, 0xb0, 0xf4, 0xb4, 0xa4, 0x93, 0x48, 0x2b, 0x7b,
+       0x6b, 0x1d, 0x77, 0xbc, 0xf3, 0xfe, 0xbd, 0xad, 0xd6, 0x83, 0x05, 0x16,
+       0xca, 0xbe, 0x31, 0xa4, 0x39, 0x53, 0x29, 0xf3, 0xd3, 0x39, 0xb0, 0xa5,
+       0xef, 0xf0, 0xc9, 0x08, 0xd6, 0x63, 0x52, 0x0b, 0xcb, 0xfc, 0x1c, 0x21,
+       0xd3, 0xa9, 0x2f, 0x23, 0x92, 0x3d, 0x46, 0x8c, 0x4b,
+       /* iqmp */
+       0x00, 0x00, 0x00, 0x80, 0x15, 0x40, 0xcc, 0xa4, 0x83, 0xdf, 0x26, 0xbe,
+       0x55, 0x82, 0x85, 0x0f, 0x71, 0x3c, 0x19, 0xa8, 0x8b, 0x42, 0x80, 0xa5,
+       0x24, 0x5d, 0xad, 0xf5, 0x99, 0x33, 0xaf, 0x7c, 0xb2, 0x27, 0xae, 0x7b,
+       0x0b, 0x0b, 0xa0, 0x03, 0xfd, 0xae, 0x53, 0x6f, 0xf1, 0xdd, 0x83, 0x54,
+       0xde, 0xf2, 0xbd, 0x87, 0x2c, 0xa9, 0x4d, 0x7b, 0xa5, 0x6e, 0xdb, 0x5e,
+       0x89, 0xf4, 0x5c, 0x79, 0x22, 0xc3, 0xc4, 0x40, 0x50, 0xeb, 0xb7, 0xf4,
+       0x17, 0x78, 0x2f, 0x06, 0xa5, 0x3a, 0x65, 0x4d, 0x85, 0x98, 0x3e, 0xd8,
+       0x4d, 0x3b, 0xfc, 0xd8, 0x9b, 0xe5, 0xd1, 0x47, 0xb6, 0xe3, 0xda, 0x2e,
+       0xc5, 0x18, 0xce, 0x37, 0xd9, 0xd7, 0x9a, 0xbf, 0xba, 0xa9, 0xef, 0xf2,
+       0xaf, 0x9b, 0xc8, 0x46, 0x57, 0x11, 0x8c, 0xa9, 0x5f, 0x68, 0x8c, 0x43,
+       0x2f, 0xb5, 0x7a, 0x39, 0x38, 0x30, 0x79, 0xd5, 0x30, 0xa8, 0x2b, 0x98,
+       /* p */
+       0x00, 0x00, 0x00, 0x81, 0x00, 0xcc, 0x50, 0xb1, 0x2c, 0x5f, 0xe4, 0x02,
+       0x85, 0x7d, 0xce, 0x77, 0xd8, 0x27, 0xc1, 0xf6, 0xee, 0xe2, 0x2b, 0x7b,
+       0x29, 0x83, 0x95, 0xf1, 0x5e, 0x3d, 0xe5, 0xa9, 0x75, 0x62, 0xc6, 0x84,
+       0xc9, 0x97, 0x26, 0x70, 0xf4, 0x0d, 0x28, 0x6a, 0xc6, 0x88, 0x7c, 0xa3,
+       0x0d, 0x35, 0xa3, 0x8f, 0xdc, 0x34, 0x4c, 0x78, 0x6b, 0xcc, 0x5d, 0x99,
+       0x7e, 0x45, 0xb0, 0xdf, 0xe3, 0x77, 0x48, 0x77, 0xd8, 0xa9, 0x1c, 0x74,
+       0xf9, 0xbc, 0xcc, 0x82, 0xdb, 0x44, 0x10, 0x96, 0xda, 0x00, 0x23, 0xaa,
+       0x04, 0x93, 0xcc, 0x98, 0xec, 0x26, 0x8b, 0x7d, 0x08, 0xf4, 0x82, 0xdc,
+       0x9a, 0xc4, 0x8c, 0xc8, 0xe9, 0x3e, 0x5b, 0xd6, 0xc7, 0x28, 0xf4, 0x38,
+       0x3a, 0x3c, 0x08, 0x56, 0xbb, 0xa2, 0xca, 0xfb, 0x05, 0xa0, 0xb7, 0xe1,
+       0x70, 0x59, 0xb4, 0x86, 0x2b, 0x29, 0x89, 0xb5, 0x82, 0x2a, 0x79, 0x61,
+       0x51,
+       /* q */
+       0x00, 0x00, 0x00, 0x81, 0x00, 0xc8, 0xc7, 0xe6, 0x93, 0x90, 0x59, 0xe7,
+       0x54, 0x1b, 0xcf, 0x9c, 0xb0, 0x07, 0x80, 0x37, 0xcd, 0xdf, 0x65, 0xf4,
+       0x29, 0x1e, 0x4a, 0x93, 0x73, 0xd1, 0x7b, 0x47, 0x1d, 0x36, 0x87, 0x89,
+       0x1d, 0xbf, 0xd5, 0x1e, 0x02, 0xc2, 0xd1, 0x2b, 0xb3, 0x67, 0x07, 0x65,
+       0xf9, 0xbc, 0xcb, 0x74, 0x4c, 0x83, 0x68, 0xa8, 0x6d, 0x30, 0x68, 0x8f,
+       0xb5, 0xb9, 0x44, 0x86, 0xb8, 0xde, 0x4e, 0xfc, 0x02, 0x1e, 0x9c, 0x05,
+       0x3b, 0x23, 0x1b, 0xdf, 0x79, 0x58, 0x73, 0x51, 0x27, 0xf0, 0xbd, 0x83,
+       0x34, 0x38, 0xcb, 0xd0, 0x20, 0x12, 0xcd, 0x1a, 0x07, 0x6e, 0xf7, 0x0a,
+       0x92, 0x29, 0xff, 0x2f, 0xbf, 0x30, 0x2a, 0x69, 0x15, 0x4d, 0x8e, 0x6e,
+       0x17, 0x26, 0x7b, 0x43, 0xfe, 0x52, 0xd1, 0x83, 0x65, 0x19, 0x22, 0x8b,
+       0xd3, 0x6f, 0x97, 0x51, 0x11, 0x3f, 0x17, 0xfe, 0x05, 0xcc, 0xa4, 0x49,
+       0x8b
+};
+
+void
+prepare_request_identities (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+
+       ret = egg_buffer_add_uint32 (req, 1);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_REQUEST_IDENTITIES);
+       g_assert_true (ret);
+}
+
+void
+check_identities_answer (EggBuffer *resp, gsize count)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (code, ==, GCR_SSH_RES_IDENTITIES_ANSWER);
+
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, count);
+}
+
+void
+prepare_add_identity (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_ADD_IDENTITY);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "ssh-rsa");
+       g_assert_true (ret);
+
+       ret = egg_buffer_append (req, private_blob, G_N_ELEMENTS(private_blob));
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "comment");
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+}
+
+GBytes *
+public_key_from_file (const gchar *path, gchar **comment)
+{
+       GBytes *public_bytes;
+       GBytes *public_key;
+
+       GError *error = NULL;
+       gchar *contents;
+       gsize length;
+
+       if (!g_file_get_contents (path, &contents, &length, &error)) {
+               g_message ("couldn't read file: %s: %s", path, error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       public_bytes = g_bytes_new_take (contents, length);
+       public_key = _gcr_ssh_agent_parse_public_key (public_bytes, comment);
+       g_bytes_unref (public_bytes);
+
+       return public_key;
+}
+
+void
+prepare_remove_identity (EggBuffer *req)
+{
+       GBytes *public_key;
+       gchar *comment;
+       gsize length;
+       const guchar *blob;
+       gboolean ret;
+
+       public_key = public_key_from_file (SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub", &comment);
+       g_free (comment);
+       blob = g_bytes_get_data (public_key, &length);
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_REMOVE_IDENTITY);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte_array (req, blob, length);
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+
+       g_bytes_unref (public_key);
+}
+
+void
+prepare_remove_all_identities (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 1);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_REMOVE_ALL_IDENTITIES);
+       g_assert_true (ret);
+}
+
+void
+check_response (EggBuffer *resp, unsigned char expected)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (expected, ==, code);
+}
+
+void
+check_success (EggBuffer *resp)
+{
+       check_response (resp, GCR_SSH_RES_SUCCESS);
+}
+
+void
+check_failure (EggBuffer *resp)
+{
+       check_response (resp, GCR_SSH_RES_FAILURE);
+}
+
+void
+prepare_sign_request (EggBuffer *req)
+{
+       GBytes *public_key;
+       gchar *comment;
+       gsize length;
+       const guchar *blob;
+       gboolean ret;
+
+       public_key = public_key_from_file (SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub", &comment);
+       g_free (comment);
+       blob = g_bytes_get_data (public_key, &length);
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_SIGN_REQUEST);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte_array (req, blob, length);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "data");
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+
+       g_bytes_unref (public_key);
+}
+
+void
+check_sign_response (EggBuffer *resp)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (code, ==, GCR_SSH_RES_SIGN_RESPONSE);
+
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+}
diff --git a/gcr/gcr-ssh-agent-test.h b/gcr/gcr-ssh-agent-test.h
new file mode 100644
index 0000000..f4849fd
--- /dev/null
+++ b/gcr/gcr-ssh-agent-test.h
@@ -0,0 +1,94 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include <glib.h>
+#include "egg/egg-buffer.h"
+
+void prepare_request_identities (EggBuffer *req);
+void prepare_add_identity (EggBuffer *req);
+void prepare_remove_identity (EggBuffer *req);
+void prepare_remove_all_identities (EggBuffer *req);
+void prepare_sign_request (EggBuffer *req);
+
+void check_identities_answer (EggBuffer *resp, gsize count);
+void check_sign_response (EggBuffer *resp);
+void check_response (EggBuffer *resp, unsigned char expected);
+void check_success (EggBuffer *resp);
+void check_failure (EggBuffer *resp);
+
+GBytes *public_key_from_file (const gchar *path, gchar **comment);
+
+#define DEFINE_CALL_FUNCS(Test, Call)                  \
+static inline void                                     \
+call_request_identities (Test *test, gsize count)      \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_request_identities (&test->req);        \
+       Call (test);                                    \
+       check_identities_answer (&test->resp, count);   \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_add_identity (Test *test)                         \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_add_identity (&test->req);              \
+       Call (test);                                    \
+       check_success (&test->resp);                    \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_remove_identity (Test *test)                      \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_remove_identity (&test->req);           \
+       Call (test);                                    \
+       check_success (&test->resp);                    \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_remove_all_identities (Test *test)                        \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_remove_all_identities (&test->req);     \
+       Call (test);                                    \
+       check_success (&test->resp);                    \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_sign (Test *test)                                 \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_sign_request (&test->req);              \
+       Call (test);                                    \
+       check_sign_response (&test->resp);              \
+}
diff --git a/gcr/meson.build b/gcr/meson.build
index b6bcd0a..87923f3 100644
--- a/gcr/meson.build
+++ b/gcr/meson.build
@@ -240,16 +240,16 @@ gcr_ssh_askpass = executable('gcr-ssh-askpass',
 
 if get_option('ssh_agent')
   # gcr-ssh-agent binary
-  gcr_ssh_agent_sources = [
-    'gcr-ssh-agent.c',
+  gcr_ssh_agent_lib_sources = [
     'gcr-ssh-agent-interaction.c',
     'gcr-ssh-agent-preload.c',
     'gcr-ssh-agent-process.c',
     'gcr-ssh-agent-service.c',
     'gcr-ssh-agent-util.c',
   ]
+
   gcr_ssh_agent = executable('gcr-ssh-agent',
-    gcr_ssh_agent_sources,
+    gcr_ssh_agent_lib_sources + [ 'gcr-ssh-agent.c' ],
     dependencies: [ gcr_base_deps, gcr_base_dep, libsecret_dep ],
     c_args: [
       '-DGCR_COMPILATION',
@@ -259,6 +259,43 @@ if get_option('ssh_agent')
     install: true,
     install_dir: get_option('libexecdir'),
   )
+
+  gcr_ssh_agent_test_cflags = [
+    '-DSRCDIR="@0@"'.format(source_root),
+    '-D_GCR_TEST_SSH_ASKPASS_PATH="@0@"'.format(gcr_ssh_askpass.full_path()),
+  ]
+
+  gcr_ssh_agent_test_lib = static_library('gcr-ssh-agent-test',
+    sources: gcr_ssh_agent_lib_sources + [ 'gcr-ssh-agent-test.c' ],
+    dependencies: [ gcr_base_deps, gcr_base_dep, libsecret_dep ],
+    c_args: [
+      '-DGCR_COMPILATION',
+      '-DGCR_API_SUBJECT_TO_CHANGE',
+    ] + gcr_ssh_agent_test_cflags,
+    include_directories: config_h_dir,
+  )
+
+  gcr_ssh_agent_test_names = [
+    'preload',
+    'process',
+    'service',
+    'util',
+  ]
+
+  foreach _test : gcr_ssh_agent_test_names
+    test_bin = executable('test-ssh-agent-'+_test,
+      'test-ssh-agent-@0@.c'.format(_test),
+      dependencies: [ gcr_base_deps, gcr_base_dep ],
+      link_with: [ gcr_ssh_agent_test_lib, egg_test_lib ],
+      c_args: [ gcr_base_cflags, gcr_ssh_agent_test_cflags ],
+      include_directories: config_h_dir,
+    )
+
+    test(_test, test_bin,
+      suite: 'gcr-ssh-agent',
+      depends: gcr_ssh_askpass,
+    )
+  endforeach
 endif
 
 # Services
diff --git a/gcr/test-ssh-agent-common.c b/gcr/test-ssh-agent-common.c
new file mode 100644
index 0000000..bc17a77
--- /dev/null
+++ b/gcr/test-ssh-agent-common.c
@@ -0,0 +1,334 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "test-common.h"
+
+#include "gcr-ssh-agent-private.h"
+#include "gcr-ssh-agent-util.h"
+
+/* RSA private key blob decoded from pkcs11/ssh-store/fixtures/id_rsa_plain */
+static const guint8 private_blob[4*6 + 0x101 + 0x1 + 0x101 + 0x80 + 0x81 + 0x81] = {
+       /* n */
+       0x00, 0x00, 0x01, 0x01, 0x00, 0xa0, 0x3e, 0x95, 0x2a, 0xa9, 0x21, 0x6b,
+       0x2e, 0xa9, 0x28, 0x74, 0x91, 0x8c, 0x01, 0x96, 0x59, 0xf1, 0x4f, 0x53,
+       0xcc, 0x5f, 0xb2, 0x2d, 0xa0, 0x9c, 0xec, 0x0f, 0xfc, 0x1d, 0x54, 0x1c,
+       0x3a, 0x33, 0xb7, 0x1d, 0xdc, 0xce, 0x13, 0xbe, 0xa7, 0x2f, 0xdf, 0x4e,
+       0x58, 0x42, 0x9d, 0x23, 0xf5, 0x8e, 0xc8, 0xe4, 0xad, 0x52, 0x19, 0x72,
+       0x7c, 0xda, 0x87, 0x67, 0xd4, 0x34, 0x51, 0x51, 0x81, 0x2e, 0x3e, 0x8d,
+       0x13, 0x81, 0xb6, 0xf6, 0xe0, 0x1e, 0xc4, 0xbb, 0xd9, 0x5d, 0x44, 0xeb,
+       0xe6, 0x68, 0x81, 0x5f, 0xa6, 0x04, 0x95, 0x96, 0x02, 0x1c, 0x34, 0x88,
+       0xfa, 0xe6, 0x43, 0x72, 0xaf, 0x9b, 0x7f, 0x03, 0xdc, 0xf0, 0x72, 0xa3,
+       0x96, 0x3b, 0xc8, 0xa3, 0xb9, 0x90, 0x81, 0xb6, 0x2e, 0x5a, 0x18, 0x2e,
+       0x3a, 0x2c, 0x27, 0x91, 0x78, 0xb3, 0x1d, 0xb1, 0x87, 0x4b, 0xb3, 0xdb,
+       0x05, 0xcd, 0xb6, 0x76, 0x35, 0x6f, 0x9c, 0x61, 0x7b, 0x6f, 0x95, 0x12,
+       0x4b, 0x26, 0xf4, 0xe0, 0x7e, 0x15, 0x76, 0x94, 0x91, 0x90, 0xb6, 0x7d,
+       0x0a, 0xd3, 0x36, 0x8f, 0x19, 0x18, 0x52, 0x50, 0x48, 0x57, 0x7c, 0x91,
+       0x48, 0x48, 0x7d, 0xb5, 0x03, 0x26, 0x69, 0x58, 0xb9, 0x9f, 0xaf, 0xbc,
+       0x73, 0x3e, 0x03, 0x72, 0xdc, 0xf6, 0xb1, 0xf2, 0x5b, 0x82, 0x0f, 0x69,
+       0x1c, 0xb1, 0x15, 0x07, 0x22, 0x46, 0x66, 0xfe, 0x65, 0x0a, 0x94, 0xda,
+       0xe4, 0x9d, 0x39, 0x70, 0x21, 0x83, 0x5e, 0xe5, 0xb2, 0x4b, 0x97, 0xfe,
+       0xaf, 0x32, 0x08, 0x8e, 0x47, 0xcb, 0x97, 0x83, 0x89, 0xc0, 0xb6, 0xdb,
+       0x6a, 0x14, 0x31, 0xd2, 0x53, 0xb5, 0x88, 0x30, 0x5f, 0x87, 0x50, 0x09,
+       0x4f, 0x13, 0x20, 0x25, 0xa1, 0xc5, 0xbd, 0xf1, 0xe1, 0x10, 0x95, 0xfa,
+       0x0e, 0xc3, 0xf7, 0xdf, 0xad, 0x90, 0x8b, 0xef, 0xfb,
+       /* e */
+       0x00, 0x00, 0x00, 0x01, 0x23,
+       /* d */
+       0x00, 0x00, 0x01, 0x01, 0x00, 0x9b, 0xaa, 0x82, 0x46, 0xb2, 0xed, 0x43,
+       0x8c, 0x69, 0xcf, 0x87, 0x2e, 0x4d, 0x7d, 0xe2, 0x83, 0x42, 0x2f, 0xcd,
+       0xbf, 0x38, 0x63, 0xf1, 0xcf, 0x39, 0x5a, 0x58, 0xab, 0xc4, 0xb8, 0x1b,
+       0x6b, 0xbd, 0x35, 0x8a, 0xb9, 0x3d, 0x37, 0xc0, 0x85, 0x27, 0x30, 0xb2,
+       0x81, 0x9f, 0xcb, 0xd9, 0xc9, 0xf8, 0x6b, 0x61, 0xcc, 0xf0, 0xab, 0x01,
+       0x80, 0x99, 0xc5, 0x5d, 0x8c, 0x50, 0x14, 0x7b, 0x0f, 0xc6, 0x85, 0xe8,
+       0x21, 0x93, 0xf3, 0x90, 0xbc, 0x75, 0xa9, 0x2b, 0x82, 0xb2, 0x60, 0x35,
+       0x9d, 0xff, 0x1e, 0x97, 0x6e, 0x13, 0x14, 0xf8, 0x1f, 0x4e, 0x99, 0x6f,
+       0x1f, 0x9d, 0xdb, 0x1e, 0xf3, 0xbb, 0x9f, 0xf5, 0x1f, 0xc5, 0x01, 0xa6,
+       0x3a, 0x2b, 0x72, 0x73, 0x29, 0x4a, 0x8c, 0xa2, 0x58, 0xe9, 0xce, 0x58,
+       0xca, 0xcb, 0xce, 0xaa, 0x92, 0x82, 0x1c, 0xd8, 0x57, 0x8b, 0x5e, 0x42,
+       0x79, 0x21, 0x0e, 0x63, 0x13, 0x0e, 0x03, 0xff, 0x2f, 0x7f, 0x64, 0xf6,
+       0x82, 0xe1, 0xfe, 0x0b, 0xc3, 0x1e, 0x4c, 0x50, 0x11, 0x3f, 0xc8, 0x8a,
+       0xba, 0xcc, 0xde, 0x24, 0xf7, 0xae, 0x96, 0x6c, 0x5e, 0x3b, 0x00, 0xfa,
+       0xf0, 0x0e, 0xac, 0x3a, 0xeb, 0xb1, 0xab, 0x8f, 0x3f, 0xdb, 0x80, 0xb3,
+       0x06, 0x91, 0x18, 0xe1, 0xf5, 0x3b, 0xec, 0x5d, 0x01, 0xcf, 0xd0, 0x1f,
+       0xaf, 0xe3, 0xd9, 0x12, 0xba, 0x7b, 0x0f, 0xee, 0x20, 0x29, 0x74, 0x57,
+       0xdc, 0x58, 0x75, 0xd4, 0xb0, 0xf4, 0xb4, 0xa4, 0x93, 0x48, 0x2b, 0x7b,
+       0x6b, 0x1d, 0x77, 0xbc, 0xf3, 0xfe, 0xbd, 0xad, 0xd6, 0x83, 0x05, 0x16,
+       0xca, 0xbe, 0x31, 0xa4, 0x39, 0x53, 0x29, 0xf3, 0xd3, 0x39, 0xb0, 0xa5,
+       0xef, 0xf0, 0xc9, 0x08, 0xd6, 0x63, 0x52, 0x0b, 0xcb, 0xfc, 0x1c, 0x21,
+       0xd3, 0xa9, 0x2f, 0x23, 0x92, 0x3d, 0x46, 0x8c, 0x4b,
+       /* iqmp */
+       0x00, 0x00, 0x00, 0x80, 0x15, 0x40, 0xcc, 0xa4, 0x83, 0xdf, 0x26, 0xbe,
+       0x55, 0x82, 0x85, 0x0f, 0x71, 0x3c, 0x19, 0xa8, 0x8b, 0x42, 0x80, 0xa5,
+       0x24, 0x5d, 0xad, 0xf5, 0x99, 0x33, 0xaf, 0x7c, 0xb2, 0x27, 0xae, 0x7b,
+       0x0b, 0x0b, 0xa0, 0x03, 0xfd, 0xae, 0x53, 0x6f, 0xf1, 0xdd, 0x83, 0x54,
+       0xde, 0xf2, 0xbd, 0x87, 0x2c, 0xa9, 0x4d, 0x7b, 0xa5, 0x6e, 0xdb, 0x5e,
+       0x89, 0xf4, 0x5c, 0x79, 0x22, 0xc3, 0xc4, 0x40, 0x50, 0xeb, 0xb7, 0xf4,
+       0x17, 0x78, 0x2f, 0x06, 0xa5, 0x3a, 0x65, 0x4d, 0x85, 0x98, 0x3e, 0xd8,
+       0x4d, 0x3b, 0xfc, 0xd8, 0x9b, 0xe5, 0xd1, 0x47, 0xb6, 0xe3, 0xda, 0x2e,
+       0xc5, 0x18, 0xce, 0x37, 0xd9, 0xd7, 0x9a, 0xbf, 0xba, 0xa9, 0xef, 0xf2,
+       0xaf, 0x9b, 0xc8, 0x46, 0x57, 0x11, 0x8c, 0xa9, 0x5f, 0x68, 0x8c, 0x43,
+       0x2f, 0xb5, 0x7a, 0x39, 0x38, 0x30, 0x79, 0xd5, 0x30, 0xa8, 0x2b, 0x98,
+       /* p */
+       0x00, 0x00, 0x00, 0x81, 0x00, 0xcc, 0x50, 0xb1, 0x2c, 0x5f, 0xe4, 0x02,
+       0x85, 0x7d, 0xce, 0x77, 0xd8, 0x27, 0xc1, 0xf6, 0xee, 0xe2, 0x2b, 0x7b,
+       0x29, 0x83, 0x95, 0xf1, 0x5e, 0x3d, 0xe5, 0xa9, 0x75, 0x62, 0xc6, 0x84,
+       0xc9, 0x97, 0x26, 0x70, 0xf4, 0x0d, 0x28, 0x6a, 0xc6, 0x88, 0x7c, 0xa3,
+       0x0d, 0x35, 0xa3, 0x8f, 0xdc, 0x34, 0x4c, 0x78, 0x6b, 0xcc, 0x5d, 0x99,
+       0x7e, 0x45, 0xb0, 0xdf, 0xe3, 0x77, 0x48, 0x77, 0xd8, 0xa9, 0x1c, 0x74,
+       0xf9, 0xbc, 0xcc, 0x82, 0xdb, 0x44, 0x10, 0x96, 0xda, 0x00, 0x23, 0xaa,
+       0x04, 0x93, 0xcc, 0x98, 0xec, 0x26, 0x8b, 0x7d, 0x08, 0xf4, 0x82, 0xdc,
+       0x9a, 0xc4, 0x8c, 0xc8, 0xe9, 0x3e, 0x5b, 0xd6, 0xc7, 0x28, 0xf4, 0x38,
+       0x3a, 0x3c, 0x08, 0x56, 0xbb, 0xa2, 0xca, 0xfb, 0x05, 0xa0, 0xb7, 0xe1,
+       0x70, 0x59, 0xb4, 0x86, 0x2b, 0x29, 0x89, 0xb5, 0x82, 0x2a, 0x79, 0x61,
+       0x51,
+       /* q */
+       0x00, 0x00, 0x00, 0x81, 0x00, 0xc8, 0xc7, 0xe6, 0x93, 0x90, 0x59, 0xe7,
+       0x54, 0x1b, 0xcf, 0x9c, 0xb0, 0x07, 0x80, 0x37, 0xcd, 0xdf, 0x65, 0xf4,
+       0x29, 0x1e, 0x4a, 0x93, 0x73, 0xd1, 0x7b, 0x47, 0x1d, 0x36, 0x87, 0x89,
+       0x1d, 0xbf, 0xd5, 0x1e, 0x02, 0xc2, 0xd1, 0x2b, 0xb3, 0x67, 0x07, 0x65,
+       0xf9, 0xbc, 0xcb, 0x74, 0x4c, 0x83, 0x68, 0xa8, 0x6d, 0x30, 0x68, 0x8f,
+       0xb5, 0xb9, 0x44, 0x86, 0xb8, 0xde, 0x4e, 0xfc, 0x02, 0x1e, 0x9c, 0x05,
+       0x3b, 0x23, 0x1b, 0xdf, 0x79, 0x58, 0x73, 0x51, 0x27, 0xf0, 0xbd, 0x83,
+       0x34, 0x38, 0xcb, 0xd0, 0x20, 0x12, 0xcd, 0x1a, 0x07, 0x6e, 0xf7, 0x0a,
+       0x92, 0x29, 0xff, 0x2f, 0xbf, 0x30, 0x2a, 0x69, 0x15, 0x4d, 0x8e, 0x6e,
+       0x17, 0x26, 0x7b, 0x43, 0xfe, 0x52, 0xd1, 0x83, 0x65, 0x19, 0x22, 0x8b,
+       0xd3, 0x6f, 0x97, 0x51, 0x11, 0x3f, 0x17, 0xfe, 0x05, 0xcc, 0xa4, 0x49,
+       0x8b
+};
+
+void
+prepare_request_identities (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+
+       ret = egg_buffer_add_uint32 (req, 1);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_REQUEST_IDENTITIES);
+       g_assert_true (ret);
+}
+
+void
+check_identities_answer (EggBuffer *resp, gsize count)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (code, ==, GCR_SSH_RES_IDENTITIES_ANSWER);
+
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, count);
+}
+
+void
+prepare_add_identity (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_ADD_IDENTITY);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "ssh-rsa");
+       g_assert_true (ret);
+
+       ret = egg_buffer_append (req, private_blob, G_N_ELEMENTS(private_blob));
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "comment");
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+}
+
+GBytes *
+public_key_from_file (const gchar *path, gchar **comment)
+{
+       GBytes *public_bytes;
+       GBytes *public_key;
+
+       GError *error = NULL;
+       gchar *contents;
+       gsize length;
+
+       if (!g_file_get_contents (path, &contents, &length, &error)) {
+               g_message ("couldn't read file: %s: %s", path, error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       public_bytes = g_bytes_new_take (contents, length);
+       public_key = _gcr_ssh_agent_parse_public_key (public_bytes, comment);
+       g_bytes_unref (public_bytes);
+
+       return public_key;
+}
+
+void
+prepare_remove_identity (EggBuffer *req)
+{
+       GBytes *public_key;
+       gchar *comment;
+       gsize length;
+       const guchar *blob;
+       gboolean ret;
+
+       public_key = public_key_from_file (SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain.pub", &comment);
+       g_free (comment);
+       blob = g_bytes_get_data (public_key, &length);
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_REMOVE_IDENTITY);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte_array (req, blob, length);
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+
+       g_bytes_unref (public_key);
+}
+
+void
+prepare_remove_all_identities (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 1);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_REMOVE_ALL_IDENTITIES);
+       g_assert_true (ret);
+}
+
+void
+check_response (EggBuffer *resp, unsigned char expected)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (expected, ==, code);
+}
+
+void
+check_success (EggBuffer *resp)
+{
+       check_response (resp, GCR_SSH_RES_SUCCESS);
+}
+
+void
+check_failure (EggBuffer *resp)
+{
+       check_response (resp, GCR_SSH_RES_FAILURE);
+}
+
+void
+prepare_sign_request (EggBuffer *req)
+{
+       GBytes *public_key;
+       gchar *comment;
+       gsize length;
+       const guchar *blob;
+       gboolean ret;
+
+       public_key = public_key_from_file (SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub", &comment);
+       g_free (comment);
+       blob = g_bytes_get_data (public_key, &length);
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_SIGN_REQUEST);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte_array (req, blob, length);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "data");
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+
+       g_bytes_unref (public_key);
+}
+
+void
+check_sign_response (EggBuffer *resp)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (code, ==, GCR_SSH_RES_SIGN_RESPONSE);
+
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+}
diff --git a/gcr/test-ssh-agent-common.h b/gcr/test-ssh-agent-common.h
new file mode 100644
index 0000000..f4849fd
--- /dev/null
+++ b/gcr/test-ssh-agent-common.h
@@ -0,0 +1,94 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include <glib.h>
+#include "egg/egg-buffer.h"
+
+void prepare_request_identities (EggBuffer *req);
+void prepare_add_identity (EggBuffer *req);
+void prepare_remove_identity (EggBuffer *req);
+void prepare_remove_all_identities (EggBuffer *req);
+void prepare_sign_request (EggBuffer *req);
+
+void check_identities_answer (EggBuffer *resp, gsize count);
+void check_sign_response (EggBuffer *resp);
+void check_response (EggBuffer *resp, unsigned char expected);
+void check_success (EggBuffer *resp);
+void check_failure (EggBuffer *resp);
+
+GBytes *public_key_from_file (const gchar *path, gchar **comment);
+
+#define DEFINE_CALL_FUNCS(Test, Call)                  \
+static inline void                                     \
+call_request_identities (Test *test, gsize count)      \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_request_identities (&test->req);        \
+       Call (test);                                    \
+       check_identities_answer (&test->resp, count);   \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_add_identity (Test *test)                         \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_add_identity (&test->req);              \
+       Call (test);                                    \
+       check_success (&test->resp);                    \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_remove_identity (Test *test)                      \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_remove_identity (&test->req);           \
+       Call (test);                                    \
+       check_success (&test->resp);                    \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_remove_all_identities (Test *test)                        \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_remove_all_identities (&test->req);     \
+       Call (test);                                    \
+       check_success (&test->resp);                    \
+}                                                      \
+                                                       \
+static inline void                                     \
+call_sign (Test *test)                                 \
+{                                                      \
+       egg_buffer_reset (&test->req);                  \
+       egg_buffer_reset (&test->resp);                 \
+                                                       \
+       prepare_sign_request (&test->req);              \
+       Call (test);                                    \
+       check_sign_response (&test->resp);              \
+}
diff --git a/gcr/test-ssh-agent-preload.c b/gcr/test-ssh-agent-preload.c
new file mode 100644
index 0000000..ddf27c4
--- /dev/null
+++ b/gcr/test-ssh-agent-preload.c
@@ -0,0 +1,166 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-preload.h"
+#include "egg/egg-testing.h"
+
+#include <glib/gstdio.h>
+#include <unistd.h>
+
+typedef struct {
+       gchar *directory;
+       GcrSshAgentPreload *preload;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+       test->directory = egg_tests_create_scratch_directory (NULL, NULL);
+
+       egg_tests_copy_scratch_file (test->directory, SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain");
+       egg_tests_copy_scratch_file (test->directory, SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub");
+
+       test->preload = gcr_ssh_agent_preload_new (test->directory);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+       g_object_unref (test->preload);
+
+       egg_tests_remove_scratch_directory (test->directory);
+       g_free (test->directory);
+}
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+       GList *keys;
+
+       keys = gcr_ssh_agent_preload_get_keys (test->preload);
+       g_assert_cmpint (1, ==, g_list_length (keys));
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+}
+
+static void
+test_added (Test *test, gconstpointer unused)
+{
+       GList *keys;
+
+       keys = gcr_ssh_agent_preload_get_keys (test->preload);
+       g_assert_cmpint (1, ==, g_list_length (keys));
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+
+       /* Mtime must change so wait between tests */
+       sleep (1);
+
+       egg_tests_copy_scratch_file (test->directory, SRCDIR "/gcr/fixtures/ssh-agent/id_ecdsa_plain");
+       egg_tests_copy_scratch_file (test->directory, SRCDIR "/gcr/fixtures/ssh-agent/id_ecdsa_plain.pub");
+
+       keys = gcr_ssh_agent_preload_get_keys (test->preload);
+       g_assert_cmpint (2, ==, g_list_length (keys));
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+}
+
+static void
+test_removed (Test *test, gconstpointer unused)
+{
+       GList *keys;
+       gchar *path;
+
+       keys = gcr_ssh_agent_preload_get_keys (test->preload);
+       g_assert_cmpint (1, ==, g_list_length (keys));
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+
+       /* Mtime must change so wait between tests */
+       sleep (1);
+
+       path = g_build_filename (test->directory, "id_rsa_plain.pub", NULL);
+       g_unlink (path);
+       g_free (path);
+
+       path = g_build_filename (test->directory, "id_rsa_plain", NULL);
+       g_unlink (path);
+       g_free (path);
+
+       keys = gcr_ssh_agent_preload_get_keys (test->preload);
+       g_assert_cmpint (0, ==, g_list_length (keys));
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+}
+
+static void
+test_changed (Test *test, gconstpointer unused)
+{
+       GList *keys;
+       gchar *path;
+       gchar *contents;
+       gsize length;
+       GError *error;
+       gchar *p;
+       gboolean ret;
+
+       keys = gcr_ssh_agent_preload_get_keys (test->preload);
+       g_assert_cmpint (1, ==, g_list_length (keys));
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+
+       /* Mtime must change so wait between tests */
+       sleep (1);
+
+       path = g_build_filename (test->directory, "id_rsa_plain.pub", NULL);
+       error = NULL;
+       ret = g_file_get_contents (path, &contents, &length, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+
+#define COMMENT "comment"
+       contents = g_realloc (contents, length + strlen (COMMENT) + 1);
+       p = strchr (contents, '\n');
+       g_assert_nonnull (p);
+       memcpy (p, " " COMMENT "\n", strlen (COMMENT) + 2);
+       error = NULL;
+       ret = g_file_set_contents (path, contents, length + strlen (COMMENT), &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+       g_free (path);
+       g_free (contents);
+
+       keys = gcr_ssh_agent_preload_get_keys (test->preload);
+       g_assert_cmpint (1, ==, g_list_length (keys));
+       g_assert_cmpstr (COMMENT, ==, ((GcrSshAgentKeyInfo *)keys->data)->comment);
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+#undef COMMENT
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add ("/ssh-agent/preload/list", Test, NULL, setup, test_list, teardown);
+       g_test_add ("/ssh-agent/preload/added", Test, NULL, setup, test_added, teardown);
+       g_test_add ("/ssh-agent/preload/removed", Test, NULL, setup, test_removed, teardown);
+       g_test_add ("/ssh-agent/preload/changed", Test, NULL, setup, test_changed, teardown);
+
+       return g_test_run ();
+}
diff --git a/gcr/test-ssh-agent-process.c b/gcr/test-ssh-agent-process.c
new file mode 100644
index 0000000..8136d35
--- /dev/null
+++ b/gcr/test-ssh-agent-process.c
@@ -0,0 +1,219 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-private.h"
+#include "gcr-ssh-agent-process.h"
+#include "gcr-ssh-agent-util.h"
+#include "gcr-ssh-agent-test.h"
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+typedef struct {
+       gchar *directory;
+       EggBuffer req;
+       EggBuffer resp;
+       GcrSshAgentProcess *process;
+       GSocketConnection *connection;
+       GMainLoop *loop;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+       gchar *path;
+
+       test->directory = egg_tests_create_scratch_directory (NULL, NULL);
+
+       egg_buffer_init_full (&test->req, 128, (EggBufferAllocator)g_realloc);
+       egg_buffer_init_full (&test->resp, 128, (EggBufferAllocator)g_realloc);
+
+       path = g_strdup_printf ("%s/.ssh.sock", test->directory);
+       test->process = gcr_ssh_agent_process_new (path);
+       g_free (path);
+       g_assert_nonnull (test->process);
+       test->connection = NULL;
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+       g_clear_object (&test->process);
+       g_clear_object (&test->connection);
+
+       egg_buffer_uninit (&test->req);
+       egg_buffer_uninit (&test->resp);
+
+       egg_tests_remove_scratch_directory (test->directory);
+       free (test->directory);
+}
+
+static void
+connect_to_process (Test *test)
+{
+       GError *error;
+
+       error = NULL;
+       test->connection = gcr_ssh_agent_process_connect (test->process, NULL, &error);
+       g_assert_nonnull (test->connection);
+       g_assert_no_error (error);
+}
+
+static void
+test_connect (Test *test, gconstpointer unused)
+{
+       connect_to_process (test);
+}
+
+static void
+call (Test *test)
+{
+       GError *error;
+       gboolean ret;
+
+       error = NULL;
+       ret = _gcr_ssh_agent_call (test->connection, &test->req, &test->resp, NULL, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+}
+
+DEFINE_CALL_FUNCS(Test, call)
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+       connect_to_process (test);
+       call_request_identities(test, 0);
+}
+
+static void
+test_add (Test *test, gconstpointer unused)
+{
+       connect_to_process (test);
+       call_add_identity (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_remove (Test *test, gconstpointer unused)
+{
+       connect_to_process (test);
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_remove_identity (test);
+       call_request_identities (test, 0);
+}
+
+static void
+test_remove_all (Test *test, gconstpointer unused)
+{
+       connect_to_process (test);
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_remove_all_identities (test);
+       call_request_identities (test, 0);
+}
+
+static void
+test_sign (Test *test, gconstpointer unused)
+{
+       connect_to_process (test);
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_sign (test);
+
+       call_remove_all_identities (test);
+       call_request_identities (test, 0);
+}
+
+static gpointer
+kill_thread (gpointer data)
+{
+       Test *test = data;
+       GPid pid;
+
+       pid = gcr_ssh_agent_process_get_pid (test->process);
+       g_assert_cmpint (-1, !=, pid);
+
+       kill (pid, SIGTERM);
+
+       return NULL;
+}
+
+static void
+on_closed (GcrSshAgentProcess *self, gpointer data)
+{
+       GMainLoop *loop = data;
+
+       g_main_loop_quit (loop);
+       g_main_loop_unref (loop);
+}
+
+static void
+test_restart (Test *test, gconstpointer unused)
+{
+       GPid pid;
+       GMainLoop *loop;
+       GThread *thread;
+
+       connect_to_process (test);
+
+       pid = gcr_ssh_agent_process_get_pid (test->process);
+       g_assert_cmpint (0, !=, pid);
+
+       thread = g_thread_new ("kill", kill_thread, test);
+
+       loop = g_main_loop_new (NULL, FALSE);
+       g_signal_connect (test->process, "closed", G_CALLBACK (on_closed), loop);
+       g_main_loop_run (loop);
+
+       g_thread_join (thread);
+
+       pid = gcr_ssh_agent_process_get_pid (test->process);
+       g_assert_cmpint (0, ==, pid);
+
+       connect_to_process (test);
+
+       pid = gcr_ssh_agent_process_get_pid (test->process);
+       g_assert_cmpint (0, !=, pid);
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add ("/ssh-agent/process/connect", Test, NULL, setup, test_connect, teardown);
+       g_test_add ("/ssh-agent/process/list", Test, NULL, setup, test_list, teardown);
+       g_test_add ("/ssh-agent/process/add", Test, NULL, setup, test_add, teardown);
+       g_test_add ("/ssh-agent/process/remove", Test, NULL, setup, test_remove, teardown);
+       g_test_add ("/ssh-agent/process/remove_all", Test, NULL, setup, test_remove_all, teardown);
+       g_test_add ("/ssh-agent/process/sign", Test, NULL, setup, test_sign, teardown);
+       g_test_add ("/ssh-agent/process/restart", Test, NULL, setup, test_restart, teardown);
+
+       return g_test_run ();
+}
diff --git a/gcr/test-ssh-agent-service.c b/gcr/test-ssh-agent-service.c
new file mode 100644
index 0000000..80afc79
--- /dev/null
+++ b/gcr/test-ssh-agent-service.c
@@ -0,0 +1,618 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-service.h"
+#include "gcr-ssh-agent-private.h"
+#include "gcr-ssh-agent-util.h"
+#include "gcr-ssh-agent-test.h"
+#include "egg/egg-testing.h"
+#include "egg/mock-interaction.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gunixsocketaddress.h>
+
+typedef struct {
+       gchar *directory;
+       EggBuffer req;
+       EggBuffer resp;
+       GcrSshAgentService *service;
+       GMainContext *server_thread_context;
+       volatile gint server_thread_stop;
+       GSocketConnection *connection;
+       GThread *thread;
+       GMutex lock;
+       GCond cond;
+} Test;
+
+static gpointer
+server_thread (gpointer data)
+{
+       Test *test = data;
+       gboolean ret;
+
+       g_main_context_push_thread_default (test->server_thread_context);
+
+       ret = gcr_ssh_agent_service_start (test->service);
+       g_assert_true (ret);
+
+       g_mutex_lock (&test->lock);
+       g_cond_signal (&test->cond);
+       g_mutex_unlock (&test->lock);
+
+       while (g_atomic_int_get (&test->server_thread_stop) == 0)
+               g_main_context_iteration (test->server_thread_context, TRUE);
+
+       g_main_context_pop_thread_default (test->server_thread_context);
+
+       return NULL;
+}
+
+static void
+connect_to_server (Test *test)
+{
+       const gchar *envvar;
+       GSocketClient *client;
+       GSocketAddress *address;
+       GError *error;
+
+       envvar = g_getenv ("SSH_AUTH_SOCK");
+       g_assert_nonnull (envvar);
+       address = g_unix_socket_address_new (envvar);
+
+       client = g_socket_client_new ();
+
+       error = NULL;
+       test->connection = g_socket_client_connect (client,
+                                                   G_SOCKET_CONNECTABLE (address),
+                                                   NULL,
+                                                   &error);
+       g_assert_nonnull (test->connection);
+       g_assert_no_error (error);
+
+       g_object_unref (address);
+       g_object_unref (client);
+}
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+       GTlsInteraction *interaction;
+       GcrSshAgentPreload *preload;
+       gchar *sockets_path;
+       gchar *preload_path;
+       gchar *path;
+
+       test->directory = egg_tests_create_scratch_directory (NULL, NULL);
+
+       sockets_path = g_build_filename (test->directory, "sockets", NULL);
+       g_mkdir (sockets_path, 0700);
+
+       preload_path = g_build_filename (test->directory, "preload", NULL);
+       g_mkdir (preload_path, 0700);
+
+       egg_tests_copy_scratch_file (preload_path, SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain");
+       egg_tests_copy_scratch_file (preload_path, SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub");
+
+       path = g_build_filename (preload_path, "id_rsa_plain", NULL);
+       g_chmod (path, 0600);
+       g_free (path);
+
+       egg_buffer_init_full (&test->req, 128, (EggBufferAllocator)g_realloc);
+       egg_buffer_init_full (&test->resp, 128, (EggBufferAllocator)g_realloc);
+
+       preload = gcr_ssh_agent_preload_new (preload_path);
+       g_free (preload_path);
+
+       test->service = gcr_ssh_agent_service_new (sockets_path, preload);
+       g_free (sockets_path);
+
+       interaction = mock_interaction_new ("password");
+       g_object_set (test->service, "interaction", interaction, NULL);
+
+       g_object_unref (interaction);
+       g_object_unref (preload);
+
+       g_mutex_init (&test->lock);
+       g_cond_init (&test->cond);
+       test->server_thread_context = g_main_context_new ();
+
+       test->thread = g_thread_new ("ssh-agent", server_thread, test);
+
+       /* Wait until the server is up */
+       g_mutex_lock (&test->lock);
+       g_cond_wait (&test->cond, &test->lock);
+       g_mutex_unlock (&test->lock);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+       g_atomic_int_set (&test->server_thread_stop, 1);
+       g_main_context_wakeup (test->server_thread_context);
+       g_thread_join (test->thread);
+
+       g_main_context_unref (test->server_thread_context);
+
+       g_clear_object (&test->connection);
+
+       gcr_ssh_agent_service_stop (test->service);
+       g_object_unref (test->service);
+
+       egg_buffer_uninit (&test->req);
+       egg_buffer_uninit (&test->resp);
+
+       egg_tests_remove_scratch_directory (test->directory);
+       g_free (test->directory);
+
+       g_cond_clear (&test->cond);
+       g_mutex_clear (&test->lock);
+}
+
+static void
+call (Test *test)
+{
+       GError *error;
+       gboolean ret;
+
+       error = NULL;
+       ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+
+       error = NULL;
+       ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+}
+
+static void
+call_error_or_failure (Test *test, gint dom, gint code)
+{
+       GError *error;
+       gboolean ret;
+
+       error = NULL;
+       ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+
+       error = NULL;
+       ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+       if (ret)
+               check_failure (&test->resp);
+       else {
+               g_assert_false (ret);
+               g_assert_error (error, dom, code);
+       }
+}
+
+DEFINE_CALL_FUNCS(Test, call)
+
+static void
+call_unparseable_add (Test *test)
+{
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_add_identity (&test->req);
+       egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
+       call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
+}
+
+static void
+call_unparseable_remove (Test *test)
+{
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_remove_identity (&test->req);
+       egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
+       call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
+}
+
+static void
+call_unparseable_sign (Test *test)
+{
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_sign_request (&test->req);
+       egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
+       call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
+}
+
+static void
+prepare_sign_request_unknown (EggBuffer *req)
+{
+       GBytes *public_key;
+       gchar *comment;
+       gsize length;
+       const guchar *blob;
+       gboolean ret;
+
+       public_key = public_key_from_file (SRCDIR "/gcr/fixtures/ssh-agent/id_ecdsa_plain.pub", &comment);
+       g_free (comment);
+       blob = g_bytes_get_data (public_key, &length);
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GCR_SSH_OP_SIGN_REQUEST);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte_array (req, blob, length);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "data");
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+
+       g_bytes_unref (public_key);
+}
+
+static void
+call_sign_unknown (Test *test)
+{
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_sign_request_unknown (&test->req);
+       call (test);
+       check_failure (&test->resp);
+}
+
+static void
+call_empty (Test *test)
+{
+       GError *error;
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       ret = egg_buffer_add_uint32 (&test->req, 0);
+       g_assert_true (ret);
+
+       error = NULL;
+       ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+
+       error = NULL;
+       ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+       g_assert_false (ret);
+       g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED);
+}
+
+static void
+call_unknown (Test *test)
+{
+       GError *error;
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       ret = egg_buffer_add_uint32 (&test->req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (&test->req, 255);
+       g_assert_true (ret);
+
+       error = NULL;
+       ret = _gcr_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+
+       error = NULL;
+       ret = _gcr_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+       g_assert_true (ret);
+       g_assert_no_error (error);
+
+       check_failure (&test->resp);
+}
+
+static void
+call_lock (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       ret = egg_buffer_add_uint32 (&test->req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (&test->req, GCR_SSH_OP_LOCK);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (&test->req, "password");
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (&test->req, 0, test->req.len - 4);
+       g_assert_true (ret);
+
+       call (test);
+
+       check_success (&test->resp);
+}
+
+static void
+call_unlock (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       ret = egg_buffer_add_uint32 (&test->req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (&test->req, GCR_SSH_OP_UNLOCK);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (&test->req, "password");
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (&test->req, 0, test->req.len - 4);
+       g_assert_true (ret);
+
+       call (test);
+
+       check_success (&test->resp);
+}
+
+static void
+test_startup_shutdown (Test *test, gconstpointer unused)
+{
+}
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_request_identities (test, 1);
+}
+
+static void
+test_add (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GCR_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_unparseable_add (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_unparseable_add (test);
+}
+
+static void
+test_unparseable_remove (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_unparseable_remove (test); /* This closes the connection */
+}
+
+static void
+test_unparseable_sign (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_unparseable_sign (test); /* This closes the connection */
+}
+
+static void
+test_remove (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GCR_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       /* Removing an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GCR_SSH_OP_REQUEST_IDENTITIES */
+       call_remove_identity (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_remove_all (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GCR_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       /* Removing an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GCR_SSH_OP_REQUEST_IDENTITIES */
+       call_remove_all_identities (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_sign_loaded (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GCR_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_sign (test);
+}
+
+static void
+test_sign (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_sign (test);
+}
+
+static void
+test_sign_unknown (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_sign_unknown (test);
+}
+
+static gpointer
+kill_thread (gpointer data)
+{
+       Test *test = data;
+       GcrSshAgentProcess *process;
+       GPid pid;
+
+       process = gcr_ssh_agent_service_get_process (test->service);
+       pid = gcr_ssh_agent_process_get_pid (process);
+       g_assert_cmpint (-1, !=, pid);
+
+       kill (pid, SIGTERM);
+
+       return NULL;
+}
+
+static void
+on_closed (GcrSshAgentProcess *self, gpointer data)
+{
+       GMainLoop *loop = data;
+
+       g_main_loop_quit (loop);
+       g_main_loop_unref (loop);
+}
+
+static void
+test_restart (Test *test, gconstpointer unused)
+{
+       GcrSshAgentProcess *process;
+       GThread *thread;
+       GMainLoop *loop;
+       GBytes *public_key;
+       gchar *comment;
+
+       connect_to_server (test);
+
+       public_key = public_key_from_file (SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_plain.pub", &comment);
+       g_free (comment);
+
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       g_assert_true (gcr_ssh_agent_service_lookup_key (test->service, public_key));
+
+       thread = g_thread_new ("kill", kill_thread, test);
+
+       loop = g_main_loop_new (NULL, FALSE);
+
+       process = gcr_ssh_agent_service_get_process (test->service);
+       g_signal_connect (process, "closed", G_CALLBACK (on_closed), loop);
+       g_main_loop_run (loop);
+
+       g_thread_join (thread);
+
+       g_assert_false (gcr_ssh_agent_service_lookup_key (test->service, public_key));
+
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       g_assert_true (gcr_ssh_agent_service_lookup_key (test->service, public_key));
+       g_bytes_unref (public_key);
+}
+
+static void
+test_empty (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_empty (test);
+}
+
+static void
+test_unknown (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_unknown (test);
+}
+
+static void
+test_lock (Test *test, gconstpointer unused)
+{
+       connect_to_server (test);
+
+       call_lock (test);
+       call_unlock (test);
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add ("/ssh-agent/service/startup_shutdown", Test, NULL, setup, test_startup_shutdown, 
teardown);
+       g_test_add ("/ssh-agent/service/list", Test, NULL, setup, test_list, teardown);
+       g_test_add ("/ssh-agent/service/add", Test, NULL, setup, test_add, teardown);
+       g_test_add ("/ssh-agent/service/remove", Test, NULL, setup, test_remove, teardown);
+       g_test_add ("/ssh-agent/service/remove_all", Test, NULL, setup, test_remove_all, teardown);
+       g_test_add ("/ssh-agent/service/sign_loaded", Test, NULL, setup, test_sign_loaded, teardown);
+       g_test_add ("/ssh-agent/service/sign", Test, NULL, setup, test_sign, teardown);
+       g_test_add ("/ssh-agent/service/sign_unknown", Test, NULL, setup, test_sign_unknown, teardown);
+       g_test_add ("/ssh-agent/service/empty", Test, NULL, setup, test_empty, teardown);
+       g_test_add ("/ssh-agent/service/unknown", Test, NULL, setup, test_unknown, teardown);
+       g_test_add ("/ssh-agent/service/unparseable_add", Test, NULL, setup, test_unparseable_add, teardown);
+       g_test_add ("/ssh-agent/service/unparseable_remove", Test, NULL, setup, test_unparseable_remove, 
teardown);
+       g_test_add ("/ssh-agent/service/unparseable_sign", Test, NULL, setup, test_unparseable_sign, 
teardown);
+       g_test_add ("/ssh-agent/service/restart", Test, NULL, setup, test_restart, teardown);
+       g_test_add ("/ssh-agent/service/lock", Test, NULL, setup, test_lock, teardown);
+
+       return g_test_run ();
+}
diff --git a/gcr/test-ssh-agent-util.c b/gcr/test-ssh-agent-util.c
new file mode 100644
index 0000000..ef4b202
--- /dev/null
+++ b/gcr/test-ssh-agent-util.c
@@ -0,0 +1,119 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2008 Stefan Walter
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef thewalter net>
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-util.h"
+
+#include <glib.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+static struct {
+       const char *filename;
+       const char *encoded;
+} PUBLIC_FILES[] = {
+       { SRCDIR "/gcr/fixtures/ssh-agent/id_rsa_test.pub",
+         
"AAAAB3NzaC1yc2EAAAABIwAAAQEAoD6VKqkhay6pKHSRjAGWWfFPU8xfsi2gnOwP/B1UHDoztx3czhO+py/fTlhCnSP1jsjkrVIZcnzah2fUNFFRgS4+jROBtvbgHsS72V1E6+ZogV+mBJWWAhw0iPrmQ3Kvm38D3PByo5Y7yKO5kIG2LloYLjosJ5F4sx2xh0uz2wXNtnY1b5xhe2+VEksm9OB+FXaUkZC2fQrTNo8ZGFJQSFd8kUhIfbUDJmlYuZ+vvHM+A3Lc9rHyW4IPaRyxFQciRmb+ZQqU2uSdOXAhg17lskuX/q8yCI5Hy5eDicC222oUMdJTtYgwX4dQCU8TICWhxb3x4RCV+g7D99+tkIvv+w=="
 },
+       { SRCDIR "/gcr/fixtures/ssh-agent/id_dsa_test.pub",
+         
"AAAAB3NzaC1kc3MAAACBANHNmw2YHEodUj4Ae27i8Rm8uoLnpS68QEiCJx8bv9P1o0AaD0w55sH+TBzlo7vtAEDlAzIOBY3PMpy5WarELTIeXmFPzKfHL8tuxMbOPaN/wDkDZNnJZsqlyRwlQKStPcAlvLBNuMjA53u2ndMTVghtUHXETQzwxKhXf7TmvfLBAAAAFQDnF/Y8MgFCP0PpRC5ZAQo1dyDEwwAAAIEAr4iOpTeZx8i1QgQpRl+dmbBAtHTXbPiophzNJBge9lixqF0T3egN2B9wGGnumIXmnst9RPPjuu+cHCLfxhXHzLlW8MLwoiF6ZQOx9M8WcfWIl5oiGyr2e969woRf5OcMGQPOQBdws6MEtemRqq5gu6dqDqVl3xfhSZSP9LpqAI8AAACAUjiuQ3qGErsCz++qd0qrR++QA185XGXAPZqQEHcr4iKSlO17hSUYA03kOWtDaeRtJOlxjIjl9iLo3juKGFgxUfo2StScOSO2saTWFGjA4MybHCK1+mIYXRcYrq314yK2Tmbql/UGDWpcCCGXLWpSFHTaXTbJjPd6VL+TO9/8tFk="
 },
+       { SRCDIR "/gcr/fixtures/ssh-agent/identity.pub",
+         NULL }
+};
+
+#define COMMENT "A public key comment"
+
+static void
+test_parse_public (void)
+{
+       GBytes *input_bytes, *output_bytes;
+       gchar *comment;
+       guchar *data;
+       const guchar *blob;
+       gsize n_data;
+       gchar *encoded;
+       gsize i;
+
+       for (i = 0; i < G_N_ELEMENTS (PUBLIC_FILES); ++i) {
+               if (!g_file_get_contents (PUBLIC_FILES[i].filename, (gchar **)&data, &n_data, NULL))
+                       g_assert_not_reached ();
+
+               input_bytes = g_bytes_new_take (data, n_data);
+               output_bytes = _gcr_ssh_agent_parse_public_key (input_bytes, &comment);
+               g_bytes_unref (input_bytes);
+               if (PUBLIC_FILES[i].encoded == NULL) {
+                       g_assert (output_bytes == NULL);
+               } else {
+                       g_assert (output_bytes);
+
+                       blob = g_bytes_get_data (output_bytes, &n_data);
+                       encoded = g_base64_encode (blob, n_data);
+                       g_bytes_unref (output_bytes);
+                       g_assert_cmpstr (encoded, ==, PUBLIC_FILES[i].encoded);
+                       g_free (encoded);
+
+                       g_assert_cmpstr (comment, ==, COMMENT);
+                       g_free (comment);
+               }
+       }
+}
+
+static void
+test_canon_error (void)
+{
+       static const gchar input[] =
+               "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\r\n"
+               "@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @\r\n"
+               "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
+               "Permissions 0620 for '/home/foo/.ssh/id_rsa' are too open.\r\n"
+               "It is required that your private key files are NOT accessible by others.\r\n"
+               "This private key will be ignored.\r\n";
+       static const gchar expected[] =
+               "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
+               "@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @\n"
+               "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
+               "Permissions 0620 for '/home/foo/.ssh/id_rsa' are too open.\n"
+               "It is required that your private key files are NOT accessible by others.\n"
+               "This private key will be ignored.\n";
+       gchar *p, *output;
+
+       p = g_strdup (input);
+       output = _gcr_ssh_agent_canon_error (p);
+
+       g_assert (output == p);
+       g_assert_cmpstr (expected, ==, output);
+
+       g_free (p);
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add_func ("/ssh-agent/util/parse_public", test_parse_public);
+       g_test_add_func ("/ssh-agent/util/canon_error", test_canon_error);
+
+       return g_test_run ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]