github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/jsonsign/sign.go (about)

     1  /*
     2  Copyright 2011 Google 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 jsonsign
    18  
    19  import (
    20  	"bytes"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  	"unicode"
    30  
    31  	"camlistore.org/pkg/blob"
    32  	"camlistore.org/pkg/osutil"
    33  	"camlistore.org/third_party/code.google.com/p/go.crypto/openpgp"
    34  )
    35  
    36  type EntityFetcher interface {
    37  	FetchEntity(keyId string) (*openpgp.Entity, error)
    38  }
    39  
    40  type FileEntityFetcher struct {
    41  	File string
    42  }
    43  
    44  func FlagEntityFetcher() *FileEntityFetcher {
    45  	return &FileEntityFetcher{File: osutil.IdentitySecretRing()}
    46  }
    47  
    48  type CachingEntityFetcher struct {
    49  	Fetcher EntityFetcher
    50  
    51  	lk sync.Mutex
    52  	m  map[string]*openpgp.Entity
    53  }
    54  
    55  func (ce *CachingEntityFetcher) FetchEntity(keyId string) (*openpgp.Entity, error) {
    56  	ce.lk.Lock()
    57  	if ce.m != nil {
    58  		e := ce.m[keyId]
    59  		if e != nil {
    60  			ce.lk.Unlock()
    61  			return e, nil
    62  		}
    63  	}
    64  	ce.lk.Unlock()
    65  
    66  	e, err := ce.Fetcher.FetchEntity(keyId)
    67  	if err == nil {
    68  		ce.lk.Lock()
    69  		defer ce.lk.Unlock()
    70  		if ce.m == nil {
    71  			ce.m = make(map[string]*openpgp.Entity)
    72  		}
    73  		ce.m[keyId] = e
    74  	}
    75  
    76  	return e, err
    77  }
    78  
    79  func (fe *FileEntityFetcher) FetchEntity(keyId string) (*openpgp.Entity, error) {
    80  	f, err := os.Open(fe.File)
    81  	if err != nil {
    82  		return nil, fmt.Errorf("jsonsign: FetchEntity: %v", err)
    83  	}
    84  	defer f.Close()
    85  	el, err := openpgp.ReadKeyRing(f)
    86  	if err != nil {
    87  		return nil, fmt.Errorf("jsonsign: openpgp.ReadKeyRing of %q: %v", fe.File, err)
    88  	}
    89  	for _, e := range el {
    90  		pubk := &e.PrivateKey.PublicKey
    91  		if pubk.KeyIdString() != keyId {
    92  			continue
    93  		}
    94  		if e.PrivateKey.Encrypted {
    95  			if err := fe.decryptEntity(e); err == nil {
    96  				return e, nil
    97  			} else {
    98  				return nil, err
    99  			}
   100  		}
   101  		return e, nil
   102  	}
   103  	return nil, fmt.Errorf("jsonsign: entity for keyid %q not found in %q", keyId, fe.File)
   104  }
   105  
   106  type SignRequest struct {
   107  	UnsignedJSON string
   108  	Fetcher      interface{} // blobref.Fetcher or blob.StreamingFetcher
   109  	ServerMode   bool        // if true, can't use pinentry or gpg-agent, etc.
   110  
   111  	// Optional signature time. If zero, time.Now() is used.
   112  	SignatureTime time.Time
   113  
   114  	// Optional function to return an entity (including decrypting
   115  	// the PrivateKey, if necessary)
   116  	EntityFetcher EntityFetcher
   117  
   118  	// SecretKeyringPath is only used if EntityFetcher is nil,
   119  	// in which case SecretKeyringPath is used if non-empty.
   120  	// As a final resort, the flag value (defaulting to
   121  	// ~/.gnupg/secring.gpg) is used.
   122  	SecretKeyringPath string
   123  }
   124  
   125  func (sr *SignRequest) secretRingPath() string {
   126  	if sr.SecretKeyringPath != "" {
   127  		return sr.SecretKeyringPath
   128  	}
   129  	return osutil.IdentitySecretRing()
   130  }
   131  
   132  func (sr *SignRequest) Sign() (signedJSON string, err error) {
   133  	trimmedJSON := strings.TrimRightFunc(sr.UnsignedJSON, unicode.IsSpace)
   134  
   135  	// TODO: make sure these return different things
   136  	inputfail := func(msg string) (string, error) {
   137  		return "", errors.New(msg)
   138  	}
   139  	execfail := func(msg string) (string, error) {
   140  		return "", errors.New(msg)
   141  	}
   142  
   143  	jmap := make(map[string]interface{})
   144  	if err := json.Unmarshal([]byte(trimmedJSON), &jmap); err != nil {
   145  		return inputfail("json parse error")
   146  	}
   147  
   148  	camliSigner, hasSigner := jmap["camliSigner"]
   149  	if !hasSigner {
   150  		return inputfail("json lacks \"camliSigner\" key with public key blobref")
   151  	}
   152  
   153  	camliSignerStr, _ := camliSigner.(string)
   154  	signerBlob, ok := blob.Parse(camliSignerStr)
   155  	if !ok {
   156  		return inputfail("json \"camliSigner\" key is malformed or unsupported")
   157  	}
   158  
   159  	var pubkeyReader io.ReadCloser
   160  	switch fetcher := sr.Fetcher.(type) {
   161  	case blob.SeekFetcher:
   162  		pubkeyReader, _, err = fetcher.Fetch(signerBlob)
   163  	case blob.StreamingFetcher:
   164  		pubkeyReader, _, err = fetcher.FetchStreaming(signerBlob)
   165  	default:
   166  		panic(fmt.Sprintf("jsonsign: bogus SignRequest.Fetcher of type %T", sr.Fetcher))
   167  	}
   168  	if err != nil {
   169  		// TODO: not really either an inputfail or an execfail.. but going
   170  		// with exec for now.
   171  		return execfail(fmt.Sprintf("failed to find public key %s: %v", signerBlob.String(), err))
   172  	}
   173  
   174  	pubk, err := openArmoredPublicKeyFile(pubkeyReader)
   175  	pubkeyReader.Close()
   176  	if err != nil {
   177  		return execfail(fmt.Sprintf("failed to parse public key from blobref %s: %v", signerBlob.String(), err))
   178  	}
   179  
   180  	// This check should be redundant if the above JSON parse succeeded, but
   181  	// for explicitness...
   182  	if len(trimmedJSON) == 0 || trimmedJSON[len(trimmedJSON)-1] != '}' {
   183  		return inputfail("json parameter lacks trailing '}'")
   184  	}
   185  	trimmedJSON = trimmedJSON[0 : len(trimmedJSON)-1]
   186  
   187  	// sign it
   188  	entityFetcher := sr.EntityFetcher
   189  	if entityFetcher == nil {
   190  		file := sr.secretRingPath()
   191  		if file == "" {
   192  			return "", errors.New("jsonsign: no EntityFetcher, SecretKeyringPath, or secret-keyring flag provided")
   193  		}
   194  		secring, err := os.Open(sr.secretRingPath())
   195  		if err != nil {
   196  			return "", fmt.Errorf("jsonsign: failed to open secret ring file %q: %v", sr.secretRingPath(), err)
   197  		}
   198  		secring.Close() // just opened to see if it's readable
   199  		entityFetcher = &FileEntityFetcher{File: file}
   200  	}
   201  	signer, err := entityFetcher.FetchEntity(pubk.KeyIdString())
   202  	if err != nil {
   203  		return "", err
   204  	}
   205  
   206  	var buf bytes.Buffer
   207  	err = openpgp.ArmoredDetachSignAt(&buf, signer, sr.SignatureTime, strings.NewReader(trimmedJSON))
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  
   212  	output := buf.String()
   213  
   214  	index1 := strings.Index(output, "\n\n")
   215  	index2 := strings.Index(output, "\n-----")
   216  	if index1 == -1 || index2 == -1 {
   217  		return execfail("Failed to parse signature from gpg.")
   218  	}
   219  	inner := output[index1+2 : index2]
   220  	signature := strings.Replace(inner, "\n", "", -1)
   221  
   222  	return fmt.Sprintf("%s,\"camliSig\":\"%s\"}\n", trimmedJSON, signature), nil
   223  }