diff --git a/cli/commands.c b/cli/commands.c index 97d03734..eabc9d38 100644 --- a/cli/commands.c +++ b/cli/commands.c @@ -270,10 +270,10 @@ int verify(int argc, char **argv) { return ret; } -static bool parse_login_path(char *path, char **handshake, char **host, - char **action) { +static bool parse_login_path(const char *path, char **handshake, + char **message) { size_t path_len = strlen(path); - if (path_len < 3 || path[0] != 'v' || path[1] != '1' || path[2] != '/') { + if (path_len < 3 || path[0] != 'v' || path[1] != '2' || path[2] != '/') { fprintf(stderr, "unexpected challenge prefix: %s\n", path); return false; } @@ -282,7 +282,7 @@ static bool parse_login_path(char *path, char **handshake, char **host, return false; } - char *start = path + 3; + const char *start = path + 3; char *slash = strchr(start, '/'); if (slash == NULL || slash - start == 0) { fprintf(stderr, "could not parse handshake from %s\n", start); @@ -294,93 +294,23 @@ static bool parse_login_path(char *path, char **handshake, char **host, return false; } + // Everything left (not including the trailing slash) is the message. start = slash + 1; - slash = strchr(start, '/'); - if (slash == NULL || slash - start == 0) { - free(*handshake); - *handshake = NULL; - fprintf(stderr, "could not parse host from %s\n", start); - return false; - } - *host = strndup(start, slash - start); - if (*host == NULL) { + *message = strndup(start, path + path_len - 1 - start); + if (*message == NULL) { free(*handshake); *handshake = NULL; - fprintf(stderr, "failed to duplicate host\n"); - return false; - } - - // Everything left (not including the trailing slash) is an action. It may - // include slashes and it can also be empty. - start = slash + 1; - *action = strndup(start, path + path_len - 1 - start); - if (*action == NULL) { - free(*host); - *host = NULL; - free(*handshake); - *handshake = NULL; - fprintf(stderr, "failed to duplicate action\n"); + fprintf(stderr, "failed to duplicate message\n"); return false; } return true; } -static int unhex(char c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } else if (c >= 'a' && c <= 'f') { - return c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - return c - 'A' + 10; - } else { - return -1; - } -} - -static char *uri_unescape(char *e) { - size_t len = strlen(e); - char *u = malloc(len + 1); - if (u == NULL) { - return NULL; - } - - size_t i, j; - for (i = 0, j = 0; i < len; j++) { - if (e[i] != '%') { - u[j] = e[i]; - i += 1; - continue; - } - if (i + 2 >= len) { - goto fail; - } - int n = unhex(e[i + 1]); - if (n < 0) { - goto fail; - } - u[j] = (char)(n << 4); - n = unhex(e[i + 2]); - if (n < 0) { - goto fail; - } - u[j] |= (char)n; - i += 3; - } - u[j] = '\0'; - return u; - -fail: - free(u); - return NULL; -} - int login(int argc, char **argv) { char *handshake = NULL; char *handshake_b64 = NULL; - char *host = NULL; - char *host_esc = NULL; - char *action = NULL; + char *message = NULL; int ret = EXIT_FAILURE; if (!parse_args(argc, argv)) { @@ -401,16 +331,14 @@ int login(int argc, char **argv) { return EXIT_FAILURE; } - char *path = strstr(argv[optind], "v1/"); - if (!parse_login_path(path, &handshake_b64, &host_esc, &action)) { - return EXIT_FAILURE; - } - - host = uri_unescape(host_esc); - if (host == NULL) { - fprintf(stderr, "failed to parse hostname in path %s\n", path); + char *path = strstr(argv[optind], "v2/"); + if (path == NULL) { + fprintf(stderr, "unsupported challenge format\n"); goto out; } + if (!parse_login_path(path, &handshake_b64, &message)) { + return EXIT_FAILURE; + } size_t handshake_b64_len = strlen(handshake_b64); handshake = malloc(DECODED_BUFSIZE(handshake_b64_len)); @@ -440,7 +368,8 @@ int login(int argc, char **argv) { memcpy(peer_key, handshake + 1, GLOME_MAX_PUBLIC_KEY_LENGTH); uint8_t tag[GLOME_MAX_TAG_LENGTH] = {0}; - if (get_authcode(host, action, peer_key, private_key, tag)) { + if (glome_tag(true, 0, private_key, peer_key, (uint8_t *)message, + strlen(message), tag)) { fprintf(stderr, "MAC authcode generation failed\n"); goto out; } @@ -453,7 +382,8 @@ int login(int argc, char **argv) { goto out; } - if (get_msg_tag(host, action, peer_key, private_key, tag)) { + if (glome_tag(false, 0, private_key, peer_key, (uint8_t *)message, + strlen(message), tag)) { fprintf(stderr, "GLOME tag generation failed\n"); goto out; } @@ -468,9 +398,7 @@ int login(int argc, char **argv) { out: free(handshake); - free(host); free(handshake_b64); - free(host_esc); - free(action); + free(message); return ret; } diff --git a/cli/test.sh b/cli/test.sh index 7b244788..a0152f9e 100755 --- a/cli/test.sh +++ b/cli/test.sh @@ -68,21 +68,9 @@ for n in 1 2; do fi done -# Test login subcommand according to specification. -key="$t/vector-1/b" -path="v1/AYUg8AmJMKdUdIt93LQ-91oNvzoNJjga9OukqY6qm05q0PU=/my-server.local/shell/root/" -expected_tag="lyHuaHuCcknb5sJEukWSFs8B1SUBIWMCXfNY64fIkFk=" -tag=$("$binary" login --key "$key" "$path") -if [ "$tag" != "$expected_tag" ]; then - echo "Generated wrong tag for test path $path" >&2 - echo "$expected_tag <- expected" >&2 - echo "$tag <- actual" >&2 - errors=$((errors + 1)) -fi - key="$t/vector-2/a" -path="v1/UYcvQ1u4uJ0OOtYqouURB07hleHDnvaogAFBi-ZW48N2/serial-number:1234567890=ABCDFGH%2F%23%3F/reboot/" -expected_tag="p8M_BUKj7zXBVM2JlQhNYFxs4J-DzxRAps83ZaNDquY=" +path="v2/R4cvQ1u4uJ0OOtYqouURB07hleHDnvaogAFBi-ZW48N2/myhost/exec=%2Fbin%2Fsh/" +expected_tag="ZmxczN4x3g4goXu-A2AuuEEVftgS6xM-6gYj-dRrlis=" tag=$("$binary" login --key "$key" "$path") if [ "$tag" != "$expected_tag" ]; then echo "Generated wrong tag for test path $path" >&2 diff --git a/login/crypto.c b/login/crypto.c index 408ff1d1..75af4b03 100644 --- a/login/crypto.c +++ b/login/crypto.c @@ -14,6 +14,7 @@ #include "crypto.h" +#include #include #include #include @@ -38,44 +39,84 @@ int derive_or_generate_key(uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH], } } -static int login_tag(bool verify, const char* host_id, const char* action, - const uint8_t peer_key[GLOME_MAX_PUBLIC_KEY_LENGTH], - const uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH], - uint8_t output[GLOME_MAX_TAG_LENGTH]) { - size_t message_len = strlen(host_id) + 1 + strlen(action) + 1; - char* message = calloc(message_len, 1); - if (message == NULL) { - return -1; - } - int ret = snprintf(message, message_len, "%s/%s", host_id, action); - if (ret < 0) { - free(message); - return -1; +static const char* valid_url_path_chars = "-._~!$&'()*+,;="; + +static const size_t escaped_char_length = 3; + +// Escape a string for use as an URL path segment. All characters in the extra +// string are escaped, even if they would not need to be by the spec, so that +// they can be used as delimiters, too. +// +// See: https://url.spec.whatwg.org/#url-path-segment-string +static char* urlescape_path(const char* src, const char* extra) { + if (!src) return NULL; + if (!extra) extra = ""; + + // First pass: output length + + size_t output_length = 1; // We need at least the trailing NUL byte. + for (const char* c = src; *c != '\0'; c++) { + if (!strchr(extra, *c) && + (isalnum(*c) || strchr(valid_url_path_chars, *c))) { + output_length += 1; + } else { + output_length += escaped_char_length; + } } - if ((size_t)ret >= message_len) { - free(message); - return -1; + char* dst = calloc(output_length, 1); + if (!dst) return dst; + + // Second pass: copy over and escape + + int dst_offset = 0; + for (const char* next_char = src; *next_char != '\0'; next_char++) { + if (!strchr(extra, *next_char) && + (isalnum(*next_char) || strchr(valid_url_path_chars, *next_char))) { + dst[dst_offset] = *next_char; + dst_offset++; + } else { + snprintf(dst + dst_offset, escaped_char_length + 1, "%%%02X", *next_char); + dst_offset += escaped_char_length; + } } - if (glome_tag(verify, 0, private_key, peer_key, (uint8_t*)message, - strlen(message), output) != 0) { - free(message); - return -1; + return dst; +} + +char* glome_login_message(const char* host_id_type, const char* host_id, + const char* action) { + char *host_id_type_escaped = NULL, *host_id_escaped = NULL, + *action_escaped = NULL, *message = NULL; + + host_id_escaped = urlescape_path(host_id, ":"); + action_escaped = urlescape_path(action, ""); + if (!host_id_escaped || !action_escaped) goto end; + + size_t message_len = strlen(host_id_escaped) + 1 + strlen(action_escaped) + 1; + + // Only prefix host_id_type if it's not empty. + if (host_id_type && *host_id_type) { + host_id_type_escaped = urlescape_path(host_id_type, ":"); + if (!host_id_type_escaped) goto end; + message_len += strlen(host_id_type_escaped) + 1; } - free(message); - return 0; -} + message = calloc(message_len, 1); + if (message == NULL) { + goto end; + } -int get_authcode(const char* host_id, const char* action, - const uint8_t peer_key[GLOME_MAX_PUBLIC_KEY_LENGTH], - const uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH], - uint8_t authcode[GLOME_MAX_TAG_LENGTH]) { - return login_tag(true, host_id, action, peer_key, private_key, authcode); -} + char* dst = message; + if (host_id_type_escaped) { + dst = stpcpy(dst, host_id_type_escaped); + *(dst++) = ':'; + } + dst = stpcpy(dst, host_id_escaped); + *(dst++) = '/'; + dst = stpcpy(dst, action_escaped); -int get_msg_tag(const char* host_id, const char* action, - const uint8_t peer_key[GLOME_MAX_PUBLIC_KEY_LENGTH], - const uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH], - uint8_t tag[GLOME_MAX_TAG_LENGTH]) { - return login_tag(false, host_id, action, peer_key, private_key, tag); +end: + free(host_id_type_escaped); + free(host_id_escaped); + free(action_escaped); + return message; } diff --git a/login/crypto.h b/login/crypto.h index 8717d5b9..0b97c237 100644 --- a/login/crypto.h +++ b/login/crypto.h @@ -28,18 +28,17 @@ int derive_or_generate_key(uint8_t private_key[PRIVATE_KEY_LENGTH], uint8_t public_key[PUBLIC_KEY_LENGTH]); -int get_authcode(const char* host_id, const char* action, - const uint8_t peer_key[PUBLIC_KEY_LENGTH], - const uint8_t private_key[PRIVATE_KEY_LENGTH], - uint8_t authcode[GLOME_MAX_TAG_LENGTH]); - -int get_msg_tag(const char* host_id, const char* action, - const uint8_t peer_key[PUBLIC_KEY_LENGTH], - const uint8_t private_key[PRIVATE_KEY_LENGTH], - uint8_t tag[GLOME_MAX_TAG_LENGTH]); - // is_zeroed() checks (in constant time) if all len bytes of buf are zeros. // This is to avoid timing attacks. int is_zeroed(const uint8_t* buf, size_t len); +// Create an encoded GLOME Login message from its constituent parts. +// The host_id_type may be empty, in which case only the host_id will be part +// of the host path segment. host_id and action must not be NULL. +// On error, a NULL pointer is returned. +// On success, a pointer to a NUL-terminated string is returned that needs to +// be freed by the caller. +char* glome_login_message(const char* host_id_type, const char* host_id, + const char* action); + #endif // LOGIN_CRYPTO_H_ diff --git a/login/crypto_test.c b/login/crypto_test.c index c806aa87..9a97a618 100644 --- a/login/crypto_test.c +++ b/login/crypto_test.c @@ -49,8 +49,8 @@ static void test_generate() { } static void test_authcode() { - const char* host_id = "serial-number:1234567890=ABCDFGH/#?"; - const char* action = "reboot"; + const char* host_id = "myhost"; + const char* action = "exec=/bin/sh"; uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH] = {0}; uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH] = {0}; @@ -65,44 +65,22 @@ static void test_authcode() { uint8_t expected_authcode[GLOME_MAX_TAG_LENGTH]; decode_hex( expected_authcode, sizeof expected_authcode, - "a7c33f0542a3ef35c154cd8995084d605c6ce09f83cf1440a6cf3765a343aae6"); - g_assert_true( - get_authcode(host_id, action, service_key, private_key, authcode) == 0); + "666c5cccde31de0e20a17bbe03602eb841157ed812eb133eea0623f9d46b962b"); + + char* message = glome_login_message(/*host_id_type=*/NULL, host_id, action); + g_assert_nonnull(message); + g_assert(glome_tag(true, 0, private_key, service_key, (uint8_t*)message, + strlen(message), authcode) == 0); g_assert_cmpmem(expected_authcode, sizeof expected_authcode, authcode, sizeof authcode); } -static void test_msg_tag() { - const char* host_id = "serial-number:1234567890=ABCDFGH/#?"; - const char* action = "reboot"; - - uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH] = {0}; - uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH] = {0}; - decode_hex( - private_key, sizeof private_key, - "fee1deadfee1deadfee1deadfee1deadfee1deadfee1deadfee1deadfee1dead"); - decode_hex( - service_key, sizeof service_key, - "d1b6941bba120bcd131f335da15778d9c68dadd398ae61cf8e7d94484ee65647"); - - uint8_t msg_tag[GLOME_MAX_TAG_LENGTH]; - uint8_t expected_msg_tag[GLOME_MAX_TAG_LENGTH]; - decode_hex( - expected_msg_tag, sizeof expected_msg_tag, - "dff5aae753a8bdce06038a20adcdb26c7be19cb6bd05a7850fae542f4af29720"); - g_assert_true( - get_msg_tag(host_id, action, service_key, private_key, msg_tag) == 0); - g_assert_cmpmem(expected_msg_tag, sizeof expected_msg_tag, msg_tag, - sizeof msg_tag); -} - int main(int argc, char** argv) { g_test_init(&argc, &argv, NULL); g_test_add_func("/test-derive", test_derive); g_test_add_func("/test-generate", test_generate); g_test_add_func("/test-authcode", test_authcode); - g_test_add_func("/test-msg-tag", test_msg_tag); return g_test_run(); } diff --git a/login/login.c b/login/login.c index 6a288982..cf850aa0 100644 --- a/login/login.c +++ b/login/login.c @@ -114,12 +114,12 @@ void timeout_handler(int sig) { int shell_action(const char* user, char** action, size_t* action_len, const char** error_tag) { - size_t buf_len = strlen("shell/") + strlen(user) + 1; + size_t buf_len = strlen("shell=") + strlen(user) + 1; char* buf = calloc(buf_len, 1); if (buf == NULL) { return failure(EXITCODE_PANIC, error_tag, "message-calloc-error"); } - int ret = snprintf(buf, buf_len, "shell/%s", user); + int ret = snprintf(buf, buf_len, "shell=%s", user); if (ret < 0) { free(buf); return failure(EXITCODE_PANIC, error_tag, "message-sprintf-error"); @@ -134,32 +134,13 @@ int shell_action(const char* user, char** action, size_t* action_len, return 0; } -char* escape_host(const char* host) { - size_t host_len = strlen(host); - char *ret = malloc(host_len * 3 + 1), *ret_end = ret; - if (ret == NULL) { - return NULL; - } - // Only /, ?, and # would be problematic given our URL encoding - for (size_t i = 0; i < host_len; ++i) { - if (host[i] == '/' || host[i] == '?' || host[i] == '#') { - ret_end += snprintf(ret_end, 3 + 1, "%%%02X", host[i]); - } else { - ret_end[0] = host[i]; - ret_end += 1; - } - } - ret_end[0] = '\0'; - return ret; -} - int request_challenge(const uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH], int service_key_id, const uint8_t public_key[PUBLIC_KEY_LENGTH], - const char* host_id, const char* action, + const char* message, const uint8_t prefix_tag[GLOME_MAX_TAG_LENGTH], size_t prefix_tag_len, char** challenge, - int* challenge_len, const char** error_tag) { + const char** error_tag) { if (prefix_tag_len > GLOME_MAX_TAG_LENGTH) { return failure(EXITCODE_PANIC, error_tag, "prefix-tag-too-large"); } @@ -172,11 +153,16 @@ int request_challenge(const uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH], uint8_t handshake[PUBLIC_KEY_LENGTH + 1 + GLOME_MAX_TAG_LENGTH] = {0}; size_t handshake_len = PUBLIC_KEY_LENGTH + 1 + prefix_tag_len; - if (service_key_id == 0) { - // If no key ID was specified, send the first key byte as the ID. - handshake[0] = service_key[0] & 0x7f; + if (service_key_id < 0 || service_key_id > 127) { + // If no key ID was specified, send the most significant key byte as the ID. + handshake[0] = service_key[GLOME_MAX_PUBLIC_KEY_LENGTH - 1]; + // Indicate 'service key prefix' by setting the high bit 0. + handshake[0] &= 0x7f; } else { - handshake[0] = service_key_id & 0x7f; + // + handshake[0] = (uint8_t)service_key_id; + // Indicate 'service key index' by setting the high bit 1. + handshake[0] |= 0x80; } memcpy(handshake + 1, public_key, PUBLIC_KEY_LENGTH); @@ -190,33 +176,21 @@ int request_challenge(const uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH], return failure(EXITCODE_PANIC, error_tag, "handshake-encode"); } - char* host_id_escaped = escape_host(host_id); - if (host_id_escaped == NULL) { - return failure(EXITCODE_PANIC, error_tag, "host-id-malloc-error"); - } + // Compute the required buffer length for the concatenated challenge string: + // "v2/" ++ handshake_encoded ++ "/" ++ message ++ "/\x00" + int len = strlen("v2/") + strlen(handshake_encoded) + strlen(message) + 3; - int len = strlen("v1/") + strlen(handshake_encoded) + 1 + - strlen(host_id_escaped) + 1 + strlen(action) + 2; - char* buf = malloc(len); + char* buf = calloc(len, 1); if (buf == NULL) { - free(host_id_escaped); return failure(EXITCODE_PANIC, error_tag, "challenge-malloc-error"); } - int ret = snprintf(buf, len, "v1/%s/%s/%s/", handshake_encoded, - host_id_escaped, action); - free(host_id_escaped); - host_id_escaped = NULL; - if (ret < 0) { - free(buf); - return failure(EXITCODE_PANIC, error_tag, "challenge-sprintf-error"); - } - if (ret >= len) { - free(buf); - return failure(EXITCODE_PANIC, error_tag, "challenge-sprintf-trunc"); - } - *challenge = buf; - *challenge_len = len; + buf = stpcpy(buf, "v2/"); + buf = stpcpy(buf, handshake_encoded); + buf = stpcpy(buf, "/"); + buf = stpcpy(buf, message); + buf = stpcpy(buf, "/"); + return 0; } @@ -326,113 +300,120 @@ int login_prompt(glome_login_config_t* config, pam_handle_t* pamh, } #endif -int login_authenticate(glome_login_config_t* config, pam_handle_t* pamh, - const char** error_tag) { - if (is_zeroed(config->service_key, sizeof config->service_key)) { - return failure(EXITCODE_PANIC, error_tag, "no-service-key"); - } - - uint8_t public_key[PUBLIC_KEY_LENGTH] = {0}; - if (derive_or_generate_key(config->secret_key, public_key)) { - return failure(EXITCODE_PANIC, error_tag, "derive-or-generate-key"); - } - +static char* create_login_message(glome_login_config_t* config, + pam_handle_t* pamh, const char** error_tag) { char* host_id = NULL; + if (config->host_id != NULL) { host_id = strdup(config->host_id); if (host_id == NULL) { - return failure(EXITCODE_PANIC, error_tag, "malloc-host-id"); + *error_tag = "malloc-host-id"; + return NULL; } } else { host_id = calloc(HOST_NAME_MAX + 1, 1); if (host_id == NULL) { - return failure(EXITCODE_PANIC, error_tag, "malloc-host-id"); + *error_tag = "malloc-host-id"; + return NULL; } if (get_machine_id(host_id, HOST_NAME_MAX + 1, error_tag) < 0) { - return failure(EXITCODE_PANIC, error_tag, "get-machine-id"); + *error_tag = "get-machine-id"; + return NULL; } } + char* host_id_type = NULL; if (config->host_id_type != NULL) { - size_t host_id_len = strlen(config->host_id_type) + 1 + strlen(host_id) + 1; - char* host_id_full = calloc(host_id_len, 1); - if (host_id_full == NULL) { - return failure(EXITCODE_PANIC, error_tag, "malloc-host-id-full"); - } - int ret = snprintf(host_id_full, host_id_len, "%s:%s", config->host_id_type, - host_id); - if (ret < 0) { - free(host_id_full); - return failure(EXITCODE_PANIC, error_tag, "generate-host-id-full"); + host_id_type = strdup(config->host_id_type); + if (host_id_type == NULL) { + *error_tag = "malloc-host-id-type"; + free(host_id); + return NULL; } - if ((size_t)ret >= host_id_len) { - free(host_id_full); - return failure(EXITCODE_PANIC, error_tag, "generate-host-id-full"); - } - free(host_id); - host_id = host_id_full; } char* action = NULL; size_t action_len = 0; if (shell_action(config->username, &action, &action_len, error_tag)) { + free(host_id_type); free(host_id); - return EXITCODE_PANIC; + return NULL; } if (config->options & VERBOSE) { - login_syslog(config, pamh, LOG_DEBUG, "host ID: %s, action: %s", host_id, - action); + login_syslog(config, pamh, LOG_DEBUG, + "host ID type: %s, host ID: %s, action: %s", host_id_type, + host_id, action); } - uint8_t authcode[GLOME_MAX_TAG_LENGTH]; - if (get_authcode(host_id, action, config->service_key, config->secret_key, - authcode)) { - free(host_id); - free(action); - return failure(EXITCODE_PANIC, error_tag, "get-authcode"); + return glome_login_message(host_id_type, host_id, action); +} + +int login_authenticate(glome_login_config_t* config, pam_handle_t* pamh, + const char** error_tag) { + uint8_t public_key[PUBLIC_KEY_LENGTH] = {0}; + + // Sanity check key material. + + if (is_zeroed(config->service_key, sizeof config->service_key)) { + return failure(EXITCODE_PANIC, error_tag, "no-service-key"); } - char* challenge = NULL; - int challenge_len = 0; - if (request_challenge(config->service_key, config->service_key_id, public_key, - host_id, action, /*prefix_tag=*/NULL, - /*prefix_tag_len=*/0, &challenge, &challenge_len, - error_tag)) { - free(host_id); - free(action); - return EXITCODE_PANIC; + if (derive_or_generate_key(config->secret_key, public_key)) { + return failure(EXITCODE_PANIC, error_tag, "derive-or-generate-key"); } - free(host_id); - host_id = NULL; - free(action); - action = NULL; + // Derive content for the GLOME Login message. - const char* prompt = ""; - if (config->prompt != NULL) { - prompt = config->prompt; + char* message = create_login_message(config, pamh, error_tag); + if (!message) { + return failure(EXITCODE_PANIC, error_tag, "glome-login-message"); } - size_t message_len = strlen(prompt) + strlen(challenge) + 1; - char* message = malloc(message_len); - if (message == NULL) { - free(challenge); - return failure(EXITCODE_PANIC, error_tag, "malloc-message"); - } - message[0] = '\0'; // required by strncat() - strncat(message, prompt, message_len - 1); - strncat(message, challenge, message_len - strlen(message) - 1); - free(challenge); - challenge = NULL; - if (message[message_len - 1] != '\0') { + + // Prepare auth code for verification of response. + + uint8_t authcode[GLOME_MAX_TAG_LENGTH]; + if (glome_tag(/*verify=*/true, 0, config->secret_key, config->service_key, + (uint8_t*)message, strlen(message), authcode)) { free(message); - return failure(EXITCODE_PANIC, error_tag, "strncat-failure"); + return failure(EXITCODE_PANIC, error_tag, "get-authcode"); + } + + // Create the final prompt. + + char* prompt = NULL; + { + char* challenge = NULL; + // TODO: Why does this not do a prefix? + if (request_challenge(config->service_key, config->service_key_id, + public_key, message, + /*prefix_tag=*/NULL, + /*prefix_tag_len=*/0, &challenge, error_tag)) { + free(message); + return EXITCODE_PANIC; + } + + free(message); + message = NULL; + + const char* prompt_prefix = ""; + if (config->prompt != NULL) { + prompt_prefix = config->prompt; + } + size_t prompt_len = strlen(prompt_prefix) + strlen(challenge) + 1; + prompt = calloc(prompt_len, 1); + if (prompt == NULL) { + free(challenge); + return failure(EXITCODE_PANIC, error_tag, "malloc-message"); + } + stpcpy(stpcpy(prompt, prompt_prefix), challenge); + free(challenge); } char input[ENCODED_BUFSIZE(GLOME_MAX_TAG_LENGTH)]; - int rc = login_prompt(config, pamh, error_tag, message, input, sizeof(input)); - free(message); + int rc = login_prompt(config, pamh, error_tag, prompt, input, sizeof(input)); + free(prompt); message = NULL; if (rc != 0) { diff --git a/login/login.h b/login/login.h index 4c77cce3..c3d71ef9 100644 --- a/login/login.h +++ b/login/login.h @@ -60,18 +60,24 @@ int shell_action(const char* user, char** action, size_t* action_len, // Construct a challenge given the key parameters, host ID, an action, and // optionally a message prefix tag. // -// The length of the message prefix tag is in bytes. Only tag sizes of multiples -// by 8 is supported. +// service_key_id is the numerical 7 bit identifier of the server's public key. +// A negative service_key_id indicates to use the public key prefix instead. // -// Caller is expected to free returned challenge. -// On error, the error_tag is set to an error token which should NOT be freed. +// If prefix_tag is supplied, it will be appended to the challenge for error +// detection at the server side. +// +// On success, 0 is returned and challenge contains a pointer to a +// NUL-terminated string, which must be freed by the caller. +// +// On error, a non-zero value is returned and the error_tag is set to an error +// token which should NOT be freed. int request_challenge(const uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH], int service_key_id, const uint8_t public_key[PUBLIC_KEY_LENGTH], - const char* host_id, const char* action, + const char* message, const uint8_t prefix_tag[GLOME_MAX_TAG_LENGTH], size_t prefix_tag_len, char** challenge, - int* challenge_len, const char** error_tag); + const char** error_tag); // Set the error_tag to the given error token and return the error code. int failure(int code, const char** error_tag, const char* message); diff --git a/login/login_test.c b/login/login_test.c index 9b85deef..32b76b52 100644 --- a/login/login_test.c +++ b/login/login_test.c @@ -28,14 +28,15 @@ static void test_shell_action() { size_t action_len = 0; shell_action("operator", &action, &action_len, &error_tag); - g_assert_cmpstr("shell/operator", ==, action); + g_assert_cmpstr("shell=operator", ==, action); g_assert_true(strlen(action) + 1 == action_len); g_assert_null(error_tag); } static void test_vector_1() { - const char* host_id = "my-server.local"; - const char* action = "shell/root"; + const char* host_id_type = "mytype"; + const char* host_id = "myhost"; + const char* action = "root"; uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH] = {0}; uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH] = {0}; @@ -49,45 +50,50 @@ static void test_vector_1() { g_assert_true(derive_or_generate_key(private_key, public_key) == 0); + char* message = glome_login_message(host_id_type, host_id, action); + g_assert_nonnull(message); + { uint8_t authcode[GLOME_MAX_TAG_LENGTH]; - g_assert_true( - get_authcode(host_id, action, service_key, private_key, authcode) == 0); - char authcode_encoded[ENCODED_BUFSIZE(sizeof authcode)] = {0}; + g_assert(glome_tag(true, 0, private_key, service_key, (uint8_t*)message, + strlen(message), authcode) == 0); + char authcode_encoded[ENCODED_BUFSIZE(sizeof authcode) + 1] = {0}; g_assert_true(base64url_encode(authcode, sizeof authcode, (uint8_t*)authcode_encoded, sizeof authcode_encoded)); - g_assert_cmpmem("lyHuaHuCck", 10, authcode_encoded, 10); + g_assert_cmpmem("BB4BYjXonlIRtXZORkQ5bF5xTZwW6o60ylqfCuyAHTQ=", 44, + authcode_encoded, 44); } { const char* error_tag = NULL; char* challenge = NULL; - int challenge_len = 0; - int service_key_id = 1; + int service_key_id = 0; + int messageTagPrefixLength = 3; uint8_t prefix_tag[GLOME_MAX_TAG_LENGTH]; - g_assert_true(get_msg_tag(host_id, action, service_key, private_key, - prefix_tag) == 0); + g_assert(glome_tag(/*verify=*/false, 0, private_key, service_key, + (uint8_t*)message, strlen(message), prefix_tag) == 0); - if (request_challenge(service_key, service_key_id, public_key, host_id, - action, prefix_tag, /*prefix_tag_len=*/2, &challenge, - &challenge_len, &error_tag)) { + if (request_challenge(service_key, service_key_id, public_key, message, + prefix_tag, messageTagPrefixLength, &challenge, + &error_tag)) { g_test_message("construct_request_challenge failed: %s", error_tag); g_test_fail(); } g_assert_cmpstr( - "v1/AYUg8AmJMKdUdIt93LQ-91oNvzoNJjga9OukqY6qm05q0PU=/" - "my-server.local/shell/root/", + "v2/gIUg8AmJMKdUdIt93LQ-91oNvzoNJjga9OukqY6qm05qlyPH/mytype:myhost/" + "root/", ==, challenge); g_assert_null(error_tag); } } static void test_vector_2() { - const char* host_id = "serial-number:1234567890=ABCDFGH/#?"; - const char* action = "reboot"; + const char* host_id_type = ""; + const char* host_id = "myhost"; + const char* action = "exec=/bin/sh"; uint8_t service_key[GLOME_MAX_PUBLIC_KEY_LENGTH] = {0}; uint8_t private_key[GLOME_MAX_PRIVATE_KEY_LENGTH] = {0}; @@ -101,34 +107,34 @@ static void test_vector_2() { g_assert_true(derive_or_generate_key(private_key, public_key) == 0); + char* message = glome_login_message(host_id_type, host_id, action); + g_assert_nonnull(message); + { uint8_t authcode[GLOME_MAX_TAG_LENGTH]; - g_assert_true( - get_authcode(host_id, action, service_key, private_key, authcode) == 0); + g_assert(glome_tag(true, 0, private_key, service_key, (uint8_t*)message, + strlen(message), authcode) == 0); char authcode_encoded[ENCODED_BUFSIZE(sizeof authcode)] = {0}; g_assert_true(base64url_encode(authcode, sizeof authcode, (uint8_t*)authcode_encoded, sizeof authcode_encoded)); - g_assert_cmpstr("p8M_BUKj7zXBVM2JlQhNYFxs4J-DzxRAps83ZaNDquY=", ==, - authcode_encoded); + g_assert_cmpmem("ZmxczN4x3g4goXu-A2AuuEEVftgS6xM-6gYj-dRrlis=", 44, + authcode_encoded, 44); } { const char* error_tag = NULL; char* challenge = NULL; - int challenge_len = 0; - int service_key_id = 0; - if (request_challenge(service_key, service_key_id, public_key, host_id, - action, - /*prefix_tag=*/NULL, /*prefix_tag_len=*/0, &challenge, - &challenge_len, &error_tag)) { + int service_key_id = -1; + if (request_challenge(service_key, service_key_id, public_key, message, + NULL, 0, &challenge, &error_tag)) { g_test_message("construct_request_challenge failed: %s", error_tag); g_test_fail(); } g_assert_cmpstr( - "v1/UYcvQ1u4uJ0OOtYqouURB07hleHDnvaogAFBi-ZW48N2/" - "serial-number:1234567890=ABCDFGH%2F%23%3F/reboot/", + "v2/R4cvQ1u4uJ0OOtYqouURB07hleHDnvaogAFBi-ZW48N2/myhost/" + "exec=%2Fbin%2Fsh/", ==, challenge); g_assert_null(error_tag); } diff --git a/login/pam_test.c b/login/pam_test.c index 42748eb6..c1587ffb 100644 --- a/login/pam_test.c +++ b/login/pam_test.c @@ -21,8 +21,8 @@ #include const char *authtoks[] = { - "lyHuaHuCck", /* Correct code */ - "lyHuaHuCc", /* Too short */ + "Xt-yvSPnAz", /* Correct code */ + "Xt-yvSPnA", /* Too short */ "INVALIDCODE", /* Wrong code */ /* fake passwords that might be provided by openssh-portable/auth-pam.c */ "\b\n\r\177", "\b\n\r\177INCORRECT", "\b\n\r\177INCORRECT\b\n\r\177",