Skip to content
PeculiarVenturesPublic

About

Go library for PIV (Personal Identity Verification) over PC/SC: ISO 7816/APDU implementation, smart card management, and support for pluggable adapters.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

43 Commits

Folders and files

Repository files navigation

piv-go

piv-go is a Go module and CLI for working with PIV (Personal Identity Verification) smart cards over PC/SC. It provides a layered library for ISO 7816 and PIV operations, vendor-aware adapters for common tokens, and a task-oriented piv command-line interface.

The repository is intended for developers and operators who need to inspect tokens, read and manage certificates, verify or rotate credentials, generate keys, and run targeted diagnostics without dropping to raw APDU tooling unless they choose to.

What is included

  • Go packages for ISO 7816 APDU/TLV handling, PIV commands, PC/SC transport, emulator flows, and vendor adapters.
  • A piv CLI for reader discovery, token inspection, certificate and key operations, credential workflows, and diagnostics.
  • Built-in vendor adapters for SafeNet/eToken and YubiKey PIV tokens.
  • Emulator-backed tests for core library and CLI flows.

Repository layout

  • pcsc/ implements the PC/SC transport layer and isolates direct scard calls.
  • iso7816/ implements APDU encoding, response parsing, status handling, and BER-TLV utilities.
  • piv/ implements the PIV protocol client and high-level token operations.
  • adapters/ contains vendor-neutral helpers and vendor-specific adapters.
  • emulator/ contains emulator-backed support used in tests and diagnostics.
  • cmd/piv/ contains the public CLI.

Additional public documentation:

Requirements

  • Go 1.25 or newer.
  • A PC/SC runtime and a compatible reader driver.
  • A PIV-capable token for real device operations.

Platform notes:

  • macOS: the PC/SC framework is available as part of the operating system.
  • Linux: install pcsc-lite, the development headers (libpcsclite-dev on Debian/Ubuntu), and pkg-config before building.
  • Windows: install the vendor reader driver and ensure the Smart Card service is enabled.

The transport layer depends on github.com/ebfe/scard, so missing PC/SC headers or runtime services will break builds or runtime discovery even if the Go toolchain is installed correctly.

Build and test

go build ./...
go test ./...
go vet ./...

Run the CLI directly from the module root:

go run ./cmd/piv --help

CLI quick start

Discover readers and detect whether a token is PIV-ready:

go run ./cmd/piv devices

Inspect the selected token:

go run ./cmd/piv info --reader "YubiKey 5C NFC"
go run ./cmd/piv slot list --reader "YubiKey 5C NFC"

Export public artifacts:

go run ./cmd/piv cert export auth --reader "YubiKey 5C NFC" --out auth-cert.pem
go run ./cmd/piv key public auth --reader "YubiKey 5C NFC" --out auth-pub.pem

Run safe diagnostics before attempting a mutation:

go run ./cmd/piv doctor --reader "YubiKey 5C NFC" --with-select

Use machine-readable output when automating:

go run ./cmd/piv info --reader "YubiKey 5C NFC" --json

See docs/CLI.md for the full command map, output conventions, and destructive-operation guidance.

Library example

package main

import (
    "fmt"
    "log"

    "github.com/PeculiarVentures/piv-go/pcsc"
    "github.com/PeculiarVentures/piv-go/piv"
)

func main() {
    ctx, err := pcsc.NewContext()
    if err != nil {
        log.Fatal(err)
    }
    defer ctx.Release()

    card, err := ctx.Connect("YubiKey 5C NFC")
    if err != nil {
        log.Fatal(err)
    }
    defer card.Close()

    client := piv.NewClient(card)
    if err := client.Select(); err != nil {
        log.Fatal(err)
    }

    certDER, err := client.GetCertificate(piv.SlotAuthentication)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("authentication certificate is %d bytes\n", len(certDER))
}

When building signing flows, prefer resolving key metadata and deriving the authorization policy before calling Sign(...). This keeps VERIFY PIN handling explicit at the application layer. The following fragment assumes you already imported github.com/PeculiarVentures/piv-go/adapters, resolved an adapter runtime, prepared the digest, and obtained the PIN value from your own application logic:

// Example fragment: assumes runtime, digest, and pin are already available.
metadata, err := adapters.ResolveKeyMetadata(runtime, piv.SlotSignature)
if err != nil {
    log.Fatal(err)
}

policy := adapters.DeriveSignAuthorization(metadata)
if policy.RequiresPIN() || !policy.IsKnown() {
    if err := runtime.Session.Client.VerifyPIN(pin); err != nil {
        log.Fatal(err)
    }
}

signature, err := runtime.Session.Client.Sign(piv.AlgECCP256, piv.SlotSignature, digest, piv.RSASignHashNone)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("signature length: %d\n", len(signature))

In a complete application, resolve runtime through the adapter layer after connecting to the card, compute digest from the message you want to sign, and source pin from your own credential-handling path rather than from a command line argument.

PIV secure messaging

piv.OpenSecureMessaging opens SP 800-73-4 Part 2 secure messaging with a card that supports it, with cipher suite 27 (P-256, AES-128) or 2E (P-384, AES-256). The card proves its key with a card verifiable certificate (CVC), and the client checks that CVC against the content signing certificate in the card's Secure Messaging Certificate Signer object (5FC122), whose path it validates to the roots you give it. The returned channel is a piv.Card, so a client built on it sends every command encrypted and MAC-protected. Over secure messaging, VerifyPairingCode activates the virtual contact interface, which lets a contactless reader do what a contact reader can.

client := piv.NewClient(card)
if err := client.Select(); err != nil {
	return err
}
ch, err := piv.OpenSecureMessaging(card, piv.SecureMessagingOptions{Roots: orgRoots})
if err != nil {
	return err // the card's CVC does not chain to orgRoots, or key establishment failed
}
sm := piv.NewClient(ch)
if err := sm.VerifyPairingCode("12345678"); err != nil {
	return err
}
if err := sm.VerifyPIN(pin); err != nil {
	return err
}

Any failure ends the session, as does a SELECT, which the channel refuses to send. Command chaining is not supported under secure messaging; a long command goes as one extended APDU. The implementation follows OpenSC's card-piv.c. Its tests replay a recorded session with goodpiv, a card that implements secure messaging and is itself tested against OpenSC.

Passive slot inspection

Use adapters/slots.DescribeSlot when an application needs both public-key provenance and private-key state. SlotDescription.PublicKeySource distinguishes metadata, stored_template, and certificate; it is empty when no public key was read. The CLI exposes the same value as public_key_source in JSON and shows the public algorithm and source alongside the private-key state in text output.

A saved public template or certificate cannot prove that the corresponding private key is still on the card. Without authoritative vendor metadata, KeyState stays unknown with reason unobservable, even when a public key and its algorithm are available. Read failures remain errors. ReadPublicKey keeps its existing return type; callers requiring this distinction should inspect the slot description instead.

The CLI's existing unknown-state guard also refuses key delete on cards whose private-key presence cannot be established from metadata. A saved public template alone does not authorize that operation.

YubiKey generation and import still save a public template for later reads. Installing a certificate replaces that template in the shared slot object. Changing a private key through another tool can leave a saved template stale. On YubiKey NEO, unsupported GET METADATA permits public-object and PIN status fallbacks. PUK retries can remain unknown, while management-key retries are unlimited. Real metadata read or parse failures are returned as errors.

Security and operational caveats

  • Real hardware operations can be destructive. Review setup, mgm, key delete, key generate, and cert delete commands before using them on a live token.
  • SafeNet and YubiKey support includes vendor-specific handling for factory-default credentials and reset flows. Treat those defaults as onboarding aids for uninitialized devices, not as acceptable production settings.
  • APDU tracing can capture credential material and token metadata. If you use --trace or store debug output, keep it out of shell history, issue trackers, and public archives.
  • The emulator is useful for tests and smoke validation, but it is not a full replacement for vendor firmware behavior.

License

This project is licensed under the Apache License 2.0. See LICENSE.

About

Go library for PIV (Personal Identity Verification) over PC/SC: ISO 7816/APDU implementation, smart card management, and support for pluggable adapters.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages