go-piv/piv-go
GitHub: go-piv/piv-go
一个用纯 Go 实现的 YubiKey PIV 智能卡操作库,提供密钥生成、证书管理与数字签名等功能。
Stars: 433 | Forks: 80
这不是官方支持的 Google 产品
# 一个 Go YubiKey PIV 实现
[](https://pkg.go.dev/github.com/go-piv/piv-go/v2/piv)
YubiKey 实现了 PIV 规范,用于管理智能卡证书。
对于在 YubiKey 上管理非对称密钥,此 applet 是 GPG 的一个更简单的替代方案。
本包是 Paul Tagliamonte 的 [go-ykpiv](https://github.com/paultag/go-ykpiv) 的替代方案,
后者是 YubiKey 的 ykpiv.h C 库的一个封装。本包旨在提供:
* 更好的错误消息
* 符合 Go 惯用的 API
* 现代特性,例如受 PIN 保护的管理密钥
```
import "github.com/go-piv/piv-go/v2/piv"
```
## 示例
* [签名](#signing)
* [PIN](#pins)
* [证书](#certificates)
* [证明](#attestation)
### 签名
piv-go 包可用于生成密钥并在 YubiKey 上存储证书。它使用管理密钥在 applet 上生成新密钥,并使用 PIN 进行签名操作。本包提供了默认的 PIN 值。如果 YubiKey 上的 PIV 凭据未被修改,以下代码会在智能卡上生成一个新的 EC 密钥,并提供一个签名接口:
```
// List all smartcards connected to the system.
cards, err := piv.Cards()
if err != nil {
// ...
}
// Find a YubiKey and open the reader.
var yk *piv.YubiKey
for _, card := range cards {
if strings.Contains(strings.ToLower(card), "yubikey") {
if yk, err = piv.Open(card); err != nil {
// ...
}
break
}
}
if yk == nil {
// ...
}
// Generate a private key on the YubiKey.
key := piv.Key{
Algorithm: piv.AlgorithmEC256,
PINPolicy: piv.PINPolicyAlways,
TouchPolicy: piv.TouchPolicyAlways,
}
pub, err := yk.GenerateKey(piv.DefaultManagementKey, piv.SlotAuthentication, key)
if err != nil {
// ...
}
auth := piv.KeyAuth{PIN: piv.DefaultPIN}
priv, err := yk.PrivateKey(piv.SlotAuthentication, pub, auth)
if err != nil {
// ...
}
// Use private key to sign or decrypt.
```
### PIN
PIV applet 有三个独特的凭据:
* 管理密钥(3DES 密钥),用于在 YubiKey 上生成新密钥。
* YubiKey 固件 5.4.0+ 增加了对 AES128/192/256 密钥的支持
* PIN(最多 8 位数字,通常为 6 位),用于访问签名操作。
* PUK(最多 8 位数字),用于解锁 PIN。通常设置一次后即丢弃,或由管理员进行管理。
piv-go 实现了受 PIN 保护的管理密钥,以将管理密钥存储在 YubiKey 上。这允许用户仅提供 PIN 即可访问管理功能。
以下代码为 YubiKey 生成新的随机凭据:
```
newPINInt, err := rand.Int(rand.Reader, big.NewInt(1_000_000))
if err != nil {
// ...
}
newPUKInt, err := rand.Int(rand.Reader, big.NewInt(100_000_000))
if err != nil {
// ...
}
newKey := make([]byte, 24)
if _, err := io.ReadFull(rand.Reader, newKey); err != nil {
// ...
}
// Format with leading zeros.
newPIN := fmt.Sprintf("%06d", newPINInt)
newPUK := fmt.Sprintf("%08d", newPUKInt)
// If you want to change PIN/PUK retries, it's recommended to do it BEFORE changing
// the PIN/PUK, as SetRetries will reset PIN/PUK to their default values.
if err := yk.SetRetries(piv.DefaultManagementKey, piv.DefaultPIN, 5, 4); err != nil {
// ...
}
// Set all values to a new value.
if err := yk.SetManagementKey(piv.DefaultManagementKey, newKey); err != nil {
// ...
}
if err := yk.SetPUK(piv.DefaultPUK, newPUK); err != nil {
// ...
}
if err := yk.SetPIN(piv.DefaultPIN, newPIN); err != nil {
// ...
}
// Store management key on the YubiKey.
m := piv.Metadata{ManagementKey: &newKey}
if err := yk.SetMetadata(newKey, m); err != nil {
// ...
}
fmt.Println("Credentials set. Your PIN is: %s", newPIN)
```
用户稍后可以使用 PIN 获取管理密钥:
```
m, err := yk.Metadata(pin)
if err != nil {
// ...
}
if m.ManagementKey == nil {
// ...
}
key := *m.ManagementKey
```
### 证书
PIV applet 还可以在 YubiKey 上存储 X.509 证书:
```
cert, err := x509.ParseCertificate(certDER)
if err != nil {
// ...
}
if err := yk.SetCertificate(managementKey, piv.SlotAuthentication, cert); err != nil {
// ...
}
```
该证书稍后可以与私钥结合使用。例如,用于处理 TLS 流量:
```
cert, err := yk.Certificate(piv.SlotAuthentication)
if err != nil {
// ...
}
priv, err := yk.PrivateKey(piv.SlotAuthentication, cert.PublicKey, auth)
if err != nil {
// ...
}
s := &http.Server{
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{
{
Certificate: [][]byte{cert.Raw},
PrivateKey: priv,
},
},
},
Handler: myHandler,
}
```
### 证明
YubiKey 可以证明某个特定密钥是在智能卡上生成的,并且它被设置了特定的 PIN 和触摸策略。客户端生成一个密钥,然后要求 YubiKey 对一个证明证书进行签名:
```
// Get the YubiKey's attestation certificate, which is signed by Yubico.
yubiKeyAttestationCert, err := yk.AttestationCertificate()
if err != nil {
// ...
}
// Generate a key on the YubiKey and generate an attestation certificate for
// that key. This will be signed by the YubiKey's attestation certificate.
key := piv.Key{
Algorithm: piv.AlgorithmEC256,
PINPolicy: piv.PINPolicyAlways,
TouchPolicy: piv.TouchPolicyAlways,
}
if _, err := yk.GenerateKey(managementKey, piv.SlotAuthentication, key); err != nil {
// ...
}
slotAttestationCertificate, err := yk.Attest(piv.SlotAuthentication)
if err != nil {
// ...
}
// Send certificates to server.
```
然后,CA 可以验证该证明,从而证明密钥是在卡上生成的,并执行相应的策略:
```
// Server receives both certificates, then proves a key was generated on the
// YubiKey.
a, err := piv.Verify(yubiKeyAttestationCert, slotAttestationCertificate)
if err != nil {
// ...
}
if a.TouchPolicy != piv.TouchPolicyAlways {
// ...
}
// Record YubiKey's serial number and public key.
pub := slotAttestationCertificate.PublicKey
serial := a.Serial
```
## 安装
在 MacOS 上,piv-go 不需要任何额外的包。
要在 Linux 上构建,piv-go 需要 PCSC lite。要在基于 Debian 的发行版上安装,请运行:
```
sudo apt-get install libpcsclite-dev
```
在 Fedora 上:
```
sudo yum install pcsc-lite-devel
```
在 CentOS 上:
```
sudo yum install 'dnf-command(config-manager)'
sudo yum config-manager --set-enabled PowerTools
sudo yum install pcsc-lite-devel
```
在 FreeBSD 上:
```
sudo pkg install pcsc-lite
```
在 Windows 上:
无需任何先决条件。Microsoft 提供的默认驱动程序支持单元测试涵盖的所有功能。但是,如果您遇到问题,请尝试官方的 [YubiKey Smart Card Minidriver](https://www.yubico.com/products/services-software/download/smart-card-drivers-tools/)。Yubico 在其网站上声明,该驱动程序增加了[_额外的智能卡功能_](https://www.yubico.com/authentication-standards/smart-card/)。
请注意以下事项:
## 非 YubiKey 智能卡
由于缺乏测试硬件,实现 PIV 标准的非 YubiKey 智能卡未获得官方支持。但是,我们欢迎修复与其他智能卡集成问题的 PR,并且 piv-go 会尽量不破坏这种支持。
## 测试
测试会自动查找已连接且可用的 YubiKey,但如果没有 `--wipe-yubikey` 标志,就不会修改智能卡。要让测试修改您 YubiKey 的 PIV applet,请运行:
```
go test -v ./piv --wipe-yubikey
```
可以使用 `--test.short` 标志跳过较长的测试。
```
go test -v --short ./piv --wipe-yubikey
```
## 为什么?
YubiKey 的 C PIV 库 ykpiv 很脆弱。它的错误消息并不友好,而且虽然它有调试选项,但通过它们进行排查既不惯用也不方便。
ykpiv 封装了 Windows、Mac 和 Linux 上可用的 PC/SC API。它并没有规定必须用任何特定的语言来编写。作为 [pault.ag/go/ykpiv][go-ykpiv] 的替代方案,本包使用 Go 重新实现了 ykpiv,而不是直接调用它。
## 替代方案
标签:EVTX分析, Go, PIV, Ruby工具, YubiKey, 密码学, 手动系统调用, 数字证书管理, 日志审计, 硬件安全模块