Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

List keys extensions #147

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ $ git config --get user.email
$ smimesign --list-keys
```

**Tell git which key to use for signing**

`smimesign --list-keys` might list more than one suitable signing key. In this case you will have to tell git which key to use. This can be done through the `user.signingkey` configuration key:

```bash
$ git config --global user.signingkey <Key-ID>
```

Of course the configuration can also be done `--local` only.

## Smart cards (PIV/CAC/Yubikey)

Many large organizations and government agencies distribute certificates and keys to end users via smart cards. These cards allow applications on the user's computer to use private keys for signing or encryption without giving them the ability to export those keys. The native certificate stores on both Windows and macOS can talk to smart cards, though special drivers or middleware may be required.
Expand Down
11 changes: 6 additions & 5 deletions certstore/certstore_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ const (
// API will be used.
//
// Possible values are:
// 0x00000000 — — Only use CryptoAPI.
// 0x00010000 — CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG — Prefer CryptoAPI.
// 0x00020000 — CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG — Prefer CNG.
// 0x00040000 — CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG — Only uyse CNG.
//
// 0x00000000 — — Only use CryptoAPI.
// 0x00010000 — CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG — Prefer CryptoAPI.
// 0x00020000 — CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG — Prefer CNG.
// 0x00040000 — CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG — Only uyse CNG.
var winAPIFlag C.DWORD = C.CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG

// winStore is a wrapper around a C.HCERTSTORE.
Expand Down Expand Up @@ -637,7 +638,7 @@ func (c errCode) Error() string {
if cmsg == nil {
return fmt.Sprintf("Error %X", int(c))
}
defer C.LocalFree(C.HLOCAL(cmsg))
defer C.LocalFree(C.HLOCAL(unsafe.Pointer(cmsg)))

gomsg := C.GoString(cmsg)

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module github.com/github/smimesign
go 1.12

require (
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pborman/getopt v0.0.0-20180811024354-2b5b3bfb099b
github.com/pkg/errors v0.8.1
github.com/stretchr/testify v1.3.0
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
golang.org/x/crypto v0.17.0
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)
44 changes: 38 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 h1:6/yVvBsKeAw05IUj4AzvrxaCnDjN4nUqKjW9+w5wixg=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -12,12 +12,44 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
24 changes: 19 additions & 5 deletions list_keys_command.go
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
package main

import (
"crypto/x509"
"fmt"
"os"
"strings"
"time"

"github.com/pkg/errors"
)

func commandListKeys() error {
for j, ident := range idents {
if j > 0 {
fmt.Print("\n")
}
func commandListKeys(showexp bool, onlydigisig bool) error {
for _, ident := range idents {

cert, err := ident.Certificate()
if err != nil {
fmt.Fprintln(os.Stderr, "WARNING:", errors.Wrap(err, "failed to get identity certificate"))
continue
}

if !showexp {
if cert.NotAfter.Before(time.Now()) {
continue
}
}

if onlydigisig {
if (int(cert.KeyUsage) & int(x509.KeyUsageDigitalSignature)) == 0 {
continue
}
}

fmt.Println(" ID:", certHexFingerprint(cert))
fmt.Println(" S/N:", cert.SerialNumber.Text(16))
fmt.Println("Algorithm:", cert.SignatureAlgorithm.String())
fmt.Println(" Validity:", cert.NotBefore.String(), "-", cert.NotAfter.String())
fmt.Println(" Issuer:", cert.Issuer.ToRDNSequence().String())
fmt.Println(" Subject:", cert.Subject.ToRDNSequence().String())
fmt.Println(" Emails:", strings.Join(certEmails(cert), ", "))
fmt.Println("Key Usage:", strings.Join(keyUsageToNames(cert.KeyUsage), ", "))
fmt.Println("Ext.Usage:", strings.Join(certExtKeyUsages(cert), ", "))
fmt.Print("\n")
}

return nil
Expand Down
12 changes: 11 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ var (
keyFormatOpt = getopt.EnumLong("keyid-format", 0, []string{"long"}, "long", "select how to display key IDs.", "{long}")
tsaOpt = getopt.StringLong("timestamp-authority", 't', defaultTSA, "URL of RFC3161 timestamp authority to use for timestamping", "url")
includeCertsOpt = getopt.IntLong("include-certs", 0, -2, "-3 is the same as -2, but ommits issuer when cert has Authority Information Access extension. -2 includes all certs except root. -1 includes all certs. 0 includes no certs. 1 includes leaf cert. >1 includes n from the leaf. Default -2.", "n")
showExpiredOpt = getopt.BoolLong("show-expired", 'e', "Also show expired certificates in --list-keys output (default is not to show expired certificates)")
onlyDigiSigOpt = getopt.BoolLong("only-digisig", 0, "Show only certificates that include Key Usage 'Digital Signature' in --list-keys output")

// Remaining arguments
fileArgs []string
Expand Down Expand Up @@ -93,6 +95,10 @@ func runCommand() error {
return errors.New("specify --help, --sign, --verify, or --list-keys")
} else if len(*localUserOpt) == 0 {
return errors.New("specify a USER-ID to sign with")
} else if *showExpiredOpt {
return errors.New("show-expired cannot be specified for signing")
} else if *onlyDigiSigOpt {
return errors.New("only-digisig cannot be specified for signing")
} else {
return commandSign()
}
Expand All @@ -105,6 +111,10 @@ func runCommand() error {
return errors.New("local-user cannot be specified for verification")
} else if *detachSignFlag {
return errors.New("detach-sign cannot be specified for verification")
} else if *showExpiredOpt {
return errors.New("show-expired cannot be specified for verification")
} else if *onlyDigiSigOpt {
return errors.New("only-digisig cannot be specified for verification")
} else if *armorFlag {
return errors.New("armor cannot be specified for verification")
} else {
Expand All @@ -122,7 +132,7 @@ func runCommand() error {
} else if *armorFlag {
return errors.New("armor cannot be specified for list-keys")
} else {
return commandListKeys()
return commandListKeys(*showExpiredOpt, *onlyDigiSigOpt)
}
}

Expand Down
73 changes: 73 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,76 @@ func certEmails(cert *x509.Certificate) []string {

return emails
}

// keyUsageNames contains the mapping between a KeyUsage and its Name.
var keyUsageNames = []struct {
keyUsage x509.KeyUsage
name string
}{
{x509.KeyUsageDigitalSignature, "DigitalSignature"},
{x509.KeyUsageContentCommitment, "ContentCommitment"},
{x509.KeyUsageKeyEncipherment, "KeyEncipherment"},
{x509.KeyUsageDataEncipherment, "DataEncipherment"},
{x509.KeyUsageKeyAgreement, "KeyAgreement"},
{x509.KeyUsageCertSign, "CertSign"},
{x509.KeyUsageCRLSign, "CRLSign"},
{x509.KeyUsageEncipherOnly, "EncipherOnly"},
{x509.KeyUsageDecipherOnly, "DecipherOnly"},
}

func keyUsageToNames(ku x509.KeyUsage) []string {

var kus []string

for _, k2n := range keyUsageNames {
if !(int(k2n.keyUsage)&int(ku) == 0) {
kus = append(kus, k2n.name)
}
}

return kus
}

// extKeyUsageNames contains the mapping between an ExtKeyUsage and its Name.
var extKeyUsageNames = []struct {
extKeyUsage x509.ExtKeyUsage
name string
}{
{x509.ExtKeyUsageAny, "Any"},
{x509.ExtKeyUsageServerAuth, "ServerAuth"},
{x509.ExtKeyUsageClientAuth, "ClientAuth"},
{x509.ExtKeyUsageCodeSigning, "CodeSigning"},
{x509.ExtKeyUsageEmailProtection, "EmailProtection"},
{x509.ExtKeyUsageIPSECEndSystem, "IPSECEndSystem"},
{x509.ExtKeyUsageIPSECTunnel, "IPSECTunnel"},
{x509.ExtKeyUsageIPSECUser, "IPSECUser"},
{x509.ExtKeyUsageTimeStamping, "TimeStamping"},
{x509.ExtKeyUsageOCSPSigning, "OCSPSigning"},
{x509.ExtKeyUsageMicrosoftServerGatedCrypto, "MicrosoftServerGatedCrypto"},
{x509.ExtKeyUsageNetscapeServerGatedCrypto, "NetscapeServerGatedCrypto"},
{x509.ExtKeyUsageMicrosoftCommercialCodeSigning, "MicrosoftCommercialCodeSigning"},
{x509.ExtKeyUsageMicrosoftKernelCodeSigning, "MicrosoftKernelCodeSigning"},
}

// extKeyUsageToName take an ExtKeyUsage and returns its name
func extKeyUsageToName(eku x509.ExtKeyUsage) (name string) {
for _, pair := range extKeyUsageNames {
if eku == pair.extKeyUsage {
return pair.name
}
}
return
}

// certExtKeyUsages extracts Key Usage fields from a certificate and returns them as string.
func certExtKeyUsages(cert *x509.Certificate) []string {

var ekus []string

for _, eku := range cert.ExtKeyUsage {
ekus = append(ekus, extKeyUsageToName(eku))
}

return ekus

}