...

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

Documentation: github.com/sassoftware/relic/signers/pgp

     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 pgp
    18  
    19  // Sign arbitrary data using PGP detached or cleartext signatures
    20  
    21  import (
    22  	"bufio"
    23  	"bytes"
    24  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"os"
    29  	"path/filepath"
    30  	"time"
    31  
    32  	"github.com/sassoftware/relic/lib/atomicfile"
    33  	"github.com/sassoftware/relic/lib/certloader"
    34  	"github.com/sassoftware/relic/lib/magic"
    35  	"github.com/sassoftware/relic/lib/pgptools"
    36  	"github.com/sassoftware/relic/signers"
    37  	"golang.org/x/crypto/openpgp"
    38  	"golang.org/x/crypto/openpgp/armor"
    39  	"golang.org/x/crypto/openpgp/packet"
    40  )
    41  
    42  var PgpSigner = &signers.Signer{
    43  	Name:         "pgp",
    44  	Magic:        magic.FileTypePGP,
    45  	CertTypes:    signers.CertTypePgp,
    46  	AllowStdin:   true,
    47  	Transform:    transform,
    48  	Sign:         sign,
    49  	VerifyStream: verify,
    50  }
    51  
    52  const maxStreamClearSignSize = 10 * 1000 * 1000
    53  
    54  func init() {
    55  	PgpSigner.Flags().BoolP("armor", "a", false, "(PGP) Create ASCII armored output")
    56  	PgpSigner.Flags().Bool("inline", false, "(PGP) Create a signed message instead of a detached signature")
    57  	PgpSigner.Flags().Bool("clearsign", false, "(PGP) Create a cleartext signature")
    58  	PgpSigner.Flags().BoolP("textmode", "t", false, "(PGP) Sign in CRLF canonical text form")
    59  	// for compat with 2.0 clients
    60  	PgpSigner.Flags().String("pgp", "", "")
    61  	PgpSigner.Flags().MarkHidden("pgp")
    62  	signers.Register(PgpSigner)
    63  }
    64  
    65  type pgpTransformer struct {
    66  	inline, clearsign, armor bool
    67  
    68  	filename string
    69  	stream   io.ReadSeeker
    70  	closer   io.Closer
    71  }
    72  
    73  func transform(f *os.File, opts signers.SignOpts) (signers.Transformer, error) {
    74  	armor := opts.Flags.GetBool("armor")
    75  	inline := opts.Flags.GetBool("inline")
    76  	if inline {
    77  		// always get a non-armored sig from the server
    78  		opts.Flags.Values["armor"] = "false"
    79  	}
    80  	clearsign := opts.Flags.GetBool("clearsign")
    81  	stream := io.ReadSeeker(f)
    82  	if _, err := f.Seek(0, 0); err != nil {
    83  		// not seekable so consume it all now
    84  		contents, err := ioutil.ReadAll(io.LimitReader(stream, maxStreamClearSignSize))
    85  		if err != nil {
    86  			return nil, err
    87  		} else if len(contents) == maxStreamClearSignSize {
    88  			return nil, errors.New("input stream is too big, try writing it to file first")
    89  		}
    90  		stream = bytes.NewReader(contents)
    91  	}
    92  	return &pgpTransformer{
    93  		inline:    inline,
    94  		clearsign: clearsign,
    95  		armor:     armor,
    96  		filename:  filepath.Base(f.Name()),
    97  		stream:    stream,
    98  		closer:    f,
    99  	}, nil
   100  }
   101  
   102  func (t *pgpTransformer) GetReader() (io.Reader, error) {
   103  	if _, err := t.stream.Seek(0, 0); err != nil {
   104  		return nil, err
   105  	}
   106  	return t.stream, nil
   107  }
   108  
   109  func sign(r io.Reader, cert *certloader.Certificate, opts signers.SignOpts) ([]byte, error) {
   110  	armor := opts.Flags.GetBool("armor")
   111  	clearsign := opts.Flags.GetBool("clearsign")
   112  	textmode := opts.Flags.GetBool("textmode")
   113  	if pgpcompat := opts.Flags.GetString("pgp"); pgpcompat == "mini-clear" {
   114  		clearsign = true
   115  	}
   116  	var sf func(io.Writer, *openpgp.Entity, io.Reader, *packet.Config) error
   117  	if clearsign {
   118  		sf = pgptools.DetachClearSign
   119  	} else if armor {
   120  		if textmode {
   121  			sf = openpgp.ArmoredDetachSignText
   122  		} else {
   123  			sf = openpgp.ArmoredDetachSign
   124  		}
   125  	} else {
   126  		if textmode {
   127  			sf = openpgp.DetachSignText
   128  		} else {
   129  			sf = openpgp.DetachSign
   130  		}
   131  	}
   132  	var buf bytes.Buffer
   133  	config := &packet.Config{
   134  		DefaultHash: opts.Hash,
   135  		Time:        func() time.Time { return opts.Time },
   136  	}
   137  	if err := sf(&buf, cert.PgpKey, r, config); err != nil {
   138  		return nil, err
   139  	} else if armor {
   140  		buf.WriteByte('\n')
   141  	}
   142  	return buf.Bytes(), nil
   143  }
   144  
   145  func (t *pgpTransformer) Apply(dest, mimeType string, result io.Reader) error {
   146  	outfile, err := atomicfile.WriteAny(dest)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	defer outfile.Close()
   151  	if t.inline || t.clearsign {
   152  		// reassemble signature
   153  		if _, err := t.stream.Seek(0, 0); err != nil {
   154  			return err
   155  		}
   156  		sig, err := ioutil.ReadAll(result)
   157  		if err != nil {
   158  			return err
   159  		}
   160  		if t.clearsign {
   161  			err = pgptools.MergeClearSign(outfile, sig, t.stream)
   162  		} else {
   163  			err = pgptools.MergeSignature(outfile, sig, t.stream, t.armor, t.filename)
   164  		}
   165  		if err != nil {
   166  			return err
   167  		}
   168  	} else {
   169  		if _, err := io.Copy(outfile, result); err != nil {
   170  			return err
   171  		}
   172  	}
   173  	t.closer.Close()
   174  	return outfile.Commit()
   175  }
   176  
   177  func verify(r io.Reader, opts signers.VerifyOpts) ([]*signers.Signature, error) {
   178  	br := bufio.NewReader(r)
   179  	// remove ASCII armor
   180  	reader := io.Reader(br)
   181  	if x, _ := br.Peek(34); len(x) >= 1 && x[0] == '-' {
   182  		if bytes.HasPrefix(x, []byte("-----BEGIN PGP SIGNED MESSAGE-----")) {
   183  			// clearsign
   184  			sig, err := pgptools.VerifyClearSign(reader, nil, opts.TrustedPgp)
   185  			return verifyPgp(sig, opts.FileName, err)
   186  		}
   187  		block, err := armor.Decode(reader)
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  		reader = block.Body
   192  	}
   193  	if opts.Content != "" {
   194  		// detached signature
   195  		fc, err := os.Open(opts.Content)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  		defer fc.Close()
   200  		sig, err := pgptools.VerifyDetached(reader, fc, opts.TrustedPgp)
   201  		return verifyPgp(sig, opts.FileName, err)
   202  	}
   203  	// inline signature
   204  	sig, err := pgptools.VerifyInline(reader, nil, opts.TrustedPgp)
   205  	return verifyPgp(sig, opts.FileName, err)
   206  }
   207  
   208  func verifyPgp(sig *pgptools.PgpSignature, name string, err error) ([]*signers.Signature, error) {
   209  	if err == nil {
   210  		return []*signers.Signature{&signers.Signature{
   211  			CreationTime: sig.CreationTime,
   212  			Hash:         sig.Hash,
   213  			SignerPgp:    sig.Key.Entity,
   214  		}}, nil
   215  	} else if sig != nil {
   216  		return nil, fmt.Errorf("bad signature from %s(%x) [%s]: %s", pgptools.EntityName(sig.Key.Entity), sig.Key.PublicKey.KeyId, sig.CreationTime, err)
   217  	}
   218  	return nil, err
   219  }
   220  

View as plain text