diff --git a/sgx-tools/gen-quote.go b/sgx-tools/gen-quote.go index ecec44e95ad45d733fd72ef0049f4252502d5382..d9e8028b0d0f888bb51be8770eea43b403915cfb 100644 --- a/sgx-tools/gen-quote.go +++ b/sgx-tools/gen-quote.go @@ -2,6 +2,7 @@ package main // import "github.com/inclavare-containers/sgx-tools" import ( "fmt" + _ "github.com/opencontainers/runc/libenclave/attestation" "github.com/opencontainers/runc/libenclave/intelsgx" "github.com/sirupsen/logrus" "github.com/urfave/cli" diff --git a/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/challenger.go b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/challenger.go new file mode 100644 index 0000000000000000000000000000000000000000..e0b6b97cb8f4c7403699b3c68523d61fe135ef16 --- /dev/null +++ b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/challenger.go @@ -0,0 +1,86 @@ +package attestation // import "github.com/opencontainers/runc/libenclave/attestation" + +import ( + "fmt" +) + +type Challenger interface { + Name() string + New(map[string]string) error + Check([]byte) error + Verify([]byte) (*ReportStatus, error) + GetReport([]byte, uint64) (*ReportStatus, map[string]string, error) + ShowReportStatus(*ReportStatus) + // TODO + // PrepareChallenge() (*pb.AttestChallenge, error) + // HandleChallengeResponse(*pb.AttestResponse) (*Quote, error) +} + +type ReportStatus struct { + StatusCode uint32 + ErrorMessage string + SpecificStatus interface{} +} + +/* +type Service struct { + NonceForChallenge Nonce + NonceForVerify Nonce +} + +type Quote struct { + // FIXME: use interface like io.Reader as callback? + Evidence []byte +} +*/ + +const ( + // FIXME: allow tuning via parameter + seedTimeout int64 = 6e10 // 60 seconds +) + +func NewChallenger(aType string, cfg map[string]string) (Challenger, error) { + for _, c := range challengerList { + if c.Name() == aType { + if err := c.New(cfg); err != nil { + return nil, err + } + + return c, nil + } + } + + return nil, fmt.Errorf("Unsupported attestation service %s specified", aType) +} + +var challengerList []Challenger + +func registerChallenger(challenger Challenger) error { + for _, c := range challengerList { + if c.Name() == challenger.Name() { + return fmt.Errorf("Attestation service %s registered already", challenger.Name()) + } + } + + challengerList = append(challengerList, challenger) + + return nil +} + +/* +func PrepareChallenger() (*pb.AttestChallenge, error) { + return &pb.AttestChallenge{ + Nonce: NonceForChallenge.Generate(), + }, nil +} + +func HandleResponse(r *pb.AttestResponse) (*attest.Quote, error) { + quote := r.GetQuote() + + if len(quote) <= intelsgx.QuoteLength { + return nil, fmt.Errorf("Invalid length of quote returned: %d-byte", len(quote)) + } + + return &Quote{Evidence: quote}, nil +} +*/ diff --git a/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/nonce.go b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/nonce.go new file mode 100644 index 0000000000000000000000000000000000000000..53a05989b3e8cbabfed0cc95f417fe71ae99e1fa --- /dev/null +++ b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/nonce.go @@ -0,0 +1,28 @@ +package attestation // import "github.com/opencontainers/runc/libenclave/attestation" + +import ( + "encoding/binary" + "github.com/opencontainers/runc/libenclave/intelsgx" + "math/rand" + "time" +) + +// FIXME: how to make seed non-global? +type Nonce struct { + seed uint64 + timeout uint64 + // FIXME: use sync.mutex +} + +func (n *Nonce) Generate() []byte { + timestamp := uint64(time.Now().UnixNano()) + if n.seed+n.timeout >= timestamp { + n.seed = timestamp + } + + buf := make([]byte, intelsgx.NonceLength) + binary.LittleEndian.PutUint64(buf, rand.Uint64()) + binary.LittleEndian.PutUint64(buf[intelsgx.NonceLength/2:], rand.Uint64()) + + return buf +} diff --git a/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx-epid-challenger.go b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx-epid-challenger.go new file mode 100644 index 0000000000000000000000000000000000000000..e08a2047cb573aef4a2623547b5d1a32d57fb56f --- /dev/null +++ b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx-epid-challenger.go @@ -0,0 +1,84 @@ +package attestation // import "github.com/opencontainers/runc/libenclave/attestation" + +import ( + "fmt" + "github.com/opencontainers/runc/libenclave/attestation/sgx/ias" + "github.com/sirupsen/logrus" +) + +type sgxEpidChallenger struct { + ias *ias.IasAttestation +} + +/* The definition of StatusCode */ +const ( + StatusSgxBit = 0x80000000 +) + +func (epid *sgxEpidChallenger) Name() string { + return "sgx-epid" +} + +func (epid *sgxEpidChallenger) New(cfg map[string]string) error { + ias, err := ias.NewIasAttestation(cfg) + if err != nil { + return nil + } + epid.ias = ias + + return nil +} + +func (epid *sgxEpidChallenger) Check(quote []byte) error { + return epid.ias.CheckQuote(quote) +} + +func (epid *sgxEpidChallenger) Verify(quote []byte) (*ReportStatus, error) { + s, err := epid.ias.VerifyQuote(quote) + if err != nil { + return nil, err + } + + /* FIXME: check whether the report status is acceptable */ + status := &ReportStatus{ + StatusCode: StatusSgxBit, + SpecificStatus: s, + } + + return status, nil +} + +func (epid *sgxEpidChallenger) GetReport(quote []byte, nonce uint64) (*ReportStatus, map[string]string, error) { + s, report, err := epid.ias.RetrieveIasReport(quote, nonce) + if err != nil { + return nil, nil, err + } + + status := &ReportStatus{ + StatusCode: StatusSgxBit, + SpecificStatus: s, + } + + return status, report, nil +} + +func (epid *sgxEpidChallenger) ShowReportStatus(status *ReportStatus) { + if status.StatusCode&StatusSgxBit != StatusSgxBit { + logrus.Error("Report status is used for SGX EPID-based") + return + } + + s, ok := status.SpecificStatus.(*ias.IasReportStatus) + if ok { + logrus.Infof("Request ID: %s\n", s.RequestId) + logrus.Infof("Report ID: %s\n", s.ReportId) + logrus.Infof("Timestamp: %s\n", s.Timestamp) + logrus.Infof("IsvEnclaveQuoteStatus: %s\n", s.QuoteStatus) + } +} + +func init() { + if err := registerChallenger(&sgxEpidChallenger{}); err != nil { + fmt.Print(err) + } +} diff --git a/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/api.go b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/api.go new file mode 100644 index 0000000000000000000000000000000000000000..a388850ca728687a15081a42c3c93fc9e12881e8 --- /dev/null +++ b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/api.go @@ -0,0 +1,34 @@ +package ias // import "github.com/opencontainers/runc/libenclave/attestation/sgx/ias" + +const ( + apiV3 = 3 + apiV4 = 4 +) + +var ( + apiVersion uint64 = apiV4 +) + +type evidencePayload struct { + IsvEnclaveQuote string `json:"isvEnclaveQuote"` + PseManifest string `json:"pseManifest,omitempty"` + Nonce string `json:"nonce,omitempty"` +} + +type verificationReport struct { + Id string `json:"id"` + Timestamp string `json:"timestamp"` + Version uint32 `json:"version"` + IsvEnclaveQuoteStatus string `json:"isvEnclaveQuoteStatus"` + IsvEnclaveQuoteBody string `json:"isvEnclaveQuoteBody"` + RevocationReason uint32 `json:"revocationReason,omitempty"` + PseManifestStatus string `json:"pseManifestStatus,omitempty"` + PseManifestHash string `json:"pseManifestHash,omitempty"` + PlatformInfoBlob string `json:"platformInfoBlob,omitempty"` + Nonce string `json:"nonce,omitempty"` + EpidPseudonym string `json:"epidPseudonym,omitempty"` + + // V4 fields + AdvisoryIds string `json:"advisoryURL,omitempty"` + AdvisoryUrl []string `json:"advisoryIDs,omitempty"` +} diff --git a/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/ca_cert.go b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/ca_cert.go new file mode 100644 index 0000000000000000000000000000000000000000..f844062cf2597e968d27ad093c6eb5c877c0fc91 --- /dev/null +++ b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/ca_cert.go @@ -0,0 +1,33 @@ +package ias + +var caCert = []byte(`-----BEGIN CERTIFICATE----- +MIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV +BAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0 +YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy +MzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL +U2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD +DCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G +CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR+tXc8u1EtJzLA10Feu1Wg+p7e +LmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh +rgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT +L/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe +NpEJUmg4ktal4qgIAxk+QHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ +byinkNndn+Bgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H +afuVeLHcDsRp6hol4P+ZFIhu8mmbI1u0hH3W/0C2BuYXB5PC+5izFFh/nP0lc2Lf +6rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM +RoOaX4AS+909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX +MFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50 +L0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW +BBR4Q3t2pn680K9+QjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9+Qjfr +NXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq +hkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir +IEqucRiJSSx+HjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi+ripMtPZ +sFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi +zLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra +Ud4APK0wZTGtfPXU7w+IBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA +152Sq049ESDz+1rRGc2NVEqh1KaGXmtXvqxXcTB+Ljy5Bw2ke0v8iGngFBPqCTVB +3op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5+xmBc388v9Dm21HGfcC8O +DD+gT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R+mJTLwPXVMrv +DaVzWh5aiEx+idkSGMnX +-----END CERTIFICATE-----`) diff --git a/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/ias.go b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/ias.go new file mode 100644 index 0000000000000000000000000000000000000000..5b0d438c14994416c14e808ab57346252e2f59bc --- /dev/null +++ b/sgx-tools/vendor/github.com/opencontainers/runc/libenclave/attestation/sgx/ias/ias.go @@ -0,0 +1,439 @@ +package ias // import "github.com/opencontainers/runc/libenclave/attestation/sgx/ias" + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "fmt" + //pb "github.com/opencontainers/runc/libenclave/attestation/proto" + "github.com/opencontainers/runc/libenclave/intelsgx" + "github.com/sirupsen/logrus" + "io" + "math/rand" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "unsafe" +) + +const ( + spidLength = 16 + subscriptionKeyLength = 16 +) + +type IasAttestation struct { + reportApiUrl string + spid [spidLength]byte + subscriptionKey [subscriptionKeyLength]byte +} + +type IasReportStatus struct { + RequestId string + ReportId string + Timestamp string + QuoteStatus string +} + +func NewIasAttestation(cfg map[string]string) (*IasAttestation, error) { + isProduct := false + v, ok := cfg["service-class"] + if ok && v == "product" { + isProduct = true + } + + spid, ok := cfg["spid"] + if !ok || spid == "" { + return nil, fmt.Errorf("EPID parameter spid not specified") + } + + if len(spid) != spidLength*2 { + return nil, fmt.Errorf("Spid must be %d-character long", spidLength*2) + } + + subKey, ok := cfg["subscription-key"] + if !ok && subKey == "" { + return nil, fmt.Errorf("EPID parameter subscription-key not specified") + } + + if len(subKey) != subscriptionKeyLength*2 { + return nil, fmt.Errorf("Subscription key must be %d-character long", + subscriptionKeyLength*2) + } + + var rawSubKey []byte + var err error + if rawSubKey, err = hex.DecodeString(subKey); err != nil { + return nil, fmt.Errorf("Failed to decode subscription key: %s", err) + } + + var rawSpid []byte + if rawSpid, err = hex.DecodeString(spid); err != nil { + return nil, fmt.Errorf("Failed to decode spid: %s", err) + } + + url := "https://api.trustedservices.intel.com/sgx" + if !isProduct { + url += "/dev" + } + + version := apiVersion + apiVer, ok := cfg["apiVer"] + if ok && apiVer != "" { + version, err = strconv.ParseUint(apiVer, 10, 32) + if err != nil { + return nil, fmt.Errorf("Invalid IAS API Version: %s", err) + } + if version != apiV3 && apiVersion != apiV4 { + return nil, fmt.Errorf("Unsupported IAS API Version: %s", apiVer) + } + } + url += fmt.Sprintf("/attestation/v%d/report", version) + + ias := &IasAttestation{ + reportApiUrl: url, + } + copy(ias.subscriptionKey[:], rawSubKey) + copy(ias.spid[:], rawSpid) + + return ias, nil +} + +func (ias *IasAttestation) CheckQuote(q []byte) error { + quote := (*intelsgx.Quote)(unsafe.Pointer(&q[0])) + + logrus.Debugf("Target Platform's Quote") + logrus.Debugf(" Quote Body") + logrus.Debugf(" QUOTE Structure Version: %d", + quote.Version) + logrus.Debugf(" EPID Signature Type: %d", + quote.SignatureType) + logrus.Debugf(" Platform's EPID Group ID: %#08x", + quote.Gid) + logrus.Debugf(" Quoting Enclave's ISV assigned SVN: %#04x", + quote.ISVSvnQe) + logrus.Debugf(" Provisioning Certification Enclave's ISV assigned SVN: %#04x", + quote.ISVSvnPce) + logrus.Debugf(" EPID Basename: 0x%v", + hex.EncodeToString(quote.Basename[:])) + logrus.Debugf(" Report Body") + logrus.Debugf(" Target CPU SVN: 0x%v", + hex.EncodeToString(quote.CpuSvn[:])) + logrus.Debugf(" Enclave Misc Select: %#08x", + quote.MiscSelect) + logrus.Debugf(" Enclave Attributes: 0x%v", + hex.EncodeToString(quote.Attributes[:])) + logrus.Debugf(" Enclave Hash: 0x%v", + hex.EncodeToString(quote.MrEnclave[:])) + logrus.Debugf(" Enclave Signer: 0x%v", + hex.EncodeToString(quote.MrSigner[:])) + logrus.Debugf(" ISV assigned Product ID: %#04x", + quote.IsvProdId) + logrus.Debugf(" ISV assigned SVN: %#04x", + quote.IsvSvn) + logrus.Debugf(" Report Data: 0x%v...", + hex.EncodeToString(quote.ReportData[:32])) + logrus.Debugf(" Encrypted EPID Signature") + logrus.Debugf(" Length: %d", + quote.SigLen) + logrus.Debugf(" Signature: 0x%v...", + hex.EncodeToString(q[intelsgx.QuoteLength:intelsgx.QuoteLength+32])) + + if quote.Version != intelsgx.QuoteVersion { + return fmt.Errorf("Invalid quote version: %d", quote.Version) + } + + if quote.SignatureType != intelsgx.QuoteSignatureTypeUnlinkable && + quote.SignatureType != intelsgx.QuoteSignatureTypeLinkable { + return fmt.Errorf("Invalid signature type: %#04x", quote.SignatureType) + } + + spid := [spidLength]byte{} + copy(spid[:], quote.Basename[:spidLength]) + if spid != ias.spid { + return fmt.Errorf("Invalid spid in quote body: 0x%v", + hex.EncodeToString(quote.Basename[:])) + } + + return nil +} + +func (ias *IasAttestation) VerifyQuote(quote []byte) (*IasReportStatus, error) { + status, _, err := ias.RetrieveIasReport(quote, 0) + if err != nil { + return nil, err + } + + return status, nil +} + +func (ias *IasAttestation) GetVerifiedReport(quote []byte, nonce uint64) (*IasReportStatus, map[string]string, error) { + return ias.RetrieveIasReport(quote, nonce) +} + +func (ias *IasAttestation) RetrieveIasReport(quote []byte, nonce uint64) (*IasReportStatus, map[string]string, error) { + var nonceStr string + + if nonce == 0 { + nonceStr = strconv.FormatUint(rand.Uint64(), 16) + strconv.FormatUint(rand.Uint64(), 16) + } else { + nonceStr = strconv.FormatUint(nonce, 16) + } + + p := &evidencePayload{ + IsvEnclaveQuote: base64.StdEncoding.EncodeToString(quote), + PseManifest: "", + Nonce: nonceStr, + } + + resp, err := ias.reportAttestationEvidence(p) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + status, rawReport, err := checkAttestationVerificationReport(resp, quote, nonceStr) + if err != nil { + return nil, nil, err + } + + return status, formatIasReport(resp, rawReport), nil +} + +func (ias *IasAttestation) reportAttestationEvidence(p *evidencePayload) (*http.Response, error) { + var jp []byte + var err error + + if jp, err = json.Marshal(p); err != nil { + return nil, fmt.Errorf("Failed to marshal evidence payload: %s", err) + } + + bjp := bytes.NewBuffer(jp) + var req *http.Request + if req, err = http.NewRequest(http.MethodPost, ias.reportApiUrl, bjp); err != nil { + return nil, fmt.Errorf("Failed to create http.Request: %s", err) + } + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Ocp-Apim-Subscription-Key", hex.EncodeToString(ias.subscriptionKey[:])) + + logrus.Debugf("Initializing attestation evidence report ...") + + if dump, err := httputil.DumpRequestOut(req, true); err == nil { + logrus.Debugf("--- start of request ---") + logrus.Debugf("%s\n", dump) + logrus.Debugf("--- end of request ---") + } + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + + var resp *http.Response + if resp, err = client.Do(req); err != nil { + return nil, fmt.Errorf("Failed to send http request and receive http response: %s", err) + } + + logrus.Debugf("Attestation evidence response retrieved ...") + + if dump, err := httputil.DumpResponse(resp, true); err == nil { + logrus.Debugf("--- start of response ---") + logrus.Debugf("%s\n", dump) + logrus.Debugf("--- end of response ---") + } + + return resp, nil +} + +func formatIasReport(resp *http.Response, rawReport string) map[string]string { + iasReport := make(map[string]string) + + iasReport["Body"] = rawReport + iasReport["StatusCode"] = strconv.FormatUint(uint64(resp.StatusCode), 10) + iasReport["Request-ID"] = resp.Header.Get("Request-ID") + iasReport["X-Iasreport-Signature"] = resp.Header.Get("X-Iasreport-Signature") + iasReport["X-Iasreport-Signing-Certificate"] = resp.Header.Get("X-Iasreport-Signing-Certificate") + iasReport["ContentLength"] = strconv.FormatUint(uint64(resp.ContentLength), 10) + iasReport["Content-Type"] = resp.Header.Get("Content-Type") + + return iasReport +} + +func checkAttestationVerificationReport(resp *http.Response, quote []byte, nonce string) (*IasReportStatus, string, error) { + status := &IasReportStatus{ + RequestId: "", + ReportId: "", + QuoteStatus: "", + } + + if resp.StatusCode != 200 { + errMsg := "Unexpected status" + + switch resp.StatusCode { + case 400: + errMsg = "Invalid Attestation Evidence Payload. The client should not repeat the request without modifications." + case 401: + errMsg = "Failed to authenticate or authorize request." + case 500: + errMsg = "Internal error occurred." + case 503: + errMsg = "IAS is currently not able to process the request due to a temporary overloading or maintenance. This is a temporary state and the same request can be repeated after some time." + default: + } + + return status, "", fmt.Errorf("%s: %s", resp.Status, errMsg) + } + + reqId := resp.Header.Get("Request-ID") + if reqId == "" { + return status, "", fmt.Errorf("No Request-ID in response header") + } + + status.RequestId = reqId + + if resp.Header.Get("X-Iasreport-Signature") == "" { + return status, "", fmt.Errorf("No X-Iasreport-Signature in response header") + } + + if resp.Header.Get("X-Iasreport-Signing-Certificate") == "" { + return status, "", fmt.Errorf("No X-Iasreport-Signing-Certificate in response header") + } + + if resp.ContentLength == -1 { + return status, "", fmt.Errorf("Unknown length of response body") + } + + if resp.Header.Get("Content-Type") != "application/json" { + return status, "", fmt.Errorf("Invalid content type (%s) in response", + resp.Header.Get("Content-Type")) + } + + var err error + rawReport := make([]byte, resp.ContentLength) + if _, err = io.ReadFull(resp.Body, rawReport); err != nil { + return status, "", fmt.Errorf("Failed to read reponse body (%d-byte): %s", + resp.ContentLength, err) + } + + var report verificationReport + if err = json.Unmarshal(rawReport, &report); err != nil { + return status, "", fmt.Errorf("Failed to unmarshal attestation verification report: %s: %s", + rawReport, err) + } + + status.ReportId = report.Id + status.Timestamp = report.Timestamp + status.QuoteStatus = report.IsvEnclaveQuoteStatus + + if report.Version != (uint32)(apiVersion) { + return status, "", fmt.Errorf("Unsupported attestation API version %d in attesation verification report", + report.Version) + } + + if report.Nonce != nonce { + return status, "", fmt.Errorf("Invalid nonce in attestation verification report: %s", + report.Nonce) + } + + if report.Id == "" || report.Timestamp == "" || + report.IsvEnclaveQuoteStatus == "" || + report.IsvEnclaveQuoteBody == "" { + return status, "", fmt.Errorf("Required fields in attestation verification report is not present: %s", + string(rawReport)) + } + + if report.IsvEnclaveQuoteStatus == "GROUP_OUT_OF_DATE" || + report.IsvEnclaveQuoteStatus == "CONFIGURATION_NEEDED" { + if report.Version == apiV3 { + if resp.Header.Get("Advisory-Ids") == "" || resp.Header.Get("Advisory-Url") == "" { + return status, "", fmt.Errorf("Advisory-Ids or Advisory-Url is not present in response header") + } + } else if report.Version == apiV4 && (report.AdvisoryIds == "" || report.AdvisoryUrl == nil) { + return status, "", fmt.Errorf("Advisory-Ids or Advisory-Url is not present in attestation verification report") + } + } + + var quoteBody []byte + if quoteBody, err = base64.StdEncoding.DecodeString(report.IsvEnclaveQuoteBody); err != nil { + return status, "", fmt.Errorf("Invalid isvEnclaveQuoteBody: %s", + report.IsvEnclaveQuoteBody) + } + + if len(quoteBody) != intelsgx.QuoteBodyLength+intelsgx.ReportBodyLength { + return status, "", fmt.Errorf("Invalid length of isvEnclaveQuoteBody: %d-byte", + len(quoteBody)) + } + + for i, v := range quoteBody { + if v != quote[i] { + return status, "", fmt.Errorf("Unexpected isvEnclaveQuoteBody: %s", + report.IsvEnclaveQuoteBody) + } + } + + var sig []byte + if sig, err = base64.StdEncoding.DecodeString( + resp.Header.Get("X-Iasreport-Signature")); err != nil { + return status, "", fmt.Errorf("Invalid X-Iasreport-Signature in response header: %s", + resp.Header.Get("X-Iasreport-Signature")) + } + + var pemCerts string + if pemCerts, err = url.QueryUnescape( + resp.Header.Get("X-Iasreport-Signing-Certificate")); err != nil { + return status, "", fmt.Errorf("Failed to unescape X-Iasreport-Signing-Certificate in response header: %s: %s", + resp.Header.Get("X-Iasreport-Signing-Certificate"), err) + } + + rawPemCerts := []byte(pemCerts) + rawPemCerts = append(rawPemCerts, caCert...) + + var derCerts []byte + for true { + var b *pem.Block + + if b, rawPemCerts = pem.Decode(rawPemCerts); err != nil { + return status, "", fmt.Errorf("Failed to convert PEM certificate to DER format: %s: %s", + pemCerts, err) + } + + if b == nil { + break + } + + if b.Type != "CERTIFICATE" { + return status, "", fmt.Errorf("Returned content is not PEM certificate: %s", + b.Type) + } + + derCerts = append(derCerts, b.Bytes...) + } + + var x509Certs []*x509.Certificate + if x509Certs, err = x509.ParseCertificates(derCerts); err != nil { + return status, "", fmt.Errorf("Failed to parse certificates: %s", err) + } + + cert := x509Certs[0] + if err = cert.CheckSignature(x509.SHA256WithRSA, rawReport, sig); err != nil { + return status, "", fmt.Errorf("Failed to verify the attestation verification report: %s", + err) + } + + for _, parentCert := range x509Certs[1:] { + if err = cert.CheckSignatureFrom(parentCert); err != nil { + return status, "", fmt.Errorf("Failed to verify the certificate (%s) with parent certificate (%s): %s", + cert.Subject.String(), parentCert.Subject.String(), err) + } + + cert = parentCert + } + + return status, string(rawReport), nil +} diff --git a/sgx-tools/vendor/modules.txt b/sgx-tools/vendor/modules.txt index 19e9165b61dec1d07275008bf9e3f21c5d009651..ea7015976efe296641d8c192646f63628251cb68 100644 --- a/sgx-tools/vendor/modules.txt +++ b/sgx-tools/vendor/modules.txt @@ -11,6 +11,8 @@ github.com/golang/protobuf/proto github.com/konsorten/go-windows-terminal-sequences # github.com/opencontainers/runc v0.0.0-00010101000000-000000000000 => github.com/alibaba/inclavare-containers/rune v0.0.0-20200910122807-fd8d2f54e423 ## explicit +github.com/opencontainers/runc/libenclave/attestation +github.com/opencontainers/runc/libenclave/attestation/sgx/ias github.com/opencontainers/runc/libenclave/intelsgx github.com/opencontainers/runc/libenclave/intelsgx/proto # github.com/pkg/errors v0.9.1