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 }