Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
stv0g committed Dec 10, 2024
1 parent f072b7c commit ec55978
Show file tree
Hide file tree
Showing 117 changed files with 2,420 additions and 418 deletions.
110 changes: 84 additions & 26 deletions algorithm.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
// SPDX-FileCopyrightText: 2020 Google LLC
// SPDX-FileCopyrightText: 2023-2024 Steffen Vogel <[email protected]>
// SPDX-License-Identifier: Apache-2.0

package piv

type algorithmType byte

const (
AlgTypeRSA algorithmType = iota + 1
AlgTypeECCP
AlgTypeEd25519
)
import "fmt"

// Algorithm represents a specific algorithm and bit size supported by the PIV
// specification.
Expand All @@ -18,41 +13,93 @@ type Algorithm byte
// Algorithms supported by this package. Note that not all cards will support
// every algorithm.
//
// AlgorithmEd25519 is currently only implemented by SoloKeys.
//
// For algorithm discovery, see: https://github.com/ericchiang/piv-go/issues/1
// For algorithm discovery, see: https://github.com/go-piv/piv-go/issues/1
const (
Alg3DES Algorithm = 0x03
AlgRSA1024 Algorithm = 0x06
AlgRSA2048 Algorithm = 0x07
AlgRSA3072 Algorithm = 0x05
// NIST SP 800-78-4
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=21
AlgRSA2048 Algorithm = 0x07 // RSA 2048 bit modulus, 65537 ≤ exponent ≤ 2256 - 1
AlgECCP256 Algorithm = 0x11 // ECC: Curve P-256
AlgECCP384 Algorithm = 0x14 // ECC: Curve P-384

// NIST SP 800-78-5 ipd (Initial Public Draft)
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-78-5.ipd.pdf#page=12
Alg3DESSalt Algorithm = 0x00 // 3 Key Triple DES – ECB (deprecated)
Alg3DES Algorithm = 0x03 // 3 Key Triple DES – ECB (deprecated)
AlgRSA3072 Algorithm = 0x05 // RSA 3072 bit modulus, 65537 ≤ exponent ≤ 2256 - 1
AlgRSA1024 Algorithm = 0x06 // RSA 1024 bit modulus, 65537 ≤ exponent ≤ 2256 - 1
AlgAES128 Algorithm = 0x08 // AES-128 – ECB
AlgAES192 Algorithm = 0x0A // AES-192 – ECB
AlgAES256 Algorithm = 0x0C // AES-256 – ECB
AlgCS2 Algorithm = 0x27 // Cipher Suite 2
AlgCS7 Algorithm = 0x2E // Cipher Suite 7

// Non-standard extensions
AlgPIN Algorithm = 0xFF

// YubiKey 5.7 Firmware Specifics - PIV Enhancements - Additional Key Types Supported
//
// https://docs.yubico.com/hardware/yubikey/yk-tech-manual/5.7-firmware-specifics.html#additional-key-types-supported
AlgRSA4096 Algorithm = 0x16
AlgECCP256 Algorithm = 0x11
AlgECCP384 Algorithm = 0x14

// Non-standard; as implemented by SoloKeys. Chosen for low probability of eventual
// clashes, if and when PIV standard adds Ed25519 support
AlgEd25519 Algorithm = 0x22
AlgEd25519 Algorithm = 0xE0 // YubiKey
AlgX25519 Algorithm = 0xE1 // YubiKey

// Trussed PIV authenticator (NitroKey / SoloKeys)
//
// https://github.com/Nitrokey/piv-authenticator/blob/efb4632b3f498af6732fc716354af746f3960038/tests/command_response.rs#L58-L72

// AlgECCP521 Algorithm = 0x15
// AlgRSA3072 Algorithm = 0xE0
// AlgRSA4096 Algorithm = 0xE1
// AlgEd25519 Algorithm = 0xE2
// AlgX25519 Algorithm = 0xE3
// AlgEd448 Algorithm = 0xE4
// AlgX448 Algorithm = 0xE5

// Internal algorithms for testing
algRSA512 Algorithm = 0xF0
algECCP224 Algorithm = 0xF1
algECCP521 Algorithm = 0xF2
)

func (a Algorithm) algType() algorithmType {
func (a Algorithm) String() string {
switch a {
case AlgRSA1024, AlgRSA2048, AlgRSA3072, AlgRSA4096:
return AlgTypeRSA
case AlgRSA1024, AlgRSA2048, AlgRSA3072, AlgRSA4096, algRSA512:
return fmt.Sprintf("RSA-%d", a.bits())

case AlgECCP256, AlgECCP384, algECCP224, algECCP521:
return fmt.Sprintf("P-%d", a.bits())

case Alg3DESSalt:
return "3DESSalt"
case Alg3DES:
return "3DES"

case AlgAES128, AlgAES192, AlgAES256:
return fmt.Sprintf("AES-%d", a.bits())

case AlgECCP256, AlgECCP384:
return AlgTypeECCP
case AlgCS2:
return "CS2"
case AlgCS7:
return "CS7"

case AlgPIN:
return "PIN"

case AlgEd25519:
return AlgTypeEd25519
return "Ed25519"
case AlgX25519:
return "X25519"

default:
return 0
return ""
}
}

func (a Algorithm) bits() int {
switch a {
case algRSA512:
return 512
case AlgRSA1024:
return 1024
case AlgRSA2048:
Expand All @@ -62,10 +109,21 @@ func (a Algorithm) bits() int {
case AlgRSA4096:
return 4096

case algECCP224:
return 224
case AlgECCP256:
return 256
case AlgECCP384:
return 384
case algECCP521:
return 521

case AlgAES128:
return 128
case AlgAES192:
return 192
case AlgAES256:
return 256

default:
return 0
Expand Down
52 changes: 35 additions & 17 deletions auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package piv

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/des" //nolint:gosec
"errors"
"fmt"
Expand All @@ -25,8 +27,13 @@ var errFailedToGenerateKey = errors.New("failed to generate random key")
// 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
func (c *Card) authenticate(key ManagementKey) error {
meta, err := c.Metadata(SlotCardManagement)
if err != nil {
return fmt.Errorf("failed to get management key metadata: %w", err)
}

// Request a witness
resp, err := sendTLV(c.tx, iso.InsGeneralAuthenticate, byte(Alg3DES), keyCardManagement,
resp, err := sendTLV(c.tx, iso.InsGeneralAuthenticate, byte(meta.Algorithm), keyCardManagement,
tlv.New(0x7c,
tlv.New(0x80),
),
Expand All @@ -35,30 +42,41 @@ func (c *Card) authenticate(key ManagementKey) error {
return fmt.Errorf("failed to execute command: %w", err)
}

var block cipher.Block

switch meta.Algorithm {
case Alg3DES:
block, err = des.NewTripleDESCipher(key[:]) //nolint:gosec

case AlgAES128, AlgAES192, AlgAES256:
block, err = aes.NewCipher(key[:])

default:
return errUnsupportedKeyType
}
if err != nil {
return fmt.Errorf("failed to create block cipher: %w", err)
}

cardChallenge, _, ok := resp.GetChild(0x7c, 0x80)
if !ok {
return errUnmarshal
} else if len(cardChallenge) != 8 {
return errUnexpectedLength
}

block, err := des.NewTripleDESCipher(key[:]) //nolint:gosec
if err != nil {
return fmt.Errorf("failed to create triple des block cipher: %w", err)
} else if len(cardChallenge) != block.BlockSize() {
return fmt.Errorf("%w: %d", errUnexpectedLength, len(cardChallenge))
}

cardResponse := make([]byte, 8)
cardResponse := make([]byte, block.BlockSize())
block.Decrypt(cardResponse, cardChallenge)

challenge := make([]byte, 8)
challenge := make([]byte, block.BlockSize())
if _, err := io.ReadFull(c.Rand, challenge); err != nil {
return fmt.Errorf("failed to read random data: %w", err)
}

response := make([]byte, 8)
response := make([]byte, block.BlockSize())
block.Encrypt(response, challenge)

if resp, err = sendTLV(c.tx, iso.InsGeneralAuthenticate, byte(Alg3DES), keyCardManagement,
if resp, err = sendTLV(c.tx, iso.InsGeneralAuthenticate, byte(meta.Algorithm), keyCardManagement,
tlv.New(0x7c,
tlv.New(0x80, cardResponse),
tlv.New(0x81, challenge),
Expand All @@ -69,7 +87,7 @@ func (c *Card) authenticate(key ManagementKey) error {

if cardResponse, _, ok = resp.GetChild(0x7c, 0x82); !ok {
return errUnmarshal
} else if len(cardResponse) != 8 {
} else if len(cardResponse) != block.BlockSize() {
return errUnexpectedLength
} else if !bytes.Equal(cardResponse, response) {
return errChallengeFailed
Expand Down Expand Up @@ -109,7 +127,7 @@ func (c *Card) authenticateWithPIN(pin string) error {
// if err := c.SetManagementKey(piv.DefaultManagementKey, newKey); err != nil {
// // ...
// }
func (c *Card) SetManagementKey(oldKey, newKey ManagementKey, requireTouch bool) error {
func (c *Card) SetManagementKey(oldKey, newKey ManagementKey, requireTouch bool, alg Algorithm) error {
if err := c.authenticate(oldKey); err != nil {
return fmt.Errorf("failed to authenticate with old key: %w", err)
}
Expand All @@ -120,7 +138,7 @@ func (c *Card) SetManagementKey(oldKey, newKey ManagementKey, requireTouch bool)
}

if _, err := send(c.tx, insSetManagementKey, 0xff, p2, append([]byte{
byte(Alg3DES), keyCardManagement, 24,
byte(alg), keyCardManagement, 24,
}, newKey[:]...)); err != nil {
return fmt.Errorf("failed to execute command: %w", err)
}
Expand All @@ -130,7 +148,7 @@ func (c *Card) SetManagementKey(oldKey, newKey ManagementKey, requireTouch bool)

// https://docs.yubico.com/yesdk/users-manual/application-piv/pin-only.html
// https://docs.yubico.com/yesdk/users-manual/application-piv/piv-objects.html#pinprotecteddata
func (c *Card) SetManagementKeyPinProtected(oldKey ManagementKey, pin string, requireTouch bool) error {
func (c *Card) SetManagementKeyPinProtected(oldKey ManagementKey, pin string, requireTouch bool, alg Algorithm) error {
var newKey ManagementKey

if n, err := c.Rand.Read(newKey[:]); err != nil {
Expand All @@ -152,7 +170,7 @@ func (c *Card) SetManagementKeyPinProtected(oldKey ManagementKey, pin string, re
return err
}

return c.SetManagementKey(oldKey, newKey, requireTouch)
return c.SetManagementKey(oldKey, newKey, requireTouch, alg)
}

// SetPIN updates the PIN to a new value. For compatibility, PINs should be 1-8
Expand Down
38 changes: 21 additions & 17 deletions auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,24 @@ func TestAuthenticate(t *testing.T) {
}

func TestSetManagementKey(t *testing.T) {
withCard(t, false, false, nil, func(t *testing.T, c *Card) {
var mgmtKey ManagementKey
_, err := io.ReadFull(c.Rand, mgmtKey[:])
require.NoError(t, err, "Failed to generate management key")

err = c.SetManagementKey(DefaultManagementKey, mgmtKey, false)
require.NoError(t, err, "Failed to set management key")

err = c.authenticate(mgmtKey)
assert.NoError(t, err, "Failed to authenticate with new management key")

err = c.SetManagementKey(mgmtKey, DefaultManagementKey, false)
require.NoError(t, err, "Failed to reset management key")
})
for _, alg := range []Algorithm{Alg3DES, AlgAES128, AlgAES192, AlgAES256} {
t.Run(alg.String(), func(t *testing.T) {
withCard(t, false, false, nil, func(t *testing.T, c *Card) {
var mgmtKey ManagementKey
_, err := io.ReadFull(c.Rand, mgmtKey[:])
require.NoError(t, err, "Failed to generate management key")

err = c.SetManagementKey(DefaultManagementKey, mgmtKey, false, alg)
require.NoError(t, err, "Failed to set management key")

err = c.authenticate(mgmtKey)
assert.NoError(t, err, "Failed to authenticate with new management key")

err = c.SetManagementKey(mgmtKey, DefaultManagementKey, false, alg)
require.NoError(t, err, "Failed to reset management key")
})
})
}
}

func TestUnblockPIN(t *testing.T) {
Expand Down Expand Up @@ -134,13 +138,13 @@ func TestChangeManagementKey(t *testing.T) {
}
}

err = c.SetManagementKey(newKey, newKey, false)
err = c.SetManagementKey(newKey, newKey, false, Alg3DES)
assert.Error(t, err, "Successfully changed management key with invalid key, expected error")

err = c.SetManagementKey(DefaultManagementKey, newKey, false)
err = c.SetManagementKey(DefaultManagementKey, newKey, false, Alg3DES)
require.NoError(t, err, "Failed to change management key")

err = c.SetManagementKey(newKey, DefaultManagementKey, false)
err = c.SetManagementKey(newKey, DefaultManagementKey, false, Alg3DES)
require.NoError(t, err, "Failed to reset management key")
})
}
Expand Down
25 changes: 22 additions & 3 deletions filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,31 @@
package piv

import (
iso "cunicu.li/go-iso7816"
"cunicu.li/go-iso7816/devices/yubikey"
"cunicu.li/go-iso7816/filter"
)

//nolint:gochecknoglobals
var (
SupportsAttestation = yubikey.HasVersionStr("4.3.0")
SupportsMetadata = yubikey.HasVersionStr("5.3.0")
SupportsAlgorithmEC384 = yubikey.HasVersionStr("4.3.0")
v430 = iso.Version{Major: 4, Minor: 3, Patch: 0}
v530 = iso.Version{Major: 5, Minor: 3, Patch: 0}
v571 = iso.Version{Major: 5, Minor: 7, Patch: 1}

SupportsAttestation = yubikey.HasVersion(v430)
SupportsMetadata = yubikey.HasVersion(v530)
SupportsKeyMoveDelete = yubikey.HasVersion(v571)
)

func SupportsAlgorithm(alg Algorithm) filter.Filter {
switch alg {
case AlgRSA1024, AlgRSA2048, AlgECCP256, AlgECCP384:
return yubikey.HasVersion(v430)

case AlgRSA3072, AlgRSA4096, AlgX25519, AlgEd25519:
return yubikey.HasVersion(v571)

default:
return filter.None
}
}
Loading

0 comments on commit ec55978

Please sign in to comment.