diff --git a/cli/Makefile b/cli/Makefile new file mode 100644 index 0000000..9d5d910 --- /dev/null +++ b/cli/Makefile @@ -0,0 +1,12 @@ +CC=gcc +CFLAGS=-Wall -O2 +LDFLAGS= -lcrypto -lcurl +TARGET=gpass_cli +SRCS=main.c ini.c + + +$(TARGET): $(SRCS) + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + +clean: + rm -f $(TARGET) *.o *~ \ No newline at end of file diff --git a/cli/gpass.ini.sample b/cli/gpass.ini.sample new file mode 100644 index 0000000..463c94b --- /dev/null +++ b/cli/gpass.ini.sample @@ -0,0 +1,6 @@ +[params] +# ca_path=./ca_path/ca_authority.pem +# server=https://demo-gpass.soutade.fr/demo +# pkdbf2_level=1000 +# server_port=443 +# verify_ssl_peer=1 diff --git a/cli/ini.c b/cli/ini.c new file mode 100644 index 0000000..27ca85b --- /dev/null +++ b/cli/ini.c @@ -0,0 +1,194 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; +#else + char* line; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)malloc(INI_MAX_LINE); + if (!line) { + return -2; + } +#endif + + /* Scan through stream line by line */ + while (reader(line, INI_MAX_LINE, stream) != NULL) { + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/cli/ini.h b/cli/ini.h new file mode 100644 index 0000000..eaa554d --- /dev/null +++ b/cli/ini.h @@ -0,0 +1,93 @@ +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Typedef for prototype of handler function. */ +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Maximum line length for any line in INI file. */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/cli/main.c b/cli/main.c new file mode 100644 index 0000000..31848c1 --- /dev/null +++ b/cli/main.c @@ -0,0 +1,578 @@ +/* + Copyright (C) 2013-2016 Grégory Soutadé + + This file is part of gPass. + + gPass 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. + + gPass 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 gPass. If not, see . +*/ + +#include +#include +#include +#include + +#include +#include + +#include "ini.h" + +#define STRNCMP(a, b) strncmp(a, b, sizeof(b)-1) + +#define DEFAULT_CONFIG_FILE ".local/share/gpass/gpass.ini" + +#define DEFAULT_PKDBF2_LEVEL 1000 +#define MASTER_KEY_LENGTH (256/8) +#define BLOCK_SIZE (128/8) +#define DEFAULT_SERVER_PORT 443 +#define SERVER_PROTOCOL 3 +#define RESPONSE_SIZE 2048 +#define MAX_SUBDOMAINS 10 + +struct gpass_parameters { + unsigned pkdbf2_level; + char *server; + char *salt; + char *domain; + char *username; + char *orig_master_key; + unsigned char *derived_master_key; + unsigned server_port; + unsigned verbose; + char *ca_path; + unsigned verify_ssl_peer; + unsigned port_set; +} ; + +static void derive_master_key(struct gpass_parameters* params) +{ + if (!params->derived_master_key) + params->derived_master_key = malloc(MASTER_KEY_LENGTH); + + PKCS5_PBKDF2_HMAC(params->orig_master_key, strlen(params->orig_master_key), + (unsigned char*)params->salt, strlen(params->salt), + params->pkdbf2_level, EVP_sha256(), + MASTER_KEY_LENGTH, params->derived_master_key); +} + +static void bin_to_hex(unsigned char* bin, unsigned char* hex, unsigned bin_size) +{ + unsigned char tmp; + + for (; bin_size--; bin++) + { + tmp = (*bin >> 4) & 0xf; + if (tmp <= 9) + *hex++ = '0' + tmp; + else + *hex++ = 'a' + (tmp-10); + + tmp = *bin & 0xf; + if (tmp <= 9) + *hex++ = '0' + tmp; + else + *hex++ = 'a' + (tmp-10); + } +} + +static void hex_to_bin(unsigned char* bin, unsigned char* hex, long hex_size) +{ + unsigned char tmp; + + // Round to 2 + hex_size &= ~1; + + for (; hex_size; hex_size-=2, bin++) + { + tmp = *hex++; + if (tmp >= '0' && tmp <= '9') + *bin = (tmp - '0') << 4; + else if (tmp >= 'a' && tmp <= 'f') + *bin = ((tmp - 'a')+10) << 4; + else + *bin = ((tmp - 'A')+10) << 4; + + tmp = *hex++; + if (tmp >= '0' && tmp <= '9') + *bin |= (tmp - '0'); + else if (tmp >= 'a' && tmp <= 'f') + *bin |= ((tmp - 'a')+10); + else + *bin |= ((tmp - 'A')+10); + } +} + +static void encrypt_domain(struct gpass_parameters* params, char* domain, + unsigned char** res, unsigned* out_size) +{ + EVP_CIPHER_CTX* evp_ctx; + unsigned size = 2+strlen(domain)+1+strlen(params->username)+1; + unsigned char* buffer, *tmp; + + if (params->verbose) + printf("%s: %s\n", __func__, domain); + + if ((size % BLOCK_SIZE)) + size = ((size/BLOCK_SIZE)+1)*BLOCK_SIZE; + + buffer = malloc(size); + memset(buffer, 0, size); + + snprintf((char*)buffer, size, "@@%s;%s", domain, params->username); + + tmp = malloc(size); + *res = malloc(size*2); + + evp_ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit(evp_ctx, EVP_aes_256_ecb(), params->derived_master_key, NULL); + EVP_CipherUpdate(evp_ctx, tmp, (int*)out_size, buffer, size); + EVP_CIPHER_CTX_free(evp_ctx); + + bin_to_hex(tmp, *res, size); + + *out_size *= 2; + + free(buffer); + free(tmp); +} + +static void append_to_request(char** request, char* new_req) +{ + static int cur_req_idx = 0; + int size_added; + + if (!cur_req_idx) + { + *request = malloc(3+strlen(new_req)+1); + sprintf(*request, "k0=%s", new_req); + } + else + { + size_added = 4+strlen(new_req); + if (cur_req_idx >= 10) + size_added++; + + *request = realloc(*request, strlen(*request)+1+size_added); + + sprintf(&((*request)[strlen(*request)]), "&k%d=%s", cur_req_idx, new_req); + } + + cur_req_idx++; +} + +static char* wildcard_domain(char* domain) +{ + int cur_level = 1; + char* level_ptr[MAX_SUBDOMAINS], *tmp, *res = NULL; + int level_length[MAX_SUBDOMAINS]; + + memset(level_ptr, 0, sizeof(level_ptr)); + memset(level_length, 0, sizeof(level_length)); + level_ptr[0] = domain; + + for (tmp=domain; *tmp && cur_level < MAX_SUBDOMAINS; tmp++) + { + if (*tmp == '.') + { + level_ptr[cur_level] = tmp+1; + level_length[cur_level-1] = tmp - level_ptr[cur_level-1]; + cur_level++; + } + } + + // Too much levels + if (cur_level == MAX_SUBDOMAINS) + { + fprintf(stderr, "Error: Too much levels for domain %s\n", domain); + return NULL; + } + + // Final level + level_length[cur_level] = tmp - level_ptr[cur_level-1]; + + tmp = NULL; + if (cur_level >= 3) + { + // Seems to be a two level root domain (ie zzz.xxx.co.uk ...) + if (level_length[cur_level-2] <= 3) + { + if (cur_level > 3) + tmp = level_ptr[cur_level-3]; + } + else + // Standard root domain (zzz.xxx.com) + tmp = level_ptr[cur_level-2]; + } + // Simple xxx.com + else if (cur_level == 2) + tmp = level_ptr[0]; + + if (tmp) + { + res = malloc(2+strlen(tmp)+1); + sprintf(res, "*.%s", tmp); + } + + return res; +} + +static size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + if ((size*nmemb) > RESPONSE_SIZE) + { + fprintf(stderr, "Error curl response is too big (%d bytes, max %d bytes)\n", + (int)(size*nmemb), RESPONSE_SIZE); + } + else + memcpy(userdata, ptr, size*nmemb); + + return size*nmemb; +} + +static int ask_server(struct gpass_parameters* params) +{ + char* wc_domain, *saveptr, *token, *cur_ptr; + unsigned char* enc_domain; + unsigned enc_size; + char* request = NULL; + int ret = -1, res; + CURL *curl; + EVP_CIPHER_CTX* evp_ctx; + char response[RESPONSE_SIZE]; + unsigned char password[256]; + + encrypt_domain(params, params->domain, &enc_domain, &enc_size); + append_to_request(&request, (char*)enc_domain); + free(enc_domain); + + wc_domain = wildcard_domain(params->domain); + if (wc_domain) + { + encrypt_domain(params, wc_domain, &enc_domain, &enc_size); + append_to_request(&request, (char*)enc_domain); + free(enc_domain); + } + + if (params->verbose) + printf("Request: %s\n", request); + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, params->server); + curl_easy_setopt(curl, CURLOPT_PORT, params->server_port); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, params->verify_ssl_peer); + if (params->ca_path) + curl_easy_setopt(curl, CURLOPT_CAINFO, params->ca_path); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request); + if (params->verbose) + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)response); + + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + + if (res != CURLE_OK) + { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + goto end; + } + + token = strtok_r(response, "\n", &saveptr); + + while (token) + { + if (params->verbose) + printf("Parse %s\n", token); + cur_ptr = token; + if (!strcmp(token, "")) + break; + else if (!STRNCMP(token, "protocol")) + { + cur_ptr += sizeof("protocol"); // includes "=" + if (STRNCMP(cur_ptr, "gpass-")) + { + fprintf(stderr, "Error: Unknown server protocol %s\n", token); + break; + } + else + { + cur_ptr += sizeof("gpass-")-1; + if (atoi(cur_ptr) > SERVER_PROTOCOL) + { + fprintf(stderr, "Error: Cannot handle server protocol %s\n", token); + break; + } + } + } + else if (!STRNCMP(token, "pass")) + { + cur_ptr += sizeof("pass"); // includes "=" + + if ((strlen(cur_ptr)/2) > sizeof(password)) + { + fprintf(stderr, "Error: retrieved password is too big !\n"); + goto end; + } + + hex_to_bin(password, (unsigned char*)cur_ptr, strlen(cur_ptr)); + + evp_ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit(evp_ctx, EVP_aes_256_ecb(), params->derived_master_key, NULL); + EVP_CipherUpdate(evp_ctx, password, &res, password, strlen(cur_ptr)/2); + EVP_CIPHER_CTX_free(evp_ctx); + + // Remove salt + password[strlen((char*)password)-3] = 0; + printf("Password found: %s\n", password); + ret = 0; + goto end; + } + else if (!STRNCMP(token, "pkdbf2_level")) + { + cur_ptr += sizeof("pkdbf2_level"); // includes "=" + + if (atoi(cur_ptr) != params->pkdbf2_level) + { + params->pkdbf2_level = atoi(cur_ptr); + ret = 1; + break; + } + } + else + { + fprintf(stderr, "Error: Unknown server response %s\n", token); + break; + } + token = strtok_r(NULL, "\n", &saveptr); + } + + if (ret) + printf("Password not found\n"); + +end: + free(request); + + return ret; +} + +static void init_parameters(struct gpass_parameters* params) +{ + memset (params, 0, sizeof(*params)); + params->pkdbf2_level = DEFAULT_PKDBF2_LEVEL; + params->server_port = DEFAULT_SERVER_PORT; + params->verify_ssl_peer = 1; +} + +static void release_parameters(struct gpass_parameters* params) +{ + if (params->server) free(params->server); + if (params->salt) free(params->salt); + if (params->domain) free(params->domain); + if (params->username) free(params->username); + if (params->orig_master_key) free(params->orig_master_key); + if (params->derived_master_key) free(params->derived_master_key); + if( params->ca_path) free(params->ca_path); +} + +static int check_parameters(struct gpass_parameters* params) +{ + if (!params->server) + { + fprintf(stderr, "Error: server not set\n"); + return 1; + } + + if (!params->domain) + { + fprintf(stderr, "Error: gpass domain not set\n"); + return 1; + } + + if (!params->username) + { + fprintf(stderr, "Error: username not set\n"); + return 1; + } + + return 0; +} + +static int gpass_ini_handler(void* user, const char* section, + const char* name, const char* value) +{ + struct gpass_parameters* params = (struct gpass_parameters*) user; + + if (!STRNCMP(name, "ca_path")) + { + if (params->ca_path) free(params->ca_path); + params->ca_path = strdup(value); + } + else if (!STRNCMP(name, "pkdbf2_level")) + params->pkdbf2_level = atoi(value); + else if (!STRNCMP(name, "verify_ssl_peer")) + params->verify_ssl_peer = atoi(value); + else if (!STRNCMP(name, "server_port")) + { + params->server_port = atoi(value); + params->port_set = 1; + } + else if (!STRNCMP(name, "server")) + { + if (params->server) free(params->server); + params->server = strdup(value); + } + else + fprintf(stderr, "Error: Unknown key '%s' in config file\n", name); + + return 1; +} + +static void usage(char* program_name) +{ + fprintf(stderr, "Usage: %s [-f config_file] [-p server_port] [-c CA_certificate_path] [-l PKDBF2_level] [-s gpass_server] [-v] -d domain -u username\n", + program_name); + exit(EXIT_FAILURE); +} + +int main(int argc, char** argv) +{ + struct gpass_parameters params; + int opt, ret = 0; + char* tmp; + char* config_file, *home; + + if (argc == 1) + usage(argv[0]); + + init_parameters(¶ms); + + home = getenv("HOME"); + if (home) + { + config_file = malloc(strlen(home)+1+sizeof(DEFAULT_CONFIG_FILE)); + sprintf(config_file, "%s/" DEFAULT_CONFIG_FILE, home); + + ini_parse(config_file, gpass_ini_handler, ¶ms); + + free(config_file); + } + + while ((opt = getopt(argc, argv, "c:d:f:l:np:s:u:vh")) != -1) { + switch (opt) { + case 'c': + if (params.ca_path) free(params.ca_path); + params.ca_path = strdup(optarg); + break; + case 'd': + if (params.domain) free(params.domain); + params.domain = strdup(optarg); + break; + case 'f': + ini_parse(optarg, gpass_ini_handler, ¶ms); + break; + case 'l': + params.pkdbf2_level = atoi(optarg); + break; + case 'n': + params.verify_ssl_peer = 0; + break; + case 'p': + params.server_port = atoi(optarg); + params.port_set = 1; + break; + case 's': + if (params.server) free(params.server); + params.server = strdup(optarg); + break; + case 'u': + if (params.username) free(params.username); + params.username = strdup(optarg); + break; + case 'v': + params.verbose++; + break; + case 'h': + case '?': + default: /* '?' */ + usage(argv[0]); + } + } + + ret = check_parameters(¶ms); + + if (ret) + goto end; + + // Manage server, server_port and salt + if (!STRNCMP(params.server, "http://")) + { + if (!params.port_set) + params.server_port = 80; + params.salt = strdup(¶ms.server[7]); + } + else if (!STRNCMP(params.server, "https://")) + { + if (!params.port_set) + params.server_port = 443; + params.salt = strdup(¶ms.server[8]); + } + + // Manage domain + if (!STRNCMP(params.domain, "http://")) + { + tmp = strdup(¶ms.domain[7]); + free(params.domain); + params.domain = tmp; + } + else if (!STRNCMP(params.domain, "https://")) + { + tmp = strdup(¶ms.domain[8]); + free(params.domain); + params.domain = tmp; + } + + // Remove query part of domain (a.com[/XXXX]) + for (tmp=params.domain; *tmp; tmp++) + { + if (*tmp == '/') + { + *tmp = 0; + break; + } + } + + // Let's go + tmp = getpass("Enter master key: "); + + if (!tmp) + goto end; + + params.orig_master_key = strdup(tmp); + derive_master_key(¶ms); + + ret = ask_server(¶ms); + + // try again with new parameters + if (ret > 0) + { + derive_master_key(¶ms); + ask_server(¶ms); + } + +end: + release_parameters(¶ms); + + return ret; +}