diff --git a/client/client.go b/client/client.go index ef55409..2569774 100644 --- a/client/client.go +++ b/client/client.go @@ -30,6 +30,7 @@ import ( "time" log "github.com/golang/glog" + ownercertificate "github.com/openconfig/bootz/common/owner_certificate" ownershipvoucher "github.com/openconfig/bootz/common/ownership_voucher" "github.com/openconfig/bootz/common/signature" @@ -72,11 +73,6 @@ func validateArtifacts(serialNumber string, resp *bpb.GetBootstrapDataResponse) log.Infof("Validated ownership voucher signed by vendor") log.Infof("=============================================================================") - oc := resp.GetOwnershipCertificate() - if len(oc) == 0 { - return fmt.Errorf("received empty ownership certificate from server") - } - // Verify the serial number for this OV log.Infof("Verifying the serial number for this OV") if parsedOV.OV.SerialNumber != serialNumber { @@ -95,18 +91,8 @@ func validateArtifacts(serialNumber string, resp *bpb.GetBootstrapDataResponse) // Parse the Ownership Certificate. log.Infof("Parsing the OC") - ocCert, err := x509.ParseCertificate(oc) + ocCert, err := ownercertificate.Verify(resp.GetOwnershipCertificate(), pdcPool) if err != nil { - return fmt.Errorf("failed to parse certificate: %v", err) - } - - // Verify that the OC is signed by the PDC. - log.Infof("Verifying that the OC is signed by the PDC") - opts := x509.VerifyOptions{ - Roots: pdcPool, - Intermediates: x509.NewCertPool(), - } - if _, err := ocCert.Verify(opts); err != nil { return err } log.Infof("Validated ownership certificate with OV PDC") diff --git a/common/owner_certificate/owner_certificate.go b/common/owner_certificate/owner_certificate.go new file mode 100644 index 0000000..a7e414f --- /dev/null +++ b/common/owner_certificate/owner_certificate.go @@ -0,0 +1,62 @@ +// Copyright 2023 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 +// +// https://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 ownercertificate provides helper functions for generating, parsing and verifying owner certs. +package ownercertificate + +import ( + "crypto" + "crypto/x509" + "fmt" + + "go.mozilla.org/pkcs7" +) + +// Verify checks that the provided CMS value is signed by a signer in the provided +// certPool and returns the Ownership Certificate. +func Verify(in []byte, certPool *x509.CertPool) (*x509.Certificate, error) { + if len(in) == 0 { + return nil, fmt.Errorf("owner certificate is empty") + } + p7, err := pkcs7.Parse(in) + if err != nil { + return nil, fmt.Errorf("unable to parse into pkcs7 format: %v", err) + } + if err = p7.VerifyWithChain(certPool); err != nil { + return nil, fmt.Errorf("failed to verify OC: %v", err) + } + if len(p7.Certificates) == 0 { + return nil, fmt.Errorf("no certificates found in pkcs7 message") + } + return p7.Certificates[0], nil +} + +// GenerateCMS takes an Ownership Certificate keypair and converts it to a CMS structure. +// The CMS structure contains the Ownership Certificate in its list of certificates. +func GenerateCMS(cert *x509.Certificate, priv crypto.PrivateKey) ([]byte, error) { + signedMessage, err := pkcs7.NewSignedData(nil) + if err != nil { + return nil, err + } + signedMessage.SetDigestAlgorithm(pkcs7.OIDDigestAlgorithmSHA256) + signedMessage.SetEncryptionAlgorithm(pkcs7.OIDEncryptionAlgorithmRSA) + signedMessage.AddCertificate(cert) + + err = signedMessage.AddSigner(cert, priv, pkcs7.SignerInfoConfig{}) + if err != nil { + return nil, err + } + + return signedMessage.Finish() +} diff --git a/common/owner_certificate/owner_certificate_test.go b/common/owner_certificate/owner_certificate_test.go new file mode 100644 index 0000000..faf92e9 --- /dev/null +++ b/common/owner_certificate/owner_certificate_test.go @@ -0,0 +1,46 @@ +// Copyright 2023 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 +// +// https://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 ownercertificate + +import ( + "crypto/x509" + "testing" + + _ "embed" + + artifacts "github.com/openconfig/bootz/testdata" +) + +// Tests that the CMS structure can be created and that it can be verified with a PDC. +func TestGenerateAndVerify(t *testing.T) { + pdc, pdcPrivateKey, err := artifacts.NewCertificateAuthority("Pinned Domain Cert", "Google", "localhost") + if err != nil { + t.Fatalf("NewCertificateAuthority(): %v", err) + } + oc, ocPrivateKey, err := artifacts.NewSignedCertificate("Owner Certificate", "Google", "localhost", pdc, pdcPrivateKey) + if err != nil { + t.Fatalf("NewSignedCertificate(): %v", err) + } + cms, err := GenerateCMS(oc, ocPrivateKey) + if err != nil { + t.Fatalf("GenerateCMS(): %v", err) + } + pdcPool := x509.NewCertPool() + pdcPool.AddCert(pdc) + _, err = Verify(cms, pdcPool) + if err != nil { + t.Fatalf("Verify(): %v", err) + } +} diff --git a/server/entitymanager/entitymanager.go b/server/entitymanager/entitymanager.go index 2a6627d..3ca3c12 100644 --- a/server/entitymanager/entitymanager.go +++ b/server/entitymanager/entitymanager.go @@ -22,6 +22,7 @@ import ( "os" "sync" + ownercertificate "github.com/openconfig/bootz/common/owner_certificate" "github.com/openconfig/bootz/common/signature" "github.com/openconfig/bootz/server/service" "google.golang.org/grpc/codes" @@ -272,7 +273,11 @@ func (m *InMemoryEntityManager) Sign(resp *bpb.GetBootstrapDataResponse, chassis log.Infof("OV populated") // Populate the OC - resp.OwnershipCertificate = m.secArtifacts.OwnerCert.Raw + ocCMS, err := ownercertificate.GenerateCMS(m.secArtifacts.OwnerCert, m.secArtifacts.OwnerCertPrivateKey) + if err != nil { + return err + } + resp.OwnershipCertificate = ocCMS log.Infof("OC populated") return nil } diff --git a/server/entitymanager/entitymanager_test.go b/server/entitymanager/entitymanager_test.go index 8345562..1dfb21f 100644 --- a/server/entitymanager/entitymanager_test.go +++ b/server/entitymanager/entitymanager_test.go @@ -23,6 +23,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/h-fam/errdiff" + ownercertificate "github.com/openconfig/bootz/common/owner_certificate" "github.com/openconfig/bootz/common/signature" "github.com/openconfig/bootz/server/service" artifacts "github.com/openconfig/bootz/testdata" @@ -314,8 +315,12 @@ func TestSign(t *testing.T) { if !bytes.Equal(test.resp.GetOwnershipVoucher(), a.OV[test.serial]) { t.Errorf("Sign() ov = %v, want %v", test.resp.GetOwnershipVoucher(), a.OV[test.serial]) } + wantOC, err := ownercertificate.GenerateCMS(a.OwnerCert, a.OwnerCertPrivateKey) + if err != nil { + t.Fatalf("unable to generate OC CMS: %v", err) + } if test.wantOC { - if !bytes.Equal(test.resp.GetOwnershipCertificate(), a.OwnerCert.Raw) { + if !bytes.Equal(test.resp.GetOwnershipCertificate(), wantOC) { t.Errorf("Sign() oc = %v, want %v", test.resp.GetOwnershipCertificate(), a.OwnerCert.Raw) } }