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/06/10 23:55:30 UTC
[mynewt-imgmod] 01/09: imgmod - initial commit
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-imgmod.git
commit c6855f438be74c531f65b53cdb6fa3641a27c6d1
Author: Christopher Collins <cc...@apache.org>
AuthorDate: Fri Mar 22 16:01:53 2019 -0700
imgmod - initial commit
---
cli/image_cmds.go | 597 +++++++++++++++++++++++++++++++++++++++++++++++++++
cli/mfg_cmds.go | 623 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
cli/util.go | 111 ++++++++++
go.mod | 7 +
go.sum | 29 +++
iimg/lvimg.go | 189 +++++++++++++++++
imfg/lvmfg.go | 249 ++++++++++++++++++++++
imgmod.go | 100 +++++++++
8 files changed, 1905 insertions(+)
diff --git a/cli/image_cmds.go b/cli/image_cmds.go
new file mode 100644
index 0000000..83b4e8f
--- /dev/null
+++ b/cli/image_cmds.go
@@ -0,0 +1,597 @@
+/**
+ * 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/binary"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "sort"
+
+ log "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+
+ "mynewt.apache.org/newt/artifact/image"
+ "mynewt.apache.org/newt/artifact/sec"
+ "mynewt.apache.org/imgmod/iimg"
+ "mynewt.apache.org/newt/util"
+)
+
+func tlvStr(tlv image.ImageTlv) string {
+ return fmt.Sprintf("%s,0x%02x",
+ image.ImageTlvTypeName(tlv.Header.Type),
+ tlv.Header.Type)
+}
+
+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 := iimg.VerifyImage(img); err != nil {
+ return err
+ }
+
+ if err := img.WriteToFile(filename); err != nil {
+ return err
+ }
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT, "Wrote image %s\n", filename)
+ return nil
+}
+
+func parseTlvArgs(typeArg string, filenameArg string) (image.ImageTlv, error) {
+ tlvType, err := util.AtoiNoOct(typeArg)
+ if err != nil || tlvType < 0 {
+ return image.ImageTlv{}, util.FmtNewtError(
+ "Invalid TLV type integer: %s", typeArg)
+ }
+
+ data, err := ioutil.ReadFile(filenameArg)
+ if err != nil {
+ return image.ImageTlv{}, util.FmtNewtError(
+ "Error reading TLV data file: %s", err.Error())
+ }
+
+ return image.ImageTlv{
+ Header: image.ImageTlvHdr{
+ Type: uint8(tlvType),
+ Pad: 0,
+ Len: uint16(len(data)),
+ },
+ Data: data,
+ }, nil
+}
+
+func runShowCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 1 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ img, err := readImage(args[0])
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ s, err := img.Json()
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+ fmt.Printf("%s\n", s)
+}
+
+func runBriefCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 1 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ img, err := readImage(args[0])
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ offsets, err := img.Offsets()
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ fmt.Printf("%8d| Header\n", offsets.Header)
+ fmt.Printf("%8d| Body\n", offsets.Body)
+ fmt.Printf("%8d| Trailer\n", offsets.Trailer)
+ for i, tlv := range img.Tlvs {
+ fmt.Printf("%8d| TLV%d: type=%s(%d)\n",
+ offsets.Tlvs[i], i, image.ImageTlvTypeName(tlv.Header.Type),
+ tlv.Header.Type)
+ }
+ fmt.Printf("Total=%d\n", offsets.TotalSize)
+}
+
+func runSignCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 2 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ inFilename := args[0]
+ outFilename, err := CalcOutFilename(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ img, err := readImage(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ keys, err := sec.ReadKeys(args[1:])
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ hash, err := img.Hash()
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Failed to read hash from specified image: %s", err.Error()))
+ }
+
+ tlvs, err := image.BuildSigTlvs(keys, hash)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ img.Tlvs = append(img.Tlvs, tlvs...)
+
+ if err := writeImage(img, outFilename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runAddTlvsCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 3 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ inFilename := args[0]
+ outFilename, err := CalcOutFilename(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ img, err := readImage(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ tlvArgs := args[1:]
+ if len(tlvArgs)%2 != 0 {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Invalid argument count; each TLV requires two arguments"))
+ }
+
+ tlvs := []image.ImageTlv{}
+ for i := 0; i < len(tlvArgs); i += 2 {
+ tlv, err := parseTlvArgs(tlvArgs[i], tlvArgs[i+1])
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ tlvs = append(tlvs, tlv)
+ }
+
+ img.Tlvs = append(img.Tlvs, tlvs...)
+
+ if err := writeImage(img, outFilename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runRmtlvsCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 2 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ inFilename := args[0]
+ outFilename, err := CalcOutFilename(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ img, err := readImage(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ tlvIndices := []int{}
+ idxMap := map[int]struct{}{}
+ for _, arg := range args[1:] {
+ idx, err := util.AtoiNoOct(arg)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError("Invalid TLV index: %s", arg))
+ }
+
+ if idx < 0 || idx >= len(img.Tlvs) {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "TLV index %s out of range; "+
+ "must be in range [0, %d] for this image",
+ arg, len(img.Tlvs)-1))
+ }
+
+ if _, ok := idxMap[idx]; ok {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "TLV index %d specified more than once", idx))
+ }
+ idxMap[idx] = struct{}{}
+
+ tlvIndices = append(tlvIndices, idx)
+ }
+
+ // Remove TLVs in reverse order to preserve index mapping.
+ sort.Sort(sort.Reverse(sort.IntSlice(tlvIndices)))
+ for _, idx := range tlvIndices {
+ tlv := img.Tlvs[idx]
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Removing TLV%d: %s\n", idx, tlvStr(tlv))
+
+ img.Tlvs = append(img.Tlvs[0:idx], img.Tlvs[idx+1:]...)
+ }
+
+ if err := writeImage(img, outFilename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runRmsigsCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 1 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ inFilename := args[0]
+ outFilename, err := CalcOutFilename(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ img, err := readImage(inFilename)
+ if err != nil {
+ ImgmodUsage(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 {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runHashableCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 1 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ if OptOutFilename == "" {
+ ImgmodUsage(cmd, util.FmtNewtError("--outfile (-o) option required"))
+ }
+
+ inFilename := args[0]
+ outFilename := OptOutFilename
+
+ img, err := readImage(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ f, err := os.Create(outFilename)
+ if err != nil {
+ ImgmodUsage(nil, util.ChildNewtError(err))
+ }
+ defer f.Close()
+
+ if err := binary.Write(f, binary.LittleEndian, &img.Header); err != nil {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "Error writing image header: %s", err.Error()))
+ }
+ _, err = f.Write(img.Body)
+ if err != nil {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "Error writing image body: %s", err.Error()))
+ }
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Wrote hashable content to %s\n", outFilename)
+}
+
+func runAddsigCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 4 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ imgFilename := args[0]
+ keyFilename := args[1]
+ sigFilename := args[2]
+
+ sigType, err := util.AtoiNoOct(args[3])
+ if err != nil || sigType < 0 || sigType > 255 ||
+ !image.ImageTlvTypeIsSig(uint8(sigType)) {
+
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Invalid signature type: %s", args[3]))
+ }
+
+ outFilename, err := CalcOutFilename(imgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ img, err := readImage(imgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ keyData, err := ioutil.ReadFile(keyFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Error reading key file: %s", err.Error()))
+ }
+
+ sigData, err := ioutil.ReadFile(sigFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Error reading signature file: %s", err.Error()))
+ }
+
+ // ECDSA256 signatures need to be padded out to >=72 bytes.
+ if sigType == image.IMAGE_TLV_ECDSA256 {
+ sigData, err = iimg.PadEcdsa256Sig(sigData)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+ }
+
+ // Build and append key hash TLV.
+ keyHashTlv := image.BuildKeyHashTlv(keyData)
+ util.StatusMessage(util.VERBOSITY_DEFAULT, "Adding TLV%d (%s)\n",
+ len(img.Tlvs), tlvStr(keyHashTlv))
+ img.Tlvs = append(img.Tlvs, keyHashTlv)
+
+ // Build and append signature TLV.
+ sigTlv := image.ImageTlv{
+ Header: image.ImageTlvHdr{
+ Type: uint8(sigType),
+ Len: uint16(len(sigData)),
+ },
+ Data: sigData,
+ }
+ util.StatusMessage(util.VERBOSITY_DEFAULT, "Adding TLV%d (%s)\n",
+ len(img.Tlvs), tlvStr(sigTlv))
+ img.Tlvs = append(img.Tlvs, sigTlv)
+
+ if err := writeImage(img, outFilename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runDecryptCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 2 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ imgFilename := args[0]
+ keyFilename := args[1]
+
+ outFilename, err := CalcOutFilename(imgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ img, err := readImage(imgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ keyBytes, err := ioutil.ReadFile(keyFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Error reading key file: %s", err.Error()))
+ }
+
+ img, err = iimg.DecryptImage(img, keyBytes)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ if err := writeImage(img, outFilename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runEncryptCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 2 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ imgFilename := args[0]
+ keyFilename := args[1]
+
+ outFilename, err := CalcOutFilename(imgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ img, err := readImage(imgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ keyBytes, err := ioutil.ReadFile(keyFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Error reading key file: %s", err.Error()))
+ }
+
+ img, err = iimg.EncryptImage(img, keyBytes)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ if err := writeImage(img, outFilename); err != nil {
+ ImgmodUsage(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)
+
+ briefCmd := &cobra.Command{
+ Use: "brief <img-file>",
+ Short: "Displays brief text description of a Mynewt image file",
+ Run: runBriefCmd,
+ }
+ imageCmd.AddCommand(briefCmd)
+
+ 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)
+
+ addtlvsCmd := &cobra.Command{
+ Use: "addtlvs <img-file> <tlv-type> <data-filename> " +
+ "[tlv-type] [data-filename] [...]",
+ Short: "Adds the specified TLVs to a Mynewt image file",
+ Run: runAddTlvsCmd,
+ }
+
+ addtlvsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+ "File to write to")
+ addtlvsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input file")
+
+ imageCmd.AddCommand(addtlvsCmd)
+
+ rmtlvsCmd := &cobra.Command{
+ Use: "rmtlvs <img-file> <tlv-index> [tlv-index] [...]",
+ Short: "Removes the specified TLVs from a Mynewt image file",
+ Run: runRmtlvsCmd,
+ }
+
+ rmtlvsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "",
+ "File to write to")
+ rmtlvsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input file")
+
+ imageCmd.AddCommand(rmtlvsCmd)
+
+ 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)
+
+ hashableCmd := &cobra.Command{
+ Use: "hashable <img-file>",
+ Short: "Removes all signatures from a Mynewt image file",
+ Run: runHashableCmd,
+ }
+
+ hashableCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+ "", "File to write to")
+
+ imageCmd.AddCommand(hashableCmd)
+
+ addsigCmd := &cobra.Command{
+ Use: "addsig <image> <pub-key-der> <sig-der> <sig-tlv-type>",
+ Short: "Adds a signature to a Mynewt image file",
+ Run: runAddsigCmd,
+ }
+
+ addsigCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+ "", "File to write to")
+ addsigCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input file")
+
+ imageCmd.AddCommand(addsigCmd)
+
+ decryptCmd := &cobra.Command{
+ Use: "decrypt <image> <priv-key-der>",
+ Short: "Decrypts an encrypted Mynewt image file",
+ Run: runDecryptCmd,
+ }
+
+ decryptCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+ "", "File to write to")
+ decryptCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input file")
+
+ imageCmd.AddCommand(decryptCmd)
+
+ encryptCmd := &cobra.Command{
+ Use: "encrypt <image> <priv-key-der>",
+ Short: "Encrypts a Mynewt image file",
+ Run: runEncryptCmd,
+ }
+
+ encryptCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+ "", "File to write to")
+ encryptCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input file")
+
+ imageCmd.AddCommand(encryptCmd)
+}
diff --git a/cli/mfg_cmds.go b/cli/mfg_cmds.go
new file mode 100644
index 0000000..a89902b
--- /dev/null
+++ b/cli/mfg_cmds.go
@@ -0,0 +1,623 @@
+/**
+ * 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"
+ "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/artifact/mfg"
+ "mynewt.apache.org/newt/artifact/misc"
+ "mynewt.apache.org/newt/artifact/sec"
+ "mynewt.apache.org/imgmod/imfg"
+ "mynewt.apache.org/newt/util"
+)
+
+const MAX_SIG_LEN = 1024 // Bytes.
+
+func readMfgBin(filename string) ([]byte, error) {
+ bin, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, util.FmtChildNewtError(err,
+ "Failed to read manufacturing image: %s", err.Error())
+ }
+
+ return bin, nil
+}
+
+func readManifest(mfgDir string) (manifest.MfgManifest, error) {
+ return manifest.ReadMfgManifest(mfgDir + "/" + mfg.MANIFEST_FILENAME)
+}
+
+func extractFlashAreas(mman manifest.MfgManifest) ([]flash.FlashArea, error) {
+ areas := flash.SortFlashAreasByDevOff(mman.FlashAreas)
+
+ if len(areas) == 0 {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "Boot loader manifest does not contain flash map"))
+ }
+
+ overlaps, conflicts := flash.DetectErrors(areas)
+ if len(overlaps) > 0 || len(conflicts) > 0 {
+ return nil, util.NewNewtError(flash.ErrorText(overlaps, conflicts))
+ }
+
+ if err := imfg.VerifyAreas(areas); err != nil {
+ return nil, err
+ }
+
+ log.Debugf("Successfully read flash areas: %+v", areas)
+ return areas, nil
+}
+
+func createNameBlobMap(binDir string,
+ areas []flash.FlashArea) (imfg.NameBlobMap, error) {
+
+ mm := imfg.NameBlobMap{}
+
+ for _, area := range areas {
+ filename := fmt.Sprintf("%s/%s.bin", binDir, area.Name)
+ bin, err := readMfgBin(filename)
+ if err != nil {
+ if !util.IsNotExist(err) {
+ return nil, util.ChildNewtError(err)
+ }
+ } else {
+ mm[area.Name] = bin
+ }
+ }
+
+ return mm, nil
+}
+
+func runMfgShowCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 2 {
+ ImgmodUsage(cmd, nil)
+ }
+ inFilename := args[0]
+
+ metaEndOff, err := util.AtoiNoOct(args[1])
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "invalid meta offset \"%s\"", args[1]))
+ }
+
+ bin, err := readMfgBin(inFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ m, err := mfg.Parse(bin, metaEndOff, 0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ if m.Meta == nil {
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Manufacturing image %s does not contain an MMR\n", inFilename)
+ } else {
+ s, err := m.Meta.Json(metaEndOff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Manufacturing image %s contains an MMR with "+
+ "the following properties:\n%s\n", inFilename, s)
+ }
+}
+
+func runSplitCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 2 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ mfgDir := args[0]
+ outDir := args[1]
+
+ mm, err := readManifest(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ areas, err := extractFlashAreas(mm)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ binPath := fmt.Sprintf("%s/%s", mfgDir, mm.BinPath)
+ bin, err := readMfgBin(binPath)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Failed to read \"%s\": %s", binPath, err.Error()))
+ }
+
+ nbmap, err := imfg.Split(bin, mm.Device, areas, 0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ if err := os.Mkdir(outDir, os.ModePerm); err != nil {
+ ImgmodUsage(nil, util.ChildNewtError(err))
+ }
+
+ for name, data := range nbmap {
+ filename := fmt.Sprintf("%s/%s.bin", outDir, name)
+ if err := WriteFile(data, filename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+ }
+
+ mfgDstDir := fmt.Sprintf("%s/mfg", outDir)
+ if err := CopyDir(mfgDir, mfgDstDir); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runJoinCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 2 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ splitDir := args[0]
+ outDir := args[1]
+
+ if util.NodeExist(outDir) {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "Destination \"%s\" already exists", outDir))
+ }
+
+ mm, err := readManifest(splitDir + "/mfg")
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+ areas, err := extractFlashAreas(mm)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ nbmap, err := createNameBlobMap(splitDir, areas)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ bin, err := imfg.Join(nbmap, 0xff, areas)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ m, err := mfg.Parse(bin, mm.Meta.EndOffset, 0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ infos, err := ioutil.ReadDir(splitDir + "/mfg")
+ if err != nil {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "Error reading source mfg directory: %s", err.Error()))
+ }
+ for _, info := range infos {
+ if info.Name() != mfg.MFG_IMG_FILENAME {
+ src := splitDir + "/mfg/" + info.Name()
+ dst := outDir + "/" + info.Name()
+ if info.IsDir() {
+ err = CopyDir(src, dst)
+ } else {
+ err = CopyFile(src, dst)
+ }
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+ }
+ }
+
+ finalBin, err := m.Bytes(0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ binPath := fmt.Sprintf("%s/%s", outDir, mfg.MFG_IMG_FILENAME)
+ if err := WriteFile(finalBin, binPath); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func genSwapKeyCmd(cmd *cobra.Command, args []string, isKek bool) {
+ if len(args) < 3 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ mfgimgFilename := args[0]
+ okeyFilename := args[1]
+ nkeyFilename := args[2]
+
+ outFilename, err := CalcOutFilename(mfgimgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ bin, err := readMfgBin(mfgimgFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Failed to read mfgimg file: %s", err.Error()))
+ }
+
+ okey, err := ioutil.ReadFile(okeyFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Failed to read old key der: %s", err.Error()))
+ }
+
+ nkey, err := ioutil.ReadFile(nkeyFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Failed to read new key der: %s", err.Error()))
+ }
+
+ if isKek {
+ err = imfg.ReplaceKek(bin, okey, nkey)
+ } else {
+ err = imfg.ReplaceIsk(bin, okey, nkey)
+ }
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ if err := WriteFile(bin, outFilename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runSwapIskCmd(cmd *cobra.Command, args []string) {
+ genSwapKeyCmd(cmd, args, false)
+}
+
+func runSwapKekCmd(cmd *cobra.Command, args []string) {
+ genSwapKeyCmd(cmd, args, true)
+}
+
+func runMfgHashableCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 1 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ if OptOutFilename == "" {
+ ImgmodUsage(cmd, util.FmtNewtError("--outfile (-o) option required"))
+ }
+
+ mfgDir := args[0]
+ outFilename := OptOutFilename
+
+ // Read manifest and mfgimg.bin.
+ mman, err := readManifest(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ binPath := fmt.Sprintf("%s/%s", mfgDir, mman.BinPath)
+ bin, err := readMfgBin(binPath)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Failed to read \"%s\": %s", binPath, err.Error()))
+ }
+
+ metaOff := -1
+ if mman.Meta != nil {
+ metaOff = mman.Meta.EndOffset
+ }
+ m, err := mfg.Parse(bin, metaOff, 0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+ // Zero-out hash so that the hash can be recalculated.
+ m.Meta.ClearHash()
+
+ // Write hashable content to disk.
+ newBin, err := m.Bytes(0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+ if err := WriteFile(newBin, outFilename); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runRehashCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 1 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ mfgDir := args[0]
+
+ outDir, err := CalcOutFilename(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ // Read manifest and mfgimg.bin.
+ mman, err := readManifest(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ binPath := fmt.Sprintf("%s/%s", mfgDir, mman.BinPath)
+ bin, err := readMfgBin(binPath)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Failed to read \"%s\": %s", binPath, err.Error()))
+ }
+
+ // Calculate accurate hash.
+ metaOff := -1
+ if mman.Meta != nil {
+ metaOff = mman.Meta.EndOffset
+ }
+ m, err := mfg.Parse(bin, metaOff, 0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ if err := m.RecalcHash(0xff); err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ hash, err := m.Hash()
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ // Update manifest.
+ mman.MfgHash = misc.HashString(hash)
+
+ // Write new artifacts.
+ if err := EnsureOutDir(mfgDir, outDir); err != nil {
+ ImgmodUsage(nil, err)
+ }
+ binPath = fmt.Sprintf("%s/%s", outDir, mman.BinPath)
+
+ newBin, err := m.Bytes(0xff)
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+ if err := WriteFile(newBin, binPath); err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ json, err := mman.MarshalJson()
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ manPath := fmt.Sprintf("%s/%s", outDir, mfg.MANIFEST_FILENAME)
+ if err := WriteFile(json, manPath); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runRmsigsMfgCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 1 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ mfgDir := args[0]
+
+ outDir, err := CalcOutFilename(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ // Read manifest.
+ mman, err := readManifest(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ // Update manifest.
+ mman.Signatures = nil
+
+ // Write new artifacts.
+ if err := EnsureOutDir(mfgDir, outDir); err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ json, err := mman.MarshalJson()
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ manPath := fmt.Sprintf("%s/%s", outDir, mfg.MANIFEST_FILENAME)
+ if err := WriteFile(json, manPath); err != nil {
+ ImgmodUsage(nil, err)
+ }
+}
+
+func runAddsigMfgCmd(cmd *cobra.Command, args []string) {
+ if len(args) < 3 {
+ ImgmodUsage(cmd, nil)
+ }
+
+ mfgDir := args[0]
+ keyFilename := args[1]
+ sigFilename := args[2]
+
+ outDir, err := CalcOutFilename(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ // Read manifest.
+ mman, err := readManifest(mfgDir)
+ if err != nil {
+ ImgmodUsage(cmd, err)
+ }
+
+ // Read public key.
+ keyBytes, err := ioutil.ReadFile(keyFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtNewtError(
+ "Error reading key file: %s", err.Error()))
+ }
+
+ // Read signature.
+ sig, err := ioutil.ReadFile(sigFilename)
+ if err != nil {
+ ImgmodUsage(cmd, util.FmtChildNewtError(err,
+ "Failed to read signature: %s", err.Error()))
+ }
+ if len(sig) > MAX_SIG_LEN {
+ ImgmodUsage(nil, util.FmtNewtError(
+ "signature larger than arbitrary maximum length (%d > %d)",
+ len(sig), MAX_SIG_LEN))
+ }
+
+ // Update manifest.
+ mman.Signatures = append(mman.Signatures, manifest.MfgManifestSig{
+ Key: hex.EncodeToString(sec.RawKeyHash(keyBytes)),
+ Sig: hex.EncodeToString(sig),
+ })
+
+ // Write new artifacts.
+ if err := EnsureOutDir(mfgDir, outDir); err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ json, err := mman.MarshalJson()
+ if err != nil {
+ ImgmodUsage(nil, err)
+ }
+
+ manPath := fmt.Sprintf("%s/%s", outDir, mfg.MANIFEST_FILENAME)
+ if err := WriteFile(json, manPath); err != nil {
+ ImgmodUsage(nil, 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)
+
+ showCmd := &cobra.Command{
+ Use: "show <mfgimg.bin> <meta-end-offset>",
+ Short: "Displays JSON describing a manufacturing image",
+ Run: runMfgShowCmd,
+ }
+
+ mfgCmd.AddCommand(showCmd)
+
+ splitCmd := &cobra.Command{
+ Use: "split <mfgimage-dir> <out-dir>",
+ Short: "Splits a Mynewt mfg section into several files",
+ Run: runSplitCmd,
+ }
+
+ mfgCmd.AddCommand(splitCmd)
+
+ joinCmd := &cobra.Command{
+ Use: "join <split-dir> <out-dir>",
+ Short: "Joins a split mfg section into a single file",
+ Run: runJoinCmd,
+ }
+
+ mfgCmd.AddCommand(joinCmd)
+
+ swapIskCmd := &cobra.Command{
+ Use: "swapisk <mfgimg-bin> <cur-key-der> <new-key-der>",
+ Short: "Replaces an image-signing key in a manufacturing image",
+ Run: runSwapIskCmd,
+ }
+
+ swapIskCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+ "", "File to write to")
+ swapIskCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input file")
+
+ mfgCmd.AddCommand(swapIskCmd)
+
+ swapKekCmd := &cobra.Command{
+ Use: "swapkek <mfgimg-bin> <cur-key-der> <new-key-der>",
+ Short: "Replaces a key-encrypting key in a manufacturing image",
+ Run: runSwapKekCmd,
+ }
+
+ swapKekCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+ "", "File to write to")
+ swapKekCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input file")
+
+ mfgCmd.AddCommand(swapKekCmd)
+
+ hashableCmd := &cobra.Command{
+ Use: "hashable <mfgimage-dir>",
+ Short: "Extracts the hashable / signable content of an mfgimage",
+ Run: runMfgHashableCmd,
+ }
+ hashableCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o",
+ "", "File to write to")
+
+ mfgCmd.AddCommand(hashableCmd)
+
+ rehashCmd := &cobra.Command{
+ Use: "rehash <mfgimage-dir>",
+ Short: "Replaces an outdated mfgimage hash with an accurate one",
+ Run: runRehashCmd,
+ }
+ rehashCmd.PersistentFlags().StringVarP(&OptOutFilename, "outdir", "o",
+ "", "Directory to write to")
+ rehashCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input files")
+
+ mfgCmd.AddCommand(rehashCmd)
+
+ rmsigsCmd := &cobra.Command{
+ Use: "rmsigs <mfgimage-dir>",
+ Short: "Removes all signatures from an mfgimage's manifest",
+ Run: runRmsigsMfgCmd,
+ }
+ rmsigsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outdir", "o",
+ "", "Directory to write to")
+ rmsigsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input files")
+
+ mfgCmd.AddCommand(rmsigsCmd)
+
+ addsigCmd := &cobra.Command{
+ Use: "addsig <mfgimage-dir> <pub-key-der> <sig-der>",
+ Short: "Adds a signature to an mfgimage's manifest",
+ Run: runAddsigMfgCmd,
+ }
+ addsigCmd.PersistentFlags().StringVarP(&OptOutFilename, "outdir", "o",
+ "", "Directory to write to")
+ addsigCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false,
+ "Replace input files")
+
+ mfgCmd.AddCommand(addsigCmd)
+}
diff --git a/cli/util.go b/cli/util.go
new file mode 100644
index 0000000..a2729ad
--- /dev/null
+++ b/cli/util.go
@@ -0,0 +1,111 @@
+/**
+ * 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/util"
+)
+
+var OptOutFilename string
+var OptInPlace bool
+
+func ImgmodUsage(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("%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
+}
+
+func CopyDir(src string, dst string) error {
+ if err := util.CopyDir(src, dst); err != nil {
+ return util.FmtNewtError(
+ "Failed to copy directory \"%s\" to \"%s\": %s",
+ src, dst, err.Error())
+ }
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Copied directory \"%s\" to \"%s\"\n", src, dst)
+ return nil
+}
+
+func EnsureOutDir(inDir, outDir string) error {
+ if inDir != outDir {
+ // Not an in-place operation; copy input directory.
+ if err := CopyDir(inDir, outDir); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func CopyFile(src string, dst string) error {
+ if err := util.CopyFile(src, dst); err != nil {
+ return util.FmtNewtError(
+ "Failed to copy file \"%s\" to \"%s\": %s",
+ src, dst, err.Error())
+ }
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "Copied file \"%s\" to \"%s\"\n", src, dst)
+ return nil
+}
+
+func WriteFile(data []byte, filename string) error {
+ if err := ioutil.WriteFile(filename, data, os.ModePerm); err != nil {
+ return util.FmtNewtError(
+ "Failed to write file \"%s\": %s", filename, err.Error())
+ }
+
+ util.StatusMessage(util.VERBOSITY_DEFAULT, "Wrote file \"%s\"\n", filename)
+ return nil
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..a8820c4
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,7 @@
+module mynewt.apache.org/imgmod
+
+require (
+ github.com/sirupsen/logrus v1.4.0
+ github.com/spf13/cobra v0.0.3
+ mynewt.apache.org/newt v0.0.0-20190314184600-c5632c20daf3
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..d545ff0
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,29 @@
+github.com/NickBall/go-aes-key-wrap v0.0.0-20170929221519-1c3aa3e4dfc5 h1:5BIUS5hwyLM298mOf8e8TEgD3cCYqc86uaJdQCYZo/o=
+github.com/NickBall/go-aes-key-wrap v0.0.0-20170929221519-1c3aa3e4dfc5/go.mod h1:w5D10RxC0NmPYxmQ438CC1S07zaC1zpvuNW7s5sUk2Q=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
+github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
+github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
+github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/sys v0.0.0-20180707002001-3c6ecd8f22c6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+mynewt.apache.org/newt v0.0.0-20190314184600-c5632c20daf3 h1:U9HanOWwCKSPTCZu7ti0MlmMlkWpTiR856BR2bRzHaU=
+mynewt.apache.org/newt v0.0.0-20190314184600-c5632c20daf3/go.mod h1:lFsPYOHxMMWA11pydOeh0GVFiXtx0A9VnzOQ6SiRR88=
diff --git a/iimg/lvimg.go b/iimg/lvimg.go
new file mode 100644
index 0000000..bacefe9
--- /dev/null
+++ b/iimg/lvimg.go
@@ -0,0 +1,189 @@
+/**
+ * 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 iimg
+
+import (
+ "encoding/hex"
+ "fmt"
+ "strings"
+
+ "mynewt.apache.org/newt/artifact/image"
+ "mynewt.apache.org/newt/artifact/sec"
+ "mynewt.apache.org/newt/util"
+)
+
+func GetDupSigs(img image.Image) []string {
+ m := map[string]struct{}{}
+ var dups []string
+
+ for _, tlv := range img.Tlvs {
+ if tlv.Header.Type == image.IMAGE_TLV_KEYHASH {
+ h := hex.EncodeToString(tlv.Data)
+ if _, ok := m[h]; ok {
+ dups = append(dups, h)
+ } else {
+ m[h] = struct{}{}
+ }
+ }
+ }
+
+ return dups
+}
+
+func DetectInvalidSigTlvs(img image.Image) error {
+ var errStrs []string
+ addErr := func(format string, args ...interface{}) {
+ s := fmt.Sprintf(format, args...)
+ errStrs = append(errStrs, s)
+ }
+
+ prevIsHash := false
+ for i, tlv := range img.Tlvs {
+ curIsHash := tlv.Header.Type == image.IMAGE_TLV_KEYHASH
+ curIsSig := image.ImageTlvTypeIsSig(tlv.Header.Type)
+ isLast := i == len(img.Tlvs)-1
+
+ if prevIsHash && !curIsSig {
+ prevTlv := img.Tlvs[i-1]
+ addErr("TLV%d (%s) not immediately followed by signature TLV",
+ i-1, image.ImageTlvTypeName(prevTlv.Header.Type))
+ } else if curIsHash && isLast {
+ addErr("TLV%d (%s) not immediately followed by signature TLV",
+ i, image.ImageTlvTypeName(tlv.Header.Type))
+ } else if !prevIsHash && curIsSig {
+ addErr("TLV%d (%s) not immediately preceded by key hash TLV",
+ i, image.ImageTlvTypeName(tlv.Header.Type))
+ }
+
+ prevIsHash = curIsHash
+ }
+
+ if len(errStrs) > 0 {
+ return util.FmtNewtError("%s", strings.Join(errStrs, "\n"))
+ }
+
+ return nil
+}
+
+func VerifyImage(img image.Image) error {
+ if len(img.Tlvs) == 0 || img.Tlvs[0].Header.Type != image.IMAGE_TLV_SHA256 {
+ return util.FmtNewtError("First TLV must be SHA256")
+ }
+
+ if err := DetectInvalidSigTlvs(img); err != nil {
+ return err
+ }
+
+ if dups := GetDupSigs(img); len(dups) > 0 {
+ s := "Duplicate signatures detected:"
+ for _, d := range dups {
+ s += fmt.Sprintf("\n %s", d)
+ }
+
+ return util.FmtNewtError("%s", s)
+ }
+
+ return nil
+}
+
+func PadEcdsa256Sig(sig []byte) ([]byte, error) {
+ if len(sig) < 70 {
+ return nil, util.FmtNewtError(
+ "Invalid ECDSA256 signature; length (%d) less than 70", len(sig))
+ }
+
+ if len(sig) < 72 {
+ sig = append(sig, []byte{0x00, 0x00}...)
+ }
+
+ return sig, nil
+}
+
+// XXX: Only RSA supported for now.
+func ExtractSecret(img *image.Image) ([]byte, error) {
+ tlvs := img.RemoveTlvsWithType(image.IMAGE_TLV_ENC_RSA)
+ if len(tlvs) != 1 {
+ return nil, util.FmtNewtError(
+ "Image contains invalid count of ENC_RSA TLVs: %d; must contain 1",
+ len(tlvs))
+ }
+
+ return tlvs[0].Data, nil
+}
+
+// XXX: Only RSA supported for now.
+func DecryptImage(img image.Image, privKeBytes []byte) (image.Image, error) {
+ cipherSecret, err := ExtractSecret(&img)
+ if err != nil {
+ return img, err
+ }
+
+ privKe, err := sec.ParsePrivKeDer(privKeBytes)
+ if err != nil {
+ return img, err
+ }
+
+ plainSecret, err := sec.DecryptSecretRsa(privKe, cipherSecret)
+ if err != nil {
+ return img, err
+ }
+
+ body, err := sec.EncryptAES(img.Body, plainSecret)
+ if err != nil {
+ return img, err
+ }
+
+ img.Body = body
+ return img, nil
+}
+
+func EncryptImage(img image.Image, pubKeBytes []byte) (image.Image, error) {
+ tlvp, err := img.FindUniqueTlv(image.IMAGE_TLV_ENC_RSA)
+ if err != nil {
+ return img, err
+ }
+ if tlvp != nil {
+ return img, util.FmtNewtError("Image already contains an ENC_RSA TLV")
+ }
+
+ plainSecret, err := image.GeneratePlainSecret()
+ if err != nil {
+ return img, err
+ }
+
+ cipherSecret, err := image.GenerateCipherSecret(pubKeBytes, plainSecret)
+ if err != nil {
+ return img, err
+ }
+
+ body, err := sec.EncryptAES(img.Body, plainSecret)
+ if err != nil {
+ return img, err
+ }
+ img.Body = body
+
+ tlv, err := image.GenerateEncTlv(cipherSecret)
+ if err != nil {
+ return img, err
+ }
+ img.Tlvs = append(img.Tlvs, tlv)
+
+ return img, nil
+}
diff --git a/imfg/lvmfg.go b/imfg/lvmfg.go
new file mode 100644
index 0000000..9472ee1
--- /dev/null
+++ b/imfg/lvmfg.go
@@ -0,0 +1,249 @@
+/**
+ * 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 imfg
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "sort"
+ "strings"
+
+ "mynewt.apache.org/newt/artifact/flash"
+ "mynewt.apache.org/newt/artifact/mfg"
+ "mynewt.apache.org/newt/util"
+)
+
+type NameBlobMap map[string][]byte
+
+func (to NameBlobMap) Union(from NameBlobMap) {
+ for k, v := range from {
+ to[k] = v
+ }
+}
+
+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) error {
+ prevDevice := -1
+ off := 0
+ for _, area := range areas {
+ if area.Device != prevDevice {
+ off = 0
+ }
+
+ if err := verifyArea(area, off); err != nil {
+ return err
+ }
+ off += area.Size
+ }
+
+ return nil
+}
+
+func Split(mfgBin []byte, deviceNum int,
+ areas []flash.FlashArea, eraseVal byte) (NameBlobMap, error) {
+
+ mm := NameBlobMap{}
+
+ 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 {
+ var areaBin []byte
+ if area.Offset < len(mfgBin) {
+ end := area.Offset + area.Size
+ overflow := end - len(mfgBin)
+ if overflow > 0 {
+ end -= overflow
+ }
+ areaBin = mfgBin[area.Offset:end]
+ }
+
+ mm[area.Name] = StripPadding(areaBin, eraseVal)
+ }
+ }
+
+ return mm, nil
+}
+
+// `areas` must be sorted by device ID, then by offset.
+func Join(mm NameBlobMap, eraseVal byte,
+ areas []flash.FlashArea) ([]byte, error) {
+
+ // Keep track of which areas we haven't seen yet.
+ unseen := map[string]struct{}{}
+ for name, _ := range mm {
+ unseen[name] = struct{}{}
+ }
+
+ joined := []byte{}
+ for _, area := range areas {
+ bin := mm[area.Name]
+
+ // Only include this area if it belongs to the mfg image we are
+ // joining.
+ if bin != nil {
+ delete(unseen, area.Name)
+
+ // Pad remainder of previous area in this section.
+ padSize := area.Offset - len(joined)
+ if padSize > 0 {
+ joined = mfg.AddPadding(joined, eraseVal, padSize)
+ }
+
+ // Append data to joined binary.
+ binstr := ""
+ if len(bin) >= 4 {
+ binstr = fmt.Sprintf("%x", bin[:4])
+ }
+ util.StatusMessage(util.VERBOSITY_DEFAULT,
+ "inserting %s (%s) at offset %d (0x%x)\n",
+ area.Name, binstr, len(joined), len(joined))
+ joined = append(joined, bin...)
+ }
+ }
+
+ // Ensure we processed every area in the 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, ", "))
+ }
+
+ // Strip padding from the end of the joined binary.
+ joined = StripPadding(joined, eraseVal)
+
+ return joined, nil
+}
+
+func replaceKey(mfgBin []byte, okey []byte, nkey []byte) (int, error) {
+ if len(okey) > len(mfgBin) {
+ return 0, util.FmtNewtError(
+ "key longer than flash section (%d > %d)", len(okey), len(mfgBin))
+ }
+
+ idx := bytes.Index(mfgBin, okey)
+ if idx == -1 {
+ return 0, util.FmtNewtError("old key not present in flash section")
+ }
+
+ lastIdx := bytes.LastIndex(mfgBin, okey)
+ if idx != lastIdx {
+ return 0, util.FmtNewtError(
+ "multiple instances of old key in flash section")
+ }
+
+ util.StatusMessage(util.VERBOSITY_VERBOSE,
+ "Replacing key at offset %d\n", idx)
+
+ copy(mfgBin[idx:idx+len(okey)], nkey)
+
+ return idx, nil
+}
+
+func ReplaceIsk(mfgBin []byte, okey []byte, nkey []byte) error {
+ if len(nkey) != len(okey) {
+ return util.FmtNewtError(
+ "key lengths differ (%d != %d)", len(nkey), len(okey))
+ }
+
+ if _, err := replaceKey(mfgBin, okey, nkey); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func ReplaceKek(mfgBin []byte, okey []byte, nkey []byte) error {
+ if len(nkey) > len(okey) {
+ return util.FmtNewtError(
+ "new key longer than old key (%d > %d)", len(nkey), len(okey))
+ }
+
+ keyIdx, err := replaceKey(mfgBin, okey, nkey)
+ if err != nil {
+ return err
+ }
+
+ // The key length is an unsigned int immediately prior to the key.
+ var kl uint32
+ klIdx := keyIdx - 4
+ buf := bytes.NewBuffer(mfgBin[klIdx : klIdx+4])
+ if err := binary.Read(buf, binary.LittleEndian, &kl); err != nil {
+ return util.ChildNewtError(err)
+ }
+
+ if int(kl) != len(okey) {
+ return util.FmtNewtError(
+ "embedded key length (off=%d) has unexpected value; "+
+ "want=%d have=%d",
+ klIdx, len(okey), kl)
+ }
+
+ buf = &bytes.Buffer{}
+ kl = uint32(len(nkey))
+ if err := binary.Write(buf, binary.LittleEndian, kl); err != nil {
+ return util.ChildNewtError(err)
+ }
+
+ copy(mfgBin[klIdx:klIdx+4], buf.Bytes())
+
+ return nil
+}
+
+func StripPadding(b []byte, eraseVal byte) []byte {
+ var pad int
+ for pad = 0; pad < len(b); pad++ {
+ off := len(b) - pad - 1
+ if b[off] != eraseVal {
+ break
+ }
+ }
+
+ return b[:len(b)-pad]
+}
diff --git a/imgmod.go b/imgmod.go
new file mode 100644
index 0000000..1677525
--- /dev/null
+++ b/imgmod.go
@@ -0,0 +1,100 @@
+/**
+ * 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/imgmod/cli"
+ "mynewt.apache.org/newt/util"
+)
+
+var ImgmodLogLevel log.Level
+var imgmodSilent bool
+var imgmodQuiet bool
+var imgmodVerbose bool
+var imgmodVersion = "0.0.2"
+
+func main() {
+ imgmodHelpText := ""
+ imgmodHelpEx := ""
+
+ logLevelStr := ""
+ imgmodCmd := &cobra.Command{
+ Use: "imgmod",
+ Short: "imgmod is a tool to view and modify Mynewt image files",
+ Long: imgmodHelpText,
+ Example: imgmodHelpEx,
+ PersistentPreRun: func(cmd *cobra.Command, args []string) {
+ verbosity := util.VERBOSITY_DEFAULT
+ if imgmodSilent {
+ verbosity = util.VERBOSITY_SILENT
+ } else if imgmodQuiet {
+ verbosity = util.VERBOSITY_QUIET
+ } else if imgmodVerbose {
+ verbosity = util.VERBOSITY_VERBOSE
+ }
+
+ logLevel, err := log.ParseLevel(logLevelStr)
+ if err != nil {
+ cli.ImgmodUsage(nil, util.ChildNewtError(err))
+ }
+ ImgmodLogLevel = logLevel
+
+ if err := util.Init(ImgmodLogLevel, "", verbosity); err != nil {
+ cli.ImgmodUsage(nil, err)
+ }
+ },
+
+ Run: func(cmd *cobra.Command, args []string) {
+ cmd.Help()
+ },
+ }
+
+ imgmodCmd.PersistentFlags().BoolVarP(&imgmodVerbose, "verbose", "v", false,
+ "Enable verbose output when executing commands")
+ imgmodCmd.PersistentFlags().BoolVarP(&imgmodQuiet, "quiet", "q", false,
+ "Be quiet; only display error output")
+ imgmodCmd.PersistentFlags().BoolVarP(&imgmodSilent, "silent", "s", false,
+ "Be silent; don't output anything")
+ imgmodCmd.PersistentFlags().StringVarP(&logLevelStr, "loglevel", "l",
+ "WARN", "Log level")
+
+ versHelpText := `Display the imgmod version number`
+ versHelpEx := " imgmod version"
+ versCmd := &cobra.Command{
+ Use: "version",
+ Short: "Display the imgmod version number",
+ Long: versHelpText,
+ Example: versHelpEx,
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Printf("%s\n", imgmodVersion)
+ },
+ }
+ imgmodCmd.AddCommand(versCmd)
+
+ cli.AddImageCommands(imgmodCmd)
+ cli.AddMfgCommands(imgmodCmd)
+
+ imgmodCmd.Execute()
+}