blob: afc846183e0616189f4143d5bdef8b4fc87ec0e2 [file] [log] [blame]
/**
* 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/imgmod/imfg"
"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/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_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(0xff)
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, 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)
}