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 jsonsign
    18	
    19	import (
    20		"bytes"
    21		"context"
    22		"crypto"
    23		"encoding/json"
    24		"errors"
    25		"fmt"
    26		"log"
    27		"os"
    28		"strings"
    29	
    30		"perkeep.org/pkg/blob"
    31		"perkeep.org/pkg/camerrors"
    32	
    33		"golang.org/x/crypto/openpgp/armor"
    34		"golang.org/x/crypto/openpgp/packet"
    35	)
    36	
    37	const sigSeparator = `,"camliSig":"`
    38	
    39	// reArmor takes a camliSig (single line armor) and turns it back into an PGP-style
    40	// multi-line armored string
    41	func reArmor(line string) string {
    42		lastEq := strings.LastIndex(line, "=")
    43		if lastEq == -1 {
    44			return ""
    45		}
    46		buf := new(bytes.Buffer)
    47		fmt.Fprintf(buf, "-----BEGIN PGP SIGNATURE-----\n\n")
    48		payload := line[0:lastEq]
    49		crc := line[lastEq:]
    50		for len(payload) > 0 {
    51			chunkLen := len(payload)
    52			if chunkLen > 60 {
    53				chunkLen = 60
    54			}
    55			fmt.Fprintf(buf, "%s\n", payload[0:chunkLen])
    56			payload = payload[chunkLen:]
    57		}
    58		fmt.Fprintf(buf, "%s\n-----END PGP SIGNATURE-----\n", crc)
    59		return buf.String()
    60	}
    61	
    62	// See doc/json-signing/* for background and details
    63	// on these variable names.
    64	type VerifyRequest struct {
    65		fetcher blob.Fetcher // fetcher used to find public key blob
    66	
    67		ba  []byte // "bytes all"
    68		bp  []byte // "bytes payload" (the part that is signed)
    69		bpj []byte // "bytes payload, JSON" (BP + "}")
    70		bs  []byte // "bytes signature", "{" + separator + camliSig, valid JSON
    71	
    72		CamliSigner     blob.Ref
    73		CamliSig        string
    74		PublicKeyPacket *packet.PublicKey
    75	
    76		// set if Verify() returns true:
    77		PayloadMap  map[string]interface{} // The JSON values from BPJ
    78		SignerKeyId string                 // e.g. "2931A67C26F5ABDA"
    79	
    80		Err error // last error encountered
    81	}
    82	
    83	func (vr *VerifyRequest) fail(msg string) bool {
    84		vr.Err = errors.New("jsonsign: " + msg)
    85		return false
    86	}
    87	
    88	func (vr *VerifyRequest) ParseSigMap() bool {
    89		sigMap := make(map[string]interface{})
    90		if err := json.Unmarshal(vr.bs, &sigMap); err != nil {
    91			return vr.fail("invalid JSON in signature")
    92		}
    93	
    94		if len(sigMap) != 1 {
    95			return vr.fail("signature JSON didn't have exactly 1 key")
    96		}
    97	
    98		sigVal, hasCamliSig := sigMap["camliSig"]
    99		if !hasCamliSig {
   100			return vr.fail("no 'camliSig' key in signature")
   101		}
   102	
   103		var ok bool
   104		vr.CamliSig, ok = sigVal.(string)
   105		if !ok {
   106			return vr.fail("camliSig not a string")
   107		}
   108	
   109		return true
   110	}
   111	
   112	func (vr *VerifyRequest) ParsePayloadMap() bool {
   113		vr.PayloadMap = make(map[string]interface{})
   114		pm := vr.PayloadMap
   115	
   116		if err := json.Unmarshal(vr.bpj, &pm); err != nil {
   117			return vr.fail("parse error; payload JSON is invalid")
   118		}
   119	
   120		if _, hasVersion := pm["camliVersion"]; !hasVersion {
   121			return vr.fail("missing 'camliVersion' in the JSON payload")
   122		}
   123	
   124		signer, hasSigner := pm["camliSigner"]
   125		if !hasSigner {
   126			return vr.fail("missing 'camliSigner' in the JSON payload")
   127		}
   128	
   129		if _, ok := signer.(string); !ok {
   130			return vr.fail("invalid 'camliSigner' in the JSON payload")
   131		}
   132	
   133		var ok bool
   134		vr.CamliSigner, ok = blob.Parse(signer.(string))
   135		if !ok {
   136			return vr.fail("malformed 'camliSigner' blobref in the JSON payload")
   137		}
   138		return true
   139	}
   140	
   141	func (vr *VerifyRequest) FindAndParsePublicKeyBlob(ctx context.Context) error {
   142		reader, _, err := vr.fetcher.Fetch(ctx, vr.CamliSigner)
   143		if err != nil {
   144			if err == os.ErrNotExist {
   145				return camerrors.ErrMissingKeyBlob
   146			}
   147			log.Printf("error fetching public key blob %v: %v", vr.CamliSigner, err)
   148			return err
   149		}
   150		defer reader.Close()
   151		pk, err := openArmoredPublicKeyFile(reader)
   152		if err != nil {
   153			return fmt.Errorf("error opening public key file: %v", err)
   154		}
   155		vr.PublicKeyPacket = pk
   156		return nil
   157	}
   158	
   159	func (vr *VerifyRequest) VerifySignature() bool {
   160		armorData := reArmor(vr.CamliSig)
   161		block, _ := armor.Decode(bytes.NewBufferString(armorData))
   162		if block == nil {
   163			return vr.fail("can't parse camliSig armor")
   164		}
   165		var p packet.Packet
   166		var err error
   167		p, err = packet.Read(block.Body)
   168		if err != nil {
   169			return vr.fail("error reading PGP packet from camliSig: " + err.Error())
   170		}
   171		sig, ok := p.(*packet.Signature)
   172		if !ok {
   173			return vr.fail("PGP packet isn't a signature packet")
   174		}
   175		if sig.Hash != crypto.SHA1 && sig.Hash != crypto.SHA256 {
   176			return vr.fail("I can only verify SHA1 or SHA256 signatures")
   177		}
   178		if sig.SigType != packet.SigTypeBinary {
   179			return vr.fail("I can only verify binary signatures")
   180		}
   181		hash := sig.Hash.New()
   182		hash.Write(vr.bp) // payload bytes
   183		err = vr.PublicKeyPacket.VerifySignature(hash, sig)
   184		if err != nil {
   185			return vr.fail(fmt.Sprintf("bad signature: %s", err))
   186		}
   187		vr.SignerKeyId = vr.PublicKeyPacket.KeyIdString()
   188		return true
   189	}
   190	
   191	func NewVerificationRequest(sjson string, fetcher blob.Fetcher) (vr *VerifyRequest) {
   192		if fetcher == nil {
   193			panic("NewVerificationRequest fetcher is nil")
   194		}
   195		vr = new(VerifyRequest)
   196		vr.ba = []byte(sjson)
   197		vr.fetcher = fetcher
   198	
   199		sigIndex := bytes.LastIndex(vr.ba, []byte(sigSeparator))
   200		if sigIndex == -1 {
   201			vr.Err = errors.New("jsonsign: no 13-byte camliSig separator found in sjson")
   202			return
   203		}
   204	
   205		// "Bytes Payload"
   206		vr.bp = vr.ba[:sigIndex]
   207	
   208		// "Bytes Payload JSON".  Note we re-use the memory (the ",")
   209		// from BA in BPJ, so we can't re-use that "," byte for
   210		// the opening "{" in "BS".
   211		vr.bpj = vr.ba[:sigIndex+1]
   212		vr.bpj[sigIndex] = '}'
   213		vr.bs = []byte("{" + sjson[sigIndex+1:])
   214		return
   215	}
   216	
   217	type VerifiedSignature struct {
   218		// TODO:
   219	}
   220	
   221	func (vr *VerifyRequest) Verify(ctx context.Context) (info VerifiedSignature, err error) {
   222		if vr.Err != nil {
   223			return VerifiedSignature{}, vr.Err
   224		}
   225		defer func() {
   226			if err != nil {
   227				// Don't allow callers to accidentally check this if it's not
   228				// valid.
   229				vr.PayloadMap = nil
   230				if vr.Err == nil {
   231					// The other functions should have filled this in
   232					// already, but just in case:
   233					vr.Err = err
   234				}
   235			}
   236		}()
   237	
   238		if !vr.ParseSigMap() {
   239			return VerifiedSignature{}, errors.New("parsing signature map failed")
   240		}
   241		if !vr.ParsePayloadMap() {
   242			return VerifiedSignature{}, errors.New("parsing payload map failed")
   243		}
   244		if err := vr.FindAndParsePublicKeyBlob(ctx); err != nil {
   245			return VerifiedSignature{}, err
   246		}
   247		if !vr.VerifySignature() {
   248			return VerifiedSignature{}, errors.New("signature verification failed")
   249		}
   250	
   251		return VerifiedSignature{
   252			// ...
   253		}, nil
   254	}
Website layout inspired by memcached.
Content by the authors.