github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/runtime/validate/validate.go (about)

     1  package validate
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/rsa"
     6  	"crypto/sha256"
     7  	"crypto/x509"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"encoding/pem"
    11  	"strings"
    12  
    13  	"github.com/ActiveState/cli/internal/errs"
    14  	"github.com/ActiveState/cli/internal/fileutils"
    15  	"github.com/ActiveState/cli/internal/httputil"
    16  	"github.com/ActiveState/cli/internal/locale"
    17  	"github.com/ActiveState/cli/internal/logging"
    18  	"go.mozilla.org/pkcs7"
    19  )
    20  
    21  type signature struct {
    22  	Sig  string `json:"sig"`
    23  	Cert string `json:"cert"`
    24  }
    25  
    26  type attestation struct {
    27  	Payload    string      `json:"payload"`
    28  	Signatures []signature `json:"signatures"`
    29  }
    30  
    31  func Attestation(attestationFile string) error {
    32  	data, err := fileutils.ReadFile(attestationFile)
    33  	if err != nil {
    34  		return errs.Wrap(err, "Could not read attestation: %s", attestationFile)
    35  	}
    36  
    37  	att := attestation{}
    38  	err = json.Unmarshal(data, &att)
    39  	if err != nil {
    40  		return errs.Wrap(err, "Could not unmarshal attestation")
    41  	}
    42  
    43  	if len(att.Signatures) == 0 {
    44  		return locale.NewError("validate_attestation_fail_no_signatures", "No signatures")
    45  	}
    46  
    47  	// Verify signing certificate.
    48  	pemBlock, _ := pem.Decode([]byte(att.Signatures[0].Cert))
    49  	if pemBlock == nil {
    50  		return locale.NewError("validate_attestation_fail_decode_cert", "Unable to decode attestation certificate")
    51  	}
    52  
    53  	cert, err := x509.ParseCertificate(pemBlock.Bytes)
    54  	if err != nil {
    55  		return errs.Wrap(err, "Unable to parse attestation certificate")
    56  	}
    57  
    58  	intermediates := x509.NewCertPool()
    59  	addIntermediatesToPool(intermediates, cert)
    60  
    61  	opts := x509.VerifyOptions{
    62  		Roots:         nil, // use system root CAs
    63  		Intermediates: intermediates,
    64  		KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning},
    65  	}
    66  	_, err = cert.Verify(opts)
    67  	if err != nil {
    68  		return errs.Wrap(err, "Unable to validate certificate")
    69  	}
    70  
    71  	// Verify signature.
    72  	payload := make([]byte, len(att.Payload))
    73  	n, err := base64.StdEncoding.Decode(payload, []byte(att.Payload))
    74  	if err != nil {
    75  		return errs.Wrap(err, "Unable to decode attestation payload")
    76  	}
    77  	payload = payload[:n]
    78  	hash := sha256.New()
    79  	hash.Write(payload)
    80  	digest := hash.Sum(nil)
    81  
    82  	signature := make([]byte, len(att.Signatures[0].Sig))
    83  	n, err = base64.StdEncoding.Decode(signature, []byte(att.Signatures[0].Sig))
    84  	if err != nil {
    85  		return errs.Wrap(err, "Unable to decode attestation signature")
    86  	}
    87  	signature = signature[:n]
    88  
    89  	publicKey, ok := cert.PublicKey.(*rsa.PublicKey)
    90  	if !ok {
    91  		return locale.NewError("validate_attestation_fail_public_key", "Certificate's public key is not an expected RSA pubkey")
    92  	}
    93  	err = rsa.VerifyPSS(publicKey, crypto.SHA256, digest, signature, &rsa.PSSOptions{Hash: crypto.SHA256})
    94  	if err != nil {
    95  		return errs.Wrap(err, "Unable to validate signature")
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  func addIntermediatesToPool(pool *x509.CertPool, cert *x509.Certificate) {
   102  	for _, url := range cert.IssuingCertificateURL {
   103  		bytes, err := httputil.GetDirect(url)
   104  		if err != nil {
   105  			logging.Debug("Unable to download intermediate certificate %s: %v", url, err)
   106  			continue
   107  		}
   108  		if !strings.HasSuffix(url, ".p7c") {
   109  			cert, err := x509.ParseCertificate(bytes)
   110  			if err != nil {
   111  				logging.Debug("Unable to parse intermediate certificate %s: %v", url, err)
   112  				continue
   113  			}
   114  			pool.AddCert(cert)
   115  			addIntermediatesToPool(pool, cert)
   116  		} else {
   117  			p7, err := pkcs7.Parse(bytes)
   118  			if err != nil {
   119  				logging.Debug("Unable to parse intermediate certificate %s: %v", url, err)
   120  				continue
   121  			}
   122  			for _, cert := range p7.Certificates {
   123  				pool.AddCert(cert)
   124  				addIntermediatesToPool(pool, cert)
   125  			}
   126  		}
   127  	}
   128  }
   129  
   130  func Checksum(archivePath string, expectedChecksum string) error {
   131  	if expectedChecksum == "" {
   132  		logging.Debug("Skipping checksum validation for %s because the Platform did not provide a checksum to validate against.")
   133  		return nil
   134  	}
   135  	logging.Debug("Validating checksum for %s", archivePath)
   136  
   137  	checksum, err := fileutils.Sha256Hash(archivePath)
   138  	if err != nil {
   139  		return errs.Wrap(err, "Failed to compute checksum for %s", archivePath)
   140  	}
   141  
   142  	if checksum != expectedChecksum {
   143  		logging.Debug("Checksum validation failed. Expected '%s', but was '%s'", expectedChecksum, checksum)
   144  		// Note: the artifact name will be reported higher up the chain
   145  		return locale.WrapError(err, "artifact_checksum_failed", "Checksum validation failed")
   146  	}
   147  
   148  	return nil
   149  }