diff --git a/cmdline/remotecmd/signcmd.go b/cmdline/remotecmd/signcmd.go index 7b7d943..635ff1c 100644 --- a/cmdline/remotecmd/signcmd.go +++ b/cmdline/remotecmd/signcmd.go @@ -34,7 +34,10 @@ var SignCmd = &cobra.Command{ RunE: signCmd, } -var argSigType string +var ( + argIfUnsigned bool + argSigType string +) func init() { RemoteCmd.AddCommand(SignCmd) @@ -42,6 +45,7 @@ func init() { SignCmd.Flags().StringVarP(&argFile, "file", "f", "", "Input file to sign") SignCmd.Flags().StringVarP(&argOutput, "output", "o", "", "Output file. Defaults to same as --file.") SignCmd.Flags().StringVarP(&argSigType, "sig-type", "T", "", "Specify signature type (default: auto-detect)") + SignCmd.Flags().BoolVar(&argIfUnsigned, "if-unsigned", false, "Skip signing if the file already has a signature") shared.AddDigestFlag(SignCmd) shared.AddLateHook(func() { signers.MergeFlags(SignCmd.Flags()) @@ -70,6 +74,9 @@ func signCmd(cmd *cobra.Command, args []string) (err error) { } var infile *os.File if argFile == "-" { + if argIfUnsigned { + return shared.Fail(errors.New("cannot use --if-unsigned with standard input")) + } if !mod.AllowStdin { return shared.Fail(errors.New("this signature type does not support reading from stdin")) } @@ -86,6 +93,17 @@ func signCmd(cmd *cobra.Command, args []string) (err error) { } defer infile.Close() } + if argIfUnsigned { + if signed, err := mod.IsSigned(infile); err != nil { + return shared.Fail(err) + } else if signed { + fmt.Fprintf(os.Stderr, "skipping already-signed file: %s\n", argFile) + return nil + } + if _, err := infile.Seek(0, 0); err != nil { + return shared.Fail(fmt.Errorf("failed to rewind input file: %s", err)) + } + } // transform input if needed hash, err := shared.GetDigest() if err != nil { diff --git a/cmdline/token/signcmd.go b/cmdline/token/signcmd.go index 9535f86..d134cbb 100644 --- a/cmdline/token/signcmd.go +++ b/cmdline/token/signcmd.go @@ -36,8 +36,9 @@ var SignCmd = &cobra.Command{ } var ( - argSigType string - argOutput string + argIfUnsigned bool + argSigType string + argOutput string ) func init() { @@ -46,6 +47,7 @@ func init() { SignCmd.Flags().StringVarP(&argFile, "file", "f", "", "Input file to sign") SignCmd.Flags().StringVarP(&argOutput, "output", "o", "", "Output file") SignCmd.Flags().StringVarP(&argSigType, "sig-type", "T", "", "Specify signature type (default: auto-detect)") + SignCmd.Flags().BoolVar(&argIfUnsigned, "if-unsigned", false, "Skip signing if the file already has a signature") shared.AddDigestFlag(SignCmd) shared.AddLateHook(func() { signers.MergeFlags(SignCmd.Flags()) @@ -88,6 +90,17 @@ func signCmd(cmd *cobra.Command, args []string) error { return shared.Fail(err) } defer infile.Close() + if argIfUnsigned { + if signed, err := mod.IsSigned(infile); err != nil { + return shared.Fail(err) + } else if signed { + fmt.Fprintf(os.Stderr, "skipping already-signed file: %s\n", argFile) + return nil + } + if _, err := infile.Seek(0, 0); err != nil { + return shared.Fail(fmt.Errorf("failed to rewind input file: %s", err)) + } + } // transform the input, sign the stream, and apply the result transform, err := mod.GetTransform(infile, *opts) if err != nil { diff --git a/cmdline/verify/verify.go b/cmdline/verify/verify.go index 74f4082..15ea52a 100644 --- a/cmdline/verify/verify.go +++ b/cmdline/verify/verify.go @@ -25,6 +25,7 @@ import ( "github.com/sassoftware/relic/cmdline/shared" "github.com/sassoftware/relic/lib/certloader" "github.com/sassoftware/relic/lib/magic" + "github.com/sassoftware/relic/lib/pgptools" "github.com/sassoftware/relic/lib/x509tools" "github.com/sassoftware/relic/signers" "github.com/spf13/cobra" @@ -108,6 +109,9 @@ func verifyOne(path string, opts signers.VerifyOpts) error { sigs, err = mod.Verify(f, opts) } if err != nil { + if _, ok := err.(pgptools.ErrNoKey); ok { + return fmt.Errorf("%s; use --cert to specify known keys", err) + } return err } for _, sig := range sigs { diff --git a/lib/appmanifest/verify.go b/lib/appmanifest/verify.go index 66e3f02..52d888b 100644 --- a/lib/appmanifest/verify.go +++ b/lib/appmanifest/verify.go @@ -27,6 +27,7 @@ import ( "github.com/sassoftware/relic/lib/pkcs9" "github.com/sassoftware/relic/lib/x509tools" "github.com/sassoftware/relic/lib/xmldsig" + "github.com/sassoftware/relic/signers/sigerrors" ) type ManifestSignature struct { @@ -46,14 +47,20 @@ func Verify(manifest []byte) (*ManifestSignature, error) { root := doc.Root() primary, err := xmldsig.Verify(root, "Signature", nil) if err != nil { + if _, ok := err.(sigerrors.NotSignedError); ok { + return nil, err + } return nil, fmt.Errorf("invalid primary signature: %s", err) } license := root.FindElement("Signature/KeyInfo/msrel:RelData/r:license") if license == nil { - return nil, fmt.Errorf("invalid authenticode signature: %s", "signature is missing") + return nil, sigerrors.NotSignedError{Type: "application manifest"} } secondary, err := xmldsig.Verify(license, "issuer/Signature", nil) if err != nil { + if _, ok := err.(sigerrors.NotSignedError); ok { + return nil, err + } return nil, fmt.Errorf("invalid authenticode signature: %s", err) } if !x509tools.SameKey(primary.PublicKey, secondary.PublicKey) { diff --git a/lib/authenticode/peverify.go b/lib/authenticode/peverify.go index cd9ed57..8e4558c 100644 --- a/lib/authenticode/peverify.go +++ b/lib/authenticode/peverify.go @@ -30,6 +30,7 @@ import ( "github.com/sassoftware/relic/lib/pkcs7" "github.com/sassoftware/relic/lib/pkcs9" "github.com/sassoftware/relic/lib/x509tools" + "github.com/sassoftware/relic/signers/sigerrors" ) type PESignature struct { @@ -46,7 +47,7 @@ func VerifyPE(r io.ReadSeeker, skipDigests bool) ([]PESignature, error) { if err != nil { return nil, err } else if hvals.certSize == 0 { - return nil, errors.New("image does not contain any signatures") + return nil, sigerrors.NotSignedError{Type: "PECOFF"} } // Read certificate table sigblob := make([]byte, hvals.certSize) diff --git a/lib/xmldsig/verify.go b/lib/xmldsig/verify.go index c3460aa..abe99a0 100644 --- a/lib/xmldsig/verify.go +++ b/lib/xmldsig/verify.go @@ -30,6 +30,7 @@ import ( "strings" "github.com/sassoftware/relic/lib/x509tools" + "github.com/sassoftware/relic/signers/sigerrors" "github.com/beevik/etree" ) @@ -56,7 +57,7 @@ func Verify(root *etree.Element, sigpath string, extraCerts []*x509.Certificate) root = root.Copy() sigs := root.FindElements(sigpath) if len(sigs) == 0 { - return nil, errors.New("xmldsig: signature not found") + return nil, sigerrors.NotSignedError{Type: "xmldsig"} } else if len(sigs) > 1 { return nil, errors.New("xmldsig: multiple signatures found") } diff --git a/signers/deb/signer.go b/signers/deb/signer.go index 5bd41ac..2ebd71c 100644 --- a/signers/deb/signer.go +++ b/signers/deb/signer.go @@ -26,7 +26,6 @@ import ( "github.com/sassoftware/relic/lib/audit" "github.com/sassoftware/relic/lib/certloader" "github.com/sassoftware/relic/lib/magic" - "github.com/sassoftware/relic/lib/pgptools" "github.com/sassoftware/relic/lib/signdeb" "github.com/sassoftware/relic/signers" "github.com/sassoftware/relic/signers/sigerrors" @@ -71,9 +70,7 @@ func sign(r io.Reader, cert *certloader.Certificate, opts signers.SignOpts) ([]b func verify(f *os.File, opts signers.VerifyOpts) ([]*signers.Signature, error) { sigmap, err := signdeb.Verify(f, opts.TrustedPgp, opts.NoDigests) - if _, ok := err.(pgptools.ErrNoKey); ok { - return nil, fmt.Errorf("%s; use --cert to specify known keys", err) - } else if err != nil { + if err != nil { return nil, err } if len(sigmap) == 0 { diff --git a/signers/pgp/signer.go b/signers/pgp/signer.go index 193d1d2..e081830 100644 --- a/signers/pgp/signer.go +++ b/signers/pgp/signer.go @@ -214,8 +214,6 @@ func verifyPgp(sig *pgptools.PgpSignature, name string, err error) ([]*signers.S }}, nil } else if sig != nil { return nil, fmt.Errorf("bad signature from %s(%x) [%s]: %s", pgptools.EntityName(sig.Key.Entity), sig.Key.PublicKey.KeyId, sig.CreationTime, err) - } else if _, ok := err.(pgptools.ErrNoKey); ok { - return nil, fmt.Errorf("%s; use --cert to specify known keys", err) } return nil, err } diff --git a/signers/rpm/signer.go b/signers/rpm/signer.go index 17e56db..9874e04 100644 --- a/signers/rpm/signer.go +++ b/signers/rpm/signer.go @@ -31,6 +31,7 @@ import ( "github.com/sassoftware/relic/lib/binpatch" "github.com/sassoftware/relic/lib/certloader" "github.com/sassoftware/relic/lib/magic" + "github.com/sassoftware/relic/lib/pgptools" "github.com/sassoftware/relic/signers" "github.com/sassoftware/relic/signers/sigerrors" ) @@ -102,7 +103,7 @@ func verify(f *os.File, opts signers.VerifyOpts) ([]*signers.Signature, error) { } if sig.Signer == nil { if !opts.NoChain { - return nil, fmt.Errorf("unknown keyId %x; use --cert to specify known keys", sig.KeyId) + return nil, pgptools.ErrNoKey(sig.KeyId) } rsig.Signer = fmt.Sprintf("UNKNOWN(%x)", sig.KeyId) } else { diff --git a/signers/signers.go b/signers/signers.go index 5308cac..49a7c2b 100644 --- a/signers/signers.go +++ b/signers/signers.go @@ -32,6 +32,7 @@ import ( "github.com/sassoftware/relic/lib/pgptools" "github.com/sassoftware/relic/lib/pkcs9" "github.com/sassoftware/relic/lib/x509tools" + "github.com/sassoftware/relic/signers/sigerrors" "golang.org/x/crypto/openpgp" ) @@ -190,3 +191,25 @@ func MergeFlags(fs *pflag.FlagSet) { }) } } + +// IsSigned checks if a file contains a signature +func (s *Signer) IsSigned(f *os.File) (bool, error) { + var err error + if s.VerifyStream != nil { + _, err = s.VerifyStream(f, VerifyOpts{NoDigests: true, NoChain: true}) + } else if s.Verify != nil { + _, err = s.Verify(f, VerifyOpts{NoDigests: true, NoChain: true}) + } else { + return false, errors.New("cannot check if this type of file is signed") + } + if err == nil { + return true, nil + } + switch err.(type) { + case sigerrors.NotSignedError: + return false, nil + case pgptools.ErrNoKey: + return true, nil + } + return false, err +} diff --git a/signers/starman/signer.go b/signers/starman/signer.go index 3808ee6..dd2ee37 100644 --- a/signers/starman/signer.go +++ b/signers/starman/signer.go @@ -118,8 +118,6 @@ func verify(r io.Reader, opts signers.VerifyOpts) ([]*signers.Signature, error) }}, nil } else if sig != nil { return nil, fmt.Errorf("bad signature from %s(%x) [%s]: %s", pgptools.EntityName(sig.Key.Entity), sig.Key.PublicKey.KeyId, sig.CreationTime, err) - } else if _, ok := err.(pgptools.ErrNoKey); ok { - return nil, fmt.Errorf("%s; use --cert to specify known keys", err) } return nil, err }