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

feat: ccm activation on tlsonly platforms #745

Merged
merged 1 commit into from
Feb 10, 2025
Merged
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
16 changes: 16 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
# Get version from the first argument
version=$1

# Download the certificate to the initial location
wget -O ./internal/certs/OnDie_CA_RootCA_Certificate.cer \
https://tsci.intel.com/content/OnDieCA/certs/OnDie_CA_RootCA_Certificate.cer

# Check if the download was successful (non-zero file size)
if [ -s ./internal/certs/OnDie_CA_RootCA_Certificate.cer ]; then
# Move the downloaded certificate to the trusted store
mv ./internal/certs/OnDie_CA_RootCA_Certificate.cer \
./internal/certs/trustedstore/OnDie_CA_RootCA_Certificate.cer
echo "Certificate moved to trusted store."
else
# Remove the file if the download failed
rm -f ./internal/certs/OnDie_CA_RootCA_Certificate.cer
echo "Download failed, file removed."
fi

# Build for Linux
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X 'rpc/pkg/utils.ProjectVersion=$version'" -trimpath -o rpc_linux_x64 ./cmd/main.go
CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "-s -w -X 'rpc/pkg/utils.ProjectVersion=$version'" -trimpath -o rpc_linux_x86 ./cmd/main.go
Expand Down
30 changes: 30 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func runRPC(args []string) error {
if err != nil {
return err
}
// Update TLS enforcement and Current Activation Mode, helps decide how to connect to LMS
err = updateConnectionSettings(flags)
if err != nil {
return err
}
if flags.Local {
err = local.ExecuteCommand(flags)
} else {
Expand Down Expand Up @@ -85,6 +90,31 @@ func main() {
}
}

func updateConnectionSettings(flags *flags.Flags) error {
// Check if TLS is Mandatory for LMS connection
resp, err := flags.AmtCommand.GetChangeEnabled()
flags.LocalTlsEnforced = false
if err != nil {
if err.Error() == "wait timeout while sending data" {
log.Trace("Operation timed out while sending data. This may occur on systems with AMT version 11 and below.")
return nil
} else {
log.Error(err)
return err
}
}
if resp.IsTlsEnforcedOnLocalPorts() {
flags.LocalTlsEnforced = true
log.Trace("TLS is enforced on local ports")
}
// Check the current provisioning mode
flags.ControlMode, err = flags.AmtCommand.GetControlMode()
if err != nil {
return err
}
return nil
}

func handleErrorAndExit(err error) {
if customErr, ok := err.(utils.CustomError); ok {
if err != utils.HelpRequested {
Expand Down
69 changes: 69 additions & 0 deletions internal/certs/embed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*********************************************************************
* Copyright (c) Intel Corporation 2025
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/
package certs

import (
"crypto/x509"
"embed"
"errors"
"io/fs"

log "github.com/sirupsen/logrus"
)

//go:embed trustedstore/*.cer
var EmbeddedCerts embed.FS

// FileSystem abstracts file operations for testability
type FileSystem interface {
ReadDir(name string) ([]fs.DirEntry, error)
ReadFile(name string) ([]byte, error)
}

// embeddedFS is a wrapper around embed.FS to implement FileSystem interface
type embeddedFS struct{}

func (embeddedFS) ReadDir(name string) ([]fs.DirEntry, error) {
return fs.ReadDir(EmbeddedCerts, name)
}

func (embeddedFS) ReadFile(name string) ([]byte, error) {
return EmbeddedCerts.ReadFile(name)
}

// LoadRootCAPool loads root CAs using the embedded file system
var LoadRootCAPool = func() (*x509.CertPool, error) {
return LoadRootCAPoolwithFS(embeddedFS{})
}

// Internal function to load root CA pool from any file system (for testing)
func LoadRootCAPoolwithFS(fs FileSystem) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
loadedCerts := 0
certFiles, err := fs.ReadDir("trustedstore")
if err != nil {
return nil, errors.New("failed to read embedded certificates directory")
}
for _, certFile := range certFiles {
if !certFile.IsDir() {
certPath := "trustedstore/" + certFile.Name()
certData, err := fs.ReadFile(certPath)
if err != nil {
log.Error("Failed to read file: ", certPath, " Error: ", err)
continue
}
derCert, err := x509.ParseCertificate(certData)
if err != nil {
continue
}
certPool.AddCert(derCert)
loadedCerts++
}
}
if loadedCerts == 0 {
return nil, errors.New("no certificates found in the trusted store")
}
return certPool, nil
}
Binary file not shown.
154 changes: 154 additions & 0 deletions internal/config/lmsTls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*********************************************************************
* Copyright (c) Intel Corporation 2025
* SPDX-License-Identifier: Apache-2.0
**********************************************************************/
package config

import (
"crypto/tls"
"crypto/x509"
"errors"
"strconv"

"rpc/internal/amt"
"rpc/internal/certs"
"strings"

log "github.com/sirupsen/logrus"
)

// generates a TLS configuration based on the provided mode.
func GetTLSConfig(mode *int) *tls.Config {
if *mode == 0 { // pre-provisioning mode
return &tls.Config{
InsecureSkipVerify: true,
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return VerifyCertificates(rawCerts, mode)
},
}
}
// default tls config if device is in ACM or CCM
log.Trace("Setting default TLS Config for ACM/CCM mode")
return &tls.Config{
InsecureSkipVerify: true,
}
}

func VerifyCertificates(rawCerts [][]byte, mode *int) error {
numCerts := len(rawCerts)
const (
selfSignedChainLength = 1
prodChainLength = 6
odcaCertLevel = 3
leafLevel = 0
)
var parsedCerts []*x509.Certificate
if numCerts == prodChainLength {
for i, rawCert := range rawCerts {
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
log.Error("Failed to parse certificate ", i, ": ", err)
return err
}
parsedCerts = append(parsedCerts, cert)
switch i {
case leafLevel:
if err := VerifyLeafCertificate(cert.Subject.CommonName); err != nil {
return err
}
case odcaCertLevel:
if err := VerifyROMODCACertificate(cert.Subject.CommonName, cert.Issuer.OrganizationalUnit); err != nil {
return err
}
}
// TODO: verify CRL for each cert
}
// verify the full chain
if err := VerifyFullChain(parsedCerts); err != nil {
return err
}
return nil
} else if numCerts == selfSignedChainLength {
return HandleAMTTransition(mode)
}
return errors.New("unexpected number of certificates received from AMT: " + strconv.Itoa(numCerts))
}

// validate the leaf certificate
func VerifyLeafCertificate(cn string) error {
allowedLeafCNs := []string{
"iAMT CSME IDevID RCFG", "AMT RCFG",
}
for _, allowed := range allowedLeafCNs {
if cn == allowed {
return nil
}
}
log.Error("leaf certificate CN is not allowed: ", cn)
return errors.New("leaf certificate CN is not allowed")
}

// validate CSME ROM ODCA certificate
func VerifyROMODCACertificate(cn string, issuerOU []string) error {
allowedOUPrefixes := []string{
"ODCA 2 CSME P", "On Die CSME P", "ODCA 2 CSME", "On Die CSME",
}

if !strings.Contains(cn, "ROM CA") && !strings.Contains(cn, "ROM DE") {
log.Error("invalid ROM ODCA Certificate: ", cn)
return errors.New("invalid ROM ODCA Certificate")
}

// check that OU of odcaCertLevel must have a prefix equal to either ODCA 2 CSME P or On Die CSME P
for _, ou := range issuerOU {
for _, prefix := range allowedOUPrefixes {
if strings.HasPrefix(ou, prefix) {
return nil
}
}
}
log.Error("ROM ODCA Certificate OU does not have a valid prefix: ", issuerOU)
return errors.New("ROM ODCA Certificate OU does not have a valid prefix")
}

// validate the full chain
func VerifyFullChain(certificates []*x509.Certificate) error {
rootCAs, err := certs.LoadRootCAPool()
if err != nil {
log.Error("Failed to load root CA pool:", err)
return err
}
// Create a pool for intermediate certificates
intermediates := x509.NewCertPool()
for _, cert := range certificates[1:] {
intermediates.AddCert(cert)
}
leafCert := certificates[0]
opts := x509.VerifyOptions{
Roots: rootCAs,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}
// Validate the full chain (leaf → intermediates → trusted root)
if _, err := leafCert.Verify(opts); err != nil {
log.Error("Certificate chain validation failed:", err)
return err
}
return nil
}

// handleAMTTransition - checks if AMT has moved from Pre-Provisioning mode.
func HandleAMTTransition(mode *int) error {
controlMode, err := amt.NewAMTCommand().GetControlMode()
if err != nil {
log.Error("failed to get control mode: ", err)
return err
}
if controlMode != 0 {
log.Trace("AMT has transitioned to mode: ", controlMode)
*mode = controlMode
return nil
}
log.Error("unexpected number of certificates received from AMT")
return errors.New("unexpected number of certificates received from AMT")
}
Loading