diff --git a/README.md b/README.md index dddf30a..9880531 100644 --- a/README.md +++ b/README.md @@ -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 +``` + +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. diff --git a/certstore/certstore_windows.go b/certstore/certstore_windows.go index 86c0b1c..c22e73f 100644 --- a/certstore/certstore_windows.go +++ b/certstore/certstore_windows.go @@ -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. @@ -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) diff --git a/go.mod b/go.mod index ba48589..f4bc146 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 0ce1304..8b13d1e 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= diff --git a/list_keys_command.go b/list_keys_command.go index a8369a4..368657d 100644 --- a/list_keys_command.go +++ b/list_keys_command.go @@ -1,18 +1,17 @@ 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 { @@ -20,6 +19,18 @@ func commandListKeys() error { 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()) @@ -27,6 +38,9 @@ func commandListKeys() error { 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 diff --git a/main.go b/main.go index cc24cce..fb1220c 100644 --- a/main.go +++ b/main.go @@ -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 @@ -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() } @@ -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 { @@ -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) } } diff --git a/utils.go b/utils.go index e55006b..0dfcae5 100644 --- a/utils.go +++ b/utils.go @@ -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 + +}