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  }