diff --git a/cli/cache.go b/cli/cache.go new file mode 100644 index 00000000..3c03f7c9 --- /dev/null +++ b/cli/cache.go @@ -0,0 +1,69 @@ +// Copyright (c) Ultraviolet +// SPDX-License-Identifier: Apache-2.0 +package cli + +import ( + "log" + "os" + "path" + + "github.com/google/go-sev-guest/abi" + "github.com/google/go-sev-guest/kds" + "github.com/google/go-sev-guest/verify/trust" + "github.com/spf13/cobra" + "github.com/ultravioletrs/cocos/pkg/clients/grpc" +) + +const ( + caBundleName = "ask_ark.pem" + filePermisionKeys = 0o766 +) + +func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command { + return &cobra.Command{ + Use: "ca-bundle", + Short: "Fetch AMD SEV-SNPs CA Bundle (ASK and ARK)", + Example: "ca-bundle ", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + attestationConfiguration := grpc.AttestationConfiguration{} + err := grpc.ReadManifest(args[0], &attestationConfiguration) + if err != nil { + log.Fatalf("Error while reading manifest: %v", err) + } + + product := attestationConfiguration.RootOfTrust.Product + + getter := trust.DefaultHTTPSGetter() + caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product) + + bundle, err := getter.Get(caURL) + if err != nil { + log.Fatalf("Error fetching ARK and ASK from AMD KDS for product: %s, error: %v", product, err) + } + + err = os.MkdirAll(path.Join(fileSavePath, product), filePermisionKeys) + if err != nil { + log.Fatalf("Error while creating directory for product name %s, error: %v", product, err) + } + + bundleFilePath := path.Join(fileSavePath, product, caBundleName) + if err = saveToFile(bundleFilePath, bundle); err != nil { + log.Fatalf("Error while saving ARK-ASK to file: %v", err) + } + }, + } +} + +func saveToFile(fileSavePath string, content []byte) error { + file, err := os.OpenFile(fileSavePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, filePermisionKeys) + if err != nil { + return err + } + + if _, err := file.Write(content); err != nil { + return err + } + + return nil +} diff --git a/cli/measurement.go b/cli/measurement.go index 6e767706..55979b06 100644 --- a/cli/measurement.go +++ b/cli/measurement.go @@ -8,16 +8,11 @@ import ( "log" "os" - "github.com/google/go-sev-guest/proto/check" "github.com/spf13/cobra" + "github.com/ultravioletrs/cocos/pkg/clients/grpc" ) -const filePermision = 0o755 - -type AttestationConfiguration struct { - SNPPolicy *check.Policy `json:"snp_policy,omitempty"` - RootOFTrust *check.RootOfTrust `json:"root_of_trust,omitempty"` -} +const filePermision = 0o644 func (cli *CLI) NewAddMeasurementCmd() *cobra.Command { return &cobra.Command{ @@ -31,7 +26,7 @@ func (cli *CLI) NewAddMeasurementCmd() *cobra.Command { log.Fatalf("Error could not decode base64: %v", err) } - attestationConfiguration := AttestationConfiguration{} + attestationConfiguration := grpc.AttestationConfiguration{} manifest, err := os.OpenFile(args[1], os.O_RDWR, filePermision) if err != nil { diff --git a/cmd/cli/main.go b/cmd/cli/main.go index cafae29b..4ea29991 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -6,8 +6,10 @@ import ( "fmt" "log" "os" + "path" mglog "github.com/absmach/magistrala/logger" + "github.com/google/logger" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/ultravioletrs/cocos/cli" @@ -21,6 +23,8 @@ const ( svcName = "cli" envPrefixAgentGRPC = "AGENT_GRPC_" completion = "completion" + filePermision = 0o755 + cocosDirectory = ".cocos" ) type config struct { @@ -29,10 +33,23 @@ type config struct { func main() { var cfg config + logger.Init("", false, false, os.Stderr) + if err := env.Parse(&cfg); err != nil { log.Fatalf("failed to load %s configuration : %s", svcName, err) } + homePath, err := os.UserHomeDir() + if err != nil { + log.Fatalf("Error fetching user home directory: %v", err) + } + + directoryCachePath := path.Join(homePath, cocosDirectory) + + if err := os.MkdirAll(directoryCachePath, filePermision); err != nil { + log.Fatalf("Error while creating directory %s, error: %v", directoryCachePath, err) + } + logger, err := mglog.New(os.Stdout, cfg.LogLevel) if err != nil { log.Fatalf("Error creating logger: %s", err) @@ -92,6 +109,7 @@ func main() { rootCmd.AddCommand(cliSVC.NewFileHashCmd()) rootCmd.AddCommand(cliSVC.NewAddMeasurementCmd()) rootCmd.AddCommand(cliSVC.NewKeysCmd()) + rootCmd.AddCommand(cliSVC.NewCABundleCmd(directoryCachePath)) // Attestation commands attestaionCmd.AddCommand(cliSVC.NewGetAttestationCmd()) diff --git a/pkg/clients/grpc/connect.go b/pkg/clients/grpc/connect.go index b0e06009..c7fc86a4 100644 --- a/pkg/clients/grpc/connect.go +++ b/pkg/clients/grpc/connect.go @@ -9,11 +9,13 @@ import ( "encoding/json" "fmt" "os" + "path" "time" "github.com/absmach/magistrala/pkg/errors" "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/proto/check" + "github.com/google/go-sev-guest/proto/sevsnp" "github.com/google/go-sev-guest/validate" "github.com/google/go-sev-guest/verify" "github.com/google/go-sev-guest/verify/trust" @@ -32,6 +34,13 @@ const ( withmTLS ) +const ( + cocosDirectory = ".cocos" + caBundleName = "ask_ark.pem" + productNameMilan = "Milan" + productNameGenoa = "Genoa" +) + var ( errGrpcConnect = errors.New("failed to connect to grpc server") errGrpcClose = errors.New("failed to close grpc connection") @@ -63,7 +72,7 @@ type Config struct { type AttestationConfiguration struct { SNPPolicy *check.Policy `json:"snp_policy,omitempty"` - RootOFTrust *check.RootOfTrust `json:"root_of_trust,omitempty"` + RootOfTrust *check.RootOfTrust `json:"root_of_trust,omitempty"` } type Client interface { @@ -132,7 +141,7 @@ func connect(cfg Config) (*grpc.ClientConn, security, error) { tc := insecure.NewCredentials() if cfg.AttestedTLS { - err := readManifest(cfg) + err := ReadManifest(cfg.Manifest, &attestationConfiguration) if err != nil { return nil, secure, fmt.Errorf("failed to read Manifest %w", err) } @@ -183,16 +192,16 @@ func connect(cfg Config) (*grpc.ClientConn, security, error) { return conn, secure, nil } -func readManifest(cfg Config) error { - if cfg.Manifest != "" { - manifest, err := os.Open(cfg.Manifest) +func ReadManifest(manifestPath string, attestationConfiguration *AttestationConfiguration) error { + if manifestPath != "" { + manifest, err := os.Open(manifestPath) if err != nil { return errors.Wrap(errManifestOpen, err) } defer manifest.Close() decoder := json.NewDecoder(manifest) - err = decoder.Decode(&attestationConfiguration) + err = decoder.Decode(attestationConfiguration) if err != nil { return errors.Wrap(errManifestDecode, err) } @@ -226,7 +235,7 @@ func verifyAttestationReportTLS(rawCerts [][]byte, verifiedChains [][]*x509.Cert attestationConfiguration.SNPPolicy.ReportData = expectedReportData[:] // Attestation verification and validation - sopts, err := verify.RootOfTrustToOptions(attestationConfiguration.RootOFTrust) + sopts, err := verify.RootOfTrustToOptions(attestationConfiguration.RootOfTrust) if err != nil { return errors.Wrap(errAttVerification, err) } @@ -243,6 +252,10 @@ func verifyAttestationReportTLS(rawCerts [][]byte, verifiedChains [][]*x509.Cert return errors.Wrap(errAttVerification, err) } + if err := fillInAttestationLocal(attestationPB); err != nil { + return err + } + if err = verify.SnpAttestation(attestationPB, sopts); err != nil { return errors.Wrap(errAttVerification, err) } @@ -278,3 +291,32 @@ func checkIfCertificateSelfSigned(cert *x509.Certificate) error { return nil } + +func fillInAttestationLocal(attestation *sevsnp.Attestation) error { + product := attestationConfiguration.RootOfTrust.Product + + chain := attestation.GetCertificateChain() + if chain == nil { + chain = &sevsnp.CertificateChain{} + attestation.CertificateChain = chain + } + if len(chain.GetAskCert()) == 0 || len(chain.GetArkCert()) == 0 { + homePath, err := os.UserHomeDir() + if err != nil { + return err + } + + bundleFilePath := path.Join(homePath, cocosDirectory, product, caBundleName) + if _, err := os.Stat(bundleFilePath); err == nil { + amdRootCerts := trust.AMDRootCerts{} + if err := amdRootCerts.FromKDSCert(bundleFilePath); err != nil { + return err + } + + chain.ArkCert = amdRootCerts.ProductCerts.Ark.Raw + chain.AskCert = amdRootCerts.ProductCerts.Ask.Raw + } + } + + return nil +} diff --git a/test/manual/README.md b/test/manual/README.md index 49e40fd9..a6fec12b 100644 --- a/test/manual/README.md +++ b/test/manual/README.md @@ -30,7 +30,13 @@ LINE="earlyprintk=serial console=ttyS0" sev-snp-measure --mode snp --vcpus 4 --vcpu-type EPYC-v4 --ovmf $OVMF_CODE --kernel $KERNEL --initrd $INITRD --append "$LINE" --output-format base64 ``` -```sh +To speed up the verification process of attested TLS, download the ARK and ASK certificates using the CLI tool. The CLI tool will download the certificates under your home directory in the `.cocos` directory. +```bash +go run cmd/cli/main.go ca-bundle +``` + +In the following text, we can see an example of how the CLI tool is used. +```bash export AGENT_GRPC_URL=localhost:7002 # For attested TLS, the CLI should also be aware of the VM measurement. To