Skip to content

Commit

Permalink
Merge pull request #5465 from oasisprotocol/ptrus/fix/ppid-pcs-fixes
Browse files Browse the repository at this point in the history
go/sgx/pcs: GetPCKCertificateChain client and minor fixes
  • Loading branch information
ptrus authored Nov 23, 2023
2 parents 259085f + 0817bd8 commit 12ec9ca
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 16 deletions.
1 change: 1 addition & 0 deletions .changelog/5465.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/common/sgx: implement `GetPCKCertificateChain` PCS API client
78 changes: 78 additions & 0 deletions go/common/sgx/pcs/http.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package pcs

import (
"bytes"
"context"
"crypto/x509"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
Expand All @@ -27,6 +29,7 @@ const (
pcsAPIGetTCBInfoPath = "/certification/v4/tcb"
pcsAPIGetQEIdentityPath = "/certification/v4/qe/identity"
pcsAPICertChainHeader = "TCB-Info-Issuer-Chain"
pcsAPIPCKIIssuerChainHeader = "SGX-PCK-Certificate-Issuer-Chain"
)

// HTTPClientConfig is the Intel SGX PCS client configuration.
Expand Down Expand Up @@ -72,6 +75,7 @@ func (hc *httpClient) doPCSRequest(ctx context.Context, u *url.URL, method, body
"method", method,
"url", u,
)
resp.Body.Close()
return nil, fmt.Errorf("pcs: response status error: %s", http.StatusText(resp.StatusCode))
}

Expand Down Expand Up @@ -133,6 +137,80 @@ func (hc *httpClient) GetTCBBundle(ctx context.Context, fmspc []byte) (*TCBBundl
return &tcbBundle, nil
}

func (hc *httpClient) GetPCKCertificateChain(ctx context.Context, platformData []byte, encPpid [384]byte, cpusvn [16]byte, pcesvn uint16, pceid uint16) ([]*x509.Certificate, error) {
u := hc.getUrl(pcsAPIGetPCKCertificatePath)
q := u.Query()

// Base16-encoded PCESVN value (2 bytes, little endian).
var pcesvnBytes [2]byte
binary.LittleEndian.PutUint16(pcesvnBytes[:], pcesvn)

// Base16-encoded PCE-ID value (2 bytes, little endian)
var pceidBytes [2]byte
binary.LittleEndian.PutUint16(pceidBytes[:], pceid)

var rsp *http.Response
var err error
switch {
case platformData == nil:
// Use GET endpoint with encrypted PPID.
q.Set("encrypted_ppid", hex.EncodeToString(encPpid[:]))
q.Set("cpusvn", hex.EncodeToString(cpusvn[:]))
q.Set("pcesvn", hex.EncodeToString(pcesvnBytes[:]))
q.Set("pceid", hex.EncodeToString(pceidBytes[:]))
u.RawQuery = q.Encode()
rsp, err = hc.doPCSRequest(ctx, u, http.MethodGet, "", nil, false) // nolint: bodyclose
default:
// Platform data is provided, use the POST endpoint with platform data.
payload, merr := json.Marshal(&struct {
PlatformManifest string `json:"platformManifest"`
CPUSVN string `json:"cpusvn"`
PCESVN string `json:"pcesvn"`
PCEID string `json:"pceid"`
}{
PlatformManifest: hex.EncodeToString(platformData),
CPUSVN: hex.EncodeToString(cpusvn[:]),
PCESVN: hex.EncodeToString(pcesvnBytes[:]),
PCEID: hex.EncodeToString(pceidBytes[:]),
})
if merr != nil {
return nil, fmt.Errorf("pcs: failed to marshal PCK certificate request payload: %w", merr)
}
rsp, err = hc.doPCSRequest(ctx, u, http.MethodPost, "application/json", bytes.NewReader(payload), false) // nolint: bodyclose
}
if err != nil {
return nil, fmt.Errorf("pcs: PCK certificate request failed: %w", err)
}
defer rsp.Body.Close()

// Parse issuer Certificate chain for SGX PCK Certificate.
rawCerts, err := url.QueryUnescape(rsp.Header.Get(pcsAPIPCKIIssuerChainHeader))
if err != nil {
return nil, fmt.Errorf("pcs: failed to parse PCK certificate issuer chain header: %w", err)
}
// It consists of SGX Root CA Certificate and SGX Intermediate CA Certificate.
intermediateCert, rest, err := CertFromPEM([]byte(rawCerts))
if err != nil {
return nil, fmt.Errorf("pcs: failed to parse SGX Intermediate CA Certificate from PCK certificate issuer chain: %w", err)
}
rootCert, _, err := CertFromPEM(rest)
if err != nil {
return nil, fmt.Errorf("pcs: failed to parse root SGX Root CA Certificate from PCK certificate issuer chain: %w", err)
}

// Parse PCK Certificate.
rawPCKCert, err := io.ReadAll(rsp.Body)
if err != nil {
return nil, fmt.Errorf("pcs: failed to read PCK certificate response body: %w", err)
}
leafCert, _, err := CertFromPEM(rawPCKCert)
if err != nil {
return nil, fmt.Errorf("pcs: failed to parse PCK certificate: %w", err)
}

return []*x509.Certificate{leafCert, intermediateCert, rootCert}, nil
}

// NewHTTPClient returns a new PCS HTTP endpoint.
func NewHTTPClient(cfg *HTTPClientConfig) (Client, error) {
hc := &httpClient{
Expand Down
6 changes: 6 additions & 0 deletions go/common/sgx/pcs/pcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pcs

import (
"context"
"crypto/x509"
"time"

"github.com/oasisprotocol/oasis-core/go/common/sgx"
Expand All @@ -17,6 +18,11 @@ var (
type Client interface {
// GetTCBBundle retrieves the signed TCB artifacts needed to verify a quote.
GetTCBBundle(ctx context.Context, fmspc []byte) (*TCBBundle, error)

// GetPCKCertificateChain retrieves the PCK certificate chain for the given platform data or PPID.
//
// If platform data is provided, it is used instead of the encrypted PPID for certificate retrieval.
GetPCKCertificateChain(ctx context.Context, platformData []byte, encPpid [384]byte, cpusvn [16]byte, pcesvn uint16, pceid uint16) ([]*x509.Certificate, error)
}

// QuoteBundle is an attestation quote together with the TCB bundle required for its verification.
Expand Down
13 changes: 10 additions & 3 deletions go/common/sgx/pcs/quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"encoding/binary"
"encoding/hex"
"fmt"
"math"
"math/big"
"time"

Expand Down Expand Up @@ -363,7 +364,7 @@ type PCKInfo struct {
PublicKey *ecdsa.PublicKey
FMSPC []byte
TCBCompSVN [16]int32
PCESVN int32
PCESVN uint16
CPUSVN [16]byte
}

Expand Down Expand Up @@ -420,15 +421,21 @@ func (qs *QuoteSignatureECDSA_P256) VerifyPCK(ts time.Time) (*PCKInfo, error) {
}
case compId == 17:
// PCESVN
if _, err = asn1.Unmarshal(tcbExt.Value.FullBytes, &pckInfo.PCESVN); err != nil {
var pcesvn int32
if _, err = asn1.Unmarshal(tcbExt.Value.FullBytes, &pcesvn); err != nil {
return nil, fmt.Errorf("pcs/quote: bad PCESVN: %w", err)
}
if pcesvn < 0 || pcesvn > math.MaxUint16 {
return nil, fmt.Errorf("pcs/quote: bad PCESVN value: %d (not uint16)", pcesvn)
}
pckInfo.PCESVN = uint16(pcesvn)
case compId == 18:
// CPUSVN
cpusvnSlice := pckInfo.CPUSVN[:]
var cpusvnSlice []byte
if _, err = asn1.Unmarshal(tcbExt.Value.FullBytes, &cpusvnSlice); err != nil {
return nil, fmt.Errorf("pcs/quote: bad CPUSVN: %w", err)
}
copy(pckInfo.CPUSVN[:], cpusvnSlice)
}
}
}
Expand Down
12 changes: 6 additions & 6 deletions go/common/sgx/pcs/tcb.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (bnd *TCBBundle) Verify(
policy *QuotePolicy,
fmspc []byte,
tcbCompSvn [16]int32,
pcesvn int32,
pcesvn uint16,
qe *ReportBody,
) error {
pk, err := bnd.getPublicKey(ts)
Expand Down Expand Up @@ -84,7 +84,7 @@ func (bnd *TCBBundle) verifyTCBInfo(
policy *QuotePolicy,
fmspc []byte,
tcbCompSvn [16]int32,
pcesvn int32,
pcesvn uint16,
) error {
tcbInfo, err := bnd.TCBInfo.open(ts, policy, pk)
if err != nil {
Expand Down Expand Up @@ -261,7 +261,7 @@ func (ti *TCBInfo) validateFMSPC(fmspc []byte) error {

func (ti *TCBInfo) validateTCBLevel(
tcbCompSvn [16]int32,
pcesvn int32,
pcesvn uint16,
) error {
tcbLevel, err := ti.getTCBLevel(tcbCompSvn, pcesvn)
if err != nil {
Expand All @@ -284,7 +284,7 @@ func (ti *TCBInfo) validateTCBLevel(

func (ti *TCBInfo) getTCBLevel(
tcbCompSvn [16]int32,
pcesvn int32,
pcesvn uint16,
) (*TCBLevel, error) {
// Find first matching TCB level.
var matchedTCBLevel *TCBLevel
Expand Down Expand Up @@ -350,7 +350,7 @@ type TCBComponent struct {
// TCBLevel is a platform TCB level.
type TCBLevel struct {
TCB struct {
PCESVN int32 `json:"pcesvn"`
PCESVN uint16 `json:"pcesvn"`
SGXComponents [16]TCBComponent `json:"sgxtcbcomponents"`
TDXComponents [16]TCBComponent `json:"tdxtcbcomponents,omitempty"`
} `json:"tcb"`
Expand All @@ -360,7 +360,7 @@ type TCBLevel struct {
}

// matches performs the SVN comparison.
func (tl *TCBLevel) matches(tcbCompSvn [16]int32, pcesvn int32) bool {
func (tl *TCBLevel) matches(tcbCompSvn [16]int32, pcesvn uint16) bool {
// a) Compare all of the SGX TCB Comp SVNs retrieved from the SGX PCK Certificate (from 01 to
// 16) with the corresponding values in the TCB Level. If all SGX TCB Comp SVNs in the
// certificate are greater or equal to the corresponding values in TCB Level, go to b,
Expand Down
8 changes: 2 additions & 6 deletions go/ias/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func (e *httpEndpoint) doIASRequest(ctx context.Context, method, uPath, bodyType
}
if resp.StatusCode != http.StatusOK {
logger.Error("ias response status error", "status", http.StatusText(resp.StatusCode), "method", method, "url", u)
resp.Body.Close()
return nil, fmt.Errorf("ias: response status error: %s", http.StatusText(resp.StatusCode))
}

Expand Down Expand Up @@ -126,9 +127,6 @@ func (e *httpEndpoint) VerifyEvidence(ctx context.Context, evidence *api.Evidenc
query = []string{iasAPIAVRTCBUpdateParam, iasAPIAVRTCBUpdateValueEarly}
}
resp, err := e.doIASRequest(ctx, http.MethodPost, iasAPIAttestationReportPath, "application/json", bytes.NewReader(reqPayload), query...)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("ias: http POST failed: %w", err)
}
Expand Down Expand Up @@ -165,12 +163,10 @@ func (e *httpEndpoint) GetSigRL(ctx context.Context, epidGID uint32) ([]byte, er
// Dispatch the request via HTTP.
p := path.Join(iasAPISigRLPath, hex.EncodeToString(gid[:]))
resp, err := e.doIASRequest(ctx, http.MethodGet, p, "", nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, fmt.Errorf("ias: http GET failed: %w", err)
}
defer resp.Body.Close()

// Extract and parse the SigRL.
sigRL, err := io.ReadAll(resp.Body)
Expand Down
6 changes: 5 additions & 1 deletion go/runtime/host/sgx/ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ func (ec *teeStateECDSA) Update(ctx context.Context, sp *sgxProvisioner, conn pr
// We have a PPID, need to retrieve PCK certificate first.
// TODO: Fetch PCK certificate based on PPID and include it in the quote, replacing the
// PPID certification data with the PCK certificate chain certification data.
return nil, fmt.Errorf("PPID certification data not yet supported")
// e.g. sp.pcs.GetPCKCertificateChain(ctx, nil, data.PPID, data.CPUSVN, data.PCESVN, data.PCEID)
//
// Due to aesmd QuoteEx APIs not supporting certification data this currently
// cannot be easily implemented. Instead we rely on a quote provider to be installed.
return nil, fmt.Errorf("PPID certification data not yet supported; please install a quote provider")
default:
return nil, fmt.Errorf("unsupported certification data type: %s", qs.CertificationData.CertificationDataType())
}
Expand Down

0 comments on commit 12ec9ca

Please sign in to comment.