Home Download Docs Code Community
     1	/*
     2	Copyright 2011 The Perkeep Authors
     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 signhandler implements the HTTP interface to signing and verifying
    18	// Perkeep JSON blobs.
    19	package signhandler // import "perkeep.org/pkg/jsonsign/signhandler"
    20	
    21	import (
    22		"context"
    23		"fmt"
    24		"log"
    25		"net/http"
    26		"strings"
    27		"sync"
    28	
    29		"perkeep.org/internal/httputil"
    30		"perkeep.org/internal/osutil"
    31		"perkeep.org/pkg/blob"
    32		"perkeep.org/pkg/blobserver"
    33		"perkeep.org/pkg/blobserver/gethandler"
    34		"perkeep.org/pkg/blobserver/memory"
    35		"perkeep.org/pkg/jsonsign"
    36		"perkeep.org/pkg/schema"
    37		"perkeep.org/pkg/types/camtypes"
    38	
    39		"go4.org/jsonconfig"
    40		"golang.org/x/crypto/openpgp"
    41	)
    42	
    43	const maxJSONLength = 1024 * 1024
    44	
    45	// Handler implements JSON signing, verification, and discovery.
    46	//
    47	// See https://perkeep.org/doc/protocol/jsonsign
    48	type Handler struct {
    49		// Optional path to non-standard secret gpg keyring file
    50		secretRing string
    51	
    52		pubKey        string // armored
    53		pubKeyBlobRef blob.Ref
    54		pubKeyFetcher blob.Fetcher
    55	
    56		pubKeyBlobRefServeSuffix string // "camli/sha1-xxxx"
    57		pubKeyHandler            http.Handler
    58	
    59		pubKeyDest blobserver.Storage // Where our public key is published
    60	
    61		pubKeyUploadMu sync.RWMutex
    62		pubKeyUploaded bool
    63	
    64		entity *openpgp.Entity
    65		signer *schema.Signer
    66	}
    67	
    68	func (h *Handler) Signer() *schema.Signer { return h.signer }
    69	
    70	func (h *Handler) secretRingPath() string {
    71		if h.secretRing != "" {
    72			return h.secretRing
    73		}
    74		return osutil.SecretRingFile()
    75	}
    76	
    77	func init() {
    78		blobserver.RegisterHandlerConstructor("jsonsign", newJSONSignFromConfig)
    79	}
    80	
    81	func newJSONSignFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) {
    82		var (
    83			// either a short form ("26F5ABDA") or one the longer forms.
    84			keyId = conf.RequiredString("keyId")
    85	
    86			pubKeyDestPrefix = conf.OptionalString("publicKeyDest", "")
    87			secretRing       = conf.OptionalString("secretRing", "")
    88		)
    89		if err := conf.Validate(); err != nil {
    90			return nil, err
    91		}
    92	
    93		h := &Handler{
    94			secretRing: secretRing,
    95		}
    96	
    97		var err error
    98		h.entity, err = jsonsign.EntityFromSecring(keyId, h.secretRingPath())
    99		if err != nil {
   100			return nil, err
   101		}
   102	
   103		h.pubKey, err = jsonsign.ArmoredPublicKey(h.entity)
   104	
   105		ctx := context.Background() // TODO: 15 second or global-configurable start-up limit?
   106	
   107		ms := &memory.Storage{}
   108		h.pubKeyBlobRef = blob.RefFromString(h.pubKey)
   109		if _, err := ms.ReceiveBlob(ctx, h.pubKeyBlobRef, strings.NewReader(h.pubKey)); err != nil {
   110			return nil, fmt.Errorf("could not store pub key blob: %v", err)
   111		}
   112		h.pubKeyFetcher = ms
   113	
   114		if pubKeyDestPrefix != "" {
   115			sto, err := ld.GetStorage(pubKeyDestPrefix)
   116			if err != nil {
   117				return nil, err
   118			}
   119			h.pubKeyDest = sto
   120		}
   121		h.pubKeyBlobRefServeSuffix = "camli/" + h.pubKeyBlobRef.String()
   122		h.pubKeyHandler = &gethandler.Handler{
   123			Fetcher: ms,
   124		}
   125	
   126		h.signer, err = schema.NewSigner(h.pubKeyBlobRef, strings.NewReader(h.pubKey), h.entity)
   127		if err != nil {
   128			return nil, err
   129		}
   130	
   131		return h, nil
   132	}
   133	
   134	// UploadPublicKey writes the public key to the destination blobserver
   135	// defined for the handler, if needed.
   136	func (h *Handler) UploadPublicKey(ctx context.Context) error {
   137		h.pubKeyUploadMu.RLock()
   138		if h.pubKeyUploaded {
   139			h.pubKeyUploadMu.RUnlock()
   140			return nil
   141		}
   142		h.pubKeyUploadMu.RUnlock()
   143	
   144		sto := h.pubKeyDest
   145	
   146		h.pubKeyUploadMu.Lock()
   147		defer h.pubKeyUploadMu.Unlock()
   148		if h.pubKeyUploaded {
   149			return nil
   150		}
   151		_, err := blobserver.StatBlob(ctx, sto, h.pubKeyBlobRef)
   152		if err == nil {
   153			h.pubKeyUploaded = true
   154			return nil
   155		}
   156		_, err = blobserver.Receive(ctx, sto, h.pubKeyBlobRef, strings.NewReader(h.pubKey))
   157		h.pubKeyUploaded = (err == nil)
   158		return err
   159	}
   160	
   161	// Discovery returns the Discovery response for the signing handler.
   162	func (h *Handler) Discovery(base string) *camtypes.SignDiscovery {
   163		sd := &camtypes.SignDiscovery{
   164			PublicKeyID:   h.entity.PrimaryKey.KeyIdString(),
   165			SignHandler:   base + "camli/sig/sign",
   166			VerifyHandler: base + "camli/sig/verify",
   167		}
   168		if h.pubKeyBlobRef.Valid() {
   169			sd.PublicKeyBlobRef = h.pubKeyBlobRef
   170			sd.PublicKey = base + h.pubKeyBlobRefServeSuffix
   171		}
   172		return sd
   173	}
   174	
   175	func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
   176		base := httputil.PathBase(req)
   177		subPath := httputil.PathSuffix(req)
   178		switch req.Method {
   179		case "GET", "HEAD":
   180			switch subPath {
   181			case "":
   182				http.Redirect(rw, req, base+"camli/sig/discovery", http.StatusFound)
   183				return
   184			case h.pubKeyBlobRefServeSuffix:
   185				h.pubKeyHandler.ServeHTTP(rw, req)
   186				return
   187			case "camli/sig/sign":
   188				fallthrough
   189			case "camli/sig/verify":
   190				http.Error(rw, "POST required", http.StatusBadRequest)
   191				return
   192			case "camli/sig/discovery":
   193				httputil.ReturnJSON(rw, h.Discovery(base))
   194				return
   195			}
   196		case "POST":
   197			switch subPath {
   198			case "camli/sig/sign":
   199				h.handleSign(rw, req)
   200				return
   201			case "camli/sig/verify":
   202				h.handleVerify(rw, req)
   203				return
   204			}
   205		}
   206		http.Error(rw, "Unsupported path or method.", http.StatusBadRequest)
   207	}
   208	
   209	func (h *Handler) handleVerify(rw http.ResponseWriter, req *http.Request) {
   210		req.ParseForm()
   211		sjson := req.FormValue("sjson")
   212		if sjson == "" {
   213			http.Error(rw, "missing \"sjson\" parameter", http.StatusBadRequest)
   214			return
   215		}
   216	
   217		// TODO: use a different fetcher here that checks memory, disk,
   218		// the internet, etc.
   219		fetcher := h.pubKeyFetcher
   220	
   221		var res camtypes.VerifyResponse
   222		vreq := jsonsign.NewVerificationRequest(sjson, fetcher)
   223		_, err := vreq.Verify(req.Context())
   224		if err != nil {
   225			res.SignatureValid = false
   226			res.ErrorMessage = err.Error()
   227		} else {
   228			res.SignatureValid = true
   229			res.SignerKeyId = vreq.SignerKeyId
   230			res.VerifiedData = vreq.PayloadMap
   231		}
   232	
   233		rw.WriteHeader(http.StatusOK) // no HTTP response code fun, error info in JSON
   234		httputil.ReturnJSON(rw, &res)
   235	}
   236	
   237	func (h *Handler) handleSign(rw http.ResponseWriter, req *http.Request) {
   238		req.ParseForm()
   239	
   240		badReq := func(s string) {
   241			http.Error(rw, s, http.StatusBadRequest)
   242			log.Printf("bad request: %s", s)
   243			return
   244		}
   245	
   246		jsonStr := req.FormValue("json")
   247		if jsonStr == "" {
   248			badReq("missing \"json\" parameter")
   249			return
   250		}
   251		if len(jsonStr) > maxJSONLength {
   252			badReq("parameter \"json\" too large")
   253			return
   254		}
   255	
   256		sreq := &jsonsign.SignRequest{
   257			UnsignedJSON:      jsonStr,
   258			Fetcher:           h.pubKeyFetcher,
   259			ServerMode:        true,
   260			SecretKeyringPath: h.secretRing,
   261		}
   262		ctx := req.Context() // TODO: restrict time to 30 seconds?
   263		signedJSON, err := sreq.Sign(ctx)
   264		if err != nil {
   265			// TODO: some aren't really a "bad request"
   266			badReq(fmt.Sprintf("%v", err))
   267			return
   268		}
   269		if err := h.UploadPublicKey(ctx); err != nil {
   270			log.Printf("signing handler failed to upload public key: %v", err)
   271		}
   272		rw.Write([]byte(signedJSON))
   273	}
   274	
   275	func (h *Handler) Sign(ctx context.Context, bb *schema.Builder) (string, error) {
   276		bb.SetSigner(h.pubKeyBlobRef)
   277		unsigned, err := bb.JSON()
   278		if err != nil {
   279			return "", err
   280		}
   281		sreq := &jsonsign.SignRequest{
   282			UnsignedJSON:      unsigned,
   283			Fetcher:           h.pubKeyFetcher,
   284			ServerMode:        true,
   285			SecretKeyringPath: h.secretRing,
   286		}
   287		claimTime, err := bb.Blob().ClaimDate()
   288		if err != nil {
   289			if !schema.IsMissingField(err) {
   290				return "", err
   291			}
   292		} else {
   293			sreq.SignatureTime = claimTime
   294		}
   295		if err := h.UploadPublicKey(ctx); err != nil {
   296			log.Printf("signing handler failed to upload public key: %v", err)
   297		}
   298		return sreq.Sign(ctx)
   299	}
Website layout inspired by memcached.
Content by the authors.