You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2019/01/04 18:21:13 UTC

[mynewt-newt] 08/17: Larva tool

This is an automated email from the ASF dual-hosted git repository.

ccollins pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git

commit 4f6740563035339a1029a49155624822e9fe62af
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Tue Nov 20 17:37:59 2018 -0800

    Larva tool
    
    This tool has the following functionality:
        * Parse a `.img` file and display it in JSON format.
        * Remove signatures from a `.img` file.
        * Add signatures to a `.img` file.
        * Split a manufacturing image into several files, one for each flash
          area.
        * Join a split manufacturing image.
---
 larva/cli/image_cmds.go | 205 ++++++++++++++++++++++++++++++++++++++++++++++++
 larva/cli/mfg_cmds.go   | 184 +++++++++++++++++++++++++++++++++++++++++++
 larva/cli/util.go       |  66 ++++++++++++++++
 larva/mfg/mfg.go        | 147 ++++++++++++++++++++++++++++++++++
 larva/mimg.go           |  84 ++++++++++++++++++++
 5 files changed, 686 insertions(+)

diff --git a/larva/cli/image_cmds.go b/larva/cli/image_cmds.go
new file mode 100644
index 0000000..ce1093e
--- /dev/null
+++ b/larva/cli/image_cmds.go
@@ -0,0 +1,205 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 cli
+
+import (
+	"encoding/hex"
+	"fmt"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/artifact/image"
+	"mynewt.apache.org/newt/util"
+)
+
+func readImage(filename string) (image.Image, error) {
+	img, err := image.ReadImage(filename)
+	if err != nil {
+		return img, err
+	}
+
+	log.Debugf("Successfully read image %s", filename)
+	return img, nil
+}
+
+func writeImage(img image.Image, filename string) error {
+	if err := img.WriteToFile(filename); err != nil {
+		return err
+	}
+
+	log.Debugf("Wrote image %s", filename)
+	return nil
+}
+
+func reportDupSigs(img image.Image) {
+	m := map[string]struct{}{}
+	dups := map[string]struct{}{}
+
+	for _, tlv := range img.Tlvs {
+		if tlv.Header.Type == image.IMAGE_TLV_KEYHASH {
+			h := hex.EncodeToString(tlv.Data)
+			if _, ok := m[h]; ok {
+				dups[h] = struct{}{}
+			} else {
+				m[h] = struct{}{}
+			}
+		}
+	}
+
+	if len(dups) > 0 {
+		fmt.Printf("Warning: duplicate signatures detected:\n")
+		for d, _ := range dups {
+			fmt.Printf("    %s\n", d)
+		}
+	}
+}
+
+func runShowCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		LarvaUsage(cmd, nil)
+	}
+
+	img, err := readImage(args[0])
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	s, err := img.Json()
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+	fmt.Printf("%s\n", s)
+}
+
+func runSignCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 2 {
+		LarvaUsage(cmd, nil)
+	}
+
+	inFilename := args[0]
+	outFilename, err := CalcOutFilename(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	keys, err := image.ReadKeys(args[1:])
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	hash, err := img.Hash()
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read hash from specified image: %s", err.Error()))
+	}
+
+	tlvs, err := image.GenerateSigTlvs(keys, hash)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	img.Tlvs = append(img.Tlvs, tlvs...)
+
+	reportDupSigs(img)
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func runRmsigsCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		LarvaUsage(cmd, nil)
+	}
+
+	inFilename := args[0]
+	outFilename, err := CalcOutFilename(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	img, err := readImage(inFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	cnt := img.RemoveTlvsIf(func(tlv image.ImageTlv) bool {
+		return tlv.Header.Type == image.IMAGE_TLV_KEYHASH ||
+			tlv.Header.Type == image.IMAGE_TLV_RSA2048 ||
+			tlv.Header.Type == image.IMAGE_TLV_ECDSA224 ||
+			tlv.Header.Type == image.IMAGE_TLV_ECDSA256
+	})
+
+	log.Debugf("Removed %d existing signatures", cnt)
+
+	if err := writeImage(img, outFilename); err != nil {
+		LarvaUsage(nil, err)
+	}
+}
+
+func AddImageCommands(cmd *cobra.Command) {
+	imageCmd := &cobra.Command{
+		Use:   "image",
+		Short: "Shows and manipulates Mynewt image (.img) files",
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Usage()
+		},
+	}
+	cmd.AddCommand(imageCmd)
+
+	showCmd := &cobra.Command{
+		Use:   "show <img-file>",
+		Short: "Displays JSON describing a Mynewt image file",
+		Run:   runShowCmd,
+	}
+	imageCmd.AddCommand(showCmd)
+
+	signCmd := &cobra.Command{
+		Use:   "sign <img-file> <priv-key-pem> [priv-key-pem...]",
+		Short: "Appends signatures to a Mynewt image file",
+		Run:   runSignCmd,
+	}
+
+	signCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+		"File to write to")
+	signCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(signCmd)
+
+	rmsigsCmd := &cobra.Command{
+		Use:   "rmsigs",
+		Short: "Removes all signatures from a Mynewt image file",
+		Run:   runRmsigsCmd,
+	}
+
+	rmsigsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+		"File to write to")
+	rmsigsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+		"Replace input file")
+
+	imageCmd.AddCommand(rmsigsCmd)
+}
diff --git a/larva/cli/mfg_cmds.go b/larva/cli/mfg_cmds.go
new file mode 100644
index 0000000..3a9e6ac
--- /dev/null
+++ b/larva/cli/mfg_cmds.go
@@ -0,0 +1,184 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 cli
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/artifact/manifest"
+	"mynewt.apache.org/newt/larva/mfg"
+	"mynewt.apache.org/newt/util"
+)
+
+var optDeviceNum int
+
+func readManifest(filename string) (manifest.Manifest, error) {
+	man, err := manifest.ReadManifest(filename)
+	if err != nil {
+		return man, err
+	}
+
+	log.Debugf("Successfully read manifest %s", filename)
+	return man, nil
+}
+
+func readFlashAreas(manifestFilename string) ([]flash.FlashArea, error) {
+	man, err := readManifest(manifestFilename)
+	if err != nil {
+		return nil, err
+	}
+
+	areas := flash.SortFlashAreasByDevOff(man.FlashAreas)
+
+	overlaps, conflicts := flash.DetectErrors(areas)
+	if len(overlaps) > 0 || len(conflicts) > 0 {
+		return nil, util.NewNewtError(flash.ErrorText(overlaps, conflicts))
+	}
+
+	if err := mfg.VerifyAreas(areas, optDeviceNum); err != nil {
+		return nil, err
+	}
+
+	log.Debugf("Successfully read flash areas: %+v", areas)
+	return areas, nil
+}
+
+func createMfgMap(binDir string, areas []flash.FlashArea) (mfg.MfgMap, error) {
+	mm := mfg.MfgMap{}
+
+	for _, area := range areas {
+		filename := fmt.Sprintf("%s/%s.bin", binDir, area.Name)
+		bin, err := ioutil.ReadFile(filename)
+		if err != nil {
+			if !os.IsNotExist(err) {
+				return nil, util.ChildNewtError(err)
+			}
+		} else {
+			mm[area.Name] = bin
+		}
+	}
+
+	return mm, nil
+}
+
+func runSplitCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 3 {
+		LarvaUsage(cmd, nil)
+	}
+
+	imgFilename := args[0]
+	manFilename := args[1]
+	outDir := args[2]
+
+	mfgBin, err := ioutil.ReadFile(imgFilename)
+	if err != nil {
+		LarvaUsage(cmd, util.FmtNewtError(
+			"Failed to read manufacturing image: %s", err.Error()))
+	}
+
+	areas, err := readFlashAreas(manFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	mm, err := mfg.Split(mfgBin, optDeviceNum, areas)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if err := os.Mkdir(outDir, os.ModePerm); err != nil {
+		LarvaUsage(nil, util.ChildNewtError(err))
+	}
+
+	for name, data := range mm {
+		filename := fmt.Sprintf("%s/%s.bin", outDir, name)
+		if err := ioutil.WriteFile(filename, data, os.ModePerm); err != nil {
+			LarvaUsage(nil, util.ChildNewtError(err))
+		}
+	}
+}
+
+func runJoinCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 3 {
+		LarvaUsage(cmd, nil)
+	}
+
+	binDir := args[0]
+	manFilename := args[1]
+	outFilename := args[2]
+
+	areas, err := readFlashAreas(manFilename)
+	if err != nil {
+		LarvaUsage(cmd, err)
+	}
+
+	mm, err := createMfgMap(binDir, areas)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	mfgBin, err := mfg.Join(mm, 0xff, areas)
+	if err != nil {
+		LarvaUsage(nil, err)
+	}
+
+	if err := ioutil.WriteFile(outFilename, mfgBin, os.ModePerm); err != nil {
+		LarvaUsage(nil, util.ChildNewtError(err))
+	}
+}
+
+func AddMfgCommands(cmd *cobra.Command) {
+	mfgCmd := &cobra.Command{
+		Use:   "mfg",
+		Short: "Manipulates Mynewt manufacturing images",
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Usage()
+		},
+	}
+	cmd.AddCommand(mfgCmd)
+
+	splitCmd := &cobra.Command{
+		Use:   "split <mfg-image> <manifest> <out-dir>",
+		Short: "Splits a Mynewt mfg section into several files",
+		Run:   runSplitCmd,
+	}
+
+	splitCmd.PersistentFlags().IntVarP(&optDeviceNum, "device", "d", 0,
+		"Flash device number")
+
+	mfgCmd.AddCommand(splitCmd)
+
+	joinCmd := &cobra.Command{
+		Use:   "join <bin-dir> <manifest> <out-mfg-image>",
+		Short: "Joins a split mfg section into a single file",
+		Run:   runJoinCmd,
+	}
+
+	joinCmd.PersistentFlags().IntVarP(&optDeviceNum, "device", "d", 0,
+		"Flash device number")
+
+	mfgCmd.AddCommand(joinCmd)
+}
diff --git a/larva/cli/util.go b/larva/cli/util.go
new file mode 100644
index 0000000..20c3d5e
--- /dev/null
+++ b/larva/cli/util.go
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 cli
+
+import (
+	"fmt"
+	"os"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/util"
+)
+
+var OptOutFilename string
+var OptInPlace bool
+
+func LarvaUsage(cmd *cobra.Command, err error) {
+	if err != nil {
+		sErr := err.(*util.NewtError)
+		log.Debugf("%s", sErr.StackTrace)
+		fmt.Fprintf(os.Stderr, "Error: %s\n", sErr.Text)
+	}
+
+	if cmd != nil {
+		fmt.Printf("\n")
+		fmt.Printf("%s - ", cmd.Name())
+		cmd.Help()
+	}
+	os.Exit(1)
+}
+
+func CalcOutFilename(inFilename string) (string, error) {
+	if OptOutFilename != "" {
+		if OptInPlace {
+			return "", util.FmtNewtError(
+				"Only one of --outfile (-o) or --inplace (-i) options allowed")
+		}
+
+		return OptOutFilename, nil
+	}
+
+	if !OptInPlace {
+		return "", util.FmtNewtError(
+			"--outfile (-o) or --inplace (-i) option required")
+	}
+
+	return inFilename, nil
+}
diff --git a/larva/mfg/mfg.go b/larva/mfg/mfg.go
new file mode 100644
index 0000000..0cbbbb0
--- /dev/null
+++ b/larva/mfg/mfg.go
@@ -0,0 +1,147 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 mfg
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"mynewt.apache.org/newt/artifact/flash"
+	"mynewt.apache.org/newt/util"
+)
+
+type MfgMap map[string][]byte
+
+func errInvalidArea(areaName string, format string,
+	args ...interface{}) error {
+
+	suffix := fmt.Sprintf(format, args...)
+	return util.FmtNewtError("Invalid flash area \"%s\": %s", areaName, suffix)
+}
+
+func verifyArea(area flash.FlashArea, minOffset int) error {
+	if area.Offset < minOffset {
+		return errInvalidArea(area.Name, "invalid offset %d; expected >= %d",
+			area.Offset, minOffset)
+	}
+
+	if area.Size < 0 {
+		return errInvalidArea(area.Name, "invalid size %d", area.Size)
+	}
+
+	return nil
+}
+
+// `areas` must be sorted by device ID, then by offset.
+func VerifyAreas(areas []flash.FlashArea, deviceNum int) error {
+	off := 0
+	for _, area := range areas {
+		if area.Device == deviceNum {
+			if err := verifyArea(area, off); err != nil {
+				return err
+			}
+			off += area.Size
+		}
+	}
+
+	return nil
+}
+
+func Split(mfgBin []byte, deviceNum int,
+	areas []flash.FlashArea) (MfgMap, error) {
+
+	mm := MfgMap{}
+
+	for _, area := range areas {
+		if _, ok := mm[area.Name]; ok {
+			return nil, util.FmtNewtError(
+				"two or more flash areas with same name: \"%s\"", area.Name)
+		}
+
+		if area.Device == deviceNum && area.Offset < len(mfgBin) {
+			end := area.Offset + area.Size
+			if end > len(mfgBin) {
+				return nil, util.FmtNewtError(
+					"area \"%s\" (offset=%d size=%d) "+
+						"extends beyond end of manufacturing image",
+					area.Name, area.Offset, area.Size)
+			}
+
+			mm[area.Name] = mfgBin[area.Offset:end]
+		}
+	}
+
+	return mm, nil
+}
+
+// `areas` must be sorted by device ID, then by offset.
+func Join(mm MfgMap, eraseVal byte, areas []flash.FlashArea) ([]byte, error) {
+	// Ensure all areas in the mfg map belong to the same flash device.
+	device := -1
+	for _, area := range areas {
+		if _, ok := mm[area.Name]; ok {
+			if device == -1 {
+				device = area.Device
+			} else if device != area.Device {
+				return nil, util.FmtNewtError(
+					"multiple flash devices: %d != %d", device, area.Device)
+			}
+		}
+	}
+
+	// Keep track of which areas we haven't seen yet.
+	unseen := map[string]struct{}{}
+	for name, _ := range mm {
+		unseen[name] = struct{}{}
+	}
+
+	joined := []byte{}
+
+	off := 0
+	for _, area := range areas {
+		bin := mm[area.Name]
+		if bin == nil {
+			break
+		}
+		delete(unseen, area.Name)
+
+		padSize := area.Offset - off
+		for i := 0; i < padSize; i++ {
+			joined = append(joined, 0xff)
+		}
+
+		joined = append(joined, bin...)
+	}
+
+	// Ensure we processed every area in the mfg map.
+	if len(unseen) > 0 {
+		names := []string{}
+		for name, _ := range unseen {
+			names = append(names, name)
+		}
+		sort.Strings(names)
+
+		return nil, util.FmtNewtError(
+			"unprocessed flash areas: %s", strings.Join(names, ", "))
+	}
+
+	return joined, nil
+}
diff --git a/larva/mimg.go b/larva/mimg.go
new file mode 100644
index 0000000..04cddec
--- /dev/null
+++ b/larva/mimg.go
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 main
+
+import (
+	"fmt"
+
+	log "github.com/Sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/newt/larva/cli"
+	"mynewt.apache.org/newt/util"
+)
+
+var LarvaLogLevel log.Level
+var larvaVersion = "0.0.1"
+
+func main() {
+	larvaHelpText := ""
+	larvaHelpEx := ""
+
+	logLevelStr := ""
+	larvaCmd := &cobra.Command{
+		Use:     "larva",
+		Short:   "larva is a tool to help you compose and build your own OS",
+		Long:    larvaHelpText,
+		Example: larvaHelpEx,
+		PersistentPreRun: func(cmd *cobra.Command, args []string) {
+			logLevel, err := log.ParseLevel(logLevelStr)
+			if err != nil {
+				cli.LarvaUsage(nil, util.ChildNewtError(err))
+			}
+			LarvaLogLevel = logLevel
+
+			if err := util.Init(LarvaLogLevel, "",
+				util.VERBOSITY_DEFAULT); err != nil {
+
+				cli.LarvaUsage(nil, err)
+			}
+		},
+
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Help()
+		},
+	}
+
+	larvaCmd.PersistentFlags().StringVarP(&logLevelStr, "loglevel", "l",
+		"WARN", "Log level")
+
+	versHelpText := `Display the larva version number`
+	versHelpEx := "  larva version"
+	versCmd := &cobra.Command{
+		Use:     "version",
+		Short:   "Display the larva version number",
+		Long:    versHelpText,
+		Example: versHelpEx,
+		Run: func(cmd *cobra.Command, args []string) {
+			fmt.Printf("%s\n", larvaVersion)
+		},
+	}
+	larvaCmd.AddCommand(versCmd)
+
+	cli.AddImageCommands(larvaCmd)
+	cli.AddMfgCommands(larvaCmd)
+
+	larvaCmd.Execute()
+}