Commit d0eef09c authored by Ian Goldberg's avatar Ian Goldberg

Add a timer_control callback to OtrlMessageAppOps

The new timer_control callback will instruct the application to call the
new otrl_message_poll function in order to actually clear out the above
stale committed keys.
parent 86e6cc35
2012-08-27
* src/auth.h:
* src/auth.c:
* src/message.c: Record the time the last COMMIT was sent from a
master context. This will be used to clear the committed key
from the master context once we don't expect any more instances
of our buddy to respond with a DHKEY message.
* UPGRADING:
* src/userstate.h:
* src/userstate.c:
* src/message.h:
* src/message.c: Add a timer_control callback to
OtrlMessageAppOps in order to actually clear out the above stale
committed keys.
2012-08-26
* src/context.c:
......
......@@ -307,6 +307,54 @@ application to tweak formatting on the plaintext if, for example, this
is something that would normally be done on the plaintext by some other
entity while the message is in transit.
/* When timer_control is called, turn off any existing periodic
* timer.
*
* Additionally, if interval > 0, set a new periodic timer
* to go off every interval seconds. When that timer fires, you
* must call otrl_message_poll(userstate, uiops, uiopdata); from the
* main libotr thread.
*
* The timing does not have to be exact; this timer is used to
* provide forward secrecy by cleaning up stale private state that
* may otherwise stick around in memory. Note that the
* timer_control callback may be invoked from otrl_message_poll
* itself, possibly to indicate that interval == 0 (that is, that
* there's no more periodic work to be done at this time).
*
* If you set this callback to NULL, then you must ensure that your
* application calls otrl_message_poll(userstate, uiops, uiopdata);
* from the main libotr thread every definterval seconds (where
* definterval can be obtained by calling
* definterval = otrl_message_poll_get_default_interval(userstate);
* right after creating the userstate). The advantage of
* implementing the timer_control callback is that the timer can be
* turned on by libotr only when it's needed.
*
* It is not a problem (except for a minor performance hit) to call
* otrl_message_poll more often than requested, whether
* timer_control is implemented or not.
*
* If you fail to implement the timer_control callback, and also
* fail to periodically call otrl_message_poll, then you open your
* users to a possible forward secrecy violation: an attacker that
* compromises the user's computer may be able to decrypt a handful
* of long-past messages (the first messages of an OTR
* conversation).
*/
void (*timer_control)(void *opdata, unsigned int interval);
In order to prevent a forward secrecy violation, applications using
libotr now need to be able to call otrl_message_poll on occasion. The
simplest thing to do is just to set up a local timer that calls that
function every definterval =
otrl_message_poll_get_default_interval(userstate) seconds. To avoid
unnecessary overhead, however, the timer_control callback is available.
If you set timer_control to non-NULL, it will be called with
instructions to turn on or off the periodic timer, and to what interval.
You must also be sure to turn off the timer before freeing your
userstate with otrl_userstate_free.
3.2. Instance Tags
......
......@@ -56,6 +56,15 @@ extern unsigned int otrl_api_version;
* resending in response to a rekey? */
#define RESEND_INTERVAL 60
/* How long should we wait for the last of the logged-in instances of
* our buddy to respond before marking our private key as a candidate
* for wiping (in seconds)? */
#define MAX_AKE_WAIT_TIME 60
/* How frequently should we check our ConnContexts for wipeable private
* keys (and wipe them) (in seconds)? */
#define POLL_DEFAULT_INTERVAL 70
/* Send a message to the network, fragmenting first if necessary.
* All messages to be sent to the network should go through this
* method immediately before they are sent, ie after encryption. */
......@@ -444,7 +453,8 @@ fragment:
* appropriate user. Otherwise, display an appripriate error dialog.
* Return the value of err that was passed. */
static gcry_error_t send_or_error_auth(const OtrlMessageAppOps *ops,
void *opdata, gcry_error_t err, ConnContext *context)
void *opdata, gcry_error_t err, ConnContext *context,
OtrlUserState us)
{
if (!err) {
const char *msg = context->auth.lastauthmsg;
......@@ -461,12 +471,19 @@ static gcry_error_t send_or_error_auth(const OtrlMessageAppOps *ops,
otrl_context_update_recent_child(context, 1);
}
/* If this is a master context, and we're sending a COMMIT
/* If this is a master context, and we're sending a v3 COMMIT
* message, update the commit_sent_time timestamp, so we can
* expire it. */
if (context == context->m_context &&
context->auth.authstate == OTRL_AUTHSTATE_AWAITING_DHKEY) {
context->auth.authstate == OTRL_AUTHSTATE_AWAITING_DHKEY &&
context->auth.protocol_version == 3) {
context->auth.commit_sent_time = now;
/* If there's not already a timer running to clean up
* this private key, try to start one. */
if (us->timer_running == 0 && ops && ops->timer_control) {
ops->timer_control(opdata, POLL_DEFAULT_INTERVAL);
us->timer_running = 1;
}
}
}
} else {
......@@ -1129,11 +1146,11 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
switch(otrl_proto_query_bestversion(message, policy)) {
case 3:
err = otrl_auth_start_v23(&(context->auth), 3);
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
break;
case 2:
err = otrl_auth_start_v23(&(context->auth), 2);
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
break;
case 1:
/* Get our private key */
......@@ -1151,7 +1168,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
if (privkey) {
err = otrl_auth_start_v1(&(context->auth), our_dh,
our_keyid, privkey);
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
}
break;
default:
......@@ -1164,7 +1181,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
case OTRL_MSGTYPE_DH_COMMIT:
err = otrl_auth_handle_commit(&(context->auth), otrtag, version);
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
if (edata.ignore_message == -1) edata.ignore_message = 1;
break;
......@@ -1186,7 +1203,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
err = otrl_auth_handle_key(&(context->auth), otrtag,
&haveauthmsg, privkey);
if (err || haveauthmsg) {
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
}
}
......@@ -1211,7 +1228,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
otrtag, &haveauthmsg, privkey, go_encrypted,
&edata);
if (err || haveauthmsg) {
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
maybe_resend(&edata);
}
}
......@@ -1223,7 +1240,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
err = otrl_auth_handle_signature(&(context->auth),
otrtag, &haveauthmsg, go_encrypted, &edata);
if (err || haveauthmsg) {
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
maybe_resend(&edata);
}
......@@ -1258,7 +1275,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
message, &haveauthmsg, privkey, our_dh, our_keyid,
go_encrypted, &edata);
if (err || haveauthmsg) {
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
maybe_resend(&edata);
}
}
......@@ -1778,11 +1795,11 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
switch(bestversion) {
case 3:
err = otrl_auth_start_v23(&(context->auth), 3);
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
break;
case 2:
err = otrl_auth_start_v23(&(context->auth), 2);
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
break;
case 1:
/* Get our private key */
......@@ -1802,7 +1819,7 @@ int otrl_message_receiving(OtrlUserState us, const OtrlMessageAppOps *ops,
if (privkey) {
err = otrl_auth_start_v1(&(context->auth), NULL, 0,
privkey);
send_or_error_auth(ops, opdata, err, context);
send_or_error_auth(ops, opdata, err, context, us);
}
break;
default:
......@@ -1970,3 +1987,54 @@ gcry_error_t otrl_message_symkey(OtrlUserState us,
/* We weren't in an encrypted session. */
return gcry_error(GPG_ERR_INV_VALUE);
}
/* If you do _not_ define a timer_control callback function, set a timer
* to go off every definterval =
* otrl_message_poll_get_default_interval(userstate) seconds, and call
* otrl_message_poll every time the timer goes off. */
unsigned int otrl_message_poll_get_default_interval(OtrlUserState us)
{
return POLL_DEFAULT_INTERVAL;
}
/* Call this function every so often, either as directed by the
* timer_control callback, or every definterval =
* otrl_message_poll_get_default_interval(userstate) seconds if you have
* no timer_control callback. This function must be called from the
* main libotr thread.*/
void otrl_message_poll(OtrlUserState us, const OtrlMessageAppOps *ops,
void *opdata)
{
/* Wipe private keys last sent before this time */
time_t expire_before = time(NULL) - MAX_AKE_WAIT_TIME;
ConnContext *contextp;
/* Is there a context still waiting for a DHKEY message, even after
* we wipe the stale ones? */
int still_waiting = 0;
if (us == NULL) return;
for (contextp = us->context_root; contextp; contextp = contextp->next) {
/* If this is a master context, and it's still waiting for a
* v3 DHKEY message, see if it's waited long enough. */
if (contextp->m_context == contextp &&
contextp->auth.authstate == OTRL_AUTHSTATE_AWAITING_DHKEY &&
contextp->auth.protocol_version == 3 &&
contextp->auth.commit_sent_time > 0) {
if (contextp->auth.commit_sent_time < expire_before) {
otrl_auth_clear(&contextp->auth);
} else {
/* Not yet expired */
still_waiting = 1;
}
}
}
/* If there's nothing more to wait for, stop the timer, if possible. */
if (still_waiting == 0 && ops && ops->timer_control) {
ops->timer_control(opdata, 0);
us->timer_running = 0;
}
}
......@@ -255,6 +255,43 @@ typedef struct s_OtrlMessageAppOps {
/* Deallocate a string returned by convert_msg. */
void (*convert_free)(void *opdata, ConnContext *context, char *dest);
/* When timer_control is called, turn off any existing periodic
* timer.
*
* Additionally, if interval > 0, set a new periodic timer
* to go off every interval seconds. When that timer fires, you
* must call otrl_message_poll(userstate, uiops, uiopdata); from the
* main libotr thread.
*
* The timing does not have to be exact; this timer is used to
* provide forward secrecy by cleaning up stale private state that
* may otherwise stick around in memory. Note that the
* timer_control callback may be invoked from otrl_message_poll
* itself, possibly to indicate that interval == 0 (that is, that
* there's no more periodic work to be done at this time).
*
* If you set this callback to NULL, then you must ensure that your
* application calls otrl_message_poll(userstate, uiops, uiopdata);
* from the main libotr thread every definterval seconds (where
* definterval can be obtained by calling
* definterval = otrl_message_poll_get_default_interval(userstate);
* right after creating the userstate). The advantage of
* implementing the timer_control callback is that the timer can be
* turned on by libotr only when it's needed.
*
* It is not a problem (except for a minor performance hit) to call
* otrl_message_poll more often than requested, whether
* timer_control is implemented or not.
*
* If you fail to implement the timer_control callback, and also
* fail to periodically call otrl_message_poll, then you open your
* users to a possible forward secrecy violation: an attacker that
* compromises the user's computer may be able to decrypt a handful
* of long-past messages (the first messages of an OTR
* conversation).
*/
void (*timer_control)(void *opdata, unsigned int interval);
} OtrlMessageAppOps;
/* Deallocate a message allocated by other otrl_message_* routines. */
......@@ -385,4 +422,18 @@ gcry_error_t otrl_message_symkey(OtrlUserState us,
unsigned int use, const unsigned char *usedata, size_t usedatalen,
unsigned char *symkey);
/* If you do _not_ define a timer_control callback function, set a timer
* to go off every definterval =
* otrl_message_poll_get_default_interval(userstate) seconds, and call
* otrl_message_poll every time the timer goes off. */
unsigned int otrl_message_poll_get_default_interval(OtrlUserState us);
/* Call this function every so often, either as directed by the
* timer_control callback, or every definterval =
* otrl_message_poll_get_default_interval(userstate) seconds if you have
* no timer_control callback. This function must be called from the
* main libotr thread.*/
void otrl_message_poll(OtrlUserState us, const OtrlMessageAppOps *ops,
void *opdata);
#endif
......@@ -41,10 +41,12 @@ OtrlUserState otrl_userstate_create(void)
us->privkey_root = NULL;
us->instag_root = NULL;
us->pending_root = NULL;
us->timer_running = 0;
return us;
}
/* Free a OtrlUserState */
/* Free a OtrlUserState. If you have a timer running for this userstate,
stop it before freeing the userstate. */
void otrl_userstate_free(OtrlUserState us)
{
otrl_context_forget_all(us);
......
......@@ -32,6 +32,7 @@ struct s_OtrlUserState {
OtrlPrivKey *privkey_root;
OtrlInsTag *instag_root;
OtrlPendingPrivKey *pending_root;
int timer_running;
};
/* Create a new OtrlUserState. Most clients will only need one of
......@@ -43,7 +44,8 @@ struct s_OtrlUserState {
* OtrlUserState. */
OtrlUserState otrl_userstate_create(void);
/* Free a OtrlUserState */
/* Free a OtrlUserState. If you have a timer running for this userstate,
stop it before freeing the userstate. */
void otrl_userstate_free(OtrlUserState us);
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment