github.com/apptainer/singularity@v3.1.1+incompatible/pkg/signing/signing.go (about)

     1  // Copyright (c) 2018, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package signing
     7  
     8  import (
     9  	"bytes"
    10  	"crypto/sha512"
    11  	"encoding/binary"
    12  	"encoding/hex"
    13  	"fmt"
    14  	"os"
    15  
    16  	"github.com/sylabs/sif/pkg/sif"
    17  	"github.com/sylabs/singularity/internal/pkg/sylog"
    18  	"github.com/sylabs/singularity/pkg/sypgp"
    19  	"golang.org/x/crypto/openpgp"
    20  	"golang.org/x/crypto/openpgp/clearsign"
    21  )
    22  
    23  // computeHashStr generates a hash from data object(s) and generates a string
    24  // to be stored in the signature block
    25  func computeHashStr(fimg *sif.FileImage, descr []*sif.Descriptor) string {
    26  	hash := sha512.New384()
    27  	for _, v := range descr {
    28  		hash.Write(v.GetData(fimg))
    29  	}
    30  
    31  	sum := hash.Sum(nil)
    32  
    33  	return fmt.Sprintf("SIFHASH:\n%x", sum)
    34  }
    35  
    36  // sifAddSignature adds a signature block to a SIF file
    37  func sifAddSignature(fimg *sif.FileImage, groupid, link uint32, fingerprint [20]byte, signature []byte) error {
    38  	// data we need to create a signature descriptor
    39  	siginput := sif.DescriptorInput{
    40  		Datatype: sif.DataSignature,
    41  		Groupid:  groupid,
    42  		Link:     link,
    43  		Fname:    "part-signature",
    44  		Data:     signature,
    45  	}
    46  	siginput.Size = int64(binary.Size(siginput.Data))
    47  
    48  	// extra data needed for the creation of a signature descriptor
    49  	err := siginput.SetSignExtra(sif.HashSHA384, hex.EncodeToString(fingerprint[:]))
    50  	if err != nil {
    51  		return err
    52  	}
    53  
    54  	// add new signature data object to SIF file
    55  	err = fimg.AddObject(siginput)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	return nil
    61  }
    62  
    63  // descrToSign determines via argument or interactively which descriptor to sign
    64  func descrToSign(fimg *sif.FileImage, id uint32, isGroup bool) (descr []*sif.Descriptor, err error) {
    65  	descr = make([]*sif.Descriptor, 1)
    66  
    67  	if id == 0 {
    68  		descr[0], _, err = fimg.GetPartPrimSys()
    69  		if err != nil {
    70  			return nil, fmt.Errorf("no primary partition found")
    71  		}
    72  	} else if isGroup {
    73  		var search = sif.Descriptor{
    74  			Groupid: id | sif.DescrGroupMask,
    75  		}
    76  		descr, _, err = fimg.GetFromDescr(search)
    77  		if err != nil {
    78  			return nil, fmt.Errorf("no descriptors found for groupid %v", id)
    79  		}
    80  	} else {
    81  		descr[0], _, err = fimg.GetFromDescrID(id)
    82  		if err != nil {
    83  			return nil, fmt.Errorf("no descriptor found for id %v", id)
    84  		}
    85  	}
    86  
    87  	return
    88  }
    89  
    90  // Sign takes the path of a container and generates an OpenPGP signature block for
    91  // its system partition. Sign uses the private keys found in the default
    92  // location if available or helps the user by prompting with key generation
    93  // configuration options. In its current form, Sign also pushes, when desired,
    94  // public material to a key server.
    95  func Sign(cpath, url string, id uint32, isGroup bool, keyIdx int, authToken string) error {
    96  	elist, err := sypgp.LoadPrivKeyring()
    97  	if err != nil {
    98  		return fmt.Errorf("could not load private keyring: %s", err)
    99  	}
   100  
   101  	// Generate a private key usable for signing
   102  	var entity *openpgp.Entity
   103  	if elist == nil {
   104  		resp, err := sypgp.AskQuestion("No OpenPGP signing keys found, autogenerate? [Y/n] ")
   105  		if err != nil {
   106  			return fmt.Errorf("could not read response: %s", err)
   107  		}
   108  		if resp == "" || resp == "y" || resp == "Y" {
   109  			entity, err = sypgp.GenKeyPair()
   110  			if err != nil {
   111  				return fmt.Errorf("generating openpgp key pair failed: %s", err)
   112  			}
   113  		} else {
   114  			return fmt.Errorf("cannot sign without installed keys")
   115  		}
   116  		resp, err = sypgp.AskQuestion("Upload public key %X to %s? [Y/n] ", entity.PrimaryKey.Fingerprint, url)
   117  		if err != nil {
   118  			return err
   119  		}
   120  		if resp == "" || resp == "y" || resp == "Y" {
   121  			if err = sypgp.PushPubkey(entity, url, authToken); err != nil {
   122  				return fmt.Errorf("failed while pushing public key to server: %s", err)
   123  			}
   124  			fmt.Printf("Uploaded key successfully!\n")
   125  		}
   126  	} else {
   127  		if keyIdx != -1 { // -k <idx> has been specified
   128  			if keyIdx >= 0 && keyIdx < len(elist) {
   129  				entity = elist[keyIdx]
   130  			} else {
   131  				return fmt.Errorf("specified (-k, --keyidx) key index out of range")
   132  			}
   133  		} else if len(elist) > 1 {
   134  			entity, err = sypgp.SelectPrivKey(elist)
   135  			if err != nil {
   136  				return fmt.Errorf("failed while reading selection: %s", err)
   137  			}
   138  		} else {
   139  			entity = elist[0]
   140  		}
   141  	}
   142  
   143  	// Decrypt key if needed
   144  	if err = sypgp.DecryptKey(entity); err != nil {
   145  		return fmt.Errorf("could not decrypt private key, wrong password?")
   146  	}
   147  
   148  	// load the container
   149  	fimg, err := sif.LoadContainer(cpath, false)
   150  	if err != nil {
   151  		return fmt.Errorf("failed to load SIF container file: %s", err)
   152  	}
   153  	defer fimg.UnloadContainer()
   154  
   155  	// figure out which descriptor has data to sign
   156  	descr, err := descrToSign(&fimg, id, isGroup)
   157  	if err != nil {
   158  		return fmt.Errorf("signing requires a primary partition: %s", err)
   159  	}
   160  
   161  	// signature also include data integrity check
   162  	sifhash := computeHashStr(&fimg, descr)
   163  
   164  	// create an ascii armored signature block
   165  	var signedmsg bytes.Buffer
   166  	plaintext, err := clearsign.Encode(&signedmsg, entity.PrivateKey, nil)
   167  	if err != nil {
   168  		return fmt.Errorf("could not build a signature block: %s", err)
   169  	}
   170  	_, err = plaintext.Write([]byte(sifhash))
   171  	if err != nil {
   172  		return fmt.Errorf("failed writing hash value to signature block: %s", err)
   173  	}
   174  	if err = plaintext.Close(); err != nil {
   175  		return fmt.Errorf("I/O error while wrapping up signature block: %s", err)
   176  	}
   177  
   178  	// finally add the signature block (for descr) as a new SIF data object
   179  	var groupid, link uint32
   180  	if isGroup {
   181  		groupid = sif.DescrUnusedGroup
   182  		link = descr[0].Groupid
   183  	} else {
   184  		groupid = descr[0].Groupid
   185  		link = descr[0].ID
   186  	}
   187  	err = sifAddSignature(&fimg, groupid, link, entity.PrimaryKey.Fingerprint, signedmsg.Bytes())
   188  	if err != nil {
   189  		return fmt.Errorf("failed adding signature block to SIF container file: %s", err)
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  // return all signatures for the primary partition
   196  func getSigsPrimPart(fimg *sif.FileImage) (sigs []*sif.Descriptor, descr []*sif.Descriptor, err error) {
   197  	descr = make([]*sif.Descriptor, 1)
   198  
   199  	descr[0], _, err = fimg.GetPartPrimSys()
   200  	if err != nil {
   201  		return nil, nil, fmt.Errorf("no primary partition found")
   202  	}
   203  
   204  	sigs, _, err = fimg.GetFromLinkedDescr(descr[0].ID)
   205  	if err != nil {
   206  		return nil, nil, fmt.Errorf("no signatures found for system partition")
   207  	}
   208  
   209  	return
   210  }
   211  
   212  // return all signatures for specified descriptor
   213  func getSigsDescr(fimg *sif.FileImage, id uint32) (sigs []*sif.Descriptor, descr []*sif.Descriptor, err error) {
   214  	descr = make([]*sif.Descriptor, 1)
   215  
   216  	descr[0], _, err = fimg.GetFromDescrID(id)
   217  	if err != nil {
   218  		return nil, nil, fmt.Errorf("no descriptor found for id %v", id)
   219  	}
   220  
   221  	sigs, _, err = fimg.GetFromLinkedDescr(id)
   222  	if err != nil {
   223  		return nil, nil, fmt.Errorf("no signatures found for id %v", id)
   224  	}
   225  
   226  	return
   227  }
   228  
   229  // return all signatures for specified group
   230  func getSigsGroup(fimg *sif.FileImage, id uint32) (sigs []*sif.Descriptor, descr []*sif.Descriptor, err error) {
   231  	// find descriptors that are part of a signing group
   232  	search := sif.Descriptor{
   233  		Groupid: id | sif.DescrGroupMask,
   234  	}
   235  	descr, _, err = fimg.GetFromDescr(search)
   236  	if err != nil {
   237  		return nil, nil, fmt.Errorf("no descriptors found for groupid %v", id)
   238  	}
   239  
   240  	// find signature blocks pointing to specified group
   241  	search = sif.Descriptor{
   242  		Datatype: sif.DataSignature,
   243  		Link:     id | sif.DescrGroupMask,
   244  	}
   245  	sigs, _, err = fimg.GetFromDescr(search)
   246  	if err != nil {
   247  		return nil, nil, fmt.Errorf("no signatures found for groupid %v", id)
   248  	}
   249  
   250  	return
   251  }
   252  
   253  // return all signatures for "id" being unique or group id
   254  func getSigsForSelection(fimg *sif.FileImage, id uint32, isGroup bool) (sigs []*sif.Descriptor, descr []*sif.Descriptor, err error) {
   255  	if id == 0 {
   256  		return getSigsPrimPart(fimg)
   257  	} else if isGroup {
   258  		return getSigsGroup(fimg, id)
   259  	}
   260  	return getSigsDescr(fimg, id)
   261  }
   262  
   263  // Verify takes a container path and look for a verification block for a
   264  // specified descriptor. If found, the signature block is used to verify the
   265  // partition hash against the signer's version. Verify takes care of looking
   266  // for OpenPGP keys in the default local store or looks it up from a key server
   267  // if access is enabled.
   268  func Verify(cpath, url string, id uint32, isGroup bool, authToken string, noPrompt bool) error {
   269  	fimg, err := sif.LoadContainer(cpath, true)
   270  	if err != nil {
   271  		return fmt.Errorf("failed to load SIF container file: %s", err)
   272  	}
   273  	defer fimg.UnloadContainer()
   274  
   275  	// get all signature blocks (signatures) for ID/GroupID selected (descr) from SIF file
   276  	signatures, descr, err := getSigsForSelection(&fimg, id, isGroup)
   277  	if err != nil {
   278  		return fmt.Errorf("error while searching for signature blocks: %s", err)
   279  	}
   280  
   281  	// the selected data object is hashed for comparison against signature block's
   282  	sifhash := computeHashStr(&fimg, descr)
   283  
   284  	// load the public keys available locally from the cache
   285  	elist, err := sypgp.LoadPubKeyring()
   286  	if err != nil {
   287  		return fmt.Errorf("could not load public keyring: %s", err)
   288  	}
   289  
   290  	// compare freshly computed hash with hashes stored in signatures block(s)
   291  	var authok string
   292  	for _, v := range signatures {
   293  		// Extract hash string from signature block
   294  		data := v.GetData(&fimg)
   295  		block, _ := clearsign.Decode(data)
   296  		if block == nil {
   297  			return fmt.Errorf("failed to parse signature block")
   298  		}
   299  
   300  		if !bytes.Equal(bytes.TrimRight(block.Plaintext, "\n"), []byte(sifhash)) {
   301  			sylog.Infof("NOTE: group signatures will fail if new data is added to a group")
   302  			sylog.Infof("after the group signature is created.")
   303  			return fmt.Errorf("hashes differ, data may be corrupted")
   304  		}
   305  
   306  		// (1) Data integrity is verified, (2) now validate identify of signers
   307  
   308  		// get the entity fingerprint for the signature block
   309  		fingerprint, err := v.GetEntityString()
   310  		if err != nil {
   311  			return fmt.Errorf("could not get the signing entity fingerprint: %s", err)
   312  		}
   313  
   314  		// try to verify with local OpenPGP store first
   315  		signer, err := openpgp.CheckDetachedSignature(elist, bytes.NewBuffer(block.Bytes), block.ArmoredSignature.Body)
   316  		if err != nil {
   317  			// verification with local keyring failed, try to fetch from key server
   318  			sylog.Infof("key missing, searching key server for KeyID: %s...", fingerprint[24:])
   319  			netlist, err := sypgp.FetchPubkey(fingerprint, url, authToken, noPrompt)
   320  			if err != nil {
   321  				return fmt.Errorf("could not fetch public key from server: %s", err)
   322  			}
   323  			sylog.Infof("key retrieved successfully!")
   324  
   325  			block, _ := clearsign.Decode(data)
   326  			if block == nil {
   327  				return fmt.Errorf("failed to parse signature block")
   328  			}
   329  
   330  			// try verification again with downloaded key
   331  			signer, err = openpgp.CheckDetachedSignature(netlist, bytes.NewBuffer(block.Bytes), block.ArmoredSignature.Body)
   332  			if err != nil {
   333  				return fmt.Errorf("signature verification failed: %s", err)
   334  			}
   335  
   336  			if noPrompt {
   337  				// always store key when prompts disabled
   338  				if err = sypgp.StorePubKey(netlist[0]); err != nil {
   339  					return fmt.Errorf("could not store public key: %s", err)
   340  				}
   341  			} else {
   342  				// Ask to store new public key
   343  				resp, err := sypgp.AskQuestion("Store new public key %X? [Y/n] ", signer.PrimaryKey.Fingerprint)
   344  				if err != nil {
   345  					return err
   346  				}
   347  				if resp == "" || resp == "y" || resp == "Y" {
   348  					if err = sypgp.StorePubKey(netlist[0]); err != nil {
   349  						return fmt.Errorf("could not store public key: %s", err)
   350  					}
   351  				}
   352  			}
   353  		}
   354  
   355  		// Get first Identity data for convenience
   356  		var name string
   357  		for _, i := range signer.Identities {
   358  			name = i.Name
   359  			break
   360  		}
   361  		authok += fmt.Sprintf("\t%s, KeyID %X\n", name, signer.PrimaryKey.KeyId)
   362  	}
   363  	fmt.Printf("Data integrity checked, authentic and signed by:\n")
   364  	fmt.Print(authok)
   365  
   366  	return nil
   367  }
   368  
   369  func getSignEntities(fimg *sif.FileImage) ([]string, error) {
   370  	// get all signature blocks (signatures) for ID/GroupID selected (descr) from SIF file
   371  	signatures, _, err := getSigsPrimPart(fimg)
   372  	if err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	var entities []string
   377  	for _, v := range signatures {
   378  		fingerprint, err := v.GetEntityString()
   379  		if err != nil {
   380  			return nil, err
   381  		}
   382  		entities = append(entities, fingerprint)
   383  	}
   384  
   385  	return entities, nil
   386  }
   387  
   388  // GetSignEntities returns all signing entities for an ID/Groupid
   389  func GetSignEntities(cpath string) ([]string, error) {
   390  	fimg, err := sif.LoadContainer(cpath, true)
   391  	if err != nil {
   392  		return nil, err
   393  	}
   394  	defer fimg.UnloadContainer()
   395  
   396  	return getSignEntities(&fimg)
   397  }
   398  
   399  // GetSignEntitiesFp returns all signing entities for an ID/Groupid
   400  func GetSignEntitiesFp(fp *os.File) ([]string, error) {
   401  	fimg, err := sif.LoadContainerFp(fp, true)
   402  	if err != nil {
   403  		return nil, err
   404  	}
   405  
   406  	return getSignEntities(&fimg)
   407  }