cli/mfg_cmds.go (615 lines of code) (raw):

/** * 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" "sort" "strconv" "strings" "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/apache/mynewt-artifact/flash" "github.com/apache/mynewt-artifact/manifest" "github.com/apache/mynewt-artifact/mfg" "github.com/apache/mynewt-artifact/sec" "mynewt.apache.org/imgmod/imfg" "mynewt.apache.org/imgmod/iutil" ) const MAX_SIG_LEN = 1024 // Bytes. func readMfgBin(filename string) ([]byte, error) { bin, err := ioutil.ReadFile(filename) if err != nil { return nil, errors.Wrapf(err, "failed to read manufacturing image") } return bin, nil } func readManifest(mfgDir string) (manifest.MfgManifest, error) { return manifest.ReadMfgManifest(mfgDir + "/" + mfg.MANIFEST_FILENAME) } func readMfgDir(mfgDir string) (mfg.Mfg, manifest.MfgManifest, error) { man, err := readManifest(mfgDir) if err != nil { return mfg.Mfg{}, manifest.MfgManifest{}, err } binPath := fmt.Sprintf("%s/%s", mfgDir, man.BinPath) bin, err := readMfgBin(binPath) if err != nil { return mfg.Mfg{}, manifest.MfgManifest{}, errors.Wrapf(err, "failed to read \"%s\"", binPath) } metaOff := -1 if man.Meta != nil { metaOff = man.Meta.EndOffset } m, err := mfg.Parse(bin, metaOff, man.EraseVal) if err != nil { return mfg.Mfg{}, manifest.MfgManifest{}, err } return m, man, nil } func mfgTlvStr(tlv mfg.MetaTlv) string { return fmt.Sprintf("%s,0x%02x", mfg.MetaTlvTypeName(tlv.Header.Type), tlv.Header.Type) } func extractFlashAreas(mman manifest.MfgManifest) ([]flash.FlashArea, error) { areas := flash.SortFlashAreasByDevOff(mman.FlashAreas) if len(areas) == 0 { ImgmodUsage(nil, errors.Errorf( "Boot loader manifest does not contain flash map")) } overlaps, conflicts := flash.DetectErrors(areas) if len(overlaps) > 0 || len(conflicts) > 0 { return nil, errors.New(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 !os.IsNotExist(errors.Cause(err)) { return nil, errors.Wrapf(err, "could not read mfgimage binary") } } 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 := strconv.Atoi(args[1]) if err != nil { ImgmodUsage(cmd, errors.Errorf("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 { iutil.Printf("Manufacturing image %s does not contain an MMR\n", inFilename) } else { s, err := m.Meta.Json(metaEndOff) if err != nil { ImgmodUsage(nil, err) } iutil.Printf("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] m, man, err := readMfgDir(mfgDir) if err != nil { ImgmodUsage(cmd, err) } bin, err := m.Bytes(man.EraseVal) if err != nil { ImgmodUsage(nil, err) } areas, err := extractFlashAreas(man) if err != nil { ImgmodUsage(nil, err) } nbmap, err := imfg.Split(bin, man, areas) if err != nil { ImgmodUsage(nil, err) } if err := os.Mkdir(outDir, os.ModePerm); err != nil { ImgmodUsage(nil, errors.Wrapf(err, "failed to make output directory")) } 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] 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, mm.EraseVal, areas) if err != nil { ImgmodUsage(nil, err) } m, err := mfg.Parse(bin, mm.Meta.EndOffset, mm.EraseVal) if err != nil { ImgmodUsage(nil, err) } infos, err := ioutil.ReadDir(splitDir + "/mfg") if err != nil { ImgmodUsage(nil, errors.Wrapf(err, "Error reading source mfg directory: %s")) } for _, info := range infos { if info.Name() != mfg.MFG_BIN_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(mm.EraseVal) if err != nil { ImgmodUsage(nil, err) } binPath := fmt.Sprintf("%s/%s", outDir, mfg.MFG_BIN_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, errors.Wrapf(err, "failed to read mfgimg file: %s")) } okey, err := ioutil.ReadFile(okeyFilename) if err != nil { ImgmodUsage(cmd, errors.Wrapf(err, "failed to read old key der: %s")) } nkey, err := ioutil.ReadFile(nkeyFilename) if err != nil { ImgmodUsage(cmd, errors.Wrapf(err, "failed to read new key der: %s")) } 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, errors.Errorf("--outfile (-o) option required")) } mfgDir := args[0] outFilename := OptOutFilename m, man, err := readMfgDir(mfgDir) if err != nil { ImgmodUsage(cmd, err) } // Zero-out hash so that the hash can be recalculated. if m.Meta != nil { m.Meta.ClearHash() } // Write hashable content to disk. newBin, err := m.Bytes(man.EraseVal) 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) } m, man, err := readMfgDir(mfgDir) if err != nil { ImgmodUsage(cmd, err) } if err := m.RefillHash(man.EraseVal); err != nil { ImgmodUsage(nil, err) } hash, err := m.Hash(man.EraseVal) if err != nil { ImgmodUsage(nil, err) } // Update manifest. man.MfgHash = hex.EncodeToString(hash) // Write new artifacts. if err := EnsureOutDir(mfgDir, outDir); err != nil { ImgmodUsage(nil, err) } binPath := fmt.Sprintf("%s/%s", outDir, man.BinPath) newBin, err := m.Bytes(man.EraseVal) if err != nil { ImgmodUsage(nil, err) } if err := WriteFile(newBin, binPath); err != nil { ImgmodUsage(nil, err) } json, err := man.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, errors.Wrapf(err, "error reading key file")) } // Read signature. sig, err := ioutil.ReadFile(sigFilename) if err != nil { ImgmodUsage(cmd, errors.Wrapf(err, "failed to read signature")) } if len(sig) > MAX_SIG_LEN { ImgmodUsage(nil, errors.Errorf( "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 runRmtlvsMfgCmd(cmd *cobra.Command, args []string) { if len(args) < 2 { ImgmodUsage(cmd, nil) } mfgDir := args[0] outFilename, err := CalcOutFilename( mfgDir + "/" + mfg.MFG_BIN_IMG_FILENAME) if err != nil { ImgmodUsage(cmd, err) } m, man, err := readMfgDir(mfgDir) if err != nil { ImgmodUsage(cmd, err) } numTlvs := 0 if m.Meta != nil { numTlvs = len(m.Meta.Tlvs) } tlvIndices := []int{} idxMap := map[int]struct{}{} for _, arg := range args[1:] { idx, err := strconv.Atoi(arg) if err != nil { ImgmodUsage(cmd, errors.Errorf("invalid TLV index: %s", arg)) } if idx < 0 || idx >= numTlvs { ImgmodUsage(nil, errors.Errorf( "TLV index %s out of range; "+ "must be in range [0, %d] for this mfgimage", arg, numTlvs-1)) } if _, ok := idxMap[idx]; ok { ImgmodUsage(nil, errors.Errorf( "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 := m.Meta.Tlvs[idx] iutil.Printf("Removing TLV%d: %s\n", idx, mfgTlvStr(tlv)) tlvSz := mfg.META_TLV_HEADER_SZ + len(tlv.Data) m.MetaOff += tlvSz m.Meta.Footer.Size -= uint16(tlvSz) m.Meta.Tlvs = append(m.Meta.Tlvs[0:idx], m.Meta.Tlvs[idx+1:]...) } // Rehash. if err := m.RefillHash(man.EraseVal); err != nil { ImgmodUsage(nil, err) } // Write new artifacts. newBin, err := m.Bytes(man.EraseVal) if err != nil { ImgmodUsage(nil, err) } if err := WriteFile(newBin, outFilename); err != nil { ImgmodUsage(nil, err) } } func verifyEmbeddedImages(m mfg.Mfg, man manifest.MfgManifest, iss []sec.PubSignKey, kes []sec.PrivEncKey) (string, bool) { prefix := " images: " good := true imgs, err := m.ExtractImages(man) if err != nil { return prefix + fmt.Sprintf("BAD (%s)", err.Error()), false } var results [][]string for _, img := range imgs { st, stgood := verifyImageStructureStr(img) ha, hagood := verifyImageHashStr(img, kes) si, sigood := verifyImageSigsStr(img, iss) results = append(results, []string{ st, ha, si, }) if !stgood || !hagood || !sigood { good = false } } sub := "\n" for i, r := range results { if i != 0 { sub += "\n" } sub += Indent(fmt.Sprintf("%d:\n", i), 12) sub += strings.Join(IndentLines(r, 16), "\n") } return prefix + sub, good } func runVerifyMfgCmd(cmd *cobra.Command, args []string) { if len(args) < 1 { ImgmodUsage(cmd, nil) } mfgDir := args[0] // Read mfgimg.bin and manifest. m, man, err := readMfgDir(mfgDir) if err != nil { ImgmodUsage(cmd, err) } iss, err := sec.ReadPubSignKeys(OptSignKeys) if err != nil { ImgmodUsage(nil, errors.Wrapf(err, "error reading signing key file")) } kes, err := sec.ReadPrivEncKeys(OptEncKeys) if err != nil { ImgmodUsage(nil, errors.Wrapf(err, "error reading encryption key file")) } st, stgood := verifyMfgStructureStr(m, man) si, sigood := verifyMfgSigsStr(m, man, iss) ma, magood := verifyMfgManifestStr(m, man) im := "" imgood := true if OptVerifyImages { im, imgood = verifyEmbeddedImages(m, man, iss, kes) } else { im = " images: n/a" } iutil.Printf("%s\n", st) iutil.Printf("%s\n", si) iutil.Printf("%s\n", ma) iutil.Printf("%s\n", im) if !stgood || !sigood || !magood || !imgood { os.Exit(94) // EBADMSG } } 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) rmtlvsCmd := &cobra.Command{ Use: "rmtlvs <mfgimage-dir> <tlv-index> [tlv-index] [...]", Short: "Removes the specified TLVs from a Mynewt mfgimage", Run: runRmtlvsMfgCmd, } rmtlvsCmd.PersistentFlags().StringVarP(&OptOutFilename, "outfile", "o", "", "File to write to") rmtlvsCmd.PersistentFlags().BoolVarP(&OptInPlace, "inplace", "i", false, "Replace input file") mfgCmd.AddCommand(rmtlvsCmd) verifyCmd := &cobra.Command{ Use: "verify <mfgimage-dir>", Short: "Verifies an Mynewt mfgimage's integrity", Run: runVerifyMfgCmd, } verifyCmd.PersistentFlags().BoolVar(&OptVerifyImages, "images", false, "Verify embedded images") verifyCmd.PersistentFlags().StringSliceVar(&OptSignKeys, "signkey", nil, "Public signing key (.pem) (can be repeated)") verifyCmd.PersistentFlags().StringSliceVar(&OptEncKeys, "enckey", nil, "Private encryption key (.der) (can be repeated)") mfgCmd.AddCommand(verifyCmd) }