ITS#9437 Implement TOTP drift correction

This commit is contained in:
Ondřej Kuzník 2021-01-26 12:59:11 +00:00 committed by Quanah Gibson-Mount
parent 87f3bad8bb
commit fe7e4697e9
2 changed files with 69 additions and 40 deletions

View File

@ -119,9 +119,14 @@ The length of the time-step period for TOTP calculation.
.B oathTOTPLastTimeStep: <number>
The order of the last TOTP token successfully redeemed by the user.
.TP
.B oathTOTPGrace: <number>
.B oathTOTPTimeStepWindow: <number>
The number of time periods around the current time to try when checking the
password provided by the user.
.TP
.B oathTOTPTimeStepDrift: <number>
If the client didn't provide the correct token but it still fit with
oathTOTPTimeStepWindow above, this attribute records the current offset to
provide for slow clock drift of the client device.
.RE
.SH "SEE ALSO"

View File

@ -130,6 +130,7 @@ AttributeDescription *ad_oathTOTPParams;
AttributeDescription *ad_oathTOTPToken;
AttributeDescription *ad_oathTOTPLastTimeStep;
AttributeDescription *ad_oathTOTPTimeStepWindow;
AttributeDescription *ad_oathTOTPTimeStepDrift;
static struct otp_at {
char *schema;
@ -327,7 +328,8 @@ static struct otp_at {
"DESC 'OATH-LDAP: Last observed time step shift seen for TOTP' "
"X-ORIGIN 'OATH-LDAP' "
"SINGLE-VALUE "
"SUP oathCounter )" },
"SUP oathCounter )",
&ad_oathTOTPTimeStepDrift },
{ "( oath-ldap-at:11 "
"NAME 'oathSecretLength' "
@ -452,7 +454,7 @@ static struct otp_oc {
"AUXILIARY "
"SUP oathParams "
"MUST ( oathTOTPTimeStepPeriod ) "
"MAY ( oathTOTPTimeStepWindow $ oathTOTPTimeStepDrift ) )",
"MAY ( oathTOTPTimeStepWindow ) )",
&oc_oathTOTPParams },
{ "( oath-ldap-oc:3 "
"NAME 'oathToken' "
@ -476,7 +478,7 @@ static struct otp_oc {
"X-ORIGIN 'OATH-LDAP' "
"AUXILIARY "
"SUP oathToken "
"MAY ( oathTOTPParams $ oathTOTPLastTimeStep ) )",
"MAY ( oathTOTPParams $ oathTOTPLastTimeStep $ oathTOTPTimeStepDrift ) )",
&oc_oathTOTPToken },
{ NULL }
};
@ -672,13 +674,14 @@ done:
}
static long
otp_totp( Operation *op, Entry *token )
otp_totp( Operation *op, Entry *token, long *drift )
{
char outbuf[MAX_DIGITS + 1];
Entry *params = NULL;
Attribute *a;
BerValue *secret, client_otp;
const void *mech;
long t, last_step = -1, found = -1, window = 0;
long t, last_step = -1, found = -1, window = 0, old_drift;
int i, otp_len, time_step;
a = attr_find( token->e_attrs, ad_oathSecret );
@ -706,7 +709,6 @@ otp_totp( Operation *op, Entry *token )
a->a_vals[0].bv_val );
goto done;
}
t = op->o_time / time_step;
a = attr_find( params->e_attrs, ad_oathTOTPTimeStepWindow );
if ( a && lutil_atol( &window, a->a_vals[0].bv_val ) != 0 ) {
@ -716,6 +718,16 @@ otp_totp( Operation *op, Entry *token )
goto done;
}
a = attr_find( params->e_attrs, ad_oathTOTPTimeStepDrift );
if ( a && lutil_atol( drift, a->a_vals[0].bv_val ) != 0 ) {
Debug( LDAP_DEBUG_ANY, "otp_totp: "
"could not parse oathTOTPTimeStepDrift value %s\n",
a->a_vals[0].bv_val );
goto done;
}
old_drift = *drift;
t = op->o_time / time_step + *drift;
a = attr_find( params->e_attrs, ad_oathOTPLength );
if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) {
Debug( LDAP_DEBUG_ANY, "otp_totp: "
@ -740,38 +752,32 @@ otp_totp( Operation *op, Entry *token )
client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len;
/* If check succeeds, advance the step counter accordingly */
for ( i = -window; i <= window; i++ ) {
char outbuf[MAX_DIGITS + 1];
/* Negation of A001057 series that enumerates all integers:
* (0, -1, 1, -2, 2, ...) */
for ( i = 0; i >= -window; i = ( i < 0 ) ? -i : ~i ) {
BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) };
if ( t + i < 0 ) continue;
if ( t + i <= last_step ) continue;
generate( secret, t + i, otp_len, &out, mech );
if ( !ber_bvcmp( &out, &client_otp ) ) {
found = t + i;
*drift = old_drift + i;
/* Would we leak information if we stopped right now? */
}
}
/* OTP check passed, trim the password */
if ( found >= 0 ) {
int offset = found - t;
assert( found > last_step );
if ( found <= last_step ) {
/* Token re-used, refuse */
found = -1;
Debug( LDAP_DEBUG_TRACE, "%s client tried to reuse old TOTP token %s, offset %d\n",
op->o_log_prefix, token->e_name.bv_val, offset );
} else {
/* OTP check passed, trim the password */
op->orb_cred.bv_len -= otp_len;
Debug( LDAP_DEBUG_TRACE, "%s TOTP token %s redeemed with offset %d\n",
op->o_log_prefix, token->e_name.bv_val, offset );
}
} else {
Debug( LDAP_DEBUG_TRACE, "%s TOTP token was not valid\n",
op->o_log_prefix );
op->orb_cred.bv_len -= otp_len;
Debug( LDAP_DEBUG_TRACE, "%s TOTP token %s redeemed with new drift of %ld\n",
op->o_log_prefix, token->e_name.bv_val, *drift );
}
done:
memset( outbuf, 0, sizeof(outbuf) );
if ( params ) {
be_entry_release_r( op, params );
}
@ -784,9 +790,9 @@ otp_op_bind( Operation *op, SlapReply *rs )
slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
BerValue totpdn = BER_BVNULL, hotpdn = BER_BVNULL, ndn;
Entry *user = NULL, *token = NULL;
AttributeDescription *ad = NULL;
AttributeDescription *ad = NULL, *drift_ad = NULL;
Attribute *a;
long t = -1;
long t = -1, drift = 0;
int rc = SLAP_CB_CONTINUE;
if ( op->oq_bind.rb_method != LDAP_AUTH_SIMPLE ) {
@ -818,7 +824,8 @@ otp_op_bind( Operation *op, SlapReply *rs )
&token ) == LDAP_SUCCESS ) {
ndn = totpdn;
ad = ad_oathTOTPLastTimeStep;
t = otp_totp( op, token );
drift_ad = ad_oathTOTPTimeStepDrift;
t = otp_totp( op, token, &drift );
be_entry_release_r( op, token );
token = NULL;
}
@ -832,27 +839,44 @@ otp_op_bind( Operation *op, SlapReply *rs )
token = NULL;
}
/* If check succeeds, advance the step counter accordingly */
/* If check succeeds, advance the step counter and drift accordingly */
if ( t >= 0 ) {
char outbuf[32];
char outbuf[32], drift_buf[32];
Operation op2;
Opheader oh;
Modifications mod;
Modifications mod[2], *m = mod;
SlapReply rs2 = { REP_RESULT };
slap_callback cb = { .sc_response = &slap_null_cb };
BerValue bv[2];
BerValue bv[2], bv_drift[2];
bv[0].bv_val = outbuf;
bv[0].bv_len = snprintf( bv[0].bv_val, sizeof(outbuf), "%ld", t );
BER_BVZERO( &bv[1] );
mod.sml_numvals = 1;
mod.sml_values = bv;
mod.sml_nvalues = NULL;
mod.sml_desc = ad;
mod.sml_op = LDAP_MOD_REPLACE;
mod.sml_flags = SLAP_MOD_INTERNAL;
mod.sml_next = NULL;
m->sml_numvals = 1;
m->sml_values = bv;
m->sml_nvalues = NULL;
m->sml_desc = ad;
m->sml_op = LDAP_MOD_REPLACE;
m->sml_flags = SLAP_MOD_INTERNAL;
if ( drift_ad ) {
m->sml_next = &mod[1];
bv_drift[0].bv_val = drift_buf;
bv_drift[0].bv_len = snprintf(
bv_drift[0].bv_val, sizeof(drift_buf), "%ld", drift );
BER_BVZERO( &bv_drift[1] );
m++;
m->sml_numvals = 1;
m->sml_values = bv_drift;
m->sml_nvalues = NULL;
m->sml_desc = drift_ad;
m->sml_op = LDAP_MOD_REPLACE;
m->sml_flags = SLAP_MOD_INTERNAL;
}
m->sml_next = NULL;
op2 = *op;
oh = *op->o_hdr;
@ -861,7 +885,7 @@ otp_op_bind( Operation *op, SlapReply *rs )
op2.o_callback = &cb;
op2.o_tag = LDAP_REQ_MODIFY;
op2.orm_modlist = &mod;
op2.orm_modlist = mod;
op2.o_dn = op->o_bd->be_rootdn;
op2.o_ndn = op->o_bd->be_rootndn;
op2.o_req_dn = ndn;