func TestListTrackingJSON(t *testing.T) {
	tc := SetupEngineTest(t, "track")
	defer tc.Cleanup()
	fu := CreateAndSignupFakeUser(tc, "track")
	fu.LoginOrBust(tc)

	trackAlice(tc, fu)
	defer untrackAlice(tc, fu)

	arg := ListTrackingEngineArg{JSON: true, Verbose: true}
	eng := NewListTrackingEngine(&arg, tc.G)
	ctx := Context{}
	err := RunEngine(eng, &ctx)
	if err != nil {
		t.Fatal("Error in ListTrackingEngine:", err)
	}

	jw, err := jsonw.Unmarshal([]byte(eng.JSONResult()))
	if err != nil {
		t.Fatal(err)
	}
	pgpKeys := jw.AtIndex(0).AtPath("body.track.pgp_keys")
	n, err := pgpKeys.Len()
	if err != nil {
		t.Fatal(err)
	}
	if n != 1 {
		t.Errorf("num pgp_keys: %d, expected 1", n)
	}
}
示例#2
0
文件: config.go 项目: qbit/client
func (h ConfigHandler) SetValue(_ context.Context, arg keybase1.SetValueArg) (err error) {
	w := h.G().Env.GetConfigWriter()
	if arg.Path == "users" {
		err = fmt.Errorf("The field 'users' cannot be edited for fear of config corruption")
		return err
	}
	switch {
	case arg.Value.IsNull:
		w.SetNullAtPath(arg.Path)
	case arg.Value.S != nil:
		w.SetStringAtPath(arg.Path, *arg.Value.S)
	case arg.Value.I != nil:
		w.SetIntAtPath(arg.Path, *arg.Value.I)
	case arg.Value.B != nil:
		w.SetBoolAtPath(arg.Path, *arg.Value.B)
	case arg.Value.O != nil:
		var jw *jsonw.Wrapper
		jw, err = jsonw.Unmarshal([]byte(*arg.Value.O))
		if err == nil {
			err = w.SetWrapperAtPath(arg.Path, jw)
		}
	default:
		err = fmt.Errorf("Bad type for setting a value")
	}
	if err == nil {
		h.G().ConfigReload()
	}
	return err
}
// CounterSign implements CounterSign in kex2.Provisioner.
func (e *Kex2Provisioner) CounterSign(input keybase1.HelloRes) (sig []byte, err error) {
	e.G().Log.Debug("+ CounterSign()")
	defer func() { e.G().Log.Debug("- CounterSign() -> %s", libkb.ErrToOk(err)) }()

	jw, err := jsonw.Unmarshal([]byte(input))
	if err != nil {
		return nil, err
	}

	// check the reverse signature
	if err = e.checkReverseSig(jw); err != nil {
		e.G().Log.Debug("provisioner failed to verify reverse sig: %s", err)
		return nil, err
	}
	e.G().Log.Debug("provisioner verified reverse sig")

	// remember some device information for ProvisionUI.ProvisionerSuccess()
	if err = e.rememberDeviceInfo(jw); err != nil {
		return nil, err
	}

	// sign the whole thing with provisioner's signing key
	s, _, _, err := libkb.SignJSON(jw, e.signingKey)
	if err != nil {
		return nil, err
	}

	return []byte(s), nil
}
示例#4
0
文件: db.go 项目: mark-adams/client
func (j *JSONLocalDb) Lookup(id DbKey) (*jsonw.Wrapper, error) {
	bytes, found, err := j.engine.Lookup(id)
	var ret *jsonw.Wrapper
	if found {
		ret, err = jsonw.Unmarshal(bytes)
	}
	return ret, err
}
示例#5
0
func (path PathSteps) VerifyPath(curr NodeHash, uidS string) (juser *jsonw.Wrapper, err error) {

	bpath := uidS
	pos := 0
	lastTyp := 0

	for i, step := range path {
		payload := step.node
		if !curr.Check(payload) {
			err = fmt.Errorf("Hash mismatch at level=%d", i)
			break
		}

		var jw *jsonw.Wrapper
		jw, err = jsonw.Unmarshal([]byte(payload))
		if err != nil {
			err = fmt.Errorf("Can't parse JSON at level=%d: %s", i, err)
			break
		}

		plen := len(step.prefix)

		epos := pos + plen
		if bpath[pos:epos] != step.prefix {
			err = fmt.Errorf("Path mismatch at level %d: %s != %s",
				i, bpath[pos:epos], step.prefix)
			break
		}
		pos = epos

		lastTyp, err = jw.AtKey("type").GetInt()
		if err != nil {
			err = fmt.Errorf("At level %d, failed to get a valid 'type'", i)
			break
		}

		if lastTyp == MerkleTreeNode {
			if plen == 0 {
				err = fmt.Errorf("Empty prefix len at level=%d", i)
				return
			}
			curr, err = GetNodeHash(jw.AtKey("tab").AtKey(step.prefix))
			if err != nil {
				err = MerkleNotFoundError{uidS, err.Error()}
				break
			}
			juser = nil
		} else {
			juser = jw.AtKey("tab").AtKey(uidS)
		}
	}

	if err == nil && juser == nil {
		err = MerkleNotFoundError{uidS, "tree path didn't end in a leaf"}
	}
	return
}
示例#6
0
文件: gregor.go 项目: qbit/client
func (h IdentifyUIHandler) handleShowTrackerPopupCreate(ctx context.Context, cli gregor1.IncomingInterface,
	item gregor.Item) error {

	h.G().Log.Debug("handleShowTrackerPopupCreate: %+v", item)
	if item.Body() == nil {
		return errors.New("gregor handler for show_tracker_popup: nil message body")
	}
	body, err := jsonw.Unmarshal(item.Body().Bytes())
	if err != nil {
		h.G().Log.Debug("body failed to unmarshal", err)
		return err
	}
	uidString, err := body.AtPath("uid").GetString()
	if err != nil {
		h.G().Log.Debug("failed to extract uid", err)
		return err
	}
	uid, err := keybase1.UIDFromString(uidString)
	if err != nil {
		h.G().Log.Debug("failed to convert UID from string", err)
		return err
	}

	identifyUI, err := h.G().UIRouter.GetIdentifyUI()
	if err != nil {
		h.G().Log.Debug("failed to get IdentifyUI", err)
		return err
	}
	if identifyUI == nil {
		h.G().Log.Debug("got nil IdentifyUI")
		return errors.New("got nil IdentifyUI")
	}
	secretUI, err := h.G().UIRouter.GetSecretUI(0)
	if err != nil {
		h.G().Log.Debug("failed to get SecretUI", err)
		return err
	}
	if secretUI == nil {
		h.G().Log.Debug("got nil SecretUI")
		return errors.New("got nil SecretUI")
	}
	engineContext := engine.Context{
		IdentifyUI: identifyUI,
		SecretUI:   secretUI,
	}

	identifyReason := keybase1.IdentifyReason{
		Type: keybase1.IdentifyReasonType_TRACK,
		// TODO: text here?
	}
	identifyArg := keybase1.Identify2Arg{Uid: uid, Reason: identifyReason}
	identifyEng := engine.NewIdentify2WithUID(h.G(), &identifyArg)
	identifyEng.SetResponsibleGregorItem(item)
	return identifyEng.Run(&engineContext)
}
示例#7
0
func importTrackingLink(t *testing.T, g *libkb.GlobalContext) *libkb.TrackChainLink {
	jw, err := jsonw.Unmarshal([]byte(trackingServerReply))
	if err != nil {
		t.Fatal(err)
	}
	cl, err := libkb.ImportLinkFromServer(g, nil, jw, trackingUID)
	if err != nil {
		t.Fatal(err)
	}
	gl := libkb.GenericChainLink{ChainLink: cl}
	tcl, err := libkb.ParseTrackChainLink(gl)
	if err != nil {
		t.Fatal(err)
	}
	return tcl
}
示例#8
0
文件: gregor.go 项目: qbit/client
func (h IdentifyUIHandler) handleShowTrackerPopupDismiss(ctx context.Context, cli gregor1.IncomingInterface,
	item gregor.Item) error {

	h.G().Log.Debug("handleShowTrackerPopupDismiss: %+v", item)
	if item.Body() == nil {
		return errors.New("gregor dismissal for show_tracker_popup: nil message body")
	}
	body, err := jsonw.Unmarshal(item.Body().Bytes())
	if err != nil {
		h.G().Log.Debug("body failed to unmarshal", err)
		return err
	}
	uidString, err := body.AtPath("uid").GetString()
	if err != nil {
		h.G().Log.Debug("failed to extract uid", err)
		return err
	}
	uid, err := keybase1.UIDFromString(uidString)
	if err != nil {
		h.G().Log.Debug("failed to convert UID from string", err)
		return err
	}
	user, err := libkb.LoadUser(libkb.NewLoadUserByUIDArg(h.G(), uid))
	if err != nil {
		h.G().Log.Debug("failed to load user from UID", err)
		return err
	}

	identifyUI, err := h.G().UIRouter.GetIdentifyUI()
	if err != nil {
		h.G().Log.Debug("failed to get IdentifyUI", err)
		return err
	}
	if identifyUI == nil {
		h.G().Log.Debug("got nil IdentifyUI")
		return errors.New("got nil IdentifyUI")
	}

	reason := keybase1.DismissReason{
		Type: keybase1.DismissReasonType_HANDLED_ELSEWHERE,
	}
	identifyUI.Dismiss(user.GetName(), reason)

	return nil
}
示例#9
0
func NewMerkleRootFromJSON(jw *jsonw.Wrapper, g *GlobalContext) (ret *MerkleRoot, err error) {
	var seqno int64
	var sigs *jsonw.Wrapper
	var payloadJSONString string
	var pj *jsonw.Wrapper
	var fp PGPFingerprint
	var rh, lurh NodeHash
	var ctime int64

	if sigs, err = jw.AtKey("sigs").ToDictionary(); err != nil {
		return
	}

	if payloadJSONString, err = jw.AtKey("payload_json").GetString(); err != nil {
		return
	}

	if pj, err = jsonw.Unmarshal([]byte(payloadJSONString)); err != nil {
		return
	}

	GetPGPFingerprintVoid(pj.AtPath("body.key.fingerprint"), &fp, &err)
	pj.AtPath("body.seqno").GetInt64Void(&seqno, &err)
	GetNodeHashVoid(pj.AtPath("body.root"), &rh, &err)
	lurh, _ = GetNodeHash(pj.AtPath("body.legacy_uid_root"))
	pj.AtKey("ctime").GetInt64Void(&ctime, &err)

	if err != nil {
		return
	}

	ret = &MerkleRoot{
		seqno:             Seqno(seqno),
		pgpFingerprint:    fp,
		sigs:              sigs,
		payloadJSONString: payloadJSONString,
		payloadJSON:       pj,
		rootHash:          rh,
		legacyUIDRootHash: lurh,
		ctime:             ctime,
		Contextified:      NewContextified(g),
	}
	return
}
示例#10
0
func (s *sendInvitationMock) Post(arg APIArg) (*APIRes, error) {
	if _, err := s.APIArgRecorder.Post(arg); err != nil {
		return nil, err
	}
	jw, err := jsonw.Unmarshal([]byte(`{"status":{"code":0,"name":"OK"},"short_code":"clip outside broccoli culture","invitation_id":"2b25175f6da1d9155f23800d","csrf_token":"lgHZIDBlNjRhNDBhOTQ3ZWYyMTMxOWQ4MzM1Y2M4YjQ1YjE5zlcVNMHOAAFRgMDEIFyHOIg/AetihKRvVMNT2NoBNNG1QoCVxtDfzEK7/rdF"}`))
	if err != nil {
		return nil, err
	}
	return &APIRes{
		Status:     jw.AtKey("status"),
		Body:       jw,
		HTTPStatus: 200,
		AppStatus: &AppStatus{
			Code: SCOk,
			Name: "OK",
			Desc: "Ok",
		},
	}, nil
}
示例#11
0
// HandleHello implements HandleHello in kex2.Provisionee.
func (e *Kex2Provisionee) HandleHello(harg keybase1.HelloArg) (res keybase1.HelloRes, err error) {
	e.G().Log.Debug("+ HandleHello()")
	defer func() { e.G().Log.Debug("- HandleHello() -> %s", libkb.ErrToOk(err)) }()

	// save parts of the hello arg for later:
	e.uid = harg.Uid
	e.sessionToken = harg.Token
	e.csrfToken = harg.Csrf
	e.pps = harg.Pps

	jw, err := jsonw.Unmarshal([]byte(harg.SigBody))
	if err != nil {
		return res, err
	}

	// need the username later:
	e.username, err = jw.AtPath("body.key.username").GetString()
	if err != nil {
		return res, err
	}

	e.eddsa, err = libkb.GenerateNaclSigningKeyPair()
	if err != nil {
		return res, err
	}

	if err = e.addDeviceSibkey(jw); err != nil {
		return res, err
	}

	if err = e.reverseSig(jw); err != nil {
		return res, err
	}

	out, err := jw.Marshal()
	if err != nil {
		return res, err
	}

	return keybase1.HelloRes(out), err
}
示例#12
0
func TestAPIServerPostJSON(t *testing.T) {

	tc := libkb.SetupTest(t, "apiserver", 2)
	defer tc.Cleanup()
	tc.G.SetService()

	_, err := kbtest.CreateAndSignupFakeUser("apivr", tc.G)
	if err != nil {
		t.Fatal(err)
	}

	jsonPayload := []keybase1.StringKVPair{
		{Key: "sigs", Value: "[]"},
	}

	arg := keybase1.PostJSONArg{
		Endpoint:    "key/multi",
		JSONPayload: jsonPayload,
	}

	handler := NewAPIServerHandler(nil, tc.G)
	res, err := handler.doPostJSON(arg)
	if err != nil {
		t.Fatal(err)
	}

	jw, err := jsonw.Unmarshal([]byte(res.Body))
	if err != nil {
		t.Fatal(err)
	}

	namew := jw.AtKey("status").AtKey("name")
	name, err := namew.GetString()
	if err != nil {
		t.Fatal(err)
	}

	if name != "OK" {
		t.Fatalf("wrong name returned: %s != %s", name, "OK")
	}
}
示例#13
0
func TestAPIServerGet(t *testing.T) {
	tc := libkb.SetupTest(t, "apiserver", 2)
	defer tc.Cleanup()
	tc.G.SetService()

	_, err := kbtest.CreateAndSignupFakeUser("apivr", tc.G)
	if err != nil {
		t.Fatal(err)
	}

	harg := []keybase1.StringKVPair{
		{Key: "username", Value: "t_alice"},
		{Key: "fields", Value: "basics"},
	}

	arg := keybase1.GetArg{
		Endpoint: "user/lookup",
		Args:     harg,
	}

	handler := NewAPIServerHandler(nil, tc.G)
	res, err := handler.doGet(arg)
	if err != nil {
		t.Fatal(err)
	}

	jw, err := jsonw.Unmarshal([]byte(res.Body))
	if err != nil {
		t.Fatal(err)
	}

	usernamew := jw.AtKey("them").AtKey("basics").AtKey("username")
	username, err := usernamew.GetString()
	if err != nil {
		t.Fatal(err)
	}

	if username != "t_alice" {
		t.Fatalf("wrong username returned: %s != %s", username, "t_alice")
	}
}
示例#14
0
文件: gregor.go 项目: qbit/client
func (g *gregorHandler) kbfsFavorites(ctx context.Context, m gregor.OutOfBandMessage) error {
	if m.Body() == nil {
		return errors.New("gregor handler for kbfs.favorites: nil message body")
	}
	body, err := jsonw.Unmarshal(m.Body().Bytes())
	if err != nil {
		return err
	}

	action, err := body.AtPath("action").GetString()
	if err != nil {
		return err
	}

	switch action {
	case "create", "delete":
		return g.notifyFavoritesChanged(ctx, m.UID())
	default:
		return fmt.Errorf("unhandled kbfs.favorites action %q", action)
	}
}
示例#15
0
func (e *Kex2Provisionee) decodeSig(sig []byte) (*decodedSig, error) {
	body, err := base64.StdEncoding.DecodeString(string(sig))
	if err != nil {
		return nil, err
	}
	packet, err := libkb.DecodePacket(body)
	if err != nil {
		return nil, err
	}
	naclSig, ok := packet.Body.(*libkb.NaclSigInfo)
	if !ok {
		return nil, libkb.UnmarshalError{T: "Nacl signature"}
	}
	jw, err := jsonw.Unmarshal(naclSig.Payload)
	if err != nil {
		return nil, err
	}
	res := decodedSig{
		sigID:  libkb.ComputeSigIDFromSigBody(body),
		linkID: libkb.ComputeLinkID(naclSig.Payload),
	}
	res.seqno, err = jw.AtKey("seqno").GetInt()
	if err != nil {
		return nil, err
	}
	seldestKID, err := jw.AtPath("body.key.eldest_kid").GetString()
	if err != nil {
		return nil, err
	}
	res.eldestKID = keybase1.KIDFromString(seldestKID)
	ssigningKID, err := jw.AtPath("body.key.kid").GetString()
	if err != nil {
		return nil, err
	}
	res.signingKID = keybase1.KIDFromString(ssigningKID)

	return &res, nil
}
示例#16
0
func (c *ChainLink) Unpack(trusted bool, selfUID keybase1.UID) (err error) {
	tmp := ChainLinkUnpacked{}

	c.packed.AtKey("sig").GetStringVoid(&tmp.sig, &err)
	tmp.sigID, err = GetSigID(c.packed.AtKey("sig_id"), true)
	c.packed.AtKey("payload_json").GetStringVoid(&tmp.payloadJSONStr, &err)

	if err != nil {
		return err
	}

	c.payloadJSON, err = jsonw.Unmarshal([]byte(tmp.payloadJSONStr))
	if err != nil {
		return err
	}

	err = c.UnpackPayloadJSON(&tmp)
	if err != nil {
		return err
	}

	// Set the unpacked.kid member if it's not already set. If it is set, check
	// that the value is consistent with what's in the outer JSON blob.
	serverKID, err := GetKID(c.packed.AtKey("kid"))
	if err != nil {
		return err
	}
	if tmp.kid.IsNil() {
		tmp.kid = serverKID
	} else if tmp.kid != serverKID {
		return ChainLinkKIDMismatchError{
			fmt.Sprintf("Payload KID (%s) doesn't match server KID (%s).",
				tmp.kid, serverKID),
		}
	}

	// only unpack the proof_text_full if owner of this link
	if tmp.uid.Equal(selfUID) {
		ptf := c.packed.AtKey("proof_text_full")
		if !ptf.IsNil() {
			ptf.GetStringVoid(&tmp.proofText, &err)
		}
	}

	c.unpacked = &tmp

	// IF we're loaded from *trusted* storage, like our local
	// DB, then we can skip verification later
	if trusted {
		b, e2 := c.packed.AtKey("sig_verified").GetBool()
		if e2 == nil && b {
			c.sigVerified = true
			c.G().VDL.Log(VLog1, "| Link is marked as 'sig_verified'")
			if e3 := c.UnpackComputedKeyInfos(c.packed.AtKey("computed_key_infos")); e3 != nil {
				c.G().Log.Warning("Problem unpacking computed key infos: %s\n", e3)
			}
		}
	}

	c.G().VDL.Log(VLog1, "| Unpacked Link %s", c.id)

	return err
}
示例#17
0
func (k *KexProvisioner) handlePleaseSign(ctx *Context, m *kex.Msg) error {
	eddsa := m.Args().SigningKey
	sig := m.Args().Sig

	keypair := libkb.NaclSigningKeyPair{Public: eddsa}
	sigPayload, _, err := keypair.VerifyStringAndExtract(sig)
	if err != nil {
		return err
	}

	k.G().Log.Debug("Got PleaseSign() on verified JSON blob %s\n", string(sigPayload))

	// k.deviceSibkey is public only
	if k.sigKey == nil {
		var err error
		arg := libkb.SecretKeyArg{
			Me:      k.user,
			KeyType: libkb.DeviceSigningKeyType,
		}
		k.sigKey, err = k.G().Keyrings.GetSecretKeyWithPrompt(nil, arg, k.engctx.SecretUI, "new device install")
		if err != nil {
			return err
		}
	}

	jw, err := jsonw.Unmarshal(sigPayload)
	if err != nil {
		return err
	}

	var newKID keybase1.KID
	var newKey libkb.GenericKey

	if newKID, err = libkb.GetKID(jw.AtPath("body.sibkey.kid")); err != nil {
		return err
	}

	if newKey, err = libkb.ImportKeypairFromKID(newKID); err != nil {
		return err
	}

	if err = k.verifyPleaseSign(jw, newKID); err != nil {
		return err
	}

	if err = jw.SetValueAtPath("body.sibkey.reverse_sig", jsonw.NewString(sig)); err != nil {
		return err
	}

	del := libkb.Delegator{
		NewKey:         newKey,
		ExistingKey:    k.sigKey,
		Me:             k.user,
		Expire:         libkb.NaclEdDSAExpireIn,
		DelegationType: libkb.SibkeyType,
		EldestKID:      k.user.GetEldestKID(),
		Contextified:   libkb.NewContextified(k.G()),
	}

	if err = del.CheckArgs(); err != nil {
		return err
	}

	if err = del.SignAndPost(ctx.LoginContext, jw); err != nil {
		return err
	}

	return nil
}
示例#18
0
func doChainTest(t *testing.T, testCase TestCase) {
	inputJSON, exists := testvectors.ChainTestInputs[testCase.Input]
	if !exists {
		t.Fatal("missing test input: " + testCase.Input)
	}
	// Unmarshal test input in two ways: once for the structured data and once
	// for the chain link blobs.
	var input TestInput
	err := json.Unmarshal([]byte(inputJSON), &input)
	if err != nil {
		t.Fatal(err)
	}
	inputBlob, err := jsonw.Unmarshal([]byte(inputJSON))
	if err != nil {
		t.Fatal(err)
	}
	uid, err := UIDFromHex(input.UID)
	if err != nil {
		t.Fatal(err)
	}
	chainLen, err := inputBlob.AtKey("chain").Len()
	if err != nil {
		t.Fatal(err)
	}

	// Get the eldest key. This is assumed to be the first key in the list of
	// bundles, unless the "eldest" field is given in the test description, in
	// which case the eldest key is specified by name.
	var eldestKID keybase1.KID
	if testCase.Eldest == "" {
		eldestKey, err := ParseGenericKey(input.Keys[0])
		if err != nil {
			t.Fatal(err)
		}
		eldestKID = eldestKey.GetKID()
	} else {
		eldestKIDStr, found := input.LabelKids[testCase.Eldest]
		if !found {
			t.Fatalf("No KID found for label %s", testCase.Eldest)
		}
		eldestKID = keybase1.KIDFromString(eldestKIDStr)
	}

	// Parse all the key bundles.
	keyFamily, err := createKeyFamily(input.Keys)
	if err != nil {
		t.Fatal(err)
	}

	// Run the actual sigchain parsing and verification. This is most of the
	// code that's actually being tested.
	var sigchainErr error
	ckf := ComputedKeyFamily{kf: keyFamily}
	sigchain := SigChain{username: NewNormalizedUsername(input.Username), uid: uid, loadedFromLinkOne: true}
	for i := 0; i < chainLen; i++ {
		linkBlob := inputBlob.AtKey("chain").AtIndex(i)
		link, err := ImportLinkFromServer(&sigchain, linkBlob, uid)
		if err != nil {
			sigchainErr = err
			break
		}
		sigchain.chainLinks = append(sigchain.chainLinks, link)
	}
	if sigchainErr == nil {
		_, sigchainErr = sigchain.VerifySigsAndComputeKeys(eldestKID, &ckf)
	}

	// Some tests expect an error. If we get one, make sure it's the right
	// type.
	if testCase.ErrType != "" {
		if sigchainErr == nil {
			t.Fatalf("Expected %s error from VerifySigsAndComputeKeys. No error returned.", testCase.ErrType)
		}
		foundType := reflect.TypeOf(sigchainErr)
		expectedTypes := getErrorTypesMap()[testCase.ErrType]
		if expectedTypes == nil || len(expectedTypes) == 0 {
			msg := "No Go error types defined for expected failure %s.\n" +
				"This could be because of new test cases in github.com/keybase/keybase-test-vectors.\n" +
				"Go error returned: %s"
			t.Fatalf(msg, testCase.ErrType, foundType)
		}
		if expectedTypes[foundType] {
			// Success! We found the error we expected. This test is done.
			G.Log.Debug("EXPECTED error encountered", sigchainErr)
			return
		}

		// Got an error, but one of the wrong type. Tests with error names
		// that are missing from the map (maybe because we add new test
		// cases in the future) will also hit this branch.
		t.Fatalf("Wrong error type encountered. Expected %v (%s), got %s: %s",
			expectedTypes, testCase.ErrType, foundType, sigchainErr)

	}

	// Tests that expected an error terminated above. Tests that get here
	// should succeed without errors.
	if sigchainErr != nil {
		t.Fatal(err)
	}

	// Check the expected results: total unrevoked links, sibkeys, and subkeys.
	unrevokedCount := 0

	// XXX we should really contextify this
	idtable, err := NewIdentityTable(nil, eldestKID, &sigchain, nil)
	if err != nil {
		t.Fatal(err)
	}
	for _, link := range idtable.links {
		if !link.IsRevoked() {
			unrevokedCount++
		}
	}
	if unrevokedCount != testCase.Len {
		t.Fatalf("Expected %d unrevoked links, but found %d.", testCase.Len, unrevokedCount)
	}
	// Don't use the current time to get keys, because that will cause test
	// failures 5 years from now :-D
	testTime := getCurrentTimeForTest(sigchain, keyFamily)
	numSibkeys := len(ckf.GetAllActiveSibkeysAtTime(testTime))
	if numSibkeys != testCase.Sibkeys {
		t.Fatalf("Expected %d sibkeys, got %d", testCase.Sibkeys, numSibkeys)
	}
	numSubkeys := len(ckf.GetAllActiveSubkeysAtTime(testTime))
	if numSubkeys != testCase.Subkeys {
		t.Fatalf("Expected %d subkeys, got %d", testCase.Subkeys, numSubkeys)
	}

	// Success!
}