// procUID allows to process a sender UID from the header in parallel. // senderUID is the JSON encoded UID message parsed from the header, res is // the result channel used to communicate the result of the calculation. func procUID(senderUID string, res chan *procUIDResult) { var r procUIDResult var err error r.uidIndex = cipher.SHA256(cipher.SHA256([]byte(senderUID))) r.msg, err = uid.NewJSON(senderUID) if err != nil { res <- &procUIDResult{uidIndex: nil, msg: nil, err: err} return } // return results res <- &r }
// Encrypt encryptes the given UID message. func (msg *Message) Encrypt() (UIDHash, UIDIndex []byte, UIDMessageEncrypted string) { Message := msg.JSON() // Calculate hash: UIDHash = sha256(UIDMessage) UIDHash = cipher.SHA256(Message) // Calculate hash: UIDIndex = sha256(UIDHash) UIDIndex = cipher.SHA256(UIDHash) // Encrypt UIDMessage: UIDMessageEncrypted = UIDIndex | nonce | aes_ctr(nonce, key=UIDHash, UIDMessage) enc := cipher.AES256CTREncrypt(UIDHash, Message, cipher.RandReader) uidEnc := make([]byte, sha256.Size+len(enc)) copy(uidEnc, UIDIndex) copy(uidEnc[sha256.Size:], enc) UIDMessageEncrypted = base64.Encode(uidEnc) return }
// NewNymAddress generates a new nym address. func NewNymAddress( domain string, secret []byte, expire int64, singleUse bool, minDelay, maxDelay int32, id string, pubkey *[ed25519.PublicKeySize]byte, server string, caCert []byte, ) (mixaddress, nymaddress string, err error) { if err := identity.IsMapped(id); err != nil { return "", "", log.Error(err) } if MixAddress == "" { return "", "", log.Error("util: MixAddress undefined") } mixAddresses, err := client.GetMixKeys(MixAddress, caCert) if err != nil { return "", "", log.Error(err) } tmp := nymaddr.AddressTemplate{ Secret: secret, System: 0, MixCandidates: mixAddresses.Addresses, Expire: expire, SingleUse: singleUse, MinDelay: minDelay, MaxDelay: maxDelay, } nymAddress, err := tmp.NewAddress(MailboxAddress(pubkey, server), cipher.SHA256([]byte(id))) if err != nil { return "", "", log.Error(err) } addr, err := nymaddr.ParseAddress(nymAddress) if err != nil { return "", "", log.Error(err) } return string(addr.MixAddress), base64.Encode(nymAddress), nil }
func (ce *CtrlEngine) procInQueue(c *cli.Context, host string) error { log.Debug("procInQueue()") for { // get message from msgDB iqIdx, myID, contactID, msg, envelope, err := ce.msgDB.GetInQueue() if err != nil { return err } if myID == "" { log.Debug("no more messages in inqueue") break // no more messages in inqueue } if envelope { log.Debugf("decrypt envelope (iqIdx=%d)", iqIdx) // decrypt envelope message, err := base64.Decode(msg) if err != nil { return log.Error(err) } privkey, server, secret, _, _, _, err := ce.msgDB.GetAccount(myID, contactID) if err != nil { return err } receiveTemplate := nymaddr.AddressTemplate{ Secret: secret[:], } var pubkey [32]byte copy(pubkey[:], privkey[32:]) dec, nym, err := mixcrypt.ReceiveFromMix(receiveTemplate, util.MailboxAddress(&pubkey, server), message) if err != nil { return log.Error(err) } if !bytes.Equal(nym, cipher.SHA256([]byte(myID))) { // discard message log.Warnf("ctrlengine: hashed nym does not match %s -> discard message", myID) if err := ce.msgDB.DelInQueue(iqIdx); err != nil { return err } } else { log.Info("envelope successfully decrypted") err := ce.msgDB.SetInQueue(iqIdx, base64.Encode(dec)) if err != nil { return err } } } else { log.Debugf("decrypt message (iqIdx=%d)", iqIdx) senderID, plainMsg, err := mutecryptDecrypt(c, ce.passphrase, []byte(msg), ce.fileTable.StatusFP) if err != nil { return err } if senderID == "" { // message could not be decrypted, but we do not want to fail if err := ce.msgDB.DelInQueue(iqIdx); err != nil { return err } continue } // check if contact exists contact, _, contactType, err := ce.msgDB.GetContact(myID, senderID) if err != nil { return log.Error(err) } // TODO: we do not have to do request UID message from server // here, but we should use the one contained in the message and // compare it with hash chain entry (doesn't compromise anonymity) var drop bool if contact == "" { err := ce.contactAdd(myID, senderID, "", host, msgdb.GrayList, c) if err != nil { return log.Error(err) } } else if contactType == msgdb.BlackList { // messages from black listed contacts are dropped directly log.Debug("message from black listed contact dropped") drop = true } err = ce.msgDB.RemoveInQueue(iqIdx, plainMsg, senderID, drop) if err != nil { return err } } } return nil }
func TestSessions(t *testing.T) { tmpdir, keyDB, err := createDB() if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) defer keyDB.Close() // make sure sessions are empty initially sessionKey := base64.Encode(cipher.SHA512([]byte("key"))) rootKeyHash, _, _, err := keyDB.GetSession(sessionKey) if err != sql.ErrNoRows { t.Error("should fail with sql.ErrNoRows") } if rootKeyHash != "" { t.Error("rootKeyHash is supposed to be empty") } // store root key hash rk := base64.Encode(cipher.SHA256([]byte("rootkey"))) master := make([]byte, 96) if _, err := io.ReadFull(cipher.RandReader, master); err != nil { t.Fatal(err) } kdf := hkdf.New(sha512.New, master, nil, nil) chainKey := make([]byte, 32) if _, err := io.ReadFull(kdf, chainKey); err != nil { t.Fatal(err) } send, recv, err := deriveKeys(chainKey, kdf) if err != nil { t.Fatal(err) } err = keyDB.AddSession(sessionKey, rk, base64.Encode(chainKey), send, recv) if err != nil { t.Fatal(err) } // check root key hash rootKeyHash, _, n, err := keyDB.GetSession(sessionKey) if err != nil { t.Fatal(err) } if rootKeyHash != rk { t.Error("rootKeyHash is supposed to equal rk") } if n != msg.NumOfFutureKeys { t.Error("n is supposed to equal msg.NumOfFutureKeys") } // update root key hash chainKey = make([]byte, 32) if _, err := io.ReadFull(kdf, chainKey); err != nil { t.Fatal(err) } send, recv, err = deriveKeys(chainKey, kdf) if err != nil { t.Fatal(err) } err = keyDB.AddSession(sessionKey, rk, base64.Encode(chainKey), send, recv) if err != nil { t.Fatal(err) } // check updated root key hash rootKeyHash, _, n, err = keyDB.GetSession(sessionKey) if err != nil { t.Fatal(err) } if rootKeyHash != rk { t.Error("rootKeyHash is supposed to equal rk") } if n != 2*msg.NumOfFutureKeys { t.Error("n is supposed to equal 2*msg.NumOfFutureKeys") } // TODO: improve tests for message keys _, err = keyDB.GetMessageKey(sessionKey, true, 0) if err != nil { t.Fatal(err) } if err := keyDB.DelMessageKey(sessionKey, true, 0); err != nil { t.Fatal(err) } }
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) }
// 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) }
// 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 }