xref: /netbsd-src/external/bsd/openldap/dist/servers/slapd/overlays/otp.c (revision 549b59ed3ccf0d36d3097190a0db27b770f3a839)
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 					&params ) ) {
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 					&params ) ) {
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