Esempio n. 1
0
// Check that the content of the UID message is consistent with it's version.
func (msg *Message) Check() error {
	// we only support version 1.0 at this stage
	if msg.UIDContent.VERSION != "1.0" {
		return log.Errorf("uid: unknown UIDContent.VERSION: %s",
			msg.UIDContent.VERSION)
	}
	// generic checks
	optional := Optional.String()
	if msg.UIDContent.PREFERENCES.FORWARDSEC != optional {
		if msg.UIDContent.MIXADDRESS != "" {
			return log.Errorf("uid: MIXADDRESS must be null, if FORWARDSEC is not %q",
				optional)
		}
		if msg.UIDContent.NYMADDRESS != "" {
			return log.Errorf("uid: NYMADDRESS must be null, if FORWARDSEC is not %q",
				optional)
		}
	}
	if err := identity.IsMapped(msg.UIDContent.IDENTITY); err != nil {
		return log.Error(err)
	}
	// check SIGKEY
	if msg.UIDContent.SIGKEY.CIPHERSUITE != DefaultCiphersuite {
		return log.Error("uid: UIDContent.SIGKEY.CIPHERSUITE != DefaultCiphersuite")
	}
	if msg.UIDContent.SIGKEY.FUNCTION != "ED25519" {
		return log.Error("uid: UIDContent.SIGKEY.FUNCTION != \"ED25519\"")
	}
	// make sure LASTENTRY is parseable for a non-keyserver localpart.
	// For keyservers the LASTENTRY can be empty, iff this is the first entry
	// in the hashchain.
	lp, _, _ := identity.Split(msg.UIDContent.IDENTITY)
	if lp != "keyserver" {
		_, _, _, _, _, _, err := hashchain.SplitEntry(msg.UIDContent.LASTENTRY)
		if err != nil {
			return err
		}
	}
	// make sure ESCROWSIGNATURE and USERSIGNATURE are not set at the same time
	if msg.ESCROWSIGNATURE != "" && msg.USERSIGNATURE != "" {
		return log.Error("uid: USERSIGNATURE and ESCROWSIGNATURE cannot be set at the same time")
	}

	// version 1.0 specific checks
	return msg.checkV1_0()
}
Esempio n. 2
0
// Create creates a new UID message for the given userID and self-signs it.
// It automatically creates all necessary keys. If sigescrow is true,  an
// escrow key is included in the created UID message.
// Necessary randomness is read from rand.
func Create(
	userID string,
	sigescrow bool,
	mixaddress, nymaddress string,
	pfsPreference PFSPreference,
	lastEntry string,
	rand io.Reader,
) (*Message, error) {
	var msg Message
	var err error
	// check user ID (identity)
	if err := identity.IsMapped(userID); err != nil {
		return nil, log.Error(err)
	}
	msg.UIDContent.VERSION = ProtocolVersion
	msg.UIDContent.MSGCOUNT = 0                            // this is the first UIDMessage
	msg.UIDContent.NOTAFTER = uint64(times.OneYearLater()) // TODO: make this settable!
	msg.UIDContent.NOTBEFORE = 0                           // TODO: make this settable
	if pfsPreference == Optional {
		msg.UIDContent.MIXADDRESS = mixaddress
		msg.UIDContent.NYMADDRESS = nymaddress
	} else {
		msg.UIDContent.MIXADDRESS = ""
		msg.UIDContent.NYMADDRESS = ""
	}
	msg.UIDContent.IDENTITY = userID
	if err = msg.UIDContent.SIGKEY.initSigKey(rand); err != nil {
		return nil, err
	}
	msg.UIDContent.PUBKEYS = make([]KeyEntry, 1)
	if err := msg.UIDContent.PUBKEYS[0].InitDHKey(rand); err != nil {
		return nil, err
	}
	if sigescrow {
		msg.UIDContent.SIGESCROW = new(KeyEntry)
		if err = msg.UIDContent.SIGESCROW.initSigKey(rand); err != nil {
			return nil, err
		}
	}
	// make sure LASTENTRY is parseable for a non-keyserver localpart.
	// For keyservers the LASTENTRY can be empty, iff this is the first entry
	// in the hashchain.
	lp, domain, _ := identity.Split(msg.UIDContent.IDENTITY)
	if lp != "keyserver" {
		if _, _, _, _, _, _, err := hashchain.SplitEntry(lastEntry); err != nil {
			return nil, err
		}
	}
	msg.UIDContent.LASTENTRY = lastEntry

	// set REPOURIS to the domain of UIDContent.IDENTITY
	// TODO: support different KeyInit repository configurations
	msg.UIDContent.REPOURIS = []string{domain}

	msg.UIDContent.PREFERENCES.FORWARDSEC = pfsPreference.String()
	msg.UIDContent.PREFERENCES.CIPHERSUITES = []string{DefaultCiphersuite}

	// TODO: CHAINLINK (later protocol version)

	// theses signatures are always empty for messages the first UIDMessage
	msg.ESCROWSIGNATURE = ""
	msg.USERSIGNATURE = ""

	selfsig := msg.UIDContent.SIGKEY.ed25519Key.Sign(msg.UIDContent.JSON())
	msg.SELFSIGNATURE = base64.Encode(selfsig)

	// TODO: LINKAUTHORITY

	return &msg, nil
}
Esempio n. 3
0
// searchHashChain searches the local hash chain corresponding to the given id
// for the id. It talks to the corresponding key server to retrieve necessary
// UIDMessageReplys and stores found UIDMessages in the local keyDB.
func (ce *CryptEngine) searchHashChain(id string, searchOnly bool) error {
	// map identity
	mappedID, domain, err := identity.MapPlus(id)
	if err != nil {
		return err
	}
	// make sure we have a hashchain for the given domain
	max, found, err := ce.keyDB.GetLastHashChainPos(domain)
	if err != nil {
		return err
	}
	if !found {
		return log.Errorf("no hash chain entries found for domain '%s'", domain)
	}

	var TYPE, NONCE, HashID, CrUID, UIDIndex []byte
	var matchFound bool
	for i := uint64(0); i <= max; i++ {
		hcEntry, err := ce.keyDB.GetHashChainEntry(domain, i)
		if err != nil {
			return err
		}
		log.Debugf("cryptengine: search hash chain entry %d: %s", i, hcEntry)

		_, TYPE, NONCE, HashID, CrUID, UIDIndex, err = hashchain.SplitEntry(hcEntry)
		if err != nil {
			return err
		}
		if !bytes.Equal(TYPE, hashchain.Type) {
			return log.Error("cryptengine: invalid hash chain entry type")
		}

		// Compute k1, k2 = CKDF(NONCE)
		k1, k2 := cipher.CKDF(NONCE)

		// Compute: HashIDTest = HASH(k1 | Identity)
		tmp := make([]byte, len(k1)+len(mappedID))
		copy(tmp, k1)
		copy(tmp[len(k1):], mappedID)
		HashIDTest := cipher.SHA256(tmp)

		// If NOT: HashID == HashIDTest: Continue
		if !bytes.Equal(HashID, HashIDTest) {
			continue
		}
		if searchOnly {
			return nil
		}
		log.Debugf("cryptengine: UIDIndex=%s", base64.Encode(UIDIndex))

		// Check UID already exists in keyDB
		_, pos, found, err := ce.keyDB.GetPublicUID(mappedID, i)
		if err != nil {
			return err
		}
		if found && pos == i {
			// UID exists already -> skip entry
			matchFound = true
			continue
		}

		// Compute: IDKEY = HASH(k2 | Identity)
		tmp = make([]byte, len(k2)+len(mappedID))
		copy(tmp, k2)
		copy(tmp[len(k2):], mappedID)
		IDKEY := cipher.SHA256(tmp)

		// Fetch from Key Repository: UIDMessageReply = GET(UIDIndex)
		msgReply, err := ce.fetchUID(domain, UIDIndex)
		if err != nil {
			return err
		}

		// Decrypt UIDHash = AES_256_CBC_Decrypt( IDKEY, CrUID)
		UIDHash := cipher.AES256CBCDecrypt(IDKEY, CrUID)
		log.Debugf("cryptengine: UIDHash=%s", base64.Encode(UIDHash))

		// Decrypt UIDMessageReply.UIDMessage with UIDHash
		index, uid, err := msgReply.Decrypt(UIDHash)
		if err != nil {
			return err
		}
		log.Debugf("cryptengine: UIDMessage=%s", uid.JSON())

		// Check index
		if !bytes.Equal(index, UIDIndex) {
			return log.Errorf("cryptengine: index != UIDIndex")
		}

		// Verify self signature
		if err := uid.VerifySelfSig(); err != nil {
			return log.Error(err)
		}

		// Verify server signature
		if err := ce.verifyServerSig(uid, msgReply, i); err != nil {
			return err
		}

		// TODO: make sure the whole chain of UIDMessages is valid

		// Store UIDMessage
		if err := ce.keyDB.AddPublicUID(uid, i); err != nil {
			return err
		}
		matchFound = true

		// If no further entry can be found, the latest UIDMessage entry has been found
	}

	if matchFound {
		return nil
	}

	return log.Errorf("no hash chain entry found of id '%s'", id)
}
Esempio n. 4
0
func (ce *CryptEngine) lookupHashChain(id string) error {
	// map identity
	mappedID, domain, err := identity.MapPlus(id)
	if err != nil {
		return err
	}
	// get JSON-RPC client
	client, _, err := ce.cache.Get(domain, ce.keydPort, ce.keydHost, ce.homedir,
		"KeyHashchain.LookupUID")
	if err != nil {
		return err
	}
	// Call KeyHashchain.LookupUID
	content := make(map[string]interface{})
	content["Identity"] = mappedID
	reply, err := client.JSONRPCRequest("KeyHashchain.LookupUID", content)
	if err != nil {
		return err
	}
	hcPositions, ok := reply["HCPositions"].([]interface{})
	if !ok {
		if _, ok := reply["HCPositions"].(interface{}); !ok {
			return log.Errorf("lookup found no entry of id '%s'", id)
		}
		return log.Error("cryptengine: lookup ID reply has the wrong type")
	}
	var TYPE, NONCE, HashID, CrUID, UIDIndex []byte
	var matchFound bool
	for k, v := range hcPositions {
		hcPosFloat, ok := v.(float64)
		if !ok {
			return log.Errorf("cryptengine: lookup ID reply position entry %d has the wrong type", k)
		}
		hcPos := uint64(hcPosFloat)
		hcEntry, err := ce.keyDB.GetHashChainEntry(domain, hcPos)
		if err != nil {
			return err
		}
		_, TYPE, NONCE, HashID, CrUID, UIDIndex, err = hashchain.SplitEntry(hcEntry)
		if err != nil {
			return err
		}
		if !bytes.Equal(TYPE, hashchain.Type) {
			return log.Error("cryptengine: invalid entry type")
		}

		// Compute k1, k2 = CKDF(NONCE)
		k1, k2 := cipher.CKDF(NONCE)

		// Compute: HashIDTest = HASH(k1 | Identity)
		tmp := make([]byte, len(k1)+len(mappedID))
		copy(tmp, k1)
		copy(tmp[len(k1):], mappedID)
		HashIDTest := cipher.SHA256(tmp)

		// If NOT: HashID == HashIDTest: Continue
		if !bytes.Equal(HashID, HashIDTest) {
			return log.Error("cryptengine: lookup ID returned bogus position")
		}
		log.Debugf("cryptengine: UIDIndex=%s", base64.Encode(UIDIndex))

		// Check UID already exists in keyDB
		_, pos, found, err := ce.keyDB.GetPublicUID(mappedID, hcPos)
		if err != nil {
			return err
		}
		if found && pos == hcPos {
			// UID exists already -> skip entry
			matchFound = true
			continue
		}

		// Compute: IDKEY = HASH(k2 | Identity)
		tmp = make([]byte, len(k2)+len(mappedID))
		copy(tmp, k2)
		copy(tmp[len(k2):], mappedID)
		IDKEY := cipher.SHA256(tmp)

		// Fetch from Key Repository: UIDMessageReply = GET(UIDIndex)
		msgReply, err := ce.fetchUID(domain, UIDIndex)
		if err != nil {
			return err
		}

		// Decrypt UIDHash = AES_256_CBC_Decrypt( IDKEY, CrUID)
		UIDHash := cipher.AES256CBCDecrypt(IDKEY, CrUID)
		log.Debugf("cryptengine: UIDHash=%s", base64.Encode(UIDHash))

		// Decrypt UIDMessageReply.UIDMessage with UIDHash
		index, uid, err := msgReply.Decrypt(UIDHash)
		if err != nil {
			return err
		}
		log.Debugf("cryptengine: UIDMessage=%s", uid.JSON())

		// Check index
		if !bytes.Equal(index, UIDIndex) {
			return log.Errorf("cryptengine: index != UIDIndex")
		}

		// Verify self signature
		if err := uid.VerifySelfSig(); err != nil {
			return log.Error(err)
		}

		// Verify server signature
		if err := ce.verifyServerSig(uid, msgReply, hcPos); err != nil {
			return err
		}

		// TODO: make sure the whole chain of UIDMessages is valid

		// Store UIDMessage
		if err := ce.keyDB.AddPublicUID(uid, hcPos); err != nil {
			return err
		}
		matchFound = true

		// If no further entry can be found, the latest UIDMessage entry has been found
	}

	if matchFound {
		return nil
	}

	return log.Errorf("lookup found no entry of id '%s'", id)
}
Esempio n. 5
0
// validateHashChain validates the local hash chain for the given domain.
// That is, it checks that each entry has the correct length and the links are
// valid.
func (ce *CryptEngine) validateHashChain(domain string) error {
	// make sure we have a hashchain for the given domain
	max, found, err := ce.keyDB.GetLastHashChainPos(domain)
	if err != nil {
		return err
	}
	if !found {
		return log.Errorf("no hash chain entries found for domain '%s'", domain)
	}

	var hashEntryN, TYPE, NONCE, HashID, CrUID, UIDIndex, hashEntryNminus1 []byte
	for i := uint64(0); i <= max; i++ {
		entry, err := ce.keyDB.GetHashChainEntry(domain, i)
		if err != nil {
			return err
		}
		log.Debugf("cryptengine: validate entry %d: %s", i, entry)

		if i == 0 {
			hashEntryNminus1 = make([]byte, sha256.Size)
		} else {
			hashEntryNminus1 = hashEntryN
		}
		hashEntryN, TYPE, NONCE, HashID, CrUID, UIDIndex, err = hashchain.SplitEntry(entry)
		if err != nil {
			return err
		}
		if !bytes.Equal(TYPE, hashchain.Type) {
			return log.Error("cryptengine: invalid hash chain entry type")
		}

		entryN := make([]byte, 153)
		copy(entryN, TYPE)
		copy(entryN[1:], NONCE)
		copy(entryN[9:], HashID)
		copy(entryN[41:], CrUID)
		copy(entryN[89:], UIDIndex)
		copy(entryN[121:], hashEntryNminus1)
		if !bytes.Equal(hashEntryN, cipher.SHA256(entryN)) {
			return log.Errorf("cryptengine: hash chain entry %d invalid", i)
		}
	}

	// get all private identities for the given domain
	ids, err := ce.keyDB.GetPrivateIdentitiesForDomain(domain)
	if err != nil {
		return err
	}

	// make sure UIDMessageReplies are recorded in hash chain
	for _, id := range ids {
		_, msgReply, err := ce.keyDB.GetPrivateUID(id, false)
		if err != nil {
			return err
		}
		if msgReply != nil && msgReply.ENTRY.HASHCHAINPOS <= max {
			entry, err := ce.keyDB.GetHashChainEntry(domain, msgReply.ENTRY.HASHCHAINPOS)
			if err != nil {
				return err
			}
			if entry != msgReply.ENTRY.HASHCHAINENTRY {
				return log.Errorf("cryptengine: hash chain entry differs from UIDMessageReply (%s)", id)
			}
		}
	}

	return nil
}