Skip to content

Commit

Permalink
Support signing firmware bundles
Browse files Browse the repository at this point in the history
Sign a firmware bundle with k1.pem (must be an EC P-256 private key)
```
mos create-fw-bundle -i build/fw.zip -o build/fw-signed.zip --sign-key=k1.pem
```

Multiple signatures can be added:
```
mos create-fw-bundle -i build/fw.zip -o build/fw-signed.zip --sign-key=k1.pem --sign-key=k2.pem --sign-key=k3.pem
```
Will add `sig`, `sig1` and `sig2`.

Multiple signatures can be added:
```
mos create-fw-bundle -i build/fw.zip -o build/fw-signed.zip --sign-key=k1.pem --sign-key= --sign-key=k3.pem
```
Will add `sig` and `sig2` only.

Generate keys using OpenSSL:

```
openssl ecparam -genkey -name prime256v1 -text -out k1.pem
```

Extract public portion as Base64 encoded DER:

```
openssl ec -in k1.pem -pubout -outform der | base64 -w 0
```
  • Loading branch information
rojer committed Sep 4, 2021
1 parent 991be5d commit 457ad33
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 6 deletions.
47 changes: 45 additions & 2 deletions cli/create_fw_bundle/create_fw_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ package create_fw_bundle

import (
"context"
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io/ioutil"
"path/filepath"
Expand All @@ -27,11 +30,11 @@ import (
"github.com/juju/errors"
flag "github.com/spf13/pflag"

"github.com/mongoose-os/mos/common/fwbundle"
moscommon "github.com/mongoose-os/mos/cli/common"
"github.com/mongoose-os/mos/cli/dev"
"github.com/mongoose-os/mos/cli/flags"
"github.com/mongoose-os/mos/cli/ourutil"
"github.com/mongoose-os/mos/common/fwbundle"
"github.com/mongoose-os/mos/version"
)

Expand Down Expand Up @@ -129,6 +132,46 @@ func CreateFWBundle(ctx context.Context, devConn dev.DevConn) error {
if err != nil {
return errors.Annotatef(err, "failed to parse --extra-attr")
}
var signers []crypto.Signer
for _, key := range *flags.SignKeys {
var s crypto.Signer
if key != "" {
// TODO(rojer): ATCA support, maybe?
privKeyBytes, err := getPEMBlock(key, "EC PRIVATE KEY")
if err != nil {
return errors.Annotatef(err, "failed to read private key %q", key)
}
ecPrivKey, err := x509.ParseECPrivateKey(privKeyBytes)
if err != nil {
return errors.Annotatef(err, "failed to parse EC private key %q", key)
}
s = ecPrivKey
}
signers = append(signers, s)
}
ourutil.Reportf("Writing %s", *flags.Output)
return fwbundle.WriteZipFirmwareBundle(fwb, *flags.Output, *flags.Compress, extraAttrs)
return fwbundle.WriteSignedZipFirmwareBundle(fwb, *flags.Output, *flags.Compress, signers, extraAttrs)
}

func getPEMBlock(file string, blockType string) ([]byte, error) {
data, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
n := 0
for {
p, rest := pem.Decode(data)
if p == nil {
break
}
n++
if p.Type == blockType {
return p.Bytes, nil
}
data = rest
}
if n == 0 {
return nil, fmt.Errorf("%s is not a PEM file", file)
}
return nil, fmt.Errorf("no PEM type %s found in %s", blockType, file)
}
2 changes: 2 additions & 0 deletions cli/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ var (
KeepTempFiles = flag.Bool("keep-temp-files", false, "keep temp files after the build is done (by default they are in ~/.mos/tmp)")
KeepFS = flag.Bool("keep-fs", false, "When flashing, skip the filesystem parts")

// create-fw-bundle flags.
Attr = flag.StringArray("attr", nil, "manifest attribute, can be used multiple times")
ExtraAttr = flag.StringArray("extra-attr", nil, "manifest extra attribute info to be added to ZIP")
SignKeys = flag.StringArray("sign-key", nil, "Signing private key file name. Can be used multiple times for multipl signatures.")

// Build flags.
BuildParams = flag.String("build-params", "", "build params file")
Expand Down
43 changes: 39 additions & 4 deletions common/fwbundle/fw_bundle_zip.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ package fwbundle
import (
"bytes"
"compress/flate"
"crypto"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"path"
Expand Down Expand Up @@ -93,7 +99,7 @@ func ReadZipFirmwareBundle(fname string) (*FirmwareBundle, error) {
return fwb, nil
}

func WriteZipFirmwareBytes(fwb *FirmwareBundle, buf *bytes.Buffer, compress bool, extraAttrs map[string]interface{}) error {
func WriteSignedZipFirmwareBytes(fwb *FirmwareBundle, buf *bytes.Buffer, compress bool, signers []crypto.Signer, extraAttrsUser map[string]interface{}) error {
zw := zip.NewWriter(buf)
// When compressing, use best compression.
zw.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
Expand All @@ -118,6 +124,28 @@ func WriteZipFirmwareBytes(fwb *FirmwareBundle, buf *bytes.Buffer, compress bool
if err != nil {
return errors.Annotatef(err, "error marshaling manifest")
}
manifestDigest := sha256.Sum256(manifestData)
glog.V(1).Infof("Manifest:\n%s", string(manifestData))
glog.V(1).Infof("Manifest digest: %s", hex.EncodeToString(manifestDigest[:]))
extraAttrs := make(map[string]interface{})
for k, v := range extraAttrsUser {
extraAttrs[k] = v
}
for si, s := range signers {
if s != nil {
sig, err := s.Sign(rand.Reader, manifestDigest[:], nil)
if err != nil {
return errors.Annotatef(err, "error signing with %d", si)
}
sigBase64 := base64.StdEncoding.EncodeToString(sig)
key := "sig"
if si > 0 {
key = fmt.Sprintf("sig%d", si)
}
extraAttrs[key] = sigBase64
glog.V(1).Infof("Signature %d: %s", si, sigBase64)
}
}
extraData := bytes.NewBuffer(nil)
if len(extraAttrs) > 0 {
extraAttrData, err := json.Marshal(extraAttrs)
Expand All @@ -128,7 +156,6 @@ func WriteZipFirmwareBytes(fwb *FirmwareBundle, buf *bytes.Buffer, compress bool
binary.Write(extraData, binary.LittleEndian, uint16(len(extraAttrData)))
extraData.Write(extraAttrData)
}
glog.V(1).Infof("Manifest:\n%s", string(manifestData))
zfh := &zip.FileHeader{
Name: ManifestFileName,
Extra: extraData.Bytes(),
Expand Down Expand Up @@ -165,10 +192,18 @@ func WriteZipFirmwareBytes(fwb *FirmwareBundle, buf *bytes.Buffer, compress bool
return nil
}

func WriteZipFirmwareBundle(fwb *FirmwareBundle, fname string, compress bool, extraAttrs map[string]interface{}) error {
func WriteZipFirmwareBytes(fwb *FirmwareBundle, buf *bytes.Buffer, compress bool, extraAttrs map[string]interface{}) error {
return WriteSignedZipFirmwareBytes(fwb, buf, compress, nil, extraAttrs)
}

func WriteSignedZipFirmwareBundle(fwb *FirmwareBundle, fname string, compress bool, signers []crypto.Signer, extraAttrs map[string]interface{}) error {
buf := new(bytes.Buffer)
if err := WriteZipFirmwareBytes(fwb, buf, compress, extraAttrs); err != nil {
if err := WriteSignedZipFirmwareBytes(fwb, buf, compress, signers, extraAttrs); err != nil {
return err
}
return ioutil.WriteFile(fname, buf.Bytes(), 0644)
}

func WriteZipFirmwareBundle(fwb *FirmwareBundle, fname string, compress bool, extraAttrs map[string]interface{}) error {
return WriteSignedZipFirmwareBundle(fwb, fname, compress, nil, extraAttrs)
}

0 comments on commit 457ad33

Please sign in to comment.