summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2014-01-28 17:11:04 -0500
committerroot <root@ipa-01.t.vda.li>2014-02-11 14:19:16 +0200
commitea3589f41b9db2ddb7bea3a69f5e1b2d285f5173 (patch)
tree80a69b4f66634837ec264301ad457009e39554ea
parentab00dce3f50042062f7171c6a6ab5ea8f494790f (diff)
downloadfreeipa-ea3589f41b9db2ddb7bea3a69f5e1b2d285f5173.tar.gz
freeipa-ea3589f41b9db2ddb7bea3a69f5e1b2d285f5173.tar.xz
freeipa-ea3589f41b9db2ddb7bea3a69f5e1b2d285f5173.zip
Add HOTP support
-rw-r--r--API.txt10
-rw-r--r--VERSION2
-rw-r--r--daemons/ipa-slapi-plugins/libotp/libotp.c43
-rw-r--r--install/share/70ipaotp.ldif2
-rw-r--r--ipalib/plugins/otptoken.py26
5 files changed, 64 insertions, 19 deletions
diff --git a/API.txt b/API.txt
index a6c3aed82..e2f5e036e 100644
--- a/API.txt
+++ b/API.txt
@@ -2220,12 +2220,13 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Output('value', <type 'unicode'>, None)
command: otptoken_add
-args: 1,20,3
+args: 1,21,3
arg: Str('ipatokenuniqueid', attribute=True, cli_name='id', multivalue=False, primary_key=True, required=False)
option: Str('addattr*', cli_name='addattr', exclude='webui')
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False)
option: Bool('ipatokendisabled', attribute=True, cli_name='disabled', multivalue=False, required=False)
+option: Int('ipatokenhotpcounter', attribute=True, cli_name='counter', minvalue=0, multivalue=False, required=False)
option: Str('ipatokenmodel', attribute=True, cli_name='model', multivalue=False, required=False)
option: Str('ipatokennotafter', attribute=True, cli_name='not_after', multivalue=False, required=False)
option: Str('ipatokennotbefore', attribute=True, cli_name='not_before', multivalue=False, required=False)
@@ -2240,7 +2241,7 @@ option: Str('ipatokenvendor', attribute=True, cli_name='vendor', multivalue=Fals
option: Flag('qrcode?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Str('setattr*', cli_name='setattr', exclude='webui')
-option: StrEnum('type', attribute=False, cli_name='type', multivalue=False, required=False, values=(u'totp',))
+option: StrEnum('type', attribute=False, cli_name='type', multivalue=False, required=False, values=(u'totp', u'hotp'))
option: Str('version?', exclude='webui')
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
@@ -2254,11 +2255,12 @@ output: Output('result', <type 'dict'>, None)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
output: Output('value', <type 'unicode'>, None)
command: otptoken_find
-args: 1,20,4
+args: 1,21,4
arg: Str('criteria?', noextrawhitespace=False)
option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
option: Bool('ipatokendisabled', attribute=True, autofill=False, cli_name='disabled', multivalue=False, query=True, required=False)
+option: Int('ipatokenhotpcounter', attribute=True, autofill=False, cli_name='counter', minvalue=0, multivalue=False, query=True, required=False)
option: Str('ipatokenmodel', attribute=True, autofill=False, cli_name='model', multivalue=False, query=True, required=False)
option: Str('ipatokennotafter', attribute=True, autofill=False, cli_name='not_after', multivalue=False, query=True, required=False)
option: Str('ipatokennotbefore', attribute=True, autofill=False, cli_name='not_before', multivalue=False, query=True, required=False)
@@ -2274,7 +2276,7 @@ option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
option: Int('sizelimit?', autofill=False, minvalue=0)
option: Int('timelimit?', autofill=False, minvalue=0)
-option: StrEnum('type', attribute=False, autofill=False, cli_name='type', multivalue=False, query=True, required=False, values=(u'totp',))
+option: StrEnum('type', attribute=False, autofill=False, cli_name='type', multivalue=False, query=True, required=False, values=(u'totp', u'hotp'))
option: Str('version?', exclude='webui')
output: Output('count', <type 'int'>, None)
output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list of LDAP entries', domain='ipa', localedir=None))
diff --git a/VERSION b/VERSION
index 5ce16b522..3072bfaed 100644
--- a/VERSION
+++ b/VERSION
@@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=72
+IPA_API_VERSION_MINOR=73
diff --git a/daemons/ipa-slapi-plugins/libotp/libotp.c b/daemons/ipa-slapi-plugins/libotp/libotp.c
index 3fb298035..31cc5915a 100644
--- a/daemons/ipa-slapi-plugins/libotp/libotp.c
+++ b/daemons/ipa-slapi-plugins/libotp/libotp.c
@@ -46,14 +46,17 @@
#define TOKEN(s) "ipaToken" s
#define O(s) TOKEN("OTP" s)
#define T(s) TOKEN("TOTP" s)
+#define H(s) TOKEN("HOTP" s)
#define IPA_OTP_DEFAULT_TOKEN_STEP 30
-#define IPA_OTP_OBJCLS_FILTER "(objectClass=ipaTokenTOTP)"
+#define IPA_OTP_OBJCLS_FILTER \
+ "(|(objectClass=ipaTokenTOTP)(objectClass=ipaTokenHOTP))"
enum otptoken_type {
OTPTOKEN_NONE = 0,
OTPTOKEN_TOTP,
+ OTPTOKEN_HOTP,
};
struct otptoken {
@@ -61,10 +64,15 @@ struct otptoken {
Slapi_DN *sdn;
struct hotp_token token;
enum otptoken_type type;
- struct {
- unsigned int step;
- int offset;
- } totp;
+ union {
+ struct {
+ unsigned int step;
+ int offset;
+ } totp;
+ struct {
+ uint64_t counter;
+ } hotp;
+ };
};
static const char *get_basedn(Slapi_DN *dn)
@@ -124,6 +132,9 @@ static bool validate(struct otptoken *token, time_t now, ssize_t step,
case OTPTOKEN_TOTP:
step = (now + token->totp.offset) / token->totp.step + step;
break;
+ case OTPTOKEN_HOTP:
+ step = token->hotp.counter + step;
+ break;
default:
return false;
}
@@ -160,6 +171,13 @@ static bool writeback(struct otptoken *token, ssize_t step, bool sync)
attr = T("clockOffset");
value = token->totp.offset + step * token->totp.step;
break;
+ case OTPTOKEN_HOTP:
+ /* Having support for LDAP_MOD_INCREMENT could be helpful here. */
+ if (step < 0)
+ return false; /* NEVER go backwards! */
+ attr = H("counter");
+ value = token->hotp.counter + step;
+ break;
default:
return false;
}
@@ -190,6 +208,9 @@ static bool writeback(struct otptoken *token, ssize_t step, bool sync)
case OTPTOKEN_TOTP:
token->totp.offset = value;
break;
+ case OTPTOKEN_HOTP:
+ token->hotp.counter = value;
+ break;
default:
break;
}
@@ -243,6 +264,8 @@ static struct otptoken *otptoken_new(Slapi_ComponentId *id, Slapi_Entry *entry)
for (int i = 0; vals[i] != NULL; i++) {
if (strcasecmp(vals[i], "ipaTokenTOTP") == 0)
token->type = OTPTOKEN_TOTP;
+ else if (strcasecmp(vals[i], "ipaTokenHOTP") == 0)
+ token->type = OTPTOKEN_HOTP;
}
slapi_ch_array_free(vals);
if (token->type == OTPTOKEN_NONE)
@@ -285,6 +308,10 @@ static struct otptoken *otptoken_new(Slapi_ComponentId *id, Slapi_Entry *entry)
if (token->totp.step == 0)
token->totp.step = IPA_OTP_DEFAULT_TOKEN_STEP;
break;
+ case OTPTOKEN_HOTP:
+ /* Get counter. */
+ token->hotp.counter = slapi_entry_attr_get_int(entry, H("counter"));
+ break;
default:
break;
}
@@ -433,7 +460,8 @@ bool otptoken_validate(struct otptoken *token, size_t steps, uint32_t code)
if (validate(token, now, i, code, NULL))
return writeback(token, i + 1, false);
- if (i == 0)
+ /* Counter-based tokens must NEVER validate old steps! */
+ if (i == 0 || token->type == OTPTOKEN_HOTP)
continue;
/* Validate the negative step. */
@@ -497,7 +525,8 @@ bool otptoken_sync(struct otptoken * const *tokens, size_t steps,
if (validate(tokens[j], now, i, first_code, &second_code))
return writeback(tokens[j], i + 2, true);
- if (i == 0)
+ /* Counter-based tokens must NEVER validate old steps! */
+ if (i == 0 || tokens[j]->type == OTPTOKEN_HOTP)
continue;
/* Validate the negative step. */
diff --git a/install/share/70ipaotp.ldif b/install/share/70ipaotp.ldif
index d257a46c3..620c2ccde 100644
--- a/install/share/70ipaotp.ldif
+++ b/install/share/70ipaotp.ldif
@@ -22,7 +22,9 @@ attributeTypes: (2.16.840.1.113730.3.8.16.1.17 NAME 'ipatokenRadiusSecret' DESC
attributeTypes: (2.16.840.1.113730.3.8.16.1.18 NAME 'ipatokenRadiusTimeout' DESC 'Server Timeout' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
attributeTypes: (2.16.840.1.113730.3.8.16.1.19 NAME 'ipatokenRadiusRetries' DESC 'Number of allowed Retries' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
attributeTypes: (2.16.840.1.113730.3.8.16.1.20 NAME 'ipatokenUserMapAttribute' DESC 'Attribute to map from the user entry for RADIUS server authentication' EQUALITY caseIgnoreMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN 'IPA OTP')
+attributeTypes: (2.16.840.1.113730.3.8.16.1.21 NAME 'ipatokenHOTPcounter' DESC 'HOTP counter' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.1 NAME 'ipaToken' SUP top ABSTRACT DESC 'Abstract token class for tokens' MUST (ipatokenUniqueID) MAY (description $ ipatokenOwner $ ipatokenDisabled $ ipatokenNotBefore $ ipatokenNotAfter $ ipatokenVendor $ ipatokenModel $ ipatokenSerial) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.2 NAME 'ipatokenTOTP' SUP ipaToken STRUCTURAL DESC 'TOTP Token Type' MAY (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenTOTPclockOffset $ ipatokenTOTPtimeStep) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.3 NAME 'ipatokenRadiusProxyUser' SUP top AUXILIARY DESC 'Radius Proxy User' MAY (ipatokenRadiusConfigLink $ ipatokenRadiusUserName) X-ORIGIN 'IPA OTP')
objectClasses: (2.16.840.1.113730.3.8.16.2.4 NAME 'ipatokenRadiusConfiguration' SUP top STRUCTURAL DESC 'Proxy Radius Configuration' MUST (cn $ ipatokenRadiusServer $ ipatokenRadiusSecret) MAY (description $ ipatokenRadiusTimeout $ ipatokenRadiusRetries $ ipatokenUserMapAttribute) X-ORIGIN 'IPA OTP')
+objectClasses: (2.16.840.1.113730.3.8.16.2.5 NAME 'ipatokenHOTP' SUP ipaToken STRUCTURAL DESC 'HOTP Token Type' MAY (ipatokenOTPkey $ ipatokenOTPalgorithm $ ipatokenOTPdigits $ ipatokenHOTPcounter) X-ORIGIN 'IPA OTP')
diff --git a/ipalib/plugins/otptoken.py b/ipalib/plugins/otptoken.py
index 67f248595..c7c0fe71f 100644
--- a/ipalib/plugins/otptoken.py
+++ b/ipalib/plugins/otptoken.py
@@ -53,7 +53,7 @@ EXAMPLES:
register = Registry()
-TOKEN_TYPES = (u'totp',)
+TOKEN_TYPES = (u'totp', u'hotp')
# NOTE: For maximum compatibility, KEY_LENGTH % 5 == 0
KEY_LENGTH = 10
@@ -102,7 +102,7 @@ class otptoken(LDAPObject):
object_name = _('OTP tokens')
object_name_plural = _('OTP tokens')
object_class = ['ipatoken']
- possible_objectclasses = ['ipatokentotp']
+ possible_objectclasses = ['ipatokentotp', 'ipatokenhotp']
default_attributes = [
'ipatokenuniqueid', 'description', 'ipatokenowner',
'ipatokendisabled', 'ipatokennotbefore', 'ipatokennotafter',
@@ -185,6 +185,12 @@ class otptoken(LDAPObject):
minvalue=5,
flags=('no_update'),
),
+ Int('ipatokenhotpcounter?',
+ cli_name='counter',
+ label=_('Counter'),
+ minvalue=0,
+ flags=('no_update'),
+ ),
)
@@ -213,14 +219,17 @@ class otptoken_add(LDAPCreate):
entry_attrs.setdefault('ipatokenserial', entry_attrs['ipatokenuniqueid'])
entry_attrs.setdefault('ipatokenotpalgorithm', u'sha1')
entry_attrs.setdefault('ipatokenotpdigits', 6)
- entry_attrs.setdefault('ipatokentotpclockoffset', 0)
- entry_attrs.setdefault('ipatokentotptimestep', 30)
entry_attrs.setdefault('ipatokenotpkey',
"".join(map(chr, random.SystemRandom().sample(range(255), KEY_LENGTH))))
- # Set the object class
+ # Set the object class and defaults for specific token types
if options['type'] == 'totp':
entry_attrs['objectclass'] = otptoken.object_class + ['ipatokentotp']
+ entry_attrs.setdefault('ipatokentotpclockoffset', 0)
+ entry_attrs.setdefault('ipatokentotptimestep', 30)
+ elif options['type'] == 'hotp':
+ entry_attrs['objectclass'] = otptoken.object_class + ['ipatokenhotp']
+ entry_attrs.setdefault('ipatokenhotpcounter', 0)
# Resolve the user's dn
_normalize_owner(self.api.Object.user, entry_attrs)
@@ -239,13 +248,16 @@ class otptoken_add(LDAPCreate):
args['issuer'] = issuer
args['secret'] = base64.b32encode(entry_attrs['ipatokenotpkey'])
args['digits'] = entry_attrs['ipatokenotpdigits']
- args['period'] = entry_attrs['ipatokentotptimestep']
args['algorithm'] = entry_attrs['ipatokenotpalgorithm']
+ if options['type'] == 'totp':
+ args['period'] = entry_attrs['ipatokentotptimestep']
+ elif options['type'] == 'hotp':
+ args['counter'] = entry_attrs['ipatokenhotpcounter']
# Build the URI
label = urllib.quote(entry_attrs['ipatokenuniqueid'])
parameters = urllib.urlencode(args)
- uri = u'otpauth://totp/%s:%s?%s' % (issuer, label, parameters)
+ uri = u'otpauth://%s/%s:%s?%s' % (options['type'], issuer, label, parameters)
setattr(context, 'uri', uri)
return dn