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:
Pol Henarejos
2026-07-10 16:37:24 +02:00
parent 950b4fc5ec
commit a22f36e03d
33 changed files with 399 additions and 50 deletions
+4
View File
@@ -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
+3
View File
@@ -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)
+75 -7
View File
@@ -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();
+13 -1
View File
@@ -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 {
+11 -1
View File
@@ -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();
}
+22 -1
View File
@@ -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
View File
@@ -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) {
+9
View File
@@ -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
+10
View File
@@ -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
+2 -7
View File
@@ -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
View File
@@ -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):