mirror of
https://github.com/polhenarejos/pico-openpgp.git
synced 2026-08-27 07:05:07 +01:00
Add support for admin-less mode.
In this mode, when PW1 is set equal to PW3 without being the default, it enters onto admin-less mode, where with PW1 gains admin privileges. Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
option(OPENPGP_TEST_INIT_LEGACY_PIN "Bootstrap legacy PIN/DEK format for migration tests" OFF)
|
||||
option(ENABLE_ADMINLESS_MODE "Enable the non-standard Gnuk-compatible admin-less PIN mode" ON)
|
||||
|
||||
set(USB_VID 0x2E8A)
|
||||
set(USB_PID 0x10FF)
|
||||
@@ -93,6 +94,9 @@ if(NOT ESP_PLATFORM)
|
||||
if(OPENPGP_TEST_INIT_LEGACY_PIN)
|
||||
target_compile_definitions(pico_openpgp PRIVATE OPENPGP_TEST_INIT_LEGACY_PIN=1)
|
||||
endif()
|
||||
if(ENABLE_ADMINLESS_MODE)
|
||||
target_compile_definitions(pico_openpgp PRIVATE ENABLE_ADMINLESS_MODE=1)
|
||||
endif()
|
||||
|
||||
set(COMMON_COMPILE_OPTIONS
|
||||
-Wall
|
||||
|
||||
@@ -6,4 +6,7 @@ idf_component_register(
|
||||
if(OPENPGP_TEST_INIT_LEGACY_PIN)
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE OPENPGP_TEST_INIT_LEGACY_PIN=1)
|
||||
endif()
|
||||
if(ENABLE_ADMINLESS_MODE)
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE ENABLE_ADMINLESS_MODE=1)
|
||||
endif()
|
||||
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)
|
||||
|
||||
@@ -18,10 +18,38 @@
|
||||
#include "openpgp.h"
|
||||
#include "otp.h"
|
||||
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
#include "mbedtls/constant_time.h"
|
||||
|
||||
static bool pw3_matches_nonfactory_pw1(const uint8_t *pin, size_t pin_len) {
|
||||
file_t *pw1 = file_search_by_fid(EF_PW1, NULL, SPECIFY_EF);
|
||||
uint8_t verifier[34];
|
||||
|
||||
if (!pw1 || !file_has_data(pw1) || file_get_data(pw1)[0] < 8) {
|
||||
return false;
|
||||
}
|
||||
if (file_get_size(pw1) == 33) {
|
||||
verifier[0] = pin_len;
|
||||
double_hash_pin(pin, pin_len, verifier + 1);
|
||||
return mbedtls_ct_memcmp(file_get_data(pw1), verifier, 33) == 0;
|
||||
}
|
||||
if (file_get_size(pw1) == 34) {
|
||||
verifier[0] = pin_len;
|
||||
verifier[1] = 0x1;
|
||||
pin_derive_verifier(pin, pin_len, verifier + 2);
|
||||
return mbedtls_ct_memcmp(file_get_data(pw1), verifier, sizeof(verifier)) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
int cmd_change_pin(void) {
|
||||
if (P1(apdu) != 0x0) {
|
||||
return SW_WRONG_P1P2();
|
||||
}
|
||||
if (P2(apdu) != 0x81 && P2(apdu) != 0x83) {
|
||||
return SW_REFERENCE_NOT_FOUND();
|
||||
}
|
||||
uint16_t fid = 0x1000 | P2(apdu);
|
||||
file_t *pw;
|
||||
if (!(pw = file_search_by_fid(fid, NULL, SPECIFY_EF))) {
|
||||
@@ -37,11 +65,28 @@ int cmd_change_pin(void) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
|
||||
const uint8_t *new_pin = apdu.data + pin_len;
|
||||
size_t new_pin_len = apdu.nc - pin_len;
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
/* Empty PW3 is Gnuk's "PW3 not configured" transition. Keep its
|
||||
* current verifier so KDF-backed host flows can still verify it. */
|
||||
bool clear_pw3 = P2(apdu) == 0x83 && new_pin_len == 0;
|
||||
if (clear_pw3) {
|
||||
new_pin = apdu.data;
|
||||
new_pin_len = pin_len;
|
||||
}
|
||||
bool sync_adminless_pw3 = P2(apdu) == 0x81 && (openpgp_adminless_is_active() || (openpgp_adminless_is_pending() && new_pin_len >= 8));
|
||||
bool disable_pending_adminless = P2(apdu) == 0x81 && openpgp_adminless_is_pending() && new_pin_len < 8;
|
||||
bool enable_adminless = P2(apdu) == 0x83 && !clear_pw3 && pw3_matches_nonfactory_pw1(new_pin, new_pin_len);
|
||||
#endif
|
||||
|
||||
uint8_t dhash[34];
|
||||
dhash[0] = apdu.nc - pin_len;
|
||||
dhash[0] = new_pin_len;
|
||||
dhash[1] = 0x1; // Format
|
||||
pin_derive_verifier(apdu.data + pin_len, apdu.nc - pin_len, dhash + 2);
|
||||
file_put_data(pw, dhash, sizeof(dhash));
|
||||
pin_derive_verifier(new_pin, new_pin_len, dhash + 2);
|
||||
if ((r = file_put_data(pw, dhash, sizeof(dhash))) != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
|
||||
if (P2(apdu) == 0x81) {
|
||||
file_t *tf = file_search_by_fid(EF_DEK_PW1, NULL, SPECIFY_EF);
|
||||
@@ -50,9 +95,22 @@ int cmd_change_pin(void) {
|
||||
}
|
||||
uint8_t def[DEK_FILE_SIZE];
|
||||
def[0] = 0x3;
|
||||
pin_derive_session(apdu.data + pin_len, apdu.nc - pin_len, session_pw1);
|
||||
encrypt_with_aad(session_pw1, dek, DEK_SIZE, PIN_KDF_DEFAULT_VERSION, def + 1);
|
||||
pin_derive_session(new_pin, new_pin_len, session_pw1);
|
||||
if ((r = encrypt_with_aad(session_pw1, dek, DEK_SIZE, PIN_KDF_DEFAULT_VERSION, def + 1)) != PICOKEYS_OK) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
r = file_put_data(tf, def, sizeof(def));
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (r == PICOKEYS_OK && sync_adminless_pw3) {
|
||||
r = openpgp_adminless_sync_pw3(new_pin, new_pin_len, dhash);
|
||||
if (r == PICOKEYS_OK && openpgp_adminless_is_pending()) {
|
||||
r = openpgp_adminless_enable();
|
||||
}
|
||||
}
|
||||
else if (r == PICOKEYS_OK && disable_pending_adminless) {
|
||||
r = openpgp_adminless_disable();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (P2(apdu) == 0x83) {
|
||||
file_t *tf = file_search_by_fid(EF_DEK_PW3, NULL, SPECIFY_EF);
|
||||
@@ -61,9 +119,19 @@ int cmd_change_pin(void) {
|
||||
}
|
||||
uint8_t def[DEK_FILE_SIZE];
|
||||
def[0] = 0x3;
|
||||
pin_derive_session(apdu.data + pin_len, apdu.nc - pin_len, session_pw3);
|
||||
encrypt_with_aad(session_pw3, dek, DEK_SIZE, PIN_KDF_DEFAULT_VERSION, def + 1);
|
||||
pin_derive_session(new_pin, new_pin_len, session_pw3);
|
||||
if ((r = encrypt_with_aad(session_pw3, dek, DEK_SIZE, PIN_KDF_DEFAULT_VERSION, def + 1)) != PICOKEYS_OK) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
r = file_put_data(tf, def, sizeof(def));
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (r == PICOKEYS_OK) {
|
||||
r = enable_adminless ? openpgp_adminless_enable() : clear_pw3 ? openpgp_adminless_reset() : openpgp_adminless_disable();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (r != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
flash_commit();
|
||||
return SW_OK();
|
||||
|
||||
@@ -88,7 +88,9 @@ int cmd_put_data(void) {
|
||||
dhash[0] = apdu.nc;
|
||||
dhash[1] = 0x1; // Format
|
||||
pin_derive_verifier(apdu.data, apdu.nc, dhash + 2);
|
||||
file_put_data(ef, dhash, sizeof(dhash));
|
||||
if ((r = file_put_data(ef, dhash, sizeof(dhash))) != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
|
||||
file_t *tf = file_search_by_fid(EF_DEK_RC, NULL, SPECIFY_EF);
|
||||
if (!tf) {
|
||||
@@ -100,6 +102,9 @@ int cmd_put_data(void) {
|
||||
pin_derive_session(apdu.data, apdu.nc, session_rc);
|
||||
encrypt_with_aad(session_rc, dek, DEK_SIZE, PIN_KDF_DEFAULT_VERSION, def + 1);
|
||||
r = file_put_data(tf, def, sizeof(def));
|
||||
if (r == PICOKEYS_OK) {
|
||||
r = pin_reset_retries(ef, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
r = file_put_data(ef, apdu.data, apdu.nc);
|
||||
@@ -107,6 +112,13 @@ int cmd_put_data(void) {
|
||||
if (r != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (requested_fid == EF_KDF && apdu.nc > 0 && !(apdu.nc == 3 && memcmp(apdu.data, "\x81\x01\x00", 3) == 0)) {
|
||||
if ((r = openpgp_adminless_begin_kdf_migration()) != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
flash_commit();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -25,6 +25,9 @@ int cmd_reset_retry(void) {
|
||||
if (P1(apdu) == 0x0 || P1(apdu) == 0x2) {
|
||||
int newpin_len = 0;
|
||||
file_t *pw = NULL;
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
bool sync_adminless_pw3 = openpgp_adminless_is_active();
|
||||
#endif
|
||||
has_pw1 = false;
|
||||
if (!(pw = file_search_by_fid(EF_PW1, NULL, SPECIFY_EF))) {
|
||||
return SW_REFERENCE_NOT_FOUND();
|
||||
@@ -72,7 +75,14 @@ int cmd_reset_retry(void) {
|
||||
dhash[0] = newpin_len;
|
||||
dhash[1] = 0x1; // Format
|
||||
pin_derive_verifier(apdu.data + (apdu.nc - newpin_len), newpin_len, dhash + 2);
|
||||
file_put_data(pw, dhash, sizeof(dhash));
|
||||
if ((r = file_put_data(pw, dhash, sizeof(dhash))) != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (sync_adminless_pw3 && (r = openpgp_adminless_sync_pw3(apdu.data + (apdu.nc - newpin_len), newpin_len, dhash)) != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
#endif
|
||||
if (pin_reset_retries(pw, true) != PICOKEYS_OK) {
|
||||
return SW_MEMORY_FAILURE();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,11 @@ int cmd_verify(void) {
|
||||
}
|
||||
if (p2 == 0x81) {
|
||||
has_pw1 = false;
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (openpgp_adminless_is_active()) {
|
||||
has_pw3 = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (p2 == 0x82) {
|
||||
has_pw2 = false;
|
||||
@@ -54,7 +59,23 @@ int cmd_verify(void) {
|
||||
return SW_REFERENCE_NOT_FOUND();
|
||||
}
|
||||
if (apdu.nc > 0) {
|
||||
return check_pin(pw, apdu.data, apdu.nc);
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (p2 == 0x81 && openpgp_adminless_is_active()) {
|
||||
has_pw1 = false;
|
||||
has_pw3 = false;
|
||||
}
|
||||
else if (p2 == 0x83 && openpgp_adminless_is_active() && !has_pw1) {
|
||||
has_pw3 = false;
|
||||
}
|
||||
#endif
|
||||
uint16_t r = check_pin(pw, apdu.data, apdu.nc);
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
/* Gnuk reports an unavailable PW3 as a security-status failure. */
|
||||
if (p2 == 0x83 && openpgp_adminless_is_active() && r != SW_OK()) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
#endif
|
||||
return r;
|
||||
}
|
||||
uint8_t retries = *(file_get_data(pw_status) + 3 + (fid & 0xf));
|
||||
if (retries == 0) {
|
||||
|
||||
+167
-2
@@ -48,6 +48,132 @@ uint8_t dek[DEK_SIZE];
|
||||
uint16_t algo_dec = EF_ALGO_PRIV2, algo_aut = EF_ALGO_PRIV3, pk_dec = EF_PK_DEC, pk_aut = EF_PK_AUT;
|
||||
extern bool is_gpg;
|
||||
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
enum {
|
||||
ADMINLESS_MODE_PENDING = 0,
|
||||
ADMINLESS_MODE_ENABLED = 1,
|
||||
ADMINLESS_MODE_DISABLED = 2,
|
||||
ADMINLESS_MODE_KDF_MIGRATION = 3,
|
||||
};
|
||||
|
||||
#define ADMINLESS_MODE_OFFSET 6
|
||||
#define ADMINLESS_RETRIES_SIZE (ADMINLESS_MODE_OFFSET + 1)
|
||||
|
||||
static int adminless_set_mode(uint8_t mode) {
|
||||
file_t *ef = file_search_by_fid(EF_PW_RETRIES, NULL, SPECIFY_EF);
|
||||
if (!ef) {
|
||||
return PICOKEYS_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
uint8_t retries[16] = { 0x1, 3, 3, 3, 3, 3, ADMINLESS_MODE_DISABLED };
|
||||
uint16_t retries_len = ADMINLESS_RETRIES_SIZE;
|
||||
if (file_has_data(ef)) {
|
||||
retries_len = MAX(file_get_size(ef), retries_len);
|
||||
if (retries_len > sizeof(retries)) {
|
||||
return PICOKEYS_ERR_NO_MEMORY;
|
||||
}
|
||||
memcpy(retries, file_get_data(ef), file_get_size(ef));
|
||||
}
|
||||
retries[ADMINLESS_MODE_OFFSET] = mode;
|
||||
return file_put_data(ef, retries, retries_len);
|
||||
}
|
||||
|
||||
static bool adminless_mode_is(uint8_t mode) {
|
||||
file_t *ef = file_search_by_fid(EF_PW_RETRIES, NULL, SPECIFY_EF);
|
||||
return ef && file_has_data(ef) && file_get_size(ef) >= ADMINLESS_RETRIES_SIZE && file_get_data(ef)[ADMINLESS_MODE_OFFSET] == mode;
|
||||
}
|
||||
|
||||
static bool pin_is_factory_default(const file_t *pin, const uint8_t *factory_pin, size_t factory_pin_len) {
|
||||
uint8_t verifier[34];
|
||||
|
||||
if (!pin || !file_has_data(pin)) {
|
||||
return false;
|
||||
}
|
||||
if (file_get_size(pin) == 33) {
|
||||
verifier[0] = factory_pin_len;
|
||||
double_hash_pin(factory_pin, factory_pin_len, verifier + 1);
|
||||
return mbedtls_ct_memcmp(file_get_data(pin), verifier, 33) == 0;
|
||||
}
|
||||
if (file_get_size(pin) == 34) {
|
||||
verifier[0] = factory_pin_len;
|
||||
verifier[1] = 0x1;
|
||||
pin_derive_verifier(factory_pin, factory_pin_len, verifier + 2);
|
||||
return mbedtls_ct_memcmp(file_get_data(pin), verifier, sizeof(verifier)) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool pw1_and_pw3_are_factory_default(void) {
|
||||
static const uint8_t factory_pw1[] = "123456";
|
||||
static const uint8_t factory_pw3[] = "12345678";
|
||||
|
||||
return pin_is_factory_default(file_search_by_fid(EF_PW1, NULL, SPECIFY_EF), factory_pw1, sizeof(factory_pw1) - 1)
|
||||
&& pin_is_factory_default(file_search_by_fid(EF_PW3, NULL, SPECIFY_EF), factory_pw3, sizeof(factory_pw3) - 1);
|
||||
}
|
||||
|
||||
bool openpgp_adminless_is_pending(void) {
|
||||
return adminless_mode_is(ADMINLESS_MODE_PENDING);
|
||||
}
|
||||
|
||||
bool openpgp_adminless_is_active(void) {
|
||||
return adminless_mode_is(ADMINLESS_MODE_ENABLED);
|
||||
}
|
||||
|
||||
int openpgp_adminless_begin_kdf_migration(void) {
|
||||
if (!openpgp_adminless_is_pending()) {
|
||||
return PICOKEYS_OK;
|
||||
}
|
||||
return adminless_set_mode(ADMINLESS_MODE_KDF_MIGRATION);
|
||||
}
|
||||
|
||||
int openpgp_adminless_sync_pw3(const uint8_t *pin, size_t pin_len, const uint8_t verifier[34]) {
|
||||
file_t *pw3 = file_search_by_fid(EF_PW3, NULL, SPECIFY_EF);
|
||||
file_t *dek_pw3 = file_search_by_fid(EF_DEK_PW3, NULL, SPECIFY_EF);
|
||||
if (!pw3 || !dek_pw3) {
|
||||
return PICOKEYS_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
int r = file_put_data(pw3, verifier, 34);
|
||||
if (r != PICOKEYS_OK) {
|
||||
return r;
|
||||
}
|
||||
uint8_t encrypted_dek[DEK_FILE_SIZE];
|
||||
encrypted_dek[0] = 0x3;
|
||||
pin_derive_session(pin, pin_len, session_pw3);
|
||||
r = encrypt_with_aad(session_pw3, dek, DEK_SIZE, PIN_KDF_DEFAULT_VERSION, encrypted_dek + 1);
|
||||
if (r != PICOKEYS_OK) {
|
||||
return r;
|
||||
}
|
||||
return file_put_data(dek_pw3, encrypted_dek, sizeof(encrypted_dek));
|
||||
}
|
||||
|
||||
int openpgp_adminless_enable(void) {
|
||||
file_t *pw_status = file_search_by_fid(EF_PW_PRIV, NULL, SPECIFY_EF);
|
||||
if (!pw_status || !file_has_data(pw_status) || file_get_size(pw_status) == 0 || file_get_size(pw_status) > 16) {
|
||||
return PICOKEYS_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
uint8_t status[16];
|
||||
uint16_t status_len = file_get_size(pw_status);
|
||||
memcpy(status, file_get_data(pw_status), status_len);
|
||||
status[0] = 0x0; // Require PW1 for every signature in admin-less mode.
|
||||
int r = file_put_data(pw_status, status, status_len);
|
||||
if (r != PICOKEYS_OK) {
|
||||
return r;
|
||||
}
|
||||
r = adminless_set_mode(ADMINLESS_MODE_ENABLED);
|
||||
if (r == PICOKEYS_OK && has_pw1) {
|
||||
has_pw3 = true;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int openpgp_adminless_disable(void) {
|
||||
return adminless_set_mode(ADMINLESS_MODE_DISABLED);
|
||||
}
|
||||
|
||||
int openpgp_adminless_reset(void) {
|
||||
return adminless_set_mode(ADMINLESS_MODE_PENDING);
|
||||
}
|
||||
#endif
|
||||
|
||||
uint8_t openpgp_aid[] = {
|
||||
6,
|
||||
0xD2, 0x76, 0x00, 0x01, 0x24, 0x01,
|
||||
@@ -147,7 +273,6 @@ void scan_files_openpgp(void) {
|
||||
pin_derive_session(def3, sizeof(def3), session_pw3);
|
||||
encrypt_with_aad(session_pw3, random_dek, DEK_SIZE, PIN_KDF_DEFAULT_VERSION, def + 1);
|
||||
mbedtls_platform_zeroize(session_pw3, sizeof(session_pw3));
|
||||
file_put_data(ef_dek_rc, def, sizeof(def));
|
||||
file_put_data(ef_dek_pw3, def, sizeof(def));
|
||||
#endif
|
||||
|
||||
@@ -216,12 +341,24 @@ void scan_files_openpgp(void) {
|
||||
file_put_data(ef, def, sizeof(def));
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
uint8_t legacy_adminless_mode = ADMINLESS_MODE_DISABLED;
|
||||
bool migrate_legacy_adminless_mode = false;
|
||||
#endif
|
||||
if ((ef = file_search_by_fid(EF_PW_PRIV, NULL, SPECIFY_ANY))) {
|
||||
if (!ef->data) {
|
||||
printf("PW status is empty. Initializing to default\r\n");
|
||||
const uint8_t def[] = { 0x1, 127, 127, 127, 3, 3, 3 };
|
||||
file_put_data(ef, def, sizeof(def));
|
||||
}
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
else if (file_get_size(ef) == 8) {
|
||||
/* Migration from the unreleased PW-status-byte implementation. */
|
||||
legacy_adminless_mode = file_get_data(ef)[7];
|
||||
migrate_legacy_adminless_mode = legacy_adminless_mode <= ADMINLESS_MODE_KDF_MIGRATION;
|
||||
file_put_data(ef, file_get_data(ef), 7);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if ((ef = file_search_by_fid(EF_UIF_SIG, NULL, SPECIFY_ANY))) {
|
||||
if (!ef->data) {
|
||||
@@ -259,10 +396,28 @@ void scan_files_openpgp(void) {
|
||||
}
|
||||
}
|
||||
if ((ef = file_search_by_fid(EF_PW_RETRIES, NULL, SPECIFY_ANY))) {
|
||||
if (!ef->data) {
|
||||
if (!ef->data
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
|| reset_dek || file_get_size(ef) < ADMINLESS_RETRIES_SIZE
|
||||
#endif
|
||||
) {
|
||||
printf("PW retries is empty. Initializing to default\r\n");
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
uint8_t def[ADMINLESS_RETRIES_SIZE] = { 0x1, 3, 3, 3, 3, 3, ADMINLESS_MODE_DISABLED };
|
||||
if (file_has_data(ef) && !reset_dek) {
|
||||
memcpy(def, file_get_data(ef), MIN(file_get_size(ef), ADMINLESS_MODE_OFFSET));
|
||||
}
|
||||
if (migrate_legacy_adminless_mode) {
|
||||
def[ADMINLESS_MODE_OFFSET] = legacy_adminless_mode;
|
||||
}
|
||||
else if (pw1_and_pw3_are_factory_default()) {
|
||||
def[ADMINLESS_MODE_OFFSET] = ADMINLESS_MODE_PENDING;
|
||||
}
|
||||
file_put_data(ef, def, sizeof(def));
|
||||
#else
|
||||
const uint8_t def[] = { 0x1, 3, 3, 3 };
|
||||
file_put_data(ef, def, sizeof(def));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
flash_commit();
|
||||
@@ -759,6 +914,11 @@ int check_pin(const file_t *pin, const uint8_t *data, size_t len) {
|
||||
if (pin->fid == EF_PW1) {
|
||||
if (P2(apdu) == 0x81) {
|
||||
has_pw1 = true;
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (openpgp_adminless_is_active()) {
|
||||
has_pw3 = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
has_pw2 = true;
|
||||
@@ -779,6 +939,11 @@ int inc_sig_count(void) {
|
||||
}
|
||||
if (file_get_data(pw_status)[0] == 0) {
|
||||
has_pw1 = false;
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
if (openpgp_adminless_is_active()) {
|
||||
has_pw3 = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
file_t *ef = file_search_by_fid(EF_SIG_COUNT, NULL, SPECIFY_ANY);
|
||||
if (!ef || !ef->data) {
|
||||
|
||||
@@ -61,6 +61,15 @@ extern void select_file(file_t *pe);
|
||||
extern int parse_do(uint16_t *fids, int mode);
|
||||
extern int load_dek(void);
|
||||
extern int check_pin(const file_t *pin, const uint8_t *data, size_t len);
|
||||
#ifdef ENABLE_ADMINLESS_MODE
|
||||
extern bool openpgp_adminless_is_pending(void);
|
||||
extern bool openpgp_adminless_is_active(void);
|
||||
extern int openpgp_adminless_begin_kdf_migration(void);
|
||||
extern int openpgp_adminless_sync_pw3(const uint8_t *pin, size_t pin_len, const uint8_t verifier[34]);
|
||||
extern int openpgp_adminless_enable(void);
|
||||
extern int openpgp_adminless_disable(void);
|
||||
extern int openpgp_adminless_reset(void);
|
||||
#endif
|
||||
extern mbedtls_ecp_group_id get_ec_group_id_from_attr(const uint8_t *algo, size_t algo_len);
|
||||
extern int reset_sig_count(void);
|
||||
extern uint16_t algo_dec, algo_aut, pk_dec, pk_aut;
|
||||
|
||||
@@ -21,5 +21,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_if_kdfreq import *
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_admin_less_1 import *
|
||||
|
||||
@@ -21,5 +21,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_if_kdfreq import *
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_public_key_operations_alt import *
|
||||
|
||||
@@ -21,5 +21,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_if_kdfreq import *
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_ds_counter1 import *
|
||||
|
||||
@@ -21,5 +21,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_if_kdfreq import *
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_admin_less_2 import *
|
||||
|
||||
@@ -21,5 +21,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_if_kdfreq import *
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_reset import *
|
||||
|
||||
@@ -21,5 +21,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_if_kdfreq import *
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_remove_keys import *
|
||||
|
||||
@@ -21,5 +21,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_if_kdfreq import *
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_reset_pw3 import *
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""Explicitly opt an existing admin-full card into admin-less mode."""
|
||||
|
||||
from card_const import FACTORY_PASSPHRASE_PW1, FACTORY_PASSPHRASE_PW3
|
||||
from constants_for_test import PW1_TEST0, PW3_TEST0
|
||||
from skip_if_kdfreq import *
|
||||
|
||||
|
||||
class Test_Card_Adminless_Upgrade:
|
||||
def test_set_existing_admin_password(self, card):
|
||||
assert card.change_passwd(3, FACTORY_PASSPHRASE_PW3, PW3_TEST0)
|
||||
|
||||
def test_set_existing_user_password(self, card):
|
||||
assert card.change_passwd(1, FACTORY_PASSPHRASE_PW1, PW1_TEST0)
|
||||
|
||||
def test_enable_adminless(self, card):
|
||||
assert card.change_passwd(3, PW3_TEST0, PW1_TEST0)
|
||||
|
||||
def test_pw1_authorizes_admin_operation(self, card):
|
||||
assert card.deauthenticate(1)
|
||||
assert card.verify(1, PW1_TEST0)
|
||||
assert card.cmd_put_data(0x00, 0x5e, b"adminless")
|
||||
|
||||
def test_pw3_accepts_user_password(self, card):
|
||||
assert card.verify(3, PW1_TEST0)
|
||||
|
||||
def test_distinct_pw3_disables_adminless(self, card):
|
||||
assert card.change_passwd(3, PW1_TEST0, PW3_TEST0)
|
||||
assert card.deauthenticate(1)
|
||||
assert card.deauthenticate(3)
|
||||
assert card.verify(1, PW1_TEST0)
|
||||
try:
|
||||
card.cmd_put_data(0x00, 0x5e, b"adminfull")
|
||||
except ValueError as e:
|
||||
assert e.args[0] == "6982"
|
||||
else:
|
||||
raise AssertionError("PW1 unexpectedly retained admin authorization")
|
||||
|
||||
def test_pw3_authorizes_admin_after_disable(self, card):
|
||||
assert card.verify(3, PW3_TEST0)
|
||||
assert card.cmd_put_data(0x00, 0x5e, b"adminfull")
|
||||
|
||||
def test_restore_adminfull_factory_passwords(self, card):
|
||||
assert card.change_passwd(3, PW3_TEST0, FACTORY_PASSPHRASE_PW3)
|
||||
assert card.change_passwd(1, PW1_TEST0, FACTORY_PASSPHRASE_PW1)
|
||||
@@ -20,5 +20,4 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_kdf_single import *
|
||||
|
||||
@@ -20,6 +20,5 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_card_1 import *
|
||||
from card_test_personalize_card_2 import *
|
||||
|
||||
@@ -20,5 +20,4 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_public_key_operations import *
|
||||
|
||||
@@ -20,5 +20,4 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_ds_counter2 import *
|
||||
|
||||
@@ -20,5 +20,4 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_reset import *
|
||||
|
||||
@@ -20,5 +20,4 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_remove_keys import *
|
||||
|
||||
@@ -20,5 +20,4 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_reset_pw3 import *
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_admin_less_1 import *
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_public_key_operations_alt import *
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_ds_counter1 import *
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_admin_less_2 import *
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_personalize_reset import *
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_remove_keys import *
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
from skip_gnuk_only_tests import *
|
||||
from card_test_reset_pw3 import *
|
||||
from card_const import FACTORY_PASSPHRASE_PW3
|
||||
|
||||
|
||||
def test_restore_adminfull_mode(card):
|
||||
r = card.change_passwd(3, FACTORY_PASSPHRASE_PW3,
|
||||
FACTORY_PASSPHRASE_PW3)
|
||||
assert r
|
||||
|
||||
@@ -87,3 +87,13 @@ class Test_Personalize_Reset(object):
|
||||
else:
|
||||
r = card.cmd_put_data(0x00, 0xd3, b"")
|
||||
assert r
|
||||
|
||||
def test_restore_reset_code(self, card):
|
||||
# This module deletes D3 while resetting personalization. Restore it
|
||||
# because the session-scoped card fixture is used by later suites.
|
||||
assert card.verify(3, FACTORY_PASSPHRASE_PW3)
|
||||
assert card.setup_reset_code(RESETCODE_TEST)
|
||||
|
||||
def test_restore_pw1_status(self, card):
|
||||
r = card.cmd_put_data(0x00, 0xc4, b"\x01")
|
||||
assert r
|
||||
|
||||
@@ -21,16 +21,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from card_const import *
|
||||
import pytest
|
||||
|
||||
class Test_Reset_PW3(object):
|
||||
# Gnuk specific feature of clear PW3
|
||||
def test_setup_pw3_null(self, card):
|
||||
if card.is_gnuk:
|
||||
r = card.change_passwd(3, FACTORY_PASSPHRASE_PW3, b'', kdf_change=-1)
|
||||
assert r
|
||||
else:
|
||||
pytest.skip("Gnuk only feature of clearing PW3")
|
||||
r = card.change_passwd(3, FACTORY_PASSPHRASE_PW3, b'', kdf_change=-1)
|
||||
assert r
|
||||
|
||||
def test_verify_pw3(self, card):
|
||||
v = card.verify(3, FACTORY_PASSPHRASE_PW3)
|
||||
|
||||
+32
-10
@@ -84,14 +84,13 @@ class OpenPGP_Card(object):
|
||||
self.kdf_supported = True
|
||||
|
||||
def configure_kdf(self, kdf_config):
|
||||
old_kdf_iters = self.__kdf_iters
|
||||
old_kdf_salt_user = self.__kdf_salt_user
|
||||
old_kdf_salt_admin = self.__kdf_salt_admin
|
||||
|
||||
self.kdf_data = kdf_config
|
||||
r = self.cmd_put_data(0x00, 0xf9, kdf_config)
|
||||
if self.kdf_data == b"" or self.kdf_data == b"\x81\x01\x00":
|
||||
if not self.is_gnuk and not self.is_yubikey:
|
||||
self.change_passwd(1, FACTORY_PASSPHRASE_PW1,
|
||||
FACTORY_PASSPHRASE_PW1, -1)
|
||||
self.change_passwd(3, FACTORY_PASSPHRASE_PW3,
|
||||
FACTORY_PASSPHRASE_PW3, -1)
|
||||
self.__kdf_iters = None
|
||||
self.__kdf_salt_user = None
|
||||
self.__kdf_salt_reset = None
|
||||
@@ -102,11 +101,25 @@ class OpenPGP_Card(object):
|
||||
self.__kdf_salt_user = salt_user
|
||||
self.__kdf_salt_reset = salt_reset
|
||||
self.__kdf_salt_admin = salt_admin
|
||||
if not self.is_gnuk and not self.is_yubikey:
|
||||
self.change_passwd(1, FACTORY_PASSPHRASE_PW1,
|
||||
FACTORY_PASSPHRASE_PW1, 1)
|
||||
self.change_passwd(3, FACTORY_PASSPHRASE_PW3,
|
||||
FACTORY_PASSPHRASE_PW3, 1)
|
||||
|
||||
if not self.is_gnuk and not self.is_yubikey:
|
||||
def kdf_value(pin, who, iterations, salt_user, salt_admin):
|
||||
if not iterations:
|
||||
return pin
|
||||
salt = salt_admin if who == 3 and salt_admin else salt_user
|
||||
return kdf_calc(pin, salt, iterations)
|
||||
|
||||
old_pw1 = kdf_value(FACTORY_PASSPHRASE_PW1, 1,
|
||||
old_kdf_iters, old_kdf_salt_user, old_kdf_salt_admin)
|
||||
new_pw1 = kdf_value(FACTORY_PASSPHRASE_PW1, 1,
|
||||
self.__kdf_iters, self.__kdf_salt_user, self.__kdf_salt_admin)
|
||||
self.cmd_change_reference_data(1, old_pw1 + new_pw1)
|
||||
|
||||
old_pw3 = kdf_value(FACTORY_PASSPHRASE_PW3, 3,
|
||||
old_kdf_iters, old_kdf_salt_user, old_kdf_salt_admin)
|
||||
new_pw3 = kdf_value(FACTORY_PASSPHRASE_PW3, 3,
|
||||
self.__kdf_iters, self.__kdf_salt_user, self.__kdf_salt_admin)
|
||||
self.cmd_change_reference_data(3, old_pw3 + new_pw3)
|
||||
return r
|
||||
|
||||
def save_algo_attribute(self, keyno, attr):
|
||||
@@ -126,6 +139,15 @@ class OpenPGP_Card(object):
|
||||
else:
|
||||
return self.cmd_verify(who, passwd)
|
||||
|
||||
def deauthenticate(self, who):
|
||||
cmd_data = iso7816_compose(0x20, 0xff, 0x80+who, b'')
|
||||
sw = self.__reader.send_cmd(cmd_data)
|
||||
if len(sw) != 2:
|
||||
raise ValueError(sw)
|
||||
if not (sw[0] == 0x90 and sw[1] == 0x00):
|
||||
raise ValueError("%02x%02x" % (sw[0], sw[1]))
|
||||
return True
|
||||
|
||||
# Higher layer CHANGE_PASSWD possibly using KDF Data Object
|
||||
# KDF_CHANGE: 0 no-change, -1 to be cleared, 1 to be configured
|
||||
def change_passwd(self, who, passwd_old, passwd_new, kdf_change=0):
|
||||
|
||||
Reference in New Issue
Block a user