commit 04d63fd09f6c00ed1cdd25a7b2cd453da2fd080c Author: Mysaa Java Date: Mon May 3 22:29:20 2021 +0200 Premier commit. Trois programmes: - authKeysPg génère un fichier .authorized_keys à partir de la BDD - pam_oath_key est un module PAM demandant un mot de passe otp selon la clé de connection SSH utilisée - bash-gitonly est un «shell» qui execute les commandes transmises par SSH, uniquement reliées à git et en vérifiant les permissions diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ac7ee3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +auth-keys-gen +bash-gitonly +*.so +*.o diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b288a50 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "nargv"] + path = nargv + url = https://github.com/hypersoft/nargv diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b033913 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: all install + +all: bash-gitonly pam_oath_key.so auth-keys-gen + +nargv/argvt.o: nargv/nargv.c + gcc -c nargv/nargv.c -o nargv/argvt.o +bash-gitonly: nargv/argvt.o bash-gitonly.c + gcc bash-gitonly.c nargv/argvt.o -I . -o bash-gitonly + +pam_oath_key.so: pam_oath_key.c + gcc pam_oath_key.c -I/usr/system/include/ -I/usr/server/postgresql/include/ -L/usr/server/postgresql/lib/ -L/usr/libraries/lib/ -shared -lpam -lpq -loath -fPIC -o pam_oath_key.so + +auth-keys-gen: authKeysPg.c + gcc authKeysPg.c -o auth-keys-gen -L/usr/server/postgresql/lib -I/usr/server/postgresql/include -lpq + + +install: all + cp pam_oath_key.so /lib/security + cp bash-gitonly /srv/git/bin + cp auth-keys-gen /srv/etc/ diff --git a/authKeysPg.c b/authKeysPg.c new file mode 100644 index 0000000..1fa7195 --- /dev/null +++ b/authKeysPg.c @@ -0,0 +1,98 @@ +#include +#include +#include "libpq-fe.h" +#include + +static void +exit_nicely(PGconn *conn) +{ + PQfinish(conn); + exit(1); +} + +int +main(int argc, char **argv) +{ + + PGconn *conn; + PGresult *res; + int nFields; + + int j,i; + + /* + char *password; + const char *passFileName = "/srv/bdd/pipi-system.pass"; + + FILE *passFile; + + passFile = fopen(passFileName, "r"); + + fgets(password,127,passFile); + fclose(passFile); + + printf("%-15s",password); + + const char *connfirst = "dbname='pipi-system' user=pipiadmin password="; + + //int lgt = strlen(connfirst) + strlen(password); + + char * conninfo = (char *) malloc(256); + + strcpy(conninfo,connfirst); + strcat(conninfo,password); + + /* Crée une connexion à la base de données */ + + char *conninfo = "dbname='pipi-system' user=pipiadmin password='oaelEAAZH3Pr+hej43NnISY+RqkDpl09EHqzWu4XGQWUkG/Tkb+an2Triybhog/lkb/NRYK6b277duP0d3MF'"; + conn = PQconnectdb(conninfo); + + /* Vérifier que la connexion au backend a été faite avec succès */ + if (PQstatus(conn) != CONNECTION_OK) + { + fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); + exit_nicely(conn); + } + + /* Initialise un search path sûr, pour qu'un utilisateur + malveillant ne puisse prendre le contrôle. */ + res = PQexec(conn, + "SELECT pg_catalog.set_config('search_path', '', false)"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + { + fprintf(stderr, "SET failed: %s", PQerrorMessage(conn)); + PQclear(res); + exit_nicely(conn); + } + + /* + * Il faut libérer PGresult avec PQclear dès que l'on en a plus besoin pour + * éviter les fuites de mémoire. + */ + PQclear(res); + + //res = PQprepare(conn,"GettingAllLoginGitUsersInfo","SELECT * FROM $1",1,NULL); + //const char * tablename = "git-users"; + //res = PQexecPrepared(conn,"GettingAllLoginGitUsersInfo",1,"git-users",NULL,NULL,0); + + res = PQexec(conn,"SELECT * FROM login.\"git-users\""); + /* affiche d'abord les noms des attributs */ + nFields = PQnfields(res); + + fprintf(stderr, PQerrorMessage(conn)); + + /* puis affiche les lignes */ + for (i = 0; i < PQntuples(res); i++) + { + printf("ssh-ed25519 %s\n",PQgetvalue(res,i,3)); + //printf("environment=\"GIT_USER_UID=%s\" ssh-ed25519 %s\n",PQgetvalue(res, i, 1),PQgetvalue(res,i,3)); + } + + PQclear(res); + + /* ferme la connexion à la base et nettoie */ + PQfinish(conn); + + return 0; +} + diff --git a/bash-gitonly.c b/bash-gitonly.c new file mode 100644 index 0000000..b7f8658 --- /dev/null +++ b/bash-gitonly.c @@ -0,0 +1,106 @@ +#include +#include +#include + +#include "nargv/nargv.h" + +#include /* for fork */ +#include /* for pid_t */ +#include /* for wait */ + + +#define ANSI_COLOR_GREEN "\x1b[32m" +#define ANSI_COLOR_RESET "\x1b[0m" +#define AUTHORIZED_SHELL_COMMAND "bash-gitonly" + +#define AUTHORIZED_COMMANDS "git-receive-pack","git-upload-pack","git-upload-archive" +#define AUTHORIZED_COMMANDS_COUNT 3 +#define COMMANDS_PATH "/bin/" +#define MAX_FULL_EXECFILENAME_LENGTH 24 + +char* isCmdAuthorized(char* cmd){ + const char* authorizedCommands[] = {AUTHORIZED_COMMANDS}; + for (int i=0; iargc); + fprintf(stderr,"Error code: %i\n",subargv->error_code); + for(int i = 0;iargc;i++){ + fprintf(stderr,"%02d -> %s\n",i,subargv->argv[i]); + } + + if (subargv->error_code) { + fprintf(stderr,"nargv parse error: %i: %s: at input column %i\n", + subargv->error_code, subargv->error_message, subargv->error_index); + return 2; + } + + pid_t pid=fork(); + if (pid==0) { /* child process */ + //static char *argv[]={"echo","Foo is my name.",NULL}; + execve(execFilename, subargv->argv, (char *const []){NULL}); + exit(127); /* only if execv fails */ + } + else { /* pid!=0; parent process */ + waitpid(pid,0,0); /* wait for child to exit */ + } + + nargv_free(subargv); + + + + + return 0; +} diff --git a/nargv b/nargv new file mode 160000 index 0000000..c022a75 --- /dev/null +++ b/nargv @@ -0,0 +1 @@ +Subproject commit c022a75fad52e297bdb6ab98f026e58f516def00 diff --git a/pam_oath_key.c b/pam_oath_key.c new file mode 100644 index 0000000..1c53ad0 --- /dev/null +++ b/pam_oath_key.c @@ -0,0 +1,376 @@ +#include "liboath/oath.h" +#include "libpq-fe.h" +#include +#include +#include +#include + + +// These #defines must be present according to PAM documentation. +#define PAM_SM_AUTH +//#define PAM_SM_ACCOUNT +//#define PAM_SM_SESSION +//#define PAM_SM_PASSWORD + + +#include +#include + + +// Les fonctions de debug +#define D(x) do {\ + printf("[%s:%s(%d)] ", __FILE__, __FUNCTION__, __LINE__);\ + printf x;\ + printf("\n");\ +} while (0) +#define DBG(x) if (cfg.debug) {\ + D(x);\ +} + +#define PAM_EXTERN extern + +// Static options +#define BDD_PASS_FILE "/srv/bdd/pipi-system.pass" +#define BDD_CONN_LENGTH 255 +#define MIN_OTP_LEN 6 +#define DEFAULT_OTP_LEN 6 +#define MAX_OTP_LEN 8 +#define OTP_SECRET_LEN 32 +#define SSH_AUTH_INFO_LEN 1024 + + + +struct cfg { + int debug; + int digits; + int window; +}; + +static void +parse_cfg(int flags, int argc, + const char ** argv, struct cfg * cfg) { + int i; + + cfg -> debug = 0; + cfg -> digits = 6; + cfg -> window = 5; + + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) + cfg -> debug = 1; + if (strncmp(argv[i], "digits=", 7) == 0) + cfg -> digits = atoi(argv[i] + 7); + if (strncmp(argv[i], "window=", 7) == 0) + cfg -> window = atoi(argv[i] + 7); + } + + if (MIN_OTP_LEN > cfg -> digits || cfg -> digits > MAX_OTP_LEN) { + D(("La longueur du code otp doit être entre %i et %i. Valeur invalide: digits=%i",MIN_OTP_LEN,MAX_OTP_LEN, cfg -> digits)); + } + + if (cfg -> debug) { + D(("called.")); + D(("flags %d argc %d", flags, argc)); + for (i = 0; i < argc; i++) + D(("argv[%d]=%s", i, argv[i])); + D(("debug=%d", cfg -> debug)); + D(("digits=%d", cfg -> digits)); + D(("window=%d", cfg -> window)); + } +} + +char * strtokk(char * str,const char cutter) { + + while((*str != cutter) && (*str != '\0')) + str++; + + bool strEnd = (*str == '\0'); + *str = '\0'; + if(!strEnd) + str++; + return str; +} + +char hexCharReader(char hex){ + if('0'<=hex && hex<='9') + return hex-'0'; + else + return hex-'a'+10; +} + +void hexToBytes(char * str, char arr[]){ + int pos = 0; + while(*str != '\0'){ + char b1 = hexCharReader(*str); + str++; + char b0 = hexCharReader(*str); + str++; + arr[pos] = b1<<4 | b0; + pos++; + } + return; +} + +PAM_EXTERN int +pam_sm_authenticate(pam_handle_t * pamh, int flags, int argc, const char ** argv) { + + int retval, rc; + const char * user = NULL; + const char * password = NULL; + char otp[MAX_OTP_LEN + 1]; + int password_len = 0; + struct pam_conv * conv; + struct pam_message * pmsg[1], msg[1]; + struct pam_response * resp; + int nargs = 1; + struct cfg cfg; + char * query_prompt = NULL; + char * onlypasswd = strdup(""); /* empty passwords never match */ + PGconn *conn; + PGresult *res; + int nFields; + char * oathSecret; + + + /***** Parsing config *****/ + parse_cfg(flags, argc, argv, & cfg); + + /***** Getting User *****/ + retval = pam_get_user(pamh, &user, NULL); + if (retval != PAM_SUCCESS) { + DBG(("Could not get PAM user: %s", pam_strerror(pamh, retval))); + goto done; + } + DBG(("We got the user: %s", user)); + + /****** Getting ssh key ******/ + + /* Retrieve SSH authentication information. */ + char * ssh_auth_info_ret = (char *)pam_getenv(pamh, "SSH_AUTH_INFO_0"); + char ssh_auth_info [SSH_AUTH_INFO_LEN]; + ssh_auth_info_ret = "pubkey ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKXQt1YWjKCsjcsFW7o1hdjAB/qxWBwesAeV0RcBeW0I"; + strcpy(ssh_auth_info,ssh_auth_info_ret); + + if (!ssh_auth_info || !*ssh_auth_info) { + DBG(("No SSH auth info, du coup on ignore")); + return PAM_IGNORE; + } + + const char delim = ' '; + + char * authType = ssh_auth_info; + char * sshKeyType = strtokk(ssh_auth_info, delim); + char * sshKeyVal = strtokk(sshKeyType, delim); + + DBG(("Authentification Type: '%s'", authType)); + DBG(("Authentification KeyType: '%s'", sshKeyType)); + DBG(("Authentification KeyVal: '%s'", sshKeyVal)); + + + rc = oath_init(); + if (rc != OATH_OK) { + DBG(("oath_init() failed (%d)", rc)); + retval = PAM_AUTHINFO_UNAVAIL; + goto done; + } + + /****** Get oath secret from database ******/ + char connInfo[BDD_CONN_LENGTH] = "dbname='pipi-system' user=pipiadmin password='"; + char lastAp = '\''; + FILE *dbPassFile; + char ch; + int pos = strlen(connInfo); + + + dbPassFile = fopen(BDD_PASS_FILE,"r"); + if (dbPassFile == NULL) { + DBG(("Cannot open file %s, on peut pas se connecter à la base de données\n", BDD_PASS_FILE)); + retval = PAM_AUTHINFO_UNAVAIL; + goto done; + } + while (feof(dbPassFile)) + { + connInfo[pos] = fgetc(dbPassFile); + pos++; + } + fclose(dbPassFile); + connInfo[pos] = '\''; + + + + conn = PQconnectdb(connInfo); + + /* Vérifier que la connexion au backend a été faite avec succès */ + if (PQstatus(conn) != CONNECTION_OK) { + DBG(("Connection to database failed: %s", PQerrorMessage(conn))); + retval = PAM_AUTH_ERR; + goto done; + } + + res = PQexec(conn, + "SELECT pg_catalog.set_config('search_path', '', false)"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + DBG(("SET failed: %s", PQerrorMessage(conn))); + PQclear(res); + retval = PAM_AUTH_ERR; + goto done; + } + + DBG(("Initialisation de la base de données efféctuée")); + + PQclear(res); + + const char *paramValues[2]; + paramValues[0] = sshKeyType; + paramValues[1] = sshKeyVal; + + res = PQexecParams(conn, "SELECT \"oathPrivate\" FROM login.git WHERE \"sshKeyType\"=$1 AND \"sshPubKey\"=$2",2,NULL,paramValues,NULL,NULL,1); + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + DBG(("SET failed: %s", PQerrorMessage(conn))); + PQclear(res); + retval = PAM_AUTH_ERR; + goto done; + } + + nFields = PQntuples(res); + if(nFields!=1){ + DBG(("Aucun code secret oath trouvé pour la clé '%s'", sshKeyVal)); + PQclear(res); + retval = PAM_AUTH_ERR; + goto done; + } + + oathSecret=PQgetvalue(res, 0, 0); + + DBG(("On a récupéré le code secret : %s",oathSecret)); + + PQclear(res); + PQfinish(conn); + + + /*** Parsing hex to byte array ***/ + + char oathSecretBin[OTP_SECRET_LEN]; + int oathSecretBinLen = OTP_SECRET_LEN+1; + oath_hex2bin(oathSecret,oathSecretBin,&oathSecretBinLen); + + + + + /****** Asking for password ******/ + retval = pam_get_item(pamh, PAM_CONV, (const void ** ) & conv); + if (retval != PAM_SUCCESS) { + DBG(("get conv returned error: %s", pam_strerror(pamh, retval))); + goto done; + } + + pmsg[0] = & msg[0]; + { + const char * query_template = "One-time password (OATH) for `%s': "; + size_t len = strlen(query_template) + strlen(user); + size_t wrote; + + query_prompt = malloc(len); + if (!query_prompt) { + retval = PAM_BUF_ERR; + goto done; + } + + wrote = snprintf(query_prompt, len, query_template, user); + if (wrote < 0 || wrote >= len) { + retval = PAM_BUF_ERR; + goto done; + } + + msg[0].msg = query_prompt; + } + + msg[0].msg_style = PAM_PROMPT_ECHO_OFF; + resp = NULL; + + retval = conv -> conv(nargs, (const struct pam_message ** ) pmsg, & + resp, conv -> appdata_ptr); + + free(query_prompt); + query_prompt = NULL; + + if (retval != PAM_SUCCESS) { + DBG(("conv returned error: %s", pam_strerror(pamh, retval))); + goto done; + } + + DBG(("conv returned: %s", resp -> resp)); + + password = resp -> resp; + + if (password) + password_len = strlen(password); + else { + DBG(("Could not read password")); + retval = PAM_AUTH_ERR; + goto done; + } + + if (password_len < MIN_OTP_LEN) { + DBG(("OTP too short: %s", password)); + retval = PAM_AUTH_ERR; + goto done; + } else if (password_len < cfg.digits) { + DBG(("OTP shorter than digits=%d: %s", cfg.digits, password)); + retval = PAM_AUTH_ERR; + goto done; + } else if (password_len > MAX_OTP_LEN) { + DBG(("OTP too long (and no digits=): %s", password)); + retval = PAM_AUTH_ERR; + goto done; + } else { + strcpy(otp, password); + password = NULL; + } + + DBG(("Pouf ! OTP: %s", otp ? otp : "(null)")); + + + + + /****** Validation du mot de passe oath ******/ + { + time_t last_otp; + time_t jetzt = 0; + time(&jetzt); + int timeStep = 30; + DBG(("Validating oath secret %s of length %i at time %ld",oathSecret,oathSecretBinLen, jetzt)); + char cdex[32]; + rc = oath_totp_validate(oathSecretBin, oathSecretBinLen, jetzt, timeStep, 0, cfg.window, otp); + } + + if (rc == OATH_INVALID_OTP) { + DBG(("One-time password not authorized to login as user '%s'", user)); + retval = PAM_AUTH_ERR; + goto done; + } + + retval = PAM_SUCCESS; + + + + + /****** Terminé ! ******/ + done: + + oath_done(); + free(query_prompt); + free(onlypasswd); + + DBG(("Terminé ! [%s]", pam_strerror(pamh, retval))); + + return retval; +} + + +PAM_EXTERN +int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc + ,const char **argv) +{ + return PAM_SUCCESS; +}