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 }