Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

COCOS-192 - Attested TLS #279

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions cli/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/google/go-sev-guest/kds"
"github.com/google/go-sev-guest/verify/trust"
"github.com/spf13/cobra"
attest "github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/clients/grpc"
)

Expand All @@ -26,13 +27,13 @@ func (cli *CLI) NewCABundleCmd(fileSavePath string) *cobra.Command {
Example: "ca-bundle <path_to_platform_info_json>",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
attestationConfiguration := grpc.AttestationConfiguration{}
attestationConfiguration := attest.AttestationConfiguration{}
err := grpc.ReadManifest(args[0], &attestationConfiguration)
if err != nil {
log.Fatalf("Error while reading manifest: %v", err)
}

product := attestationConfiguration.RootOfTrust.Product
product := attestationConfiguration.RootOfTrust.ProductLine

getter := trust.DefaultHTTPSGetter()
caURL := kds.ProductCertChainURL(abi.VcekReportSigner, product)
Expand Down
46 changes: 20 additions & 26 deletions internal/server/grpc/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"log/slog"
Expand All @@ -24,8 +23,8 @@ import (
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/internal/server"
atls "github.com/ultravioletrs/cocos/pkg/tls_extensions"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"golang.org/x/crypto/sha3"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
Expand All @@ -42,6 +41,7 @@ const (
notAfterYear = 1
notAfterMonth = 0
notAfterDay = 0
nonceSize = 32
)

type Server struct {
Expand Down Expand Up @@ -85,15 +85,12 @@ func (s *Server) Start() error {
grpcServerOptions = append(grpcServerOptions, grpc.StreamInterceptor(stream))
}

listener, err := net.Listen("tcp", s.Address)
if err != nil {
return fmt.Errorf("failed to listen on port %s: %w", s.Address, err)
}
creds := grpc.Creds(insecure.NewCredentials())
var listener net.Listener = nil

switch {
case s.Config.AttestedTLS:
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS(s.quoteProvider)
certificateBytes, privateKeyBytes, err := generateCertificatesForATLS()
if err != nil {
return fmt.Errorf("failed to create certificate: %w", err)
}
Expand All @@ -109,7 +106,17 @@ func (s *Server) Start() error {
}

creds = grpc.Creds(credentials.NewTLS(tlsConfig))

listener, err = atls.Listen(
s.Address,
certificateBytes,
privateKeyBytes,
)
if err != nil {
return fmt.Errorf("failed to create Listener for aTLS: %w", err)
}
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with Attested TLS", s.Name, s.Address))

case s.Config.CertFile != "" || s.Config.KeyFile != "":
certificate, err := loadX509KeyPair(s.Config.CertFile, s.Config.KeyFile)
if err != nil {
Expand Down Expand Up @@ -157,6 +164,11 @@ func (s *Server) Start() error {
default:
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s with TLS cert %s and key %s", s.Name, s.Address, s.Config.CertFile, s.Config.KeyFile))
}

listener, err = net.Listen("tcp", s.Address)
if err != nil {
return fmt.Errorf("failed to listen on port %s: %w", s.Address, err)
}
default:
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s without TLS", s.Name, s.Address))
}
Expand Down Expand Up @@ -228,24 +240,13 @@ func loadX509KeyPair(certfile, keyfile string) (tls.Certificate, error) {
return tls.X509KeyPair(cert, key)
}

func generateCertificatesForATLS(qp client.QuoteProvider) ([]byte, []byte, error) {
func generateCertificatesForATLS() ([]byte, []byte, error) {
curve := elliptic.P256()
privateKey, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("failed to generate private/public key: %w", err)
}

publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal the public key: %w", err)
}

// The Attestation Report will be added as an X.509 certificate extension
attestationReport, err := qp.GetRawQuote(sha3.Sum512(publicKeyBytes))
if err != nil {
return nil, nil, fmt.Errorf("failed to fetch the attestation report: %w", err)
}

certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(202403311),
Subject: pkix.Name{
Expand All @@ -261,13 +262,6 @@ func generateCertificatesForATLS(qp client.QuoteProvider) ([]byte, []byte, error
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
ExtraExtensions: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 2, 3, 4, 5, 6},
Critical: false,
Value: attestationReport,
},
},
}

certDERBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &privateKey.PublicKey, privateKey)
Expand Down
141 changes: 141 additions & 0 deletions pkg/attestation/attestation.go
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try combining with exisiting code in cli, to avoid repetition

Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package attest

import (
"fmt"
"io"
"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"
"github.com/google/logger"
"github.com/ultravioletrs/cocos/agent"
"github.com/ultravioletrs/cocos/agent/quoteprovider"
)

const (
cocosDirectory = ".cocos"
caBundleName = "ask_ark.pem"
attestationReportSize = 0x4A0
)

var (
AttConfigurationSEVSNP = AttestationConfiguration{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid package level variable, this is not thread safe

timeout = time.Minute * 2
maxTryDelay = time.Second * 30
)

var (
errAttVerification = errors.New("attestation verification failed")
errAttValidation = errors.New("attestation validation failed")
)

type AttestationConfiguration struct {
SNPPolicy *check.Policy `json:"snp_policy,omitempty"`
RootOfTrust *check.RootOfTrust `json:"root_of_trust,omitempty"`
}

func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idiomatic golang way is to return an error

logger.Init("", false, false, io.Discard)

AttConfigurationSEVSNP.SNPPolicy.ReportData = reportData[:]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SNPPolicy could be nil, this could panic


// Attestation verification and validation
sopts, err := verify.RootOfTrustToOptions(AttConfigurationSEVSNP.RootOfTrust)
if err != nil {
fmt.Fprintf(os.Stderr, "%v", errors.Wrap(errAttVerification, err))
return -1
}

sopts.Product = AttConfigurationSEVSNP.SNPPolicy.Product
sopts.Getter = &trust.RetryHTTPSGetter{
Timeout: timeout,
MaxRetryDelay: maxTryDelay,
Getter: &trust.SimpleHTTPSGetter{},
}

attestationPB, err := abi.ReportCertsToProto(attestationBytes)
if err != nil {
fmt.Fprintf(os.Stderr, "%v", errors.Wrap(errAttVerification, err))
return -1
}

if err := fillInAttestationLocal(attestationPB); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
return -1
}

if err = verify.SnpAttestation(attestationPB, sopts); err != nil {
fmt.Fprintf(os.Stderr, "%v", errors.Wrap(errAttVerification, err))
return -1
}

opts, err := validate.PolicyToOptions(AttConfigurationSEVSNP.SNPPolicy)
if err != nil {
fmt.Fprintf(os.Stderr, "%v", errors.Wrap(errAttVerification, err))
return -1
}

if err = validate.SnpAttestation(attestationPB, opts); err != nil {
fmt.Fprintf(os.Stderr, "%v", errors.Wrap(errAttValidation, err))
return -1
}

return 0
}

func fillInAttestationLocal(attestation *sevsnp.Attestation) error {
product := AttConfigurationSEVSNP.RootOfTrust.ProductLine

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
}

func FetchAttestation(reportDataSlice []byte) []byte {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

propagate error

var reportData [agent.ReportDataSize]byte

qp, err := quoteprovider.GetQuoteProvider()
if err != nil {
return []byte{}
}

if len(reportData) > agent.ReportDataSize {
return []byte{}
}
copy(reportData[:], reportDataSlice)

rawQuote, err := qp.GetRawQuote(reportData)
if err != nil {
return []byte{}
}

return rawQuote
}
Loading