1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908
|
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package verify includes logic and embedded AMD keys to check attestation report signatures.
package verify
import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"flag"
"fmt"
"time"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/kds"
cpb "github.com/google/go-sev-guest/proto/check"
spb "github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/verify/trust"
"github.com/google/logger"
"go.uber.org/multierr"
)
const (
askVersion = 1
askKeyUsage = 0x13
arkVersion = 1
arkKeyUsage = 0x0
askX509Version = 3
asvkX509Version = 3
arkX509Version = 3
)
var (
// ErrMissingVlek is returned when attempting to verify a VLEK-signed report that doesn't also
// have its VLEK certificate attached.
ErrMissingVlek = errors.New("report signed with VLEK, but VLEK certificate is missing")
workaroundStepping = flag.Bool("workaround_kds_productname", false, "If true, don't compare "+
"stepping values from the VCEK certificate and the attestation's or options' Product")
)
func askVerifiedBy(signee, signer *abi.AskCert, signeeName, signerName string) error {
if signee.CertifyingID != signer.KeyID {
return fmt.Errorf("%s's certifying ID (%s) is not %s's key ID (%s) ",
signeeName, signerName, signee.CertifyingID.String(), signer.KeyID.String())
}
// The signatures in the AskCert format cannot be verified. The signed contents are an x509
// certificate with additional metadata that are not reconstructible from the sevcert file.
return nil
}
func askCertPubKey(cert *abi.AskCert) (*rsa.PublicKey, error) {
var result rsa.PublicKey
result.N = abi.AmdBigInt(cert.Modulus)
exponent := abi.AmdBigInt(cert.PubExp)
if !exponent.IsInt64() {
return nil, fmt.Errorf("AMD certificate public key exponent too large %s", exponent.String())
}
result.E = int(exponent.Int64())
return &result, nil
}
func crossCheckSevX509(sev *abi.AskCert, x *x509.Certificate) error {
// The cross-check is only meaningful if there's more than the X.509 certificates to trust.
if sev == nil {
return nil
}
// Perform a cross-check between the X.509 and AMD SEV format certificates.
switch pub := x.PublicKey.(type) {
case *rsa.PublicKey:
certPub, err := askCertPubKey(sev)
if err != nil {
return err
}
if !pub.Equal(certPub) {
return fmt.Errorf("cross-check failed: SEV cert public key (%v) not equal to X.509 public key (%v)", pub, certPub)
}
default:
return fmt.Errorf("product public key not RSA: %v", x.PublicKey)
}
return nil
}
// Check the expected metadata as documented in AMD's KDS specification
// https://www.amd.com/system/files/TechDocs/57230.pdf
func validateAmdLocation(name pkix.Name, role string) error {
checkSingletonList := func(l []string, name, names, value string) error {
if len(l) != 1 {
return fmt.Errorf("%s has %d %s, want 1", role, len(l), names)
}
if l[0] != value {
return fmt.Errorf("%s %s '%s' not expected for AMD. Expected '%s'", role, name, l[0], value)
}
return nil
}
if err := checkSingletonList(name.Country, "country", "countries", "US"); err != nil {
return err
}
if err := checkSingletonList(name.Locality, "locality", "localities", "Santa Clara"); err != nil {
return err
}
if err := checkSingletonList(name.Province, "state", "states", "CA"); err != nil {
return err
}
if err := checkSingletonList(name.Organization, "organization", "organizations", "Advanced Micro Devices"); err != nil {
return err
}
return checkSingletonList(name.OrganizationalUnit, "organizational unit", "organizational units", "Engineering")
}
func validateRootX509(productLine string, x *x509.Certificate, version int, role, cn string) error {
// Additionally check that the X.509 cert's public key matches the SEV format cert.
if x == nil {
return fmt.Errorf("no X.509 certificate for %s", role)
}
if x.Version != version {
return fmt.Errorf("%s certificate version: %d. Expected %d", role, x.Version, version)
}
if err := validateAmdLocation(x.Issuer, fmt.Sprintf("%s issuer", role)); err != nil {
return err
}
if err := validateAmdLocation(x.Subject, fmt.Sprintf("%s subject", role)); err != nil {
return err
}
// Only check product name if it's specified.
if cn != "" && x.Subject.CommonName != cn {
return fmt.Errorf("%s common-name is %s. Expected %s", role, x.Subject.CommonName, cn)
}
return validateCRLlink(x, productLine, role)
}
// validateAskX509 checks expected metadata about the ASK X.509 certificate. It does not verify the
// cryptographic signatures.
func validateAskX509(r *trust.AMDRootCerts) error {
if r == nil {
r = trust.DefaultRootCerts["Milan"]
}
cn := intermediateKeyCommonName(r.GetProductLine(), abi.VcekReportSigner)
if err := validateRootX509(r.GetProductLine(), r.ProductCerts.Ask, askX509Version, "ASK", cn); err != nil {
return err
}
if r.AskSev != nil {
return crossCheckSevX509(r.AskSev, r.ProductCerts.Ask)
}
return nil
}
func endorsementKeyCommonName(key abi.ReportSigner) string {
return fmt.Sprintf("SEV-%v", key)
}
func intermediateKeyCommonName(productLine string, key abi.ReportSigner) string {
if productLine != "" {
switch key {
case abi.VcekReportSigner:
return fmt.Sprintf("SEV-%s", productLine)
case abi.VlekReportSigner:
return fmt.Sprintf("SEV-VLEK-%s", productLine)
}
}
return ""
}
// validateAsvkX509 checks expected metadata about the ASVK X.509 certificate. It does not verify the
// cryptographic signatures.
func validateAsvkX509(r *trust.AMDRootCerts) error {
if r == nil {
r = trust.DefaultRootCerts["Milan"]
}
cn := intermediateKeyCommonName(r.GetProductLine(), abi.VlekReportSigner)
// There is no ASVK SEV ABI key released by AMD to cross-check.
return validateRootX509(r.GetProductLine(), r.ProductCerts.Asvk, asvkX509Version, "ASVK", cn)
}
// ValidateArkX509 checks expected metadata about the ARK X.509 certificate. It does not verify the
// cryptographic signatures.
func validateArkX509(r *trust.AMDRootCerts) error {
if r == nil {
r = trust.DefaultRootCerts["Milan"]
}
var cn string
if r.GetProductLine() != "" {
cn = fmt.Sprintf("ARK-%s", r.GetProductLine())
}
if err := validateRootX509(r.GetProductLine(), r.ProductCerts.Ark, arkX509Version, "ARK", cn); err != nil {
return err
}
if r.ArkSev != nil {
return crossCheckSevX509(r.ArkSev, r.ProductCerts.Ark)
}
return nil
}
// Checks some steps of AMD SEV API Appendix B.3
func validateRootSev(subject, issuer *abi.AskCert, version, keyUsage uint32, subjectRole, issuerRole string) error {
// Step 1 or 5
if subject.Version != version {
return fmt.Errorf("%s AMD cert is version %d, expected %d", subjectRole, subject.Version, version)
}
// Step 2 or 6
if subject.KeyUsage != keyUsage {
return fmt.Errorf("%s certificate KeyUsage is 0x%x, should be 0x%x", subjectRole, subject.KeyUsage, keyUsage)
}
return askVerifiedBy(subject, issuer, subjectRole, issuerRole)
}
// validateAskSev checks ASK SEV format certificate validity according to AMD SEV API Appendix B.3
// This covers steps 1, 2, and 5
func validateAskSev(r *trust.AMDRootCerts) error {
if r == nil {
r = trust.DefaultRootCerts["Milan"]
}
return validateRootSev(r.AskSev, r.ArkSev, askVersion, askKeyUsage, "ASK", "ARK")
}
// ValidateArkSev checks ARK certificate validity according to AMD SEV API Appendix B.3
// This covers steps 5, 6, 9, and 11.
func validateArkSev(r *trust.AMDRootCerts) error {
if r == nil {
r = trust.DefaultRootCerts["Milan"]
}
return validateRootSev(r.ArkSev, r.ArkSev, arkVersion, arkKeyUsage, "ARK", "ARK")
}
// validateX509 will validate the x509 certificates of the ASK, ASVK, and ARK.
func validateX509(r *trust.AMDRootCerts, key abi.ReportSigner) error {
if err := validateArkX509(r); err != nil {
return fmt.Errorf("ARK validation error: %v", err)
}
if r.ProductCerts.Ask == nil && key == abi.VcekReportSigner {
return fmt.Errorf("trusted root must have ASK certificate for VCEK chain")
}
if r.ProductCerts.Asvk == nil && key == abi.VlekReportSigner {
return fmt.Errorf("trusted root must have ASVK certificate for VLEK chain")
}
if r.ProductCerts.Ask != nil {
if err := validateAskX509(r); err != nil {
return fmt.Errorf("ASK validation error: %v", err)
}
}
if r.ProductCerts.Asvk != nil {
if err := validateAsvkX509(r); err != nil {
return fmt.Errorf("ASVK validation error: %v", err)
}
}
return nil
}
// validateKDSCertSubject checks KDS-specified values of the subject metadata of the AMD certificate.
func validateKDSCertSubject(subject pkix.Name, key abi.ReportSigner) error {
if err := validateAmdLocation(subject, fmt.Sprintf("%v subject", key)); err != nil {
return err
}
want := endorsementKeyCommonName(key)
if subject.CommonName != want {
return fmt.Errorf("%s certificate subject common name %v not expected. Expected %s", key, subject.CommonName, want)
}
return nil
}
// validateKDSCertIssuer checks KDS-specified values of the issuer metadata of the AMD certificate.
func validateKDSCertIssuer(r *trust.AMDRootCerts, issuer pkix.Name, key abi.ReportSigner) error {
if err := validateAmdLocation(issuer, fmt.Sprintf("%v issuer", key)); err != nil {
return err
}
cn := intermediateKeyCommonName(r.GetProductLine(), key)
if issuer.CommonName != cn {
return fmt.Errorf("%s certificate issuer common name %v not expected. Expected %s", key, issuer.CommonName, cn)
}
return nil
}
// CRLUnavailableErr represents a problem with fetching the CRL from the network.
// This type is special to allow for easy "fail open" semantics for CRL unavailability. See
// Adam Langley's write-up on CRLs and network unreliability
// https://www.imperialviolet.org/2014/04/19/revchecking.html
type CRLUnavailableErr struct {
error
}
// GetCrlAndCheckRoot downloads the given cert's CRL from one of the distribution points and
// verifies that the CRL is valid and doesn't revoke an intermediate key.
func GetCrlAndCheckRoot(r *trust.AMDRootCerts, opts *Options) (*x509.RevocationList, error) {
r.Mu.Lock()
defer r.Mu.Unlock()
getter := opts.Getter
if getter == nil {
getter = trust.DefaultHTTPSGetter()
}
if r.CRL != nil && opts.Now.Before(r.CRL.NextUpdate) {
if err := verifyCRL(r); err != nil {
return nil, err
}
return r.CRL, nil
}
var errs error
for _, url := range r.ProductCerts.Ask.CRLDistributionPoints {
bytes, err := getter.Get(url)
if err != nil {
errs = multierr.Append(errs, err)
continue
}
crl, err := x509.ParseRevocationList(bytes)
if err != nil {
errs = multierr.Append(errs, err)
continue
}
r.CRL = crl
if err := verifyCRL(r); err != nil {
return nil, err
}
return r.CRL, nil
}
return nil, CRLUnavailableErr{multierr.Append(errs, errors.New("could not fetch product CRL"))}
}
// verifyCRL checks that the VCEK CRL is signed by the ARK. Must be called after r.CRL is set and while
// r.Mu is held.
func verifyCRL(r *trust.AMDRootCerts) error {
if r.CRL == nil {
return errors.New("internal error: CRL not set")
}
if r.ProductCerts.Ark == nil {
return errors.New("missing ARK x509 certificate to check CRL validity")
}
if r.ProductCerts.Ask == nil {
return errors.New("missing ASK x509 certificate to check intermediate key validity")
}
if err := r.CRL.CheckSignatureFrom(r.ProductCerts.Ark); err != nil {
return fmt.Errorf("CRL is not signed by ARK: %v", err)
}
for _, bad := range r.CRL.RevokedCertificates {
if r.ProductCerts.Ask.SerialNumber.Cmp(bad.SerialNumber) == 0 {
return fmt.Errorf("ASK was revoked at %v", bad.RevocationTime)
}
// From offline discussions with AMD, we don't expect them to ever explicitly revoke a VCEK
// since TCB numbers serve the purpose of superceding previous certificates.
}
return nil
}
// VcekNotRevoked will consult the online CRL listed in the VCEK certificate for whether this cert
// has been revoked. Returns nil if not revoked, error on any problem.
func VcekNotRevoked(r *trust.AMDRootCerts, _ *x509.Certificate, options *Options) error {
_, err := GetCrlAndCheckRoot(r, options)
return err
}
// product is expected to be of form "Milan" or "Genoa".
// role is expected to be one of "ARK", "ASK", "ASVK".
func validateCRLlink(x *x509.Certificate, productLine, role string) error {
url := kds.CrlLinkByRole(productLine, role)
if len(x.CRLDistributionPoints) != 1 {
return fmt.Errorf("%s has %d CRL distribution points, want 1", role, len(x.CRLDistributionPoints))
}
if x.CRLDistributionPoints[0] != url {
return fmt.Errorf("%s CRL distribution point is '%s', want '%s'", role, x.CRLDistributionPoints[0], url)
}
return nil
}
// validateVcekExtensions checks if the certificate extensions match
// wellformedness expectations.
func validateExtensions(exts *kds.Extensions, key abi.ReportSigner) error {
_, err := kds.ParseProductName(exts.ProductName, key)
return err
}
// validateKDSCertificateProductNonspecific returns an error if the given certificate doesn't have
// the documented qualities of a V[CL]EK certificate according to Key Distribution Service
// documentation:
// https://www.amd.com/system/files/TechDocs/57230.pdf
// This does not check the certificate revocation list since that requires internet access.
// If valid, then returns the V[CL]EK-specific certificate extensions in the VcekExtensions type.
func validateKDSCertificateProductNonspecific(cert *x509.Certificate, key abi.ReportSigner) (*kds.Extensions, error) {
if cert.Version != 3 {
return nil, fmt.Errorf("%v certificate version is %v, expected 3", key, cert.Version)
}
// Signature algorithm: RSASSA-PSS
// Signature hash algorithm sha384
if cert.SignatureAlgorithm != x509.SHA384WithRSAPSS {
return nil, fmt.Errorf("%v certificate signature algorithm is %v, expected SHA-384 with RSASSA-PSS", key, cert.SignatureAlgorithm)
}
// Subject Public Key Info ECDSA on curve P-384
if cert.PublicKeyAlgorithm != x509.ECDSA {
return nil, fmt.Errorf("%v certificate public key type is %v, expected ECDSA", key, cert.PublicKeyAlgorithm)
}
// Locally bind the public key any type to allow for occurrence typing in the switch statement.
switch pub := cert.PublicKey.(type) {
case *ecdsa.PublicKey:
if pub.Curve.Params().Name != "P-384" {
return nil, fmt.Errorf("%v certificate public key curve is %s, expected P-384", key, pub.Curve.Params().Name)
}
default:
return nil, fmt.Errorf("%v certificate public key not ecdsa PublicKey type %v", key, pub)
}
if err := validateKDSCertSubject(cert.Subject, key); err != nil {
return nil, err
}
exts, err := kds.CertificateExtensions(cert, key)
if err != nil {
return nil, err
}
if err := validateExtensions(exts, key); err != nil {
return nil, err
}
return exts, nil
}
func validateKDSCertificateProductSpecifics(r *trust.AMDRootCerts, cert *x509.Certificate, key abi.ReportSigner, opts *Options) error {
if err := validateKDSCertIssuer(r, cert.Issuer, key); err != nil {
return err
}
// ica: Intermediate Certificate Authority.
ica := r.ProductCerts.Ask
if key == abi.VlekReportSigner {
ica = r.ProductCerts.Asvk
}
if ica == nil {
return fmt.Errorf("root of trust missing intermediate certificate authority certificate for key %v", key)
}
verifyOpts := r.X509Options(opts.Now, key)
if verifyOpts == nil {
return fmt.Errorf("internal error: could not get X509 options for %v (missing ARK cert or ICA cert)", key)
}
if _, err := cert.Verify(*verifyOpts); err != nil {
return fmt.Errorf("error verifying %v certificate: %v (%v)", key, err, ica.IsCA)
}
// VCEK is not expected to have a CRL link.
return nil
}
func checkProductName(got, want *spb.SevProduct, key abi.ReportSigner) error {
// No constraint
if want == nil {
return nil
}
if got == nil {
return fmt.Errorf("internal error: no product name")
}
if got.Name != want.Name {
return fmt.Errorf("%v cert product name %v is not %v", key, got, want)
}
// The model stepping number is only part of the VLEK product name, not VLEK's.
if key == abi.VcekReportSigner && want.MachineStepping != nil {
if got.MachineStepping == nil {
return fmt.Errorf("stepping value in VCEK certificate should not be nil")
}
if got.MachineStepping.Value != want.MachineStepping.Value && !*workaroundStepping {
return fmt.Errorf("%v cert product stepping number 0x%X is not 0x%X",
key, got.MachineStepping.Value, want.MachineStepping.Value)
}
}
return nil
}
// decodeCerts checks that the V[CL]EK certificate matches expected fields
// from the KDS specification and also that its certificate chain matches
// hardcoded trusted root certificates from AMD.
func decodeCerts(chain *spb.CertificateChain, key abi.ReportSigner, knownProductLine string, options *Options) (*x509.Certificate, *trust.AMDRootCerts, error) {
var ek []byte
switch key {
case abi.VcekReportSigner:
ek = chain.GetVcekCert()
case abi.VlekReportSigner:
ek = chain.GetVlekCert()
}
if len(ek) == 0 {
return nil, nil, fmt.Errorf("missing %v certificate", key)
}
endorsementKeyCert, err := trust.ParseCert(ek)
if err != nil {
return nil, nil, fmt.Errorf("could not interpret %v DER bytes %v: %v", key, ek, err)
}
exts, err := validateKDSCertificateProductNonspecific(endorsementKeyCert, key)
if err != nil {
return nil, nil, err
}
roots := options.TrustedRoots
productLine := knownProductLine
// Relevant for v2 reports only.
if productLine == "" {
product, err := kds.ParseProductName(exts.ProductName, key)
if err != nil {
return nil, nil, err
}
productLine = kds.ProductLine(product)
// Ensure the extension product info matches expectations.
if err := checkProductName(product, options.Product, key); err != nil {
return nil, nil, err
}
}
if len(roots) == 0 {
root := trust.AMDRootCertsProduct(productLine)
// Require that the root matches embedded root certs.
root.AskSev = trust.DefaultRootCerts[productLine].AskSev
root.ArkSev = trust.DefaultRootCerts[productLine].ArkSev
if err := root.Decode(chain.GetAskCert(), chain.GetArkCert()); err != nil {
return nil, nil, err
}
if err := validateX509(root, key); err != nil {
return nil, nil, err
}
roots = map[string][]*trust.AMDRootCerts{
productLine: {root},
}
}
var lastErr error
for _, productRoot := range roots[productLine] {
if err := validateKDSCertificateProductSpecifics(productRoot, endorsementKeyCert, key, options); err != nil {
lastErr = err
continue
}
return endorsementKeyCert, productRoot, nil
}
return nil, nil, fmt.Errorf("%v could not be verified by any trusted roots. Last error: %v", key, lastErr)
}
// SnpReportSignature verifies the attestation report's signature based on the report's
// SignatureAlgo.
func SnpReportSignature(report []byte, vcek *x509.Certificate) error {
if err := abi.ValidateReportFormat(report); err != nil {
return fmt.Errorf("attestation report format error: %v", err)
}
der, err := abi.ReportToSignatureDER(report)
if err != nil {
return fmt.Errorf("could not interpret report signature: %v", err)
}
if abi.SignatureAlgo(report) == abi.SignEcdsaP384Sha384 {
if err := vcek.CheckSignature(x509.ECDSAWithSHA384, abi.SignedComponent(report), der); err != nil {
return fmt.Errorf("report signature verification error: %v", err)
}
return nil
}
return fmt.Errorf("unknown SignatureAlgo: %d", abi.SignatureAlgo(report))
}
// SnpProtoReportSignature verifies the protobuf representation of an attestation report's signature
// based on the report's SignatureAlgo.
func SnpProtoReportSignature(report *spb.Report, vcek *x509.Certificate) error {
raw, err := abi.ReportToAbiBytes(report)
if err != nil {
return fmt.Errorf("could not interpret report: %v", err)
}
return SnpReportSignature(raw, vcek)
}
// Options represents verification options for an SEV-SNP attestation report.
type Options struct {
// CheckRevocations set to true if the verifier should retrieve the CRL from the network and check
// if the VCEK or ASK have been revoked according to the ARK.
CheckRevocations bool
// DisableCertFetching set to true if SnpAttestation should not connect to the AMD KDS to fill in
// any missing certificates in an attestation's certificate chain. Uses Getter if false.
DisableCertFetching bool
// Getter takes a URL and returns the body of its contents. By default uses http.Get and returns
// the body.
Getter trust.HTTPSGetter
// Now is the time at which to verify the validity of certificates. If unset, uses time.Now().
Now time.Time
// TrustedRoots specifies the ARK and ASK certificates to trust when checking the VCEK. If nil,
// then verification will fall back on embedded AMD-published root certificates.
// Maps the product name to an array of allowed roots.
TrustedRoots map[string][]*trust.AMDRootCerts
// Product is a forced value for the attestation product name when verifying or retrieving
// VCEK certificates. An attestation should carry the product of the reporting
// machine. Only used for v2 attestation reports.
Product *spb.SevProduct
}
// DefaultOptions returns a useful default verification option setting
func DefaultOptions() *Options {
return &Options{
Getter: trust.DefaultHTTPSGetter(),
Now: time.Now(),
}
}
func getTrustedRoots(rot *cpb.RootOfTrust) (map[string][]*trust.AMDRootCerts, error) {
result := map[string][]*trust.AMDRootCerts{}
for _, path := range rot.CabundlePaths {
root := trust.AMDRootCertsProduct(rot.ProductLine)
if err := root.FromKDSCert(path); err != nil {
return nil, fmt.Errorf("could not parse CA bundle %q: %v", path, err)
}
result[rot.ProductLine] = append(result[rot.ProductLine], root)
}
for _, cabundle := range rot.Cabundles {
root := trust.AMDRootCertsProduct(rot.ProductLine)
if err := root.FromKDSCertBytes([]byte(cabundle)); err != nil {
return nil, fmt.Errorf("could not parse CA bundle bytes: %v", err)
}
result[rot.ProductLine] = append(result[rot.ProductLine], root)
}
return result, nil
}
// RootOfTrustToOptions translates the RootOfTrust message into the Options type needed
// for driving an attestation verification.
func RootOfTrustToOptions(rot *cpb.RootOfTrust) (*Options, error) {
trustedRoots, err := getTrustedRoots(rot)
if err != nil {
return nil, err
}
return &Options{
CheckRevocations: rot.CheckCrl,
DisableCertFetching: rot.DisallowNetwork,
TrustedRoots: trustedRoots,
}, nil
}
func updateProductExpectation(product **spb.SevProduct, reportProduct *spb.SevProduct) error {
// The expected product in verification options may be under-specified or conflicting with a given
// product, so check and update the expectation to match the reported product info.
if *product == nil {
if *workaroundStepping && reportProduct != nil {
*product = &spb.SevProduct{Name: reportProduct.Name}
} else {
*product = reportProduct
}
return nil
}
if (*product).GetName() != reportProduct.GetName() {
return fmt.Errorf("expected product name %v, got %v", (*product).GetName(), reportProduct.GetName())
}
expectStepping := (*product).GetMachineStepping()
reportStepping := reportProduct.GetMachineStepping()
if expectStepping == nil {
if !*workaroundStepping {
(*product).MachineStepping = reportStepping
}
return nil
}
if reportStepping == nil {
return nil
}
// This check is not skipped for Issue#115 since we only want to skip inferred stepping, not
// explicitly expected stepping.
if expectStepping.GetValue() != reportStepping.GetValue() {
return fmt.Errorf("expected product stepping %d, got %d", expectStepping.Value, reportStepping.Value)
}
return nil
}
// SnpAttestation verifies the protobuf representation of an attestation report's signature based
// on the report's SignatureAlgo, provided the certificate chain is valid.
func SnpAttestation(attestation *spb.Attestation, options *Options) error {
if options == nil {
return fmt.Errorf("options cannot be nil")
}
if attestation == nil {
return fmt.Errorf("attestation cannot be nil")
}
// Make sure we have the whole certificate chain, or at least the product
// info.
if err := fillInAttestation(attestation, options); err != nil {
return err
}
report := attestation.GetReport()
info, err := abi.ParseSignerInfo(report.GetSignerInfo())
if err != nil {
return err
}
chain := attestation.GetCertificateChain()
var knownProductLine string
if fms := attestation.GetReport().GetCpuid1EaxFms(); fms != 0 {
knownProductLine = kds.ProductLineFromFms(fms)
}
endorsementKeyCert, root, err := decodeCerts(chain, info.SigningKey, knownProductLine, options)
if err != nil {
return err
}
if options.CheckRevocations {
if err := VcekNotRevoked(root, endorsementKeyCert, options); err != nil {
return err
}
}
return SnpProtoReportSignature(report, endorsementKeyCert)
}
func getProductFromCerts(attestation *spb.Attestation) *spb.SevProduct {
certs := abi.CertsFromProto(attestation.CertificateChain)
blob, err := certs.GetByGUIDString(abi.ExtraPlatformInfoGUID)
if err != nil {
return nil
}
info, err := abi.ParseExtraPlatformInfo(blob)
if err != nil {
return nil
}
return abi.SevProductFromCpuid1Eax(info.Cpuid1Eax)
}
// Returns the product information in the attestation.
func getProduct(attestation *spb.Attestation) *spb.SevProduct {
product := getProductFromCerts(attestation)
if product != nil {
return product
}
// TODO(Issue#109): Remove.
return attestation.Product
}
// Updates the attestation representation of the product. This is lossy given the product -> Cpuid1Eax translation.
func setProduct(attestation *spb.Attestation, product *spb.SevProduct) {
blob, _ := (&abi.ExtraPlatformInfo{
Size: abi.ExtraPlatformInfoV0Size,
Cpuid1Eax: abi.MaskedCpuid1EaxFromSevProduct(product),
}).Marshal()
attestation.CertificateChain.Extras[abi.ExtraPlatformInfoGUID] = blob
// TODO(Issue#109): Remove
attestation.Product = product
}
// In version 2 attestation reports, there is no cpuid_1_eax information about the
// family/model/stepping of the chip, so it's difficult to derive the endpoint from which to
// fetch a VCEK certificate.
// In version 3 attestation reports, the information is present, so we can directly return
// the product line from those fields of the report.
//
// The result values are a product line string, a method of updating product information when there
// is no explicit product expectation from options, and a method of updating the product expectation
// when relevant. This can correct any inaccuracy about a stepping value.
// For v3 reports, these update functions are trivial, as there are no inaccuracies to correct when
// the information is directly in the attestation report.
func cpuidWorkaround(attestation *spb.Attestation, options *Options) (string, func([]byte) error, func() error, error) {
productUpdate := func([]byte) error { return nil }
updateExpectation := func() error { return nil }
fms := attestation.GetReport().GetCpuid1EaxFms()
if fms != 0 {
return kds.ProductLineFromFms(fms), productUpdate, updateExpectation, nil
}
// ATTESTATION_REPORT v2 makes product determination difficult.
product := getProduct(attestation)
if product == nil {
if options.Product != nil {
product = options.Product
} else {
logger.Warning("Attestation missing product information. KDS certificate may be invalid. Using default Milan-B1")
attestation.Product = abi.DefaultSevProduct()
}
productUpdate = func(vcek []byte) error {
cert, err := x509.ParseCertificate(vcek)
if err != nil {
return err
}
exts, err := kds.VcekCertificateExtensions(cert)
if err != nil {
return err
}
product, err = kds.ParseProductName(exts.ProductName, abi.VcekReportSigner)
if err != nil {
return err
}
return nil
}
}
updateExpectation = func() error {
// Pass along the expected product information for VcekDER. fillInAttestation will ensure
// that this is a noop if options.Product began as non-nil.
return updateProductExpectation(&options.Product, product)
}
return kds.ProductLine(product), productUpdate, updateExpectation, nil
}
// fillInAttestation uses AMD's KDS to populate any empty certificate field in the attestation's
// certificate chain.
func fillInAttestation(attestation *spb.Attestation, options *Options) error {
if options.DisableCertFetching {
return nil
}
productLine, productUpdate, updateExpectation, err := cpuidWorkaround(attestation, options)
if err != nil {
return err
}
getter := options.Getter
if getter == nil {
getter = trust.DefaultHTTPSGetter()
}
report := attestation.GetReport()
info, err := abi.ParseSignerInfo(report.GetSignerInfo())
if err != nil {
return err
}
chain := attestation.GetCertificateChain()
if chain == nil {
chain = &spb.CertificateChain{}
attestation.CertificateChain = chain
}
if len(chain.GetAskCert()) == 0 || len(chain.GetArkCert()) == 0 {
askark, err := trust.GetProductChain(productLine, info.SigningKey, getter)
if err != nil {
return err
}
if len(chain.GetAskCert()) == 0 {
chain.AskCert = askark.Ask.Raw
}
if len(chain.GetArkCert()) == 0 {
chain.ArkCert = askark.Ark.Raw
}
}
switch info.SigningKey {
case abi.VcekReportSigner:
if len(chain.GetVcekCert()) == 0 {
vcekURL := kds.VCEKCertURL(productLine, report.GetChipId(), kds.TCBVersion(report.GetReportedTcb()))
vcek, err := getter.Get(vcekURL)
if err != nil {
return &trust.AttestationRecreationErr{
Msg: fmt.Sprintf("could not download VCEK certificate: %v", err),
}
}
chain.VcekCert = vcek
// An attempt was made with defaults or the option's product, so now use
// the VCEK cert to determine the real product info.
if err := productUpdate(vcek); err != nil {
return err
}
}
case abi.VlekReportSigner:
// We can't lazily ask KDS for the certificate as a user. The CSP must cache their provisioned
// certificates and provide them in GET_EXT_REPORT.
if len(chain.GetVlekCert()) == 0 {
return ErrMissingVlek
}
}
return updateExpectation()
}
// GetAttestationFromReport uses AMD's Key Distribution Service (KDS) to download the certificate
// chain for the VCEK that supposedly signed the given report, and returns the Attestation
// representation of their combination. If getter is nil, uses Golang's http.Get.
func GetAttestationFromReport(report *spb.Report, options *Options) (*spb.Attestation, error) {
result := &spb.Attestation{
Report: report,
CertificateChain: &spb.CertificateChain{Extras: map[string][]byte{}},
}
if err := fillInAttestation(result, options); err != nil {
return nil, err
}
// Attempt to fill in the product field of the attestation. Don't error at this
// point since this is not validation.
info, _ := abi.ParseSignerInfo(report.SignerInfo)
var exts *kds.Extensions
parse := func(der []byte) *x509.Certificate {
out, _ := x509.ParseCertificate(der)
return out
}
switch info.SigningKey {
case abi.VcekReportSigner:
exts, _ = kds.VcekCertificateExtensions(parse(result.CertificateChain.VcekCert))
case abi.VlekReportSigner:
exts, _ = kds.VlekCertificateExtensions(parse(result.CertificateChain.VlekCert))
}
if exts != nil && report.GetCpuid1EaxFms() == 0 {
product, _ := kds.ParseProductName(exts.ProductName, info.SigningKey)
setProduct(result, product)
}
return result, nil
}
// SnpReport verifies the protobuf representation of an attestation report's signature based
// on the report's SignatureAlgo and uses the AMD Key Distribution Service to download the
// report's corresponding VCEK certificate.
func SnpReport(report *spb.Report, options *Options) error {
if options.DisableCertFetching {
return errors.New("cannot verify attestation report without fetching certificates")
}
attestation, err := GetAttestationFromReport(report, options)
if err != nil {
return fmt.Errorf("could not recreate attestation from report: %w", err)
}
return SnpAttestation(attestation, options)
}
// RawSnpReport verifies the raw bytes representation of an attestation report's signature
// based on the report's SignatureAlgo and uses the AMD Key Distribution Service to download
// the report's corresponding VCEK certificate.
func RawSnpReport(rawReport []byte, options *Options) error {
report, err := abi.ReportToProto(rawReport)
if err != nil {
return fmt.Errorf("could not interpret report bytes: %v", err)
}
return SnpReport(report, options)
}
|