diff --git a/CREDITS b/CREDITS index ec988b3..10f549e 100644 --- a/CREDITS +++ b/CREDITS @@ -21,6 +21,7 @@ ideas: Don Lewis Emmanuel Dreyfus Eric Melville + Espen Grøndahl Gary Winiger Gleb Smirnoff Hubert Feyrer diff --git a/autogen.des b/autogen.des index 6aa38a0..ced1748 100755 --- a/autogen.des +++ b/autogen.des @@ -13,6 +13,7 @@ export CONFIG_SHELL=/bin/sh ./configure \ --with-doc \ --with-pam-unix \ + --with-pam-oath \ --with-pamtest \ --with-su \ --with-modules-dir=/usr/lib \ diff --git a/configure.ac b/configure.ac index 9ed2387..39143d7 100644 --- a/configure.ac +++ b/configure.ac @@ -64,11 +64,17 @@ AC_ARG_WITH([doc], AM_CONDITIONAL([WITH_DOC], [test x"$with_doc" = x"yes"]) AC_ARG_WITH([pam-unix], - AC_HELP_STRING([--with-pam-unix], [compile sample pam_unix(8) implementation]), + AC_HELP_STRING([--with-pam-unix], [compile sample pam_unix(8) module]), [], [with_pam_unix=no]) AM_CONDITIONAL([WITH_PAM_UNIX], [test x"$with_pam_unix" = x"yes"]) +AC_ARG_WITH([pam-oath], + AC_HELP_STRING([--with-pam-oath], [compile pam_oath(8) module]), + [], + [with_pam_oath=no]) +AM_CONDITIONAL([WITH_PAM_OATH], [test x"$with_pam_oath" = x"yes"]) + AC_ARG_WITH(pamtest, AC_HELP_STRING([--with-pamtest], [compile test application]), [], @@ -99,6 +105,13 @@ CRYPT_LIBS="${LIBS}" LIBS="${saved_LIBS}" AC_SUBST(CRYPT_LIBS) +saved_LIBS="${LIBS}" +LIBS="" +AC_CHECK_LIB([crypto], [OpenSSL_add_all_algorithms]) +CRYPTO_LIBS="${LIBS}" +LIBS="${saved_LIBS}" +AC_SUBST(CRYPTO_LIBS) + AC_ARG_ENABLE([developer-warnings], AS_HELP_STRING([--enable-developer-warnings], [enable strict warnings (default is NO)]), [CFLAGS="${CFLAGS} -Wall -Wextra"]) @@ -124,6 +137,7 @@ AC_CONFIG_FILES([ modules/pam_deny/Makefile modules/pam_permit/Makefile modules/pam_unix/Makefile + modules/pam_oath/Makefile t/Makefile ]) AC_CONFIG_FILES([pamgdb],[chmod +x pamgdb]) diff --git a/modules/Makefile.am b/modules/Makefile.am index 9ae44ae..e863bb3 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -1,3 +1,5 @@ # $Id$ -SUBDIRS = pam_unix pam_deny pam_permit +SUBDIRS = pam_deny pam_permit +SUBDIRS += pam_unix +SUBDIRS += pam_oath diff --git a/modules/pam_oath/Makefile.am b/modules/pam_oath/Makefile.am new file mode 100644 index 0000000..fd05996 --- /dev/null +++ b/modules/pam_oath/Makefile.am @@ -0,0 +1,14 @@ +# $Id: Makefile.am 429 2010-03-09 17:51:29Z des $ + +pkglibdir = @OPENPAM_MODULES_DIR@ +AM_CPPFLAGS = -I$(top_srcdir)/include + +if WITH_PAM_OATH +pkglib_LTLIBRARIES = pam_oath.la + +noinst_HEADERS = oath.h +pam_oath_la_SOURCES = pam_oath.c oath_hotp.c oath_totp.c +pam_oath_la_LDFLAGS = -no-undefined -module -version-info @LIB_MAJ@ \ + -export-symbols-regex '^pam_sm_' +pam_oath_la_LIBADD = $(top_builddir)/lib/libpam.la @CRYPTO_LIBS@ +endif diff --git a/modules/pam_oath/oath.h b/modules/pam_oath/oath.h new file mode 100644 index 0000000..66546f9 --- /dev/null +++ b/modules/pam_oath/oath.h @@ -0,0 +1,57 @@ +/*- + * Copyright (c) 2012-2013 Universitetet i Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifndef OATH_H_INCLUDED +#define OATH_H_INCLUDED + +#define base32_enclen(l) (((l + 4) / 5) * 8) +#define base32_declen(l) (((l + 7) / 8) * 5) +int base32_enc(const uint8_t *, size_t, char *, size_t *); +int base32_dec(const char *, size_t, uint8_t *, size_t *); + +#define base64_enclen(l) (((l + 2) / 3) * 4) +#define base64_declen(l) (((l + 3) / 4) * 3) +int base64_enc(const uint8_t *, size_t, char *, size_t *); +int base64_dec(const char *, size_t, uint8_t *, size_t *); + +enum oath_alg { undef, hotp, totp }; + +struct oath { + enum oath_alg alg; + unsigned int seq; + size_t keylen; + uint8_t key[]; +}; + +unsigned int oath_hotp(const uint8_t *, size_t, uint64_t, unsigned int); +unsigned int oath_totp(const uint8_t *, size_t, unsigned int); + +#endif diff --git a/modules/pam_oath/oath_base32.c b/modules/pam_oath/oath_base32.c new file mode 100644 index 0000000..4c1ceea --- /dev/null +++ b/modules/pam_oath/oath_base32.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 2013 Universitetet i Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "oath.h" + +static const char b32 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +/* + * Encode data in RFC 3548 base 32 representation. The target buffer must + * have room for base32_enclen(len) characters and a terminating NUL. + */ +int +base32_enc(const uint8_t *in, size_t ilen, char *out, size_t *olen) +{ + uint64_t bits; + + if (*olen <= base32_enclen(ilen)) + return (-1); + *olen = 0; + while (ilen >= 5) { + bits = 0; + bits |= (uint64_t)in[0] << 32; + bits |= (uint64_t)in[1] << 24; + bits |= (uint64_t)in[2] << 16; + bits |= (uint64_t)in[3] << 8; + bits |= (uint64_t)in[4]; + ilen -= 5; + in += 5; + out[0] = b32[bits >> 35 & 0x1f]; + out[1] = b32[bits >> 30 & 0x1f]; + out[2] = b32[bits >> 25 & 0x1f]; + out[3] = b32[bits >> 20 & 0x1f]; + out[4] = b32[bits >> 15 & 0x1f]; + out[5] = b32[bits >> 10 & 0x1f]; + out[6] = b32[bits >> 5 & 0x1f]; + out[7] = b32[bits & 0x1f]; + olen += 8; + out += 8; + } + if (ilen > 0) { + bits = 0; + switch (ilen) { + case 4: + bits |= (uint64_t)in[3] << 8; + case 3: + bits |= (uint64_t)in[2] << 16; + case 2: + bits |= (uint64_t)in[1] << 24; + case 1: + bits |= (uint64_t)in[0] << 32; + } + out[0] = b32[bits >> 35 & 0x1f]; + out[1] = b32[bits >> 30 & 0x1f]; + out[2] = ilen > 1 ? b32[bits >> 25 & 0x1f] : '='; + out[3] = ilen > 1 ? b32[bits >> 20 & 0x1f] : '='; + out[4] = ilen > 2 ? b32[bits >> 15 & 0x1f] : '='; + out[5] = ilen > 3 ? b32[bits >> 10 & 0x1f] : '='; + out[6] = ilen > 3 ? b32[bits >> 5 & 0x1f] : '='; + out[7] = '='; + olen += 8; + out += 8; + } + out[0] = '\0'; + return (0); +} + +/* + * Decode data in RFC 2548 base 32 representation, stopping at the + * terminating NUL, the first invalid (non-base32, non-whitespace) + * character or after len characters, whichever comes first. + * + * The olen argument is used by the caller to pass the size of the buffer + * and by base32_dec() to return the amount of data successfully decoded. + * If the buffer is too small, base32_dec() discards the excess data, but + * returns the total amount. + */ +int +base32_dec(const char *in, size_t ilen, uint8_t *out, size_t *olen) +{ + size_t len; + uint64_t bits; + int shift; + + for (len = 0, bits = 0, shift = 40; ilen && *in; --ilen, ++in) { + if (*in == ' ' || *in == '\t' || *in == '\r' || *in == '\n') { + continue; + } else if (*in >= 'A' && *in <= 'Z') { + shift -= 5; + bits |= (uint64_t)(*in - 'A') << shift; + } else if (*in >= 'a' && *in <= 'z') { + shift -= 5; + bits |= (uint64_t)(*in - 'a') << shift; + } else if (*in >= '2' && *in <= '7') { + shift -= 5; + bits |= (uint64_t)(*in - '2' + 26) << shift; + } else if (*in == '=' && + (shift == 30 || shift == 20 || shift == 15 || shift == 5)) { + /* hack: assume the rest of the padding is ok */ + shift = 0; + } else { + *olen = 0; + return (-1); + } + if (shift == 0) { + if ((len += 5) <= *olen) { + out[0] = (bits >> 32) & 0xff; + out[1] = (bits >> 24) & 0xff; + out[2] = (bits >> 16) & 0xff; + out[3] = (bits >> 8) & 0xff; + out[4] = bits & 0xff; + out += 5; + } + bits = 0; + shift = 40; + } + if (*in == '=') + break; + } + if (len > *olen) { + *olen = len; + return (-1); + } + *olen = len; + return (0); +} diff --git a/modules/pam_oath/oath_base64.c b/modules/pam_oath/oath_base64.c new file mode 100644 index 0000000..2c2302f --- /dev/null +++ b/modules/pam_oath/oath_base64.c @@ -0,0 +1,152 @@ +/*- + * Copyright (c) 2013 Universitetet i Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "oath.h" + +static const char b64 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* + * Encode data in RFC 3548 base 64 representation. The target buffer must + * have room for base64_enclen(len) characters and a terminating NUL. + */ +int +base64_enc(const uint8_t *in, size_t ilen, char *out, size_t *olen) +{ + uint32_t bits; + + if (*olen <= base64_enclen(ilen)) + return (-1); + *olen = 0; + while (ilen >= 3) { + bits = 0; + bits |= (uint32_t)in[0] << 16; + bits |= (uint32_t)in[1] << 8; + bits |= (uint32_t)in[2]; + ilen -= 3; + in += 3; + out[0] = b64[bits >> 18 & 0x3f]; + out[1] = b64[bits >> 12 & 0x3f]; + out[2] = b64[bits >> 6 & 0x3f]; + out[3] = b64[bits & 0x3f]; + olen += 4; + out += 4; + } + if (ilen > 0) { + bits = 0; + switch (ilen) { + case 2: + bits |= (uint32_t)in[1] << 8; + case 1: + bits |= (uint32_t)in[0] << 16; + } + out[0] = b64[bits >> 18 & 0x1f]; + out[1] = b64[bits >> 12 & 0x1f]; + out[2] = ilen > 1 ? b64[bits >> 6 & 0x1f] : '='; + out[3] = '='; + olen += 4; + out += 4; + } + out[0] = '\0'; + return (0); +} + +/* + * Decode data in RFC 2548 base 64 representation, stopping at the + * terminating NUL, the first invalid (non-base64, non-whitespace) + * character or after len characters, whichever comes first. + * + * The olen argument is used by the caller to pass the size of the buffer + * and by base64_dec() to return the amount of data successfully decoded. + * If the buffer is too small, base64_dec() discards the excess data, but + * returns the total amount. + */ +int +base64_dec(const char *in, size_t ilen, uint8_t *out, size_t *olen) +{ + size_t len; + uint32_t bits; + int shift; + + for (len = 0, bits = 0, shift = 24; ilen && *in; --ilen, ++in) { + if (*in == ' ' || *in == '\t' || *in == '\r' || *in == '\n') { + continue; + } else if (*in >= 'A' && *in <= 'Z') { + shift -= 6; + bits |= (uint32_t)(*in - 'A') << shift; + } else if (*in >= 'a' && *in <= 'z') { + shift -= 6; + bits |= (uint32_t)(*in - 'a' + 26) << shift; + } else if (*in >= '0' && *in <= '9') { + shift -= 6; + bits |= (uint32_t)(*in - '2' + 52) << shift; + } else if (*in == '+') { + shift -= 6; + bits |= (uint32_t)62 << shift; + } else if (*in == '/') { + shift -= 6; + bits |= (uint32_t)63 << shift; + } else if (*in == '=' && (shift == 12 || shift == 6)) { + /* hack: assume the rest of the padding is ok */ + shift = 0; + } else { + *olen = 0; + return (-1); + } + if (shift == 0) { + if ((len += 3) <= *olen) { + out[1] = (bits >> 16) & 0xff; + out[1] = (bits >> 8) & 0xff; + out[2] = bits & 0xff; + out += 3; + } + bits = 0; + shift = 24; + } + if (*in == '=') + break; + } + if (len > *olen) { + *olen = len; + return (-1); + } + *olen = len; + return (0); +} diff --git a/modules/pam_oath/oath_hotp.c b/modules/pam_oath/oath_hotp.c new file mode 100644 index 0000000..741719e --- /dev/null +++ b/modules/pam_oath/oath_hotp.c @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 2012-2013 Universitetet i Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include + +#include "oath.h" + +#define StToNum(St) (St) + +static uint32_t +DT(const uint8_t *String) +{ + uint8_t OffsetBits; + int Offset; + uint32_t P; + + OffsetBits = String[19] & 0x0f; + Offset = StToNum(OffsetBits); + P = (uint32_t)String[Offset + 0] << 24 | + (uint32_t)String[Offset + 1] << 16 | + (uint32_t)String[Offset + 2] << 8 | + (uint32_t)String[Offset + 3]; + return (P & 0x7fffffffUL); +} + +unsigned int +oath_hotp(const uint8_t *K, size_t Klen, uint64_t seq, unsigned int Digit) +{ + HMAC_CTX ctx; + uint8_t C[8]; + uint8_t HS[20]; + unsigned int HSlen; + uint32_t Sbits, Snum; + unsigned int mod, D; + + for (int i = 7; i >= 0; --i) { + C[i] = seq & 0xff; + seq >>= 8; + } + + /* HS = HMAC-SHA-1(K,C) */ + HMAC_CTX_init(&ctx); + HMAC_Init_ex(&ctx, K, Klen, EVP_sha1(), NULL); + HMAC_Update(&ctx, (const uint8_t *)&C, sizeof C); + HMAC_Final(&ctx, HS, &HSlen); + HMAC_CTX_cleanup(&ctx); + + Sbits = DT(HS); + Snum = StToNum(Sbits); + for (mod = 1; Digit > 0; --Digit) + mod *= 10; + D = Snum % mod; + return (D); +} diff --git a/modules/pam_oath/oath_key.c b/modules/pam_oath/oath_key.c new file mode 100644 index 0000000..96b79ec --- /dev/null +++ b/modules/pam_oath/oath_key.c @@ -0,0 +1,161 @@ +/*- + * Copyright (c) 2013 Universitetet i Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "oath.h" + +/* amount of space necessary to store base32-encoded data */ +#define base32_enclen(l) (((l + 4) / 5) * 8) + +/* maximum decoded length of base32-encoded data */ +#define base32_declen(l) (((l + 7) / 8) * 5) + +static const char b32 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +/* + * Encode data in RFC 3548 base 32 representation. The target buffer must + * have room for base32_enclen(len) characters and a terminating NUL. + */ +static int +base32_enc(const uint8_t *in, size_t ilen, char *out, size_t *olen) +{ + uint64_t bits; + + if (*olen <= base32_enclen(ilen)) + return (-1); + *olen = 0; + while (ilen >= 5) { + bits = 0; + bits = bits << 8 | in[0]; + bits = bits << 8 | in[1]; + bits = bits << 8 | in[2]; + bits = bits << 8 | in[3]; + bits = bits << 8 | in[4]; + ilen -= 5; + in += 5; + out[0] = b32[bits >> 5*7 & 0x1f]; + out[1] = b32[bits >> 5*6 & 0x1f]; + out[2] = b32[bits >> 5*5 & 0x1f]; + out[3] = b32[bits >> 5*4 & 0x1f]; + out[4] = b32[bits >> 5*3 & 0x1f]; + out[5] = b32[bits >> 5*2 & 0x1f]; + out[6] = b32[bits >> 5*1 & 0x1f]; + out[7] = b32[bits >> 5*0 & 0x1f]; + olen += 8; + out += 8; + } + if (ilen > 0) { + bits = 0; + switch (ilen) { + case 4: + bits |= (uint64_t)in[3] << 8; + case 3: + bits |= (uint64_t)in[2] << 16; + case 2: + bits |= (uint64_t)in[1] << 24; + case 1: + bits |= (uint64_t)in[1] << 32; + } + out[0] = b32[bits >> 5*7 & 0x1f]; + out[1] = b32[bits >> 5*6 & 0x1f]; + out[2] = ilen > 1 ? b32[bits >> 5*5 & 0x1f] : '='; + out[3] = ilen > 1 ? b32[bits >> 5*4 & 0x1f] : '='; + out[4] = ilen > 2 ? b32[bits >> 5*3 & 0x1f] : '='; + out[5] = ilen > 3 ? b32[bits >> 5*2 & 0x1f] : '='; + out[6] = ilen > 3 ? b32[bits >> 5*1 & 0x1f] : '='; + out[7] = '='; + olen += 8; + out += 8; + } + out[0] = '\0'; + return (0); +} + +/* + * Decode data in RFC 2548 base 32 representation, stopping at the + * terminating NUL, the first invalid (non-base32, non-whitespace) + * character or after len characters, whichever comes first. + * + * The olen argument is used by the caller to pass the size of the buffer + * and by base32_dec() to return the amount of data successfully decoded. + * If the buffer is too small, base32_dec() discards the excess data, but + * returns the total amount. + */ +static int +base32_dec(const char *in, size_t ilen, uint8_t *out, size_t *olen) +{ + size_t len; + uint64_t bits; + int shift; + + for (len = 0, bits = 0, shift = 40; ilen && *in; --ilen, ++in) { + if (*in == ' ' || *in == '\t' || *in == '\r' || *in == '\n') { + continue; + } else if (*in >= 'A' && *in <= 'Z') { + shift -= 5; + bits |= (uint64_t)(*in - 'A') << shift; + } else if (*in >= 'a' && *in <= 'z') { + shift -= 5; + bits |= (uint64_t)(*in - 'a') << shift; + } else if (*in >= '2' && *in <= '7') { + shift -= 5; + bits |= (uint64_t)(*in - '2' + 26) << shift; + } else { + *olen = 0; + return (-1); + } + if (shift == 0) { + if ((len += 5) <= *olen) { + out[0] = (bits >> 32) & 0xff; + out[1] = (bits >> 24) & 0xff; + out[2] = (bits >> 16) & 0xff; + out[3] = (bits >> 8) & 0xff; + out[4] = bits & 0xff; + out += 5; + } + bits = 0; + shift = 40; + } + } + if (len > *olen) { + *olen = len; + return (-1); + } + *olen = len; + return (0); +} diff --git a/modules/pam_oath/oath_totp.c b/modules/pam_oath/oath_totp.c new file mode 100644 index 0000000..5cfc6b6 --- /dev/null +++ b/modules/pam_oath/oath_totp.c @@ -0,0 +1,50 @@ +/*- + * Copyright (c) 2012-2013 Universitetet i Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include "oath.h" + +#define TOTP_TIME_STEP 30 + +unsigned int +oath_totp(const uint8_t *K, size_t Klen, unsigned int Digit) +{ + time_t now; + + time(&now); + return (oath_hotp(K, Klen, now / TOTP_TIME_STEP, Digit)); +} diff --git a/modules/pam_oath/pam_oath.c b/modules/pam_oath/pam_oath.c new file mode 100644 index 0000000..be5c50c --- /dev/null +++ b/modules/pam_oath/pam_oath.c @@ -0,0 +1,99 @@ +/*- + * Copyright (c) 2012-2013 Universitetet i Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id$ + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include + +#define PAM_SM_AUTH +#define PAM_SM_ACCOUNT + +#include +#include + +PAM_EXTERN int +pam_sm_authenticate(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + struct passwd *pwd; + const char *user; + char *password; + int pam_err; + + (void)flags; + (void)argc; + (void)argv; + + /* identify user */ + if ((pam_err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) + return (pam_err); + if ((pwd = getpwnam(user)) == NULL) + return (PAM_USER_UNKNOWN); + + /* get code */ + pam_err = pam_get_authtok(pamh, PAM_AUTHTOK, + (const char **)&password, NULL); + if (pam_err == PAM_CONV_ERR) + return (pam_err); + if (pam_err != PAM_SUCCESS) + return (PAM_AUTH_ERR); + + pam_err = PAM_AUTH_ERR; + return (pam_err); +} + +PAM_EXTERN int +pam_sm_setcred(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + (void)pamh; + (void)flags; + (void)argc; + (void)argv; + return (PAM_SUCCESS); +} + +PAM_EXTERN int +pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, + int argc, const char *argv[]) +{ + + (void)pamh; + (void)flags; + (void)argc; + (void)argv; + return (PAM_SUCCESS); +} + +PAM_MODULE_ENTRY("pam_unix");