зеркало из https://github.com/github/smimesign.git
182 строки
4.2 KiB
Go
182 строки
4.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/github/smimesign/certstore"
|
|
cms "github.com/github/smimesign/ietf-cms"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func commandSign() error {
|
|
userIdent, err := findUserIdentity()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get identity matching specified user-id")
|
|
}
|
|
if userIdent == nil {
|
|
return fmt.Errorf("could not find identity matching specified user-id: %s", *localUserOpt)
|
|
}
|
|
|
|
// Git is looking for "\n[GNUPG:] SIG_CREATED ", meaning we need to print a
|
|
// line before SIG_CREATED. BEGIN_SIGNING seems appropraite. GPG emits this,
|
|
// though GPGSM does not.
|
|
sBeginSigning.emit()
|
|
|
|
cert, err := userIdent.Certificate()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get idenity certificate")
|
|
}
|
|
|
|
signer, err := userIdent.Signer()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get idenity signer")
|
|
}
|
|
|
|
var f io.ReadCloser
|
|
if len(fileArgs) == 1 {
|
|
if f, err = os.Open(fileArgs[0]); err != nil {
|
|
return errors.Wrapf(err, "failed to open message file (%s)", fileArgs[0])
|
|
}
|
|
defer f.Close()
|
|
} else {
|
|
f = stdin
|
|
}
|
|
|
|
dataBuf := new(bytes.Buffer)
|
|
if _, err = io.Copy(dataBuf, f); err != nil {
|
|
return errors.Wrap(err, "failed to read message from stdin")
|
|
}
|
|
|
|
sd, err := cms.NewSignedData(dataBuf.Bytes())
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to create signed data")
|
|
}
|
|
if err = sd.Sign([]*x509.Certificate{cert}, signer); err != nil {
|
|
return errors.Wrap(err, "failed to sign message")
|
|
}
|
|
if *detachSignFlag {
|
|
sd.Detached()
|
|
}
|
|
|
|
if len(*tsaOpt) > 0 {
|
|
if err = sd.AddTimestamps(*tsaOpt); err != nil {
|
|
return errors.Wrap(err, "failed to add timestamp")
|
|
}
|
|
}
|
|
|
|
chain, err := userIdent.CertificateChain()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get idenity certificate chain")
|
|
}
|
|
if chain, err = certsForSignature(chain); err != nil {
|
|
return err
|
|
}
|
|
if err = sd.SetCertificates(chain); err != nil {
|
|
return errors.Wrap(err, "failed to set certificates")
|
|
}
|
|
|
|
der, err := sd.ToDER()
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to serialize signature")
|
|
}
|
|
|
|
emitSigCreated(cert, *detachSignFlag)
|
|
|
|
if *armorFlag {
|
|
err = pem.Encode(stdout, &pem.Block{
|
|
Type: "SIGNED MESSAGE",
|
|
Bytes: der,
|
|
})
|
|
} else {
|
|
_, err = stdout.Write(der)
|
|
}
|
|
if err != nil {
|
|
return errors.New("failed to write signature")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// findUserIdentity attempts to find an identity to sign with in the certstore
|
|
// by checking available identities against the --local-user argument.
|
|
func findUserIdentity() (certstore.Identity, error) {
|
|
var (
|
|
email string
|
|
fpr []byte
|
|
)
|
|
|
|
if strings.ContainsRune(*localUserOpt, '@') {
|
|
email = normalizeEmail(*localUserOpt)
|
|
} else {
|
|
fpr = normalizeFingerprint(*localUserOpt)
|
|
}
|
|
|
|
if len(email) == 0 && len(fpr) == 0 {
|
|
return nil, fmt.Errorf("bad user-id format: %s", *localUserOpt)
|
|
}
|
|
|
|
for _, ident := range idents {
|
|
if cert, err := ident.Certificate(); err == nil && (certHasEmail(cert, email) || certHasFingerprint(cert, fpr)) {
|
|
return ident, nil
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// certsForSignature determines which certificates to include in the signature
|
|
// based on the --include-certs option specified by the user.
|
|
func certsForSignature(chain []*x509.Certificate) ([]*x509.Certificate, error) {
|
|
include := *includeCertsOpt
|
|
|
|
if include < -3 {
|
|
include = -2 // default
|
|
}
|
|
if include > len(chain) {
|
|
include = len(chain)
|
|
}
|
|
|
|
switch include {
|
|
case -3:
|
|
for i := len(chain) - 1; i > 0; i-- {
|
|
issuer, cert := chain[i], chain[i-1]
|
|
|
|
// remove issuer when cert has AIA extension
|
|
if bytes.Equal(issuer.RawSubject, cert.RawIssuer) && len(cert.IssuingCertificateURL) > 0 {
|
|
chain = chain[0:i]
|
|
}
|
|
}
|
|
return chainWithoutRoot(chain), nil
|
|
case -2:
|
|
return chainWithoutRoot(chain), nil
|
|
case -1:
|
|
return chain, nil
|
|
default:
|
|
return chain[0:include], nil
|
|
}
|
|
}
|
|
|
|
// Returns the provided chain, having removed the root certificate, if present.
|
|
// This includes removing the cert itself if the chain is a single self-signed
|
|
// cert.
|
|
func chainWithoutRoot(chain []*x509.Certificate) []*x509.Certificate {
|
|
if len(chain) == 0 {
|
|
return chain
|
|
}
|
|
|
|
lastIdx := len(chain) - 1
|
|
last := chain[lastIdx]
|
|
|
|
if bytes.Equal(last.RawIssuer, last.RawSubject) {
|
|
return chain[0:lastIdx]
|
|
}
|
|
|
|
return chain
|
|
}
|