github.com/apptainer/singularity@v3.1.1+incompatible/pkg/sypgp/sypgp.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 sypgp implements the openpgp integration into the singularity project. 7 package sypgp 8 9 import ( 10 "bufio" 11 "bytes" 12 "crypto" 13 "fmt" 14 "io/ioutil" 15 "net/http" 16 "net/url" 17 "os" 18 "path/filepath" 19 "strconv" 20 "strings" 21 22 "github.com/sylabs/singularity/internal/pkg/sylog" 23 "github.com/sylabs/singularity/internal/pkg/util/user" 24 "github.com/sylabs/singularity/pkg/util/user-agent" 25 "golang.org/x/crypto/openpgp" 26 "golang.org/x/crypto/openpgp/armor" 27 "golang.org/x/crypto/openpgp/packet" 28 "golang.org/x/crypto/ssh/terminal" 29 ) 30 31 const helpAuth = `Access token is expired or missing. To update or obtain a token: 32 1) Go to : https://cloud.sylabs.io/ 33 2) Click "Sign in to Sylabs" and follow the sign in steps 34 3) Click on your login id (same and updated button as the Sign in one) 35 4) Select "Access Tokens" from the drop down menu 36 5) Click the "Manage my API tokens" button from the "Account Management" page 37 6) Click "Create" 38 7) Click "Copy token to Clipboard" from the "New API Token" page 39 8) Paste the token string to the waiting prompt below and then press "Enter" 40 41 WARNING: this may overwrite a previous token if ~/.singularity/sylabs-token exists 42 43 ` 44 45 // routine that outputs signature type (applies to vindex operation) 46 func printSigType(sig *packet.Signature) { 47 switch sig.SigType { 48 case packet.SigTypeBinary: 49 fmt.Printf("sbin ") 50 case packet.SigTypeText: 51 fmt.Printf("stext") 52 case packet.SigTypeGenericCert: 53 fmt.Printf("sgenc") 54 case packet.SigTypePersonaCert: 55 fmt.Printf("sperc") 56 case packet.SigTypeCasualCert: 57 fmt.Printf("scasc") 58 case packet.SigTypePositiveCert: 59 fmt.Printf("sposc") 60 case packet.SigTypeSubkeyBinding: 61 fmt.Printf("sbind") 62 case packet.SigTypePrimaryKeyBinding: 63 fmt.Printf("sprib") 64 case packet.SigTypeDirectSignature: 65 fmt.Printf("sdirc") 66 case packet.SigTypeKeyRevocation: 67 fmt.Printf("skrev") 68 case packet.SigTypeSubkeyRevocation: 69 fmt.Printf("sbrev") 70 } 71 } 72 73 // routine that displays signature information (applies to vindex operation) 74 func putSigInfo(sig *packet.Signature) { 75 fmt.Print("sig ") 76 printSigType(sig) 77 fmt.Print(" ") 78 if sig.IssuerKeyId != nil { 79 fmt.Printf("%08X ", uint32(*sig.IssuerKeyId)) 80 } 81 y, m, d := sig.CreationTime.Date() 82 fmt.Printf("%02d-%02d-%02d ", y, m, d) 83 } 84 85 // output all the signatures related to a key (entity) (applies to vindex 86 // operation). 87 func printSignatures(entity *openpgp.Entity) error { 88 fmt.Println("=>++++++++++++++++++++++++++++++++++++++++++++++++++") 89 90 fmt.Printf("uid ") 91 for _, i := range entity.Identities { 92 fmt.Printf("%s", i.Name) 93 } 94 fmt.Println("") 95 96 // Self signature and other Signatures 97 for _, i := range entity.Identities { 98 if i.SelfSignature != nil { 99 putSigInfo(i.SelfSignature) 100 fmt.Printf("--------- --------- [selfsig]\n") 101 } 102 for _, s := range i.Signatures { 103 putSigInfo(s) 104 fmt.Printf("--------- --------- ---------\n") 105 } 106 } 107 108 // Revocation Signatures 109 for _, s := range entity.Revocations { 110 putSigInfo(s) 111 fmt.Printf("--------- --------- ---------\n") 112 } 113 fmt.Println("") 114 115 // Subkeys Signatures 116 for _, sub := range entity.Subkeys { 117 putSigInfo(sub.Sig) 118 fmt.Printf("--------- --------- [%s]\n", entity.PrimaryKey.KeyIdShortString()) 119 } 120 121 fmt.Println("<=++++++++++++++++++++++++++++++++++++++++++++++++++") 122 123 return nil 124 } 125 126 // AskQuestion prompts the user with a question and return the response 127 func AskQuestion(format string, a ...interface{}) (string, error) { 128 fmt.Printf(format, a...) 129 scanner := bufio.NewScanner(os.Stdin) 130 scanner.Scan() 131 response := scanner.Text() 132 if err := scanner.Err(); err != nil { 133 return "", err 134 } 135 return response, nil 136 } 137 138 // AskQuestionNoEcho works like AskQuestion() except it doesn't echo user's input 139 func AskQuestionNoEcho(format string, a ...interface{}) (string, error) { 140 fmt.Printf(format, a...) 141 response, err := terminal.ReadPassword(int(os.Stdin.Fd())) 142 fmt.Println("") 143 if err != nil { 144 return "", err 145 } 146 return string(response), nil 147 } 148 149 // GetTokenFile returns a string describing the path to the stored token file 150 func GetTokenFile() string { 151 user, err := user.GetPwUID(uint32(os.Getuid())) 152 if err != nil { 153 sylog.Warningf("could not lookup user's real home folder %s", err) 154 sylog.Warningf("using current directory for %s", filepath.Join(".singularity", "sylabs-token")) 155 return filepath.Join(".singularity", "sylabs-token") 156 } 157 158 return filepath.Join(user.Dir, ".singularity", "sylabs-token") 159 } 160 161 // DirPath returns a string describing the path to the sypgp home folder 162 func DirPath() string { 163 user, err := user.GetPwUID(uint32(os.Getuid())) 164 if err != nil { 165 sylog.Warningf("could not lookup user's real home folder %s", err) 166 sylog.Warningf("using current directory for %s", filepath.Join(".singularity", "sypgp")) 167 return filepath.Join(".singularity", "sypgp") 168 } 169 170 return filepath.Join(user.Dir, ".singularity", "sypgp") 171 } 172 173 // SecretPath returns a string describing the path to the private keys store 174 func SecretPath() string { 175 return filepath.Join(DirPath(), "pgp-secret") 176 } 177 178 // PublicPath returns a string describing the path to the public keys store 179 func PublicPath() string { 180 return filepath.Join(DirPath(), "pgp-public") 181 } 182 183 // PathsCheck creates the sypgp home folder, secret and public keyring files 184 func PathsCheck() error { 185 // create the sypgp base directory 186 if err := os.MkdirAll(DirPath(), 0700); err != nil { 187 return err 188 } 189 190 dirinfo, err := os.Stat(DirPath()) 191 if err != nil { 192 return err 193 } 194 if dirinfo.Mode() != os.ModeDir|0700 { 195 sylog.Warningf("directory mode (%v) on %v needs to be 0700, fixing that...", dirinfo.Mode(), DirPath()) 196 if err = os.Chmod(DirPath(), 0700); err != nil { 197 return err 198 } 199 } 200 201 // create or open the secret OpenPGP key cache file 202 fs, err := os.OpenFile(SecretPath(), os.O_RDWR|os.O_CREATE, 0600) 203 if err != nil { 204 return err 205 } 206 defer fs.Close() 207 208 // check and fix permissions (secret cache file) 209 fsinfo, err := fs.Stat() 210 if err != nil { 211 return err 212 } 213 if fsinfo.Mode() != 0600 { 214 sylog.Warningf("file mode (%v) on %v needs to be 0600, fixing that...", fsinfo.Mode(), SecretPath()) 215 if err = fs.Chmod(0600); err != nil { 216 return err 217 } 218 } 219 220 // create or open the public OpenPGP key cache file 221 fp, err := os.OpenFile(PublicPath(), os.O_RDWR|os.O_CREATE, 0600) 222 if err != nil { 223 return err 224 } 225 defer fp.Close() 226 227 // check and fix permissions (public cache file) 228 fpinfo, err := fp.Stat() 229 if err != nil { 230 return err 231 } 232 if fpinfo.Mode() != 0600 { 233 sylog.Warningf("file mode (%v) on %v needs to be 0600, fixing that...", fpinfo.Mode(), PublicPath()) 234 if err = fp.Chmod(0600); err != nil { 235 return err 236 } 237 } 238 return nil 239 } 240 241 // LoadPrivKeyring loads the private keys from local store into an EntityList 242 func LoadPrivKeyring() (openpgp.EntityList, error) { 243 if err := PathsCheck(); err != nil { 244 return nil, err 245 } 246 247 f, err := os.Open(SecretPath()) 248 if err != nil { 249 return nil, err 250 } 251 defer f.Close() 252 253 el, err := openpgp.ReadKeyRing(f) 254 if err != nil { 255 return nil, err 256 } 257 258 return el, nil 259 } 260 261 // LoadPubKeyring loads the public keys from local store into an EntityList 262 func LoadPubKeyring() (openpgp.EntityList, error) { 263 if err := PathsCheck(); err != nil { 264 return nil, err 265 } 266 267 f, err := os.Open(PublicPath()) 268 if err != nil { 269 return nil, err 270 } 271 defer f.Close() 272 273 el, err := openpgp.ReadKeyRing(f) 274 if err != nil { 275 return nil, err 276 } 277 278 return el, nil 279 } 280 281 // PrintEntity pretty prints an entity entry 282 func PrintEntity(index int, e *openpgp.Entity) { 283 for _, v := range e.Identities { 284 fmt.Printf("%v) U: %v (%v) <%v>\n", index, v.UserId.Name, v.UserId.Comment, v.UserId.Email) 285 } 286 fmt.Printf(" C: %v\n", e.PrimaryKey.CreationTime) 287 fmt.Printf(" F: %0X\n", e.PrimaryKey.Fingerprint) 288 bits, _ := e.PrimaryKey.BitLength() 289 fmt.Printf(" L: %v\n", bits) 290 } 291 292 // PrintPubKeyring prints the public keyring read from the public local store 293 func PrintPubKeyring() (err error) { 294 var pubEntlist openpgp.EntityList 295 296 if pubEntlist, err = LoadPubKeyring(); err != nil { 297 return 298 } 299 300 for i, e := range pubEntlist { 301 PrintEntity(i, e) 302 fmt.Println(" --------") 303 } 304 305 return 306 } 307 308 // PrintPrivKeyring prints the secret keyring read from the public local store 309 func PrintPrivKeyring() (err error) { 310 var privEntlist openpgp.EntityList 311 312 if privEntlist, err = LoadPrivKeyring(); err != nil { 313 return 314 } 315 316 for i, e := range privEntlist { 317 PrintEntity(i, e) 318 fmt.Println(" --------") 319 } 320 321 return 322 } 323 324 // StorePrivKey stores a private entity list into the local key cache 325 func StorePrivKey(e *openpgp.Entity) (err error) { 326 f, err := os.OpenFile(SecretPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 327 if err != nil { 328 return 329 } 330 defer f.Close() 331 332 if err = e.SerializePrivate(f, nil); err != nil { 333 return 334 } 335 return 336 } 337 338 // StorePubKey stores a public key entity list into the local key cache 339 func StorePubKey(e *openpgp.Entity) (err error) { 340 f, err := os.OpenFile(PublicPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 341 if err != nil { 342 return 343 } 344 defer f.Close() 345 346 if err = e.Serialize(f); err != nil { 347 return 348 } 349 return 350 } 351 352 // GenKeyPair generates an OpenPGP key pair and store them in the sypgp home folder 353 func GenKeyPair() (entity *openpgp.Entity, err error) { 354 conf := &packet.Config{RSABits: 4096, DefaultHash: crypto.SHA384} 355 356 if err = PathsCheck(); err != nil { 357 return 358 } 359 360 name, err := AskQuestion("Enter your name (e.g., John Doe) : ") 361 if err != nil { 362 return 363 } 364 365 email, err := AskQuestion("Enter your email address (e.g., john.doe@example.com) : ") 366 if err != nil { 367 return 368 } 369 370 comment, err := AskQuestion("Enter optional comment (e.g., development keys) : ") 371 if err != nil { 372 return 373 } 374 375 fmt.Print("Generating Entity and OpenPGP Key Pair... ") 376 entity, err = openpgp.NewEntity(name, comment, email, conf) 377 if err != nil { 378 return 379 } 380 fmt.Println("Done") 381 382 // encrypt private key 383 pass, err := AskQuestionNoEcho("Enter encryption passphrase : ") 384 if err != nil { 385 return 386 } 387 if err = EncryptKey(entity, pass); err != nil { 388 return 389 } 390 391 // Store key parts in local key caches 392 if err = StorePrivKey(entity); err != nil { 393 return 394 } 395 if err = StorePubKey(entity); err != nil { 396 return 397 } 398 399 return 400 } 401 402 // DecryptKey decrypts a private key provided a pass phrase 403 func DecryptKey(k *openpgp.Entity) error { 404 if k.PrivateKey.Encrypted == true { 405 pass, err := AskQuestionNoEcho("Enter key passphrase: ") 406 if err != nil { 407 return err 408 } 409 410 if err := k.PrivateKey.Decrypt([]byte(pass)); err != nil { 411 return err 412 } 413 } 414 return nil 415 } 416 417 // EncryptKey encrypts a private key using a pass phrase 418 func EncryptKey(k *openpgp.Entity, pass string) (err error) { 419 if k.PrivateKey.Encrypted == true { 420 return fmt.Errorf("key already encrypted") 421 } 422 err = k.PrivateKey.Encrypt([]byte(pass)) 423 return 424 } 425 426 // SelectPubKey prints a public key list to user and returns the choice 427 func SelectPubKey(el openpgp.EntityList) (*openpgp.Entity, error) { 428 PrintPubKeyring() 429 430 index, err := AskQuestion("Enter # of public key to use : ") 431 if err != nil { 432 return nil, err 433 } 434 if index == "" { 435 return nil, fmt.Errorf("invalid key choice") 436 } 437 i, err := strconv.ParseUint(index, 10, 32) 438 if err != nil { 439 return nil, err 440 } 441 442 if i < 0 || i > uint64(len(el))-1 { 443 return nil, fmt.Errorf("invalid key choice") 444 } 445 446 return el[i], nil 447 } 448 449 // SelectPrivKey prints a secret key list to user and returns the choice 450 func SelectPrivKey(el openpgp.EntityList) (*openpgp.Entity, error) { 451 PrintPrivKeyring() 452 453 index, err := AskQuestion("Enter # of signing key to use : ") 454 if err != nil { 455 return nil, err 456 } 457 if index == "" { 458 return nil, fmt.Errorf("invalid key choice") 459 } 460 i, err := strconv.ParseUint(index, 10, 32) 461 if err != nil { 462 return nil, err 463 } 464 465 if i < 0 || i > uint64(len(el))-1 { 466 return nil, fmt.Errorf("invalid key choice") 467 } 468 469 return el[i], nil 470 } 471 472 // helpAuthentication advises the client on how to procure an authentication token 473 func helpAuthentication() (token string, err error) { 474 sylog.Infof(helpAuth) 475 476 token, err = AskQuestion("Paste Token HERE: ") 477 if err != nil { 478 return "", fmt.Errorf("could not read pasted token: %s", err) 479 } 480 481 // Create/Overwrite token file 482 err = ioutil.WriteFile(GetTokenFile(), []byte(token), 0600) 483 if err != nil { 484 return "", fmt.Errorf("could not create/update token file: %s", err) 485 } 486 487 return 488 } 489 490 // doSearchRequest prepares an HKP search request 491 func doSearchRequest(search, keyserverURI, authToken string) (*http.Request, error) { 492 v := url.Values{} 493 v.Set("search", search) 494 v.Set("op", "index") 495 v.Set("fingerprint", "on") 496 497 u, err := url.Parse(keyserverURI) 498 if err != nil { 499 return nil, err 500 } 501 u.Path = "pks/lookup" 502 u.RawQuery = v.Encode() 503 504 r, err := http.NewRequest(http.MethodGet, u.String(), nil) 505 if err != nil { 506 return nil, err 507 } 508 if authToken != "" { 509 r.Header.Set("Authorization", fmt.Sprintf("BEARER %s", authToken)) 510 } 511 r.Header.Set("User-Agent", useragent.Value()) 512 513 return r, nil 514 } 515 516 // SearchPubkey connects to a key server and searches for a specific key 517 func SearchPubkey(search, keyserverURI, authToken string) (string, error) { 518 r, err := doSearchRequest(search, keyserverURI, authToken) 519 if err != nil { 520 return "", fmt.Errorf("error while preparing http request: %s", err) 521 } 522 523 resp, err := http.DefaultClient.Do(r) 524 if err != nil { 525 return "", err 526 } 527 defer resp.Body.Close() 528 529 // check if error is authentication failure and help user when it's the case 530 if resp.StatusCode == http.StatusUnauthorized { 531 token, err := helpAuthentication() 532 if err != nil { 533 return "", fmt.Errorf("could not obtain or install authentication token: %s", err) 534 } 535 // try request again 536 r, err := doSearchRequest(search, keyserverURI, token) 537 if err != nil { 538 return "", fmt.Errorf("error while preparing http request: %s", err) 539 } 540 resp, err = http.DefaultClient.Do(r) 541 if err != nil { 542 return "", err 543 } 544 } 545 546 if resp.StatusCode == http.StatusNotFound { 547 return "", fmt.Errorf("no keys match provided search string") 548 } 549 550 b, err := ioutil.ReadAll(resp.Body) 551 if err != nil { 552 return "", err 553 } 554 555 return string(b), nil 556 } 557 558 // doFetchRequest prepares an HKP get request 559 func doFetchRequest(fingerprint, keyserverURI, authToken string) (*http.Request, error) { 560 v := url.Values{} 561 v.Set("op", "get") 562 v.Set("options", "mr") 563 v.Set("search", "0x"+fingerprint) 564 565 u, err := url.Parse(keyserverURI) 566 if err != nil { 567 return nil, err 568 } 569 u.Path = "pks/lookup" 570 u.RawQuery = v.Encode() 571 572 r, err := http.NewRequest(http.MethodGet, u.String(), nil) 573 if err != nil { 574 return nil, err 575 } 576 if authToken != "" { 577 r.Header.Set("Authorization", fmt.Sprintf("BEARER %s", authToken)) 578 } 579 r.Header.Set("User-Agent", useragent.Value()) 580 581 return r, nil 582 } 583 584 // FetchPubkey connects to a key server and requests a specific key 585 func FetchPubkey(fingerprint, keyserverURI, authToken string, noPrompt bool) (openpgp.EntityList, error) { 586 r, err := doFetchRequest(fingerprint, keyserverURI, authToken) 587 if err != nil { 588 return nil, fmt.Errorf("error while preparing http request: %s", err) 589 } 590 591 resp, err := http.DefaultClient.Do(r) 592 if err != nil { 593 return nil, err 594 } 595 defer resp.Body.Close() 596 597 // check if error is authentication failure and help user when it's the case 598 if resp.StatusCode == http.StatusUnauthorized { 599 if noPrompt { 600 return nil, fmt.Errorf("%s returned %s", keyserverURI, http.StatusText(http.StatusUnauthorized)) 601 } 602 token, err := helpAuthentication() 603 if err != nil { 604 return nil, fmt.Errorf("could not obtain or install authentication token: %s", err) 605 } 606 // try request again 607 r, err := doFetchRequest(fingerprint, keyserverURI, token) 608 if err != nil { 609 return nil, fmt.Errorf("error while preparing http request: %s", err) 610 } 611 resp, err = http.DefaultClient.Do(r) 612 if err != nil { 613 return nil, err 614 } 615 } 616 617 if resp.StatusCode == http.StatusNotFound { 618 return nil, fmt.Errorf("no matching keys found for fingerprint") 619 } 620 621 el, err := openpgp.ReadArmoredKeyRing(resp.Body) 622 if err != nil { 623 return nil, err 624 } 625 if len(el) == 0 { 626 return nil, fmt.Errorf("no keys in keyring") 627 } 628 if len(el) > 1 { 629 return nil, fmt.Errorf("server returned more than one key for unique fingerprint") 630 } 631 632 return el, nil 633 } 634 635 // doPushRequest prepares an HKP pks/add request 636 func doPushRequest(w *bytes.Buffer, keyserverURI, authToken string) (*http.Request, error) { 637 v := url.Values{} 638 v.Set("keytext", w.String()) 639 640 u, err := url.Parse(keyserverURI) 641 if err != nil { 642 return nil, err 643 } 644 u.Path = "pks/add" 645 646 r, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(v.Encode())) 647 if err != nil { 648 return nil, err 649 } 650 if authToken != "" { 651 r.Header.Set("Authorization", fmt.Sprintf("BEARER %s", authToken)) 652 } 653 r.Header.Set("User-Agent", useragent.Value()) 654 r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 655 656 return r, nil 657 } 658 659 // PushPubkey pushes a public key to a key server 660 func PushPubkey(entity *openpgp.Entity, keyserverURI, authToken string) error { 661 w := bytes.NewBuffer(nil) 662 wr, err := armor.Encode(w, openpgp.PublicKeyType, nil) 663 if err != nil { 664 return err 665 } 666 667 err = entity.Serialize(wr) 668 if err != nil { 669 return err 670 } 671 wr.Close() 672 673 r, err := doPushRequest(w, keyserverURI, authToken) 674 if err != nil { 675 return fmt.Errorf("error while preparing http request: %s", err) 676 } 677 678 resp, err := http.DefaultClient.Do(r) 679 if err != nil { 680 return err 681 } 682 defer resp.Body.Close() 683 684 // check if error is authentication failure and help user when it's the case 685 if resp.StatusCode == http.StatusUnauthorized { 686 token, err := helpAuthentication() 687 if err != nil { 688 return fmt.Errorf("could not obtain or install authentication token: %s", err) 689 } 690 // try request again 691 r, err := doPushRequest(w, keyserverURI, token) 692 if err != nil { 693 return fmt.Errorf("error while preparing http request: %s", err) 694 } 695 resp, err = http.DefaultClient.Do(r) 696 if err != nil { 697 return err 698 } 699 } 700 701 if resp.StatusCode != http.StatusOK { 702 return fmt.Errorf("Key server did not accept OpenPGP key, HTTP status: %v", resp.StatusCode) 703 } 704 705 return nil 706 }