smimesign/command_sign.go

182 строки
4.2 KiB
Go
Исходник Обычный вид История

2017-11-22 18:31:25 +03:00
package main
import (
"bytes"
"crypto/x509"
2017-11-22 18:31:25 +03:00
"encoding/pem"
"fmt"
"io"
2019-04-03 01:09:07 +03:00
"os"
2017-11-22 18:31:25 +03:00
"strings"
"github.com/github/smimesign/certstore"
cms "github.com/github/smimesign/ietf-cms"
2017-11-29 23:50:15 +03:00
"github.com/pkg/errors"
2017-11-22 18:31:25 +03:00
)
func commandSign() error {
2017-11-29 23:50:15 +03:00
userIdent, err := findUserIdentity()
2017-11-22 18:31:25 +03:00
if err != nil {
return errors.Wrap(err, "failed to get identity matching specified user-id")
2017-11-22 18:31:25 +03:00
}
if userIdent == nil {
2018-09-10 21:27:34 +03:00
return fmt.Errorf("could not find identity matching specified user-id: %s", *localUserOpt)
2017-11-22 18:31:25 +03:00
}
2017-11-29 03:25:14 +03:00
// 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,
2017-11-29 03:25:14 +03:00
// though GPGSM does not.
sBeginSigning.emit()
cert, err := userIdent.Certificate()
2017-11-22 18:31:25 +03:00
if err != nil {
return errors.Wrap(err, "failed to get idenity certificate")
2017-11-22 18:31:25 +03:00
}
signer, err := userIdent.Signer()
if err != nil {
return errors.Wrap(err, "failed to get idenity signer")
2017-11-22 18:31:25 +03:00
}
2019-04-03 02:10:10 +03:00
var f io.ReadCloser
2019-04-03 01:09:07 +03:00
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
}
2017-11-22 18:31:25 +03:00
dataBuf := new(bytes.Buffer)
2019-04-03 01:09:07 +03:00
if _, err = io.Copy(dataBuf, f); err != nil {
return errors.Wrap(err, "failed to read message from stdin")
2017-11-22 18:31:25 +03:00
}
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")
}
2017-11-22 18:31:25 +03:00
if *detachSignFlag {
sd.Detached()
2017-11-22 18:31:25 +03:00
}
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")
}
2018-09-06 00:27:35 +03:00
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()
2017-11-22 18:31:25 +03:00
if err != nil {
return errors.Wrap(err, "failed to serialize signature")
2017-11-22 18:31:25 +03:00
}
emitSigCreated(cert, *detachSignFlag)
2017-11-22 18:31:25 +03:00
if *armorFlag {
err = pem.Encode(stdout, &pem.Block{
2017-11-22 18:31:25 +03:00
Type: "SIGNED MESSAGE",
Bytes: der,
})
} else {
_, err = stdout.Write(der)
2017-11-22 18:31:25 +03:00
}
if err != nil {
return errors.New("failed to write signature")
2017-11-22 18:31:25 +03:00
}
return nil
2017-11-22 18:31:25 +03:00
}
// findUserIdentity attempts to find an identity to sign with in the certstore
// by checking available identities against the --local-user argument.
2017-11-29 23:50:15 +03:00
func findUserIdentity() (certstore.Identity, error) {
2017-11-22 18:31:25 +03:00
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)) {
2017-11-22 18:31:25 +03:00
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.
2018-09-06 00:27:35 +03:00
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]
}
}
2018-09-06 00:27:35 +03:00
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
}