// 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() }
// 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 }
// 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) }
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) }
// 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 }