...

Source file src/github.com/sassoftware/relic/lib/signdeb/debsign.go

Documentation: github.com/sassoftware/relic/lib/signdeb

     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 signdeb
    18  
    19  import (
    20  	"bytes"
    21  	"crypto"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"path"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/sassoftware/relic/lib/binpatch"
    31  	"github.com/sassoftware/relic/lib/pgptools"
    32  	"github.com/sassoftware/relic/lib/readercounter"
    33  
    34  	"github.com/qur/ar"
    35  	"golang.org/x/crypto/openpgp"
    36  	"golang.org/x/crypto/openpgp/packet"
    37  )
    38  
    39  type DebSignature struct {
    40  	Info         PackageInfo
    41  	CreationTime time.Time
    42  	PatchSet     *binpatch.PatchSet
    43  }
    44  
    45  // Sign a .deb file with the given PGP key. A role name is needed for the
    46  // signature, e.g. "builder". Returns a structure holding a PatchSet that can
    47  // be applied to the original file to add or replace the signature.
    48  func Sign(r io.Reader, signer *openpgp.Entity, opts crypto.SignerOpts, role string) (*DebSignature, error) {
    49  	counter := readercounter.New(r)
    50  	now := time.Now().UTC()
    51  	reader := ar.NewReader(counter)
    52  	msg := new(bytes.Buffer)
    53  	fmt.Fprintln(msg, "Version: 4")
    54  	fmt.Fprintln(msg, "Signer:", pgptools.EntityName(signer))
    55  	fmt.Fprintln(msg, "Date:", now.Format(time.ANSIC))
    56  	fmt.Fprintln(msg, "Role:", role)
    57  	fmt.Fprintln(msg, "Files: ")
    58  	var patchOffset, patchLength int64
    59  	var info *PackageInfo
    60  	filename := "_gpg" + role
    61  	for {
    62  		hdr, err := reader.Next()
    63  		if err == io.EOF {
    64  			break
    65  		} else if err != nil {
    66  			return nil, err
    67  		}
    68  		name := path.Clean(hdr.Name)
    69  		if name == filename {
    70  			// mark the old signature for removal
    71  			patchOffset = counter.N - 60
    72  			patchLength = int64(60 + ((hdr.Size+1)/2)*2)
    73  		}
    74  		if strings.HasPrefix(name, "_gpg") {
    75  			continue
    76  		}
    77  		save := io.Writer(ioutil.Discard)
    78  		var closer io.Closer
    79  		var infoch chan *PackageInfo
    80  		var errch chan error
    81  		if strings.HasPrefix(name, "control.tar") {
    82  			// use a goroutine pipe to parse the control tarball as it's digested
    83  			ext := name[11:]
    84  			r, w := io.Pipe()
    85  			save = w
    86  			closer = w
    87  			infoch = make(chan *PackageInfo, 1)
    88  			errch = make(chan error, 1)
    89  			go func() {
    90  				info, err := parseControl(r, ext)
    91  				// ensure whole file is read, otherwise pipe will stall
    92  				_, _ = io.Copy(ioutil.Discard, r)
    93  				infoch <- info
    94  				errch <- err
    95  			}()
    96  		}
    97  		md5 := crypto.MD5.New()
    98  		sha1 := crypto.SHA1.New()
    99  		if _, err := io.Copy(io.MultiWriter(md5, sha1, save), reader); err != nil {
   100  			return nil, err
   101  		}
   102  		if closer != nil {
   103  			closer.Close()
   104  		}
   105  		fmt.Fprintf(msg, "\t%x %x %d %s\n", md5.Sum(nil), sha1.Sum(nil), hdr.Size, hdr.Name)
   106  		if errch != nil {
   107  			// retrieve the result of parsing the control file
   108  			info = <-infoch
   109  			if err := <-errch; err != nil {
   110  				return nil, err
   111  			}
   112  		}
   113  	}
   114  	if info == nil {
   115  		return nil, errors.New("deb has no control.tar")
   116  	}
   117  	fmt.Fprintln(msg)
   118  	signed := new(bytes.Buffer)
   119  	config := &packet.Config{
   120  		DefaultHash: opts.HashFunc(),
   121  		Time:        func() time.Time { return now },
   122  	}
   123  	if err := pgptools.ClearSign(signed, signer, msg, config); err != nil {
   124  		return nil, err
   125  	}
   126  	// Format as an ar fragment and turn it into a binpatch that will update
   127  	// the original archive
   128  	pbuf := new(bytes.Buffer)
   129  	writer := ar.NewWriter(pbuf)
   130  	hdr := &ar.Header{
   131  		Name:    filename,
   132  		Size:    int64(signed.Len()),
   133  		ModTime: now,
   134  		Mode:    0100644,
   135  	}
   136  	if err := writer.WriteHeader(hdr); err != nil {
   137  		return nil, err
   138  	}
   139  	if _, err := writer.Write(signed.Bytes()); err != nil {
   140  		return nil, err
   141  	}
   142  	if patchOffset == 0 {
   143  		patchOffset = counter.N // end of file
   144  	}
   145  	patch := binpatch.New()
   146  	patch.Add(patchOffset, patchLength, pbuf.Bytes())
   147  	return &DebSignature{*info, now, patch}, nil
   148  }
   149  

View as plain text