COCOS-192 - Add support for attested TLS (#279)

* add draft tls extension

* add client support for ipv6

* remove vscode

* add evidence request server payload

* clean up the code

* add fetch and verify for quote provider

* add build parameters for buildroot

* change Makefile to always enable CGO

* fix ci

* add malloc check for NULL

* add copyright

* renamed files and fix cgo lint

* fix cache test

* fix server tests

* remove ineffective assignment

* fix no-TLS connection

* add check for SSL_set_fd failure

* add tests for verification of attestation

* fix CI

* fix failing tests

* fix backend tests

* remove commented code

* separate verify and validate function

* fix failing test

* Simplify function name

---------

Co-authored-by: ultraviolet <cocosai@ultraviolet.local.pragmatic-it.com>
This commit is contained in:
Danko Miladinovic
2024-11-04 19:10:34 +01:00
committed by GitHub
parent 6f747190b9
commit e372cfc219
28 changed files with 2056 additions and 591 deletions
+26 -26
View File
@@ -11,7 +11,6 @@ import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"log/slog"
@@ -25,8 +24,8 @@ import (
agentgrpc "github.com/ultravioletrs/cocos/agent/api/grpc"
"github.com/ultravioletrs/cocos/agent/auth"
"github.com/ultravioletrs/cocos/internal/server"
"github.com/ultravioletrs/cocos/pkg/atls"
"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"
@@ -45,6 +44,7 @@ const (
notAfterYear = 1
notAfterMonth = 0
notAfterDay = 0
nonceSize = 32
)
type Server struct {
@@ -89,15 +89,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)
}
@@ -113,7 +110,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 {
@@ -161,7 +168,18 @@ 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:
var err error
listener, err = net.Listen("tcp", s.Address)
if err != nil {
return fmt.Errorf("failed to listen on port %s: %w", s.Address, err)
}
s.Logger.Info(fmt.Sprintf("%s service gRPC server listening at %s without TLS", s.Name, s.Address))
}
@@ -237,24 +255,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{
@@ -270,13 +277,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)
+2 -4
View File
@@ -18,10 +18,9 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
authmocks "github.com/ultravioletrs/cocos/agent/mocks"
"github.com/ultravioletrs/cocos/agent/quoteprovider/mocks"
"github.com/ultravioletrs/cocos/internal/server"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider/mocks"
"google.golang.org/grpc"
"google.golang.org/grpc/test/bufconn"
)
@@ -139,7 +138,6 @@ func TestServerStartWithAttestedTLS(t *testing.T) {
logger := slog.New(slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug}))
qp := new(mocks.QuoteProvider)
authSvc := new(authmocks.Authenticator)
qp.On("GetRawQuote", mock.Anything).Return([]byte("mock-quote"), nil)
srv := New(ctx, cancel, "TestServer", config, func(srv *grpc.Server) {}, logger, qp, authSvc)
@@ -158,7 +156,7 @@ func TestServerStartWithAttestedTLS(t *testing.T) {
cancel()
time.Sleep(100 * time.Millisecond)
time.Sleep(1000 * time.Millisecond)
logContent := logBuffer.String()
assert.Contains(t, logContent, "TestServer service gRPC server listening at localhost:0 with Attested TLS")