Allow SSH keys on multiple accounts

Update validation and duplicate checks to scope key uniqueness per
account, and clarify the console error message. Document cross-account
key assignment and authentication testing.
This commit is contained in:
2026-08-30 12:11:16 +02:00
parent c7d0d59f3e
commit 7a4769fdd8
3 changed files with 18 additions and 31 deletions
+3 -1
View File
@@ -48,10 +48,12 @@ Verify all of the following fail without changing the account generation or key
- Unsupported RSA, certificate, or unknown key types. - Unsupported RSA, certificate, or unknown key types.
- Truncated/invalid Base64, mismatched outer and embedded types, trailing blob data, malformed Ed25519 lengths, and an invalid/off-curve ECDSA point. - Truncated/invalid Base64, mismatched outer and embedded types, trailing blob data, malformed Ed25519 lengths, and an invalid/off-curve ECDSA point.
- Adding the same key again to the same account or assigning it to a different account. - Adding the same key again to the same account.
- Adding a fourth key to an account that already has three. - Adding a fourth key to an account that already has three.
- Deleting an empty/out-of-range slot. - Deleting an empty/out-of-range slot.
Assign the same public key to a second account and confirm it is accepted, receives an independent slot, and authenticates as the username selected by the SSH client.
Then exercise `user key delete <username> <index> --force` and `user key clear <username> --force`, reboot, and confirm the exact remaining fingerprints persist. Phase 8A stores these keys but does not yet accept SSH key login. Then exercise `user key delete <username> <index> --force` and `user key clear <username> --force`, reboot, and confirm the exact remaining fingerprints persist. Phase 8A stores these keys but does not yet accept SSH key login.
### 5. Legacy credential rotation boundary and reboot reconciliation ### 5. Legacy credential rotation boundary and reboot reconciliation
+1 -1
View File
@@ -357,7 +357,7 @@ static int add_key(const char *username)
secure_wipe(line, sizeof(line)); secure_wipe(line, sizeof(line));
if (error != ESP_OK) { if (error != ESP_OK) {
if (error == USER_DATABASE_ERR_DUPLICATE_SSH_KEY) { if (error == USER_DATABASE_ERR_DUPLICATE_SSH_KEY) {
printf("Could not add SSH key: that public key is already assigned to an account.\n"); printf("Could not add SSH key: that public key is already assigned to this account.\n");
} else if (error == ESP_ERR_NO_MEM) { } else if (error == ESP_ERR_NO_MEM) {
printf("Could not add SSH key: the account already has %u keys.\n", printf("Could not add SSH key: the account already has %u keys.\n",
USER_DATABASE_MAX_SSH_KEYS_PER_USER); USER_DATABASE_MAX_SSH_KEYS_PER_USER);
+8 -23
View File
@@ -387,17 +387,11 @@ static esp_err_t validate_database(const stored_database_t *database)
sizeof(key->blob) - key->blob_length)) { sizeof(key->blob) - key->blob_length)) {
return ESP_ERR_INVALID_RESPONSE; return ESP_ERR_INVALID_RESPONSE;
} }
for (size_t prior_user = 0U; prior_user <= index; ++prior_user) { for (size_t prior_key = 0U; prior_key < key_index; ++prior_key) {
size_t prior_key_limit = prior_user == index if (stored_keys_equal(key, &user->keys[prior_key])) {
? key_index
: USER_DATABASE_MAX_SSH_KEYS_PER_USER;
for (size_t prior_key = 0U; prior_key < prior_key_limit; ++prior_key) {
if (stored_keys_equal(key,
&database->users[prior_user].keys[prior_key])) {
return ESP_ERR_INVALID_RESPONSE; return ESP_ERR_INVALID_RESPONSE;
} }
} }
}
uint8_t fingerprint[USER_DATABASE_SHA256_LENGTH]; uint8_t fingerprint[USER_DATABASE_SHA256_LENGTH];
if (mbedtls_sha256(key->blob, key->blob_length, fingerprint, 0) != 0 || if (mbedtls_sha256(key->blob, key->blob_length, fingerprint, 0) != 0 ||
!constant_time_equal(fingerprint, key->fingerprint, !constant_time_equal(fingerprint, key->fingerprint,
@@ -1157,21 +1151,16 @@ esp_err_t user_database_add_ssh_key(
if (error == ESP_OK) { if (error == ESP_OK) {
stored_user_t *user = &s_candidate.users[user_index]; stored_user_t *user = &s_candidate.users[user_index];
int free_index = -1; int free_index = -1;
for (size_t candidate_user_index = 0U;
candidate_user_index < USER_DATABASE_MAX_USERS; ++candidate_user_index) {
stored_user_t *candidate_user =
&s_candidate.users[candidate_user_index];
if (candidate_user->active == 0U) {
continue;
}
for (size_t index = 0U; for (size_t index = 0U;
index < USER_DATABASE_MAX_SSH_KEYS_PER_USER; ++index) { index < USER_DATABASE_MAX_SSH_KEYS_PER_USER; ++index) {
stored_key_t *key = &candidate_user->keys[index]; stored_key_t *key = &user->keys[index];
if (candidate_user_index == (size_t)user_index && if (key->active == 0U) {
key->active == 0U && free_index < 0) { if (free_index < 0) {
free_index = (int)index; free_index = (int)index;
} }
if (key->active != 0U && key->type_length == key_type_length && continue;
}
if (key->type_length == key_type_length &&
key->blob_length == key_blob_length && key->blob_length == key_blob_length &&
memcmp(key->type, key_type, key_type_length) == 0 && memcmp(key->type, key_type, key_type_length) == 0 &&
constant_time_equal(key->blob, key_blob, key_blob_length)) { constant_time_equal(key->blob, key_blob, key_blob_length)) {
@@ -1179,10 +1168,6 @@ esp_err_t user_database_add_ssh_key(
break; break;
} }
} }
if (error != ESP_OK) {
break;
}
}
if (error == ESP_OK && free_index < 0) { if (error == ESP_OK && free_index < 0) {
error = ESP_ERR_NO_MEM; error = ESP_ERR_NO_MEM;
} }