...

Source file src/github.com/sassoftware/relic/signers/signers.go

Documentation: github.com/sassoftware/relic/signers

     1  //
     2  // Copyright (c) SAS Institute Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package signers
    18  
    19  import (
    20  	"crypto"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"os"
    25  	"time"
    26  
    27  	"github.com/spf13/pflag"
    28  
    29  	"github.com/sassoftware/relic/lib/audit"
    30  	"github.com/sassoftware/relic/lib/certloader"
    31  	"github.com/sassoftware/relic/lib/magic"
    32  	"github.com/sassoftware/relic/lib/pgptools"
    33  	"github.com/sassoftware/relic/lib/pkcs9"
    34  	"github.com/sassoftware/relic/lib/x509tools"
    35  	"github.com/sassoftware/relic/signers/sigerrors"
    36  	"golang.org/x/crypto/openpgp"
    37  )
    38  
    39  type Signer struct {
    40  	Name       string
    41  	Aliases    []string
    42  	Magic      magic.FileType
    43  	CertTypes  CertType
    44  	AllowStdin bool
    45  	// Return true if the given filename is associated with this signer
    46  	TestPath func(string) bool
    47  	// Format audit attributes for logfile
    48  	FormatLog func(*audit.Info) string
    49  	// Verify a file, returning the set of signatures found. Performs integrity
    50  	// checks but does not build X509 chains.
    51  	Verify func(*os.File, VerifyOpts) ([]*Signature, error)
    52  	// VerifyStream is like Verify but doesn't need to seek.
    53  	VerifyStream func(io.Reader, VerifyOpts) ([]*Signature, error)
    54  	// Transform a file into a stream to upload
    55  	Transform func(*os.File, SignOpts) (Transformer, error)
    56  	// Sign a input stream (possibly transformed) and return a mode-specific result blob
    57  	Sign func(io.Reader, *certloader.Certificate, SignOpts) ([]byte, error)
    58  	// Final step to run on the client after the file is patched
    59  	Fixup func(*os.File) error
    60  
    61  	flags *pflag.FlagSet
    62  }
    63  
    64  type CertType uint
    65  
    66  const (
    67  	CertTypeX509 CertType = 1 << iota
    68  	CertTypePgp
    69  )
    70  
    71  type Signature struct {
    72  	Package       string
    73  	SigInfo       string
    74  	CreationTime  time.Time
    75  	Hash          crypto.Hash
    76  	Signer        string
    77  	SignerPgp     *openpgp.Entity
    78  	X509Signature *pkcs9.TimestampedSignature
    79  }
    80  
    81  func (s *Signature) SignerName() string {
    82  	if s.Signer != "" {
    83  		return s.Signer
    84  	}
    85  	if s.X509Signature != nil {
    86  		return fmt.Sprintf("`%s`", x509tools.FormatSubject(s.X509Signature.Certificate))
    87  	}
    88  	if s.SignerPgp != nil {
    89  		return fmt.Sprintf("`%s`(%x)", pgptools.EntityName(s.SignerPgp), s.SignerPgp.PrimaryKey.KeyId)
    90  	}
    91  	return "UNKNOWN"
    92  }
    93  
    94  var registered []*Signer
    95  var flagMap map[string][]string
    96  
    97  func Register(s *Signer) {
    98  	registered = append(registered, s)
    99  }
   100  
   101  // Return the signer module with the given name or alias
   102  func ByName(name string) *Signer {
   103  	for _, s := range registered {
   104  		if s.Name == name {
   105  			return s
   106  		}
   107  		for _, n2 := range s.Aliases {
   108  			if n2 == name {
   109  				return s
   110  			}
   111  		}
   112  	}
   113  	return nil
   114  }
   115  
   116  // Return the signer module responsible for the given file magic
   117  func ByMagic(m magic.FileType) *Signer {
   118  	if m == magic.FileTypeUnknown {
   119  		return nil
   120  	}
   121  	for _, s := range registered {
   122  		if s.Magic == m {
   123  			return s
   124  		}
   125  	}
   126  	return nil
   127  }
   128  
   129  // Return the signer associated with the given filename extension
   130  func ByFileName(name string) *Signer {
   131  	for _, s := range registered {
   132  		if s.TestPath != nil && s.TestPath(name) {
   133  			return s
   134  		}
   135  	}
   136  	return nil
   137  }
   138  
   139  // Return the named signer module if given, otherwise identify the file at the
   140  // given path by contents or extension
   141  func ByFile(name, sigtype string) (*Signer, error) {
   142  	if sigtype != "" {
   143  		mod := ByName(sigtype)
   144  		if mod == nil {
   145  			return nil, errors.New("no signer with that name")
   146  		}
   147  		return mod, nil
   148  	}
   149  	if name == "-" {
   150  		return nil, errors.New("reading from standard input is not supported")
   151  	}
   152  	f, err := os.Open(name)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	defer f.Close()
   157  	fileType, compressionType := magic.DetectCompressed(f)
   158  	if compressionType != magic.CompressedNone {
   159  		return nil, errors.New("cannot sign compressed file")
   160  	}
   161  	if mod := ByMagic(fileType); mod != nil {
   162  		return mod, nil
   163  	} else if mod := ByFileName(name); mod != nil {
   164  		return mod, nil
   165  	}
   166  	return nil, errors.New("unknown filetype")
   167  }
   168  
   169  // Create a FlagSet for flags associated with this module. These will be added
   170  // to "sign" and "remote sign", and transferred to a remote server via the URL
   171  // query parameters.
   172  func (s *Signer) Flags() *pflag.FlagSet {
   173  	if s.flags == nil {
   174  		s.flags = pflag.NewFlagSet(s.Name, pflag.ExitOnError)
   175  	}
   176  	return s.flags
   177  }
   178  
   179  // Add this module's flags to a command FlagSet
   180  func MergeFlags(fs *pflag.FlagSet) {
   181  	if flagMap == nil {
   182  		flagMap = make(map[string][]string)
   183  	}
   184  	for _, s := range registered {
   185  		if s.flags == nil {
   186  			continue
   187  		}
   188  		fs.AddFlagSet(s.flags)
   189  		s.flags.VisitAll(func(flag *pflag.Flag) {
   190  			flagMap[flag.Name] = append(flagMap[flag.Name], s.Name)
   191  		})
   192  	}
   193  }
   194  
   195  // IsSigned checks if a file contains a signature
   196  func (s *Signer) IsSigned(f *os.File) (bool, error) {
   197  	var err error
   198  	if s.VerifyStream != nil {
   199  		_, err = s.VerifyStream(f, VerifyOpts{NoDigests: true, NoChain: true})
   200  	} else if s.Verify != nil {
   201  		_, err = s.Verify(f, VerifyOpts{NoDigests: true, NoChain: true})
   202  	} else {
   203  		return false, errors.New("cannot check if this type of file is signed")
   204  	}
   205  	if err == nil {
   206  		return true, nil
   207  	}
   208  	switch err.(type) {
   209  	case sigerrors.NotSignedError:
   210  		return false, nil
   211  	case pgptools.ErrNoKey:
   212  		return true, nil
   213  	}
   214  	return false, err
   215  }
   216  

View as plain text