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 <ccoll...@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() +}