Hello
I received a private reply asking if it was similar to OPIE, so I will
first explain how OTP works : imagine for some reason (you go in
vacation, firewall, etc.) you can only access your machine on non
secured channels such as telnet or web port 80 in http mode only
(shellinabox)
You do not want to compromise your password, so you give instead a
password that will only work one time : in the next N minutes
timeframe.
If you know the precise "acceptable window" of your remote server and
if your times are synchronized, you can also give a password only
valid for say the next 5 seconds.
The One Time Password t is generated by your laptop or mobile (ex: OTP
for the N900) from a shared secret. Giving the OTP will not expose the
shared secret.
Now for busybox:
On Sat, May 5, 2012 at 4:08 AM, walter harms <[email protected]> wrote:
> feel free to post your code. please provide some information so we can
> evaluate it, e.g.
> how did you test your code ?
I first made a standalone implementation, then ported it to busybox.
(the code is being reviewed by someone well versed in security - I
will pass the review result/fixes as soon as I get them)
The tests were similar and quite basic :
I did set the pin to a given value, printed the list of acceptable
password and tested with randomly generated passwords along with the
right passwords.
I tried to login for unkown and given users with the right password
and a wrong password.
There is no test suite yet, because it would require that the OTP
generator knows the shared secret. It is a first implementation and I
would welcome some feedback to change it.
First, I was wondering where the shared secret should go - at this
time it is a compile-time #define to be a drop-in replacement for
/bin/login. It could also go into a separate file, or into a field in
/etc/password so that each user could have its own shared secret, or
it could be a list of random values changing each day (the most secure
option)
For this last idea, I was thinking of adding QOTD support to inetd,
which could be used to publish yesterday shared secret (no longer
accepted), and thus help the OTP generator synchronize it list. It may
be security through obscurity (port knocking like), but a QOTD
followed by an OTP attempt could also be used in firewall tables to
"unlock" the telnet port.
Is it acceptable to add a QOTD support to inetd?
> are additional libraries needed ?
No library are needed. Everything is implemented in the function.
I also create a generic "getpin" function should other apps in busybox
need to generate a random password of non repeating elements.
Currently epoch/10 is printed next to the challenge, but could be
removed (the otp generator could connect to port 37 if there is a
worry that time is not synced).
Also, the PIN could be send by a different channel such as SMS.
> special compiler options ?
Nothing needed. Compiles with default Busybox options.
> What is about hardware requirements ?
As long as you can run busybox you are good to go.
The use case when the option is enabled is as follows :
- if the user submits a known password, then accept it,
- if the password is unknown, see if it matches with a list of valid
OTP for the time window
- if it matches, then accept it.
The OTP can be generated by any MOTP capable application, such as OTP
for the n900, or various shell script available online.
I also attached a quick and dirty motp-gen written in C. I'm not use
how usefull it could be for busybox.
The basic concepts of OTP are simple, but there are design issues, and
since it goes into /bin/login, it's better to be carefull :-)
I welcome any code review. Considering it was done on a friday night,
I may very well have done mistakes. I will run further tests on the
code soon.
Also, the code may not be very optimized.
It is my very first contribution to busybox, let me know if I'm doing
something wrong
Guylhem
/* Copyright (C) Guylhem 2012. GPL v2 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <bsd/md5.h>
/* OTP shared secret */
#define OTPSECRET "658736a696bd8b173f70e645520c95ab"
#define OTPPIN_LEN 7
#define OTPCHALLENGE_MAX 32
int main (int argc, char **argv) {
// char pin[OTPPIN_LEN+1];
// char shared[OTPCHALLENGE_MAX+1];
/* MOTP : FIRST 6 chars of MD5(10s granularity EPOCH, 4-7 digit PIN, 16-32 hex SECRET) */
char motp[9 + OTPPIN_LEN + OTPCHALLENGE_MAX + 1];
char *md5full;
int now=time(NULL);
int otpepoch=time(NULL)/10;
MD5_CTX md5;
MD5Init(&md5);
if (argc < 3) {
fprintf (stderr, "\nGives a password to reply to the OTP-MD5 challenge\n\nUsage:\t%s shared_secret proposed_pin\n", argv[0]);
exit(1);
}
/* No \n in MOTP */
sprintf (motp, "%d%s%s", otpepoch, argv[2], argv[1]);
MD5Update(&md5, motp, strlen(motp));
md5full = MD5End(&md5, NULL);
printf ("\nValid for the next %d seconds:\n\t\t\t\t%.6s\n\n", 10+(otpepoch*10-now), md5full);
free (md5full);
exit (0);
}
/*
* Copyright (C) Guylhem, 2012
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
#define ENABLE_FEATURE_LOGINOTP 1
#include "libbb.h"
#if ENABLE_FEATURE_LOGINOTP
/* For a PIN of non-repeating digits :
*
* Floyd algorithm (elements of size N, lenght of M)
* Then a Knuth Shuffle
*/
#define OTPPIN_LEN 6
#define OTPPIN_SIZ 9
/* OTP reply length */
#define OTPCHALLENGE_MAX 6
/* OTP shared secret */
#define OTPSECRET "658736a696bd8b173f70e645520c95ab"
/* OTP acceptable window before/after current time in minutes */
#define OTPWINDOW 2
/* OTP valid iff all chars are hexadecimal */
#define OTPCHARS "0123456789abcdefABCDEF"
/* Pretend to read a password while it's known to be incorrect? Since we won't
* accept it, don't bother, even if it might theoretically be used in a
* carefull measurement of the response delay attack to detect which user *do*
* exist with a dictionnary-based attack.
*/
#define PRETEND sprintf (challenge, "Password or OTP-MD5 Challenge %s @ %d: ", pin, otpepoch); unencrypted = bb_ask_stdin(challenge); return (0);
#else
#define PRETEND unencrypted = bb_ask_stdin("Password: "); return (0);
#endif
void pin_floyd(char pin[]) {
int in, im;
int list[OTPPIN_LEN];
im = 0;
/* seed from epoch and salt with shared secret 1st decimal segment len */
srand((int) (time(NULL) / 1+strspn(OTPSECRET, "0123456789")));
for (in = 0; in < OTPPIN_SIZ && im < OTPPIN_LEN; ++in) {
int rn = OTPPIN_SIZ - in;
int rm = OTPPIN_LEN - im;
if (rand() % rn < rm)
list[im++] = in + 1;
}
for (in = OTPPIN_LEN-1; in >1; --in) {
int out = rand() % in + 1;
int temp = list[out];
list[out] = list[in];
list[in] = temp;
}
for (in = 0; in < OTPPIN_LEN; ++in) {
sprintf (&pin[in], "%1d", list[in]);
}
pin[OTPPIN_LEN]='\0';
}
/* correct_password : ask the user its password.
*
* in: const struct passwd *pw : NULL if inexistant user tries to login
* out: 1 if correct password || empty password
* 0 if wrong password
*/
int FAST_FUNC correct_password (const struct passwd *pw) {
char *unencrypted, *encrypted;
const char *correct;
int i;
#if ENABLE_FEATURE_SHADOWPASSWDS
/* Using _r function to avoid pulling in static buffers */
struct spwd spw;
char buffer[256];
#endif
#if ENABLE_FEATURE_LOGINOTP
char challenge[255];
char pin[OTPPIN_LEN+1];
/* MOTP : FIRST 6 chars of MD5(10s granularity EPOCH, 4-7 digit PIN, 16-32 hex SECRET) */
char motp[9 + OTPPIN_LEN + 32];
uint32_t md5[16/4];
// char *md5;
/* 12 series of 5 seconds per minutes window in the past and the future */
char acceptable[2*OTPWINDOW*12][6+1];
/* initiate epoch at the window past to ease array creation*/
int otpepoch=time(NULL)/10 - OTPWINDOW*12;
/* get ready if PRETENDing is necessary */
pin_floyd(pin);
#endif
/* empty password for non existing users */
if (!pw) {
PRETEND
}
correct=pw->pw_passwd;
#if ENABLE_FEATURE_SHADOWPASSWDS
if ((correct[0] == 'x' || correct[0] == '*') && !correct[1]) {
struct spwd *result = NULL;
i = getspnam_r(pw->pw_name, &spw, buffer, sizeof(buffer), &result);
/* glibc 2.4 BUG: getspnam_r returns 0 instead of -1, but set result to NULL */
if (i || !result) {
PRETEND
} else {
correct = result->sp_pwdp;
}
}
#endif
/* If the password field is really empty, accept it without a prompt */
if (!correct[0]) {
return (1);
}
#if ENABLE_FEATURE_LOGINOTP
sprintf (challenge, "Password or OTP-MD5 Challenge %s @ %d: ", pin, otpepoch);
unencrypted = bb_ask_stdin(challenge);
#else
unencrypted = bb_ask_stdin("Password: ");
#endif
if (!unencrypted) {
return (0);
}
encrypted = pw_encrypt(unencrypted, correct, 1);
i = (strcmp(encrypted, correct) == 0);
free(encrypted);
#ifndef ENABLE_FEATURE_LOGINOTP
memset(unencrypted, 0, strlen(unencrypted));
return (i);
#else
/* First try it as a password */
if (i==1) {
memset(unencrypted, 0, strlen(unencrypted));
return (1);
}
/* Now that we know it doesn't match, better be safe than sorry */
/* pad with spaces at the exact len to avoid early strstr matches.
* It might also avoid some potential unicode strstr weirdness
*/
sprintf (unencrypted, "%-*.*s", OTPCHALLENGE_MAX, OTPCHALLENGE_MAX, unencrypted);
/* now create a list of the valid answer to the challenge given the time window */
for (i=0; i<2*OTPWINDOW*12; ++i) {
md5_ctx_t ctx;
/* No \n in MOTP or your md5 won't match */
sprintf (motp, "%d%s%s", otpepoch+i, pin, OTPSECRET);
/* Do each md5 */
md5_begin(&ctx);
md5_hash(&ctx, motp, strlen(motp));
// md5=xmalloc(4096);
md5_end(&ctx, md5);
/* get first 6 hex chars and add a space*/
sprintf (acceptable[i], "%02x", md5[0]);
sprintf (acceptable[i]+2, "%02x", md5[1]);
sprintf (acceptable[i]+4, "%02x", md5[2]);
sprintf (acceptable[i]+6, " ");
// free (md5);
}
if (strcasestr((char *) acceptable, unencrypted)) {
memset(unencrypted, 0, strlen(unencrypted));
return (1);
} else {
memset(unencrypted, 0, strlen(unencrypted));
return (0);
}
#endif /* ENABLE_FEATURE_LOGINOTP */
}
_______________________________________________
busybox mailing list
[email protected]
http://lists.busybox.net/mailman/listinfo/busybox