1 /* $NetBSD: otp.c,v 1.2 2021/08/14 16:15:02 christos Exp $ */
2
3 /* otp.c - OATH 2-factor authentication module */
4 /* $OpenLDAP$ */
5 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 *
7 * Copyright 2015-2021 The OpenLDAP Foundation.
8 * Portions Copyright 2015 by Howard Chu, Symas Corp.
9 * Portions Copyright 2016-2017 by Michael Ströder <michael@stroeder.com>
10 * Portions Copyright 2018 by Ondřej Kuzník, Symas Corp.
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted only as authorized by the OpenLDAP
15 * Public License.
16 *
17 * A copy of this license is available in the file LICENSE in the
18 * top-level directory of the distribution or, alternatively, at
19 * <http://www.OpenLDAP.org/license.html>.
20 */
21 /* ACKNOWLEDGEMENTS:
22 * This work includes code from the lastbind overlay.
23 */
24
25 #include <portable.h>
26
27 #ifdef SLAPD_OVER_OTP
28
29 #if HAVE_STDINT_H
30 #include <stdint.h>
31 #endif
32
33 #include <lber.h>
34 #include <lber_pvt.h>
35 #include "lutil.h"
36 #include <ac/stdlib.h>
37 #include <ac/ctype.h>
38 #include <ac/string.h>
39 /* include socket.h to get sys/types.h and/or winsock2.h */
40 #include <ac/socket.h>
41
42 #if HAVE_OPENSSL
43 #include <openssl/sha.h>
44 #include <openssl/hmac.h>
45
46 #define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_LENGTH
47 #define TOTP_SHA1 EVP_sha1()
48 #define TOTP_SHA224 EVP_sha224()
49 #define TOTP_SHA256 EVP_sha256()
50 #define TOTP_SHA384 EVP_sha384()
51 #define TOTP_SHA512 EVP_sha512()
52 #define TOTP_HMAC_CTX HMAC_CTX *
53
54 #if OPENSSL_VERSION_NUMBER < 0x10100000L
55 static HMAC_CTX *
HMAC_CTX_new(void)56 HMAC_CTX_new( void )
57 {
58 HMAC_CTX *ctx = OPENSSL_malloc( sizeof(*ctx) );
59 if ( ctx != NULL ) {
60 HMAC_CTX_init( ctx );
61 }
62 return ctx;
63 }
64
65 static void
HMAC_CTX_free(HMAC_CTX * ctx)66 HMAC_CTX_free( HMAC_CTX *ctx )
67 {
68 if ( ctx != NULL ) {
69 HMAC_CTX_cleanup( ctx );
70 OPENSSL_free( ctx );
71 }
72 }
73 #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
74
75 #define HMAC_setup( ctx, key, len, hash ) \
76 ctx = HMAC_CTX_new(); \
77 HMAC_Init_ex( ctx, key, len, hash, 0 )
78 #define HMAC_crunch( ctx, buf, len ) HMAC_Update( ctx, buf, len )
79 #define HMAC_finish( ctx, dig, dlen ) \
80 HMAC_Final( ctx, dig, &dlen ); \
81 HMAC_CTX_free( ctx )
82
83 #elif HAVE_GNUTLS
84 #include <nettle/hmac.h>
85
86 #define TOTP_SHA512_DIGEST_LENGTH SHA512_DIGEST_SIZE
87 #define TOTP_SHA1 &nettle_sha1
88 #define TOTP_SHA224 &nettle_sha224
89 #define TOTP_SHA256 &nettle_sha256
90 #define TOTP_SHA384 &nettle_sha384
91 #define TOTP_SHA512 &nettle_sha512
92 #define TOTP_HMAC_CTX struct hmac_sha512_ctx
93
94 #define HMAC_setup( ctx, key, len, hash ) \
95 const struct nettle_hash *h = hash; \
96 hmac_set_key( &ctx.outer, &ctx.inner, &ctx.state, h, len, key )
97 #define HMAC_crunch( ctx, buf, len ) hmac_update( &ctx.state, h, len, buf )
98 #define HMAC_finish( ctx, dig, dlen ) \
99 hmac_digest( &ctx.outer, &ctx.inner, &ctx.state, h, h->digest_size, dig ); \
100 dlen = h->digest_size
101
102 #else
103 #error Unsupported crypto backend.
104 #endif
105
106 #include "slap.h"
107 #include "slap-config.h"
108
109 /* Schema from OATH-LDAP project by Michael Ströder */
110
111 static struct {
112 char *name, *oid;
113 } otp_oid[] = {
114 { "oath-ldap", "1.3.6.1.4.1.5427.1.389.4226" },
115 { "oath-ldap-at", "oath-ldap:4" },
116 { "oath-ldap-oc", "oath-ldap:6" },
117 { NULL }
118 };
119
120 AttributeDescription *ad_oathOTPToken;
121 AttributeDescription *ad_oathSecret;
122 AttributeDescription *ad_oathOTPLength;
123 AttributeDescription *ad_oathHMACAlgorithm;
124
125 AttributeDescription *ad_oathHOTPParams;
126 AttributeDescription *ad_oathHOTPToken;
127 AttributeDescription *ad_oathHOTPCounter;
128 AttributeDescription *ad_oathHOTPLookahead;
129
130 AttributeDescription *ad_oathTOTPTimeStepPeriod;
131 AttributeDescription *ad_oathTOTPParams;
132 AttributeDescription *ad_oathTOTPToken;
133 AttributeDescription *ad_oathTOTPLastTimeStep;
134 AttributeDescription *ad_oathTOTPTimeStepWindow;
135 AttributeDescription *ad_oathTOTPTimeStepDrift;
136
137 static struct otp_at {
138 char *schema;
139 AttributeDescription **adp;
140 } otp_at[] = {
141 { "( oath-ldap-at:1 "
142 "NAME 'oathSecret' "
143 "DESC 'OATH-LDAP: Shared Secret (possibly encrypted with public key in oathEncKey)' "
144 "X-ORIGIN 'OATH-LDAP' "
145 "SINGLE-VALUE "
146 "EQUALITY octetStringMatch "
147 "SUBSTR octetStringSubstringsMatch "
148 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )",
149 &ad_oathSecret },
150
151 { "( oath-ldap-at:2 "
152 "NAME 'oathTokenSerialNumber' "
153 "DESC 'OATH-LDAP: Proprietary hardware token serial number assigned by vendor' "
154 "X-ORIGIN 'OATH-LDAP' "
155 "SINGLE-VALUE "
156 "EQUALITY caseIgnoreMatch "
157 "SUBSTR caseIgnoreSubstringsMatch "
158 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.44{64})" },
159
160 { "( oath-ldap-at:3 "
161 "NAME 'oathTokenIdentifier' "
162 "DESC 'OATH-LDAP: Globally unique OATH token identifier' "
163 "X-ORIGIN 'OATH-LDAP' "
164 "SINGLE-VALUE "
165 "EQUALITY caseIgnoreMatch "
166 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )" },
167
168 { "( oath-ldap-at:4 "
169 "NAME 'oathParamsEntry' "
170 "DESC 'OATH-LDAP: DN pointing to OATH parameter/policy object' "
171 "X-ORIGIN 'OATH-LDAP' "
172 "SINGLE-VALUE "
173 "SUP distinguishedName )" },
174 { "( oath-ldap-at:4.1 "
175 "NAME 'oathTOTPTimeStepPeriod' "
176 "DESC 'OATH-LDAP: Time window for TOTP (seconds)' "
177 "X-ORIGIN 'OATH-LDAP' "
178 "SINGLE-VALUE "
179 "EQUALITY integerMatch "
180 "ORDERING integerOrderingMatch "
181 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
182 &ad_oathTOTPTimeStepPeriod },
183
184 { "( oath-ldap-at:5 "
185 "NAME 'oathOTPLength' "
186 "DESC 'OATH-LDAP: Length of OTP (number of digits)' "
187 "X-ORIGIN 'OATH-LDAP' "
188 "SINGLE-VALUE "
189 "EQUALITY integerMatch "
190 "ORDERING integerOrderingMatch "
191 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )",
192 &ad_oathOTPLength },
193 { "( oath-ldap-at:5.1 "
194 "NAME 'oathHOTPParams' "
195 "DESC 'OATH-LDAP: DN pointing to HOTP parameter object' "
196 "X-ORIGIN 'OATH-LDAP' "
197 "SINGLE-VALUE "
198 "SUP oathParamsEntry )",
199 &ad_oathHOTPParams },
200 { "( oath-ldap-at:5.2 "
201 "NAME 'oathTOTPParams' "
202 "DESC 'OATH-LDAP: DN pointing to TOTP parameter object' "
203 "X-ORIGIN 'OATH-LDAP' "
204 "SINGLE-VALUE "
205 "SUP oathParamsEntry )",
206 &ad_oathTOTPParams },
207
208 { "( oath-ldap-at:6 "
209 "NAME 'oathHMACAlgorithm' "
210 "DESC 'OATH-LDAP: HMAC algorithm used for generating OTP values' "
211 "X-ORIGIN 'OATH-LDAP' "
212 "SINGLE-VALUE "
213 "EQUALITY objectIdentifierMatch "
214 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 )",
215 &ad_oathHMACAlgorithm },
216
217 { "( oath-ldap-at:7 "
218 "NAME 'oathTimestamp' "
219 "DESC 'OATH-LDAP: Timestamp (not directly used).' "
220 "X-ORIGIN 'OATH-LDAP' "
221 "SINGLE-VALUE "
222 "EQUALITY generalizedTimeMatch "
223 "ORDERING generalizedTimeOrderingMatch "
224 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 )" },
225 { "( oath-ldap-at:7.1 "
226 "NAME 'oathLastFailure' "
227 "DESC 'OATH-LDAP: Timestamp of last failed OATH validation' "
228 "X-ORIGIN 'OATH-LDAP' "
229 "SINGLE-VALUE "
230 "SUP oathTimestamp )" },
231 { "( oath-ldap-at:7.2 "
232 "NAME 'oathLastLogin' "
233 "DESC 'OATH-LDAP: Timestamp of last successful OATH validation' "
234 "X-ORIGIN 'OATH-LDAP' "
235 "SINGLE-VALUE "
236 "SUP oathTimestamp )" },
237 { "( oath-ldap-at:7.3 "
238 "NAME 'oathSecretTime' "
239 "DESC 'OATH-LDAP: Timestamp of generation of oathSecret attribute.' "
240 "X-ORIGIN 'OATH-LDAP' "
241 "SINGLE-VALUE "
242 "SUP oathTimestamp )" },
243
244 { "( oath-ldap-at:8 "
245 "NAME 'oathSecretMaxAge' "
246 "DESC 'OATH-LDAP: Time in seconds for which the shared secret (oathSecret) will be valid from oathSecretTime value.' "
247 "X-ORIGIN 'OATH-LDAP' "
248 "SINGLE-VALUE "
249 "EQUALITY integerMatch "
250 "ORDERING integerOrderingMatch "
251 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
252
253 { "( oath-ldap-at:9 "
254 "NAME 'oathToken' "
255 "DESC 'OATH-LDAP: DN pointing to OATH token object' "
256 "X-ORIGIN 'OATH-LDAP' "
257 "SINGLE-VALUE "
258 "SUP distinguishedName )" },
259 { "( oath-ldap-at:9.1 "
260 "NAME 'oathHOTPToken' "
261 "DESC 'OATH-LDAP: DN pointing to OATH/HOTP token object' "
262 "X-ORIGIN 'OATH-LDAP' "
263 "SINGLE-VALUE "
264 "SUP oathToken )",
265 &ad_oathHOTPToken },
266 { "( oath-ldap-at:9.2 "
267 "NAME 'oathTOTPToken' "
268 "DESC 'OATH-LDAP: DN pointing to OATH/TOTP token object' "
269 "X-ORIGIN 'OATH-LDAP' "
270 "SINGLE-VALUE "
271 "SUP oathToken )",
272 &ad_oathTOTPToken },
273
274 { "( oath-ldap-at:10 "
275 "NAME 'oathCounter' "
276 "DESC 'OATH-LDAP: Counter for OATH data (not directly used)' "
277 "X-ORIGIN 'OATH-LDAP' "
278 "SINGLE-VALUE "
279 "EQUALITY integerMatch "
280 "ORDERING integerOrderingMatch "
281 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
282 { "( oath-ldap-at:10.1 "
283 "NAME 'oathFailureCount' "
284 "DESC 'OATH-LDAP: OATH failure counter' "
285 "X-ORIGIN 'OATH-LDAP' "
286 "SINGLE-VALUE "
287 "SUP oathCounter )" },
288 { "( oath-ldap-at:10.2 "
289 "NAME 'oathHOTPCounter' "
290 "DESC 'OATH-LDAP: Counter for HOTP' "
291 "X-ORIGIN 'OATH-LDAP' "
292 "SINGLE-VALUE "
293 "SUP oathCounter )",
294 &ad_oathHOTPCounter },
295 { "( oath-ldap-at:10.3 "
296 "NAME 'oathHOTPLookAhead' "
297 "DESC 'OATH-LDAP: Look-ahead window for HOTP' "
298 "X-ORIGIN 'OATH-LDAP' "
299 "SINGLE-VALUE "
300 "SUP oathCounter )",
301 &ad_oathHOTPLookahead },
302 { "( oath-ldap-at:10.5 "
303 "NAME 'oathThrottleLimit' "
304 "DESC 'OATH-LDAP: Failure throttle limit' "
305 "X-ORIGIN 'OATH-LDAP' "
306 "SINGLE-VALUE "
307 "SUP oathCounter )" },
308 { "( oath-ldap-at:10.6 "
309 "NAME 'oathTOTPLastTimeStep' "
310 "DESC 'OATH-LDAP: Last time step seen for TOTP (time/period)' "
311 "X-ORIGIN 'OATH-LDAP' "
312 "SINGLE-VALUE "
313 "SUP oathCounter )",
314 &ad_oathTOTPLastTimeStep },
315 { "( oath-ldap-at:10.7 "
316 "NAME 'oathMaxUsageCount' "
317 "DESC 'OATH-LDAP: Maximum number of times a token can be used' "
318 "X-ORIGIN 'OATH-LDAP' "
319 "SINGLE-VALUE "
320 "SUP oathCounter )" },
321 { "( oath-ldap-at:10.8 "
322 "NAME 'oathTOTPTimeStepWindow' "
323 "DESC 'OATH-LDAP: Size of time step +/- tolerance window used for TOTP validation' "
324 "X-ORIGIN 'OATH-LDAP' "
325 "SINGLE-VALUE "
326 "SUP oathCounter )",
327 &ad_oathTOTPTimeStepWindow },
328 { "( oath-ldap-at:10.9 "
329 "NAME 'oathTOTPTimeStepDrift' "
330 "DESC 'OATH-LDAP: Last observed time step shift seen for TOTP' "
331 "X-ORIGIN 'OATH-LDAP' "
332 "SINGLE-VALUE "
333 "SUP oathCounter )",
334 &ad_oathTOTPTimeStepDrift },
335
336 { "( oath-ldap-at:11 "
337 "NAME 'oathSecretLength' "
338 "DESC 'OATH-LDAP: Length of plain-text shared secret (number of bytes)' "
339 "X-ORIGIN 'OATH-LDAP' "
340 "SINGLE-VALUE "
341 "EQUALITY integerMatch "
342 "ORDERING integerOrderingMatch "
343 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
344
345 { "( oath-ldap-at:12 "
346 "NAME 'oathEncKey' "
347 "DESC 'OATH-LDAP: public key to be used for encrypting new shared secrets' "
348 "X-ORIGIN 'OATH-LDAP' "
349 "SINGLE-VALUE "
350 "EQUALITY caseIgnoreMatch "
351 "SUBSTR caseIgnoreSubstringsMatch "
352 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" },
353
354 { "( oath-ldap-at:13 "
355 "NAME 'oathResultCode' "
356 "DESC 'OATH-LDAP: LDAP resultCode to use in response' "
357 "X-ORIGIN 'OATH-LDAP' "
358 "SINGLE-VALUE "
359 "EQUALITY integerMatch "
360 "ORDERING integerOrderingMatch "
361 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )" },
362 { "( oath-ldap-at:13.1 "
363 "NAME 'oathSuccessResultCode' "
364 "DESC 'OATH-LDAP: success resultCode to use in bind/compare response' "
365 "X-ORIGIN 'OATH-LDAP' "
366 "SUP oathResultCode )" },
367 { "( oath-ldap-at:13.2 "
368 "NAME 'oathFailureResultCode' "
369 "DESC 'OATH-LDAP: failure resultCode to use in bind/compare response' "
370 "X-ORIGIN 'OATH-LDAP' "
371 "SUP oathResultCode )" },
372
373 { "( oath-ldap-at:14 "
374 "NAME 'oathTokenPIN' "
375 "DESC 'OATH-LDAP: Configuration PIN (possibly encrypted with oathEncKey)' "
376 "X-ORIGIN 'OATH-LDAP' "
377 "SINGLE-VALUE "
378 "EQUALITY caseIgnoreMatch "
379 "SUBSTR caseIgnoreSubstringsMatch "
380 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )" },
381
382 { "( oath-ldap-at:15 "
383 "NAME 'oathMessage' "
384 "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' "
385 "X-ORIGIN 'OATH-LDAP' "
386 "SINGLE-VALUE "
387 "EQUALITY caseIgnoreMatch "
388 "SUBSTR caseIgnoreSubstringsMatch "
389 "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1024} )" },
390 { "( oath-ldap-at:15.1 "
391 "NAME 'oathSuccessMessage' "
392 "DESC 'OATH-LDAP: success diagnosticMessage to use in bind/compare response' "
393 "X-ORIGIN 'OATH-LDAP' "
394 "SUP oathMessage )" },
395 { "( oath-ldap-at:15.2 "
396 "NAME 'oathFailureMessage' "
397 "DESC 'OATH-LDAP: failure diagnosticMessage to use in bind/compare response' "
398 "X-ORIGIN 'OATH-LDAP' "
399 "SUP oathMessage )" },
400
401 { NULL }
402 };
403
404 ObjectClass *oc_oathOTPUser;
405 ObjectClass *oc_oathHOTPToken;
406 ObjectClass *oc_oathTOTPToken;
407 ObjectClass *oc_oathHOTPParams;
408 ObjectClass *oc_oathTOTPParams;
409
410 static struct otp_oc {
411 char *schema;
412 ObjectClass **ocp;
413 } otp_oc[] = {
414 { "( oath-ldap-oc:1 "
415 "NAME 'oathUser' "
416 "DESC 'OATH-LDAP: User Object' "
417 "X-ORIGIN 'OATH-LDAP' "
418 "ABSTRACT )",
419 &oc_oathOTPUser },
420 { "( oath-ldap-oc:1.1 "
421 "NAME 'oathHOTPUser' "
422 "DESC 'OATH-LDAP: HOTP user object' "
423 "X-ORIGIN 'OATH-LDAP' "
424 "AUXILIARY "
425 "SUP oathUser "
426 "MAY ( oathHOTPToken ) )" },
427 { "( oath-ldap-oc:1.2 "
428 "NAME 'oathTOTPUser' "
429 "DESC 'OATH-LDAP: TOTP user object' "
430 "X-ORIGIN 'OATH-LDAP' "
431 "AUXILIARY "
432 "SUP oathUser "
433 "MUST ( oathTOTPToken ) )" },
434 { "( oath-ldap-oc:2 "
435 "NAME 'oathParams' "
436 "DESC 'OATH-LDAP: Parameter object' "
437 "X-ORIGIN 'OATH-LDAP' "
438 "ABSTRACT "
439 "MUST ( oathOTPLength $ oathHMACAlgorithm ) "
440 "MAY ( oathSecretMaxAge $ oathSecretLength $ "
441 "oathMaxUsageCount $ oathThrottleLimit $ oathEncKey $ "
442 "oathSuccessResultCode $ oathSuccessMessage $ "
443 "oathFailureResultCode $ oathFailureMessage ) )" },
444 { "( oath-ldap-oc:2.1 "
445 "NAME 'oathHOTPParams' "
446 "DESC 'OATH-LDAP: HOTP parameter object' "
447 "X-ORIGIN 'OATH-LDAP' "
448 "AUXILIARY "
449 "SUP oathParams "
450 "MUST ( oathHOTPLookAhead ) )",
451 &oc_oathHOTPParams },
452 { "( oath-ldap-oc:2.2 "
453 "NAME 'oathTOTPParams' "
454 "DESC 'OATH-LDAP: TOTP parameter object' "
455 "X-ORIGIN 'OATH-LDAP' "
456 "AUXILIARY "
457 "SUP oathParams "
458 "MUST ( oathTOTPTimeStepPeriod ) "
459 "MAY ( oathTOTPTimeStepWindow ) )",
460 &oc_oathTOTPParams },
461 { "( oath-ldap-oc:3 "
462 "NAME 'oathToken' "
463 "DESC 'OATH-LDAP: User Object' "
464 "X-ORIGIN 'OATH-LDAP' "
465 "ABSTRACT "
466 "MAY ( oathSecret $ oathSecretTime $ "
467 "oathLastLogin $ oathFailureCount $ oathLastFailure $ "
468 "oathTokenSerialNumber $ oathTokenIdentifier $ oathTokenPIN ) )" },
469 { "( oath-ldap-oc:3.1 "
470 "NAME 'oathHOTPToken' "
471 "DESC 'OATH-LDAP: HOTP token object' "
472 "X-ORIGIN 'OATH-LDAP' "
473 "AUXILIARY "
474 "SUP oathToken "
475 "MAY ( oathHOTPParams $ oathHOTPCounter ) )",
476 &oc_oathHOTPToken },
477 { "( oath-ldap-oc:3.2 "
478 "NAME 'oathTOTPToken' "
479 "DESC 'OATH-LDAP: TOTP token' "
480 "X-ORIGIN 'OATH-LDAP' "
481 "AUXILIARY "
482 "SUP oathToken "
483 "MAY ( oathTOTPParams $ oathTOTPLastTimeStep $ oathTOTPTimeStepDrift ) )",
484 &oc_oathTOTPToken },
485 { NULL }
486 };
487
488 typedef struct myval {
489 ber_len_t mv_len;
490 void *mv_val;
491 } myval;
492
493 static void
do_hmac(const void * hash,myval * key,myval * data,myval * out)494 do_hmac( const void *hash, myval *key, myval *data, myval *out )
495 {
496 TOTP_HMAC_CTX ctx;
497 unsigned int digestLen;
498
499 HMAC_setup( ctx, key->mv_val, key->mv_len, hash );
500 HMAC_crunch( ctx, data->mv_val, data->mv_len );
501 HMAC_finish( ctx, out->mv_val, digestLen );
502 out->mv_len = digestLen;
503 }
504
505 #define MAX_DIGITS 8
506 static const int DIGITS_POWER[] = {
507 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
508 };
509
510 static const void *
otp_choose_mech(struct berval * oid)511 otp_choose_mech( struct berval *oid )
512 {
513 /* RFC 8018 OIDs */
514 const struct berval oid_hmacwithSHA1 = BER_BVC("1.2.840.113549.2.7");
515 const struct berval oid_hmacwithSHA224 = BER_BVC("1.2.840.113549.2.8");
516 const struct berval oid_hmacwithSHA256 = BER_BVC("1.2.840.113549.2.9");
517 const struct berval oid_hmacwithSHA384 = BER_BVC("1.2.840.113549.2.10");
518 const struct berval oid_hmacwithSHA512 = BER_BVC("1.2.840.113549.2.11");
519
520 if ( !ber_bvcmp( &oid_hmacwithSHA1, oid ) ) {
521 return TOTP_SHA1;
522 } else if ( !ber_bvcmp( &oid_hmacwithSHA224, oid ) ) {
523 return TOTP_SHA224;
524 } else if ( !ber_bvcmp( &oid_hmacwithSHA256, oid ) ) {
525 return TOTP_SHA256;
526 } else if ( !ber_bvcmp( &oid_hmacwithSHA384, oid ) ) {
527 return TOTP_SHA384;
528 } else if ( !ber_bvcmp( &oid_hmacwithSHA512, oid ) ) {
529 return TOTP_SHA512;
530 }
531
532 Debug( LDAP_DEBUG_TRACE, "otp_choose_mech: "
533 "hmac OID %s unsupported\n",
534 oid->bv_val );
535 return NULL;
536 }
537
538 static void
generate(struct berval * bv,uint64_t tval,int digits,struct berval * out,const void * mech)539 generate(
540 struct berval *bv,
541 uint64_t tval,
542 int digits,
543 struct berval *out,
544 const void *mech )
545 {
546 unsigned char digest[TOTP_SHA512_DIGEST_LENGTH];
547 myval digval;
548 myval key, data;
549 unsigned char msg[8];
550 int i, offset, res, otp;
551
552 #if WORDS_BIGENDIAN
553 *(uint64_t *)msg = tval;
554 #else
555 for ( i = 7; i >= 0; i-- ) {
556 msg[i] = tval & 0xff;
557 tval >>= 8;
558 }
559 #endif
560
561 key.mv_len = bv->bv_len;
562 key.mv_val = bv->bv_val;
563
564 data.mv_val = msg;
565 data.mv_len = sizeof(msg);
566
567 digval.mv_val = digest;
568 digval.mv_len = sizeof(digest);
569 do_hmac( mech, &key, &data, &digval );
570
571 offset = digest[digval.mv_len - 1] & 0xf;
572 res = ( (digest[offset] & 0x7f) << 24 ) |
573 ( ( digest[offset + 1] & 0xff ) << 16 ) |
574 ( ( digest[offset + 2] & 0xff ) << 8 ) |
575 ( digest[offset + 3] & 0xff );
576
577 otp = res % DIGITS_POWER[digits];
578 out->bv_len = snprintf( out->bv_val, out->bv_len, "%0*d", digits, otp );
579 }
580
581 static int
otp_bind_response(Operation * op,SlapReply * rs)582 otp_bind_response( Operation *op, SlapReply *rs )
583 {
584 if ( rs->sr_err == LDAP_SUCCESS ) {
585 /* If the bind succeeded, return our result */
586 rs->sr_err = LDAP_INVALID_CREDENTIALS;
587 }
588 return SLAP_CB_CONTINUE;
589 }
590
591 static long
otp_hotp(Operation * op,Entry * token)592 otp_hotp( Operation *op, Entry *token )
593 {
594 char outbuf[MAX_DIGITS + 1];
595 Entry *params = NULL;
596 Attribute *a;
597 BerValue *secret, client_otp;
598 const void *mech;
599 long last_step = -1, found = -1;
600 int i, otp_len, window;
601
602 a = attr_find( token->e_attrs, ad_oathSecret );
603 secret = &a->a_vals[0];
604
605 a = attr_find( token->e_attrs, ad_oathHOTPCounter );
606 if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) {
607 Debug( LDAP_DEBUG_ANY, "otp_hotp: "
608 "could not parse oathHOTPCounter value %s\n",
609 a->a_vals[0].bv_val );
610 goto done;
611 }
612
613 a = attr_find( token->e_attrs, ad_oathHOTPParams );
614 if ( !a ||
615 be_entry_get_rw( op, &a->a_nvals[0], oc_oathHOTPParams, NULL, 0,
616 ¶ms ) ) {
617 goto done;
618 }
619
620 a = attr_find( params->e_attrs, ad_oathOTPLength );
621 if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) {
622 Debug( LDAP_DEBUG_ANY, "otp_hotp: "
623 "could not parse oathOTPLength value %s\n",
624 a->a_vals[0].bv_val );
625 goto done;
626 }
627 if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) {
628 /* Client didn't even send the token, fail immediately */
629 goto done;
630 }
631
632 a = attr_find( params->e_attrs, ad_oathHOTPLookahead );
633 if ( lutil_atoi( &window, a->a_vals[0].bv_val ) != 0 ) {
634 Debug( LDAP_DEBUG_ANY, "otp_hotp: "
635 "could not parse oathHOTPLookAhead value %s\n",
636 a->a_vals[0].bv_val );
637 goto done;
638 }
639 window++;
640
641 a = attr_find( params->e_attrs, ad_oathHMACAlgorithm );
642 if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) {
643 goto done;
644 }
645 be_entry_release_r( op, params );
646 params = NULL;
647
648 /* We are provided "password" + "OTP", split accordingly */
649 client_otp.bv_len = otp_len;
650 client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len;
651
652 /* If check succeeds, advance the step counter accordingly */
653 for ( i = 1; i <= window; i++ ) {
654 BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) };
655
656 generate( secret, last_step + i, otp_len, &out, mech );
657 if ( !ber_bvcmp( &out, &client_otp ) ) {
658 found = last_step + i;
659 /* Would we leak information if we stopped right now? */
660 }
661 }
662
663 if ( found >= 0 ) {
664 /* OTP check passed, trim the password */
665 op->orb_cred.bv_len -= otp_len;
666 Debug( LDAP_DEBUG_STATS, "%s HOTP token %s no. %ld redeemed\n",
667 op->o_log_prefix, token->e_name.bv_val, found );
668 }
669
670 done:
671 memset( outbuf, 0, sizeof(outbuf) );
672 if ( params ) {
673 be_entry_release_r( op, params );
674 }
675 return found;
676 }
677
678 static long
otp_totp(Operation * op,Entry * token,long * drift)679 otp_totp( Operation *op, Entry *token, long *drift )
680 {
681 char outbuf[MAX_DIGITS + 1];
682 Entry *params = NULL;
683 Attribute *a;
684 BerValue *secret, client_otp;
685 const void *mech;
686 long t, last_step = -1, found = -1, window = 0, old_drift;
687 int i, otp_len, time_step;
688
689 a = attr_find( token->e_attrs, ad_oathSecret );
690 secret = &a->a_vals[0];
691
692 a = attr_find( token->e_attrs, ad_oathTOTPLastTimeStep );
693 if ( a && lutil_atol( &last_step, a->a_vals[0].bv_val ) != 0 ) {
694 Debug( LDAP_DEBUG_ANY, "otp_totp: "
695 "could not parse oathTOTPLastTimeStep value %s\n",
696 a->a_vals[0].bv_val );
697 goto done;
698 }
699
700 a = attr_find( token->e_attrs, ad_oathTOTPParams );
701 if ( !a ||
702 be_entry_get_rw( op, &a->a_nvals[0], oc_oathTOTPParams, NULL, 0,
703 ¶ms ) ) {
704 goto done;
705 }
706
707 a = attr_find( params->e_attrs, ad_oathTOTPTimeStepPeriod );
708 if ( lutil_atoi( &time_step, a->a_vals[0].bv_val ) != 0 ) {
709 Debug( LDAP_DEBUG_ANY, "otp_totp: "
710 "could not parse oathTOTPTimeStepPeriod value %s\n",
711 a->a_vals[0].bv_val );
712 goto done;
713 }
714
715 a = attr_find( params->e_attrs, ad_oathTOTPTimeStepWindow );
716 if ( a && lutil_atol( &window, a->a_vals[0].bv_val ) != 0 ) {
717 Debug( LDAP_DEBUG_ANY, "otp_totp: "
718 "could not parse oathTOTPTimeStepWindow value %s\n",
719 a->a_vals[0].bv_val );
720 goto done;
721 }
722
723 a = attr_find( params->e_attrs, ad_oathTOTPTimeStepDrift );
724 if ( a && lutil_atol( drift, a->a_vals[0].bv_val ) != 0 ) {
725 Debug( LDAP_DEBUG_ANY, "otp_totp: "
726 "could not parse oathTOTPTimeStepDrift value %s\n",
727 a->a_vals[0].bv_val );
728 goto done;
729 }
730 old_drift = *drift;
731 t = op->o_time / time_step + *drift;
732
733 a = attr_find( params->e_attrs, ad_oathOTPLength );
734 if ( lutil_atoi( &otp_len, a->a_vals[0].bv_val ) != 0 ) {
735 Debug( LDAP_DEBUG_ANY, "otp_totp: "
736 "could not parse oathOTPLength value %s\n",
737 a->a_vals[0].bv_val );
738 goto done;
739 }
740 if ( otp_len > MAX_DIGITS || op->orb_cred.bv_len < otp_len ) {
741 /* Client didn't even send the token, fail immediately */
742 goto done;
743 }
744
745 a = attr_find( params->e_attrs, ad_oathHMACAlgorithm );
746 if ( !(mech = otp_choose_mech( &a->a_vals[0] )) ) {
747 goto done;
748 }
749 be_entry_release_r( op, params );
750 params = NULL;
751
752 /* We are provided "password" + "OTP", split accordingly */
753 client_otp.bv_len = otp_len;
754 client_otp.bv_val = op->orb_cred.bv_val + op->orb_cred.bv_len - otp_len;
755
756 /* If check succeeds, advance the step counter accordingly */
757 /* Negation of A001057 series that enumerates all integers:
758 * (0, -1, 1, -2, 2, ...) */
759 for ( i = 0; i >= -window; i = ( i < 0 ) ? -i : ~i ) {
760 BerValue out = { .bv_val = outbuf, .bv_len = sizeof(outbuf) };
761
762 if ( t + i <= last_step ) continue;
763
764 generate( secret, t + i, otp_len, &out, mech );
765 if ( !ber_bvcmp( &out, &client_otp ) ) {
766 found = t + i;
767 *drift = old_drift + i;
768 /* Would we leak information if we stopped right now? */
769 }
770 }
771
772 /* OTP check passed, trim the password */
773 if ( found >= 0 ) {
774 assert( found > last_step );
775
776 op->orb_cred.bv_len -= otp_len;
777 Debug( LDAP_DEBUG_TRACE, "%s TOTP token %s redeemed with new drift of %ld\n",
778 op->o_log_prefix, token->e_name.bv_val, *drift );
779 }
780
781 done:
782 memset( outbuf, 0, sizeof(outbuf) );
783 if ( params ) {
784 be_entry_release_r( op, params );
785 }
786 return found;
787 }
788
789 static int
otp_op_bind(Operation * op,SlapReply * rs)790 otp_op_bind( Operation *op, SlapReply *rs )
791 {
792 slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
793 BerValue totpdn = BER_BVNULL, hotpdn = BER_BVNULL, ndn;
794 Entry *user = NULL, *token = NULL;
795 AttributeDescription *ad = NULL, *drift_ad = NULL;
796 Attribute *a;
797 long t = -1, drift = 0;
798 int rc = SLAP_CB_CONTINUE;
799
800 if ( op->oq_bind.rb_method != LDAP_AUTH_SIMPLE ) {
801 return rc;
802 }
803
804 op->o_bd->bd_info = (BackendInfo *)on->on_info;
805
806 if ( be_entry_get_rw( op, &op->o_req_ndn, NULL, NULL, 0, &user ) ) {
807 goto done;
808 }
809
810 if ( !is_entry_objectclass_or_sub( user, oc_oathOTPUser ) ) {
811 be_entry_release_r( op, user );
812 goto done;
813 }
814
815 if ( (a = attr_find( user->e_attrs, ad_oathTOTPToken )) ) {
816 ber_dupbv_x( &totpdn, &a->a_nvals[0], op->o_tmpmemctx );
817 }
818
819 if ( (a = attr_find( user->e_attrs, ad_oathHOTPToken )) ) {
820 ber_dupbv_x( &hotpdn, &a->a_nvals[0], op->o_tmpmemctx );
821 }
822 be_entry_release_r( op, user );
823
824 if ( !BER_BVISNULL( &totpdn ) &&
825 be_entry_get_rw( op, &totpdn, oc_oathTOTPToken, ad_oathSecret, 0,
826 &token ) == LDAP_SUCCESS ) {
827 ndn = totpdn;
828 ad = ad_oathTOTPLastTimeStep;
829 drift_ad = ad_oathTOTPTimeStepDrift;
830 t = otp_totp( op, token, &drift );
831 be_entry_release_r( op, token );
832 token = NULL;
833 }
834 if ( t < 0 && !BER_BVISNULL( &hotpdn ) &&
835 be_entry_get_rw( op, &hotpdn, oc_oathHOTPToken, ad_oathSecret, 0,
836 &token ) == LDAP_SUCCESS ) {
837 ndn = hotpdn;
838 ad = ad_oathHOTPCounter;
839 t = otp_hotp( op, token );
840 be_entry_release_r( op, token );
841 token = NULL;
842 }
843
844 /* If check succeeds, advance the step counter and drift accordingly */
845 if ( t >= 0 ) {
846 char outbuf[32], drift_buf[32];
847 Operation op2;
848 Opheader oh;
849 Modifications mod[2], *m = mod;
850 SlapReply rs2 = { REP_RESULT };
851 slap_callback cb = { .sc_response = &slap_null_cb };
852 BerValue bv[2], bv_drift[2];
853
854 bv[0].bv_val = outbuf;
855 bv[0].bv_len = snprintf( bv[0].bv_val, sizeof(outbuf), "%ld", t );
856 BER_BVZERO( &bv[1] );
857
858 m->sml_numvals = 1;
859 m->sml_values = bv;
860 m->sml_nvalues = NULL;
861 m->sml_desc = ad;
862 m->sml_op = LDAP_MOD_REPLACE;
863 m->sml_flags = SLAP_MOD_INTERNAL;
864
865 if ( drift_ad ) {
866 m->sml_next = &mod[1];
867
868 bv_drift[0].bv_val = drift_buf;
869 bv_drift[0].bv_len = snprintf(
870 bv_drift[0].bv_val, sizeof(drift_buf), "%ld", drift );
871 BER_BVZERO( &bv_drift[1] );
872
873 m++;
874 m->sml_numvals = 1;
875 m->sml_values = bv_drift;
876 m->sml_nvalues = NULL;
877 m->sml_desc = drift_ad;
878 m->sml_op = LDAP_MOD_REPLACE;
879 m->sml_flags = SLAP_MOD_INTERNAL;
880 }
881 m->sml_next = NULL;
882
883 op2 = *op;
884 oh = *op->o_hdr;
885 op2.o_hdr = &oh;
886
887 op2.o_callback = &cb;
888
889 op2.o_tag = LDAP_REQ_MODIFY;
890 op2.orm_modlist = mod;
891 op2.o_dn = op->o_bd->be_rootdn;
892 op2.o_ndn = op->o_bd->be_rootndn;
893 op2.o_req_dn = ndn;
894 op2.o_req_ndn = ndn;
895 op2.o_opid = -1;
896
897 op2.o_bd->be_modify( &op2, &rs2 );
898 if ( rs2.sr_err != LDAP_SUCCESS ) {
899 rc = LDAP_OTHER;
900 goto done;
901 }
902 } else {
903 /* Client failed the bind, but we still have to pass it over to the
904 * backend and fail the Bind later */
905 slap_callback *cb;
906 cb = op->o_tmpcalloc( 1, sizeof(slap_callback), op->o_tmpmemctx );
907 cb->sc_response = otp_bind_response;
908 cb->sc_next = op->o_callback;
909 op->o_callback = cb;
910 }
911
912 done:
913 if ( !BER_BVISNULL( &hotpdn ) ) {
914 ber_memfree_x( hotpdn.bv_val, op->o_tmpmemctx );
915 }
916 if ( !BER_BVISNULL( &totpdn ) ) {
917 ber_memfree_x( totpdn.bv_val, op->o_tmpmemctx );
918 }
919 op->o_bd->bd_info = (BackendInfo *)on;
920 return rc;
921 }
922
923 static slap_overinst otp;
924
925 int
otp_initialize(void)926 otp_initialize( void )
927 {
928 ConfigArgs ca;
929 char *argv[4];
930 int i;
931
932 otp.on_bi.bi_type = "otp";
933 otp.on_bi.bi_op_bind = otp_op_bind;
934
935 ca.argv = argv;
936 argv[0] = "otp";
937 ca.argv = argv;
938 ca.argc = 3;
939 ca.fname = argv[0];
940
941 argv[3] = NULL;
942 for ( i = 0; otp_oid[i].name; i++ ) {
943 argv[1] = otp_oid[i].name;
944 argv[2] = otp_oid[i].oid;
945 parse_oidm( &ca, 0, NULL );
946 }
947
948 /* schema integration */
949 for ( i = 0; otp_at[i].schema; i++ ) {
950 if ( register_at( otp_at[i].schema, otp_at[i].adp, 0 ) ) {
951 Debug( LDAP_DEBUG_ANY, "otp_initialize: "
952 "register_at failed\n" );
953 return -1;
954 }
955 }
956
957 for ( i = 0; otp_oc[i].schema; i++ ) {
958 if ( register_oc( otp_oc[i].schema, otp_oc[i].ocp, 0 ) ) {
959 Debug( LDAP_DEBUG_ANY, "otp_initialize: "
960 "register_oc failed\n" );
961 return -1;
962 }
963 }
964
965 return overlay_register( &otp );
966 }
967
968 #if SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC
969 int
init_module(int argc,char * argv[])970 init_module( int argc, char *argv[] )
971 {
972 return otp_initialize();
973 }
974 #endif /* SLAPD_OVER_OTP == SLAPD_MOD_DYNAMIC */
975
976 #endif /* defined(SLAPD_OVER_OTP) */
977