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