blob: 4a926a48424a23db16988a512de9cc1eb05694c3 [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/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)
}
if (img.Header.Flags & image.IMAGE_F_ENCRYPTED) != 0 {
util.StatusMessage(util.VERBOSITY_QUIET,
"* Warning: extracting hashable content from an encrypted image\n")
}
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)
}