Home Download Docs Code Community
     1	/*
     2	Copyright 2013 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 schema
    18	
    19	import (
    20		"context"
    21		"errors"
    22		"fmt"
    23		"io"
    24		"io/ioutil"
    25		"os"
    26		"strings"
    27		"time"
    28	
    29		"perkeep.org/pkg/blob"
    30		"perkeep.org/pkg/jsonsign"
    31	
    32		"golang.org/x/crypto/openpgp"
    33	)
    34	
    35	// A Signer signs the JSON schema blobs that require signing, such as claims
    36	// and permanodes.
    37	type Signer struct {
    38		keyID      string // short one; 8 capital hex digits
    39		pubref     blob.Ref
    40		privEntity *openpgp.Entity
    41	
    42		// baseSigReq is the prototype signing request used with the jsonsig
    43		// package.
    44		baseSigReq jsonsign.SignRequest
    45	}
    46	
    47	func (s *Signer) String() string {
    48		return fmt.Sprintf("[*schema.Signer for key=%s pubkey=%s]", s.keyID, s.pubref)
    49	}
    50	
    51	// KeyID returns the short (8 digit) capital hex GPG key ID.
    52	func (s *Signer) KeyID() string {
    53		return s.keyID
    54	}
    55	
    56	// KeyIDLong returns the long (16 digit) capital hex GPG key ID.
    57	func (s *Signer) KeyIDLong() string {
    58		return s.privEntity.PrivateKey.KeyIdString()
    59	}
    60	
    61	// Entity returns the signer's entity, which includes its public and private keys.
    62	func (s *Signer) Entity() *openpgp.Entity {
    63		return s.privEntity
    64	}
    65	
    66	// NewSigner returns an Signer given an armored public key's blobref,
    67	// its armored content, and its associated private key entity.
    68	// The privateKeySource must be either an *openpgp.Entity or a string filename to a secret key.
    69	func NewSigner(pubKeyRef blob.Ref, armoredPubKey io.Reader, privateKeySource interface{}) (*Signer, error) {
    70		hash := pubKeyRef.Hash()
    71		keyID, armoredPubKeyString, err := jsonsign.ParseArmoredPublicKey(io.TeeReader(armoredPubKey, hash))
    72		if err != nil {
    73			return nil, err
    74		}
    75		if !pubKeyRef.HashMatches(hash) {
    76			return nil, fmt.Errorf("pubkey ref of %v doesn't match provided armored public key", pubKeyRef)
    77		}
    78	
    79		var privateKey *openpgp.Entity
    80		switch v := privateKeySource.(type) {
    81		case *openpgp.Entity:
    82			privateKey = v
    83		case string:
    84			privateKey, err = jsonsign.EntityFromSecring(keyID, v)
    85			if err != nil {
    86				return nil, err
    87			}
    88		default:
    89			return nil, fmt.Errorf("invalid privateKeySource type %T", v)
    90		}
    91		if privateKey == nil {
    92			return nil, errors.New("nil privateKey")
    93		}
    94	
    95		return &Signer{
    96			keyID:      keyID,
    97			pubref:     pubKeyRef,
    98			privEntity: privateKey,
    99			baseSigReq: jsonsign.SignRequest{
   100				ServerMode: true, // shouldn't matter, since we're supplying the rest of the fields
   101				Fetcher: memoryBlobFetcher{
   102					pubKeyRef: func() (uint32, io.ReadCloser) {
   103						return uint32(len(armoredPubKeyString)), ioutil.NopCloser(strings.NewReader(armoredPubKeyString))
   104					},
   105				},
   106				EntityFetcher: entityFetcherFunc(func(wantKeyId string) (*openpgp.Entity, error) {
   107					if privateKey.PrivateKey.KeyIdString() != wantKeyId &&
   108						privateKey.PrivateKey.KeyIdShortString() != wantKeyId {
   109						return nil, fmt.Errorf("jsonsign code unexpectedly requested keyID %q; only have %q",
   110							wantKeyId, keyID)
   111					}
   112					return privateKey, nil
   113				}),
   114			},
   115		}, nil
   116	}
   117	
   118	// SignJSON signs the provided json at the optional time t.
   119	// If t is the zero Time, the current time is used.
   120	func (s *Signer) SignJSON(ctx context.Context, json string, t time.Time) (string, error) {
   121		sr := s.baseSigReq
   122		sr.UnsignedJSON = json
   123		sr.SignatureTime = t
   124		return sr.Sign(ctx)
   125	}
   126	
   127	type memoryBlobFetcher map[blob.Ref]func() (size uint32, rc io.ReadCloser)
   128	
   129	func (m memoryBlobFetcher) Fetch(ctx context.Context, br blob.Ref) (file io.ReadCloser, size uint32, err error) {
   130		fn, ok := m[br]
   131		if !ok {
   132			return nil, 0, os.ErrNotExist
   133		}
   134		size, file = fn()
   135		return
   136	}
   137	
   138	type entityFetcherFunc func(keyID string) (*openpgp.Entity, error)
   139	
   140	func (f entityFetcherFunc) FetchEntity(keyID string) (*openpgp.Entity, error) {
   141		return f(keyID)
   142	}
Website layout inspired by memcached.
Content by the authors.