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

Improve usability #78

Merged
merged 2 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
- name: Install Dependencies (ubuntu)
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get -u install libpcsclite-dev
- name: Set up environment
run: echo "GOVERSION=$(go version)" >> $GITHUB_ENV
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ jobs:
- name: Install Dependencies
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get -u install libpcsclite-dev
- name: Set up environment
run: echo "GOVERSION=$(go version)" >> $GITHUB_ENV
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/tag-to-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
- name: Install Dependencies (ubuntu)
if: matrix.os == 'ubuntu-latest'
run: sudo apt-get update && sudo apt-get -u install libpcsclite-dev
- name: Set up environment
run: echo "GOVERSION=$(go version)" >> $GITHUB_ENV
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
Expand Down
4 changes: 4 additions & 0 deletions .goreleaser.macos-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ builds:
- darwin
goarch:
- amd64
ldflags:
- >
-s -w -X main.date={{.Date}} -X "main.goVersion={{.Env.GOVERSION}}"
-X main.shortCommit={{.ShortCommit}} -X main.version={{.Version}}
checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_darwin_checksums.txt"
4 changes: 4 additions & 0 deletions .goreleaser.ubuntu-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ builds:
- linux
goarch:
- amd64
ldflags:
- >
-s -w -X main.date={{.Date}} -X "main.goVersion={{.Env.GOVERSION}}"
-X main.shortCommit={{.ShortCommit}} -X main.version={{.Version}}
checksum:
name_template: "{{ .ProjectName }}_{{ .Version }}_linux_checksums.txt"
16 changes: 10 additions & 6 deletions cmd/piv-agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import (
)

var (
version string
date string
date string
goVersion string
shortCommit string
version string
)

// CLI represents the command-line interface.
type CLI struct {
Debug bool `kong:"help='Enable debug logging'"`
Serve ServeCmd `kong:"cmd,default=1,help='(default) Listen for signing requests'"`
Setup SetupCmd `kong:"cmd,help='Set up the hardware security key for use with piv-agent'"`
List ListCmd `kong:"cmd,help='List cryptographic keys available on each hardware security key'"`
Debug bool `kong:"help='Enable debug logging'"`
Serve ServeCmd `kong:"cmd,default=1,help='(default) Listen for signing requests'"`
Setup SetupCmd `kong:"cmd,help='Set up the hardware security key for use with piv-agent'"`
SetupSlots SetupSlotsCmd `kong:"cmd,help='Set up a single slot on the hardware security key PIV applet'"`
List ListCmd `kong:"cmd,help='List cryptographic keys available on each hardware security key'"`
Version VersionCmd `kong:"cmd,help='Print version information'"`
}

func main() {
Expand Down
2 changes: 1 addition & 1 deletion cmd/piv-agent/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (cmd *SetupCmd) Run() error {
}
err = k.Setup(strconv.FormatUint(cmd.PIN, 10), version,
cmd.ResetSecurityKey, cmd.SigningKeys, cmd.DecryptingKey)
if errors.Is(err, securitykey.ErrNotReset) {
if errors.Is(err, securitykey.ErrKeySetUp) {
return fmt.Errorf("--reset-security-key not specified: %w", err)
}
return err
Expand Down
43 changes: 43 additions & 0 deletions cmd/piv-agent/setupslots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"errors"
"fmt"
"strconv"

"github.com/smlx/piv-agent/internal/securitykey"
)

// SetupSlotsCmd represents the setup command.
type SetupSlotsCmd struct {
Card string `kong:"help='Specify a smart card device'"`
ResetSlots bool `kong:"help='Overwrite existing keys in the targeted slots'"`
PIN uint64 `kong:"help='The PIN/PUK of the device (6-8 digits). Will be prompted interactively if not provided.'"`
SigningKeys []string `kong:"default='',enum='cached,always,never',help='Set up slots for signing keys with various touch policies (default none)'"`
DecryptingKey bool `kong:"default='false',help='Set up slot for a decrypting key (default false)'"`
}

// Run the setup-slot command to configure a slot on a security key.
func (cmd *SetupSlotsCmd) Run() error {
// if PIN has not been specified, ask interactively
var err error
if cmd.PIN == 0 {
cmd.PIN, err = interactivePIN()
if err != nil {
return err
}
}
if cmd.PIN < 100000 || cmd.PIN > 99999999 {
return fmt.Errorf("invalid PIN, must be 6-8 digits")
}
k, err := securitykey.New(cmd.Card)
if err != nil {
return fmt.Errorf("couldn't get security key: %v", err)
}
err = k.SetupSlots(strconv.FormatUint(cmd.PIN, 10), version, cmd.ResetSlots,
cmd.SigningKeys, cmd.DecryptingKey)
if errors.Is(err, securitykey.ErrKeySetUp) {
return fmt.Errorf("--reset-slots not specified: %w", err)
}
return err
}
13 changes: 13 additions & 0 deletions cmd/piv-agent/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import "fmt"

// VersionCmd represents the version command.
type VersionCmd struct{}

// Run the version command to print version information.
func (cmd *VersionCmd) Run() error {
fmt.Printf("piv-agent %v (%v) compiled with %v on %v\n", version,
shortCommit, goVersion, date)
return nil
}
62 changes: 52 additions & 10 deletions internal/securitykey/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,65 @@ import (
"github.com/go-piv/piv-go/piv"
)

// ErrNotReset is returned from Setup when the security key is already set up
// ErrKeySetUp is returned from Setup when the security key is already set up
// and reset is false.
var ErrNotReset = errors.New("security key already set up")
var ErrKeySetUp = errors.New("security key already set up")

// checkSlotSetUp checks if the provided slot is set up, returning true if the slot
// is set up and false otherwise.
func (k *SecurityKey) checkSlotSetUp(s SlotSpec) (bool, error) {
_, err := k.yubikey.Certificate(s.Slot)
if err == nil {
return true, nil
} else if errors.Is(err, piv.ErrNotFound) {
return false, nil
}
return false, fmt.Errorf("couldn't check slot certificate: %v", err)
}

// checkSlotsSetUp checks if the provided slots are set up returning true if any of
// the slots are set up, and false otherwise.
func (k *SecurityKey) checkSlotsSetUp(signingKeys []string,
decryptingKey bool) (bool, error) {
for _, p := range signingKeys {
setUp, err := k.checkSlotSetUp(defaultSignSlots[p])
if err != nil {
return false, err
}
if setUp {
return true, nil
}
}
if decryptingKey {
setUp, err := k.checkSlotSetUp(defaultDecryptSlots["never"])
if err != nil {
return false, err
}
if setUp {
return true, nil
}
}
return false, nil
}

// Setup configures the SecurityKey to work with piv-agent.
func (k *SecurityKey) Setup(pin, version string, reset bool,
signingKeys []string, decryptingKey bool) error {
_, err := k.yubikey.Certificate(piv.SlotAuthentication)
if err == nil {
if !reset {
return ErrNotReset
var err error
if !reset {
setUp, err := k.checkSlotsSetUp(signingKeys, decryptingKey)
if err != nil {
return fmt.Errorf("couldn't check slots: %v", err)
}
if err = k.yubikey.Reset(); err != nil {
return fmt.Errorf("couldn't reset security key: %v", err)
if setUp {
return ErrKeySetUp
}
} else if !errors.Is(err, piv.ErrNotFound) {
return fmt.Errorf("couldn't get certificate: %v", err)
}
// reset security key
if err = k.yubikey.Reset(); err != nil {
return fmt.Errorf("couldn't reset security key: %v", err)
}
// generate management key and store on the security key
var mgmtKey [24]byte
if _, err := rand.Read(mgmtKey[:]); err != nil {
return fmt.Errorf("couldn't get random bytes: %v", err)
Expand All @@ -44,6 +85,7 @@ func (k *SecurityKey) Setup(pin, version string, reset bool,
if err != nil {
return fmt.Errorf("couldn't store management key: %v", err)
}
// set pin/puk
if err = k.yubikey.SetPIN(piv.DefaultPIN, pin); err != nil {
return fmt.Errorf("couldn't set PIN: %v", err)
}
Expand Down
43 changes: 43 additions & 0 deletions internal/securitykey/setupslots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package securitykey

import "fmt"

// SetupSlots configures slots on the security key without resetting it
// completely.
func (k *SecurityKey) SetupSlots(pin, version string, reset bool,
signingKeys []string, decryptingKey bool) error {
var err error
if !reset {
setUp, err := k.checkSlotsSetUp(signingKeys, decryptingKey)
if err != nil {
return fmt.Errorf("couldn't check slots: %v", err)
}
if setUp {
return ErrKeySetUp
}
}
// get the management key
metadata, err := k.yubikey.Metadata(pin)
if err != nil {
return fmt.Errorf("coudnt' get metadata: %v", err)
}
// setup signing keys
for _, p := range signingKeys {
err := k.configureSlot(*metadata.ManagementKey, defaultSignSlots[p],
version)
if err != nil {
return fmt.Errorf("couldn't configure slot %v: %v", defaultSignSlots[p],
err)
}
}
// setup decrypt key
if decryptingKey {
err := k.configureSlot(*metadata.ManagementKey,
defaultDecryptSlots["never"], version)
if err != nil {
return fmt.Errorf("couldn't configure slot %v: %v",
defaultDecryptSlots["cached"], err)
}
}
return nil
}