safing-portmaster/base/updater/fetch.go
2024-09-05 10:25:57 +03:00

347 lines
12 KiB
Go

package updater
// import (
// "bytes"
// "context"
// "errors"
// "fmt"
// "hash"
// "io"
// "net/http"
// "net/url"
// "os"
// "path"
// "path/filepath"
// "time"
// "github.com/safing/jess/filesig"
// "github.com/safing/jess/lhash"
// "github.com/safing/portmaster/base/log"
// "github.com/safing/portmaster/base/utils/renameio"
// )
// func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client, rv *ResourceVersion, tries int) error {
// // backoff when retrying
// if tries > 0 {
// select {
// case <-ctx.Done():
// return nil // module is shutting down
// case <-time.After(time.Duration(tries*tries) * time.Second):
// }
// }
// // check destination dir
// dirPath := filepath.Dir(rv.storagePath())
// err := reg.storageDir.EnsureAbsPath(dirPath)
// if err != nil {
// return fmt.Errorf("could not create updates folder: %s", dirPath)
// }
// // If verification is enabled, download signature first.
// var (
// verifiedHash *lhash.LabeledHash
// sigFileData []byte
// )
// if rv.resource.VerificationOptions != nil {
// verifiedHash, sigFileData, err = reg.fetchAndVerifySigFile(
// ctx, client,
// rv.resource.VerificationOptions,
// rv.versionedSigPath(), rv.SigningMetadata(),
// tries,
// )
// if err != nil {
// switch rv.resource.VerificationOptions.DownloadPolicy {
// case SignaturePolicyRequire:
// return fmt.Errorf("signature verification failed: %w", err)
// case SignaturePolicyWarn:
// log.Warningf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
// case SignaturePolicyDisable:
// log.Debugf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
// }
// }
// }
// // open file for writing
// atomicFile, err := renameio.TempFile(reg.tmpDir.Path, rv.storagePath())
// if err != nil {
// return fmt.Errorf("could not create temp file for download: %w", err)
// }
// defer atomicFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway
// // start file download
// resp, downloadURL, err := reg.makeRequest(ctx, client, rv.versionedPath(), tries)
// if err != nil {
// return err
// }
// defer func() {
// _ = resp.Body.Close()
// }()
// // Write to the hasher at the same time, if needed.
// var hasher hash.Hash
// var writeDst io.Writer = atomicFile
// if verifiedHash != nil {
// hasher = verifiedHash.Algorithm().RawHasher()
// writeDst = io.MultiWriter(hasher, atomicFile)
// }
// // Download and write file.
// n, err := io.Copy(writeDst, resp.Body)
// if err != nil {
// return fmt.Errorf("failed to download %q: %w", downloadURL, err)
// }
// if resp.ContentLength != n {
// return fmt.Errorf("failed to finish download of %q: written %d out of %d bytes", downloadURL, n, resp.ContentLength)
// }
// // Before file is finalized, check if hash, if available.
// if hasher != nil {
// downloadDigest := hasher.Sum(nil)
// if verifiedHash.EqualRaw(downloadDigest) {
// log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
// } else {
// switch rv.resource.VerificationOptions.DownloadPolicy {
// case SignaturePolicyRequire:
// return errors.New("file does not match signed checksum")
// case SignaturePolicyWarn:
// log.Warningf("%s: checksum does not match file from %s", reg.Name, downloadURL)
// case SignaturePolicyDisable:
// log.Debugf("%s: checksum does not match file from %s", reg.Name, downloadURL)
// }
// // Reset hasher to signal that the sig should not be written.
// hasher = nil
// }
// }
// // Write signature file, if we have one and if verification succeeded.
// if len(sigFileData) > 0 && hasher != nil {
// sigFilePath := rv.storagePath() + filesig.Extension
// err := os.WriteFile(sigFilePath, sigFileData, 0o0644) //nolint:gosec
// if err != nil {
// switch rv.resource.VerificationOptions.DownloadPolicy {
// case SignaturePolicyRequire:
// return fmt.Errorf("failed to write signature file %s: %w", sigFilePath, err)
// case SignaturePolicyWarn:
// log.Warningf("%s: failed to write signature file %s: %s", reg.Name, sigFilePath, err)
// case SignaturePolicyDisable:
// log.Debugf("%s: failed to write signature file %s: %s", reg.Name, sigFilePath, err)
// }
// }
// }
// // finalize file
// err = atomicFile.CloseAtomicallyReplace()
// if err != nil {
// return fmt.Errorf("%s: failed to finalize file %s: %w", reg.Name, rv.storagePath(), err)
// }
// // set permissions
// if !onWindows {
// // TODO: only set executable files to 0755, set other to 0644
// err = os.Chmod(rv.storagePath(), 0o0755) //nolint:gosec // See TODO above.
// if err != nil {
// log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
// }
// }
// log.Debugf("%s: fetched %s and stored to %s", reg.Name, downloadURL, rv.storagePath())
// return nil
// }
// func (reg *ResourceRegistry) fetchMissingSig(ctx context.Context, client *http.Client, rv *ResourceVersion, tries int) error {
// // backoff when retrying
// if tries > 0 {
// select {
// case <-ctx.Done():
// return nil // module is shutting down
// case <-time.After(time.Duration(tries*tries) * time.Second):
// }
// }
// // Check destination dir.
// dirPath := filepath.Dir(rv.storagePath())
// err := reg.storageDir.EnsureAbsPath(dirPath)
// if err != nil {
// return fmt.Errorf("could not create updates folder: %s", dirPath)
// }
// // Download and verify the missing signature.
// verifiedHash, sigFileData, err := reg.fetchAndVerifySigFile(
// ctx, client,
// rv.resource.VerificationOptions,
// rv.versionedSigPath(), rv.SigningMetadata(),
// tries,
// )
// if err != nil {
// switch rv.resource.VerificationOptions.DownloadPolicy {
// case SignaturePolicyRequire:
// return fmt.Errorf("signature verification failed: %w", err)
// case SignaturePolicyWarn:
// log.Warningf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
// case SignaturePolicyDisable:
// log.Debugf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
// }
// return nil
// }
// // Check if the signature matches the resource file.
// ok, err := verifiedHash.MatchesFile(rv.storagePath())
// if err != nil {
// switch rv.resource.VerificationOptions.DownloadPolicy {
// case SignaturePolicyRequire:
// return fmt.Errorf("error while verifying resource file: %w", err)
// case SignaturePolicyWarn:
// log.Warningf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
// case SignaturePolicyDisable:
// log.Debugf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
// }
// return nil
// }
// if !ok {
// switch rv.resource.VerificationOptions.DownloadPolicy {
// case SignaturePolicyRequire:
// return errors.New("resource file does not match signed checksum")
// case SignaturePolicyWarn:
// log.Warningf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
// case SignaturePolicyDisable:
// log.Debugf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
// }
// return nil
// }
// // Write signature file.
// err = os.WriteFile(rv.storageSigPath(), sigFileData, 0o0644) //nolint:gosec
// if err != nil {
// switch rv.resource.VerificationOptions.DownloadPolicy {
// case SignaturePolicyRequire:
// return fmt.Errorf("failed to write signature file %s: %w", rv.storageSigPath(), err)
// case SignaturePolicyWarn:
// log.Warningf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
// case SignaturePolicyDisable:
// log.Debugf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
// }
// }
// log.Debugf("%s: fetched %s and stored to %s", reg.Name, rv.versionedSigPath(), rv.storageSigPath())
// return nil
// }
// func (reg *ResourceRegistry) fetchAndVerifySigFile(ctx context.Context, client *http.Client, verifOpts *VerificationOptions, sigFilePath string, requiredMetadata map[string]string, tries int) (*lhash.LabeledHash, []byte, error) {
// // Download signature file.
// resp, _, err := reg.makeRequest(ctx, client, sigFilePath, tries)
// if err != nil {
// return nil, nil, err
// }
// defer func() {
// _ = resp.Body.Close()
// }()
// sigFileData, err := io.ReadAll(resp.Body)
// if err != nil {
// return nil, nil, err
// }
// // Extract all signatures.
// sigs, err := filesig.ParseSigFile(sigFileData)
// switch {
// case len(sigs) == 0 && err != nil:
// return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
// case len(sigs) == 0:
// return nil, nil, errors.New("no signatures found in signature file")
// case err != nil:
// return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
// }
// // Verify all signatures.
// var verifiedHash *lhash.LabeledHash
// for _, sig := range sigs {
// fd, err := filesig.VerifyFileData(
// sig,
// requiredMetadata,
// verifOpts.TrustStore,
// )
// if err != nil {
// return nil, sigFileData, err
// }
// // Save or check verified hash.
// if verifiedHash == nil {
// verifiedHash = fd.FileHash()
// } else if !fd.FileHash().Equal(verifiedHash) {
// // Return an error if two valid hashes mismatch.
// // For simplicity, all hash algorithms must be the same for now.
// return nil, sigFileData, errors.New("file hashes from different signatures do not match")
// }
// }
// return verifiedHash, sigFileData, nil
// }
// func (reg *ResourceRegistry) fetchData(ctx context.Context, client *http.Client, downloadPath string, tries int) (fileData []byte, downloadedFrom string, err error) {
// // backoff when retrying
// if tries > 0 {
// select {
// case <-ctx.Done():
// return nil, "", nil // module is shutting down
// case <-time.After(time.Duration(tries*tries) * time.Second):
// }
// }
// // start file download
// resp, downloadURL, err := reg.makeRequest(ctx, client, downloadPath, tries)
// if err != nil {
// return nil, downloadURL, err
// }
// defer func() {
// _ = resp.Body.Close()
// }()
// // download and write file
// buf := bytes.NewBuffer(make([]byte, 0, resp.ContentLength))
// n, err := io.Copy(buf, resp.Body)
// if err != nil {
// return nil, downloadURL, fmt.Errorf("failed to download %q: %w", downloadURL, err)
// }
// if resp.ContentLength != n {
// return nil, downloadURL, fmt.Errorf("failed to finish download of %q: written %d out of %d bytes", downloadURL, n, resp.ContentLength)
// }
// return buf.Bytes(), downloadURL, nil
// }
// func (reg *ResourceRegistry) makeRequest(ctx context.Context, client *http.Client, downloadPath string, tries int) (resp *http.Response, downloadURL string, err error) {
// // parse update URL
// updateBaseURL := reg.UpdateURLs[tries%len(reg.UpdateURLs)]
// u, err := url.Parse(updateBaseURL)
// if err != nil {
// return nil, "", fmt.Errorf("failed to parse update URL %q: %w", updateBaseURL, err)
// }
// // add download path
// u.Path = path.Join(u.Path, downloadPath)
// // compile URL
// downloadURL = u.String()
// // create request
// req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, http.NoBody)
// if err != nil {
// return nil, "", fmt.Errorf("failed to create request for %q: %w", downloadURL, err)
// }
// // set user agent
// if reg.UserAgent != "" {
// req.Header.Set("User-Agent", reg.UserAgent)
// }
// // start request
// resp, err = client.Do(req)
// if err != nil {
// return nil, "", fmt.Errorf("failed to make request to %q: %w", downloadURL, err)
// }
// // check return code
// if resp.StatusCode != http.StatusOK {
// _ = resp.Body.Close()
// return nil, "", fmt.Errorf("failed to fetch %q: %d %s", downloadURL, resp.StatusCode, resp.Status)
// }
// return resp, downloadURL, err
// }