-
-
Notifications
You must be signed in to change notification settings - Fork 211
Expand file tree
/
Copy pathoem-factory-reset.sh
More file actions
executable file
·1718 lines (1531 loc) · 65.2 KB
/
Copy pathoem-factory-reset.sh
File metadata and controls
executable file
·1718 lines (1531 loc) · 65.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# Automated setup of TPM, GPG keys, and disk
# TODO: Find a stronger mechanism for passing GPG commands that avoids the
# brittle --command-fd loop behavior. The current approach using
# "quit" relies on internal GPG behavior (keyedit.c:1510-1513, :2227-2229)
# and may break in future GPG versions.
set -o pipefail
## External files sourced
. /etc/functions.sh
. /etc/gui_functions.sh
. /etc/gpg_functions.sh
. /etc/luks-functions.sh
. /tmp/config
# Reset background color - may be inherited as "error" from TPM error menu
BG_COLOR_MAIN_MENU="normal"
# Allow firmware display in OEM reset context (flag may have been set during integrity report)
rm -f /tmp/hotpkey_fw_shown
TRACE_FUNC
# Detect branding early — $DONGLE_BRAND is used throughout this script.
STATUS "Initializing USB security dongle detection"
enable_usb
detect_usb_security_dongle_branding
STATUS_OK "USB security dongle detection initialized"
# use TERM to exit on error
trap "exit 1" TERM
export TOP_PID=$$
## Static local variables
CLEAR="--clear"
CONTINUE="--yes-button Continue"
CANCEL="--no-button Cancel"
HEIGHT="0"
WIDTH="80"
# Default values
USER_PIN_DEF=123456
ADMIN_PIN_DEF=12345678
TPM_PASS_DEF=12345678
GPG_GEN_KEY_IN_MEMORY="n"
GPG_GEN_KEY_IN_MEMORY_COPY_TO_SMARTCARD="n"
GPG_EXPORT=0
#Circumvent Librem Key/Nitrokey HOTP firmware bug https://github.com/osresearch/heads/issues/1167
MAX_HOTP_GPG_PIN_LENGTH=25
# What are the Security components affected by custom passphrases
CUSTOM_PASS_AFFECTED_COMPONENTS=""
# Default GPG Algorithm is RSA (key length set by RSA_KEY_LENGTH below)
# NIST P-256 also supported for Nitrokey 3 (chose NIST P-256 when RSA was not generated into secrets app)
GPG_ALGO="RSA"
# Default RSA key length is 3072 bits for OEM key gen
# 4096 are way longer to generate in smartcard
RSA_KEY_LENGTH=3072
# If we use complex generated passphrases, we will really try hard to make the
# user record them
MAKE_USER_RECORD_PASSPHRASES=
# Function to handle --mode parameter
handle_mode() {
TRACE_FUNC
local mode=$1
case $mode in
oem)
DEBUG "OEM mode selected"
CUSTOM_SINGLE_PASS=$(generate_passphrase --number_words 2 --max_length $MAX_HOTP_GPG_PIN_LENGTH)
USER_PIN=$CUSTOM_SINGLE_PASS
ADMIN_PIN=$CUSTOM_SINGLE_PASS
TPM_PASS=$CUSTOM_SINGLE_PASS
# User doesn't know this passphrase, really badger them to record it
MAKE_USER_RECORD_PASSPHRASES=y
title_text="OEM Factory Reset Mode"
;;
user)
DEBUG "User mode selected"
USER_PIN=$(generate_passphrase --number_words 2 --max_length $MAX_HOTP_GPG_PIN_LENGTH)
ADMIN_PIN=$(generate_passphrase --number_words 2 --max_length $MAX_HOTP_GPG_PIN_LENGTH)
TPM_PASS=$ADMIN_PIN
# User doesn't know this passphrase, really badger them to record it
MAKE_USER_RECORD_PASSPHRASES=y
title_text="User Re-Ownership Mode"
;;
*)
WARN "Unknown oem-factory-reset.sh launched mode, setting PINs to weak defaults"
USER_PIN=$USER_PIN_DEF
ADMIN_PIN=$ADMIN_PIN_DEF
TPM_PASS=$ADMIN_PIN_DEF
;;
esac
}
# Parse command-line arguments
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
--mode)
MODE="$2"
shift # past argument
shift # past value
;;
*)
shift # past unrecognized argument
;;
esac
done
# Handle the --mode parameter if provided
if [[ -n "$MODE" ]]; then
handle_mode "$MODE"
fi
#Override RSA_KEY_LENGTH to 2048 bits for Canokey under qemu testing boards until canokey fixes
if [[ "$CONFIG_BOARD_NAME" == qemu-* ]] && [[ "$DONGLE_BRAND" == "Canokey" ]]; then
DEBUG "Overriding RSA_KEY_LENGTH to 2048 bits for Canokey under qemu testing boards"
RSA_KEY_LENGTH=2048
fi
GPG_USER_NAME="OEM Key"
GPG_KEY_NAME=$(date +%Y%m%d%H%M%S)
GPG_USER_MAIL="oem-${GPG_KEY_NAME}@example.com"
GPG_USER_COMMENT="OEM-generated key"
SKIP_BOOT="n"
## functions
DIE() {
local msg=$1
if [ -n "$msg" ]; then
WARN "$msg"
fi
kill -s TERM $TOP_PID
exit 1
}
local_whiptail_error() {
TRACE_FUNC
local msg=$1
if [ "$msg" = "" ]; then
DIE "whiptail error: An error msg is required"
fi
whiptail_error --msgbox "${msg}\n\n" $HEIGHT $WIDTH --title "Error"
}
whiptail_error_die() {
local_whiptail_error "$@"
DIE
}
mount_boot() {
TRACE_FUNC
# Mount local disk if it is not already mounted.
# Added so that 'o' can be typed early at boot to enter directly into OEM Factory Reset
if ! grep -q /boot /proc/mounts; then
# try to mount if CONFIG_BOOT_DEV exists
if [ -e "$CONFIG_BOOT_DEV" ]; then
mount -o ro $CONFIG_BOOT_DEV /boot || DIE "Failed to mount $CONFIG_BOOT_DEV. Please change boot device under Configuration > Boot Device"
fi
fi
}
reset_nk3_secret_app() {
TRACE_FUNC
# Reset Nitrokey 3 Secrets app PIN with $ADMIN_PIN (default 12345678, or customised)
if [ "$DONGLE_BRAND" = "Nitrokey 3" ] && [ -x /bin/hotp_verification ]; then
STATUS "Resetting Nitrokey 3 Secrets app (physical touch will be required)"
# TODO: change message when https://github.com/Nitrokey/nitrokey-hotp-verification/issues/41 is fixed
# Reset Nitrokey 3 secret app with PIN
# Do 3 attempts to reset Nitrokey 3 Secrets app if return code is 3 (no touch)
for attempt in 1 2 3; do
if hotp_verification reset "${ADMIN_PIN}"; then
STATUS_OK "Nitrokey 3 Secrets app reset"
return 0
else
error_code=$?
if [ $error_code -eq 3 ] && [ $attempt -lt 3 ]; then
whiptail_warning --msgbox "$DONGLE_BRAND requires physical presence: touch the dongle when requested" $HEIGHT $WIDTH --title "$DONGLE_BRAND secrets app reset attempt: $attempt/3"
else
whiptail_error_die "Nitrokey 3's Secrets app reset failed with error:$error_code. Contact Nitrokey support"
fi
fi
done
fi
}
#Generate a gpg master key: no expiration date, ${RSA_KEY_LENGTH} bits
#This key will be used to sign 3 subkeys: encryption, authentication and signing
#The master key and subkeys will be copied to backup, and the subkeys moved from memory keyring to the smartcard
generate_inmemory_RSA_master_and_subkeys() {
TRACE_FUNC
STATUS "Generating RSA ${RSA_KEY_LENGTH}-bit master key for $DONGLE_BRAND"
# Generate GPG master key
{
echo "Key-Type: RSA" # RSA key
echo "Key-Length: ${RSA_KEY_LENGTH}" # RSA key length
echo "Key-Usage: sign" # RSA key usage
echo "Name-Real: ${GPG_USER_NAME}" # User name
echo "Name-Comment: ${GPG_USER_COMMENT}" # User comment
echo "Name-Email: ${GPG_USER_MAIL}" # User email
echo "Expire-Date: 0" # No expiration date
echo "Passphrase: ${ADMIN_PIN}" # Admin PIN
echo "%commit" # Commit changes
} | DO_WITH_DEBUG gpg --expert --batch --command-fd=0 --status-fd=1 --pinentry-mode=loopback --generate-key >/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG on-card RSA key generation output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key generation failed!\n\n$ERROR"
fi
STATUS_OK "RSA ${RSA_KEY_LENGTH}-bit master key generated"
STATUS "Generating RSA signing subkey for $DONGLE_BRAND"
# Add signing subkey
{
echo addkey # add key in --edit-key mode
echo 4 # RSA (sign only)
echo ${RSA_KEY_LENGTH} # Signing key size set to RSA_KEY_LENGTH
echo 0 # No expiration date
echo ${ADMIN_PIN} # Local keyring admin pin (passphrase requested before key creation, no confirm prompt)
echo save # save changes and commit to keyring
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key "${GPG_USER_MAIL}" \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG RSA signing subkey output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key signing subkey generation failed!\n\n$ERROR"
fi
STATUS_OK "RSA signing subkey generated"
STATUS "Generating RSA encryption subkey for $DONGLE_BRAND"
#Add encryption subkey
{
echo addkey # add key in --edit-key mode
echo 6 # RSA (encrypt only)
echo ${RSA_KEY_LENGTH} # Encryption key size set to RSA_KEY_LENGTH
echo 0 # No expiration date
echo ${ADMIN_PIN} # Local keyring admin pin (passphrase requested before key creation, no confirm prompt)
echo save # save changes and commit to keyring
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key "${GPG_USER_MAIL}" \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG RSA encryption subkey output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key encryption subkey generation failed!\n\n$ERROR"
fi
STATUS_OK "RSA encryption subkey generated"
STATUS "Generating RSA authentication subkey for $DONGLE_BRAND"
#Add authentication subkey
{
#Authentication subkey needs gpg in expert mode to select RSA custom mode (8)
# in order to disable encryption and signing capabilities of subkey
# and then enable authentication capability
echo addkey # add key in --edit-key mode
echo 8 # RSA (set your own capabilities)
echo S # disable sign capability
echo E # disable encryption capability
echo A # enable authentication capability
echo Q # Quit
echo ${RSA_KEY_LENGTH} # Authentication key size set to RSA_KEY_LENGTH
echo 0 # No expiration date
echo ${ADMIN_PIN} # Local keyring admin pin (passphrase requested before key creation, no confirm prompt)
echo save # save changes and commit to keyring
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --expert --edit-key "${GPG_USER_MAIL}" \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG RSA authentication subkey output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key authentication subkey generation failed!\n\n$ERROR"
fi
STATUS_OK "RSA authentication subkey generated"
}
#Generate a gpg master key: no expiration date, NIST P-256 key (ECC)
#This key will be used to sign 3 subkeys: encryption, authentication and signing
#The master key and subkeys will be copied to backup, and the subkeys moved from memory keyring to the smartcard
generate_inmemory_p256_master_and_subkeys() {
TRACE_FUNC
STATUS "Generating NIST P-256 master key for $DONGLE_BRAND"
DEBUG "GPG batch key generation: Key-Type=ECDSA, Key-Curve=nistp256, Key-Usage=cert"
{
echo "Key-Type: ECDSA" # ECDSA key
echo "Key-Curve: nistp256" # ECDSA key curve
echo "Key-Usage: cert" # ECDSA key usage
echo "Name-Real: ${GPG_USER_NAME}" # User name
echo "Name-Comment: ${GPG_USER_COMMENT}" # User comment
echo "Name-Email: ${GPG_USER_MAIL}" # User email
echo "Passphrase: ${ADMIN_PIN}" # Local keyring admin pin
echo "Expire-Date: 0" # No expiration date
echo "%commit" # Commit changes
} | DO_WITH_DEBUG gpg --expert --batch --command-fd=0 --status-fd=1 --pinentry-mode=loopback --generate-key \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG p256 master key generation output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG NIST P-256 Key generation failed!\n\n$ERROR"
fi
STATUS_OK "NIST P-256 master key generated"
#Keep Master key fingerprint for add key calls
MASTER_KEY_FP=$(gpg --list-secret-keys --with-colons | grep fpr | cut -d: -f10)
STATUS "Generating NIST P-256 signing subkey for $DONGLE_BRAND"
{
echo addkey # add key in --edit-key mode
echo 11 # ECC own set capability
echo Q # sign already present, do not modify
echo 3 # P-256
echo 0 # No validity/expiration date
echo ${ADMIN_PIN} # Local keyring admin pin
echo save # save changes and commit to keyring
} | DO_WITH_DEBUG gpg --expert --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key ${MASTER_KEY_FP} >/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG p256 signing subkey output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR_MSG=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "Failed to add ECC nistp256 signing key to master key\n\n${ERROR_MSG}"
fi
STATUS_OK "NIST P-256 signing subkey generated"
STATUS "Generating NIST P-256 encryption subkey for $DONGLE_BRAND"
{
echo addkey
echo 12 # ECC own set capability
echo 3 # P-256
echo 0 # No validity/expiration date
echo ${ADMIN_PIN} # Local keyring admin pin
echo save # save changes and commit to keyring
} | DO_WITH_DEBUG gpg --expert --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key ${MASTER_KEY_FP} >/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG p256 encryption subkey output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR_MSG=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "Failed to add ECC nistp256 encryption key to master key\n\n${ERROR_MSG}"
fi
STATUS_OK "NIST P-256 encryption subkey generated"
STATUS "Generating NIST P-256 authentication subkey for $DONGLE_BRAND"
{
echo addkey # add key in --edit-key mode
echo 11 # ECC own set capability
echo S # deactivate sign
echo A # activate auth
echo Q # Quit
echo 3 # P-256
echo 0 # no expiration
echo ${ADMIN_PIN} # Local keyring admin pin
echo save # save changes and commit to keyring
} | DO_WITH_DEBUG gpg --expert --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key ${MASTER_KEY_FP} >/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG p256 authentication subkey output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR_MSG=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "Failed to add ECC nistp256 authentication key to master key\n\n${ERROR_MSG}"
fi
STATUS_OK "NIST P-256 authentication subkey generated"
}
#Function to move current gpg keyring subkeys to card (keytocard)
# This is aimed to be used after having generated master key and subkeys in memory and having backed up them to a LUKS container
# This function will keytocard the subkeys from the master key in the keyring
# The master key will be kept in the keyring
# The master key was already used to sign the subkeys, so it is not needed anymore
# Delete the master key from the keyring once key to card is done (already backed up on LUKS private partition)
keytocard_subkeys_to_smartcard() {
TRACE_FUNC
#make sure usb ready and USB Security dongle ready to communicate with
enable_usb
enable_usb_storage
STATUS "Accessing $DONGLE_BRAND OpenPGP smartcard"
gpg --card-status >/dev/null 2>&1 || DIE "Error getting GPG card status"
gpg_key_factory_reset
STATUS "Moving subkeys to $DONGLE_BRAND"
{
echo "key 1" #Toggle on Signature key in --edit-key mode on local keyring
echo "keytocard" #Move Signature key to smartcard
echo "1" #Select Signature key key slot on smartcard
echo "${ADMIN_PIN}" #Local keyring Subkey PIN
echo "${ADMIN_PIN_DEF}" #Smartcard Admin PIN (prompted once; scdaemon caches it for subsequent keytocard ops)
echo "key 1" #Toggle off Signature key
echo "key 2" #Toggle on Encryption key
echo "keytocard" #Move Encryption key to smartcard
echo "2" #Select Encryption key key slot on smartcard
echo "${ADMIN_PIN}" #Local keyring Subkey PIN (card PIN already cached by scdaemon)
echo "key 2" #Toggle off Encryption key
echo "key 3" #Toggle on Authentication key
echo "keytocard" #Move Authentication key to smartcard
echo "3" #Select Authentication key slot on smartcard
echo "${ADMIN_PIN}" #Local keyring Subkey PIN (card PIN still cached by scdaemon)
echo "key 3" #Toggle off Authentication key
echo "save" #Save changes and commit to keyring
} | DO_WITH_DEBUG gpg --expert --command-fd=0 --status-fd=1 --pinentry-mode=loopback --edit-key "${GPG_USER_MAIL}" \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG keytocard output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key moving subkeys to smartcard failed!\n\n$ERROR"
fi
STATUS_OK "Subkeys moved to smartcard"
TRACE_FUNC
}
#Whiptail prompt to insert to be wiped thumb drive
prompt_insert_to_be_wiped_thumb_drive() {
TRACE_FUNC
#Whiptail warning about having only desired to be wiped thumb drive inserted
whiptail_warning --title 'WARNING: Please insert the thumb drive to be wiped' \
--msgbox "The thumb drive will be WIPED next.\n\nPlease connect only the thumb drive to be wiped and disconnect others." 0 80 ||
DIE "Error displaying warning about having only desired to be wiped thumb drive inserted"
}
set_card_identity() {
TRACE_FUNC
# Determine which fields we have custom values for
local set_name=0 set_login=0
local surname given
# Name: skip if still the OEM default
if [ "$GPG_USER_NAME" != "OEM Key" ] && [ -n "$GPG_USER_NAME" ]; then
set_name=1
# OpenPGP card stores surname and given name separately;
# gpg displays them as "given surname"
if [[ "$GPG_USER_NAME" == *" "* ]]; then
given="${GPG_USER_NAME% *}"
surname="${GPG_USER_NAME##* }"
else
surname="$GPG_USER_NAME"
given=""
fi
DEBUG "Will set cardholder name: surname='$surname' given='$given'"
else
DEBUG "Skipping cardholder name: no custom name set"
fi
# Login: skip if still the auto-generated OEM default (oem-*@example.com)
if [ -n "$GPG_USER_MAIL" ] && [[ "$GPG_USER_MAIL" != oem-*@example.com ]]; then
set_login=1
DEBUG "Will set login data: '$GPG_USER_MAIL'"
else
DEBUG "Skipping login data: no custom email set"
fi
[ "$set_name" -eq 0 ] && [ "$set_login" -eq 0 ] && return
STATUS "Setting identity fields on OpenPGP smartcard"
{
echo "admin"
if [ "$set_name" -eq 1 ]; then
echo "name"
echo "${surname}"
echo "${given}"
# scdaemon caches the admin PIN from the preceding keytocard/generate
# session; name and login do not re-prompt for it
fi
if [ "$set_login" -eq 1 ]; then
echo "login"
echo "${GPG_USER_MAIL}"
# scdaemon admin PIN still cached; no re-prompt needed
fi
echo "quit"
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=2 --pinentry-mode=loopback --card-edit ||
DIE "Failed to set identity fields on OpenPGP smartcard"
local summary=""
[ "$set_name" -eq 1 ] && summary="${given:+$given }${surname}"
[ "$set_login" -eq 1 ] && summary="${summary:+$summary, }${GPG_USER_MAIL}"
STATUS_OK "Card identity set: $summary"
#TODO: set card `url` field and GPG key preferred keyserver after uploading to keys.openpgp.org
# Two separate operations needed:
# 1. card `url` — set via gpg --card-edit admin → url → <fetch_url>
# 2. key `keyserver` preference — set via gpg --edit-key → keyserver → <url> → save
# (applies to both on-card and in-memory key paths)
# Requires: network access in initrd, curl, and user email verification on keyserver.
# Note: keys.openpgp.org hides UID until owner verifies email — upload works but key
# is not searchable by email until verified from a normal OS session after provisioning.
}
#export master key and subkeys to thumbdrive's private LUKS contained partition
export_master_key_subkeys_and_revocation_key_to_private_LUKS_container() {
TRACE_FUNC
#Sanity check on passed arguments
while [ $# -gt 0 ]; do
case "$1" in
--mode)
mode="$2"
shift
shift
;;
--device)
device="$2"
shift
shift
;;
--mountpoint)
mountpoint="$2"
shift
shift
;;
--pass)
pass="${2}"
shift
shift
;;
*)
DIE "Error: unknown argument: $1"
;;
esac
done
mount-usb.sh --mode "$mode" --device "$device" --mountpoint "$mountpoint" --pass "$pass" || DIE "Error mounting thumb drive's private partition"
#Export master key and subkeys to thumb drive
STATUS "Exporting master key and subkeys to backup LUKS container"
if gpg --export-secret-key --armor --pinentry-mode loopback --passphrase="${pass}" "${GPG_USER_MAIL}" >"$mountpoint"/privkey.sec 2>/tmp/gpg_export_err; then
DEBUG "GPG master key export succeeded"
else
DEBUG "GPG master key export failed: $(cat /tmp/gpg_export_err)"
DIE "Error exporting master key to private LUKS container's partition"
fi
if gpg --export-secret-subkeys --armor --pinentry-mode loopback --passphrase="${pass}" "${GPG_USER_MAIL}" >"$mountpoint"/subkeys.sec 2>/tmp/gpg_export_err; then
DEBUG "GPG subkeys export succeeded"
else
DEBUG "GPG subkeys export failed: $(cat /tmp/gpg_export_err)"
DIE "Error exporting subkeys to private LUKS container's partition"
fi
#copy whole keyring to thumb drive, including revocation key and trust database
cp -af ~/.gnupg "$mountpoint"/.gnupg || DIE "Error copying whole keyring to private LUKS container's partition"
#Unmount private LUKS container's mount point
umount "$mountpoint" || DIE "Error unmounting private LUKS container's mount point"
STATUS_OK "Master key and subkeys backed up to USB"
TRACE_FUNC
}
#Export public key to thumb drive's public partition
export_public_key_to_thumbdrive_public_partition() {
TRACE_FUNC
#Sanity check on passed arguments
while [ $# -gt 0 ]; do
case "$1" in
--mode)
mode="$2"
shift
shift
;;
--device)
device="$2"
shift
shift
;;
--mountpoint)
mountpoint="$2"
shift
shift
;;
*)
DIE "Error: unknown argument: $1"
;;
esac
done
#pass non-empty arguments to --pass, --mountpoint, --device, --mode
mount-usb.sh --device "$device" --mode "$mode" --mountpoint "$mountpoint" || DIE "Error mounting thumb drive's public partition"
#TODO: reuse "Obtain GPG key ID" so that pubkey on public thumb drive partition is named after key ID
STATUS "Exporting public key to USB"
if gpg --export --armor "${GPG_USER_MAIL}" >"$mountpoint"/pubkey.asc 2>/tmp/gpg_export_err; then
DEBUG "GPG public key export succeeded"
else
DEBUG "GPG public key export failed: $(cat /tmp/gpg_export_err)"
DIE "Error exporting public key to thumb drive's public partition"
fi
umount "$mountpoint" || DIE "Error unmounting thumb drive's public partition"
STATUS_OK "Public key exported to USB"
TRACE_FUNC
}
# Select thumb drive and LUKS container size for GPG key export
# Sets variables containing selections:
# - thumb_drive
# - thumb_drive_luks_percent
select_thumb_drive_for_key_material() {
TRACE_FUNC
#enable usb storage
enable_usb
enable_usb_storage
prompt_insert_to_be_wiped_thumb_drive
#loop until user chooses a disk
thumb_drive=""
while [ -z "$thumb_drive" ]; do
#list usb storage devices
list_usb_storage disks >/tmp/usb_disk_list
# Abort if:
# - no disks found (prevent file_selector's nonsense prompt)
# - file_selector fails for any reason
# - user aborts (file_selector succeeds but FILE is empty)
if [ $(cat /tmp/usb_disk_list | wc -l) -gt 0 ] &&
file_selector --show-size "/tmp/usb_disk_list" "Select USB device to partition" &&
[ -n "$FILE" ]; then
# Obtain size of thumb drive to be wiped with fdisk
disk_size_bytes="$(blockdev --getsize64 "$FILE")"
if [ "$disk_size_bytes" -lt "$((128 * 1024 * 1024))" ]; then
WARN "Thumb drive size is less than 128MB!"
WARN "LUKS container needs to be at least 8MB!"
WARN "If the next operation fails, try with a bigger thumb drive"
fi
select_luks_container_size_percent
thumb_drive_luks_percent="$(cat /tmp/luks_container_size_percent)"
if ! confirm_thumb_drive_format "$FILE" "$thumb_drive_luks_percent"; then
INFO "Thumb drive wipe aborted by user"
continue
fi
#User chose and confirmed a thumb drive and its size to be wiped
thumb_drive=$FILE
else
#No USB storage device detected
WARN "No USB storage device detected! Aborting OEM Factory Reset / Re-Ownership"
sleep 3
DIE "No USB storage device detected! User decided to not wipe any thumb drive"
fi
done
}
#Wipe a thumb drive and export master key and subkeys to it
# $1 - thumb drive block device
# $2 - LUKS container percentage [1-99]
wipe_thumb_drive_and_copy_gpg_key_material() {
TRACE_FUNC
local thumb_drive thumb_drive_luks_percent
thumb_drive="$1"
thumb_drive_luks_percent="$2"
#Wipe thumb drive with a LUKS container of size $(cat /tmp/luks_container_size_percent)
prepare_thumb_drive "$thumb_drive" "$thumb_drive_luks_percent" "${ADMIN_PIN}"
#Export master key and subkeys to thumb drive first partition
export_master_key_subkeys_and_revocation_key_to_private_LUKS_container --mode rw --device "$thumb_drive"1 --mountpoint /media --pass "${ADMIN_PIN}"
#Export public key to thumb drive's public partition
export_public_key_to_thumbdrive_public_partition --mode rw --device "$thumb_drive"2 --mountpoint /media
TRACE_FUNC
}
gpg_key_factory_reset() {
TRACE_FUNC
#enable usb storage
enable_usb
# Factory reset GPG card
STATUS "GPG factory reset of $DONGLE_BRAND OpenPGP smartcard"
{
echo admin # admin menu
echo factory-reset # factory reset smartcard
echo y # confirm
echo yes # confirm
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG factory-reset output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key factory reset failed!\n\n$ERROR"
fi
# If Nitrokey Storage is inserted, reset AES keys as well
if [ "$DONGLE_BRAND" = "Nitrokey Storage" ] && [ -x /bin/hotp_verification ]; then
STATUS "Resetting Nitrokey Storage AES keys"
hotp_verification regenerate ${ADMIN_PIN_DEF}
STATUS_OK "Nitrokey Storage AES keys reset"
fi
# Toggle forced sig (good security practice, forcing PIN request for each signature request)
if gpg --card-status | grep "Signature PIN" | grep -q "not forced"; then
STATUS "Enabling forced signature PIN on smartcard"
{
echo admin # admin menu
echo forcesig # toggle forcesig
echo ${ADMIN_PIN_DEF} # local keyring PIN
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG forcesig toggle output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key forcesig toggle on failed!\n\n$ERROR"
fi
STATUS_OK "Forced signature PIN enabled"
fi
# use NIST P-256 for key generation if requested
if [ "$GPG_ALGO" = "p256" ]; then
STATUS "Setting NIST-P256 key attributes on $DONGLE_BRAND"
{
echo admin # admin menu
echo key-attr # key attributes
echo 2 # ECC
echo 3 # P-256
echo ${ADMIN_PIN_DEF} # local keyring PIN
echo 2 # ECC
echo 3 # P-256
echo ${ADMIN_PIN_DEF} # local keyring PIN
echo 2 # ECC
echo 3 # P-256
echo ${ADMIN_PIN_DEF} # local keyring PIN
} | DO_WITH_DEBUG gpg --expert --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG NIST-P256 key-attr output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "Setting key to NIST-P256 in $DONGLE_BRAND failed."
fi
STATUS_OK "NIST-P256 key attributes set on $DONGLE_BRAND"
# fallback to RSA key generation by default
elif [ "$GPG_ALGO" = "RSA" ]; then
STATUS "Setting RSA ${RSA_KEY_LENGTH}-bit key attributes on $DONGLE_BRAND (may take a minute)"
# Set RSA key length
{
echo admin
echo key-attr
echo 1 # RSA
echo ${RSA_KEY_LENGTH} #Signing key size set to RSA_KEY_LENGTH
echo ${ADMIN_PIN_DEF} #Local keyring PIN
echo 1 # RSA
echo ${RSA_KEY_LENGTH} #Encryption key size set to RSA_KEY_LENGTH
echo ${ADMIN_PIN_DEF} #Local keyring PIN
echo 1 # RSA
echo ${RSA_KEY_LENGTH} #Authentication key size set to RSA_KEY_LENGTH
echo ${ADMIN_PIN_DEF} #Local keyring PIN
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=1 --pinentry-mode=loopback --card-edit \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG RSA key-attr output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "Setting key attributed to RSA ${RSA_KEY_LENGTH} bits in $DONGLE_BRAND failed."
fi
STATUS_OK "RSA ${RSA_KEY_LENGTH}-bit key attributes set on $DONGLE_BRAND"
else
#Unknown GPG_ALGO
whiptail_error_die "Unknown GPG_ALGO: $GPG_ALGO"
fi
TRACE_FUNC
}
generate_OEM_gpg_keys() {
TRACE_FUNC
#This function simply generates subkeys in smartcard following smarcard config from gpg_key_factory_reset
if [ "$GPG_ALGO" = "RSA" ]; then
STATUS "Generating RSA ${RSA_KEY_LENGTH}-bit keys on $DONGLE_BRAND"
else
STATUS "Generating NIST P-256 keys on $DONGLE_BRAND"
fi
{
echo admin # admin menu
echo generate # generate keys
echo n # Do not export keys
echo ${ADMIN_PIN_DEF} # Default admin PIN since we just factory reset
echo ${USER_PIN_DEF} # Default user PIN since we just factory reset
echo 0 # No key expiration
echo ${GPG_USER_NAME} # User name
echo ${GPG_USER_MAIL} # User email
echo ${GPG_USER_COMMENT} # User comment
echo ${USER_PIN_DEF} # Default user PIN since we just factory reset
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=2 --pinentry-mode=loopback --card-edit \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG on-card key generation output: $(cat /tmp/gpg_card_edit_output)"
#This outputs to console \
# "gpg: checking the trustdb"
# "gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model"
# "gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u"
#TODO: Suppress this output to console (stdout shown in DEBUG mode)?
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output)
whiptail_error_die "GPG Key automatic keygen failed!\n\n$ERROR"
fi
STATUS_OK "GPG keys generated on $DONGLE_BRAND"
TRACE_FUNC
}
gpg_key_change_pin() {
TRACE_FUNC
# 1 = user PIN, 3 = admin PIN
PIN_TYPE=$1
PIN_ORIG=${2}
PIN_NEW=${3}
# Change PIN
{
echo admin # admin menu
echo passwd # change PIN
echo ${PIN_TYPE} # 1 = user PIN, 3 = admin PIN
echo ${PIN_ORIG} # old PIN
echo ${PIN_NEW} # new PIN
echo ${PIN_NEW} # confirm new PIN
echo q # quit
echo q
} | DO_WITH_DEBUG gpg --command-fd=0 --status-fd=2 --pinentry-mode=loopback --card-edit \
>/tmp/gpg_card_edit_output 2>&1
TRACE_FUNC
DEBUG "GPG PIN change output: $(cat /tmp/gpg_card_edit_output)"
if [ $? -ne 0 ]; then
ERROR=$(cat /tmp/gpg_card_edit_output | fold -s)
whiptail_error_die "GPG Key PIN change failed!\n\n$ERROR"
fi
TRACE_FUNC
}
generate_checksums() {
TRACE_FUNC
# ensure /boot mounted
if ! grep -q /boot /proc/mounts; then
mount -o rw /boot || whiptail_error_die "Unable to mount /boot"
else
mount -o remount,rw /boot || whiptail_error_die "Unable to mount /boot"
fi
#Check if previous LUKS TPM Disk Unlock Key was set
if [ -e /boot/kexec_key_devices.txt ]; then
TPM_DISK_ENCRYPTION_KEY_SET=1
fi
# clear any existing checksums/signatures
rm /boot/kexec* 2>/dev/null
# create Heads TPM counter
if [ "$CONFIG_TPM" = "y" ]; then
if [ "$CONFIG_IGNORE_ROLLBACK" != "y" ]; then
tpmr.sh counter_create \
-pwdc '' \
-la -3135106223 |
tee /tmp/counter >/dev/null 2>&1 ||
whiptail_error_die "Unable to create TPM counter"
TPM_COUNTER=$(cut -d: -f1 </tmp/counter)
[ -n "$TPM_COUNTER" ] || whiptail_error_die "Unable to parse TPM counter id"
# increment TPM counter so /tmp/counter-$TPM_COUNTER is populated,
# then persist rollback metadata under /boot for next-boot preflight.
increment_tpm_counter "$TPM_COUNTER" ||
whiptail_error_die "Unable to increment TPM counter"
[ -s /tmp/counter-"$TPM_COUNTER" ] ||
whiptail_error_die "TPM counter increment did not produce counter state for rollback file"
# create rollback file
sha256sum /tmp/counter-"$TPM_COUNTER" >/boot/kexec_rollback.txt 2>/dev/null ||
whiptail_error_die "Unable to create rollback file"
fi
# If HOTP is enabled from board config, create HOTP counter
if [ -x /bin/hotp_verification ]; then
## needs to exist for initial call to unseal-hotp.sh
echo "0" >/boot/kexec_hotp_counter
fi
fi
# set default boot option only if no LUKS TPM Disk Unlock Key previously set
if [ -z "$TPM_DISK_ENCRYPTION_KEY_SET" ]; then
set_default_boot_option
fi
STATUS "Generating /boot file hashes"
(
set -e -o pipefail
cd /boot
find ./ -type f ! -path './kexec*' -print0 |
xargs -0 sha256sum >/boot/kexec_hashes.txt 2>/dev/null
print_tree >/boot/kexec_tree.txt
)
[ $? -eq 0 ] || whiptail_error_die "Error generating kexec hashes"
STATUS_OK "/boot file hashes generated"
# Collect relative basenames so sha256sum output is path-independent and
# matches what check_config produces when verifying (also uses cd+relative).
param_files=()
for f in /boot/kexec*.txt; do
[ -e "$f" ] || continue
param_files+=("$(basename "$f")")
done
[ ${#param_files[@]} -eq 0 ] &&
whiptail_error_die "No kexec parameter files to sign"
if [ "$GPG_GEN_KEY_IN_MEMORY" = "y" -a "$GPG_GEN_KEY_IN_MEMORY_COPY_TO_SMARTCARD" = "n" ]; then
#The local keyring used to generate in memory subkeys is still valid since no key has been moved to smartcard
#Local keyring passwd is ADMIN_PIN. We need to set USER_PIN to ADMIN_PIN to be able to sign next in this boot session
DEBUG "Setting GPG User PIN to GPG Admin PIN so local keyring can be used to detach-sign kexec files next"
USER_PIN=$ADMIN_PIN
fi
DEBUG "oem-factory-reset.sh: ${#param_files[@]} file(s) to sign (relative): ${param_files[*]}"
DEBUG "oem-factory-reset.sh: signing with USER_PIN='$USER_PIN' (length=${#USER_PIN})"
TRACE_FUNC
if (cd /boot && sha256sum "${param_files[@]}") 2>/dev/null | gpg --detach-sign \
--pinentry-mode loopback \
--passphrase-file <(echo -n "$USER_PIN") \
--digest-algo SHA256 \
-a \
>/boot/kexec.sig 2>/tmp/error; then
DEBUG "oem-factory-reset.sh: signing succeeded, running check_config /boot"
# successful - update the validated params
if ! check_config /boot >/dev/null 2>/tmp/error; then
cat /tmp/error
ret=1
else
STATUS_OK "/boot files signed and verified"
ret=0
fi
else
DEBUG "oem-factory-reset.sh: signing failed: $(cat /tmp/error)"
cat /tmp/error
ret=1
fi
# done writing to /boot, switch back to RO
mount -o ro,remount /boot
if [ $ret = 1 ]; then
ERROR=$(tail -n 1 /tmp/error | fold -s)
whiptail_error_die "Error signing kexec boot files:\n\n$ERROR"
fi
TRACE_FUNC
}
set_default_boot_option() {
TRACE_FUNC
option_file="/tmp/kexec_options.txt"
tmp_menu_file="/tmp/kexec/kexec_menu.txt"
hash_file="/boot/kexec_default_hashes.txt"
mkdir -p /tmp/kexec/
rm $option_file 2>/dev/null
# parse boot options from grub.cfg
for i in $(find /boot -name "grub.cfg"); do
kexec-parse-boot.sh "/boot" "$i" >>$option_file
done
# FC29/30+ may use BLS format grub config files
# https://fedoraproject.org/wiki/Changes/BootLoaderSpecByDefault
# only parse these if $option_file is still empty
if [ ! -s $option_file ] && [ -d "/boot/loader/entries" ]; then
for i in $(find /boot -name "grub.cfg"); do
kexec-parse-bls.sh "/boot" "$i" "/boot/loader/entries" >>$option_file
done
fi
[ ! -s $option_file ] &&
whiptail_error_die "Failed to parse any boot options"
# sort boot options
sort -r $option_file | uniq >$tmp_menu_file
## save first option as default
entry=$(head -n 1 $tmp_menu_file | tail -1)
# clear existing default configs
rm "/boot/kexec_default.*.txt" 2>/dev/null