예제 #1
0
// SessionAnchor returns the decrypted and verified session anchor for KeyInit.
func (ki *KeyInit) SessionAnchor(sigPubKey string) (*SessionAnchor, error) {
	// SIGKEYHASH corresponds to the SIGKEY of the Identity
	pubKey, err := base64.Decode(sigPubKey)
	if err != nil {
		return nil, err
	}
	keyHash := cipher.SHA512(pubKey)
	if ki.Contents.SIGKEYHASH != base64.Encode(cipher.SHA512(keyHash)) {
		log.Error(ErrWrongSigKeyHash)
		return nil, ErrWrongSigKeyHash
	}
	// verify that SESSIONANCHORHASH matches decrypted SESSIONANCHOR
	enc, err := base64.Decode(ki.Contents.SESSIONANCHOR)
	if err != nil {
		return nil, err
	}
	txt := cipher.AES256CTRDecrypt(keyHash[:32], enc)
	var sa SessionAnchor
	if err := json.Unmarshal(txt, &sa); err != nil {
		return nil, log.Error(err)
	}
	if ki.Contents.SESSIONANCHORHASH != base64.Encode(cipher.SHA512(sa.json())) {
		log.Error(ErrSessionAnchor)
		return nil, ErrSessionAnchor
	}
	return &sa, nil
}
예제 #2
0
func TestSigKeyHash(t *testing.T) {
	msg, err := Create("*****@*****.**", false, "", "", Strict,
		hashchain.TestEntry, cipher.RandReader)
	if err != nil {
		t.Fatal(err)
	}
	if msg.Identity() != "*****@*****.**" {
		t.Error("wrong identity")
	}
	sigKeyHash, err := msg.SigKeyHash()
	if err != nil {
		t.Fatal(err)
	}
	sigPubKey, err := base64.Decode(msg.SigPubKey())
	if err != nil {
		t.Fatal(err)
	}
	if sigKeyHash != base64.Encode(cipher.SHA512(cipher.SHA512(sigPubKey))) {
		t.Fatal("SIGKEYHASHs differ")
	}

	privKey := msg.PrivateEncKey()
	if err := msg.SetPrivateEncKey(privKey); err != nil {
		t.Fatal(err)
	}
	if privKey != msg.PrivateEncKey() {
		t.Error("private keys differ")
	}
}
예제 #3
0
파일: uid.go 프로젝트: JonathanLogan/mute
// SigKeyHash returns the SIGKEYHASH which corresponds to the sigPubKey.
func SigKeyHash(sigPubKey string) (string, error) {
	keyHash, err := base64.Decode(sigPubKey)
	if err != nil {
		return "", err
	}
	return base64.Encode(cipher.SHA512(keyHash)), nil
}
예제 #4
0
// CalcKey computes the session key from senderIdentityHash,
// recipientIdentityHash, senderSessionHash, and recipientSessionHash.
func CalcKey(
	senderIdentityHash string,
	recipientIdentityHash string,
	senderSessionHash string,
	recipientSessionHash string,
) string {
	key := senderIdentityHash + recipientIdentityHash
	key += senderSessionHash + recipientSessionHash
	return base64.Encode(cipher.SHA512([]byte(key)))
}
예제 #5
0
// KeyInit returns a new KeyInit message for the given UID message. It also
// returns the pubKeyHash and privateKey for convenient further use.
// msgcount must increase for each message of the same type and user.
// notafter is the unixtime after which the key(s) should not be used anymore.
// notbefore is the unixtime before which the key(s) should not be used yet.
// fallback determines if the key may serve as a fallback key.
// repoURI is URI of the corresponding KeyInit repository.
// Necessary randomness is read from rand.
func (msg *Message) KeyInit(
	msgcount, notafter, notbefore uint64,
	fallback bool,
	repoURI, mixaddress, nymaddress string,
	rand io.Reader,
) (ki *KeyInit, pubKeyHash, privateKey string, err error) {
	var keyInit KeyInit
	// time checks
	if notbefore >= notafter {
		log.Error(ErrInvalidTimes)
		return nil, "", "", ErrInvalidTimes
	}
	if notafter < uint64(times.Now()) {
		log.Error(ErrExpired)
		return nil, "", "", ErrExpired
	}
	if notafter > uint64(times.Now())+MaxNotAfter {
		log.Error(ErrFuture)
		return nil, "", "", ErrFuture
	}
	// init
	keyInit.Contents.VERSION = ProtocolVersion
	keyInit.Contents.MSGCOUNT = msgcount
	keyInit.Contents.NOTAFTER = notafter
	keyInit.Contents.NOTBEFORE = notbefore
	keyInit.Contents.FALLBACK = fallback
	keyHash, err := base64.Decode(msg.UIDContent.SIGKEY.HASH)
	if err != nil {
		return nil, "", "", err
	}
	keyInit.Contents.SIGKEYHASH = base64.Encode(cipher.SHA512(keyHash))

	// make sure REPOURIS is set to the first REPOURI of UIDContent.REPOURIS
	// TODO: support different KeyInit repository configurations
	if repoURI != msg.UIDContent.REPOURIS[0] {
		return nil, "", "",
			log.Error("uri: repoURI differs from msg.UIDContent.REPOURIS[0]")
	}
	keyInit.Contents.REPOURI = repoURI

	// create SessionAnchor
	sa, sah, pubKeyHash, privateKey, err := msg.sessionAnchor(keyHash,
		mixaddress, nymaddress, rand)
	if err != nil {
		return nil, "", "", err
	}
	keyInit.Contents.SESSIONANCHOR = sa
	keyInit.Contents.SESSIONANCHORHASH = sah
	// sign KeyInit: the content doesn't have to be hashed, because Ed25519 is
	// already taking care of that.
	sig := msg.UIDContent.SIGKEY.ed25519Key.Sign(keyInit.Contents.json())
	keyInit.SIGNATURE = base64.Encode(sig)
	ki = &keyInit
	return
}
예제 #6
0
func decrypt(sender, recipient *uid.Message, r io.Reader, recipientTemp *uid.KeyEntry,
	privateKey string, sign bool, chkMsg bool) error {
	// decrypt
	var res bytes.Buffer
	identities := []*uid.Message{recipient}
	input := base64.NewDecoder(r)
	version, preHeader, err := ReadFirstOuterHeader(input)
	if err != nil {
		return err
	}
	if version != Version {
		return errors.New("wrong version")
	}
	ms := memstore.New()
	if err := recipientTemp.SetPrivateKey(privateKey); err != nil {
		return err
	}
	ms.AddPrivateKeyEntry(recipientTemp)
	args := &DecryptArgs{
		Writer:     &res,
		Identities: identities,
		PreHeader:  preHeader,
		Reader:     input,
		Rand:       cipher.RandReader,
		KeyStore:   ms,
	}
	_, sig, err := Decrypt(args)
	if err != nil {
		return err
	}
	// do not compare messages when fuzzing, because messages have to be different!
	if chkMsg && res.String() != msgs.Message1 {
		return errors.New("messages differ")
	}
	if sign {
		contentHash := cipher.SHA512(res.Bytes())
		decSig, err := base64.Decode(sig)
		if err != nil {
			return err
		}
		if len(decSig) != ed25519.SignatureSize {
			return errors.New("signature has wrong length")
		}
		var sigBuf [ed25519.SignatureSize]byte
		copy(sigBuf[:], decSig)
		if !ed25519.Verify(sender.PublicSigKey32(), contentHash, &sigBuf) {
			return errors.New("signature verification failed")
		}
	}
	return nil
}
예제 #7
0
func TestSignatureSize(t *testing.T) {
	t.Parallel()
	_, privKey, err := ed25519.GenerateKey(cipher.RandReader)
	if err != nil {
		t.Fatal(err)
	}
	sig := ed25519.Sign(privKey, cipher.SHA512([]byte("test")))
	ih := newInnerHeader(signatureType, false, sig[:])
	var buf bytes.Buffer
	if err := ih.write(&buf); err != nil {
		t.Fatal(err)
	}
	oh := newOuterHeader(encryptedPacket, 4, buf.Bytes())
	if oh.size() != signatureSize {
		t.Errorf("oh.size() = %d != %d", oh.size(), signatureSize)
	}
}
예제 #8
0
func (msg *Message) sessionAnchor(
	key []byte,
	mixaddress, nymaddress string,
	rand io.Reader,
) (sessionAnchor, sessionAnchorHash, pubKeyHash, privateKey string, err error) {
	var sa SessionAnchor
	sa.MIXADDRESS = mixaddress
	sa.NYMADDRESS = nymaddress
	sa.PFKEYS = make([]KeyEntry, 1)
	if err := sa.PFKEYS[0].InitDHKey(rand); err != nil {
		return "", "", "", "", err
	}
	jsn := sa.json()
	hash := cipher.SHA512(jsn)
	// SESSIONANCHOR = AES256_CTR(key=UIDMessage.UIDContent.SIGKEY.HASH, SessionAnchor)
	enc := base64.Encode(cipher.AES256CTREncrypt(key[:32], jsn, rand))
	return enc, base64.Encode(hash), sa.PFKEYS[0].HASH, base64.Encode(sa.PFKEYS[0].PrivateKey32()[:]), nil
}
예제 #9
0
func TestSessionState(t *testing.T) {
	ms := New()
	ss := &session.State{
		SenderSessionCount: 1,
		SenderMessageCount: 2,
	}
	sessionStateKey := base64.Encode(cipher.SHA512([]byte("sessionstatekey")))
	err := ms.SetSessionState(sessionStateKey, ss)
	if err != nil {
		t.Fatal(err)
	}
	sss, err := ms.GetSessionState(sessionStateKey)
	if err != nil {
		t.Fatal(err)
	}
	if ss != sss {
		t.Error("session states differ")
	}
}
예제 #10
0
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)
	}
}
예제 #11
0
func TestSessionStates(t *testing.T) {
	tmpdir, keyDB, err := createDB()
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpdir)
	defer keyDB.Close()
	var (
		rt    uid.KeyEntry
		ssp   uid.KeyEntry
		nssp  uid.KeyEntry
		nrsps uid.KeyEntry
	)
	if err := rt.InitDHKey(cipher.RandReader); err != nil {
		t.Fatal(err)
	}
	if err := ssp.InitDHKey(cipher.RandReader); err != nil {
		t.Fatal(err)
	}
	if err := nssp.InitDHKey(cipher.RandReader); err != nil {
		t.Fatal(err)
	}
	if err := nrsps.InitDHKey(cipher.RandReader); err != nil {
		t.Fatal(err)
	}
	sessionStateKey1 := base64.Encode(cipher.SHA512([]byte("key1")))
	sessionStateKey2 := base64.Encode(cipher.SHA512([]byte("key2")))
	ss1 := &session.State{
		SenderSessionCount:          1,
		SenderMessageCount:          2,
		MaxRecipientCount:           3,
		RecipientTemp:               rt,
		SenderSessionPub:            ssp,
		NextSenderSessionPub:        &nssp,
		NextRecipientSessionPubSeen: &nrsps,
		NymAddress:                  "NYMADDRESS",
		KeyInitSession:              true,
	}
	ss2 := &session.State{
		RecipientTemp:    rt,
		SenderSessionPub: ssp,
		NymAddress:       "NYMADDRESS",
	}
	if err := keyDB.SetSessionState(sessionStateKey1, ss1); err != nil {
		t.Fatal(err)
	}
	if err := keyDB.SetSessionState(sessionStateKey2, ss2); err != nil {
		t.Fatal(err)
	}
	ss1db, err := keyDB.GetSessionState(sessionStateKey1)
	if err != nil {
		t.Fatal(err)
	}
	ss2db, err := keyDB.GetSessionState(sessionStateKey2)
	if err != nil {
		t.Fatal(err)
	}
	if !session.StateEqual(ss1, ss1db) {
		t.Error("ss1 and ss1db differ")
	}
	if !session.StateEqual(ss2, ss2db) {
		t.Error("ss2 and ss2db differ")
	}
	if err := keyDB.SetSessionState(sessionStateKey1, ss2); err != nil {
		t.Fatal(err)
	}
	ss1db, err = keyDB.GetSessionState(sessionStateKey1)
	if err != nil {
		t.Fatal(err)
	}
	if !session.StateEqual(ss2, ss1db) {
		t.Error("ss2 and ss1db differ")
	}
}
예제 #12
0
// Decrypt decrypts a message with the argument given in args.
// The senderID is returned.
// If the message was signed and the signature could be verified successfully
// the base64 encoded signature is returned. If the message was signed and the
// signature could not be verfied an error is returned.
func Decrypt(args *DecryptArgs) (senderID, sig string, err error) {
	log.Debug("msg.Decrypt()")

	// set default
	if args.NumOfKeys == 0 {
		args.NumOfKeys = NumOfFutureKeys
	}

	// read pre-header
	ph, err := readPreHeader(bytes.NewBuffer(args.PreHeader))
	if err != nil {
		return "", "", err
	}
	if ph.LengthSenderHeaderPub != 32 {
		return "", "", log.Errorf("msg: ph.LengthSenderHeaderPub != 32")
	}
	var senderHeaderPub [32]byte
	copy(senderHeaderPub[:], ph.SenderHeaderPub)

	// read header packet
	oh, err := readOuterHeader(args.Reader)
	if err != nil {
		return "", "", err
	}
	if oh.Type != encryptedHeader {
		return "", "", log.Error(ErrNotEncryptedHeader)
	}
	count := uint32(1)
	if oh.PacketCount != count {
		return "", "", log.Error(ErrWrongCount)
	}
	count++
	identity, h, err := readHeader(&senderHeaderPub, args.Identities,
		bytes.NewBuffer(oh.inner))
	if err != nil {
		return "", "", err
	}
	senderID = h.SenderIdentity
	recipientID := identity.PubKey()

	log.Debugf("senderID:    %s", h.SenderIdentityPub.HASH)
	log.Debugf("recipientID: %s", recipientID.HASH)
	log.Debugf("h.SenderSessionCount: %d", h.SenderSessionCount)
	log.Debugf("h.SenderMessageCount: %d", h.SenderMessageCount)
	log.Debugf("h.SenderSessionPub:             %s", h.SenderSessionPub.HASH)
	if h.NextSenderSessionPub != nil {
		log.Debugf("h.NextSenderSessionPub:         %s", h.NextSenderSessionPub.HASH)
	}
	if h.NextRecipientSessionPubSeen != nil {
		log.Debugf("h.NextRecipientSessionPubSeen:  %s",
			h.NextRecipientSessionPubSeen.HASH)
	}

	// proc sender UID in parallel
	res := make(chan *procUIDResult, 1)
	go procUID(h.SenderUID, res)

	// get session state
	sender := h.SenderIdentity
	recipient := identity.Identity()
	log.Debugf("%s -> %s", sender, recipient)
	sessionStateKey := session.CalcStateKey(recipientID.PublicKey32(),
		h.SenderIdentityPub.PublicKey32())
	ss, err := args.KeyStore.GetSessionState(sessionStateKey)
	if err != nil {
		return "", "", err
	}
	sessionKey := session.CalcKey(recipientID.HASH, h.SenderIdentityPub.HASH,
		h.RecipientTempHash, h.SenderSessionPub.HASH)

	if !args.KeyStore.HasSession(sessionKey) { // session unknown
		// try to start session from KeyInit message
		recipientKI, err := args.KeyStore.GetPrivateKeyEntry(h.RecipientTempHash)
		if err != nil && err != session.ErrNoKeyEntry {
			return "", "", err
		}
		if err != session.ErrNoKeyEntry { // KeyInit message found
			// root key agreement
			err = rootKeyAgreementRecipient(&senderHeaderPub, sender, recipient,
				&h.SenderSessionPub, &h.SenderIdentityPub, recipientKI, recipientID,
				nil, args.NumOfKeys, args.KeyStore)
			if err != nil {
				return "", "", err
			}

			// TODO: delete single-use KeyInit message

			// use the 'smaller' session as the definite one
			// TODO: h.SenderSessionPub.HASH < ss.SenderSessionPub.HASH
			if ss == nil || (ss.KeyInitSession && sender < recipient) {
				// create next session key
				var nextSenderSession uid.KeyEntry
				if err := nextSenderSession.InitDHKey(args.Rand); err != nil {
					return "", "", err
				}
				// store next session key
				err := addSessionKey(args.KeyStore, &nextSenderSession)
				if err != nil {
					return "", "", err
				}
				// if we already got h.NextSenderSessionPub prepare next session
				if h.NextSenderSessionPub != nil {
					previousRootKeyHash, err := args.KeyStore.GetRootKeyHash(sessionKey)
					if err != nil {
						return "", "", err
					}
					// root key agreement
					err = rootKeyAgreementSender(&senderHeaderPub, recipient,
						sender, &nextSenderSession, recipientID,
						h.NextSenderSessionPub, &h.SenderIdentityPub,
						previousRootKeyHash, args.NumOfKeys, args.KeyStore)
					if err != nil {
						return "", "", err
					}
				}
				// set session state
				ss = &session.State{
					SenderSessionCount:          0,
					SenderMessageCount:          0,
					MaxRecipientCount:           0,
					RecipientTemp:               h.SenderSessionPub,
					SenderSessionPub:            *recipientKI,
					NextSenderSessionPub:        &nextSenderSession,
					NextRecipientSessionPubSeen: h.NextSenderSessionPub,
					NymAddress:                  h.NymAddress,
					KeyInitSession:              false,
				}
				err = args.KeyStore.SetSessionState(sessionStateKey, ss)
				if err != nil {
					return "", "", err
				}
			}
		} else { // no KeyInit message found
			// TODO: ???
		}
	} else { // session known
		log.Debug("session known")
		// check if session state reflects that session
		if h.RecipientTempHash == ss.SenderSessionPub.HASH &&
			h.SenderSessionPub.HASH == ss.RecipientTemp.HASH {
			log.Debug("session state reflects that session")
			if h.NextSenderSessionPub != nil {
				log.Debug("h.NextSenderSessionPub is defined")
			}
			if h.NextRecipientSessionPubSeen != nil {
				log.Debug("h.NextRecipientSessionPubSeen is defined")
			}
			if h.NextSenderSessionPub != nil {
				// if other side has set its NextSenderSessionPubKey we set
				// ours immediately
				if ss.NextSenderSessionPub == nil {
					// prepare upcoming session, but do not switch to it yet
					nextSenderSession, err := setNextSenderSessionPub(args.KeyStore, ss,
						sessionStateKey, args.Rand)
					if err != nil {
						return "", "", err
					}
					previousRootKeyHash, err := args.KeyStore.GetRootKeyHash(sessionKey)
					if err != nil {
						return "", "", err
					}
					// root key agreement
					err = rootKeyAgreementSender(&senderHeaderPub, recipient,
						sender, nextSenderSession, recipientID,
						h.NextSenderSessionPub, &h.SenderIdentityPub,
						previousRootKeyHash, args.NumOfKeys, args.KeyStore)
					if err != nil {
						return "", "", err
					}
					if ss.NextRecipientSessionPubSeen == nil {
						// save h.NextSenderSessionPub, if necessary
						ss.NextRecipientSessionPubSeen = h.NextSenderSessionPub
						err := args.KeyStore.SetSessionState(sessionStateKey, ss)
						if err != nil {
							return "", "", err
						}
					}
				} else if h.NextRecipientSessionPubSeen != nil &&
					h.NextRecipientSessionPubSeen.HASH == ss.NextSenderSessionPub.HASH {
					// switch to next session
					nextSenderSession, err := getSessionKey(args.KeyStore,
						ss.NextSenderSessionPub.HASH)
					if err != nil {
						return "", "", err
					}
					previousRootKeyHash, err := args.KeyStore.GetRootKeyHash(sessionKey)
					if err != nil {
						return "", "", err
					}
					// root key agreement
					err = rootKeyAgreementRecipient(&senderHeaderPub, sender,
						recipient, h.NextSenderSessionPub, &h.SenderIdentityPub,
						nextSenderSession, recipientID, previousRootKeyHash,
						args.NumOfKeys, args.KeyStore)
					if err != nil {
						return "", "", err
					}
					// store new session state
					ss = &session.State{
						SenderSessionCount:          ss.SenderSessionCount + ss.SenderMessageCount,
						SenderMessageCount:          0,
						MaxRecipientCount:           0,
						RecipientTemp:               *h.NextSenderSessionPub,
						SenderSessionPub:            *nextSenderSession,
						NextSenderSessionPub:        nil,
						NextRecipientSessionPubSeen: nil,
						NymAddress:                  h.NymAddress,
						KeyInitSession:              false,
					}
					err = args.KeyStore.SetSessionState(sessionStateKey, ss)
					if err != nil {
						return "", "", err
					}
				}
			}
		} else {
			// check if session matches next session
			if ss.NextSenderSessionPub != nil &&
				ss.NextRecipientSessionPubSeen != nil &&
				ss.NextSenderSessionPub.HASH == h.RecipientTempHash &&
				ss.NextRecipientSessionPubSeen.HASH == h.SenderSessionPub.HASH {
				// switch session
				ss = &session.State{
					SenderSessionCount:          ss.SenderSessionCount + ss.SenderMessageCount,
					SenderMessageCount:          0,
					MaxRecipientCount:           0,
					RecipientTemp:               h.SenderSessionPub,
					SenderSessionPub:            *ss.NextSenderSessionPub,
					NextSenderSessionPub:        nil,
					NextRecipientSessionPubSeen: nil,
					NymAddress:                  h.NymAddress,
					KeyInitSession:              false,
				}
				err = args.KeyStore.SetSessionState(sessionStateKey, ss)
				if err != nil {
					return "", "", err
				}
			}
		}
		// a message with this session key has been decrypted -> delete key
		if err := args.KeyStore.DelPrivSessionKey(h.RecipientTempHash); err != nil {
			return "", "", err
		}
	}

	// make sure we got enough message keys
	n, err := args.KeyStore.NumMessageKeys(sessionKey)
	if err != nil {
		return "", "", err
	}
	if h.SenderMessageCount >= n {
		// generate more message keys
		log.Debugf("generate more message keys (h.SenderMessageCount=%d, n=%d)",
			h.SenderMessageCount, n)
		chainKey, err := args.KeyStore.GetChainKey(sessionKey)
		if err != nil {
			return "", "", err
		}
		// prevent denial of service attack by very large h.SenderMessageCount
		numOfKeys := h.SenderMessageCount / args.NumOfKeys
		if h.SenderMessageCount%args.NumOfKeys > 0 {
			numOfKeys++
		}
		numOfKeys *= args.NumOfKeys
		if numOfKeys > mime.MaxMsgSize/MaxContentLength+NumOfFutureKeys {
			return "", "",
				log.Errorf("msg: requested number of message keys too large")
		}
		log.Debugf("numOfKeys=%d", numOfKeys)
		var recipientPub *[32]byte
		if h.RecipientTempHash == ss.SenderSessionPub.HASH {
			recipientPub = ss.SenderSessionPub.PublicKey32()
		} else {
			log.Debug("different session")
			recipientKI, err := args.KeyStore.GetPrivateKeyEntry(h.RecipientTempHash)
			if err != nil && err != session.ErrNoKeyEntry {
				return "", "", err
			}
			if err != session.ErrNoKeyEntry {
				recipientPub = recipientKI.PublicKey32()
			} else {
				recipientKE, err := getSessionKey(args.KeyStore,
					h.RecipientTempHash)
				if err != nil {
					return "", "", err
				}
				recipientPub = recipientKE.PublicKey32()
			}
		}
		err = generateMessageKeys(sender, recipient, h.SenderIdentityPub.HASH,
			recipientID.HASH, chainKey, true,
			h.SenderSessionPub.PublicKey32(), recipientPub, numOfKeys,
			args.KeyStore)
		if err != nil {
			return "", "", err
		}
	}

	// get message key
	messageKey, err := args.KeyStore.GetMessageKey(sessionKey, false,
		h.SenderMessageCount)
	if err != nil {
		return "", "", err
	}

	// derive symmetric keys
	cryptoKey, hmacKey, err := deriveSymmetricKeys(messageKey)
	if err != nil {
		return "", "", err
	}

	// read crypto setup packet
	oh, err = readOuterHeader(args.Reader)
	if err != nil {
		return "", "", err
	}
	if oh.Type != cryptoSetup {
		return "", "", log.Error(ErrNotCryptoSetup)
	}
	if oh.PacketCount != count {
		return "", "", log.Error(ErrWrongCount)
	}
	count++
	if oh.PLen != aes.BlockSize {
		return "", "", log.Error(ErrWrongCryptoSetup)
	}
	iv := oh.inner

	// start HMAC calculation
	mac := hmac.New(sha512.New, hmacKey)
	if err := oh.write(mac, true); err != nil {
		return "", "", err
	}

	// actual decryption
	oh, err = readOuterHeader(args.Reader)
	if err != nil {
		return "", "", err
	}
	if oh.Type != encryptedPacket {
		return "", "", log.Error(ErrNotEncryptedPacket)
	}
	if oh.PacketCount != count {
		return "", "", log.Error(ErrWrongCount)
	}
	count++
	ciphertext := oh.inner
	plaintext := make([]byte, len(ciphertext))
	stream := cipher.AES256CTRStream(cryptoKey, iv)
	stream.XORKeyStream(plaintext, ciphertext)
	ih, err := readInnerHeader(bytes.NewBuffer(plaintext))
	if err != nil {
		return "", "", err
	}
	if ih.Type&dataType == 0 {
		return "", "", log.Error(ErrNotData)
	}
	var contentHash []byte
	if ih.Type&signType != 0 {
		// create signature hash
		contentHash = cipher.SHA512(ih.content)
	}
	if _, err := args.Writer.Write(ih.content); err != nil {
		return "", "", log.Error(err)
	}

	// continue HMAC calculation
	if err := oh.write(mac, true); err != nil {
		return "", "", err
	}

	// verify signature
	var sigBuf [ed25519.SignatureSize]byte
	if contentHash != nil {
		oh, err = readOuterHeader(args.Reader)
		if err != nil {
			return "", "", err
		}
		if oh.Type != encryptedPacket {
			return "", "", log.Error(ErrNotEncryptedPacket)
		}
		if oh.PacketCount != count {
			return "", "", log.Error(ErrWrongCount)
		}
		count++

		// continue HMAC calculation
		if err := oh.write(mac, true); err != nil {
			return "", "", err
		}

		ciphertext = oh.inner
		plaintext = make([]byte, len(ciphertext))
		stream.XORKeyStream(plaintext, ciphertext)
		ih, err = readInnerHeader(bytes.NewBuffer(plaintext))
		if err != nil {
			return "", "", err
		}
		if ih.Type&signatureType == 0 {
			return "", "", log.Error(ErrNotSignaturePacket)
		}

		if len(ih.content) != ed25519.SignatureSize {
			return "", "", log.Error(ErrWrongSignatureLength)
		}

		copy(sigBuf[:], ih.content)
	} else {
		oh, err = readOuterHeader(args.Reader)
		if err != nil {
			return "", "", err
		}
		if oh.Type != encryptedPacket {
			return "", "", log.Error(ErrNotEncryptedPacket)
		}
		if oh.PacketCount != count {
			return "", "", log.Error(ErrWrongCount)
		}
		count++

		// continue HMAC calculation
		if err := oh.write(mac, true); err != nil {
			return "", "", err
		}

		ciphertext = oh.inner
		plaintext = make([]byte, len(ciphertext))
		stream.XORKeyStream(plaintext, ciphertext)
		ih, err = readInnerHeader(bytes.NewBuffer(plaintext))
		if err != nil {
			return "", "", err
		}
		if ih.Type&paddingType == 0 {
			return "", "", log.Error(ErrNotPaddingPacket)
		}
	}
	// get processed sender UID
	uidRes := <-res
	if uidRes.err != nil {
		return "", "", uidRes.err
	}

	// verify signature, if necessary
	if contentHash != nil {
		if !ed25519.Verify(uidRes.msg.PublicSigKey32(), contentHash, &sigBuf) {
			return "", "", log.Error(ErrInvalidSignature)
		}
		// encode signature to base64 as return value
		sig = base64.Encode(sigBuf[:])
	}

	// read HMAC packet
	oh, err = readOuterHeader(args.Reader)
	if err != nil {
		return "", "", err
	}
	if oh.Type != hmacPacket {
		return "", "", log.Error(ErrNotHMACPacket)
	}
	if oh.PacketCount != count {
		return "", "", log.Error(ErrWrongCount)
	}
	count++
	if err := oh.write(mac, false); err != nil {
		return "", "", err
	}
	sum := mac.Sum(nil)
	log.Debugf("HMAC:       %s", base64.Encode(sum))

	if !hmac.Equal(sum, oh.inner) {
		return "", "", log.Error(ErrHMACsDiffer)
	}

	// delete message key
	err = args.KeyStore.DelMessageKey(sessionKey, false, h.SenderMessageCount)
	if err != nil {
		return "", "", err
	}

	return
}
예제 #13
0
// CalcStateKey computes the session state key from senderIdentityPub and
// recipientIdentityPub.
func CalcStateKey(senderIdentityPub, recipientIdentityPub *[32]byte) string {
	key := append(senderIdentityPub[:], recipientIdentityPub[:]...)
	return base64.Encode(cipher.SHA512(key))
}
예제 #14
0
func TestSessionStore(t *testing.T) {
	ms := New()
	sendKey, err := genMessageKey()
	if err != nil {
		t.Fatal(err)
	}
	recvKey, err := genMessageKey()
	if err != nil {
		t.Fatal(err)
	}
	sessionKey := base64.Encode(cipher.SHA512([]byte("sessionkey")))
	rootKeyHash := cipher.SHA512([]byte("rootkey"))
	if ms.HasSession(sessionKey) {
		t.Error("HasSession() should fail")
	}
	err = ms.StoreSession(sessionKey,
		base64.Encode(rootKeyHash),
		base64.Encode(cipher.SHA512([]byte("chainkey"))),
		[]string{base64.Encode(sendKey[:])},
		[]string{base64.Encode(recvKey[:])})
	if err != nil {
		t.Fatal(err)
	}
	if !ms.HasSession(sessionKey) {
		t.Error("HasSession() should succeed")
	}
	if ms.SessionKey() != sessionKey {
		t.Error("wrong SessionKey() result")
	}
	// test root key hash
	h, err := ms.GetRootKeyHash(sessionKey)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(h[:], rootKeyHash[:]) {
		t.Error("root key hashes are not equal")
	}
	// test sender key
	key, err := ms.GetMessageKey(sessionKey, true, 0)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(key[:], sendKey[:]) {
		t.Error("send key differs")
	}
	err = ms.DelMessageKey(sessionKey, true, 0)
	if err != nil {
		t.Fatal(err)
	}
	_, err = ms.GetMessageKey(sessionKey, true, 0)
	if err != session.ErrMessageKeyUsed {
		t.Error("should fail with session.ErrMessageKeyUsed")
	}
	// test receiver key
	key, err = ms.GetMessageKey(sessionKey, false, 0)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(key[:], recvKey[:]) {
		t.Error("recv key differs")
	}
	err = ms.DelMessageKey(sessionKey, false, 0)
	if err != nil {

		t.Fatal(err)
	}
	_, err = ms.GetMessageKey(sessionKey, false, 0)
	if err != session.ErrMessageKeyUsed {
		t.Error("should fail with session.ErrMessageKeyUsed")
	}
}
예제 #15
0
func TestGenerateMessageKeys(t *testing.T) {
	defer log.Flush()
	rk, err := base64.Decode("CH9NjvU/usWcT0vNgiiUHNt9UFgWKneEPRgN0HIvlP0=")
	if err != nil {
		t.Fatal(err)
	}
	ssp, err := base64.Decode("XRzQmXbf1TRTMVOpU9354Vx8mR32im0gK3IzzVPI/JE=")
	if err != nil {
		t.Fatal(err)
	}
	rp, err := base64.Decode("y2mzWFL3I16rkNPeMFleX/6a8Ynx93L8oirS4uSYTPo=")
	if err != nil {
		t.Fatal(err)
	}
	var rootKey [32]byte
	var senderSessionPub [32]byte
	var recipientPub [32]byte
	copy(rootKey[:], rk)
	copy(senderSessionPub[:], ssp)
	copy(recipientPub[:], rp)
	a := "*****@*****.**"
	b := "*****@*****.**"
	ms1 := memstore.New()

	senderSessionKey := session.CalcKey("sender", "recipient",
		base64.Encode(cipher.SHA512(senderSessionPub[:])),
		base64.Encode(cipher.SHA512(recipientPub[:])))

	recipientSessionKey := session.CalcKey("recipient", "sender",
		base64.Encode(cipher.SHA512(recipientPub[:])),
		base64.Encode(cipher.SHA512(senderSessionPub[:])))

	err = generateMessageKeys(a, b, "sender", "recipient", &rootKey, false,
		&senderSessionPub, &recipientPub, NumOfFutureKeys, ms1)
	if err != nil {
		t.Fatal(err)
	}

	rootKeyHash, err := ms1.GetRootKeyHash(senderSessionKey)
	if err != nil {
		t.Fatal(err)
	}
	b64 := base64.Encode(rootKeyHash[:])
	if b64 != "KJgsEto4kssCEBJAgGJTt2fJ6/FJqMupevapOwtkdgjF0z0VNI8Zzv15hwRVfZPGrGtgc5AGaeZiyao2wZLE2Q==" {
		t.Errorf("wrong rootKeyHash: %s", b64)
	}

	key, err := ms1.GetMessageKey(senderSessionKey, true, 0)
	if err != nil {
		t.Fatal(err)
	}
	b64 = base64.Encode(key[:])
	if b64 != "1EaJ70EOJ1tEYEksbmv1FOgmG+SB0A0LMcx4gp687NdrEmeb/T04GYneFw9hAenUGsgkOjGtySLIL36xqQlgmw==" {
		t.Errorf("wrong message key (sender, 0): %s", b64)
	}

	key, err = ms1.GetMessageKey(senderSessionKey, false, 0)
	if err != nil {
		t.Fatal(err)
	}
	b64 = base64.Encode(key[:])
	if b64 != "o5K0K5cKuWEAMX6g1Cbv2yrcddg2eoB7PhJjtECO1IQsVbNkTf/FqiW4X2/Tmy6XbXhEoysdYPJL4bokoINvsA==" {
		t.Errorf("wrong message key (recipient, 0): %s", b64)
	}

	key, err = ms1.GetMessageKey(senderSessionKey, true, 49)
	if err != nil {
		t.Fatal(err)
	}
	b64 = base64.Encode(key[:])
	if b64 != "r1TwPGq7WF5ysN2ZFyX4ZmnnNxMzH3hAAOfWew8mIND7BqFPSY01H/A7U48awcOwFd9pCnVXd5yc5W0TYvON/Q==" {
		t.Errorf("wrong message key (sender, 49): %s", b64)
	}

	key, err = ms1.GetMessageKey(senderSessionKey, false, 49)
	if err != nil {
		t.Fatal(err)
	}
	b64 = base64.Encode(key[:])
	if b64 != "d45Eic1g95nDrSncvo4FML/zha9lHtnDO/9kDyARQP3AgguhXD1bjw+/ep8MkI91qjAlnmHcsxVOAEEMbecmaQ==" {
		t.Errorf("wrong message key (recipient, 49): %s", b64)
	}

	// generate additional keys from chainKey
	chainKey, err := ms1.GetChainKey(senderSessionKey)
	if err != nil {
		t.Fatal(err)
	}
	err = generateMessageKeys(a, b, "sender", "recipient", chainKey, false,
		&senderSessionPub, &recipientPub, NumOfFutureKeys, ms1)
	if err != nil {
		t.Fatal(err)
	}

	// generate all keys at the same time
	ms2 := memstore.New()
	copy(rootKey[:], rk)
	err = generateMessageKeys(a, b, "sender", "recipient", &rootKey, true,
		&senderSessionPub, &recipientPub, 2*NumOfFutureKeys, ms2)
	if err != nil {
		t.Fatal(err)
	}
	n1, err := ms1.NumMessageKeys(senderSessionKey)
	if err != nil {
		t.Fatal(err)
	}
	n2, err := ms2.NumMessageKeys(recipientSessionKey)
	if err != nil {
		t.Fatal(err)
	}
	if n1 != n2 {
		t.Error("number of message keys differ")
	}

	// compare keys
	k1, err := ms1.GetMessageKey(senderSessionKey, true, NumOfFutureKeys-1)
	if err != nil {
		t.Fatal(err)
	}
	k2, err := ms2.GetMessageKey(recipientSessionKey, false, NumOfFutureKeys-1)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(k1[:], k2[:]) {
		t.Error("keys differ")
	}

	k1, err = ms1.GetMessageKey(senderSessionKey, true, 2*NumOfFutureKeys-1)
	if err != nil {
		t.Fatal(err)
	}
	k2, err = ms2.GetMessageKey(recipientSessionKey, false, 2*NumOfFutureKeys-1)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(k1[:], k2[:]) {
		t.Error("keys differ")
	}
}
예제 #16
0
파일: keys.go 프로젝트: JonathanLogan/mute
// generateMessageKeys generates the next numOfKeys many session keys from
// from rootKey for given senderIdentity and recipientIdentity.
// If recipientKeys is true the generated sender and reciever keys are stored in
// reverse order.
// It uses senderSessionPub and recipientPub in the process and calls
// keyStore.StoresSession and keyStore.SetSessionState to store the result.
func generateMessageKeys(
	senderIdentity, recipientIdentity string,
	senderIdentityPubkeyHash, recipientIdentityPubkeyHash string,
	rootKey *[32]byte,
	recipientKeys bool,
	senderSessionPub, recipientPub *[32]byte,
	numOfKeys uint64,
	keyStore session.Store,
) error {
	var (
		identities string
		send       []string
		recv       []string
	)

	// identity_fix = HASH(SORT(SenderNym, RecipientNym))
	if senderIdentity < recipientIdentity {
		identities = senderIdentity + recipientIdentity
	} else {
		identities = recipientIdentity + senderIdentity
	}
	identityFix := cipher.SHA512([]byte(identities))
	recipientPubHash := cipher.SHA512(recipientPub[:])
	senderSessionPubHash := cipher.SHA512(senderSessionPub[:])

	chainKey := rootKey[:]
	for i := uint64(0); i < numOfKeys; i++ {
		// messagekey_send[i] = HMAC_HASH(chainkey, "MESSAGE" | HASH(RecipientPub) | identity_fix)
		buffer := append([]byte("MESSAGE"), recipientPubHash...)
		buffer = append(buffer, identityFix...)
		send = append(send, base64.Encode(cipher.HMAC(chainKey, buffer)))

		// messagekey_recv[i] = HMAC_HASH(chainkey, "MESSAGE" | HASH(SenderSessionPub) | identity_fix)
		buffer = append([]byte("MESSAGE"), senderSessionPubHash...)
		buffer = append(buffer, identityFix...)
		recv = append(recv, base64.Encode(cipher.HMAC(chainKey, buffer)))

		// chainkey = HMAC_HASH(chainkey, "CHAIN" )
		chainKey = cipher.HMAC(chainKey, []byte("CHAIN"))[:32]
	}

	// calculate root key hash
	rootKeyHash := base64.Encode(cipher.SHA512(rootKey[:]))
	bzero.Bytes(rootKey[:])

	// reverse key material, if necessary
	if recipientKeys {
		send, recv = recv, send
	}

	// store session
	var sessionKey string
	if recipientKeys {
		key := recipientIdentityPubkeyHash
		key += senderIdentityPubkeyHash
		key += base64.Encode(cipher.SHA512(recipientPub[:]))
		key += base64.Encode(cipher.SHA512(senderSessionPub[:]))
		sessionKey = base64.Encode(cipher.SHA512([]byte(key)))
	} else {
		key := senderIdentityPubkeyHash
		key += recipientIdentityPubkeyHash
		key += base64.Encode(cipher.SHA512(senderSessionPub[:]))
		key += base64.Encode(cipher.SHA512(recipientPub[:]))
		sessionKey = base64.Encode(cipher.SHA512([]byte(key)))
	}
	err := keyStore.StoreSession(sessionKey, rootKeyHash,
		base64.Encode(chainKey), send, recv)
	if err != nil {
		return err
	}

	return nil
}
예제 #17
0
// Encrypt encrypts a message with the argument given in args and returns the
// nymAddress the message should be delivered to.
func Encrypt(args *EncryptArgs) (nymAddress string, err error) {
	log.Debugf("msg.Encrypt(): %s -> %s", args.From.Identity(), args.To.Identity())

	// set defaults
	if args.NumOfKeys == 0 {
		args.NumOfKeys = NumOfFutureKeys
	}
	if args.AvgSessionSize == 0 {
		args.AvgSessionSize = AverageSessionSize
	}

	// create sender key
	senderHeaderKey, err := cipher.Curve25519Generate(cipher.RandReader)
	if err != nil {
		return "", log.Error(err)
	}

	// create pre-header
	ph := newPreHeader(senderHeaderKey.PublicKey()[:])

	// create base64 encoder
	var out bytes.Buffer
	wc := base64.NewEncoder(&out)

	// write pre-header
	var buf bytes.Buffer
	var count uint32
	if err := ph.write(&buf); err != nil {
		return "", err
	}
	oh := newOuterHeader(preHeaderPacket, count, buf.Bytes())
	if err := oh.write(wc, true); err != nil {
		return "", err
	}
	count++

	// get session state
	sender := args.From.Identity()
	recipient := args.To.Identity()
	sessionStateKey := session.CalcStateKey(args.From.PubKey().PublicKey32(),
		args.To.PubKey().PublicKey32())
	var ss *session.State
	if args.StatusCode != StatusReset {
		ss, err = args.KeyStore.GetSessionState(sessionStateKey)
		if err != nil {
			return "", err
		}
	}
	if ss == nil {
		// no session found -> start first session
		log.Debug("no session found -> start first session")
		var recipientTemp *uid.KeyEntry
		recipientTemp, nymAddress, err = args.KeyStore.GetPublicKeyEntry(args.To)
		if err != nil {
			return "", err
		}
		// create session key
		var senderSession uid.KeyEntry
		if err := senderSession.InitDHKey(args.Rand); err != nil {
			return "", err
		}
		// store session key
		if err := addSessionKey(args.KeyStore, &senderSession); err != nil {
			return "", err
		}
		// root key agreement
		err = rootKeyAgreementSender(senderHeaderKey.PublicKey(),
			args.From.Identity(), args.To.Identity(), &senderSession,
			args.From.PubKey(), recipientTemp, args.To.PubKey(), nil,
			args.NumOfKeys, args.KeyStore)
		if err != nil {
			return "", err
		}
		// set session state
		ss = &session.State{
			SenderSessionCount:          0,
			SenderMessageCount:          0,
			MaxRecipientCount:           0,
			RecipientTemp:               *recipientTemp,
			SenderSessionPub:            senderSession,
			NextSenderSessionPub:        nil,
			NextRecipientSessionPubSeen: nil,
			NymAddress:                  nymAddress,
			KeyInitSession:              true,
		}
		log.Debugf("set session: %s", ss.SenderSessionPub.HASH)
		err = args.KeyStore.SetSessionState(sessionStateKey, ss)
		if err != nil {
			return "", err
		}
	} else if args.StatusCode != StatusError { // do not update sessions for StatusError messages
		log.Debug("session found")
		log.Debugf("got session: %s", ss.SenderSessionPub.HASH)
		nymAddress = ss.NymAddress
		if ss.NextSenderSessionPub == nil {
			// start new session in randomized fashion
			n, err := rand.Int(cipher.RandReader, big.NewInt(int64(args.AvgSessionSize)))
			if err != nil {
				return "", err
			}
			if n.Int64() == 0 {
				_, err := setNextSenderSessionPub(args.KeyStore, ss,
					sessionStateKey, args.Rand)
				if err != nil {
					return "", err
				}
			}
		}
	}

	// create header
	log.Debugf("senderID:    %s", args.From.UIDContent.PUBKEYS[0].HASH)
	log.Debugf("recipientID: %s", args.To.UIDContent.PUBKEYS[0].HASH)
	log.Debugf("ss.SenderSessionCount: %d", ss.SenderSessionCount)
	log.Debugf("ss.SenderMessageCount: %d", ss.SenderMessageCount)
	log.Debugf("ss.RecipientTempHash:  %s", ss.RecipientTemp.HASH)
	h, err := newHeader(args.From, args.To, ss.RecipientTemp.HASH,
		&ss.SenderSessionPub, ss.NextSenderSessionPub,
		ss.NextRecipientSessionPubSeen, args.NymAddress, ss.SenderSessionCount,
		ss.SenderMessageCount, args.SenderLastKeychainHash, args.Rand,
		args.StatusCode)
	if err != nil {
		return "", err
	}
	log.Debugf("h.SenderSessionPub:             %s", h.SenderSessionPub.HASH)
	if h.NextSenderSessionPub != nil {
		log.Debugf("h.NextSenderSessionPub:         %s", h.NextSenderSessionPub.HASH)
	}
	if h.NextRecipientSessionPubSeen != nil {
		log.Debugf("h.NextRecipientSessionPubSeen:  %s",
			h.NextRecipientSessionPubSeen.HASH)
	}

	// create (encrypted) header packet
	recipientIdentityPub, err := args.To.PublicKey()
	if err != nil {
		return "", err
	}

	hp, err := newHeaderPacket(h, recipientIdentityPub,
		senderHeaderKey.PrivateKey(), args.Rand)
	if err != nil {
		return "", err
	}

	// write (encrypted) header packet
	buf.Reset()
	if err := hp.write(&buf); err != nil {
		return "", err
	}
	oh = newOuterHeader(encryptedHeader, count, buf.Bytes())
	if err := oh.write(wc, true); err != nil {
		return "", err
	}
	count++

	sessionKey := session.CalcKey(args.From.PubKey().HASH,
		args.To.PubKey().HASH, ss.SenderSessionPub.HASH, ss.RecipientTemp.HASH)

	// make sure we got enough message keys
	n, err := args.KeyStore.NumMessageKeys(sessionKey)
	if err != nil {
		return "", err
	}
	if ss.SenderMessageCount >= n {
		// generate more message keys
		log.Debugf("generate more message keys (ss.SenderMessageCount=%d)",
			ss.SenderMessageCount)
		chainKey, err := args.KeyStore.GetChainKey(sessionKey)
		if err != nil {
			return "", err
		}

		err = generateMessageKeys(sender, recipient, args.From.PubKey().HASH,
			args.To.PubKey().HASH, chainKey, false,
			ss.SenderSessionPub.PublicKey32(), ss.RecipientTemp.PublicKey32(),
			args.NumOfKeys, args.KeyStore)
		if err != nil {
			return "", err
		}
	}

	// get message key
	messageKey, err := args.KeyStore.GetMessageKey(sessionKey, true,
		ss.SenderMessageCount)
	if err != nil {
		return "", err
	}

	// derive symmetric keys
	cryptoKey, hmacKey, err := deriveSymmetricKeys(messageKey)
	if err != nil {
		return "", err
	}

	// write crypto setup packet
	iv := make([]byte, aes.BlockSize)
	if _, err := io.ReadFull(args.Rand, iv); err != nil {
		return "", log.Error(err)
	}
	oh = newOuterHeader(cryptoSetup, count, iv)

	if err := oh.write(wc, true); err != nil {
		return "", err
	}
	count++

	// start HMAC calculation
	mac := hmac.New(sha512.New, hmacKey)
	if err := oh.write(mac, true); err != nil {
		return "", err
	}

	// actual encryption
	var content []byte
	if args.StatusCode == StatusOK { // StatusReset and StatusError messages are empty
		content, err = ioutil.ReadAll(args.Reader)
		if err != nil {
			return "", log.Error(err)
		}
	}
	// enforce maximum content length
	if len(content) > MaxContentLength {
		return "", log.Errorf("len(content) = %d > %d = MaxContentLength)",
			len(content), MaxContentLength)
	}

	// encrypted packet
	var contentHash []byte
	var innerType uint8
	if args.PrivateSigKey != nil {
		contentHash = cipher.SHA512(content)
		innerType = dataType | signType
	} else {
		innerType = dataType
	}
	ih := newInnerHeader(innerType, false, content)
	buf.Reset()
	if err := ih.write(&buf); err != nil {
		return "", err
	}
	stream := cipher.AES256CTRStream(cryptoKey, iv)
	stream.XORKeyStream(buf.Bytes(), buf.Bytes())
	oh = newOuterHeader(encryptedPacket, count, buf.Bytes())
	if err := oh.write(wc, true); err != nil {
		return "", err
	}
	count++

	// continue HMAC calculation
	if err := oh.write(mac, true); err != nil {
		return "", err
	}

	// signature header & padding
	buf.Reset()
	if args.PrivateSigKey != nil {
		sig := ed25519.Sign(args.PrivateSigKey, contentHash)
		// signature
		ih = newInnerHeader(signatureType, true, sig[:])
		if err := ih.write(&buf); err != nil {
			return "", err
		}
		// padding
		padLen := MaxContentLength - len(content)
		pad, err := padding.Generate(padLen, cipher.RandReader)
		if err != nil {
			return "", err
		}
		ih = newInnerHeader(paddingType, false, pad)
		if err := ih.write(&buf); err != nil {
			return "", err
		}
	} else {
		// just padding
		padLen := MaxContentLength + signatureSize - encryptedPacketSize +
			innerHeaderSize - len(content)
		pad, err := padding.Generate(padLen, cipher.RandReader)
		if err != nil {
			return "", err
		}
		ih = newInnerHeader(paddingType, false, pad)
		if err := ih.write(&buf); err != nil {
			return "", err
		}
	}
	// encrypt inner header
	stream.XORKeyStream(buf.Bytes(), buf.Bytes())
	oh = newOuterHeader(encryptedPacket, count, buf.Bytes())
	if err := oh.write(wc, true); err != nil {
		return "", err
	}
	count++

	// continue HMAC calculation
	if err := oh.write(mac, true); err != nil {
		return "", err
	}

	// create HMAC header
	oh = newOuterHeader(hmacPacket, count, nil)
	oh.PLen = sha512.Size
	if err := oh.write(mac, false); err != nil {
		return "", err
	}
	oh.inner = mac.Sum(oh.inner)
	log.Debugf("HMAC:       %s", base64.Encode(oh.inner))
	if err := oh.write(wc, true); err != nil {
		return "", err
	}
	count++

	// write output
	wc.Close()
	if out.Len() != EncodedMsgSize {
		return "", log.Errorf("out.Len() = %d != %d = EncodedMsgSize)",
			out.Len(), EncodedMsgSize)
	}
	if _, err := io.Copy(args.Writer, &out); err != nil {
		return "", log.Error(err)
	}

	// delete message key
	err = args.KeyStore.DelMessageKey(sessionKey, true, ss.SenderMessageCount)
	if err != nil {
		return "", err
	}
	// increase SenderMessageCount
	ss.SenderMessageCount++
	err = args.KeyStore.SetSessionState(sessionStateKey, ss)
	if err != nil {
		return "", err
	}

	return
}