// // Copyright (c) SAS Institute Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package signdeb import ( "bytes" "crypto" "errors" "fmt" "io" "io/ioutil" "path" "strings" "time" "github.com/sassoftware/relic/lib/binpatch" "github.com/sassoftware/relic/lib/pgptools" "github.com/sassoftware/relic/lib/readercounter" "github.com/qur/ar" "golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp/packet" ) type DebSignature struct { Info PackageInfo CreationTime time.Time PatchSet *binpatch.PatchSet } // Sign a .deb file with the given PGP key. A role name is needed for the // signature, e.g. "builder". Returns a structure holding a PatchSet that can // be applied to the original file to add or replace the signature. func Sign(r io.Reader, signer *openpgp.Entity, opts crypto.SignerOpts, role string) (*DebSignature, error) { counter := readercounter.New(r) now := time.Now().UTC() reader := ar.NewReader(counter) msg := new(bytes.Buffer) fmt.Fprintln(msg, "Version: 4") fmt.Fprintln(msg, "Signer:", pgptools.EntityName(signer)) fmt.Fprintln(msg, "Date:", now.Format(time.ANSIC)) fmt.Fprintln(msg, "Role:", role) fmt.Fprintln(msg, "Files: ") var patchOffset, patchLength int64 var info *PackageInfo filename := "_gpg" + role for { hdr, err := reader.Next() if err == io.EOF { break } else if err != nil { return nil, err } name := path.Clean(hdr.Name) if name == filename { // mark the old signature for removal patchOffset = counter.N - 60 patchLength = int64(60 + ((hdr.Size+1)/2)*2) } if strings.HasPrefix(name, "_gpg") { continue } save := io.Writer(ioutil.Discard) var closer io.Closer var infoch chan *PackageInfo var errch chan error if strings.HasPrefix(name, "control.tar") { // use a goroutine pipe to parse the control tarball as it's digested ext := name[11:] r, w := io.Pipe() save = w closer = w infoch = make(chan *PackageInfo, 1) errch = make(chan error, 1) go func() { info, err := parseControl(r, ext) // ensure whole file is read, otherwise pipe will stall _, _ = io.Copy(ioutil.Discard, r) infoch <- info errch <- err }() } md5 := crypto.MD5.New() sha1 := crypto.SHA1.New() if _, err := io.Copy(io.MultiWriter(md5, sha1, save), reader); err != nil { return nil, err } if closer != nil { closer.Close() } fmt.Fprintf(msg, "\t%x %x %d %s\n", md5.Sum(nil), sha1.Sum(nil), hdr.Size, hdr.Name) if errch != nil { // retrieve the result of parsing the control file info = <-infoch if err := <-errch; err != nil { return nil, err } } } if info == nil { return nil, errors.New("deb has no control.tar") } fmt.Fprintln(msg) signed := new(bytes.Buffer) config := &packet.Config{ DefaultHash: opts.HashFunc(), Time: func() time.Time { return now }, } if err := pgptools.ClearSign(signed, signer, msg, config); err != nil { return nil, err } // Format as an ar fragment and turn it into a binpatch that will update // the original archive pbuf := new(bytes.Buffer) writer := ar.NewWriter(pbuf) hdr := &ar.Header{ Name: filename, Size: int64(signed.Len()), ModTime: now, Mode: 0100644, } if err := writer.WriteHeader(hdr); err != nil { return nil, err } if _, err := writer.Write(signed.Bytes()); err != nil { return nil, err } if patchOffset == 0 { patchOffset = counter.N // end of file } patch := binpatch.New() patch.Add(patchOffset, patchLength, pbuf.Bytes()) return &DebSignature{*info, now, patch}, nil }