Skip to content

Commit db9313c

Browse files
committed
Write admin key in PrintedInfo field by default, support UPN in certs
The admin key in PrintedInfo thing is used by ykman and the Windows PIV minidriver, so it's nice to interop with those. UPN support allows certs to be used with Windows AD more easily (by default, only the 9A key, but you can use the option for others)
1 parent 5f60f93 commit db9313c

File tree

3 files changed

+187
-24
lines changed

3 files changed

+187
-24
lines changed

piv.c

+5
Original file line numberDiff line numberDiff line change
@@ -3201,6 +3201,11 @@ piv_reset_pin(struct piv_token *pk, enum piv_pin type, const char *puk,
32013201
err = errf("PermissionError", swerrf("INS_RESET_PIN(%x)",
32023202
apdu->a_sw, type), "Incorrect PUK supplied");
32033203

3204+
} else if (apdu->a_sw == SW_FILE_INVALID) {
3205+
err = errf("PermissionError", swerrf("INS_RESET_PIN(%x)",
3206+
apdu->a_sw, type), "PUK is blocked due to too many "
3207+
"incorrect attempts");
3208+
32043209
} else {
32053210
err = swerrf("INS_RESET_PIN(%x)", apdu->a_sw, type);
32063211
bunyan_log(BNY_DEBUG, "unexpected card error",

piv.h

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ enum piv_tags {
138138
PIV_TAG_CHUID = 0x5FC102,
139139
PIV_TAG_SECOBJ = 0x5FC106,
140140
PIV_TAG_KEYHIST = 0x5FC10C,
141+
PIV_TAG_PRINTINFO = 0x5FC109,
141142
PIV_TAG_DISCOV = 0x7E,
142143
PIV_TAG_CERT_9A = 0x5FC105,
143144
PIV_TAG_CERT_9C = 0x5FC10A,

pivy-tool.c

+181-24
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ int PEM_write_X509(FILE *fp, X509 *x);
6969
boolean_t debug = B_FALSE;
7070
static boolean_t parseable = B_FALSE;
7171
static const char *cn = NULL;
72+
static const char *upn = NULL;
73+
static boolean_t save_pinfo_admin = B_TRUE;
7274
static uint8_t *guid = NULL;
7375
static size_t guid_len = 0;
7476
static uint min_retries = 1;
@@ -542,6 +544,90 @@ cmd_list(void)
542544
return (ERRF_OK);
543545
}
544546

547+
static errf_t *
548+
save_pinfo_admin_key(struct piv_token *tk)
549+
{
550+
struct tlv_state *tlv;
551+
errf_t *err;
552+
553+
tlv = tlv_init_write();
554+
tlv_push(tlv, 0x88);
555+
tlv_push(tlv, 0x89);
556+
tlv_write(tlv, admin_key, 24);
557+
tlv_pop(tlv);
558+
tlv_pop(tlv);
559+
560+
err = piv_write_file(tk, PIV_TAG_PRINTINFO,
561+
tlv_buf(tlv), tlv_len(tlv));
562+
tlv_free(tlv);
563+
return (err);
564+
}
565+
566+
static errf_t *
567+
try_pinfo_admin_key(struct piv_token *tk)
568+
{
569+
uint8_t *data = NULL;
570+
size_t dlen = 0;
571+
errf_t *err;
572+
uint tag;
573+
struct tlv_state *tlv = NULL;
574+
575+
assert_pin(tk, B_FALSE);
576+
again:
577+
err = piv_read_file(tk, PIV_TAG_PRINTINFO, &data, &dlen);
578+
if (err && errf_caused_by(err, "PermissionError")) {
579+
assert_pin(tk, B_TRUE);
580+
goto again;
581+
}
582+
if (err == ERRF_OK) {
583+
tlv = tlv_init(data, 0, dlen);
584+
while (!tlv_at_end(tlv)) {
585+
if ((err = tlv_read_tag(tlv, &tag)))
586+
goto out;
587+
if (tag == 0x88) {
588+
if ((err = tlv_read_tag(tlv, &tag)))
589+
goto out;
590+
if (tag == 0x89) {
591+
uint8_t *key;
592+
size_t keylen;
593+
err = tlv_read_alloc(tlv, &key,
594+
&keylen);
595+
if (err)
596+
goto out;
597+
if ((err = tlv_end(tlv)))
598+
goto out;
599+
if (keylen == 24) {
600+
admin_key = key;
601+
err = ERRF_OK;
602+
} else {
603+
err = errf("BadLength", NULL,
604+
"Data is wrong length for "
605+
"an admin key (%d bytes)",
606+
keylen);
607+
goto out;
608+
}
609+
} else {
610+
tlv_skip(tlv);
611+
}
612+
if ((err = tlv_end(tlv)))
613+
goto out;
614+
} else {
615+
tlv_skip(tlv);
616+
}
617+
}
618+
bunyan_log(BNY_DEBUG, "using admin key from printedinfo file",
619+
NULL);
620+
tlv_free(tlv);
621+
tlv = NULL;
622+
}
623+
624+
out:
625+
if (tlv != NULL)
626+
tlv_abort(tlv);
627+
freezero(data, dlen);
628+
return (err);
629+
}
630+
545631
static errf_t *
546632
cmd_init(void)
547633
{
@@ -628,7 +714,14 @@ cmd_init(void)
628714
if ((err = piv_txn_begin(selk)))
629715
return (err);
630716
assert_select(selk);
717+
admin_again:
631718
err = piv_auth_admin(selk, admin_key, 24);
719+
if (err && errf_caused_by(err, "PermissionError") &&
720+
admin_key == DEFAULT_ADMIN_KEY) {
721+
err = try_pinfo_admin_key(selk);
722+
if (err == ERRF_OK)
723+
goto admin_again;
724+
}
632725
if (err == ERRF_OK) {
633726
err = piv_write_file(selk, PIV_TAG_CARDCAP,
634727
tlv_buf(ccc), tlv_len(ccc));
@@ -679,6 +772,13 @@ cmd_set_admin(uint8_t *new_admin_key)
679772
if (err) {
680773
err = funcerrf(err, "Failed to set new admin key");
681774
}
775+
if (!err && save_pinfo_admin) {
776+
admin_key = new_admin_key;
777+
if ((err = save_pinfo_admin_key(selk))) {
778+
err = funcerrf(err, "Failed to write new "
779+
"admin key to printed info object");
780+
}
781+
}
682782
}
683783
piv_txn_end(selk);
684784

@@ -890,7 +990,7 @@ selfsign_slot(uint slotid, enum piv_alg alg, struct sshkey *pub)
890990
X509 *cert;
891991
EVP_PKEY *pkey;
892992
X509_NAME *subj;
893-
const char *ku, *basic;
993+
const char *ku, *basic, *eku = NULL;
894994
char *name;
895995
enum sshdigest_types wantalg, hashalg;
896996
int nid;
@@ -904,30 +1004,38 @@ selfsign_slot(uint slotid, enum piv_alg alg, struct sshkey *pub)
9041004
X509_EXTENSION *ext;
9051005
X509V3_CTX x509ctx;
9061006
const char *guidhex;
1007+
const char *myupn = upn;
1008+
const char *mycn = cn;
9071009

908-
guidhex = piv_token_guid_hex(selk);
1010+
guidhex = piv_token_shortid(selk);
1011+
1012+
name = calloc(1, 64);
9091013

9101014
switch (slotid) {
9111015
case 0x9A:
912-
name = "piv-auth";
1016+
snprintf(name, 64, "piv-auth@%s", guidhex);
9131017
basic = "critical,CA:FALSE";
9141018
ku = "critical,digitalSignature,nonRepudiation";
1019+
eku = "clientAuth,1.3.6.1.4.1.311.20.2.2";
1020+
if (myupn == NULL)
1021+
myupn = getenv("LOGNAME");
9151022
break;
9161023
case 0x9C:
917-
name = "piv-sign";
1024+
snprintf(name, 64, "piv-sign@%s", guidhex);
9181025
basic = "critical,CA:TRUE";
9191026
ku = "critical,digitalSignature,nonRepudiation,"
9201027
"keyCertSign,cRLSign";
9211028
break;
9221029
case 0x9D:
923-
name = "piv-key-mgmt";
1030+
snprintf(name, 64, "piv-key-mgmt@%s", guidhex);
9241031
basic = "critical,CA:FALSE";
9251032
ku = "critical,keyAgreement,keyEncipherment,dataEncipherment";
9261033
break;
9271034
case 0x9E:
928-
name = "piv-card-auth";
1035+
snprintf(name, 64, "piv-card-auth@%s", guidhex);
9291036
basic = "critical,CA:FALSE";
9301037
ku = "critical,digitalSignature,nonRepudiation";
1038+
eku = "clientAuth";
9311039
break;
9321040
case 0x82:
9331041
case 0x83:
@@ -949,8 +1057,7 @@ selfsign_slot(uint slotid, enum piv_alg alg, struct sshkey *pub)
9491057
case 0x93:
9501058
case 0x94:
9511059
case 0x95:
952-
name = calloc(1, 64);
953-
snprintf(name, 64, "piv-retired-%u", slotid - 0x81);
1060+
snprintf(name, 64, "piv-retired-%u@%s", slotid - 0x81, guidhex);
9541061
basic = "critical,CA:FALSE";
9551062
ku = "critical,digitalSignature,nonRepudiation";
9561063
if (slotid - 0x82 > piv_token_keyhistory_oncard(selk)) {
@@ -966,6 +1073,17 @@ selfsign_slot(uint slotid, enum piv_alg alg, struct sshkey *pub)
9661073
return (err);
9671074
}
9681075

1076+
if (myupn != NULL && eku == NULL) {
1077+
eku = "clientAuth,1.3.6.1.4.1.311.20.2.2";
1078+
}
1079+
1080+
if (myupn != NULL) {
1081+
char *newupn = calloc(1, 128);
1082+
snprintf(newupn, 128,
1083+
"otherName:1.3.6.1.4.1.311.20.2.3;UTF8:%s", myupn);
1084+
myupn = newupn;
1085+
}
1086+
9691087
pkey = EVP_PKEY_new();
9701088
VERIFY(pkey != NULL);
9711089
if (pub->type == KEY_RSA) {
@@ -1021,15 +1139,11 @@ selfsign_slot(uint slotid, enum piv_alg alg, struct sshkey *pub)
10211139

10221140
subj = X509_NAME_new();
10231141
VERIFY(subj != NULL);
1024-
if (cn == NULL) {
1025-
VERIFY(X509_NAME_add_entry_by_NID(subj, NID_title, MBSTRING_ASC,
1026-
(unsigned char *)name, -1, -1, 0) == 1);
1027-
VERIFY(X509_NAME_add_entry_by_NID(subj, NID_commonName,
1028-
MBSTRING_ASC, (unsigned char *)guidhex, -1, -1, 0) == 1);
1029-
} else {
1030-
VERIFY(X509_NAME_add_entry_by_NID(subj, NID_commonName,
1031-
MBSTRING_ASC, (unsigned char *)cn, -1, -1, 0) == 1);
1142+
if (mycn == NULL) {
1143+
mycn = name;
10321144
}
1145+
VERIFY(X509_NAME_add_entry_by_NID(subj, NID_commonName,
1146+
MBSTRING_ASC, (unsigned char *)mycn, -1, -1, 0) == 1);
10331147
/*VERIFY(X509_NAME_add_entry_by_NID(subj, NID_organizationalUnitName,
10341148
MBSTRING_ASC, (unsigned char *)"tokens", -1, -1, 0) == 1);
10351149
VERIFY(X509_NAME_add_entry_by_NID(subj, NID_organizationName,
@@ -1051,6 +1165,22 @@ selfsign_slot(uint slotid, enum piv_alg alg, struct sshkey *pub)
10511165
X509_add_ext(cert, ext, -1);
10521166
X509_EXTENSION_free(ext);
10531167

1168+
if (eku != NULL) {
1169+
ext = X509V3_EXT_conf_nid(NULL, &x509ctx, NID_ext_key_usage,
1170+
(char *)eku);
1171+
VERIFY(ext != NULL);
1172+
X509_add_ext(cert, ext, -1);
1173+
X509_EXTENSION_free(ext);
1174+
}
1175+
1176+
if (myupn != NULL) {
1177+
ext = X509V3_EXT_conf_nid(NULL, &x509ctx, NID_subject_alt_name,
1178+
(char *)myupn);
1179+
VERIFY(ext != NULL);
1180+
X509_add_ext(cert, ext, -1);
1181+
X509_EXTENSION_free(ext);
1182+
}
1183+
10541184
VERIFY(X509_set_pubkey(cert, pkey) == 1);
10551185

10561186
cert->sig_alg->algorithm = OBJ_nid2obj(nid);
@@ -1185,7 +1315,14 @@ cmd_import(uint slotid)
11851315
if ((err = piv_txn_begin(selk)))
11861316
return (err);
11871317
assert_select(selk);
1318+
admin_again:
11881319
err = piv_auth_admin(selk, admin_key, 24);
1320+
if (err && errf_caused_by(err, "PermissionError") &&
1321+
admin_key == DEFAULT_ADMIN_KEY) {
1322+
err = try_pinfo_admin_key(selk);
1323+
if (err == ERRF_OK)
1324+
goto admin_again;
1325+
}
11891326
if (err == ERRF_OK) {
11901327
err = ykpiv_import(selk, slotid, priv, pinpolicy, touchpolicy);
11911328
}
@@ -1241,7 +1378,14 @@ cmd_generate(uint slotid, enum piv_alg alg)
12411378
if ((err = piv_txn_begin(selk)))
12421379
return (err);
12431380
assert_select(selk);
1381+
admin_again:
12441382
err = piv_auth_admin(selk, admin_key, 24);
1383+
if (err && errf_caused_by(err, "PermissionError") &&
1384+
admin_key == DEFAULT_ADMIN_KEY) {
1385+
err = try_pinfo_admin_key(selk);
1386+
if (err == ERRF_OK)
1387+
goto admin_again;
1388+
}
12451389
if (err == ERRF_OK) {
12461390
if (pinpolicy == YKPIV_PIN_DEFAULT &&
12471391
touchpolicy == YKPIV_TOUCH_DEFAULT) {
@@ -2033,15 +2177,15 @@ cmd_setup(SCARDCONTEXT ctx)
20332177
arc4random_buf(admin_key, 24);
20342178
if (usetouch)
20352179
touchpolicy = YKPIV_TOUCH_ALWAYS;
2036-
hex = buf_to_hex(admin_key, 24, B_FALSE);
2037-
printf("Admin 3DES key: %s\n", hex);
2038-
fprintf(stderr, "This key is only needed to generate new slot keys or "
2039-
"change certificates in future. If you don't intend to do either "
2040-
"you can simply forget about this key and the Yubikey will be "
2041-
"sealed.\n");
2180+
20422181
if ((err = cmd_set_admin(admin_key)))
20432182
return (err);
20442183

2184+
if (!save_pinfo_admin) {
2185+
hex = buf_to_hex(admin_key, 24, B_FALSE);
2186+
printf("Admin 3DES key: %s\n", hex);
2187+
}
2188+
20452189
fprintf(stderr, "Done!\n");
20462190

20472191
return (ERRF_OK);
@@ -2119,6 +2263,8 @@ usage(void)
21192263
" RSA algos: rsa1024, rsa2048, rsa4096\n"
21202264
" -n <cn> Set a CN= attribute to be used on\n"
21212265
" the new slot's certificate\n"
2266+
" -u <upn> Set a UPN= attribute to be used on\n"
2267+
" the new slot's certificate\n"
21222268
" -t <never|always|cached>\n"
21232269
" Set the touch policy. Only supported\n"
21242270
" with YubiKeys\n"
@@ -2127,7 +2273,12 @@ usage(void)
21272273
"\n"
21282274
"Options for 'box'/'unbox':\n"
21292275
" -k <pubkey> Use a public key for box operation\n"
2130-
" instead of a slot\n");
2276+
" instead of a slot\n"
2277+
"\n"
2278+
"Options for 'set-admin'/'setup':\n"
2279+
" -R Don't save admin key in the PIV\n"
2280+
" 'printed info' object (compat with\n"
2281+
" Yubico PIV manager)\n");
21312282
exit(EXIT_BAD_ARGS);
21322283
}
21332284

@@ -2140,7 +2291,7 @@ usage(void)
21402291
"f(force)"
21412292
"K:(admin-key)"
21422293
"k:(key)";*/
2143-
const char *optstring = "dpg:P:a:fK:k:n:t:i:";
2294+
const char *optstring = "dpg:P:a:fK:k:n:t:i:u:R";
21442295

21452296
int
21462297
main(int argc, char *argv[])
@@ -2167,6 +2318,9 @@ main(int argc, char *argv[])
21672318
if (++d_level > 1)
21682319
piv_full_apdu_debug = B_TRUE;
21692320
break;
2321+
case 'R':
2322+
save_pinfo_admin = B_FALSE;
2323+
break;
21702324
case 'K':
21712325
if (strcmp(optarg, "default") == 0) {
21722326
admin_key = DEFAULT_ADMIN_KEY;
@@ -2186,6 +2340,9 @@ main(int argc, char *argv[])
21862340
"24 bytes in length (%d given)", len);
21872341
}
21882342
break;
2343+
case 'u':
2344+
upn = optarg;
2345+
break;
21892346
case 'n':
21902347
cn = optarg;
21912348
break;

0 commit comments

Comments
 (0)