/* * Build the first server-side message sent to the client in a SCRAM * communication exchange. */ static char * build_server_first_message(scram_state *state) { /*------ * The syntax for the server-first-message is: (RFC 5802) * * server-first-message = * [reserved-mext ","] nonce "," salt "," * iteration-count ["," extensions] * * nonce = "r=" c-nonce [s-nonce] * ;; Second part provided by server. * * c-nonce = printable * * s-nonce = printable * * salt = "s=" base64 * * iteration-count = "i=" posit-number * ;; A positive number. * * Example: * * r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096 *------ */ /* * Per the spec, the nonce may consist of any printable ASCII characters. * For convenience, however, we don't use the whole range available, * rather, we generate some random bytes, and base64 encode them. */ char raw_nonce[SCRAM_RAW_NONCE_LEN]; int encoded_len; if (!pg_backend_random(raw_nonce, SCRAM_RAW_NONCE_LEN)) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate random nonce"))); state->server_nonce = palloc(pg_b64_enc_len(SCRAM_RAW_NONCE_LEN) + 1); encoded_len = pg_b64_encode(raw_nonce, SCRAM_RAW_NONCE_LEN, state->server_nonce); state->server_nonce[encoded_len] = '\0'; state->server_first_message = psprintf("r=%s%s,s=%s,i=%u", state->client_nonce, state->server_nonce, state->salt, state->iterations); return pstrdup(state->server_first_message); }
/* * Build the final server-side message of an exchange. */ static char * build_server_final_message(scram_state *state) { uint8 ServerSignature[SCRAM_KEY_LEN]; char *server_signature_base64; int siglen; scram_HMAC_ctx ctx; /* calculate ServerSignature */ scram_HMAC_init(&ctx, state->ServerKey, SCRAM_KEY_LEN); scram_HMAC_update(&ctx, state->client_first_message_bare, strlen(state->client_first_message_bare)); scram_HMAC_update(&ctx, ",", 1); scram_HMAC_update(&ctx, state->server_first_message, strlen(state->server_first_message)); scram_HMAC_update(&ctx, ",", 1); scram_HMAC_update(&ctx, state->client_final_message_without_proof, strlen(state->client_final_message_without_proof)); scram_HMAC_final(ServerSignature, &ctx); server_signature_base64 = palloc(pg_b64_enc_len(SCRAM_KEY_LEN) + 1); siglen = pg_b64_encode((const char *) ServerSignature, SCRAM_KEY_LEN, server_signature_base64); server_signature_base64[siglen] = '\0'; /*------ * The syntax for the server-final-message is: (RFC 5802) * * verifier = "v=" base64 * ;; base-64 encoded ServerSignature. * * server-final-message = (server-error / verifier) * ["," extensions] * *------ */ return psprintf("v=%s", server_signature_base64); }
static void mock_scram_verifier(const char *username, int *iterations, char **salt, uint8 *stored_key, uint8 *server_key) { char *raw_salt; char *encoded_salt; int encoded_len; /* Generate deterministic salt */ raw_salt = scram_mock_salt(username); encoded_salt = (char *) palloc(pg_b64_enc_len(SCRAM_DEFAULT_SALT_LEN) + 1); encoded_len = pg_b64_encode(raw_salt, SCRAM_DEFAULT_SALT_LEN, encoded_salt); encoded_salt[encoded_len] = '\0'; *salt = encoded_salt; *iterations = SCRAM_DEFAULT_ITERATIONS; /* StoredKey and ServerKey are not used in a doomed authentication */ memset(stored_key, 0, SCRAM_KEY_LEN); memset(server_key, 0, SCRAM_KEY_LEN); }
/* * Read and parse the final message received from client. */ static void read_client_final_message(scram_state *state, char *input) { char attr; char *channel_binding; char *value; char *begin, *proof; char *p; char *client_proof; begin = p = pstrdup(input); /*------ * The syntax for the server-first-message is: (RFC 5802) * * gs2-header = gs2-cbind-flag "," [ authzid ] "," * ;; GS2 header for SCRAM * ;; (the actual GS2 header includes an optional * ;; flag to indicate that the GSS mechanism is not * ;; "standard", but since SCRAM is "standard", we * ;; don't include that flag). * * cbind-input = gs2-header [ cbind-data ] * ;; cbind-data MUST be present for * ;; gs2-cbind-flag of "p" and MUST be absent * ;; for "y" or "n". * * channel-binding = "c=" base64 * ;; base64 encoding of cbind-input. * * proof = "p=" base64 * * client-final-message-without-proof = * channel-binding "," nonce ["," * extensions] * * client-final-message = * client-final-message-without-proof "," proof *------ */ /* * Read channel binding. This repeats the channel-binding flags and is * then followed by the actual binding data depending on the type. */ channel_binding = read_attr_value(&p, 'c'); if (state->channel_binding_in_use) { #ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH const char *cbind_data = NULL; size_t cbind_data_len = 0; size_t cbind_header_len; char *cbind_input; size_t cbind_input_len; char *b64_message; int b64_message_len; Assert(state->cbind_flag == 'p'); /* Fetch hash data of server's SSL certificate */ cbind_data = be_tls_get_certificate_hash(state->port, &cbind_data_len); /* should not happen */ if (cbind_data == NULL || cbind_data_len == 0) elog(ERROR, "could not get server certificate hash"); cbind_header_len = strlen("p=tls-server-end-point,,"); /* p=type,, */ cbind_input_len = cbind_header_len + cbind_data_len; cbind_input = palloc(cbind_input_len); snprintf(cbind_input, cbind_input_len, "p=tls-server-end-point,,"); memcpy(cbind_input + cbind_header_len, cbind_data, cbind_data_len); b64_message = palloc(pg_b64_enc_len(cbind_input_len) + 1); b64_message_len = pg_b64_encode(cbind_input, cbind_input_len, b64_message); b64_message[b64_message_len] = '\0'; /* * Compare the value sent by the client with the value expected by the * server. */ if (strcmp(channel_binding, b64_message) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), (errmsg("SCRAM channel binding check failed")))); #else /* shouldn't happen, because we checked this earlier already */ elog(ERROR, "channel binding not supported by this build"); #endif } else { /* * If we are not using channel binding, the binding data is expected * to always be "biws", which is "n,," base64-encoded, or "eSws", * which is "y,,". We also have to check whether the flag is the same * one that the client originally sent. */ if (!(strcmp(channel_binding, "biws") == 0 && state->cbind_flag == 'n') && !(strcmp(channel_binding, "eSws") == 0 && state->cbind_flag == 'y')) ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), (errmsg("unexpected SCRAM channel-binding attribute in client-final-message")))); } state->client_final_nonce = read_attr_value(&p, 'r'); /* ignore optional extensions */ do { proof = p - 1; value = read_any_attr(&p, &attr); } while (attr != 'p'); client_proof = palloc(pg_b64_dec_len(strlen(value))); if (pg_b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN) ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("malformed SCRAM message"), errdetail("Malformed proof in client-final-message."))); memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN); pfree(client_proof); if (*p != '\0') ereport(ERROR, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("malformed SCRAM message"), errdetail("Garbage found at the end of client-final-message."))); state->client_final_message_without_proof = palloc(proof - begin + 1); memcpy(state->client_final_message_without_proof, input, proof - begin); state->client_final_message_without_proof[proof - begin] = '\0'; }
int main(int argc, char *argv[]) { #define PRINT_USAGE(exit_code) print_usage(argv[0], exit_code) char conf_file[POOLMAXPATHLEN + 1]; char enc_key[MAX_POOL_KEY_LEN + 1]; char pg_pass[MAX_PGPASS_LEN + 1]; char username[MAX_USER_NAME_LEN + 1]; char key_file_path[POOLMAXPATHLEN + sizeof(POOLKEYFILE) + 1]; int opt; int optindex; bool updatepasswd = false; bool prompt = false; bool prompt_for_key = false; char *pool_key = NULL; static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"prompt", no_argument, NULL, 'p'}, {"prompt-for-key", no_argument, NULL, 'P'}, {"update-pass", no_argument, NULL, 'm'}, {"username", required_argument, NULL, 'u'}, {"enc-key", required_argument, NULL, 'K'}, {"key-file", required_argument, NULL, 'k'}, {"config-file", required_argument, NULL, 'f'}, {NULL, 0, NULL, 0} }; snprintf(conf_file, sizeof(conf_file), "%s/%s", DEFAULT_CONFIGDIR, POOL_CONF_FILE_NAME); /* * initialize username buffer with zeros so that we can use strlen on it * later to check if a username was given on the command line */ memset(username, 0, sizeof(username)); memset(enc_key, 0, sizeof(enc_key)); memset(key_file_path, 0, sizeof(key_file_path)); while ((opt = getopt_long(argc, argv, "hPpmf:u:k:K:", long_options, &optindex)) != -1) { switch (opt) { case 'p': /* prompt for postgres password */ prompt = true; break; case 'P': /* prompt for encryption key */ prompt_for_key = true; break; case 'm': /* update password file */ updatepasswd = true; break; case 'f': /* specify configuration file */ if (!optarg) { PRINT_USAGE(EXIT_SUCCESS); } strlcpy(conf_file, optarg, sizeof(conf_file)); break; case 'k': /* specify key file for encrypting * pool_password entries */ if (!optarg) { PRINT_USAGE(EXIT_SUCCESS); } strlcpy(key_file_path, optarg, sizeof(key_file_path)); break; case 'K': /* specify configuration file */ if (!optarg) { PRINT_USAGE(EXIT_SUCCESS); } strlcpy(enc_key, optarg, sizeof(enc_key)); break; case 'u': if (!optarg) { PRINT_USAGE(EXIT_SUCCESS); } /* check the input limit early */ if (strlen(optarg) > sizeof(username)) { fprintf(stderr, "Error: input exceeds maximum username length!\n\n"); exit(EXIT_FAILURE); } strlcpy(username, optarg, sizeof(username)); break; default: PRINT_USAGE(EXIT_SUCCESS); break; } } /* Prompt for password. */ if (prompt || optind >= argc) { char buf[MAX_PGPASS_LEN]; int len; set_tio_attr(1); printf("db password: "******"Couldn't read input from stdin. (fgets(): %s)", strerror(eno)); exit(EXIT_FAILURE); } printf("\n"); set_tio_attr(0); /* Remove LF at the end of line, if there is any. */ len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') { buf[len - 1] = '\0'; len--; } stpncpy(pg_pass, buf, sizeof(pg_pass)); } /* Read password from argv. */ else { int len; len = strlen(argv[optind]); if (len > MAX_PGPASS_LEN) { fprintf(stderr, "Error: Input exceeds maximum password length given:%d max allowed:%d!\n\n", len, MAX_PGPASS_LEN); PRINT_USAGE(EXIT_FAILURE); } stpncpy(pg_pass, argv[optind], sizeof(pg_pass)); } /* prompt for key, overrides all key related arguments */ if (prompt_for_key) { char buf[MAX_POOL_KEY_LEN]; int len; /* we need to read the encryption key from stdin */ set_tio_attr(1); printf("encryption key: "); if (!fgets(buf, sizeof(buf), stdin)) { int eno = errno; fprintf(stderr, "Couldn't read input from stdin. (fgets(): %s)", strerror(eno)); exit(EXIT_FAILURE); } printf("\n"); set_tio_attr(0); /* Remove LF at the end of line, if there is any. */ len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') { buf[len - 1] = '\0'; len--; } if (len == 0) { fprintf(stderr, "encryption key not provided\n"); exit(EXIT_FAILURE); } stpncpy(enc_key, buf, sizeof(enc_key)); } else { /* check if we already have not got the key from command line argument */ if (strlen(enc_key) == 0) { /* read from file */ if (strlen(key_file_path) == 0) { get_pool_key_filename(key_file_path); } fprintf(stdout, "trying to read key from file %s\n", key_file_path); pool_key = read_pool_key(key_file_path); } else { pool_key = enc_key; } } if (pool_key == NULL) { fprintf(stderr, "encryption key not provided\n"); exit(EXIT_FAILURE); } if (updatepasswd) { update_pool_passwd(conf_file, username, pg_pass, pool_key); } else { unsigned char ciphertext[MAX_ENCODED_PASSWD_LEN]; unsigned char b64_enc[MAX_ENCODED_PASSWD_LEN]; int len; int cypher_len; cypher_len = aes_encrypt_with_password((unsigned char *) pg_pass, strlen(pg_pass), pool_key, ciphertext); /* generate the hash for the given username */ len = pg_b64_encode((const char *) ciphertext, cypher_len, (char *) b64_enc); b64_enc[len] = 0; fprintf(stdout, "\n%s\n", b64_enc); fprintf(stdout, "pool_passwd string: AES%s\n", b64_enc); #ifdef DEBUG_ENCODING unsigned char b64_dec[MAX_ENCODED_PASSWD_LEN]; unsigned char plaintext[MAX_PGPASS_LEN]; len = pg_b64_decode(b64_enc, len, b64_dec); len = aes_decrypt_with_password(b64_dec, len, pool_key, plaintext); plaintext[len] = 0; #endif } if (pool_key != enc_key) free(pool_key); return EXIT_SUCCESS; }
static void update_pool_passwd(char *conf_file, char *username, char *password, char *key) { struct passwd *pw; char pool_passwd[MAX_PGPASS_LEN + 1]; char dirnamebuf[POOLMAXPATHLEN + 1]; char *dirp; char *user = username; unsigned char ciphertext[MAX_ENCODED_PASSWD_LEN]; unsigned char b64_enc[MAX_ENCODED_PASSWD_LEN]; int len; if (pool_init_config()) { fprintf(stderr, "pool_init_config() failed\n\n"); exit(EXIT_FAILURE); } if (pool_get_config(conf_file, CFGCXT_RELOAD) == false) { fprintf(stderr, "Unable to get configuration. Exiting...\n\n"); exit(EXIT_FAILURE); } strlcpy(dirnamebuf, conf_file, sizeof(dirnamebuf)); dirp = dirname(dirnamebuf); snprintf(pool_passwd, sizeof(pool_passwd), "%s/%s", dirp, pool_config->pool_passwd); pool_init_pool_passwd(pool_passwd, POOL_PASSWD_RW); if (username == NULL || strlen(username) == 0) { /* get the user information from the current uid */ pw = getpwuid(getuid()); if (!pw) { fprintf(stderr, "getpwuid() failed\n\n"); exit(EXIT_FAILURE); } user = pw->pw_name; } /* generate the hash for the given username */ int cypher_len = aes_encrypt_with_password((unsigned char *) password, strlen(password), key, ciphertext); if (cypher_len <= 0) { fprintf(stderr, "password encryption failed\n\n"); exit(EXIT_FAILURE); } /* copy the prefix at the start of string */ strcpy((char *) b64_enc, (char *) PASSWORD_AES_PREFIX); len = pg_b64_encode((const char *) ciphertext, cypher_len, (char *) b64_enc + strlen(PASSWORD_AES_PREFIX)); if (cypher_len <= 0) { fprintf(stderr, "base64 encoding failed\n\n"); exit(EXIT_FAILURE); } len += strlen(PASSWORD_AES_PREFIX); b64_enc[len] = 0; pool_create_passwdent(user, (char *) b64_enc); pool_finish_pool_passwd(); }