New Upstream Release - golang-github-go-piv-piv-go

Ready changes

Summary

Merged new upstream version: 1.10.0 (was: 1.8.0).

Resulting package

Built on 2022-10-20T04:51 (took 1m57s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases golang-github-go-piv-piv-go-dev

Lintian Result

Diff

diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 39d1882..1790b87 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -1,40 +1,49 @@
 name: test
 on: 
+  push:
+    branches:
+      - master
   pull_request:
     branches:
       - master
 
 jobs:
   build:
+    strategy:
+      matrix:
+        go-version: [1.18.x, 1.19.x]
     name: Linux
     runs-on: ubuntu-latest
     steps:
-    - name: Set up Go 1.14
+    - name: Set up Go
       uses: actions/setup-go@v2
       with:
-        go-version: '^1.14.2'
+        go-version: ${{ matrix.go-version }}
       id: go
     - name: Check out code into the Go module directory
       uses: actions/checkout@v2
-    - name: Install golint
-      run: go get -u golang.org/x/lint/golint
+    - name: Install staticcheck
+      run: go install honnef.co/go/tools/cmd/staticcheck@v0.3.3
     - name: Install libpcsc
       run: sudo apt-get install -y libpcsclite-dev pcscd pcsc-tools
     - name: Test
       run: "make test"
   build-windows:
+    strategy:
+      matrix:
+        go-version: [1.18.x, 1.19.x]
     name: Windows
     runs-on: windows-latest
     steps:
-    - name: Set up Go 1.14
+    - name: Set up Go
       uses: actions/setup-go@v2
       with:
-        go-version: '^1.14.2'
+        go-version: ${{ matrix.go-version }}
       id: go
     - name: Check out code into the Go module directory
       uses: actions/checkout@v2
-    - name: Install golint
-      run: go get -u golang.org/x/lint/golint
+    - name: Install staticcheck
+      run: go install honnef.co/go/tools/cmd/staticcheck@v0.3.3
     - name: Test
       run: "make build"
       env:
diff --git a/Makefile b/Makefile
index 15275a8..698d316 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ test: lint
 
 .PHONY: lint
 lint:
-	golint -set_exit_status ./...
+	staticcheck ./...
 
 .PHONY: build
 build: lint
diff --git a/README.md b/README.md
index 5247022..059ce19 100644
--- a/README.md
+++ b/README.md
@@ -121,7 +121,7 @@ if err := yk.SetMetadata(newKey, m); err != nil {
 fmt.Println("Credentials set. Your PIN is: %s", newPIN)
 ```
 
-The user can user the PIN later to fetch the management key:
+The user can use the PIN later to fetch the management key:
 
 ```go
 m, err := yk.Metadata(pin)
diff --git a/debian/changelog b/debian/changelog
index ef17b80..a93799c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-go-piv-piv-go (1.10.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 20 Oct 2022 04:49:31 -0000
+
 golang-github-go-piv-piv-go (1.8.0-3) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/go.mod b/go.mod
index b359a22..7306c19 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,3 @@
 module github.com/go-piv/piv-go
 
-go 1.13
+go 1.16
diff --git a/piv/key.go b/piv/key.go
index a9f742e..d3ec1a5 100644
--- a/piv/key.go
+++ b/piv/key.go
@@ -76,16 +76,47 @@ type Version struct {
 }
 
 // Formfactor enumerates the physical set of forms a key can take. USB-A vs.
-// USB-C and Keychain vs. Nano.
+// USB-C and Keychain vs. Nano (and FIPS variants for these).
 type Formfactor int
 
-// Formfactors recognized by this package.
+// The mapping between known Formfactor values and their descriptions.
+var formFactorStrings = map[Formfactor]string{
+	FormfactorUSBAKeychain:          "USB-A Keychain",
+	FormfactorUSBANano:              "USB-A Nano",
+	FormfactorUSBCKeychain:          "USB-C Keychain",
+	FormfactorUSBCNano:              "USB-C Nano",
+	FormfactorUSBCLightningKeychain: "USB-C/Lightning Keychain",
+
+	FormfactorUSBAKeychainFIPS:          "USB-A Keychain FIPS",
+	FormfactorUSBANanoFIPS:              "USB-A Nano FIPS",
+	FormfactorUSBCKeychainFIPS:          "USB-C Keychain FIPS",
+	FormfactorUSBCNanoFIPS:              "USB-C Nano FIPS",
+	FormfactorUSBCLightningKeychainFIPS: "USB-C/Lightning Keychain FIPS",
+}
+
+// String returns the human-readable description for the given form-factor
+// value, or a fallback value for any other, unknown form-factor.
+func (f Formfactor) String() string {
+	if s, ok := formFactorStrings[f]; ok {
+		return s
+	}
+	return fmt.Sprintf("unknown(0x%02x)", int(f))
+}
+
+// Formfactors recognized by this package. See the reference for more information:
+// https://developers.yubico.com/yubikey-manager/Config_Reference.html#_form_factor
 const (
-	FormfactorUSBAKeychain = iota + 1
-	FormfactorUSBANano
-	FormfactorUSBCKeychain
-	FormfactorUSBCNano
-	FormfactorUSBCLightningKeychain
+	FormfactorUSBAKeychain          = 0x1
+	FormfactorUSBANano              = 0x2
+	FormfactorUSBCKeychain          = 0x3
+	FormfactorUSBCNano              = 0x4
+	FormfactorUSBCLightningKeychain = 0x5
+
+	FormfactorUSBAKeychainFIPS          = 0x81
+	FormfactorUSBANanoFIPS              = 0x82
+	FormfactorUSBCKeychainFIPS          = 0x83
+	FormfactorUSBCNanoFIPS              = 0x84
+	FormfactorUSBCLightningKeychainFIPS = 0x85
 )
 
 // Prefix in the x509 Subject Common Name for YubiKey attestations
@@ -163,28 +194,11 @@ func (a *Attestation) addExt(e pkix.Extension) error {
 		if len(e.Value) != 1 {
 			return fmt.Errorf("expected 1 byte from formfactor, got: %d", len(e.Value))
 		}
-		switch e.Value[0] {
-		case 0x01:
-			a.Formfactor = FormfactorUSBAKeychain
-		case 0x02:
-			a.Formfactor = FormfactorUSBANano
-		case 0x03:
-			a.Formfactor = FormfactorUSBCKeychain
-		case 0x04:
-			a.Formfactor = FormfactorUSBCNano
-		case 0x05:
-			a.Formfactor = FormfactorUSBCLightningKeychain
-		default:
-			return fmt.Errorf("unrecognized formfactor: 0x%x", e.Value[0])
-		}
+		a.Formfactor = Formfactor(e.Value[0])
 	}
 	return nil
 }
 
-func verifySignature(parent, c *x509.Certificate) error {
-	return parent.CheckSignature(c.SignatureAlgorithm, c.RawTBSCertificate, c.Signature)
-}
-
 // Verify proves that a key was generated on a YubiKey. It ensures the slot and
 // YubiKey certificate chains up to the Yubico CA, parsing additional information
 // out of the slot certificate, such as the touch and PIN policies of a key.
@@ -194,23 +208,36 @@ func Verify(attestationCert, slotCert *x509.Certificate) (*Attestation, error) {
 }
 
 type verifier struct {
-	Root *x509.Certificate
+	Roots *x509.CertPool
 }
 
 func (v *verifier) Verify(attestationCert, slotCert *x509.Certificate) (*Attestation, error) {
-	root := v.Root
-	if root == nil {
-		ca, err := yubicoCA()
+	o := x509.VerifyOptions{KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}}
+	o.Roots = v.Roots
+	if o.Roots == nil {
+		cas, err := yubicoCAs()
 		if err != nil {
-			return nil, fmt.Errorf("parsing yubico ca: %v", err)
+			return nil, fmt.Errorf("failed to load yubico CAs: %v", err)
 		}
-		root = ca
+		o.Roots = cas
 	}
-	if err := verifySignature(root, attestationCert); err != nil {
-		return nil, fmt.Errorf("attestation certifcate not signed by : %v", err)
+
+	o.Intermediates = x509.NewCertPool()
+
+	// The attestation cert in some yubikey 4 does not encode X509v3 Basic Constraints.
+	// This isn't valid as per https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9
+	// (fourth paragraph) and thus makes x509.go validation fail.
+	// Work around this by setting this constraint here.
+	if !attestationCert.BasicConstraintsValid {
+		attestationCert.BasicConstraintsValid = true
+		attestationCert.IsCA = true
 	}
-	if err := verifySignature(attestationCert, slotCert); err != nil {
-		return nil, fmt.Errorf("slot certificate not signed by attestation certifcate: %v", err)
+
+	o.Intermediates.AddCert(attestationCert)
+
+	_, err := slotCert.Verify(o)
+	if err != nil {
+		return nil, fmt.Errorf("error verifying attestation certificate: %v", err)
 	}
 	return parseAttestation(slotCert)
 }
@@ -256,10 +283,10 @@ func parseSlot(commonName string) (Slot, bool) {
 	return RetiredKeyManagementSlot(uint32(key))
 }
 
-// yubicoPIVCAPEM is the PEM encoded attestation certificate used by Yubico.
+// yubicoPIVCAPEMAfter2018 is the PEM encoded attestation certificate used by Yubico.
 //
 // https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
-const yubicoPIVCAPEM = `-----BEGIN CERTIFICATE-----
+const yubicoPIVCAPEMAfter2018 = `-----BEGIN CERTIFICATE-----
 MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1
 YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY
 DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg
@@ -279,12 +306,58 @@ Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8
 SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7
 -----END CERTIFICATE-----`
 
-func yubicoCA() (*x509.Certificate, error) {
-	b, _ := pem.Decode([]byte(yubicoPIVCAPEM))
-	if b == nil {
+// Yubikeys manufactured sometime in 2018 and prior to mid-2017
+// were certified using the U2F root CA with serial number 457200631
+// See https://github.com/Yubico/developers.yubico.com/pull/392/commits/a58f1003f003e04fc9baf09cad9f64f0c284fd47
+// Cert available at https://developers.yubico.com/U2F/yubico-u2f-ca-certs.txt
+const yubicoPIVCAPEMU2F = `-----BEGIN CERTIFICATE-----
+MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
+dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
+MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
+IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk
+5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep
+8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw
+nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT
+9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw
+LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ
+hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN
+BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4
+MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt
+hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k
+LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U
+sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
+U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
+-----END CERTIFICATE-----`
+
+func yubicoCAs() (*x509.CertPool, error) {
+	certPool := x509.NewCertPool()
+
+	if !certPool.AppendCertsFromPEM([]byte(yubicoPIVCAPEMAfter2018)) {
+		return nil, fmt.Errorf("failed to parse yubico cert")
+	}
+
+	bU2F, _ := pem.Decode([]byte(yubicoPIVCAPEMU2F))
+	if bU2F == nil {
 		return nil, fmt.Errorf("failed to decode yubico pem data")
 	}
-	return x509.ParseCertificate(b.Bytes)
+
+	certU2F, err := x509.ParseCertificate(bU2F.Bytes)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse yubico cert: %v", err)
+	}
+
+	// The U2F root cert has pathlen x509 basic constraint set to 0.
+	// As per RFC 5280 this means that no intermediate cert is allowed
+	// in the validation path. This isn't really helpful since we do
+	// want to use the device attestation cert as intermediate cert in
+	// the chain. To make this work, set pathlen of the U2F root to 1.
+	//
+	// See https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9
+	certU2F.MaxPathLen = 1
+	certPool.AddCert(certU2F)
+
+	return certPool, nil
 }
 
 // Slot combinations pre-defined by this package.
@@ -670,14 +743,6 @@ type KeyAuth struct {
 	PINPolicy PINPolicy
 }
 
-func isAuthErr(err error) bool {
-	var e *apduErr
-	if !errors.As(err, &e) {
-		return false
-	}
-	return e.sw1 == 0x69 && e.sw2 == 0x82 // "security status not satisfied"
-}
-
 func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error {
 	// PINPolicyNever shouldn't require a PIN.
 	if pp == PINPolicyNever {
diff --git a/piv/key_test.go b/piv/key_test.go
index 1133682..46b9b8a 100644
--- a/piv/key_test.go
+++ b/piv/key_test.go
@@ -25,6 +25,7 @@ import (
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/asn1"
+	"encoding/pem"
 	"errors"
 	"math/big"
 	"testing"
@@ -766,7 +767,7 @@ func TestSetRSAPrivateKey(t *testing.T) {
 				t.Fatalf("decrypting data: %v", err)
 			}
 
-			if bytes.Compare(data, decrypted) != 0 {
+			if !bytes.Equal(data, decrypted) {
 				t.Fatalf("decrypted data is different to the source data")
 			}
 		})
@@ -896,3 +897,68 @@ func TestParseSlot(t *testing.T) {
 		})
 	}
 }
+
+func TestVerify(t *testing.T) {
+	tests := []struct {
+		name       string
+		deviceCert string
+		keyCert    string
+		ok         bool
+	}{
+		{
+			// Valid attestation chain from a recent YubiKey.
+			name:       "ValidChain",
+			deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIJAKs/UIpBjg1uMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNV\nBAMMIFl1YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAw\nMDAwMFoYDzIwNTIwNDE3MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0\ndGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zdJWGnk\naLE8Rb+TP7iSffhJV9SJEp2Me4QcfVidgHqyIdo0lruBk69RF1nrmS3i+G1yyUh/\nymAPZkcQCpms0E23Dmhue1VRpBedcsVtO/xSrfu0qAWTslp/k57ry6vkidrQU1cx\nl2KodH3KTmnZmaskQD8eGtxXwcmLOmhKem6GSqhN/3QznaDhZmVUAvUKSOaIzOxn\n2u1mDHhGwaHhR7dklsDwN7oni4WWX1GJXtzpB8j6JhoqyqXwSbq+ck54PfzUoOFd\n/2yKyFRDXnQvzbNL7+afbxBQQMxxo1e24DNE/cp+K09eT7Gh1Urao6meaSssN4aV\nFfmkhC2NapGKMQIDAQABoykwJzARBgorBgEEAYLECgMDBAMFBAMwEgYDVR0TAQH/\nBAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAJfOLOQYGyIMQ5y+sDkYz+e6G\nH8BqqiYL9VOC3U3KQX9mrtZnaIexqJOCQyCFOSvaTFJvOfNiCCKQuLbmS+Qn4znd\nnSitCsdJSFKskQP7hbXqUK01epb6iTuuko4w3V57YVudnniZBD2s4XoNcJ6BFizZ\n3iXQqRMaLVfFHS9Qx0iLZLcR2s29nIl6NI/qFdIgkyo07J5cPnBiD6wxQft8FdfR\nbgx9yrrjY0mvj/k5LRN6lab8lTolgI5luJtKNueq96LVkTkAzcCaJPQ9YQ4cxeU9\nOapsEeOk6xf5bRPtdf0WhEKthXywt9D0pSHhAI+fpLNe/VtlZpt3hn9aTbqSug==\n-----END CERTIFICATE-----\n",
+			keyCert:    "-----BEGIN CERTIFICATE-----\nMIICVTCCAT2gAwIBAgIQAU4Yg7Qnw9FZgMBEaJ7ZMzANBgkqhkiG9w0BAQsFADAh\nMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAw\nMFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRl\nc3RhdGlvbiA5YTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABATzM3sJuwemL2Ha\nHkGIzmCVjUMreNIVrRLOvnbZjoVflk1eab/iLUlKzk/2jXTu9TISRg2dhyXcutct\nvnqr66yjTjBMMBEGCisGAQQBgsQKAwMEAwUEAzAUBgorBgEEAYLECgMHBAYCBADw\nDxQwEAYKKwYBBAGCxAoDCAQCAgEwDwYKKwYBBAGCxAoDCQQBBDANBgkqhkiG9w0B\nAQsFAAOCAQEAFX0hL5gi/g4ZM7vCH5kDAtma7eBp0LpbCzR313GGyBR7pJFtuj2l\nbWU+V3SFRihXBTDb8q+uvyCBqgz1szdZzrpfjqNkhEPfPNabxjxJxVoe6Gdcn115\naduxfqqT2u+YIsERzaIIIisehLQkc/5zLkpocA6jbKBZnZWUBJIxuz4QmYTIf0O4\nHPE2o4JbAyGx/hRaqVvDgNeAz94ZFjb4Mp3RNbbdRUZB0ehrT/IGRJoHRu2HKFGM\nylRJL2kjKPoEc4XHbCu+MfmAIrQ4Xseg85zyI7ThhYvAzktdLHhQyfYr4wrrLCN3\noeTzmiqIHe9AataJXQ+mEQEEc9TNY23RFg==\n-----END CERTIFICATE-----\n",
+			ok:         true,
+		},
+		{
+			// Valid attestation chain from a yubikey manufactured in 2018 showing a manufacture bug (device certified using U2F root, and device cert does not encode X509 basic constraints).
+			name:       "ValidChain2018",
+			deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC6TCCAdGgAwIBAgIJALvwZFDESwMlMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNV\nBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgw\nMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElW\nIEF0dGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXnZ\n+lxX0nNzy3jn+lrZ+1cHTVUNYVKPqGTjvRw/7XOEnInWC1VCPJqwHYtnnoH4EIXN\n7kDGXwInfs9pwyjpgQw/V23yywFtUhaR8Xgw8zqC/YfJpeK4PetJ9/k+xFbICuX7\nWDv/k5Wth3VZSaVjm/tunWajtt3OLOQQaMSoLqP41XAHHuCyzfCwJ2Vsa2FyCINF\nyG6XobokeICDRnH44POqudcLVIDvZLQqu2LF+mZd+OO5nqmTa68kkwRf/m93eOJP\no7GvYtQSp7CPJC7ks2gl8U7wuT9DQT5/0wqkoEyLZg/KLUlzgXjMa+7GtCLTC1Ku\nOh9vw02f4K44RW4nWwIDAQABoxUwEzARBgorBgEEAYLECgMDBAMEAwcwDQYJKoZI\nhvcNAQELBQADggEBAHD/uXqNgCYywj2ee7s7kix2TT4XN9OIn0fTNh5LEiUN+q7U\nzJc9q7b5WD7PfaG6UNyuaSnLaq+dLOCJ4bX4h+/MwQSndQg0epMra1ThVQZkMkGa\nktAJ5JT6j9qxNxD1RWMl91e4JwtGzFyDwFyyUGnSwhMsqMdwfBsmTpvgxmAD/NMs\nkWB/m91FV9D+UBqsZRoLoc44kEFYBZ09ypTsR699oJRsBfG0AqVYyK7rnG6663fF\nGUSWk7noVdUPXedlwXCqCymCsVheoss9qF1cffaFIl9RxGvVvCFybx0LGiYDxfgv\n80yGZIY/mAqZVDWyHZSs4f6kWK9GeLKU2Y9yby4=\n-----END CERTIFICATE-----\n",
+			keyCert:    "-----BEGIN CERTIFICATE-----\nMIICLzCCARegAwIBAgIRAIxiihk4fSKK6keqJYujvnkwDQYJKoZIhvcNAQELBQAw\nITEfMB0GA1UEAwwWWXViaWNvIFBJViBBdHRlc3RhdGlvbjAgFw0xNDA4MDEwMDAw\nMDBaGA8yMDUwMDkwNDAwMDAwMFowJTEjMCEGA1UEAwwaWXViaUtleSBQSVYgQXR0\nZXN0YXRpb24gOWEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATHEzJsrhTHuvsx\n685AiWsAuT8Poe/zQfDRZNfpUSzJ31v6MZ9nz70pNrdd/sbG7O1UA6ceWhq1jHTU\n96Dnp99voycwJTARBgorBgEEAYLECgMDBAMEAwcwEAYKKwYBBAGCxAoDCAQCAgEw\nDQYJKoZIhvcNAQELBQADggEBADoswZ1LJ5GYVNgtRE0+zMQkAzam8YqeKmIDHtir\nvolIpGtJHzgCG2SdJlR/KnjRWF/1i8TRMhQ0O/KgkIEh+IyhJtD7DojgWvIBsCnX\nJXF7EPQMy17l7/9940QSOnQRIDb+z0eq9ACAjC3FWzqeR5VgN4C1QpCw7gKgqLTs\npmmDHHg4HsKl0PsPwim0bYIqEHttrLjPQiPnoa3qixzNKbwJjXb4/f/dvCTx9dRP\n0FVABj5Yh8f728xzrzw2nLZ9X/c0GoXfKu9s7lGNLcZ5OO+zys1ATei2h/PFJLDH\nAdrenw31WOYRtdjcNBKyAk80ajryjTAX3GXfbKpkdVB9hEo=\n-----END CERTIFICATE-----\n",
+			ok:         true,
+		},
+		{
+			// Invalid attestation chain. Device cert from yubikey A, key cert from yubikey B.
+			name:       "InvalidChain",
+			deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC+jCCAeKgAwIBAgIJAKs/UIpBjg1uMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNV\nBAMMIFl1YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAw\nMDAwMFoYDzIwNTIwNDE3MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0\ndGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0zdJWGnk\naLE8Rb+TP7iSffhJV9SJEp2Me4QcfVidgHqyIdo0lruBk69RF1nrmS3i+G1yyUh/\nymAPZkcQCpms0E23Dmhue1VRpBedcsVtO/xSrfu0qAWTslp/k57ry6vkidrQU1cx\nl2KodH3KTmnZmaskQD8eGtxXwcmLOmhKem6GSqhN/3QznaDhZmVUAvUKSOaIzOxn\n2u1mDHhGwaHhR7dklsDwN7oni4WWX1GJXtzpB8j6JhoqyqXwSbq+ck54PfzUoOFd\n/2yKyFRDXnQvzbNL7+afbxBQQMxxo1e24DNE/cp+K09eT7Gh1Urao6meaSssN4aV\nFfmkhC2NapGKMQIDAQABoykwJzARBgorBgEEAYLECgMDBAMFBAMwEgYDVR0TAQH/\nBAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAJfOLOQYGyIMQ5y+sDkYz+e6G\nH8BqqiYL9VOC3U3KQX9mrtZnaIexqJOCQyCFOSvaTFJvOfNiCCKQuLbmS+Qn4znd\nnSitCsdJSFKskQP7hbXqUK01epb6iTuuko4w3V57YVudnniZBD2s4XoNcJ6BFizZ\n3iXQqRMaLVfFHS9Qx0iLZLcR2s29nIl6NI/qFdIgkyo07J5cPnBiD6wxQft8FdfR\nbgx9yrrjY0mvj/k5LRN6lab8lTolgI5luJtKNueq96LVkTkAzcCaJPQ9YQ4cxeU9\nOapsEeOk6xf5bRPtdf0WhEKthXywt9D0pSHhAI+fpLNe/VtlZpt3hn9aTbqSug==\n-----END CERTIFICATE-----\n",
+			keyCert:    "-----BEGIN CERTIFICATE-----\nMIICLzCCARegAwIBAgIRAIxiihk4fSKK6keqJYujvnkwDQYJKoZIhvcNAQELBQAw\nITEfMB0GA1UEAwwWWXViaWNvIFBJViBBdHRlc3RhdGlvbjAgFw0xNDA4MDEwMDAw\nMDBaGA8yMDUwMDkwNDAwMDAwMFowJTEjMCEGA1UEAwwaWXViaUtleSBQSVYgQXR0\nZXN0YXRpb24gOWEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATHEzJsrhTHuvsx\n685AiWsAuT8Poe/zQfDRZNfpUSzJ31v6MZ9nz70pNrdd/sbG7O1UA6ceWhq1jHTU\n96Dnp99voycwJTARBgorBgEEAYLECgMDBAMEAwcwEAYKKwYBBAGCxAoDCAQCAgEw\nDQYJKoZIhvcNAQELBQADggEBADoswZ1LJ5GYVNgtRE0+zMQkAzam8YqeKmIDHtir\nvolIpGtJHzgCG2SdJlR/KnjRWF/1i8TRMhQ0O/KgkIEh+IyhJtD7DojgWvIBsCnX\nJXF7EPQMy17l7/9940QSOnQRIDb+z0eq9ACAjC3FWzqeR5VgN4C1QpCw7gKgqLTs\npmmDHHg4HsKl0PsPwim0bYIqEHttrLjPQiPnoa3qixzNKbwJjXb4/f/dvCTx9dRP\n0FVABj5Yh8f728xzrzw2nLZ9X/c0GoXfKu9s7lGNLcZ5OO+zys1ATei2h/PFJLDH\nAdrenw31WOYRtdjcNBKyAk80ajryjTAX3GXfbKpkdVB9hEo=\n-----END CERTIFICATE-----\n",
+			ok:         false,
+		},
+		{
+			// Invalid attestation chain. Device cert from yubikey B, key cert from yubikey A.
+			name:       "InvalidChain2",
+			deviceCert: "-----BEGIN CERTIFICATE-----\nMIIC6TCCAdGgAwIBAgIJALvwZFDESwMlMA0GCSqGSIb3DQEBCwUAMC4xLDAqBgNV\nBAMTI1l1YmljbyBVMkYgUm9vdCBDQSBTZXJpYWwgNDU3MjAwNjMxMCAXDTE0MDgw\nMTAwMDAwMFoYDzIwNTAwOTA0MDAwMDAwWjAhMR8wHQYDVQQDDBZZdWJpY28gUElW\nIEF0dGVzdGF0aW9uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXnZ\n+lxX0nNzy3jn+lrZ+1cHTVUNYVKPqGTjvRw/7XOEnInWC1VCPJqwHYtnnoH4EIXN\n7kDGXwInfs9pwyjpgQw/V23yywFtUhaR8Xgw8zqC/YfJpeK4PetJ9/k+xFbICuX7\nWDv/k5Wth3VZSaVjm/tunWajtt3OLOQQaMSoLqP41XAHHuCyzfCwJ2Vsa2FyCINF\nyG6XobokeICDRnH44POqudcLVIDvZLQqu2LF+mZd+OO5nqmTa68kkwRf/m93eOJP\no7GvYtQSp7CPJC7ks2gl8U7wuT9DQT5/0wqkoEyLZg/KLUlzgXjMa+7GtCLTC1Ku\nOh9vw02f4K44RW4nWwIDAQABoxUwEzARBgorBgEEAYLECgMDBAMEAwcwDQYJKoZI\nhvcNAQELBQADggEBAHD/uXqNgCYywj2ee7s7kix2TT4XN9OIn0fTNh5LEiUN+q7U\nzJc9q7b5WD7PfaG6UNyuaSnLaq+dLOCJ4bX4h+/MwQSndQg0epMra1ThVQZkMkGa\nktAJ5JT6j9qxNxD1RWMl91e4JwtGzFyDwFyyUGnSwhMsqMdwfBsmTpvgxmAD/NMs\nkWB/m91FV9D+UBqsZRoLoc44kEFYBZ09ypTsR699oJRsBfG0AqVYyK7rnG6663fF\nGUSWk7noVdUPXedlwXCqCymCsVheoss9qF1cffaFIl9RxGvVvCFybx0LGiYDxfgv\n80yGZIY/mAqZVDWyHZSs4f6kWK9GeLKU2Y9yby4=\n-----END CERTIFICATE-----\n",
+			keyCert:    "-----BEGIN CERTIFICATE-----\nMIICVTCCAT2gAwIBAgIQAU4Yg7Qnw9FZgMBEaJ7ZMzANBgkqhkiG9w0BAQsFADAh\nMR8wHQYDVQQDDBZZdWJpY28gUElWIEF0dGVzdGF0aW9uMCAXDTE2MDMxNDAwMDAw\nMFoYDzIwNTIwNDE3MDAwMDAwWjAlMSMwIQYDVQQDDBpZdWJpS2V5IFBJViBBdHRl\nc3RhdGlvbiA5YTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABATzM3sJuwemL2Ha\nHkGIzmCVjUMreNIVrRLOvnbZjoVflk1eab/iLUlKzk/2jXTu9TISRg2dhyXcutct\nvnqr66yjTjBMMBEGCisGAQQBgsQKAwMEAwUEAzAUBgorBgEEAYLECgMHBAYCBADw\nDxQwEAYKKwYBBAGCxAoDCAQCAgEwDwYKKwYBBAGCxAoDCQQBBDANBgkqhkiG9w0B\nAQsFAAOCAQEAFX0hL5gi/g4ZM7vCH5kDAtma7eBp0LpbCzR313GGyBR7pJFtuj2l\nbWU+V3SFRihXBTDb8q+uvyCBqgz1szdZzrpfjqNkhEPfPNabxjxJxVoe6Gdcn115\naduxfqqT2u+YIsERzaIIIisehLQkc/5zLkpocA6jbKBZnZWUBJIxuz4QmYTIf0O4\nHPE2o4JbAyGx/hRaqVvDgNeAz94ZFjb4Mp3RNbbdRUZB0ehrT/IGRJoHRu2HKFGM\nylRJL2kjKPoEc4XHbCu+MfmAIrQ4Xseg85zyI7ThhYvAzktdLHhQyfYr4wrrLCN3\noeTzmiqIHe9AataJXQ+mEQEEc9TNY23RFg==\n-----END CERTIFICATE-----\n",
+			ok:         false,
+		},
+	}
+
+	parseCert := func(cert string) (*x509.Certificate, error) {
+		block, _ := pem.Decode([]byte(cert))
+		if block == nil {
+			t.Fatalf("decoding PEM cert, empty block")
+		}
+		return x509.ParseCertificate(block.Bytes)
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			deviceCert, err := parseCert(test.deviceCert)
+			if err != nil {
+				t.Fatalf("parsing device cert: %v", err)
+			}
+
+			keyCert, err := parseCert(test.keyCert)
+			if err != nil {
+				t.Fatalf("parsing key cert: %v", err)
+			}
+
+			_, err = Verify(deviceCert, keyCert)
+			if (err == nil) != test.ok {
+				t.Errorf("Verify returned %v, expected test outcome %v", err, test.ok)
+			}
+		})
+	}
+}
diff --git a/piv/pcsc_openbsd.go b/piv/pcsc_openbsd.go
new file mode 100644
index 0000000..54e6d13
--- /dev/null
+++ b/piv/pcsc_openbsd.go
@@ -0,0 +1,30 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package piv
+
+import "C"
+
+// Return codes for PCSC are different on different platforms (int vs. long).
+
+func scCheck(rc C.long) error {
+	if rc == rcSuccess {
+		return nil
+	}
+	return &scErr{int64(rc)}
+}
+
+func isRCNoReaders(rc C.long) bool {
+	return rc == 0x8010002E
+}
diff --git a/piv/pcsc_unix.go b/piv/pcsc_unix.go
index 55d6318..a43d259 100644
--- a/piv/pcsc_unix.go
+++ b/piv/pcsc_unix.go
@@ -12,7 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// +build darwin linux freebsd
+//go:build darwin || linux || freebsd || openbsd
+// +build darwin linux freebsd openbsd
 
 package piv
 
@@ -24,6 +25,10 @@ package piv
 // #cgo freebsd CFLAGS: -I/usr/local/include/PCSC
 // #cgo freebsd LDFLAGS: -L/usr/local/lib/
 // #cgo freebsd LDFLAGS: -lpcsclite
+// #cgo openbsd CFLAGS: -I/usr/local/include/
+// #cgo openbsd CFLAGS: -I/usr/local/include/PCSC
+// #cgo openbsd LDFLAGS: -L/usr/local/lib/
+// #cgo openbsd LDFLAGS: -lpcsclite
 // #include <PCSC/winscard.h>
 // #include <PCSC/wintypes.h>
 import "C"
diff --git a/piv/pcsc_windows.go b/piv/pcsc_windows.go
index 930ef38..845194f 100644
--- a/piv/pcsc_windows.go
+++ b/piv/pcsc_windows.go
@@ -127,9 +127,13 @@ func (c *scContext) Connect(reader string) (*scHandle, error) {
 		handle         syscall.Handle
 		activeProtocol uint16
 	)
+	readerPtr, err := syscall.UTF16PtrFromString(reader)
+	if err != nil {
+		return nil, fmt.Errorf("invalid reader string: %v", err)
+	}
 	r0, _, _ := procSCardConnectW.Call(
 		uintptr(c.ctx),
-		uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(reader))),
+		uintptr(unsafe.Pointer(readerPtr)),
 		scardShareExclusive,
 		scardProtocolT1,
 		uintptr(unsafe.Pointer(&handle)),
diff --git a/piv/piv.go b/piv/piv.go
index 001d5c3..4e7171a 100644
--- a/piv/piv.go
+++ b/piv/piv.go
@@ -402,12 +402,12 @@ func ykAuthenticate(tx *scTx, key [24]byte, rand io.Reader) error {
 	response := make([]byte, 8)
 	block.Encrypt(response, challenge)
 
-	data := append([]byte{
+	data := []byte{
 		0x7c, // Dynamic Authentication Template tag
 		20,   // 2+8+2+8
 		0x80, // 'Witness'
 		0x08, // Tag length
-	})
+	}
 	data = append(data, cardResponse...)
 	data = append(data,
 		0x81, // 'Challenge'
@@ -632,47 +632,6 @@ func ykSerial(tx *scTx, v *version) (uint32, error) {
 	return binary.BigEndian.Uint32(resp), nil
 }
 
-// ykChangeManagementKey sets the Management Key to the new key provided. The
-// user must have authenticated with the existing key first.
-func ykChangeManagementKey(tx *scTx, key [24]byte) error {
-	cmd := apdu{
-		instruction: insSetMGMKey,
-		param1:      0xff,
-		param2:      0xff, // TODO: support touch policy
-		data: append([]byte{
-			alg3DES, keyCardManagement, 24,
-		}, key[:]...),
-	}
-	if _, err := tx.Transmit(cmd); err != nil {
-		return fmt.Errorf("command failed: %w", err)
-	}
-	return nil
-}
-
-func unmarshalDERField(b []byte, tag uint64) (obj []byte, err error) {
-	var prefix []byte
-	for tag > 0 {
-		prefix = append(prefix, byte(tag))
-		tag = tag >> 8
-	}
-	for i, j := 0, len(prefix)-1; i < j; i, j = i+1, j-1 {
-		prefix[i], prefix[j] = prefix[j], prefix[i]
-	}
-
-	hasPrefix := bytes.HasPrefix(b, prefix)
-	for len(b) > 0 {
-		var v asn1.RawValue
-		b, err = asn1.Unmarshal(b, &v)
-		if err != nil {
-			return nil, err
-		}
-		if hasPrefix {
-			return v.Bytes, nil
-		}
-	}
-	return nil, fmt.Errorf("no der value with tag 0x%x", prefix)
-}
-
 // Metadata returns protected data stored on the card. This can be used to
 // retrieve PIN protected management keys.
 func (yk *YubiKey) Metadata(pin string) (*Metadata, error) {
@@ -780,9 +739,7 @@ func (m *Metadata) unmarshal(b []byte) error {
 			return fmt.Errorf("invalid management key length: %d", len(v.Bytes))
 		}
 		var key [24]byte
-		for i := 0; i < len(v.Bytes); i++ {
-			key[i] = v.Bytes[i]
-		}
+		copy(key[:], v.Bytes)
 		m.ManagementKey = &key
 	}
 	return nil

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/gocode/src/github.com/go-piv/piv-go/piv/pcsc_openbsd.go

No differences were encountered in the control files

More details

Full run details