From 94413bb3ca255231eccc41db84d6cca6a00f9752 Mon Sep 17 00:00:00 2001 From: Philipp Deppenwiese Date: Mon, 1 Mar 2021 02:45:23 +0100 Subject: [PATCH] Implement km & bpm stitching of signature --- cmd/bg-prov/cmd.go | 113 +++++++++++++++--- pkg/intel/metadata/manifest/key_signature.go | 17 +++ pkg/intel/metadata/manifest/signature.go | 49 +++++++- .../metadata/manifest/signature_types.go | 44 ++++++- pkg/provisioning/bg/marshal.go | 25 ++++ pkg/provisioning/bg/unmarshal.go | 26 ++++ 6 files changed, 248 insertions(+), 26 deletions(-) diff --git a/cmd/bg-prov/cmd.go b/cmd/bg-prov/cmd.go index fe1f384..0b2f044 100644 --- a/cmd/bg-prov/cmd.go +++ b/cmd/bg-prov/cmd.go @@ -158,6 +158,20 @@ type readConfigCmd struct { BIOS string `arg required name:"bios" help:"Path to the full BIOS binary file." type:"path"` } +type stitchingKMCmd struct { + KM string `arg required name:"km" help:"Path to the Key Manifest binary file." type:"path"` + Signature string `arg required name:"signature" help:"Path to the Key Manifest signature file." type:"path"` + PubKey string `arg required name:"pubkey" help:"Path to the Key Manifest public key file." type:"path"` + Out string `arg required name:"out" help:"Path to the newly stitched KM binary file." type:"path"` +} + +type stitchingBPMCmd struct { + BPM string `arg required name:"bpm" help:"Path to the Boot Policy Manifest binary file." type:"path"` + Signature string `arg required name:"signature" help:"Path to the Boot Policy Manifest signature file." type:"path"` + PubKey string `arg required name:"pubkey" help:"Path to the Boot Policy Manifest public key file." type:"path"` + Out string `arg required name:"out" help:"Path to the newly stitched BPM binary file." type:"path"` +} + type stitchingCmd struct { BIOS string `arg required name:"bios" help:"Path to the full BIOS binary file." type:"path"` ACM string `arg required name:"acm" help:"Path to the ACM binary file." type:"path"` @@ -592,6 +606,68 @@ func (rc *readConfigCmd) Run(ctx *context) error { return nil } +func (s *stitchingKMCmd) Run(ctx *context) error { + kmData, err := ioutil.ReadFile(s.KM) + if err != nil { + return err + } + sig, err := ioutil.ReadFile(s.Signature) + if err != nil { + return err + } + pub, err := bg.ReadPubKey(s.PubKey) + if err != nil { + return err + } + if len(kmData) < 1 || len(sig) < 1 { + return fmt.Errorf("loaded files are empty") + } + reader := bytes.NewReader(kmData) + km, err := bg.ParseKM(reader) + if err != nil { + return err + } + kmRaw, err := bg.StitchKM(km, pub, sig) + if err != nil { + return err + } + if err := ioutil.WriteFile(s.Out, kmRaw, 0644); err != nil { + return err + } + return nil +} + +func (s *stitchingBPMCmd) Run(ctx *context) error { + bpmData, err := ioutil.ReadFile(s.BPM) + if err != nil { + return err + } + sig, err := ioutil.ReadFile(s.Signature) + if err != nil { + return err + } + pub, err := bg.ReadPubKey(s.PubKey) + if err != nil { + return err + } + if len(bpmData) < 1 || len(sig) < 1 { + return fmt.Errorf("loaded files are empty") + } + reader := bytes.NewReader(bpmData) + bpm, err := bg.ParseBPM(reader) + if err != nil { + return err + } + bpmRaw, err := bg.StitchBPM(bpm, pub, sig) + if err != nil { + return err + } + if err := ioutil.WriteFile(s.Out, bpmRaw, 0644); err != nil { + return err + } + return nil +} + func (s *stitchingCmd) Run(ctx *context) error { bpm, _ := ioutil.ReadFile(s.BPM) km, _ := ioutil.ReadFile(s.KM) @@ -655,20 +731,25 @@ var cli struct { Debug bool `help:"Enable debug mode."` ManifestStrictOrderCheck bool `help:"Enable checking of manifest elements order"` - Version versionCmd `cmd help:"Prints the version of the program"` - ShowKm kmPrintCmd `cmd help:"Prints Key Manifest binary in human-readable format"` - ShowBpm bpmPrintCmd `cmd help:"Prints Boot Policy Manifest binary in human-readable format"` - ShowAcm acmPrintCmd `cmd help:"Prints ACM binary in human-readable format"` - ShowAll biosPrintCmd `cmd help:"Prints BPM, KM, FIT and ACM from BIOS binary in human-readable format"` - ExportAcm acmExportCmd `cmd help:"Exports ACM structures from BIOS image into file"` - ExportKm kmExportCmd `cmd help:"Exports KM structures from BIOS image into file"` - ExportBpm bpmExportCmd `cmd help:"Exports BPM structures from BIOS image into file"` - Template templateCmd `cmd help:"Writes template JSON configuration into file"` - ReadConfig readConfigCmd `cmd help:"Reads config from existing BIOS file and translates it to a JSON configuration"` - KmGen generateKMCmd `cmd help:"Generate KM file based von json configuration"` - BpmGen generateBPMCmd `cmd help:"Generate BPM file based von json configuration"` - KmSign signKMCmd `cmd help:"Sign key manifest with given key"` - BpmSign signBPMCmd `cmd help:"Sign Boot Policy Manifest with given key"` - Stitch stitchingCmd `cmd help:"Stitches BPM, KM and ACM into given BIOS image file"` - KeyGen keygenCmd `cmd help:"Generates key for KM and BPM signing"` + KMShow kmPrintCmd `cmd help:"Prints Key Manifest binary in human-readable format"` + KMGen generateKMCmd `cmd help:"Generate KM file based von json configuration"` + KMSign signKMCmd `cmd help:"Sign key manifest with given key"` + KMStitch stitchingKMCmd `cmd help:"Stitches KM Signatue into unsigned KM"` + KMExport kmExportCmd `cmd help:"Exports KM structures from BIOS image into file"` + + BPMShow bpmPrintCmd `cmd help:"Prints Boot Policy Manifest binary in human-readable format"` + BPMGen generateBPMCmd `cmd help:"Generate BPM file based von json configuration"` + BPMSign signBPMCmd `cmd help:"Sign Boot Policy Manifest with given key"` + BPMStitch stitchingBPMCmd `cmd help:"Stitches BPM Signatue into unsigned BPM"` + BPMExport bpmExportCmd `cmd help:"Exports BPM structures from BIOS image into file"` + + ACMExport acmExportCmd `cmd help:"Exports ACM structures from BIOS image into file"` + ACMShow acmPrintCmd `cmd help:"Prints ACM binary in human-readable format"` + + ShowAll biosPrintCmd `cmd help:"Prints BPM, KM, FIT and ACM from BIOS binary in human-readable format"` + Stitch stitchingCmd `cmd help:"Stitches BPM, KM and ACM into given BIOS image file"` + KeyGen keygenCmd `cmd help:"Generates key for KM and BPM signing"` + Template templateCmd `cmd help:"Writes template JSON configuration into file"` + ReadConfig readConfigCmd `cmd help:"Reads config from existing BIOS file and translates it to a JSON configuration"` + Version versionCmd `cmd help:"Prints the version of the program"` } diff --git a/pkg/intel/metadata/manifest/key_signature.go b/pkg/intel/metadata/manifest/key_signature.go index fdefe13..9e06686 100644 --- a/pkg/intel/metadata/manifest/key_signature.go +++ b/pkg/intel/metadata/manifest/key_signature.go @@ -37,6 +37,7 @@ func (m *KeySignature) Verify(signedData []byte) error { // if signAlgo is zero then it is detected automatically, based on the type // of the provided private key. func (ks *KeySignature) SetSignature(signAlgo Algorithm, privKey crypto.Signer, signedData []byte) error { + ks.Version = 0x10 err := ks.Key.SetPubKey(privKey.Public()) if err != nil { return fmt.Errorf("unable to set public key: %w", err) @@ -51,6 +52,7 @@ func (ks *KeySignature) SetSignature(signAlgo Algorithm, privKey crypto.Signer, // Signing algorithm will be detected automatically based on the type of the // provided private key. func (ks *KeySignature) SetSignatureAuto(privKey crypto.Signer, signedData []byte) error { + ks.Version = 0x10 err := ks.Key.SetPubKey(privKey.Public()) if err != nil { return fmt.Errorf("unable to set public key: %w", err) @@ -58,3 +60,18 @@ func (ks *KeySignature) SetSignatureAuto(privKey crypto.Signer, signedData []byt return ks.SetSignature(0, privKey, signedData) } + +// FillSignature sets a signature and all the values of KeyManifest, +// accordingly to arguments signAlgo, pubKey and signedData. +// +// if signAlgo is zero then it is detected automatically, based on the type +// of the provided private key. +func (ks *KeySignature) FillSignature(signAlgo Algorithm, pubKey crypto.PublicKey, signedData []byte, hashAlgo Algorithm) error { + ks.Version = 0x10 + err := ks.Key.SetPubKey(pubKey) + if err != nil { + return fmt.Errorf("unable to set public key: %w", err) + } + + return ks.Signature.FillSignature(signAlgo, pubKey, signedData, hashAlgo) +} diff --git a/pkg/intel/metadata/manifest/signature.go b/pkg/intel/metadata/manifest/signature.go index 9bf4ef3..931025f 100644 --- a/pkg/intel/metadata/manifest/signature.go +++ b/pkg/intel/metadata/manifest/signature.go @@ -66,7 +66,7 @@ func (m Signature) SignatureData() (SignatureDataInterface, error) { // * SignatureRSAASA // * SignatureECDSA // * SignatureSM2 -func (m *Signature) SetSignatureByData(sig SignatureDataInterface) error { +func (m *Signature) SetSignatureByData(sig SignatureDataInterface, hashAlgo Algorithm) error { err := m.SetSignatureData(sig) if err != nil { return err @@ -75,19 +75,35 @@ func (m *Signature) SetSignatureByData(sig SignatureDataInterface) error { switch sig := sig.(type) { case SignatureRSAPSS: m.SigScheme = AlgRSAPSS - m.HashAlg = AlgSHA256 + if hashAlgo.IsNull() { + m.HashAlg = AlgSHA256 + } else { + m.HashAlg = hashAlgo + } m.KeySize.SetInBytes(uint16(len(m.Data))) case SignatureRSAASA: m.SigScheme = AlgRSASSA - m.HashAlg = AlgSHA256 + if hashAlgo.IsNull() { + m.HashAlg = AlgSHA256 + } else { + m.HashAlg = hashAlgo + } m.KeySize.SetInBytes(uint16(len(m.Data))) case SignatureECDSA: m.SigScheme = AlgECDSA - m.HashAlg = AlgSHA256 + if hashAlgo.IsNull() { + m.HashAlg = AlgSHA512 + } else { + m.HashAlg = hashAlgo + } m.KeySize.SetInBits(uint16(sig.R.BitLen())) case SignatureSM2: m.SigScheme = AlgSM2 - m.HashAlg = AlgSM3_256 + if hashAlgo.IsNull() { + m.HashAlg = AlgSM3_256 + } else { + m.HashAlg = hashAlgo + } m.KeySize.SetInBits(uint16(sig.R.BitLen())) default: return fmt.Errorf("unexpected signature type: %T", sig) @@ -138,12 +154,33 @@ func (m *Signature) SetSignatureData(sig SignatureDataInterface) error { // if signAlgo is zero then it is detected automatically, based on the type // of the provided private key. func (m *Signature) SetSignature(signAlgo Algorithm, privKey crypto.Signer, signedData []byte) error { + m.Version = 0x10 signData, err := NewSignatureData(signAlgo, privKey, signedData) if err != nil { return fmt.Errorf("unable to construct the signature data: %w", err) } - err = m.SetSignatureByData(signData) + err = m.SetSignatureByData(signData, AlgNull) + if err != nil { + return fmt.Errorf("unable to set the signature: %w", err) + } + + return nil +} + +// FillSignature sets the signature accordingly to arguments signAlgo, +// pubKey and signedData; and sets all the fields of the structure Signature. +// +// if signAlgo is zero then it is detected automatically, based on the type +// of the provided private key. +func (m *Signature) FillSignature(signAlgo Algorithm, pubKey crypto.PublicKey, signedData []byte, hashAlgo Algorithm) error { + m.Version = 0x10 + signData, err := NewSignatureByData(signAlgo, pubKey, signedData) + if err != nil { + return fmt.Errorf("unable to construct the signature data: %w", err) + } + + err = m.SetSignatureByData(signData, hashAlgo) if err != nil { return fmt.Errorf("unable to set the signature: %w", err) } diff --git a/pkg/intel/metadata/manifest/signature_types.go b/pkg/intel/metadata/manifest/signature_types.go index 98351a0..cd6e3e7 100644 --- a/pkg/intel/metadata/manifest/signature_types.go +++ b/pkg/intel/metadata/manifest/signature_types.go @@ -53,7 +53,6 @@ func NewSignatureData( return nil, fmt.Errorf("unable to sign with RSAPSS the data: %w", err) } return SignatureRSAPSS(data), nil - case AlgRSASSA: rsaPrivateKey, ok := privKey.(*rsa.PrivateKey) if !ok { @@ -67,7 +66,6 @@ func NewSignatureData( return nil, fmt.Errorf("unable to sign with RSASSA the data: %w", err) } return SignatureRSAASA(data), nil - case AlgECDSA: eccPrivateKey, ok := privKey.(*ecdsa.PrivateKey) if !ok { @@ -80,7 +78,6 @@ func NewSignatureData( return nil, fmt.Errorf("unable to sign with ECDSA the data: %w", err) } return data, nil - case AlgSM2: eccPrivateKey, ok := privKey.(*sm2.PrivateKey) if !ok { @@ -98,6 +95,46 @@ func NewSignatureData( return nil, fmt.Errorf("signing algorithm '%s' is not implemented in this library", signAlgo) } +// NewSignatureByData returns an implementation of SignatureDataInterface, +// accordingly to signAlgo, publicKey and signedData. +// +// if signAlgo is zero then it is detected automatically, based on the type +// of the provided private key. +func NewSignatureByData( + signAlgo Algorithm, + pubKey crypto.PublicKey, + signedData []byte, +) (SignatureDataInterface, error) { + if signAlgo == 0 { + // auto-detect the sign algorithm, based on the provided signing key + switch pubKey.(type) { + case *rsa.PublicKey: + signAlgo = AlgRSASSA + case *ecdsa.PublicKey: + signAlgo = AlgECDSA + case *sm2.PublicKey: + signAlgo = AlgSM2 + } + } + switch signAlgo { + case AlgRSAPSS: + return SignatureRSAPSS(signedData), nil + case AlgRSASSA: + return SignatureRSAASA(signedData), nil + case AlgECDSA: + return SignatureECDSA{ + R: new(big.Int).SetBytes(reverseBytes(signedData[:len(signedData)/2])), + S: new(big.Int).SetBytes(reverseBytes(signedData[len(signedData)/2:])), + }, nil + case AlgSM2: + return SignatureSM2{ + R: new(big.Int).SetBytes(reverseBytes(signedData[:len(signedData)/2])), + S: new(big.Int).SetBytes(reverseBytes(signedData[len(signedData)/2:])), + }, nil + } + return nil, fmt.Errorf("signing algorithm '%s' is not implemented in this library", signAlgo) +} + // SignatureDataInterface is the interface which abstracts all the signature data types. type SignatureDataInterface interface { fmt.Stringer @@ -167,7 +204,6 @@ func (s SignatureRSAASA) Verify(pkIface crypto.PublicKey, signedData []byte) err type SignatureECDSA struct { // R is the R component of the signature. R *big.Int - // S is the S component of the signature. S *big.Int } diff --git a/pkg/provisioning/bg/marshal.go b/pkg/provisioning/bg/marshal.go index d125ddf..5446a61 100644 --- a/pkg/provisioning/bg/marshal.go +++ b/pkg/provisioning/bg/marshal.go @@ -7,6 +7,7 @@ import ( "encoding/pem" "fmt" + "github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/manifest" "github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/manifest/bootpolicy" "github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/manifest/key" ) @@ -25,6 +26,30 @@ func WriteBPM(bpm *bootpolicy.Manifest) ([]byte, error) { return buf.Bytes(), err } +// StitchKM returns a key manifest manifest as byte slice +func StitchKM(km *key.Manifest, pubKey crypto.PublicKey, signature []byte) ([]byte, error) { + if err := km.KeyAndSignature.FillSignature(0, pubKey, signature, km.PubKeyHashAlg); err != nil { + return nil, err + } + km.RehashRecursive() + if err := km.Validate(); err != nil { + return nil, err + } + return WriteKM(km) +} + +// StitchBPM returns a boot policy manifest as byte slice +func StitchBPM(bpm *bootpolicy.Manifest, pubKey crypto.PublicKey, signature []byte) ([]byte, error) { + if err := bpm.PMSE.KeySignature.FillSignature(0, pubKey, signature, manifest.AlgNull); err != nil { + return nil, err + } + bpm.RehashRecursive() + if err := bpm.Validate(); err != nil { + return nil, err + } + return WriteBPM(bpm) +} + func parsePrivateKey(raw []byte) (crypto.Signer, error) { for { block, rest := pem.Decode(raw) diff --git a/pkg/provisioning/bg/unmarshal.go b/pkg/provisioning/bg/unmarshal.go index d5680dc..22ccbdb 100644 --- a/pkg/provisioning/bg/unmarshal.go +++ b/pkg/provisioning/bg/unmarshal.go @@ -2,6 +2,7 @@ package bg import ( "errors" + "fmt" "io" "github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/manifest/bootpolicy" @@ -18,6 +19,17 @@ func ParseBPM(reader io.Reader) (*bootpolicy.Manifest, error) { return bpm, nil } +// ValidateBPM reads from a binary, parses into the boot policy manifest structure +// and validates the structure +func ValidateBPM(reader io.Reader) error { + bpm := &bootpolicy.Manifest{} + _, err := bpm.ReadFrom(reader) + if err != nil && !errors.Is(err, io.EOF) { + return err + } + return bpm.Validate() +} + // ParseKM reads from a binary source and parses into the key manifest structure func ParseKM(reader io.Reader) (*key.Manifest, error) { km := &key.Manifest{} @@ -27,3 +39,17 @@ func ParseKM(reader io.Reader) (*key.Manifest, error) { } return km, nil } + +// ValidateKM reads from a binary source, parses into the key manifest structure +// and validates the structure +func ValidateKM(reader io.Reader) error { + km := &key.Manifest{} + _, err := km.ReadFrom(reader) + if err != nil && !errors.Is(err, io.EOF) { + return err + } + if km.PubKeyHashAlg != km.KeyAndSignature.Signature.HashAlg { + return fmt.Errorf("header pubkey hash algorithm doesn't match signature hash") + } + return km.Validate() +}