pax_global_header00006660000000000000000000000064137572163130014522gustar00rootroot0000000000000052 comment=f4690239f1aa42002af79c41a21639a35f71ac26 go-ykpiv-1.3.4/000077500000000000000000000000001375721631300132745ustar00rootroot00000000000000go-ykpiv-1.3.4/.dockerignore000066400000000000000000000000361375721631300157470ustar00rootroot00000000000000.dockerignore .git Dockerfile go-ykpiv-1.3.4/.gitignore000066400000000000000000000000261375721631300152620ustar00rootroot00000000000000*swp *.dll win/ *.exe go-ykpiv-1.3.4/AUTHORS000066400000000000000000000003641375721631300143470ustar00rootroot00000000000000Paul Tagliamonte Tianon Gravi Edward Betts Ian Haken Lucas Manuel Rodriguez Manfred Schreiber Mehmet Gurevin go-ykpiv-1.3.4/Dockerfile000066400000000000000000000010131375721631300152610ustar00rootroot00000000000000# $ docker build -t paultag/go-ykpiv . # $ docker run -it --rm -v /run/pcscd/pcscd.comm:/run/pcscd/pcscd.comm paultag/go-ykpiv FROM debian:stretch-slim RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ gcc \ git \ golang-any \ libc6-dev \ libykpiv-dev \ \ # this isn't strictly necessary, but it's a useful tool to have available yubico-piv-tool \ && rm -rf /var/lib/apt/lists/* ENV GOPATH /go WORKDIR $GOPATH/src/pault.ag/go/ykpiv COPY . . RUN go get -v -t ./... go-ykpiv-1.3.4/LICENSE000066400000000000000000000020741375721631300143040ustar00rootroot00000000000000Copyright (c) Paul R. Tagliamonte , 2017 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-ykpiv-1.3.4/README.md000066400000000000000000000075151375721631300145630ustar00rootroot00000000000000go-ykpiv ======== go-ykpiv is a high level cgo wrapper around `libykpiv.so.1` that implements an idiomatic go API fit for use when applications need to communicate with a Yubikey in PIV mode. What's PIV? ----------- PIV Cards are cards defined by FIPS 201, a Federal US Government standard defining the ID cards employees use. At its core, it's a set of x509 Certificates and corresponding private keys in a configuration that is standardized across implementations. For more details on how PIV Tokens can be used, the FICAM (Federal Identity, Credential, and Access Management) team at GSA (General Services Administration) has published some guides on GitHub under [GSA/piv-guides](https://github.com/GSA/piv-guides) How is this different than OpenSC? ---------------------------------- Most PIV tokens, Yubikeys included, can be used as a PKCS#11 device using [OpenSC](https://github.com/opensc/opensc), and Yubikeys are even capable of doing Signing and Decryption through that interface. However, some management functions are not exposed in the PKCS#11 OpenSC interface, so this library may be of use when one wants to write a new Certificate, or set PINs. Development =========== Testing ------- To run the tests, you'll need to find a Yubikey that you're willing to wipe clean, and destroy all data on it. After you've found such a key, remove all other Yubikeys from your machine. The tests will panic if the `YKPIV_YES_DESTROY_MY_KEY` environment variable is unset. Running the tests will **reset** your Yubikey a few times (once per test), and you will wind up with a key with the default PIN, PUK and Management Key. Installation ============ Debian ------ ``` sudo apt install build-essential libykpiv-dev go get pault.ag/go/ykpiv ``` MacOS X ------ ``` brew install yubico-piv-tool go get pault.ag/go/ykpiv ``` Examples ======== ```go package main import ( "fmt" "pault.ag/go/ykpiv" ) func main() { yubikey, err := ykpiv.New(ykpiv.Options{ // Verbose: true, Reader: "Yubico Yubikey NEO U2F+CCID 01 00", }) if err != nil { panic(err) } defer yubikey.Close() version, err := yubikey.Version() if err != nil { panic(err) } fmt.Printf("Application version %s found.\n", version) } ``` Running on Windows ================== 1. Download `yubico-piv-tool-1.7.0-win64.zip` from the official site 2. Create directory win in the root of the project 3. Copy `lib` and `include` directories from the downloaded archive to the `win` directory 4. Copy all DLLs from the `bin` directory in the archive to the root of your project 5. Copy `libwinpthread-1.dll` from `C:\TDM-GCC-64\bin` to the root of your project Related work ============ Eric Chiang has published [piv-go](https://github.com/ericchiang/piv-go), a go implementation of the yubikey piv bindings. LICENSE ======= ``` Copyright (c) Paul R. Tagliamonte , 2017 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` go-ykpiv-1.3.4/attestation.go000066400000000000000000000122511375721631300161630ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2019 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv import ( "crypto/x509" "encoding/asn1" "fmt" ) var ( yubicoAttestionRoot2018 []byte = []byte(` -----BEGIN CERTIFICATE----- MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1 YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg U2VyaWFsIDI2Mzc1MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMN2 cMTNR6YCdcTFRxuPy31PabRn5m6pJ+nSE0HRWpoaM8fc8wHC+Tmb98jmNvhWNE2E ilU85uYKfEFP9d6Q2GmytqBnxZsAa3KqZiCCx2LwQ4iYEOb1llgotVr/whEpdVOq joU0P5e1j1y7OfwOvky/+AXIN/9Xp0VFlYRk2tQ9GcdYKDmqU+db9iKwpAzid4oH BVLIhmD3pvkWaRA2H3DA9t7H/HNq5v3OiO1jyLZeKqZoMbPObrxqDg+9fOdShzgf wCqgT3XVmTeiwvBSTctyi9mHQfYd2DwkaqxRnLbNVyK9zl+DzjSGp9IhVPiVtGet X02dxhQnGS7K6BO0Qe8CAwEAAaNCMEAwHQYDVR0OBBYEFMpfyvLEojGc6SJf8ez0 1d8Cv4O/MA8GA1UdEwQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 DQEBCwUAA4IBAQBc7Ih8Bc1fkC+FyN1fhjWioBCMr3vjneh7MLbA6kSoyWF70N3s XhbXvT4eRh0hvxqvMZNjPU/VlRn6gLVtoEikDLrYFXN6Hh6Wmyy1GTnspnOvMvz2 lLKuym9KYdYLDgnj3BeAvzIhVzzYSeU77/Cupofj093OuAswW0jYvXsGTyix6B3d bW5yWvyS9zNXaqGaUmP3U9/b6DlHdDogMLu3VLpBB9bm5bjaKWWJYgWltCVgUbFq Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8 SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7 -----END CERTIFICATE----- `) ) var ( // OIDs of some Yubikey specific fields. These are present on the forms // of a Yubikey Attestion Certificate. oidFirmwareVersion = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 3} oidSerialNumber = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 7} oidPolicy = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 8} // This is not implemented; I don't have hardware that returns a Certificate // that has this Extension. This can be implemented once we get an example. oidFormFactor = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 41482, 3, 9} ) // Verify that the attestion certificate is correctly signed by the Yubikey // root (provided with this package), as well as verifying that the Attestion // Certificate is signed by that slot correctly. // // The `attestationCert` is the Certificate from the Yubikey Attestion slot, // signed by the Yubico roots. // // The `attestedCert` is the Certificate signed by the Attestion slot asserting // that the public key was generated on-chip. func VerifyAttestation(attestationCert, attestedCert *x509.Certificate) ([][]*x509.Certificate, error) { roots := x509.NewCertPool() if !roots.AppendCertsFromPEM(yubicoAttestionRoot2018) { return nil, fmt.Errorf("ykpiv: INTERNAL ERROR: Attestion Root PEM is wrong!") } ints := x509.NewCertPool() attestationCert.IsCA = true attestationCert.BasicConstraintsValid = true ints.AddCert(attestationCert) return attestationCert.Verify(x509.VerifyOptions{ Roots: roots, Intermediates: ints, }) } // Struct with an anonymous member (`x509.Certificate`) that allows you to // both access the regular fields for ease of use, as well as handle the // Yubikey particular fields. type AttestionCertificate struct { x509.Certificate // Version of the Yubikey Firmware that generated this key. FirmwareVersion *[3]byte // Serial Number of the Yubikey in question. SerialNumber *int // PinPolicy *byte TouchPolicy *byte } // Parse the Yubikey specific Extensions from the x509.Certificate, and place // them into a struct for use by end users. func NewAttestionCertificate(cert *x509.Certificate) (*AttestionCertificate, error) { aC := AttestionCertificate{Certificate: *cert} for _, extension := range aC.Extensions { switch { case extension.Id.Equal(oidFirmwareVersion): var firmware = [3]byte{extension.Value[0], extension.Value[1], extension.Value[2]} aC.FirmwareVersion = &firmware break case extension.Id.Equal(oidSerialNumber): var serial int = 0 if _, err := asn1.Unmarshal(extension.Value, &serial); err != nil { return nil, err } aC.SerialNumber = &serial break case extension.Id.Equal(oidPolicy): var ( pin byte = extension.Value[0] touch byte = extension.Value[1] ) aC.PinPolicy = &pin aC.TouchPolicy = &touch break } } return &aC, nil } // vim: foldmethod=marker go-ykpiv-1.3.4/cmd/000077500000000000000000000000001375721631300140375ustar00rootroot00000000000000go-ykpiv-1.3.4/cmd/ykls/000077500000000000000000000000001375721631300150215ustar00rootroot00000000000000go-ykpiv-1.3.4/cmd/ykls/ykls.go000066400000000000000000000037751375721631300163460ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package main import ( "fmt" "pault.ag/go/ykpiv" ) func ohshit(err error) { if err != nil { panic(err) } } func main() { readers, err := ykpiv.Readers() ohshit(err) for _, reader := range readers { fmt.Printf("Reader: %s\n", reader) token, err := ykpiv.New(ykpiv.Options{ Reader: reader, }) ohshit(err) defer token.Close() version, err := token.Version() ohshit(err) fmt.Printf("Version: %s\n", version) serial, err := token.Serial() ohshit(err) fmt.Printf("Serial: %d\n", serial) for _, slotId := range []ykpiv.SlotId{ ykpiv.Authentication, ykpiv.Signature, ykpiv.CardAuthentication, ykpiv.KeyManagement, } { slot, err := token.Slot(slotId) if err != nil || slot.Certificate == nil { continue } fmt.Printf("Slot %s: %s\n", slotId, slot.Certificate.Subject.CommonName) } fmt.Printf("\n") } } // vim: foldmethod=marker go-ykpiv-1.3.4/decrypt.go000066400000000000000000000054231375721631300153010ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv /* #cgo pkg-config: ykpiv #include #include */ import "C" import ( "crypto/ecdsa" "crypto/rsa" "fmt" "io" "unsafe" "crypto" "pault.ag/go/ykpiv/internal/pkcs1v15" ) // Decrypt implements the crypto.Decrypter interface. // If the slot holds an RSA key, then it will decrypt the ciphertext with the // private key backing the Slot we're operating on. // If the slot holds an EC key, then we will perform ECDH and return the shared // secret. In this case, msg must be the peer's public key in octet form, as // specified in section 4.3.6 of ANSI X9.62. // // The `rand` argument is disregarded in favor of the on-chip RNG on the Yubikey // The `opts` argument is not used at this time, but may in the future. func (s Slot) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) { var cMessage = (*C.uchar)(C.CBytes(msg)) defer C.free(unsafe.Pointer(cMessage)) var cMessageLen = C.size_t(len(msg)) var cPlaintextLen = C.size_t(len(msg)) var cPlaintext = (*C.uchar)(C.malloc(cMessageLen)) defer C.free(unsafe.Pointer(cPlaintext)) algorithm, err := s.getAlgorithm() if err != nil { return nil, err } if err := getError(C.ykpiv_decipher_data( s.yubikey.state, cMessage, cMessageLen, cPlaintext, &cPlaintextLen, algorithm, C.uchar(s.Id.Key), ), "decipher_data"); err != nil { return nil, err } plaintext := C.GoBytes(unsafe.Pointer(cPlaintext), C.int(cPlaintextLen)) switch s.PublicKey.(type) { case *rsa.PublicKey: return pkcs1v15.Unpad(plaintext) case *ecdsa.PublicKey: return plaintext, nil default: return nil, fmt.Errorf("unknown slot key type") } } // vim: foldmethod=marker go-ykpiv-1.3.4/docs/000077500000000000000000000000001375721631300142245ustar00rootroot00000000000000go-ykpiv-1.3.4/docs/quirks.md000066400000000000000000000027351375721631300160730ustar00rootroot00000000000000Qurks of ykpiv ============== ykpiv.Yubikey.Slot ------------------ Slots which have private keys backing them but were not generated on the Yubikey are unable to be loaded unless a public certificate has already been set. If an attestation key exists, and an attestation certificate can be loaded for the slot, the public key will be available. These slots will however not have a valid x509 certificate available for authentication. Basically, the only way to get a Slot with a nil Certificate is to call the Generation code, where we are actually able to pull the Public Key material out, and allow for bootstrapping. The advisable workflow would be to generate the key, then sign a CSR to import the Certificate later when we have one, or to Self Sign a Certificate, then import it. ykpiv.Slot.Update ----------------- Because the Certificate is hanging off the Slot object, there is a tradeoff in terms of number of times we read the Certificate off the Key, and the ability to get updated Certificates when they are pushed in. There have been no attempts to be clever here, so it is *not* advisable to keep a `Slot` around for long-running processes, if you can avoid it. If it becomes strictly needed, at least understand your Certificate may become out of sync if another process Updates it. ykpiv.Options / ykpiv.Yubikey.ChangePIN --------------------------------------- Changing the PIN does not touch the Options entries, and attempts to Login after ChangePIN will use the old PIN. go-ykpiv-1.3.4/errors.go000066400000000000000000000116111375721631300151370ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv /* #cgo pkg-config: ykpiv #include */ import "C" import ( "fmt" ) // Go wrapper around the internal C ykpiv error integers. Error codes as // they exist in ykpiv.h are brought into Go, since we can add some additional // context around them, as well as implement the Error interface. type Error struct { // int representing the underlying error code as ykpiv had given us. The // exact numbers can be found in your local ykpiv.h, inside the ykpiv_rc // enum. Code int // Human readable message regarding what happened. Message string // internal marker to know where this fell out of. it's helpful to know if // this came out of ykpiv_sign_data or ykpiv_done where string } // Check to see if another Error is the same as our struct. This compares // the underlying Code integer. func (e Error) Equal(err error) bool { otherError, ok := err.(Error) if !ok { return false } return e.Code == otherError.Code } // Error interface. This will sprintf a string containing where this error // came from, the human message, and the underlying ykpiv code, to aid with // debugging. func (e Error) Error() string { return fmt.Sprintf("%s: %s (%d) - %s", e.where, e.Message, e.Code, C.GoString(C.ykpiv_strerror(C.ykpiv_rc(e.Code)))) } // Create a helpful mapping between 8 bit integers and the Error that it // belongs to. This is used to look errors up at runtime later. func createErrorLookupMap(errs ...Error) map[int]Error { ret := map[int]Error{} for _, err := range errs { ret[err.Code] = err } return ret } var ( MemoryError = Error{Code: C.YKPIV_MEMORY_ERROR, Message: "Memory Error"} PCSCError = Error{Code: C.YKPIV_PCSC_ERROR, Message: "PKCS Error"} SizeError = Error{Code: C.YKPIV_SIZE_ERROR, Message: "Size Error"} AppletError = Error{Code: C.YKPIV_APPLET_ERROR, Message: "Applet Error"} AuthenticationError = Error{Code: C.YKPIV_AUTHENTICATION_ERROR, Message: "Authentication Error"} RandomnessError = Error{Code: C.YKPIV_RANDOMNESS_ERROR, Message: "Randomness Error"} GenericError = Error{Code: C.YKPIV_GENERIC_ERROR, Message: "Generic Error"} KeyError = Error{Code: C.YKPIV_KEY_ERROR, Message: "Key Error"} ParseError = Error{Code: C.YKPIV_PARSE_ERROR, Message: "Parse Error"} WrongPIN = Error{Code: C.YKPIV_WRONG_PIN, Message: "Wrong PIN"} InvalidObject = Error{Code: C.YKPIV_INVALID_OBJECT, Message: "Invalid Object"} AlgorithmError = Error{Code: C.YKPIV_ALGORITHM_ERROR, Message: "Algorithm Error"} PINLockedError = Error{Code: C.YKPIV_PIN_LOCKED, Message: "PIN Locked"} SecurityStatusError = Error{Code: C.SW_ERR_SECURITY_STATUS, Message: "Security Status Error"} AuthBlocked = Error{Code: C.SW_ERR_AUTH_BLOCKED, Message: "Auth Blocked"} IncorrectParam = Error{Code: C.SW_ERR_INCORRECT_PARAM, Message: "Incorrect Param"} IncorrectSlot = Error{Code: C.SW_ERR_INCORRECT_SLOT, Message: "Incorrect Slot"} errorLookupMap = createErrorLookupMap(MemoryError, PCSCError, SizeError, AppletError, AuthenticationError, RandomnessError, GenericError, KeyError, ParseError, WrongPIN, InvalidObject, AlgorithmError, PINLockedError) swErrorLookupMap = createErrorLookupMap(SecurityStatusError, AuthBlocked, IncorrectParam, IncorrectSlot) ) // Take a ykpiv_rc return code and turn it into a ykpiv.Error. func getError(whoops C.ykpiv_rc, name string) error { if err, ok := errorLookupMap[int(whoops)]; ok { err.where = fmt.Sprintf("ykpiv ykpiv_%s", name) return err } return nil } // Take a ykpiv_rc return code and turn it into a ykpiv.Error. func getSWError(whoops int, name string) error { if err, ok := swErrorLookupMap[int(whoops)]; ok { err.where = fmt.Sprintf("ykpiv sw ykpiv_%s", name) return err } return nil } // vim: foldmethod=marker go-ykpiv-1.3.4/generate.go000066400000000000000000000164531375721631300154260ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv /* #cgo pkg-config: ykpiv #include #include */ import "C" import ( "fmt" "math/big" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rsa" "pault.ag/go/ykpiv/internal/bytearray" ) type TouchPolicy byte const ( TouchPolicyNull TouchPolicy = 0 TouchPolicyNever = TouchPolicy(C.YKPIV_TOUCHPOLICY_NEVER) TouchPolicyAlways = TouchPolicy(C.YKPIV_TOUCHPOLICY_ALWAYS) TouchPolicyCached = TouchPolicy(C.YKPIV_TOUCHPOLICY_CACHED) ) type PinPolicy byte const ( PinPolicyNull PinPolicy = 0 PinPolicyNever = PinPolicy(C.YKPIV_PINPOLICY_NEVER) PinPolicyOnce = PinPolicy(C.YKPIV_PINPOLICY_ONCE) PinPolicyAlways = PinPolicy(C.YKPIV_PINPOLICY_ALWAYS) ) // Decode a DER encoded list of byte arrays into an rsa.PublicKey. func decodeYubikeyRSAPublicKey(der []byte) (*rsa.PublicKey, error) { byteArray, err := bytearray.DERDecode(der) if err != nil { return nil, err } if len(byteArray) != 2 { return nil, fmt.Errorf("ykpiv: decodeYubikeyRSAPublicKey: Byte Array isn't length 2") } n := byteArray[0] if n.Tag != 1 { return nil, fmt.Errorf("ykpiv: decodeYubikeyRSAPublicKey: I'm confused about n: %x", n.Tag) } pubN := big.NewInt(0) pubN.SetBytes(n.Bytes) e := byteArray[1] if e.Tag != 2 { return nil, fmt.Errorf("ykpiv: decodeYubikeyRSAPublicKey: I'm confused about e: %x", e.Tag) } pubE := big.NewInt(0) pubE.SetBytes(e.Bytes) pubKey := rsa.PublicKey{ N: pubN, E: int(pubE.Int64()), } return &pubKey, nil } func decodeYubikeyECPublicKey(curve elliptic.Curve, der []byte) (*ecdsa.PublicKey, error) { byteArray, err := bytearray.DERDecode(der) if err != nil { return nil, err } if len(byteArray) != 1 { return nil, fmt.Errorf("ykpiv: decodeYubikeyECPublicKey: Byte Array isn't length 1") } publicPointSpec := byteArray[0].Bytes if publicPointSpec[0] != 0x04 { return nil, fmt.Errorf("ykpiv: decodeYubikeyECPublicKey: EC public point byte != 4: %d", publicPointSpec[0]) } pointLen := (curve.Params().BitSize + 7) / 8 if len(publicPointSpec) != 2*pointLen+1 { return nil, fmt.Errorf("ykpiv: decodeYubikeyECPublicKey: EC public point bytes wrong length; %d", len(publicPointSpec)) } x := new(big.Int) x.SetBytes(publicPointSpec[1 : 1+pointLen]) y := new(big.Int) y.SetBytes(publicPointSpec[1+pointLen : 1+2*pointLen]) return &ecdsa.PublicKey{ Curve: curve, X: x, Y: y, }, nil } // Generate an RSA Keypair in slot `id` (using a modulus size of `bits`), // and construct a Certificate-less Slot. This Slot can not be recovered // later, so it should be used to sign a CSR or Self-Signed Certificate // before we lose the key material. func (y Yubikey) GenerateRSA(id SlotId, bits int) (*Slot, error) { return y.GenerateRSAWithPolicies(id, bits, PinPolicyNull, TouchPolicyNull) } // The same as GenerateRSAWithPolicies, but with additional parameters to // specify the pin policy and touch policy to be assigned to the generated // key. func (y Yubikey) GenerateRSAWithPolicies(id SlotId, bits int, pinPolicy PinPolicy, touchPolicy TouchPolicy) (*Slot, error) { pubKey, err := y.generateRSAKey(id, bits, pinPolicy, touchPolicy) if err != nil { return nil, err } return &Slot{yubikey: y, Id: id, PublicKey: pubKey}, nil } // Generate an RSA public key on the Yubikey, parse the output and return // a crypto.PublicKey. This will create the key in slot `slot`, with a // modulus size of `bits`. func (y Yubikey) generateRSAKey(slot SlotId, bits int, pinPolicy PinPolicy, touchPolicy TouchPolicy) (crypto.PublicKey, error) { var algorithm byte switch bits { case 1024: algorithm = C.YKPIV_ALGO_RSA1024 case 2048: algorithm = C.YKPIV_ALGO_RSA2048 default: return nil, fmt.Errorf("ykpiv: GenerateRSA: Unknown bit size: %d", bits) } der, err := y.generateKey(slot, algorithm, pinPolicy, touchPolicy) if err != nil { return nil, err } return decodeYubikeyRSAPublicKey(der) } func (y Yubikey) GenerateEC(slot SlotId, bits int) (*Slot, error) { return y.GenerateECWithPolicies(slot, bits, PinPolicyNull, TouchPolicyNull) } func (y Yubikey) GenerateECWithPolicies(slot SlotId, bits int, pinPolicy PinPolicy, touchPolicy TouchPolicy) (*Slot, error) { pubKey, err := y.generateECKey(slot, bits, pinPolicy, touchPolicy) if err != nil { return nil, err } return &Slot{yubikey: y, Id: slot, PublicKey: pubKey}, nil } func (y Yubikey) generateECKey(slot SlotId, bits int, pinPolicy PinPolicy, touchPolicy TouchPolicy) (crypto.PublicKey, error) { var curve elliptic.Curve var algorithm byte switch bits { case 256: curve = elliptic.P256() algorithm = C.YKPIV_ALGO_ECCP256 case 384: curve = elliptic.P384() algorithm = C.YKPIV_ALGO_ECCP384 default: return nil, fmt.Errorf("ykpiv: generateECKey: Unknown bit size: %d", bits) } der, err := y.generateKey(slot, algorithm, pinPolicy, touchPolicy) if err != nil { return nil, err } pubKey, err := decodeYubikeyECPublicKey(curve, der) if err != nil { return nil, err } return pubKey, nil } // This is a low-level binding into the underlying instruction to actually // generate a new asymetric key on the Yubikey. This will create a key of // type `algorithm` (something like C.YKPIV_ALGO_RSA2048) in slot `slot`. // // This will return the raw bytes from the actual Yubikey itself back to // the caller to appropriately parse the output. In the case of RSA keys, // this is a DER encoded series of DER encoded byte arrays for N and E. func (y Yubikey) generateKey(slot SlotId, algorithm byte, pinPolicy PinPolicy, touchPolicy TouchPolicy) ([]byte, error) { inData := []byte{C.YKPIV_ALGO_TAG, 1, algorithm} if pinPolicy != PinPolicyNull { inData = append(inData, C.YKPIV_PINPOLICY_TAG, 1, byte(pinPolicy)) } if touchPolicy != TouchPolicyNull { inData = append(inData, C.YKPIV_TOUCHPOLICY_TAG, 1, byte(touchPolicy)) } // Prepend with 0xAC and length of the inData inData = append([]byte{0xAC, byte(len(inData))}, inData...) sw, data, err := y.transferData( []byte{0x00, byte(C.YKPIV_INS_GENERATE_ASYMMETRIC), 0x00, byte(slot.Key)}, inData, 1024, ) if err != nil { return nil, err } err = getSWError(sw, "transfer_data") if err != nil { return nil, err } return data, nil } // vim: foldmethod=marker go-ykpiv-1.3.4/go.mod000066400000000000000000000002041375721631300143760ustar00rootroot00000000000000module pault.ag/go/ykpiv go 1.13 require ( github.com/aead/ecdh v0.2.0 golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc ) go-ykpiv-1.3.4/go.sum000066400000000000000000000017411375721631300144320ustar00rootroot00000000000000github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ= github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= go-ykpiv-1.3.4/internal/000077500000000000000000000000001375721631300151105ustar00rootroot00000000000000go-ykpiv-1.3.4/internal/bytearray/000077500000000000000000000000001375721631300171125ustar00rootroot00000000000000go-ykpiv-1.3.4/internal/bytearray/bytearray.go000066400000000000000000000043331375721631300214460ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package bytearray import ( "encoding/asn1" ) // Decode will unpack a byte array of DER encoded byte arrays into // asn1.RawValue structs. func Decode(bytes []byte) ([]asn1.RawValue, error) { ret := []asn1.RawValue{} for { rawData := asn1.RawValue{} rest, err := asn1.Unmarshal(bytes, &rawData) if err != nil { return nil, err } ret = append(ret, rawData) if len(rest) == 0 { break } bytes = rest } return ret, nil } // This will DER unpack a byte array, and Decode the nested Byte array // that sits underneath it. func DERDecode(bytes []byte) ([]asn1.RawValue, error) { rawData := asn1.RawValue{} if _, err := asn1.Unmarshal(bytes, &rawData); err != nil { return nil, err } return Decode(rawData.Bytes) } // Take a list of asn1.RawValue structs, Marshal them, and push the combined // array into a byte array to drop out. func Encode(values []asn1.RawValue) ([]byte, error) { ret := []byte{} for _, value := range values { bytes, err := asn1.Marshal(value) if err != nil { return nil, err } ret = append(ret, bytes...) } return ret, nil } // vim: foldmethod=marker go-ykpiv-1.3.4/internal/pkcs1v15/000077500000000000000000000000001375721631300164655ustar00rootroot00000000000000go-ykpiv-1.3.4/internal/pkcs1v15/pad.go000066400000000000000000000045751375721631300175730ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package pkcs1v15 import ( "fmt" ) // PKCS#1 1.5 defines a method to pad data passed into a signing operation // which is (basically) to set some bits at the lower indexes, then a bunch of // 0xFF, finally, a 0x00, then the data until the end of the block. // Pad a message to padLen bytes according to PKCS#1 v1.5 rules func Pad(message []byte, padLen int) []byte { padding := make([]byte, (padLen - 3 - len(message))) for i := 0; i < len(padding); i++ { padding[i] = 0xFF } return expandBytes([]byte{0x00, 0x01}, padding, []byte{0x00}, message) } // Unpad a message according to PKCS#1 v1.5 rules func Unpad(message []byte) ([]byte, error) { for i := 2; i < len(message); i++ { if message[i] == 0x00 { return message[i+1:], nil } // if message[i] != 0xFF { // return nil, fmt.Errorf("ykpiv: pkcs1v15: Invalid padding byte: %x", message[i]) // } } return nil, fmt.Errorf("ykpiv: pkcs1v15: Input does not appear to be in PKCS#1 v 1.5 padded format") } // Take some byte arrays, and return the concatenation of all of those byte // arrays. It's basically like append, but for byte arrays, not bytes. func expandBytes(els ...[]byte) []byte { out := []byte{} for _, el := range els { out = append(out, el...) } return out } // vim: foldmethod=marker go-ykpiv-1.3.4/pivman.go000066400000000000000000000072651375721631300151270ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv /* #cgo pkg-config: ykpiv #include #include */ import "C" import ( "crypto" "golang.org/x/crypto/pbkdf2" "pault.ag/go/ykpiv/internal/bytearray" ) var ( pivmanObjData = 0x5FFF00 /* pivman's source defines this as 0x80, but since we're using an actual * der decoder, we'll see the tag value, which would just be 1 */ pivmanTagFlags1 = 0x01 pivmanTagSalt = 0x02 pivmanTagTimestamp = 0x03 pivmanTagFlags1PUKBlocked = 0x01 ) // Get the salt off the Yubikey PIV token, which is stored in a DER encoded // array of arrays. This salt is a couple of bytes of calming entropy. func (y Yubikey) getSalt() ([]byte, error) { attributes, err := y.getPIVMANAttributes() if err != nil { return nil, err } return attributes[pivmanTagSalt], nil } // Compute the PIVMAN Management Key using 10000 rounds of PBKDF2 SHA1 // utilizing the salt off the Yubikey to derive the 3DES management key. func (y Yubikey) deriveManagementKey() ([]byte, error) { // Description of the Management key derivation can be found on the // Yubikey website: // https://developers.yubico.com/yubikey-piv-manager/PIN_and_Management_Key.html // // Technical description of Key derivation from PIN // // When choosing to use a Management Key derived from the PIN, the following takes place: // // A random 8-byte SALT value is generated and stored on the YubiKey. // // The derived Management Key is calculated as PBKDF2(PIN, SALT, 24, 10000). // // The PBKDF2 function (described in RFC 2898) is run using the PIN // (encoded using UTF-8) as the password, for 10000 rounds, to produce a 24 // byte key, which is used as the management key. Whenever the user changes // the PIN this process is repeated, using a new SALT and the new PIN. pin, err := y.options.GetPIN() if err != nil { return nil, err } salt, err := y.getSalt() if err != nil { return nil, err } return pbkdf2.Key([]byte(pin), salt, 10000, 24, crypto.SHA1.New), nil } // Return a mapping of pivmanTags -> byte arrays. The exact semantics // of this byte array is defined entirely by the tag, and should be treated // as semantically opaque to the user, unless specific parsing code is in place. func (y Yubikey) getPIVMANAttributes() (map[int][]byte, error) { attributes := map[int][]byte{} bytes, err := y.GetObject(pivmanObjData) if err != nil { return nil, err } byteArray, err := bytearray.DERDecode(bytes) if err != nil { return nil, err } for _, rawValue := range byteArray { attributes[rawValue.Tag] = rawValue.Bytes } return attributes, nil } // vim: foldmethod=marker go-ykpiv-1.3.4/sign.go000066400000000000000000000126671375721631300145770ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv /* #cgo pkg-config: ykpiv #include #include */ import "C" import ( "fmt" "io" "unsafe" "crypto" "pault.ag/go/ykpiv/internal/pkcs1v15" ) // It's never a real party until you import both `unsafe`, *and* `crypto`. // PKCS#1 9.2.1 defines a method to push the hash algorithm used into the // digest before the signature. More exactly, we prepend some ASN.1 with // contains the Object ID for the hash algorithm used. Since we know a lot // about the digest and the OID, we can just prefix the digest with some ASN.1 // fresh off the CPU var hashOIDs = map[crypto.Hash][]byte{ crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}, crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c}, crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}, crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}, crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}, } // Sign implements the crypto.Signer.Sign interface. // // Unlike other Sign implementations, `rand` will be completely discarded in // favor of the on-chip RNG. // // The output will be a PKCS#1 v1.5 signature (for RSA) or ECDSA signature (for EC keys) over the digest. func (s Slot) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { algorithm, err := s.getAlgorithm() if err != nil { return nil, err } switch algorithm { case C.YKPIV_ALGO_RSA1024: return s.signRsa(digest, opts, algorithm) case C.YKPIV_ALGO_RSA2048: return s.signRsa(digest, opts, algorithm) case C.YKPIV_ALGO_ECCP256: return s.signEcdsa(digest, opts, algorithm) case C.YKPIV_ALGO_ECCP384: return s.signEcdsa(digest, opts, algorithm) default: return nil, fmt.Errorf("ykpiv: Sign: Unsupported algorithm") } } func (s Slot) signRsa(digest []byte, opts crypto.SignerOpts, algorithm C.uchar) ([]byte, error) { hash := opts.HashFunc() if len(digest) != hash.Size() { return nil, fmt.Errorf("ykpiv: Sign: Digest length doesn't match passed crypto algorithm") } prefix, ok := hashOIDs[hash] if !ok { return nil, fmt.Errorf("ykpiv: Sign: Unsupported algorithm") } digest = append(prefix, digest...) var computedDigest []byte switch algorithm { case C.YKPIV_ALGO_RSA1024: computedDigest = pkcs1v15.Pad(digest, 128) case C.YKPIV_ALGO_RSA2048: computedDigest = pkcs1v15.Pad(digest, 256) default: return nil, fmt.Errorf("ykpiv: Sign: Can't preform padding for signature, unknown algorithm") } var cDigestLen = C.size_t(len(computedDigest)) var cDigest = (*C.uchar)(C.CBytes(computedDigest)) defer C.free(unsafe.Pointer(cDigest)) var cSignatureLen = C.size_t(1024) var cSignature = (*C.uchar)(C.malloc(cSignatureLen)) defer C.free(unsafe.Pointer(cSignature)) if err := getError(C.ykpiv_sign_data( s.yubikey.state, cDigest, cDigestLen, cSignature, &cSignatureLen, algorithm, C.uchar(s.Id.Key), ), "sign_data"); err != nil { return nil, err } return C.GoBytes(unsafe.Pointer(cSignature), C.int(cSignatureLen)), nil } func (s Slot) signEcdsa(digest []byte, opts crypto.SignerOpts, algorithm C.uchar) ([]byte, error) { var curveSizeBytes int switch algorithm { case C.YKPIV_ALGO_ECCP256: curveSizeBytes = 32 case C.YKPIV_ALGO_ECCP384: curveSizeBytes = 48 default: return nil, fmt.Errorf("ykpiv: Sign: Can't perform ECDSA signature, unknown algorithm") } var computedDigest []byte if len(digest) > curveSizeBytes { computedDigest = digest[:curveSizeBytes] } else { computedDigest = digest } var cDigestLen = C.size_t(len(computedDigest)) var cDigest = (*C.uchar)(C.CBytes(computedDigest)) defer C.free(unsafe.Pointer(cDigest)) var cSignatureLen = C.size_t(1024) var cSignature = (*C.uchar)(C.malloc(cSignatureLen)) defer C.free(unsafe.Pointer(cSignature)) if err := getError(C.ykpiv_sign_data( s.yubikey.state, cDigest, cDigestLen, cSignature, &cSignatureLen, algorithm, C.uchar(s.Id.Key), ), "sign_data"); err != nil { return nil, err } return C.GoBytes(unsafe.Pointer(cSignature), C.int(cSignatureLen)), nil } // vim: foldmethod=marker go-ykpiv-1.3.4/slot.go000066400000000000000000000161131375721631300146060ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv /* #cgo pkg-config: ykpiv #include #include */ import "C" import ( "fmt" "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/x509" ) // SlotId encapsulates the Identifiers required to preform key operations // on the Yubikey. The identifier most people would know (if this is a thing // that people do who don't write PIV aware applications) would be the `Key` // Id, something like 0x9A. type SlotId struct { Certificate int32 Key int32 Name string } // Return a human readable string mostly useful for debugging which Slot you // might have your hands on. Since most people (for some value of "most") // would want the Key, this only includes that. func (s SlotId) String() string { return fmt.Sprintf("%s (%x)", s.Name, s.Key) } // More information regarding the basic PIV slots can be founnd at the // FICAM piv-gude: https://piv.idmanagement.gov/elements/ var ( // PIV Authentication, which is a certificate and key pair and can be used // to verify that the PIV credential was issued by an authorized entity, // has not expired, has not been revoked, and holder of the credential // (YOU) is the same individual it was issued to. Authentication SlotId = SlotId{ Certificate: C.YKPIV_OBJ_AUTHENTICATION, Key: C.YKPIV_KEY_AUTHENTICATION, Name: "Authentication", } // Digital Signature, which is a certificate and key pair allows the YOU to // digitally sign a document or email, providing both integrity and // non-repudiation. Signature SlotId = SlotId{ Certificate: C.YKPIV_OBJ_SIGNATURE, Key: C.YKPIV_KEY_SIGNATURE, Name: "Digital Signature", } // Card Authentication, which is a certificate and key pair that can be // used to verify that the PIV credential was issued by an authorized // entity, has not expired, and has not been revoked. CardAuthentication SlotId = SlotId{ Certificate: C.YKPIV_OBJ_CARD_AUTH, Key: C.YKPIV_KEY_CARDAUTH, Name: "Card Authentication", } KeyManagement SlotId = SlotId{ Certificate: C.YKPIV_OBJ_KEY_MANAGEMENT, Key: C.YKPIV_KEY_KEYMGM, Name: "Key Management", } // Attestation, which contains a certificate issued by the Yubico CA // and can be used to attest keys generated in the other slots. // Requires YubiKey 4.3 or later. // NB: if this key or cert is overwritten it cannot be brought back! Attestation SlotId = SlotId{ Certificate: C.YKPIV_OBJ_ATTESTATION, Key: C.YKPIV_KEY_ATTESTATION, Name: "Attestation", } ) // Slot abstracts a public key, private key, and x509 Certificate stored // on the PIV device. // // Internally, this keeps track of the Yubikey this came from, the underlying // object identifiers for the Certificate and Key we care about, as well as // other bits and bobs of state. type Slot struct { yubikey Yubikey Id SlotId PublicKey crypto.PublicKey Certificate *x509.Certificate } // Get the PIV Authentication Slot off the Yubikey. This is identical to // invoking `yubikey.Slot(ykpiv.Authentication)`. func (y Yubikey) Authentication() (*Slot, error) { return y.Slot(Authentication) } // Get the Digital Signature Slot off the Yubikey. This is identical to // invoking `yubikey.Slot(ykpiv.Signature)` func (y Yubikey) Signature() (*Slot, error) { return y.Slot(Signature) } // Get the PIV Card Authentication Slot off the Yubikey. This is identical to // invoking `yubikey.Slot(ykpiv.CardAuthentication)` func (y Yubikey) CardAuthentication() (*Slot, error) { return y.Slot(CardAuthentication) } // Get the PIV Key Management Slot off the Yubikey. This is identical to // invoking `yubikey.Slot(ykpiv.KeyManagement)` func (y Yubikey) KeyManagement() (*Slot, error) { return y.Slot(KeyManagement) } // Get a Slot off of the Yubikey by the SlotId. // // This will trigger an attempt to get (and parse) the x509 Certificate // for this slot. Only slots with an x509 Certificate can be used on // firmware < 4.3. On firmware >= 4.3 any slots with a private key generated // on the Yuibkey can be used if attestation key is present. func (y Yubikey) Slot(id SlotId) (*Slot, error) { /* Right, let's see what we can do here */ slot := Slot{yubikey: y, Id: id} certificate, err := slot.GetCertificate() if err != nil { var errAttest error certificate, errAttest = y.Attest(id) if errAttest != nil { return nil, err } } else { slot.Certificate = certificate } slot.PublicKey = certificate.PublicKey return &slot, nil } // Return the crypto.PublicKey that we know corresponds to the Certificate // we have on hand. func (s Slot) Public() crypto.PublicKey { return s.PublicKey } // Get the Yubikey C.YKPIV_ALGO_* uchar for the key material backing the // slot. func (y Slot) getAlgorithm() (C.uchar, error) { pubKey := y.PublicKey switch pubKey.(type) { case *rsa.PublicKey: rsaPub := pubKey.(*rsa.PublicKey) switch rsaPub.N.BitLen() { case 1024: return C.YKPIV_ALGO_RSA1024, nil case 2048: return C.YKPIV_ALGO_RSA2048, nil default: return C.uchar(0), fmt.Errorf("ykpiv: getAlgorithm: Unknown RSA Modulus size") } case *ecdsa.PublicKey: ecPub := pubKey.(*ecdsa.PublicKey) switch ecPub.Params().BitSize { case 256: return C.YKPIV_ALGO_ECCP256, nil case 384: return C.YKPIV_ALGO_ECCP384, nil default: return C.uchar(0), fmt.Errorf("ykpiv: getAlgorithm: Unknown ECDSA curive size") } default: return C.uchar(0), fmt.Errorf("ykpiv: getAlgorithm: Unknown public key algorithm") } } // Attest the key in this slot and get the attestation certificate func (y Slot) Attest() (*x509.Certificate, error) { return y.yubikey.Attest(y.Id) } // Get the x509.Certificate stored in the PIV Slot off the chip func (y Slot) GetCertificate() (*x509.Certificate, error) { return y.yubikey.GetCertificate(y.Id) } // Write the x509 Certificate to the Yubikey. func (y *Slot) Update(cert x509.Certificate) error { if err := y.yubikey.SaveCertificate(y.Id, cert); err != nil { return err } y.Certificate = &cert return nil } // vim: foldmethod=marker go-ykpiv-1.3.4/tls.go000066400000000000000000000036041375721631300144300ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv import ( "crypto" "crypto/ecdsa" "crypto/tls" ) // Create a tls.Certificate fit for use in crypto/tls applications, // such as net/http, or grpc. func (slot Slot) TLSCertificate() tls.Certificate { var privKey crypto.PrivateKey = slot if _, ok := slot.PublicKey.(*ecdsa.PublicKey); ok { // ECDSA keys don't implement decryption and crypto/tls will return // an error if the private key implements crypto.Decrypter. Hide the // Decrypt() method for EC keys. privKey = struct{ crypto.Signer }{slot} } if slot.Certificate == nil { return tls.Certificate{ PrivateKey: privKey, } } return tls.Certificate{ Certificate: [][]byte{slot.Certificate.Raw}, PrivateKey: privKey, Leaf: slot.Certificate, } } // vim: foldmethod=marker go-ykpiv-1.3.4/ykpiv.go000066400000000000000000000415301375721631300147700ustar00rootroot00000000000000// {{{ Copyright (c) Paul R. Tagliamonte , 2017 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. }}} package ykpiv // XXX: -Wl,--allow-multiple-definition is needed because the test suite fails // when I build it. For now this will keep it quiet :\ /* #cgo pkg-config: ykpiv #include #include */ import "C" import ( "bytes" "crypto" "crypto/rsa" "crypto/x509" "encoding/asn1" "errors" "fmt" "math/big" "unsafe" "pault.ag/go/ykpiv/internal/bytearray" ) // Configuration for initialization of the Yubikey, as well as options that // may be used during runtime. type Options struct { // When true, this will cause the underlying ykpiv library to emit additional // information to stderr. This can be helpful when debugging why something // isn't working as expected. Verbose bool // String to be used when searching for a Yubikey. The comparison // will be against the output you can observe from // `yubico-piv-tool -a list-readers`. Reader string // PIN is the pin that will be used when logging in PIN *string // PUK is the PUK to be used when logging in PUK *string // ManagementKey is the Management Key to be used for key operations ManagementKey []byte // Flag to let ykpiv know if this PIV token has a ManagementKey that was // set by pivman, which is a PBKDF2 SHA1 key derived with a salt held on // chip in the internal pivman data. // // If this is `true`, ManagementKey will be ignored in favor of deriving // the key from the PIN. ManagementKeyIsPIN bool } // Get the Management Key. // // On some configurations, users have set the Management Key to a PBKDF2 // SHA1 key derived from the PIN, so this may return one of two things: // // If `ManagementKeyIsPIN` is false, the `ManagementKey` byte array // will be returned. // // If `ManagementKeyIsPIN` is true, the `PIN` will be used, in conjunction // with a salt held within the PIVMON object address to compute the // ManagementKey. If PIN is nil, this will result in an error being returned. func (o Options) GetManagementKey(y Yubikey) ([]byte, error) { key := o.ManagementKey if o.ManagementKeyIsPIN { var err error key, err = y.deriveManagementKey() if err != nil { return nil, err } } if len(key) == 0 { return nil, fmt.Errorf("ykpiv: GetManagementKey: ManagementKey is empty!") } return key, nil } // Get the user defined PUK. This will return an error if PUK is nil. func (o Options) GetPUK() (string, error) { if o.PUK == nil { return "", fmt.Errorf("ykpiv: GetPUK: No PUK set in Options") } return *(o.PUK), nil } // Get the user defined PIN. This will return an error if PUK is nil. func (o Options) GetPIN() (string, error) { if o.PIN == nil { return "", fmt.Errorf("ykpiv: GetPIN: No PIN set in Options") } return *(o.PIN), nil } // Encapsulation of the ykpiv internal state object, and the configuration // in new. This needs to be initalized through `ykpiv.New` to ensure the // internal state is brought up correctly. // // This object represents a single physical yubikey that we've connected to. // This object provides a number of helper functions hanging off the struct // to avoid keeping and passing the internal ykpiv state object in C. // // `.Close()` must be called, or this will leak memory. type Yubikey struct { state *C.ykpiv_state options Options } // Close the Yubikey object, and preform any finization needed to avoid leaking // memory or holding locks. func (y Yubikey) Close() error { if err := getError(C.ykpiv_disconnect(y.state), "disconnect"); err != nil { return err } return getError(C.ykpiv_done(y.state), "done") } // Write the raw bytes out of a slot stored on the Yubikey. Callers to this // function should only do so if they understand exactly what data they're // writnig, what the data should look like, and avoid rebuilding existing // interfaces if at all possible. // // The related method, GetObject, can be used to read data later. // Care must be taken to ensure the `id` is *not* being used by // any other applications. func (y Yubikey) SaveObject(id int32, data []byte) error { cData := (*C.uchar)(C.CBytes(data)) cDataLen := C.size_t(len(data)) defer C.free(unsafe.Pointer(cData)) return getError(C.ykpiv_save_object( y.state, C.int(id), cData, cDataLen, ), "save_object") } // Get the raw bytes out of a slot stored on the Yubikey. Callers to this // function should only do so if they understand exactly what data they're // reading, what the data should look like, and avoid rebuilding existing // interfaces if at all possible. // // The related method, SaveObject, can be used to write data to be read back // later. Care must be taken to ensure the `id` is *not* being used by // any other applications. func (y Yubikey) GetObject(id int) ([]byte, error) { var cDataLen C.ulong = 4096 var cData *C.uchar = (*C.uchar)(C.malloc(4096)) defer C.free(unsafe.Pointer(cData)) if err := getError(C.ykpiv_fetch_object(y.state, C.int(id), cData, &cDataLen), "fetch_object"); err != nil { return nil, err } return C.GoBytes(unsafe.Pointer(cData), C.int(cDataLen)), nil } // Return the ykpiv application version. This is expected to be in the format of // '1.2.3', but is up to the underlying ykpiv application code. func (y Yubikey) Version() ([]byte, error) { var cVersionLen C.size_t = C.size_t(7) var cVersion *C.char = (*C.char)(C.malloc(cVersionLen)) defer C.free(unsafe.Pointer(cVersion)) if err := getError(C.ykpiv_get_version(y.state, cVersion, cVersionLen), "get_version"); err != nil { return nil, err } return C.GoBytes(unsafe.Pointer(cVersion), C.int(cVersionLen)), nil } func (y Yubikey) Serial() (uint32, error) { serial := C.uint32_t(0) if err := getError(C.ykpiv_get_serial(y.state, &serial), "get_serial"); err != nil { return 0, err } return uint32(serial), nil } func (y Yubikey) verify(cPin *C.char) (int, error) { tries := C.int(0) err := getError(C.ykpiv_verify(y.state, cPin, &tries), "verify") if cPin == nil && WrongPIN.Equal(err) { return int(tries), nil } if err != nil { return -1, err } return int(tries), nil } // PIN Retries func (y Yubikey) PINRetries() (int, error) { return y.verify(nil) } // Log into the Yubikey using the user PIN. func (y Yubikey) Login() error { pin, err := y.options.GetPIN() if err != nil { return err } cPin := (*C.char)(C.CString(pin)) defer C.free(unsafe.Pointer(cPin)) _, err = y.verify(cPin) return err } // Using the PUK, unblock the PIN, resetting the retry counter. func (y Yubikey) UnblockPIN(newPin string) error { tries := C.int(0) puk, err := y.options.GetPUK() if err != nil { return err } cPuk := (*C.char)(C.CString(puk)) cPukLen := C.size_t(len(puk)) defer C.free(unsafe.Pointer(cPuk)) cPin := (*C.char)(C.CString(newPin)) cPinLen := C.size_t(len(newPin)) defer C.free(unsafe.Pointer(cPin)) return getError(C.ykpiv_unblock_pin( y.state, cPuk, cPukLen, cPin, cPinLen, &tries, ), "change_puk") } // Change the PUK. func (y Yubikey) ChangePUK(newPuk string) error { if y.options.ManagementKeyIsPIN { return fmt.Errorf("ykpiv: ChangePIN: Please change your PUK through pivman") } tries := C.int(0) puk, err := y.options.GetPUK() if err != nil { return err } cOldPuk := (*C.char)(C.CString(puk)) cOldPukLen := C.size_t(len(puk)) defer C.free(unsafe.Pointer(cOldPuk)) cNewPuk := (*C.char)(C.CString(newPuk)) cNewPukLen := C.size_t(len(newPuk)) defer C.free(unsafe.Pointer(cNewPuk)) return getError(C.ykpiv_change_puk( y.state, cOldPuk, cOldPukLen, cNewPuk, cNewPukLen, &tries, ), "change_puk") } // Set the Yubikey Management Key. The Management key is a 24 byte // key that's used as a 3DES key internally to preform key operations, // such as Certificate import, or keypair generation. func (y Yubikey) SetMGMKey(key []byte) error { if y.options.ManagementKeyIsPIN { return fmt.Errorf("ykpiv: ChangePIN: Please change your Management Key through pivman") } cMgmKey := (*C.uchar)(C.CBytes(key)) defer C.free(unsafe.Pointer(cMgmKey)) return getError(C.ykpiv_set_mgmkey(y.state, cMgmKey), "set_mgmkey") } // Change your PIN on the Yubikey from the oldPin to the newPin. func (y Yubikey) ChangePIN(oldPin, newPin string) error { if y.options.ManagementKeyIsPIN { return fmt.Errorf("ykpiv: ChangePIN: Please change your PIN through pivman") } tries := C.int(0) cOldPin := (*C.char)(C.CString(oldPin)) cOldPinLen := C.size_t(len(oldPin)) defer C.free(unsafe.Pointer(cOldPin)) cNewPin := (*C.char)(C.CString(newPin)) cNewPinLen := C.size_t(len(newPin)) defer C.free(unsafe.Pointer(cNewPin)) return getError(C.ykpiv_change_pin( y.state, cOldPin, cOldPinLen, cNewPin, cNewPinLen, &tries, ), "change_pin") } // Attest returns an *x509.Certificate attesting the key in slotId. // Use it with the attestation certificate in the Attestation slot // and the Yubico PIV Root CA certificate to verify attestation. func (y Yubikey) Attest(slotId SlotId) (*x509.Certificate, error) { var cDataLen C.size_t = 4096 var cData *C.uchar = (*C.uchar)(C.malloc(4096)) defer C.free(unsafe.Pointer(cData)) if err := getError(C.ykpiv_attest(y.state, C.uchar(slotId.Key), cData, &cDataLen), "attest"); err != nil { return nil, err } return x509.ParseCertificate(C.GoBytes(unsafe.Pointer(cData), C.int(cDataLen))) } // Authenticate to the Yubikey using the Management Key. This key is a 24 byte // key that's used as a 3DES key internally to write new Certificates, or // create a new keypair. func (y Yubikey) Authenticate() error { managementKey, err := y.options.GetManagementKey(y) if err != nil { return err } cKey := (*C.uchar)(C.CBytes(managementKey)) defer C.free(unsafe.Pointer(cKey)) return getError(C.ykpiv_authenticate(y.state, cKey), "authenticate") } // sw, data, error func (y Yubikey) transferData( template []byte, input []byte, maxReturnSize int, ) (int, []byte, error) { sw := C.int(0) cInputLen := C.long(len(input)) cInput := (*C.uchar)(C.CBytes(input)) defer C.free(unsafe.Pointer(cInput)) cDataLen := C.ulong(maxReturnSize) cData := (*C.uchar)(C.malloc(C.size_t(cDataLen))) defer C.free(unsafe.Pointer(cData)) cTemplate := (*C.uchar)(C.CBytes(template)) defer C.free(unsafe.Pointer(cTemplate)) if err := getError(C.ykpiv_transfer_data( y.state, cTemplate, cInput, cInputLen, cData, &cDataLen, &sw, ), "transfer_data"); err != nil { return 0, nil, err } return int(sw), C.GoBytes(unsafe.Pointer(cData), C.int(cDataLen)), nil } // Reset the Yubikey. // // This can only be done if both the PIN and PUK have been blocked, and will // wipe all data on the Key. This includes all Certificates, public and private // key material. func (y Yubikey) Reset() error { template := []byte{0, C.YKPIV_INS_RESET, 0, 0} sw, _, err := y.transferData(template, nil, 128) if err != nil { return err } return getSWError(sw, "transfer_data") } // ImportKey function imports a private key to the specified slot func (y Yubikey) ImportKey(slotID SlotId, privKey crypto.PrivateKey) (*Slot, error) { var rsaPrivKey rsa.PrivateKey switch privKey.(type) { case rsa.PrivateKey: rsaPrivKey = privKey.(rsa.PrivateKey) case *rsa.PrivateKey: rsaPrivKey = *(privKey.(*rsa.PrivateKey)) default: return nil, errors.New("ykpiv: ImportKey: non RSA key importing not supported yet") } var algorithm byte switch rsaPrivKey.N.BitLen() { case 1024: algorithm = C.YKPIV_ALGO_RSA1024 case 2048: algorithm = C.YKPIV_ALGO_RSA2048 default: return nil, fmt.Errorf("ykpiv: ImportKey: Unusable key of %d bits, only 1024 and 2048 are supported", rsaPrivKey.N.BitLen()) } e := big.NewInt(int64(rsaPrivKey.PublicKey.E)).Bytes() p := rsaPrivKey.Primes[0].Bytes() q := rsaPrivKey.Primes[1].Bytes() dp := rsaPrivKey.Precomputed.Dp.Bytes() dq := rsaPrivKey.Precomputed.Dq.Bytes() qinv := rsaPrivKey.Precomputed.Qinv.Bytes() elLen := rsaPrivKey.N.BitLen() / 16 var _p, _q, _dp, _dq, _qinv *C.uchar if len(e) != 3 || e[0] != 0x01 || e[1] != 0x00 || e[2] != 0x01 { return nil, errors.New("ykpiv: ImportKey: Invalid public exponent (E) for import (only 0x10001 supported)") } if len(p) <= elLen { _p = (*C.uchar)(C.CBytes(p)) defer C.free(unsafe.Pointer(_p)) } else { return nil, errors.New("ykpiv: ImportKey: Failed setting P component") } if len(q) <= elLen { _q = (*C.uchar)(C.CBytes(q)) defer C.free(unsafe.Pointer(_q)) } else { return nil, errors.New("ykpiv: ImportKey: Failed setting Q component") } if len(dp) <= elLen { _dp = (*C.uchar)(C.CBytes(dp)) defer C.free(unsafe.Pointer(_dp)) } else { return nil, errors.New("ykpiv: ImportKey: Failed setting DP component") } if len(dq) <= elLen { _dq = (*C.uchar)(C.CBytes(dq)) defer C.free(unsafe.Pointer(_dq)) } else { return nil, errors.New("ykpiv: ImportKey: Failed setting DQ component") } if len(qinv) <= elLen { _qinv = (*C.uchar)(C.CBytes(qinv)) defer C.free(unsafe.Pointer(_qinv)) } else { return nil, errors.New("ykpiv: ImportKey: Failed setting QINV component") } if err := getError(C.ykpiv_import_private_key( y.state, C.uchar(slotID.Key), C.uchar(algorithm), _p, C.size_t(len(p)), _q, C.size_t(len(q)), _dp, C.size_t(len(dp)), _dq, C.size_t(len(dq)), _qinv, C.size_t(len(qinv)), nil, C.uchar(0), C.uchar(0), C.uchar(0), ), "import_private_key"); err != nil { return nil, err } return &Slot{yubikey: y, Id: slotID, PublicKey: &rsaPrivKey.PublicKey}, nil } // Write the x509 Certificate to the Yubikey. func (y Yubikey) SaveCertificate(slotId SlotId, cert x509.Certificate) error { certDer, err := bytearray.Encode([]asn1.RawValue{ asn1.RawValue{Tag: 0x10, IsCompound: true, Class: 0x01, Bytes: cert.Raw}, asn1.RawValue{Tag: 0x11, IsCompound: true, Class: 0x01, Bytes: []byte{0x00}}, asn1.RawValue{Tag: 0x1E, IsCompound: true, Class: 0x03, Bytes: []byte{}}, }) if err != nil { return err } return y.SaveObject(slotId.Certificate, certDer) } func (y Yubikey) GetCertificate(slotId SlotId) (*x509.Certificate, error) { bytes, err := y.GetObject(int(slotId.Certificate)) if err != nil { return nil, err } objects, err := bytearray.Decode(bytes) if err != nil { return nil, err } if len(objects) != 3 && slotId != Attestation { return nil, fmt.Errorf("ykpiv: GetCertificate: We expected two der byte arrays from the key") } return x509.ParseCertificate(objects[0].Bytes) } // Create a new Yubikey. // // This will use the options in the given `ykpiv.Options` struct to // find the correct Yubikey, initialize the underlying state, and ensure // the right bits are set. func New(opts Options) (*Yubikey, error) { var state *C.ykpiv_state yubikey := Yubikey{ state: state, options: opts, } verbosity := 0 if opts.Verbose { verbosity = 1 } if err := getError(C.ykpiv_init(&yubikey.state, C.int(verbosity)), "init"); err != nil { return nil, err } something := C.CString(opts.Reader) defer C.free(unsafe.Pointer(something)) if err := getError(C.ykpiv_connect(yubikey.state, something), "connect"); err != nil { return nil, err } return &yubikey, nil } // Get a list of strings that the ykpiv library has identified as unique ways // to fetch every reader attached to the system. This can be handy to define a // "Reader" argument in ykpiv.Options, and may include things ykpiv can't talk // to. func Readers() ([]string, error) { var state *C.ykpiv_state if err := getError(C.ykpiv_init(&state, C.int(0)), "init"); err != nil { return nil, err } var cReadersLen = C.size_t(2048) var cReaders *C.char = (*C.char)(C.malloc(cReadersLen)) defer C.free(unsafe.Pointer(cReaders)) if err := getError(C.ykpiv_list_readers(state, cReaders, &cReadersLen), "list_readers"); err != nil { return nil, err } readerBytes := C.GoBytes(unsafe.Pointer(cReaders), C.int(cReadersLen)) readers := []string{} for _, reader := range bytes.Split(readerBytes, []byte{0x00}) { if len(reader) == 0 { continue } readers = append(readers, string(reader)) } if err := getError(C.ykpiv_done(state), "done"); err != nil { return nil, err } return readers, nil } // vim: foldmethod=marker go-ykpiv-1.3.4/ykpiv_test.go000066400000000000000000000337301375721631300160320ustar00rootroot00000000000000/* {{{ Copyright (c) Paul R. Tagliamonte , 2017 * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. }}} */ package ykpiv_test import ( "bytes" "crypto" "fmt" "hash" "io" "net" "os" "testing" "time" "encoding/asn1" "math/big" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/sha512" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "github.com/aead/ecdh" "pault.ag/go/ykpiv" ) func isok(t *testing.T, err error) { t.Helper() if err != nil && err != io.EOF { t.Fatalf("Error! Error is not nil! %s", err) } } func notok(t *testing.T, err error) { t.Helper() if err == nil { t.FailNow() t.Fatal("Error! Error is nil!") } } func assert(t *testing.T, expr bool, what string) { t.Helper() if !expr { t.Fatalf("Assertion failed: %s", what) } } func isDestructive() { if os.Getenv("YKPIV_YES_DESTROY_MY_KEY") == "" { panic("export YKPIV_YES_DESTROY_MY_KEY=true # if you want to test this code on a Key") } if err := wipeYubikey(); err != nil { panic(err) } } func TestImportKey(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) privKey, err := rsa.GenerateKey(rand.Reader, 2048) isok(t, err) slot, err := yubikey.ImportKey(ykpiv.KeyManagement, privKey) isok(t, err) plaintext := []byte("Well ain't this dandy") encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, &privKey.PublicKey, plaintext) isok(t, err) decrypted, err := slot.Decrypt(rand.Reader, encrypted, nil) isok(t, err) assert(t, bytes.Equal(plaintext, decrypted), "Plaintexts don't match") template := certificateTemplate() derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, slot.PublicKey, slot) isok(t, err) cert, err := x509.ParseCertificate(derBytes) isok(t, err) err = slot.Update(*cert) isok(t, err) slot, err = yubikey.KeyManagement() isok(t, err) assert(t, slot.Certificate.Subject.CommonName == template.Subject.CommonName, "Certificate common names are doesn't match") hasher := sha512.New() _, err = hasher.Write(plaintext) isok(t, err) hashed := hasher.Sum(plaintext[:0]) signature, err := slot.Sign(nil, hashed, crypto.SHA512) isok(t, err) err = rsa.VerifyPKCS1v15(&privKey.PublicKey, crypto.SHA512, hashed, signature) isok(t, err) } func getYubikey(PIN, PUK string) (*ykpiv.Yubikey, func() error, error) { yk, err := ykpiv.New(ykpiv.Options{ Reader: yubikeyReaderName, PIN: &PIN, PUK: &PUK, ManagementKeyIsPIN: false, ManagementKey: []byte{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, }, }) if err != nil { return nil, nil, err } return yk, yk.Close, nil } func wipeYubikey() error { yubikey, closer, err := getYubikey("654321", "87654321") if err != nil { return err } defer closer() retries, err := yubikey.PINRetries() if err != nil { return err } for i := 0; i < retries; i++ { yubikey.Login() } retries, _ = yubikey.PINRetries() if retries != 0 { return fmt.Errorf("Error wiping Yubikey") } yubikey.ChangePUK("87654321") yubikey.ChangePUK("87654321") yubikey.ChangePUK("87654321") return yubikey.Reset() } var yubikeyReaderName = "Yubikey" var defaultPIN = "123456" var defaultPUK = "12345678" var allSlots = []ykpiv.SlotId{ ykpiv.Authentication, ykpiv.Signature, ykpiv.KeyManagement, ykpiv.CardAuthentication, } func TestReader(t *testing.T) { readers, err := ykpiv.Readers() isok(t, err) assert(t, len(readers) != 0, "No readers found") } func certificateTemplate() x509.Certificate { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { panic(err) } notBefore := time.Now() notAfter := notBefore.Add(time.Hour * 24) return x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ CommonName: "p̶͕͉̟ͅḁ̲̳̕u̪̬̯̗͎͡l̷͍͎̤̠t̥̗͞ag", Organization: []string{"go-ykpiv"}, }, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageKeyAgreement, BasicConstraintsValid: true, } } func TestUpdate(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) slotFunc := map[ykpiv.SlotId]func() (*ykpiv.Slot, error){ ykpiv.Authentication: yubikey.Authentication, ykpiv.Signature: yubikey.Signature, ykpiv.KeyManagement: yubikey.KeyManagement, ykpiv.CardAuthentication: yubikey.CardAuthentication, } for _, slotId := range allSlots { slot, err := yubikey.GenerateRSA(slotId, 1024) isok(t, err) // When using "Digital Signature" slot, PIN must be provided every time. if slot.Id == ykpiv.Signature { isok(t, yubikey.Login()) } template := certificateTemplate() derCertificate, err := x509.CreateCertificate(rand.Reader, &template, &template, slot.PublicKey, slot) isok(t, err) certificate, err := x509.ParseCertificate(derCertificate) isok(t, err) isok(t, slot.Update(*certificate)) slot1, err := slotFunc[slot.Id]() isok(t, err) assert( t, slot1.Certificate.Subject.CommonName == template.Subject.CommonName, "Common Name is wrong", ) // Now, let's assert it's not what we're going to check next. assert( t, slot1.Certificate.Subject.CommonName != "paultag", "Common Name is wrong", ) if slot.Id == ykpiv.Signature { isok(t, yubikey.Login()) } template.Subject.CommonName = "paultag" derCertificate, err = x509.CreateCertificate(rand.Reader, &template, &template, slot.PublicKey, slot) isok(t, err) certificate, err = x509.ParseCertificate(derCertificate) isok(t, err) isok(t, slot.Update(*certificate)) slot2, err := slotFunc[slot.Id]() isok(t, err) assert( t, slot2.Certificate.Subject.CommonName == "paultag", "Common Name is wrong", ) } } func TestAttestation(t *testing.T) { // NB: this will fail if the Yubico CA-signed attestation cert // has been overwritten. isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) attestationCert, err := yubikey.GetCertificate(ykpiv.Attestation) isok(t, err) slot := ykpiv.Authentication _, err = yubikey.GenerateEC(slot, 256) isok(t, err) attestedCert, err := yubikey.Attest(slot) isok(t, err) // instead of maintaining a bundle of Yubico CA certs here, we'll just // set the attestation cert as the root and mark it as valid for signing // this won't check the full chain, just that the attestation cert can // verify the attested cert attestationCert.BasicConstraintsValid = true attestationCert.IsCA = true opts := x509.VerifyOptions{ Roots: x509.NewCertPool(), } opts.Roots.AddCert(attestationCert) _, err = attestedCert.Verify(opts) isok(t, err) } func TestGenerateRSAEncryption(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) slot, err := yubikey.GenerateRSA(ykpiv.Authentication, 1024) isok(t, err) assert(t, slot.PublicKey.(*rsa.PublicKey).N.BitLen() == 1024, "BitLen is wrong") plaintext := []byte("Well ain't this dandy") ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, slot.PublicKey.(*rsa.PublicKey), plaintext) isok(t, err) computedPlaintext, err := slot.Decrypt(rand.Reader, ciphertext, nil) isok(t, err) assert(t, bytes.Compare(plaintext, computedPlaintext) == 0, "Plaintexts don't match") } func TestGenerateRSA1024(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() for _, slotId := range allSlots { isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) slot, err := yubikey.GenerateRSA(slotId, 1024) isok(t, err) assert(t, slot.PublicKey.(*rsa.PublicKey).N.BitLen() == 1024, "BitLen is wrong") } } func TestWriteSaveCycle(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() yubikey.Login() yubikey.Authenticate() isok(t, yubikey.SaveObject(0x5FCAFE, []byte("p̶͕͉̟ͅḁ̲̳̕u̪̬̯̗͎͡l̷͍͎̤̠t̥̗͞ag"))) whoami, err := yubikey.GetObject(0x5FCAFE) isok(t, err) assert(t, bytes.Compare(whoami, []byte("p̶͕͉̟ͅḁ̲̳̕u̪̬̯̗͎͡l̷͍͎̤̠t̥̗͞ag")) == 0, "get object returns good data") } func TestGenerateRSA2048(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() for _, slotId := range allSlots { isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) slot, err := yubikey.GenerateRSA(slotId, 2048) isok(t, err) assert(t, slot.PublicKey.(*rsa.PublicKey).N.BitLen() == 2048, "BitLen is wrong") } } func TestSignEC(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() for _, bits := range []int{256, 384} { for _, slotId := range allSlots { for _, hf := range []struct { newh func() hash.Hash hash crypto.Hash }{ {sha512.New, crypto.SHA512}, {sha256.New, crypto.SHA256}, } { isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) slot, err := yubikey.GenerateEC(slotId, bits) isok(t, err) h := hf.newh() _, err = h.Write([]byte("test")) isok(t, err) digest := h.Sum(nil) // When using "Digital Signature" slot, PIN must be provided every time. if slotId == ykpiv.Signature { isok(t, yubikey.Login()) } sig, err := slot.Sign(nil, digest[:], hf.hash) isok(t, err) pubKey, ok := slot.PublicKey.(*ecdsa.PublicKey) assert(t, ok, "invalid public key type") R, S := decodeSig(t, sig) ok = ecdsa.Verify(pubKey, digest[:], R, S) assert(t, ok, "ECDSA verification failed") } } } } func TestTLSCertificate(t *testing.T) { isDestructive() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) tmpl := &x509.Certificate{ Subject: pkix.Name{CommonName: "my-server"}, SerialNumber: big.NewInt(1000), NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, DNSNames: []string{"pipe"}, } slot, err := yubikey.GenerateEC(ykpiv.Authentication, 256) isok(t, err) certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, slot.PublicKey, slot) isok(t, err) cert, err := x509.ParseCertificate(certDER) isok(t, err) isok(t, slot.Update(*cert)) certPool := x509.NewCertPool() certPool.AddCert(cert) clientConn, serverConn := net.Pipe() defer clientConn.Close() defer serverConn.Close() c := tls.Client(clientConn, &tls.Config{ServerName: "pipe", RootCAs: certPool}) s := tls.Server(serverConn, &tls.Config{ Certificates: []tls.Certificate{slot.TLSCertificate()}, }) errc := make(chan error) go func() { errc <- c.Handshake() }() go func() { errc <- s.Handshake() }() isok(t, <-errc) isok(t, <-errc) } func decodeSig(t *testing.T, sig []byte) (R *big.Int, S *big.Int) { t.Helper() rawData := asn1.RawValue{} _, err := asn1.Unmarshal(sig, &rawData) isok(t, err) RB := asn1.RawValue{} rest, err := asn1.Unmarshal(rawData.Bytes, &RB) isok(t, err) assert(t, len(rest) != 0, "S missing") SB := asn1.RawValue{} rest, err = asn1.Unmarshal(rest, &SB) assert(t, len(rest) == 0, "unexpected extra data") R = new(big.Int) R.SetBytes(RB.Bytes) S = new(big.Int) S.SetBytes(SB.Bytes) return } func TestMain(m *testing.M) { isDestructive() os.Exit(m.Run()) } func TestECDH(t *testing.T) { isDestructive() curve := elliptic.P256() yubikey, closer, err := getYubikey(defaultPIN, defaultPUK) isok(t, err) defer closer() isok(t, yubikey.Login()) isok(t, yubikey.Authenticate()) slotid := ykpiv.KeyManagement slot, err := yubikey.GenerateEC(slotid, curve.Params().BitSize) isok(t, err) //Create the other side for testing keyEx := ecdh.Generic(curve) peerPrivate, peerPublic, err := keyEx.GenerateKey(rand.Reader) isok(t, err) yubiPublic := slot.Public().(*ecdsa.PublicKey) yubiPublicAsPoint := ecdh.Point{X: yubiPublic.X, Y: yubiPublic.Y} //Get secret as computed by peer expectedSecret := keyEx.ComputeSecret(peerPrivate, yubiPublicAsPoint) //Convert peer's public key into the format required by ykpiv pt := peerPublic.(ecdh.Point) peerPublicOctet := elliptic.Marshal(curve, pt.X, pt.Y) //Perform ECDH on the yubikey computedSecret, err := slot.Decrypt(rand.Reader, peerPublicOctet, nil) isok(t, err) //Check the two shared secrets match assert(t, bytes.Equal(expectedSecret, computedSecret), "incorrect ECDH secret") } // vim: foldmethod=marker