github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/kbfsmd/root_metadata_signed.go (about)

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package kbfsmd
     6  
     7  import (
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  
    12  	"github.com/keybase/client/go/kbfs/kbfscodec"
    13  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    14  	"github.com/keybase/client/go/kbfs/tlf"
    15  	"github.com/keybase/client/go/protocol/keybase1"
    16  )
    17  
    18  // RootMetadataSigned is the top-level MD object stored in MD server
    19  type RootMetadataSigned struct {
    20  	// SigInfo is the signature over the root metadata by the
    21  	// last modifying user's private signing key.
    22  	SigInfo kbfscrypto.SignatureInfo
    23  	// WriterSigInfo is the signature over the writer metadata by
    24  	// the last modifying writer's private signing key.
    25  	WriterSigInfo kbfscrypto.SignatureInfo
    26  	// all the metadata
    27  	MD RootMetadata
    28  }
    29  
    30  func checkWriterSig(rmds *RootMetadataSigned) error {
    31  	if mdv2, ok := rmds.MD.(*RootMetadataV2); ok {
    32  		if !mdv2.WriterMetadataSigInfo.Equals(rmds.WriterSigInfo) {
    33  			return fmt.Errorf(
    34  				"Expected writer sig info %v, got %v",
    35  				mdv2.WriterMetadataSigInfo, rmds.WriterSigInfo)
    36  		}
    37  	}
    38  	return nil
    39  }
    40  
    41  // makeRootMetadataSigned makes a RootMetadataSigned object from the
    42  // given info. If md stores the writer signature info internally, it
    43  // must match the given one.
    44  func makeRootMetadataSigned(sigInfo, writerSigInfo kbfscrypto.SignatureInfo,
    45  	md RootMetadata) (*RootMetadataSigned, error) {
    46  	rmds := &RootMetadataSigned{
    47  		MD:            md,
    48  		SigInfo:       sigInfo,
    49  		WriterSigInfo: writerSigInfo,
    50  	}
    51  	err := checkWriterSig(rmds)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	return rmds, nil
    56  }
    57  
    58  // SignRootMetadata signs the given RootMetadata and returns a
    59  // *RootMetadataSigned object. rootMetadataSigner and
    60  // writerMetadataSigner should be the same, except in tests.
    61  func SignRootMetadata(
    62  	ctx context.Context, codec kbfscodec.Codec,
    63  	rootMetadataSigner, writerMetadataSigner kbfscrypto.Signer,
    64  	brmd RootMetadata) (*RootMetadataSigned, error) {
    65  	// encode the root metadata
    66  	buf, err := codec.Encode(brmd)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	var sigInfo, writerSigInfo kbfscrypto.SignatureInfo
    72  	if mdv2, ok := brmd.(*RootMetadataV2); ok {
    73  		// sign the root metadata
    74  		sigInfo, err = rootMetadataSigner.Sign(ctx, buf)
    75  		if err != nil {
    76  			return nil, err
    77  		}
    78  		// Assume that writerMetadataSigner has already signed
    79  		// mdv2 internally. If not, makeRootMetadataSigned
    80  		// will catch it.
    81  		writerSigInfo = mdv2.WriterMetadataSigInfo
    82  	} else {
    83  		// sign the root metadata -- use the KBFS signing prefix.
    84  		sigInfo, err = rootMetadataSigner.SignForKBFS(ctx, buf)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		buf, err = brmd.GetSerializedWriterMetadata(codec)
    89  		if err != nil {
    90  			return nil, err
    91  		}
    92  		// sign the writer metadata
    93  		writerSigInfo, err = writerMetadataSigner.SignForKBFS(ctx, buf)
    94  		if err != nil {
    95  			return nil, err
    96  		}
    97  	}
    98  	return makeRootMetadataSigned(sigInfo, writerSigInfo, brmd)
    99  }
   100  
   101  // GetWriterMetadataSigInfo returns the signature of the writer
   102  // metadata.
   103  func (rmds *RootMetadataSigned) GetWriterMetadataSigInfo() kbfscrypto.SignatureInfo {
   104  	return rmds.WriterSigInfo
   105  }
   106  
   107  // Version returns the metadata version of this MD block, depending on
   108  // which features it uses.
   109  func (rmds *RootMetadataSigned) Version() MetadataVer {
   110  	return rmds.MD.Version()
   111  }
   112  
   113  // MakeFinalCopy returns a complete copy of this RootMetadataSigned
   114  // with the revision incremented and the final bit set.
   115  func (rmds *RootMetadataSigned) MakeFinalCopy(
   116  	codec kbfscodec.Codec, finalizedInfo *tlf.HandleExtension) (*RootMetadataSigned, error) {
   117  	if finalizedInfo.Type != tlf.HandleExtensionFinalized {
   118  		return nil, fmt.Errorf(
   119  			"Extension %s does not have finalized type",
   120  			finalizedInfo)
   121  	}
   122  	if rmds.MD.IsFinal() {
   123  		return nil, MetadataIsFinalError{}
   124  	}
   125  	newMd, err := rmds.MD.DeepCopy(codec)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	// Set the final flag.
   130  	newMd.SetFinalBit()
   131  	// Set the copied bit, so that clients don't take the ops and byte
   132  	// counts in it seriously.
   133  	newMd.SetWriterMetadataCopiedBit()
   134  	// Increment revision but keep the PrevRoot --
   135  	// We want the client to be able to verify the signature by masking out the final
   136  	// bit, decrementing the revision, and nulling out the finalized extension info.
   137  	// This way it can easily tell a server didn't modify anything unexpected when
   138  	// creating the final metadata block. Note that PrevRoot isn't being updated. This
   139  	// is to make verification easier for the client as otherwise it'd need to request
   140  	// the head revision - 1.
   141  	newMd.SetRevision(rmds.MD.RevisionNumber() + 1)
   142  	newMd.SetFinalizedInfo(finalizedInfo)
   143  	return makeRootMetadataSigned(
   144  		rmds.SigInfo.DeepCopy(), rmds.WriterSigInfo.DeepCopy(),
   145  		newMd)
   146  }
   147  
   148  // IsValidAndSigned verifies the RootMetadataSigned, checks the root
   149  // signature, and returns an error if a problem was found.  This
   150  // should be the first thing checked on an RMDS retrieved from an
   151  // untrusted source, and then the signing users and keys should be
   152  // validated, either by comparing to the current device key (using
   153  // IsLastModifiedBy), or by checking with KBPKI.
   154  func (rmds *RootMetadataSigned) IsValidAndSigned(
   155  	ctx context.Context, codec kbfscodec.Codec,
   156  	teamMemChecker TeamMembershipChecker, extra ExtraMetadata,
   157  	offline keybase1.OfflineAvailability) error {
   158  	// Optimization -- if the RootMetadata signature is nil, it
   159  	// will fail verification.
   160  	if rmds.SigInfo.IsNil() {
   161  		return errors.New("Missing RootMetadata signature")
   162  	}
   163  	// Optimization -- if the WriterMetadata signature is nil, it
   164  	// will fail verification.
   165  	if rmds.WriterSigInfo.IsNil() {
   166  		return errors.New("Missing WriterMetadata signature")
   167  	}
   168  
   169  	err := rmds.MD.IsValidAndSigned(
   170  		ctx, codec, teamMemChecker, extra, rmds.WriterSigInfo.VerifyingKey,
   171  		offline)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	md := rmds.MD
   177  	if rmds.MD.IsFinal() {
   178  		mdCopy, err := md.DeepCopy(codec)
   179  		if err != nil {
   180  			return err
   181  		}
   182  		// Mask out finalized additions.  These are the only
   183  		// things allowed to change in the finalized metadata
   184  		// block.
   185  		mdCopy.ClearFinalBit()
   186  		mdCopy.ClearWriterMetadataCopiedBit()
   187  		mdCopy.SetRevision(md.RevisionNumber() - 1)
   188  		mdCopy.SetFinalizedInfo(nil)
   189  		md = mdCopy
   190  	}
   191  	// Re-marshal the whole RootMetadata. This is not avoidable
   192  	// without support from ugorji/codec.
   193  	buf, err := codec.Encode(md)
   194  	if err != nil {
   195  		return err
   196  	}
   197  
   198  	err = kbfscrypto.Verify(buf, rmds.SigInfo)
   199  	if err != nil {
   200  		return fmt.Errorf("Could not verify root metadata: %v", err)
   201  	}
   202  
   203  	buf, err = md.GetSerializedWriterMetadata(codec)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	err = kbfscrypto.Verify(buf, rmds.WriterSigInfo)
   209  	if err != nil {
   210  		return fmt.Errorf("Could not verify writer metadata: %v", err)
   211  	}
   212  
   213  	return nil
   214  }
   215  
   216  // IsLastModifiedBy verifies that the RootMetadataSigned is written by
   217  // the given user and device (identified by the device verifying key),
   218  // and returns an error if not.
   219  func (rmds *RootMetadataSigned) IsLastModifiedBy(
   220  	uid keybase1.UID, key kbfscrypto.VerifyingKey) error {
   221  	err := rmds.MD.IsLastModifiedBy(uid, key)
   222  	if err != nil {
   223  		return err
   224  	}
   225  
   226  	if rmds.SigInfo.VerifyingKey != key {
   227  		return fmt.Errorf("Last modifier verifying key %v != %v",
   228  			rmds.SigInfo.VerifyingKey, key)
   229  	}
   230  
   231  	writer := rmds.MD.LastModifyingWriter()
   232  	if !rmds.MD.IsWriterMetadataCopiedSet() {
   233  		if writer != uid {
   234  			return fmt.Errorf("Last writer %s != %s", writer, uid)
   235  		}
   236  		if rmds.WriterSigInfo.VerifyingKey != key {
   237  			return fmt.Errorf(
   238  				"Last writer verifying key %v != %v",
   239  				rmds.WriterSigInfo.VerifyingKey, key)
   240  		}
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  // EncodeRootMetadataSigned serializes a metadata block. This should
   247  // be used instead of directly calling codec.Encode(), as it handles
   248  // some version-specific quirks.
   249  func EncodeRootMetadataSigned(
   250  	codec kbfscodec.Codec, rmds *RootMetadataSigned) ([]byte, error) {
   251  	err := checkWriterSig(rmds)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	rmdsCopy := *rmds
   256  	if rmdsCopy.Version() < SegregatedKeyBundlesVer {
   257  		// For v2, the writer signature is in rmds.MD, so
   258  		// remove the one in rmds.
   259  		rmdsCopy.WriterSigInfo = kbfscrypto.SignatureInfo{}
   260  	}
   261  	return codec.Encode(rmdsCopy)
   262  }
   263  
   264  // DecodeRootMetadataSigned deserializes a metadata block into the
   265  // specified versioned structure.
   266  func DecodeRootMetadataSigned(
   267  	codec kbfscodec.Codec, tlf tlf.ID, ver, max MetadataVer, buf []byte) (
   268  	*RootMetadataSigned, error) {
   269  	rmd, err := makeMutableRootMetadataForDecode(codec, tlf, ver, max, buf)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  	rmds := RootMetadataSigned{
   274  		MD: rmd,
   275  	}
   276  	if err := codec.Decode(buf, &rmds); err != nil {
   277  		return nil, err
   278  	}
   279  	if ver < SegregatedKeyBundlesVer {
   280  		// For v2, the writer signature is in rmds.MD, so copy
   281  		// it out.
   282  		if !rmds.WriterSigInfo.IsNil() {
   283  			return nil, fmt.Errorf(
   284  				"Decoded RootMetadataSigned with version "+
   285  					"%d unexpectedly has non-nil "+
   286  					"writer signature %s",
   287  				ver, rmds.WriterSigInfo)
   288  		}
   289  		mdv2 := rmds.MD.(*RootMetadataV2)
   290  		rmds.WriterSigInfo = mdv2.WriterMetadataSigInfo
   291  	}
   292  	return &rmds, nil
   293  }