diff --git a/sso_test.go b/sso_test.go
index 420c7645a935eec08df2ea9764ad7f62ce282d06..ebc33a11973cd15735fc1fe617fdf527a192a6ab 100644
--- a/sso_test.go
+++ b/sso_test.go
@@ -1,6 +1,11 @@
 package sso
 
 import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
 	"testing"
 	"time"
 
@@ -42,6 +47,8 @@ var (
 	legacyPublicKey = []byte{47, 234, 144, 101, 76, 245, 1, 73, 155, 115, 89, 105, 165, 252, 49, 114, 48, 166, 231, 130, 82, 123, 147, 179, 50, 50, 34, 198, 219, 251, 151, 17}
 )
 
+// TestLegacy verifies that tickets signed by the legacy C
+// implementation can be verified correctly by the Go code.
 func TestLegacy(t *testing.T) {
 	validator := &ssoValidator{publicKey: legacyPublicKey}
 	tkt, err := validator.parse(legacyTicket)
@@ -52,3 +59,101 @@ func TestLegacy(t *testing.T) {
 		t.Fatalf("decoded bad values: %+v", tkt)
 	}
 }
+
+// Create a ssoSigner and sign a valid ticket.
+func makeTestSigner(t *testing.T) (string, string, func()) {
+	dir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	pub, priv, err := ed25519.GenerateKey(nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	pubPath := filepath.Join(dir, "public.key")
+	ioutil.WriteFile(pubPath, pub, 0600)
+	//ioutil.WriteFile(filepath.Join(dir, "secret.key"), priv, 0600)
+
+	tkt := NewTicket("user", "service/", "domain", "nonce", nil, 300*time.Second)
+	signer := &ssoSigner{key: priv}
+	signed, err := signer.Sign(tkt)
+	if err != nil {
+		t.Fatal("Sign():", err)
+	}
+
+	return dir, signed, func() {
+		os.RemoveAll(dir)
+	}
+}
+
+// TestLegacyIntegration verifies that tickets signed by the Go code
+// can be verified successfully by the legacy C implementation.
+//
+// To run this test, set the SSO_SRCDIR environment variable and point
+// it at the root of the ai/sso source package repository.
+func TestLegacyIntegration(t *testing.T) {
+	ssoSrcDir := os.Getenv("SSO_SRCDIR")
+	if ssoSrcDir == "" {
+		t.Skip("SSO_SRCDIR not set")
+	}
+	ssotool := filepath.Join(ssoSrcDir, "src/sso/ssotool")
+	if _, err := os.Stat(ssotool); os.IsNotExist(err) {
+		t.Skip("ssotool not installed")
+	}
+
+	dir, signed, cleanup := makeTestSigner(t)
+	defer cleanup()
+	pubPath := filepath.Join(dir, "public.key")
+
+	cmd := exec.Command(ssotool, "--public-key", pubPath, "--service", "service/", "--domain", "domain", "--nonce", "nonce", "--verify", signed)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+
+	err := cmd.Run()
+	if err != nil {
+		t.Fatalf("ssotool validation failed: %v", err)
+	}
+}
+
+// TestLegacyIntegration verifies that tickets signed by the Go code
+// can be verified successfully by the legacy Python implementation.
+//
+// To run this test, set the SSO_SRCDIR environment variable and point
+// it at the root of the ai/sso source package repository.
+func TestLegacyIntegration_Python(t *testing.T) {
+	ssoSrcDir := os.Getenv("SSO_SRCDIR")
+	if ssoSrcDir == "" {
+		t.Skip("SSO_SRCDIR not set")
+	}
+	pythonPath := fmt.Sprintf(
+		"%s:%s",
+		filepath.Join(ssoSrcDir, "src/python"),
+		filepath.Join(ssoSrcDir, "src/python/sso"),
+	)
+	libPath := filepath.Join(ssoSrcDir, "src/sso/.libs")
+
+	dir, signed, cleanup := makeTestSigner(t)
+	defer cleanup()
+
+	pubPath := filepath.Join(dir, "public.key")
+	scriptPath := filepath.Join(dir, "test.py")
+	ioutil.WriteFile(scriptPath, []byte(`
+import sso
+import sys
+with open(sys.argv[1]) as fd:
+    pubkey = fd.read()
+verifier = sso.Verifier(pubkey, "service/", "domain", None)
+tkt = verifier.verify(sys.argv[2].encode(), "nonce")
+sys.exit(0 if (tkt.user() == "user") else 1)
+`), 0600)
+
+	cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("env PYTHONPATH=%s LD_LIBRARY_PATH=%s python %s %s %s", pythonPath, libPath, scriptPath, pubPath, signed))
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+
+	err := cmd.Run()
+	if err != nil {
+		t.Fatalf("python validation failed: %v", err)
+	}
+}