/*␊ |
␊ |
␉otpauthexternal - "middleware" for mod_auth_external to use TOTP One time passwords␊ |
␊ |
Copyright [2013] [Nathan Adams]␊ |
␊ |
Licensed under the Apache License, Version 2.0 (the "License");␊ |
you may not use this file except in compliance with the License.␊ |
You may obtain a copy of the License at␊ |
␊ |
http://www.apache.org/licenses/LICENSE-2.0␊ |
␊ |
Unless required by applicable law or agreed to in writing, software␊ |
distributed under the License is distributed on an "AS IS" BASIS,␊ |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.␊ |
See the License for the specific language governing permissions and␊ |
limitations under the License.␊ |
␊ |
*/␊ |
␊ |
#include <stdio.h>␊ |
#include <stdlib.h>␊ |
#include <string.h>␊ |
#include <math.h>␊ |
␊ |
#include <openssl/sha.h>␊ |
#include <openssl/evp.h>␊ |
#include <openssl/bio.h>␊ |
#include <openssl/hmac.h>␊ |
#include <openssl/md5.h>␊ |
#include <openssl/buffer.h>␊ |
#include <time.h>␊ |
#include <ctype.h>␊ |
#include <stdint.h>␊ |
␊ |
#include <my_global.h>␊ |
#include <mysql.h>␊ |
␊ |
#include <signal.h>␊ |
␊ |
// http://stackoverflow.com/a/14291421/195722␊ |
unsigned int hash(const char *mode, const char* dataToHash, size_t dataSize, unsigned char* outHashed) {␊ |
unsigned int md_len = -1;␊ |
const EVP_MD *md = EVP_get_digestbyname(mode);␊ |
if(NULL != md) {␊ |
EVP_MD_CTX mdctx;␊ |
EVP_MD_CTX_init(&mdctx);␊ |
EVP_DigestInit_ex(&mdctx, md, NULL);␊ |
EVP_DigestUpdate(&mdctx, dataToHash, dataSize);␊ |
EVP_DigestFinal_ex(&mdctx, outHashed, &md_len);␊ |
EVP_MD_CTX_cleanup(&mdctx);␊ |
}␊ |
return md_len;␊ |
}␊ |
␊ |
void printHash(unsigned char * hash)␊ |
{␊ |
␉int i;␊ |
␉for(i=0; i<SHA_DIGEST_LENGTH; i++) {␊ |
␉␉printf("%02x", hash[i]);␊ |
␉}␊ |
}␊ |
␊ |
unsigned int calcB64Size(unsigned int lng)␊ |
{␊ |
␉unsigned int adjustment = ( (lng % 3) ? (3 - (lng % 3)) : 0);␊ |
␉unsigned int code_padded_size = ( (lng + adjustment) / 3) * 4;␊ |
␉unsigned int newline_size = ((code_padded_size) / 72) * 2;␊ |
␊ |
␉ return code_padded_size + newline_size; //extra +1 for null terminated char␊ |
}␊ |
␊ |
char * b64encode(unsigned char * msg, int lng)␊ |
{␊ |
␉ BIO *bio, *b64;␊ |
␉ BUF_MEM *bptr;␊ |
␉ //int size;␊ |
␉ unsigned int b64ln = calcB64Size(lng);␊ |
␉ char * buff;␊ |
␉ ␊ |
␉ b64 = BIO_new(BIO_f_base64());␊ |
␉ bio = BIO_new(BIO_s_mem());␊ |
␉ bio = BIO_push(b64, bio);␊ |
␊ |
␉ BIO_write(b64, msg, lng);␊ |
␉ BIO_flush(bio);␊ |
␊ |
␉ BIO_get_mem_ptr(b64, &bptr);␊ |
␊ |
␉ buff = (char *)malloc(bptr->length);␊ |
␉ memcpy(buff, bptr->data, bptr->length-1);␊ |
␉ buff[bptr->length-1] = 0;␊ |
␉ ␊ |
␉ BIO_free_all(b64);␊ |
␉ return buff;␊ |
}␊ |
␊ |
static const int powers10[] = { 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 1000000000 };␊ |
␊ |
// hotp function copied from https://code.google.com/p/mod-authn-otp/source/browse/trunk/hotp.c␊ |
␊ |
/*␊ |
* otptool - HOTP/OATH one-time password utility␊ |
*␊ |
* Copyright 2009 Archie L. Cobbs <archie@dellroad.org>␊ |
*␊ |
* Licensed under the Apache License, Version 2.0 (the "License");␊ |
* you may not use this file except in compliance with the License.␊ |
* You may obtain a copy of the License at␊ |
*␊ |
* http://www.apache.org/licenses/LICENSE-2.0␊ |
*␊ |
* Unless required by applicable law or agreed to in writing, software␊ |
* distributed under the License is distributed on an "AS IS" BASIS,␊ |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.␊ |
* See the License for the specific language governing permissions and␊ |
* limitations under the License.␊ |
*␊ |
* $Id$␊ |
*/␊ |
␊ |
␊ |
void␊ |
hotp(const unsigned char *key, size_t keylen, unsigned long counter, int ndigits, char *buf10, char *buf16, size_t buflen)␊ |
{␊ |
const int max10 = sizeof(powers10) / sizeof(*powers10);␊ |
const int max16 = 8;␊ |
const EVP_MD *sha1_md = EVP_sha1();␊ |
unsigned char hash[EVP_MAX_MD_SIZE];␊ |
unsigned int hash_len;␊ |
unsigned char tosign[8];␊ |
int offset;␊ |
int value;␊ |
int i;␊ |
␊ |
/* Encode counter */␊ |
for (i = sizeof(tosign) - 1; i >= 0; i--) {␊ |
tosign[i] = counter & 0xff;␊ |
counter >>= 8;␊ |
}␊ |
␊ |
/* Compute HMAC */␊ |
HMAC(sha1_md, key, keylen, tosign, sizeof(tosign), hash, &hash_len);␊ |
␊ |
/* Extract selected bytes to get 32 bit integer value */␊ |
offset = hash[hash_len - 1] & 0x0f;␊ |
value = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)␊ |
| ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);␊ |
␊ |
/* Sanity check max # digits */␊ |
if (ndigits < 1)␊ |
ndigits = 1;␊ |
␊ |
/* Generate decimal digits */␊ |
if (buf10 != NULL) {␊ |
snprintf(buf10, buflen, "%0*d", ndigits < max10 ? ndigits : max10,␊ |
ndigits < max10 ? value % powers10[ndigits - 1] : value);␊ |
}␊ |
␊ |
/* Generate hexadecimal digits */␊ |
if (buf16 != NULL) {␊ |
snprintf(buf16, buflen, "%0*x", ndigits < max16 ? ndigits : max16,␊ |
ndigits < max16 ? (value & ((1 << (4 * ndigits)) - 1)) : value);␊ |
}␊ |
}␊ |
␊ |
␊ |
typedef enum {␊ |
␉MYSQLDB,␊ |
␉SQLITEDB,␊ |
␉UNDEFINEDDB␊ |
} DB_TYPE;␊ |
␊ |
typedef struct␊ |
{␊ |
␉char dbhost[255]; ␊ |
␉char dbname[255];␊ |
␉char dbuser[255];␊ |
␉char dbpass[255];␊ |
␉char dbtable[255];␊ |
␉char passfield[255];␊ |
␉char otpfield[255];␊ |
␉int dbport;␊ |
␉DB_TYPE type;␊ |
} DB;␊ |
␊ |
typedef struct ␊ |
{␊ |
␉char * user;␊ |
␉char * otp;␊ |
␉char * password;␊ |
} User;␊ |
␊ |
DB * initDBStruct()␊ |
{␊ |
␉DB * d = (DB *)malloc(sizeof(*d));␊ |
␉memset(d->dbhost, '\0', 255);␊ |
␉memset(d->dbname, '\0', 255);␊ |
␉memset(d->dbuser, '\0', 255);␊ |
␉memset(d->dbpass, '\0', 255);␊ |
␉memset(d->dbtable, '\0', 255);␊ |
␉strcpy(d->passfield, "password");␊ |
␉strcpy(d->otpfield, "otpkey");␊ |
␉d->dbport = 3306;␊ |
␉d->type = UNDEFINEDDB;␊ |
␉return d;␊ |
}␊ |
␊ |
User * initUserStruct()␊ |
{␊ |
␉User * u = (User *)malloc(sizeof(*u));␊ |
␉u->otp = NULL;␊ |
␉u->password = NULL;␊ |
␉u->user = NULL;␊ |
␉return u;␊ |
}␊ |
␊ |
User * getMySQLUser(char * user, DB * db)␊ |
{␊ |
␉char q[512];␊ |
␉int strp = 0;␊ |
␉MYSQL_RES *result;␊ |
␉MYSQL_ROW row;␊ |
␉User * u;␊ |
␉MYSQL *con;␊ |
␉memset(q, '\0', 512);␊ |
␉if (db->type != MYSQLDB)␊ |
␉␉return NULL;␊ |
␉con = mysql_init(NULL);␊ |
␉u = initUserStruct();␊ |
␊ |
␉if (con == NULL) ␊ |
␉{␊ |
␉␉fprintf(stderr, "%s\n", mysql_error(con));␊ |
␉␉free(u);␊ |
␉␉return NULL;␊ |
␉}␊ |
␊ |
␉if (mysql_real_connect(con, db->dbhost, db->dbuser, db->dbpass, db->dbname,␊ |
␉␉db->dbport, NULL, 0) == NULL) ␊ |
␉{␊ |
␉␉fprintf(stderr, "%s\n", mysql_error(con));␊ |
␉␉mysql_close(con);␊ |
␉␉free(u);␊ |
␉␉return NULL;␊ |
␉}␊ |
␊ |
␉strncpy(q, "SELECT ", 7);␊ |
␉strp += 7;␊ |
␉strncpy(q + strp, db->passfield, strlen(db->passfield));␊ |
␉strp += strlen(db->passfield);␊ |
␉strncpy(q + strp, ",", 1);␊ |
␉strp += 1;␊ |
␉strncpy(q + strp, db->otpfield, strlen(db->otpfield));␊ |
␉strp += strlen(db->otpfield);␊ |
␉strncpy(q + strp, " FROM ", 6);␊ |
␉strp += 6;␊ |
␉strncpy(q + strp, db->dbtable, strlen(db->dbtable));␊ |
␉strp += strlen(db->dbtable);␊ |
␉strncpy(q + strp, " WHERE login = '", 16);␊ |
␉strp += 16;␊ |
␉strncpy(q + strp, user, strlen(user));␊ |
␉strp += strlen(user);␊ |
␉strncpy(q + strp, "' LIMIT 1", 9);␊ |
␉if (mysql_query(con, q))␊ |
␉{␊ |
␉␉return NULL;␊ |
␉}␊ |
␊ |
␉result = mysql_store_result(con);␊ |
␉␊ |
␉row = mysql_fetch_row(result);␊ |
␊ |
␉u->user = user;␊ |
␉u->password = row[0];␊ |
␉if (strcmp(row[1], "") != 0)␊ |
␉␉u->otp = row[1];␊ |
␊ |
␉mysql_free_result(result);␊ |
␉mysql_close(con);␊ |
␉return u;␊ |
}␊ |
␊ |
// Config file will have the following format:␊ |
// config{space}value␊ |
␊ |
// Sample config file:␊ |
// dbtype mysql␊ |
// dbhost localhost␊ |
// dbport 3306␊ |
// dbuser root␊ |
// dbpass root␊ |
// dbtable table␊ |
// dbname db1␊ |
␊ |
// optional␊ |
// dbpassfield field␊ |
// dbotpfield field␊ |
␊ |
// future␊ |
// linger 300␊ |
DB * readConfig(char * config_path)␊ |
{␊ |
␉FILE *ifp;␊ |
␉DB * d;␊ |
␉char config[255];␊ |
␉char value[255];␊ |
␉ifp = fopen(config_path, "r");␊ |
␉␊ |
␉d = initDBStruct();␊ |
␊ |
␉while(!feof(ifp))␊ |
␉{␊ |
␉␉if (fscanf(ifp, "%s %s", config, value) != 2)␊ |
␉␉{␊ |
␉␉␉break;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbtype") == 0)␊ |
␉␉{␊ |
␉␉␉if (strcmp(value, "mysql") == 0)␊ |
␉␉␉{␊ |
␉␉␉␉d->type = MYSQLDB;␊ |
␉␉␉}␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbhost") == 0)␊ |
␉␉{␊ |
␉␉␉strcpy(d->dbhost, value);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbport") == 0)␊ |
␉␉{␊ |
␉␉␉sprintf(value, "%d", &d->dbport);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbuser") == 0)␊ |
␉␉{␊ |
␉␉␉strcpy(d->dbuser, value);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbpass") == 0)␊ |
␉␉{␊ |
␉␉␉strcpy(d->dbpass, value);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbtable") == 0)␊ |
␉␉{␊ |
␉␉␉strcpy(d->dbtable, value);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbname") == 0)␊ |
␉␉{␊ |
␉␉␉strcpy(d->dbname, value);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbpassfield") == 0)␊ |
␉␉{␊ |
␉␉␉strcpy(d->passfield, value);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␊ |
␉␉if (strcmp(config, "dbotpfield") == 0)␊ |
␉␉{␊ |
␉␉␉strcpy(d->otpfield, value);␊ |
␉␉␉continue;␊ |
␉␉}␊ |
␉}␊ |
␉return d;␊ |
}␊ |
␊ |
int authotp(char * user_name, char * user_passwd) //, char * config_path)␊ |
{␊ |
␉DB * db;␊ |
␉User * user;␊ |
␉unsigned char outHash[20];␊ |
␉char * ret;␊ |
␉char * ret2;␊ |
␉int keylen;␊ |
␉unsigned char newkey[256];␊ |
␉int ik = 0;␊ |
␉int i;␊ |
␉int nibs[2];␊ |
␉char buf10[256];␊ |
␉char buf16[256];␊ |
␉time_t now = time(NULL);␊ |
␉char inotp[7];␊ |
␉char password[256];␊ |
␉OpenSSL_add_all_algorithms();␊ |
␉␊ |
␉␊ |
␉//db = readConfig(getenv("OTPCONFIG"));␊ |
␉db = readConfig("/etc/apache2/configotp");␊ |
␉user = getMySQLUser(user_name, db);␊ |
␉// if user does not have a OTP set - just verify password␊ |
␉if (user->otp == NULL) ␊ |
␉{␊ |
␉␉hash("SHA1", user_passwd, strlen(user_passwd), outHash);␊ |
␉␉ret = b64encode(outHash, 20);␊ |
␉␉if (strcmp(ret, user->password) == 0)␊ |
␉␉␉exit(0);␊ |
␉␉else␊ |
␉␉␉exit(1);␊ |
␉} else {␊ |
␉␉// password should be in the form {OTP}{PASSWORD}␊ |
␉␉// ie 123456password␊ |
␉␉if (strlen(user_passwd) < 7) // 6 OTP digits and 1 char for password␊ |
␉␉{␊ |
␉␉␉printf("password not long enough!");␊ |
␉␉␉exit(1);␊ |
␉␉}␊ |
␉␉for(keylen = 0; keylen < sizeof(newkey) && user->otp[ik] != '\0'; keylen++)␊ |
␉␉{␊ |
␉␉␉for(i = 0; i < 2; i++)␊ |
␉␉␉{␊ |
␉␉␉␉if (isdigit(user->otp[ik]))␊ |
␉␉␉␉␉nibs[i] = user->otp[ik] - '0';␊ |
␉␉␉␉else if (isxdigit(user->otp[ik]))␊ |
␉␉␉␉␉nibs[i] = tolower(user->otp[ik]) - 'a' + 10;␊ |
␉␉␉␉ik++;␊ |
␉␉␉}␊ |
␉␉␉newkey[keylen] = (nibs[0] << 4) | nibs[1];␊ |
␉␉}␊ |
␊ |
␉␉hotp(newkey, keylen, (int)now / 30, 6, buf10, buf16, 256);␊ |
␉␉strncpy(inotp, user_passwd, 6);␊ |
␉␉inotp[6] = '\0';␊ |
␉␉strcpy(password, user_passwd + 6);␊ |
␉␉hash("SHA1", password, strlen(password), outHash);␊ |
␉␉ret2 = b64encode(outHash, 20);␊ |
␉␉if (strcmp(ret2, user->password) == 0 && strcmp(buf10, inotp) == 0)␊ |
␉␉{␊ |
␉␉␉exit(0);␊ |
␉␉} else {␊ |
␉␉␉exit(1);␊ |
␉␉}␊ |
␉}␊ |
␊ |
␉free(user);␊ |
␉free(db);␊ |
␉return 0;␊ |
}␊ |
␊ |
/*char * getSQLiteUser(char * user, DB db)␊ |
{␊ |
␊ |
}*/␊ |
␊ |
int main()␊ |
{␊ |
␉char user[100], password[100], *p;␊ |
␉if (fgets(user, sizeof(user), stdin) == NULL) exit(2);␊ |
if ((p= strchr(user, '\n')) == NULL) exit(4);␊ |
*p= '\0';␊ |
␊ |
if (fgets(password, sizeof(password), stdin) == NULL) exit(3);␊ |
if ((p= strchr(password, '\n')) == NULL) exit(5);␊ |
*p= '\0';␊ |
␊ |
␉authotp(user, password);␊ |
}␊ |