From 6ce32bba731361a96f3b54f6b73e604f0e62621b Mon Sep 17 00:00:00 2001 From: Merlin Zerbe Date: Mon, 20 Jan 2025 09:42:39 +0100 Subject: [PATCH] feat: allow to read and write raw PIV objects the YubiKey allows to store raw data in PIV DataTags (see https://docs.yubico.com/yesdk/users-manual/application-piv/piv-objects.html). example use cases are storage of gpg public key counterparts of gpg private keys and encrypted storage of other key data (e.g. wireguard keys). this feature is also available in the official ykman app from Yubico (ykman piv objects import/export). --- v2/piv/key.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/v2/piv/key.go b/v2/piv/key.go index 177d6ae..9e8d841 100644 --- a/v2/piv/key.go +++ b/v2/piv/key.go @@ -688,6 +688,35 @@ func (yk *YubiKey) KeyInfo(slot Slot) (KeyInfo, error) { return ki, nil } +// Object returns the raw object stored in a given slot. +func (yk *YubiKey) Object(slot Slot) ([]byte, error) { + cmd := apdu{ + instruction: insGetData, + param1: 0x3f, + param2: 0xff, + data: []byte{ + 0x5c, // Tag list + 0x03, // Length of tag + byte(slot.Object >> 16), + byte(slot.Object >> 8), + byte(slot.Object), + }, + } + + resp, err := yk.tx.Transmit(cmd) + if err != nil { + return nil, fmt.Errorf("command failed: %w", err) + } + + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=85 + obj, _, err := unmarshalASN1(resp, 1, 0x13) // tag 0x53 + if err != nil { + return nil, fmt.Errorf("unmarshaling response: %v", err) + } + + return obj, nil +} + // Certificate returns the certifiate object stored in a given slot. // // If a certificate hasn't been set in the provided slot, the returned error @@ -748,6 +777,37 @@ func marshalASN1(tag byte, data []byte) []byte { return append(d, data...) } +// SetObject stores a raw object in the provided slot. +func (yk *YubiKey) SetObject(key []byte, slot Slot, obj []byte) error { + if err := ykAuthenticate(yk.tx, key, yk.rand, yk.version); err != nil { + return fmt.Errorf("authenticating with management key: %w", err) + } + return ykStoreObject(yk.tx, slot, obj) +} + +func ykStoreObject(tx *scTx, slot Slot, obj []byte) error { + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=40 + data := marshalASN1(0x70, obj) + + data = append([]byte{ + 0x5c, // Tag list + 0x03, // Length of tag + byte(slot.Object >> 16), + byte(slot.Object >> 8), + byte(slot.Object), + }, marshalASN1(0x53, data)...) + cmd := apdu{ + instruction: insPutData, + param1: 0x3f, + param2: 0xff, + data: data, + } + if _, err := tx.Transmit(cmd); err != nil { + return fmt.Errorf("command failed: %v", err) + } + return nil +} + // SetCertificate stores a certificate object in the provided slot. Setting a // certificate isn't required to use the associated key for signing or // decryption.