Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic support for 5.7.0+ yubikeys that default to AES192 management keys #148

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 67 additions & 39 deletions piv/piv.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package piv

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/rand"
"encoding/asn1"
Expand All @@ -26,23 +28,6 @@ import (
"math/big"
)

var (
// DefaultPIN for the PIV applet. The PIN is used to change the Management Key,
// and slots can optionally require it to perform signing operations.
DefaultPIN = "123456"
// DefaultPUK for the PIV applet. The PUK is only used to reset the PIN when
// the card's PIN retries have been exhausted.
DefaultPUK = "12345678"
// DefaultManagementKey for the PIV applet. The Management Key is a Triple-DES
// key required for slot actions such as generating keys, setting certificates,
// and signing.
DefaultManagementKey = [24]byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
}
)

// Cards lists all smart cards available via PC/SC interface. Card names are
// strings describing the key, such as "Yubico Yubikey NEO OTP+U2F+CCID 00 00".
//
Expand All @@ -59,6 +44,7 @@ const (
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-78-4.pdf#page=17
algTag = 0x80
alg3DES = 0x03
algAES192 = 0x0a
algRSA1024 = 0x06
algRSA2048 = 0x07
algECCP256 = 0x11
Expand Down Expand Up @@ -96,6 +82,24 @@ const (
insGetMetadata = 0xf7
)

var (
// DefaultPIN for the PIV applet. The PIN is used to change the Management Key,
// and slots can optionally require it to perform signing operations.
DefaultPIN = "123456"
// DefaultPUK for the PIV applet. The PUK is only used to reset the PIN when
// the card's PIN retries have been exhausted.
DefaultPUK = "12345678"
// DefaultManagementKey for the PIV applet. The Management Key is a Triple-DES
// key required for slot actions such as generating keys, setting certificates,
// and signing.
DefaultManagementKey = [24]byte{
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
}
DefaultManagementKeyType byte = alg3DES
)

// YubiKey is an exclusive open connection to a YubiKey smart card. While open,
// no other process can query the given card.
//
Expand Down Expand Up @@ -176,6 +180,12 @@ func (c *client) Open(card string) (*YubiKey, error) {
return nil, fmt.Errorf("getting yubikey version: %w", err)
}
yk.version = v

// If yubikey is 5.7.0 or newer, management key default is AES192
if int(v.major) >= 5 && int(v.minor) >= 7 {
DefaultManagementKeyType = algAES192
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be on the client rather than global to the package?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested with v4.3.7, v5.4.3, and v5.7.1 yubikeys. It has been possible for users to change the type of management key since 5.4, but the default has remained 3DES. I decided in lieu of this minimal fix, I would take things a step further and just implement dynamic support for all the supported management key types. Closing this PR in favor of : #149

}

if c.Rand != nil {
yk.rand = c.Rand
} else {
Expand Down Expand Up @@ -368,10 +378,18 @@ func ykAuthenticate(tx *scTx, key [24]byte, rand io.Reader) error {
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=92
// https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=918402#page=114

var challengeLength byte
switch DefaultManagementKeyType {
case algAES192:
challengeLength = 16
default:
challengeLength = 8
}

// request a witness
cmd := apdu{
instruction: insAuthenticate,
param1: alg3DES,
param1: DefaultManagementKeyType,
param2: keyCardManagement,
data: []byte{
0x7c, // Dynamic Authentication Template tag
Expand All @@ -389,45 +407,55 @@ func ykAuthenticate(tx *scTx, key [24]byte, rand io.Reader) error {
}
if !bytes.Equal(resp[:4], []byte{
0x7c,
0x0a,
0x80, // 'Witness'
0x08, // Tag length
challengeLength + 2,
0x80, // 'Witness'
challengeLength, // Tag length
}) {
return fmt.Errorf("invalid authentication object header: %x", resp[:4])
}

cardChallenge := resp[4 : 4+8]
cardResponse := make([]byte, 8)

block, err := des.NewTripleDESCipher(key[:])
if err != nil {
return fmt.Errorf("creating triple des block cipher: %v", err)
var block cipher.Block
switch DefaultManagementKeyType {
case algAES192:
block, err = aes.NewCipher(key[:])
if err != nil {
return fmt.Errorf("creating aes block cipher: %v", err)
}
default:
block, err = des.NewTripleDESCipher(key[:])
if err != nil {
return fmt.Errorf("creating des block cipher: %v", err)
}
}

cardChallenge := resp[4 : 4+challengeLength]
cardResponse := make([]byte, challengeLength)

block.Decrypt(cardResponse, cardChallenge)

challenge := make([]byte, 8)
challenge := make([]byte, challengeLength)
if _, err := io.ReadFull(rand, challenge); err != nil {
return fmt.Errorf("reading rand data: %v", err)
}
response := make([]byte, 8)
response := make([]byte, challengeLength)
block.Encrypt(response, challenge)

data := []byte{
0x7c, // Dynamic Authentication Template tag
20, // 2+8+2+8
0x80, // 'Witness'
0x08, // Tag length
(challengeLength + 2) * 2,
0x80, // 'Witness'
challengeLength, // Tag length
}
data = append(data, cardResponse...)
data = append(data,
0x81, // 'Challenge'
0x08, // Tag length
0x81, // 'Challenge'
challengeLength, // Tag length
)
data = append(data, challenge...)

cmd = apdu{
instruction: insAuthenticate,
param1: alg3DES,
param1: DefaultManagementKeyType,
param2: keyCardManagement,
data: data,
}
Expand All @@ -440,13 +468,13 @@ func ykAuthenticate(tx *scTx, key [24]byte, rand io.Reader) error {
}
if !bytes.Equal(resp[:4], []byte{
0x7c,
0x0a,
challengeLength + 2,
0x82, // 'Response'
0x08,
challengeLength,
}) {
return fmt.Errorf("response invalid authentication object header: %x", resp[:4])
}
if !bytes.Equal(resp[4:4+8], response) {
if !bytes.Equal(resp[4:4+challengeLength], response) {
return fmt.Errorf("challenge failed")
}

Expand Down Expand Up @@ -482,7 +510,7 @@ func ykSetManagementKey(tx *scTx, key [24]byte, touch bool) error {
param1: 0xff,
param2: 0xff,
data: append([]byte{
alg3DES, keyCardManagement, 24,
DefaultManagementKeyType, keyCardManagement, 24,
}, key[:]...),
}
if touch {
Expand Down
Loading