Canokey 指南:OTP,FIDO2,PGP 与 PIV

居然在 2021 年收到了 Canokey Pigeon,这很不鸽子。折腾了几个小时,把几个主要的用法都试了一遍,包括 OTP, FIDO2,OpenPGP Smartcard 和 PIV。除了 Windows 下 PIV 无法使用 NIST P-256/P-384 算法,以及某些 OpenPGP 最新的特性还未支持,整体算是物美价廉。

本指南基于 Windows 10 Powershell,其它操作系统基本也可参考使用。虽然本文基于 Canokey Pigeon,但大部分内容均可适用于其他硬件密钥,包括但不限于 Yubikey,Feitian key 等。同时本文的 OpenPGP 部分也可单独作为比较完善的 PGP 使用指南来参考,PIV 部分基本可以说是目前最完整的中文使用指南。

基础配置

拿到后插入电脑会自动弹出 Canokey Web Console 提示,也可通过自行访问,注意目前只支持 Chromium 内核浏览器,点击右上角连接,如果失败请尝试重新插拔后等待一会重试。

连接成功后会显示 Canokey 的信息,首先进入 Settings 设置全局的 Admin PIN(默认 123456并牢记,同时可以配置部分选项,个人建议把 WebUSB prompt enabled 关闭,有些烦人。

注意这里的 Admin PIN 只用于 Canokey Web ConsoleSettings,与下方所有 PIN 均无关,请勿混淆,如觉得麻烦也可跳过这一步。

OTP

One-time Password (OTP) 是最常见的二步验证方法,主要分为 HMAC-based One-time Password algorithm (HOTP) 和 Time-based One-time Password (TOTP),由于绝大部分平台支持的都是后者,本文仅介绍 TOTP。当然国内平台爱用的短信验证码也算一种 OTP,但是这种方法有其缺陷,并不自主可控。

如果那你使用过 Google Authenticator 之类的软件,那你一定对 TOTP 不陌生,其最常见的形式是一个有着 30 秒倒计时的六位数字,本质上是基于一个预共享的密钥和当前时间计算出的验证码。

通常网站会给出一个二维码让我们扫描,其内容一般如下

1
otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

Canokey Web Console 的 TOTP/HTOP 部分添加,重要的是 secret 参数,也就是密钥。部分网站可能会直接给出一个密钥,同样添加即可。Canokey 最多支持存储 128 个密钥,反正我是用不完。还可以通过 Yubico Authenticator 来管理和读取 OTP,可能需要到设置中指定 custom reader 为 canokey。

FIDO2

首先引用谈谈 WebAuthn 的一段的介绍:

FIDO2 标准主要包括四个部分,其一是用于网站和访客设备交互的 WebAuthn,而 Client to Authenticator Protocol 2(CTAP2,客户端-认证器协议)作为 WebAuthn 的补充,则是用于访客的设备和认证器交互的协议。标准的其他两个部分则是 U2F 和 UAF 规范。

实际上这部分的使用是最简单的,只需要在支持的网站或应用中添加硬件密钥即可,目前大部分是用作 2FA,我自己常用的包括 GitHub, Cloudflare, Microsoft, Google, Twitter, Facebook 等,更多支持 FIDO2/U2F/TOTP 的网站可以参考 2FA Directory

注意部分网站添加时会首先弹出含有 Windows Hello 的选项,需要点击取消跳过,再添加硬件密钥。首次添加时需要自定义 PIN,牢记即可。

目前 Microsoft 账户支持使用硬件密钥实现单因素登录,即无需密码直接登录,可以进入账户的其他安全选项部分进行添加。

OpenPGP

这部分可以算是 Canokey 最主要的应用场景,关于 OpenPGP 的具体介绍就不在这里展开了,可以参考 2021年,用更现代的方法使用 PGP 系列文章。下面完整记录一下配置流程。

注意以下操作全部基于 Windows 10 Powershell,其它系统可能存在细微差异。为保护私钥安全,建议全程在离线、一次性环境中完成。

安装

Windows 用户可下载 Gpg4Win,Linux/macOS 用户使用对应包管理软件安装即可。

生成主密钥

首先生成主密钥,注意以下全部密钥均为一次性示例,并非本人 PGP 密钥。

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
gpg --expert --full-gen-key

gpg (GnuPG) 2.3.4; Copyright (C) 2021 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
(1) RSA and RSA
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(7) DSA (set your own capabilities)
(8) RSA (set your own capabilities)
(9) ECC (sign and encrypt) *default*
(10) ECC (sign only)
(11) ECC (set your own capabilities)
(13) Existing key
(14) Existing key from card
Your selection? 11

# 推荐使用 ECC 算法

Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify

(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished

Your selection? s

# 主密钥只保留 Certify 功能,其他功能使用子密钥

Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Certify

(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished

Your selection? q

Please select which elliptic curve you want:
(1) Curve 25519 *default*
(2) Curve 448
(3) NIST P-256
(4) NIST P-384
(5) NIST P-521
(6) Brainpool P-256
(7) Brainpool P-384
(8) Brainpool P-512
(9) secp256k1
Your selection? 1

Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y

# 主密钥永不过期即可

Real name: Editst
Email address: editst@example.com
Comment:
You selected this USER-ID:
"Editst <editst@example.com>"

# 这里按实际情况填写,注意保护自己的隐私

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.

# Windnows 下会弹出窗口输入密码,注意一定要保管好!!!

gpg: revocation certificate stored as 'C:\\Users\\XXX\\AppData\\Roaming\\gnupg\\openpgp-revocs.d\\68697537A54B1F0BFC05E1D9787E848E1A98D086.rev'
public and secret key created and signed.

# 会自动生成吊销证书,注意保存到安全的地方

pub ed25519/787E848E1A98D086 2022-01-01 [C]
Key fingerprint = 6869 7537 A54B 1F0B FC05 E1D9 787E 848E 1A98 D086
uid Editst <editst@example.com>

生成子密钥

查看目前的私钥,可以发现只有一个用作 Certify 的主密钥。

1
2
3
4
5
6
gpg --fingerprint --keyid-format long -K
C:\Users\XXX\AppData\Roaming\gnupg\pubring.kbx
------------------------------------------------
sec ed25519/787E848E1A98D086 2022-01-01 [C]
Key fingerprint = 6869 7537 A54B 1F0B FC05 E1D9 787E 848E 1A98 D086
uid [ultimate] Editst <editst@example.com>

下面生成不同功能的子密钥,其中 <fingerprint> 为上面输出的密钥指纹,本示例中即为 68697537A54B1F0BFC05E1D9787E848E1A98D086。最后的 2y 为密钥过期时间,可自行设置,如不填写默认永不过期。

1
2
3
gpg --quick-add-key <fingerprint> cv25519 encr 2y
gpg --quick-add-key <fingerprint> ed25519 auth 2y
gpg --quick-add-key <fingerprint> ed25519 sign 2y

再次查看目前的私钥,可以看到已经包含了这三个子密钥。

1
2
3
4
5
6
7
8
9
10
11
12
gpg --fingerprint --keyid-format long -K
C:\Users\XXX\AppData\Roaming\gnupg\pubring.kbx
------------------------------------------------
sec ed25519/787E848E1A98D086 2022-01-01 [C]
Key fingerprint = 6869 7537 A54B 1F0B FC05 E1D9 787E 848E 1A98 D086
uid [ultimate] Editst <editst@example.com>
ssb ed25519/055917609C9C0D7B 2022-01-01 [S] [expires: 2024-01-01]
Key fingerprint = E99F 3D15 7ACF 7E24 3DC8 FFE7 0559 1760 9C9C 0D7B
ssb ed25519/05F4A6C335157258 2022-01-01 [A] [expires: 2024-01-01]
Key fingerprint = C4B9 7EEC 4060 F856 7A4D 2956 05F4 A6C3 3515 7258
ssb cv25519/C5B8214C3AD21C6C 2022-01-01 [E] [expires: 2024-01-01]
Key fingerprint = E39E E067 3233 BD73 7ED1 15F1 C5B8 214C 3AD2 1C6C

上面生成了三种功能的子密钥(ssb),分别为加密(E)、认证(A)、签名(S),对应 OpenPGP Applet 中的三个插槽。由于 ECC 实现的原因,加密密钥的算法区别于其他密钥的算法。

加密密钥用于加密文件和信息。签名密钥主要用于给自己的信息签名,保证这真的是来自的信息。认证密钥主要用于 SSH 登录。

UID 设置

额外提醒,如果你想把这个密钥用于 Git Commit 的签名,同时提交到 GitHub,GitLab等服务,请确保 UID 信息 Editst <editst@example.com> 与你的本地 Git 邮箱以及 GitHub 已验证邮箱一致。

也可以单独添加 Git 使用的 UID

1
gpg --quick-add-uid <fingerprint> 'Editst <editst.github@example.com>'

gpg 会把你最近添加的 UID 作为主 UID,你也可以手动指定。

1
gpg --quick-set-primary-uid <fingerprint> 'Editst <editst@example.com>'

备份

首先导出公钥。

1
gpg -ao public-key.pub --export 787E848E1A98D086

然后分别导出各个私钥,请务必备份好,特别是主密钥和吊销证书,建议多介质异地备份。同时记得备份之前自动生成的吊销证书:%APPDATA%\gnupg\openpgp-revocs.d\68697537A54B1F0BFC05E1D9787E848E1A98D086.rev

注意 key id 后面的 !,表示只导出这一个私钥,若没有的话默认导出全部私钥。

1
2
3
4
5
gpg -ao sec-key.asc --export-secret-key 787E848E1A98D086!
# 主密钥,请务必保存好!!!
gpg -ao sign-key.asc --export-secret-key 055917609C9C0D7B!
gpg -ao auth-key.asc --export-secret-key 05F4A6C335157258!
gpg -ao encr-key.asc --export-secret-key C5B8214C3AD21C6C!

具体备份策略可以参照 2021年,用更现代的方法使用 PGP(中)

  • 主密钥只保留一份,建议备份在一个全盘加密的U盘中,然后放在一个绝对安全的地方。
  • 子密钥可以复制多份,通过U盘导入各个设备,专密专用,日常使用推荐用智能卡(比如Yubikey),还能免去每次输密码的麻烦
  • 撤销凭证可以和主密钥放在一起备份一份, 另外单独备份一份(这样丢失密钥,起码还可以撤销)

下面介绍配合智能卡使用子密钥

导入 Canokey

首先修改 OpenPGP Applet 的密码

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
gpg --edit-card
Reader ...........: canokeys.org OpenPGP PIV OATH 0
Application ID ...: xxxxxxxxxxxxxxxxxxxxxxxxxxx
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
Serial number ....: xxxxxxxx
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 64 64 64
PIN retry counter : 3 0 3
Signature counter : 0
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: [none]
Encryption key....: [none]
Authentication key: [none]
General key info..: [none]

# 进入 Admin 模式
gpg/card> admin
Admin commands are allowed

gpg/card> passwd
gpg: OpenPGP card no. xxxxxxxxxxxxxxxxxxxxxxxxxxx detected

1 - change PIN
2 - unblock PIN
3 - change Admin PIN
4 - set the Reset Code
Q - quit

分别设置 PIN(默认 123456) 和 Admin PIN(默认 12345678),注意牢记,忘记后只能重置 OpenPGP Applet

之后把各个子密钥导入对应插槽,请注意,这步是不可逆的,一但私钥导入智能卡并保存,本地私钥会被删除,无法再次恢复,因此务必确认已经正确完善备份。

如果你使用了多个 Canokey,想同时向其中导入子密钥,请注意最后一步不要保存,直接强行退出,然后更换另一枚 Canokey 或其他智能卡,重复操作即可。

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
gpg --edit-key 787E848E1A98D086
gpg (GnuPG) 2.3.4; Copyright (C) 2021 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Secret key is available.

sec ed25519/787E848E1A98D086
created: 2022-01-01 expires: never usage: C
trust: ultimate validity: ultimate
ssb ed25519/055917609C9C0D7B
created: 2022-01-01 expires: 2024-01-01 usage: S
ssb ed25519/05F4A6C335157258
created: 2022-01-01 expires: 2024-01-01 usage: A
ssb cv25519/C5B8214C3AD21C6C
created: 2022-01-01 expires: 2024-01-01 usage: E
[ultimate] (1). Editst <editst@example.com>

gpg> key 1 # 首先选中第一个子密钥

gpg> keytocard
Please select where to store the key:
(1) Signature key
(3) Authentication key
Your selection? 1 # 选择对应插槽

# 首先输入 OpenPGP 的密码,再输入 OpenPGP Applet 对应的 Admin PIN
# 之后先反选 key 1,再依次选择 key 2,key 3,重复操作即可

gpg> key 1
gpg> key 2
gpg> keytocard
Please select where to store the key:
(3) Authentication key
Your selection? 3
gpg> key 2
gpg> key 3
gpg> keytocard
Please select where to store the key:
(2) Encryption key
Your selection? 2

gpg> save # 保存修改

这时再次查看 Canokey 状态,确认导入成功。

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
gpg --card-status
Reader ...........: canokeys.org OpenPGP PIV OATH 0
Application ID ...: xxxxxxxxxxxxxxxxxxxxxxxxxxx
Application type .: OpenPGP
Version ..........: 3.4
Manufacturer .....: CanoKeys
Serial number ....: xxxxxxxx
Signature PIN ....: forced
Key attributes ...: rsa2048 rsa2048 rsa2048
Max. PIN lengths .: 64 64 64
PIN retry counter : 3 0 3
Signature counter : 0
UIF setting ......: Sign=off Decrypt=off Auth=off
Signature key ....: E99F 3D15 7ACF 7E24 3DC8 FFE7 0559 1760 9C9C 0D7B
created ....: 2022-01-01 13:09:11
Encryption key....: E39E E067 3233 BD73 7ED1 15F1 C5B8 214C 3AD2 1C6C
created ....: 2022-01-01 13:09:32
Authentication key: C4B9 7EEC 4060 F856 7A4D 2956 05F4 A6C3 3515 7258
created ....: 2022-01-01 13:09:49
General key info..: sub ed25519/055917609C9C0D7B 2022-01-01 Editst <editst@example.com>
sec ed25519/787E848E1A98D086 created: 2022-01-01 expires: never
ssb> cv25519/055917609C9C0D7B created: 2022-01-01 expires: 2024-01-01
card-no: F1D0 xxxxxxxx
ssb> ed25519/05F4A6C335157258 created: 2022-01-01 expires: 2024-01-01
card-no: F1D0 xxxxxxxx
ssb> ed25519/C5B8214C3AD21C6C created: 2022-01-01 expires: 2024-01-01
card-no: F1D0 xxxxxxxx

可以看到此时子密钥标识符为 ssb>,表示本地只有一个指向 card-no: F1D0 xxxxxxxx 智能卡的指针,已不存在私钥。现在可以删除掉主密钥了,请再次确认你已安全备份好主密钥

1
gpg --delete-secret-keys 787E848E1A98D086

为确保安全,也可直接删除 gpg 的工作目录:%APPDATA%\gnupg,Linux/macOS: ~/.gunpg

使用 Canokey

此时切换回日常使用的环境,首先导入公钥

1
gpg --import public-key.pub

然后设置子密钥指向 Canokey

1
2
gpg --edit-card
gpg/card> fetch

此时查看本地的私钥,可以看到已经指向了 Canokey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gpg --fingerprint --keyid-format long -K
C:\Users\XXX\AppData\Roaming\gnupg\pubring.kbx
------------------------------------------------
sec# ed25519/787E848E1A98D086 2022-01-01 [C]
Key fingerprint = 6869 7537 A54B 1F0B FC05 E1D9 787E 848E 1A98 D086
uid [ultimate] Editst <editst@example.com>
ssb> ed25519/055917609C9C0D7B 2022-01-01 [S] [expires: 2024-01-01]
Key fingerprint = E99F 3D15 7ACF 7E24 3DC8 FFE7 0559 1760 9C9C 0D7B
Card serial no. = F1D0 xxxxxxxx
ssb> ed25519/05F4A6C335157258 2022-01-01 [A] [expires: 2024-01-01]
Key fingerprint = C4B9 7EEC 4060 F856 7A4D 2956 05F4 A6C3 3515 7258
Card serial no. = F1D0 xxxxxxxx
ssb> cv25519/C5B8214C3AD21C6C 2022-01-01 [E] [expires: 2024-01-01]
Key fingerprint = E39E E067 3233 BD73 7ED1 15F1 C5B8 214C 3AD2 1C6C
Card serial no. = F1D0 xxxxxxxx

之后可以使用 gpg 进一步配置 Canokey,可以设置 nameforcesig 等内容,同时可前往 Canokey Web Console Touch Policy 配置每次使用时是否需要触摸以及缓存时间等。

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
gpg --card-edit

gpg/card> admin
Admin commands are allowed

gpg/card> help
quit quit this menu
admin show admin commands
help show this help
list list all available data
name change card holder's name
url change URL to retrieve key
fetch fetch the key specified in the card URL
login change the login name
lang change the language preferences
salutation change card holder's salutation
cafpr change a CA fingerprint
forcesig toggle the signature force PIN flag
generate generate new keys
passwd menu to change or unblock the PIN
verify verify the PIN and list all data
unblock unblock the PIN using a Reset Code
factory-reset destroy all keys and data
kdf-setup setup KDF for PIN authentication (on/single/off)
key-attr change the key attribute
uif change the User Interaction Flag

如果你使用了多个智能卡,切换后可能会因为私钥仍指向其他卡而出现问题,可以使用下面命令刷新 Card serial no

1
gpg-connect-agent "scd serialno" "learn --force" /bye

Git Commit 签名

首先确保 Git 本地配置以及 GitHub 中的邮箱信息包含在 UID 中,然后设置 Git 来指定使用子密钥中的签名(S)密钥。

1
git config --global user.signingkey 055917609C9C0D7B

之后在 git commit 时增加 -S 参数即可使用 gpg 进行签名。也可在配置中设置自动 gpg 签名,此处不建议全局开启该选项,因为有的脚本可能会使用 git am 之类的涉及到 commit 的命令,如果全局开启的话会导致问题。

1
git config commit.gpgsign true

如果提交到 GitHub,前往 GitHub SSH and GPG keys 添加公钥。此处添加后,可以直接通过对应 GitHub ID 来获取公钥:https://github.com/<yourid>.gpg

SSH with gpg agent

新版 gpg(最低 2.4.0 版本,对应 Gpg4win 4.1.0 版本)的 gpg agent 已支持 Win32-OpenSSH,无需使用其他软件即可使用目前 Windows 自带的 SSH 客户端进行公钥认证登录,需要手动配置开启该功能。

首先在 %AppData%\gnupg\gpg-agent.conf 中写入一行:

1
enable-win32-openssh-support

然后运行 gpg -k --with-keygrip 获取 [A] Subkey 的 Keygrip,写入 %AppData%\gnupg\sshcontrol,重启 gpg agent:

1
2
gpg-connect-agent killagent /bye
gpg-connect-agent /bye

查看 openSSH 读取到的公钥信息,把输出的公钥信息添加到服务器的 ~/.ssh/authorized_keys

1
2
3
ssh-add -L

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAzFAR5puWAj0OflZJVzAJqejVEZCap2NhFJbzedYwX2 cardno:F1D0 xxxxxxxx

此时连接 ssh user@host,会弹出提示输入 PIN 的页面,如果 Touch Policy 开启的话还需要触摸一下 Canokey,之后就可以连接到服务器了。

最后在启动目录 %AppData%\Microsoft\Windows\Start Menu\Programs\Startup 中增加一段 vbs 脚本,开机运行 gpg agent:

1
2
3
Set WinScriptHost = CreateObject("WScript.Shell")
WinScriptHost.Run Chr(34) & "C:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe" & Chr(34) & "/bye" , 0
Set WinScriptHost = Nothing

如果需要在 WSL/WSL2 或转发认证,可以进一步参考 win-gpg-agent 的 GitHub 页面配置使用。

PIV

PIV Applet 包含多个插槽,具体信息可以参考 PIV certificate slots,前往 Canokey Web Console,进入 PIV 设置 PIN(默认 123456)和 Management Key(需要 24 Bytes,默认 010203040506070801020304050607080102030405060708)并牢记。

生成证书

下面介绍两种不同的方法,可以根据自身需求选择。

注意,受限于内存大小,Canokey PIV 证书最大支持 1335 Bytes,过大会导致无法导入 2022年6月发售的 Canokey 据信已改进了这一问题,因本人目前不再持有 Canokey,无法测试。同时由于 Windows 系统的问题,Canokey PIV 目前只支持 RSA2048 算法,NIST P-256/P-384 算法生成的证书无法正常识别(没有 Canokey minidriver)。下面以 RSA2048 作为示例。

OpenSSL

首先安装生成 RSA2048 私钥,然后使用 OpenSSL 生成证书

1
2
3
ssh-keygen -t rsa -b 2048 -m PEM -f rsa2048.pem
openssl req -new -key rsa2048.pem -out editst.csr
openssl x509 -req -days 3650 -in editst.csr -signkey rsa2048.pem -out editst.crt

之后安装 yubikey-manager 导入私钥和证书,目前 ckman = ykman,这里使用 9a 插槽

1
2
3
python -m pip install yubikey-manager
ykman -r Canokey piv keys import 9a rsa2048.pem
ykman -r Canokey piv certificates import 9a editst.crt

还可以直接使用 ykman 调用 Canokey 来生成私钥和证书,注意这样生成的私钥是无法导出的。也可以在导入私钥的情况下直接调用第二个命令,只生成证书。

下面 -d 参数为有效期,默认为 365 天,-s 参数为 Subject for the certificate,必须传入,具体内容包括 CN: CommonName, OU: OrganizationalUnit, O: Organization, L: Locality, S: StateOrProvinceName, C: CountryName

1
2
ykman -r Canokey piv keys generate 9a rsa2048.pub
ykman -r Canokey piv certificates generate 9a rsa2048.pub -s "CN=Editst, C=CN" -d 3650

完成后重新插拔 Canokey,Windows 前往 certmgr.msc,在个人-证书中可以看到已经有了刚才生成的证书。

certreq

在 Windows 环境下虽然没有自带的 OpenSSL 组件,但是提供了强大的 certreq 组件来生成证书。下面直接给出参考的配置,可以自行修改,也可以前往 certreq docs 查看更详细地参数说明。

其中 Subject 具体内容包括 CN: CommonName, OU: OrganizationalUnit, O: Organization, L: Locality, S: StateOrProvinceName, C: CountryName,可以自行配置。

1
2
3
4
5
6
7
8
9
10
11
12
[NewRequest]
Subject = "CN=Editst"
KeyLength = 2048
Exportable = TRUE
HashAlgorithm = SHA256
KeySpec = "AT_KEYEXCHANGE"
KeyUsage = "CERT_KEY_ENCIPHERMENT_KEY_USAGE"
KeyUsageProperty = "NCRYPT_ALLOW_DECRYPT_FLAG"
RequestType = Cert
ValidityPeriodUnits = 5
ValidityPeriod = Years
SMIME = FALSE

保存上面的配置文件到 cert.ini,之后使用 certreq 生成证书,注意这一步生成的 cert_req.req 没啥用,直接删除即可

1
certreq -new cert.ini cert_req.req

此时生成的证书已经自动安装在系统中,前往 certmgr.msc,在个人-证书中可以看到已经有了刚才生成的证书。右键选择所有任务-导出,在导出向导中选择导出私钥即可获得完整的证书和私钥文件 cert.pfx

之后导入需要使用 yubikey-piv-tool,下载安装后手动添加程序路径到环境变量中:C:\Program Files\Yubico\Yubico PIV Tool\bin,之后把证书和私钥导入到 Canokrey

1
yubico-piv-tool -r Canokey -a import-key -a import-certificate -s 9e -K PKCS12 -k -i cert.pfx

需要首先输入导出证书时的密码,之后再输入 Management Key

SSH with PIV

前往 WinCrypt SSH Agent 下载并运行,此时查看 ssh-agent 读取到的公钥信息,把输出的公钥信息添加到服务器的 ~/.ssh/authorized_keys

1
2
3
4
$Env:SSH_AUTH_SOCK="\\.\pipe\openssh-ssh-agent"
ssh-add -L

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCYSnYY1UFzct9qi7QGNsE9GRJXmsy7HNLxHHO0uxuZTKQUT2OHrCHwVoeahPZRGwOXqxiziR6MxMVwK8mmfpM/2iRwkJhiW4AmeAA5s3zeO9sdZTYRzb9njCaa7uqOER6fbgW99ctp2LwsKxG0z2TvBgh+fwum96APggozLbapfweiWbbmymJ4J60p7yP/5a8Uh9AABNijp3iCmpnydICuWLG4Vtj4vMJ4NGNH5POY7qU0lvBULEXMXaFRoZrv93VRaj0hw2GOtgd9IdB+edSmze/vjJ+eodk5tqjLATi2BxlvSKPHDb14a34b5JK5idMUwlRfOOvOA10nZ7+kZetX Editst

此时连接 ssh user@host,会弹出提示输入 PIN 的页面,注意此时输入的是 PIV Applet PIN,输入后即可成功连接服务器。

注意这种使用方法与 SSH with gpg-agent 并不兼容,选择其中一种使用即可。

BitLokcer with PIV

如果已经按照上一步生成了证书,需要修改注册表来允许自签名证书,在注册表 HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\FVE 下新建一个 DWORD 的值,名称为 SelfSignedCertificates,数值数据为 1

或者直接保存下面的注册表配置文件为 selfsig.reg,然后导入。

1
2
3
4
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\FVE]
"SelfSignedCertificates"=dword:00000001

注意,需要验证智能卡中存有证书对应的私钥,否则会导致可以添加智能卡,但在解锁时失败。运行下面命令,确保输出信息中包含私钥验证测试通过信息。

1
2
3
4
5
6
certutil -scinfo

# 应含有下面输出
私钥验证
Microsoft Smart Card Key Storage Provider: KeySpec=0
AES256+RSAES_OAEP(RSA:CNG) 测试通过

BitLocker 不要求证书具备 EKU 属性,但是如果已为证书配置了该属性,则必须将前往组策略计算机配置-管理模板-Windows 组件-BitLocker 驱动器加密,设置验证智能卡证书使用合规性为已启用,同时为 BitLocker 配置的对象标识符 (OID) 匹配的 OID。

如果遇到其他问题请尝试执行下面命令,初始化 Card Holder Unique IdentifierCCC

1
2
ykman -r Canokey piv objects generate chuid
ykman -r Canokey piv objects generate ccc

之后启用 BitLocker 时选择使用智能卡解锁驱动器即可,对于已启用 BitLocker 的驱动器,也可以通过管理 BitLocker 来添加智能卡。

感谢你看到这里,如有任何问题欢迎留言或邮件联系 i#editst.com,或者尝试从下面链接中寻找答案。

参考