image/image.go (613 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 image import ( "bufio" "bytes" "encoding/binary" "fmt" "io" "io/ioutil" "os" "github.com/apache/mynewt-artifact/errors" "github.com/apache/mynewt-artifact/sec" ) const ( IMAGE_MAGIC = 0x96f3b83d /* Image header magic */ IMAGE_TRAILER_MAGIC = 0x6907 /* TLV info magic */ IMAGE_PROT_TRAILER_MAGIC = 0x6908 /* Protected TLV info magic */ ) const ( IMAGE_HEADER_SIZE = 32 IMAGE_TRAILER_SIZE = 4 IMAGE_TLV_SIZE = 4 /* Plus `value` field. */ ) /* * Image header flags. */ const ( IMAGE_F_PIC = 0x00000001 IMAGE_F_ENCRYPTED = 0x00000004 /* encrypted image */ IMAGE_F_NON_BOOTABLE = 0x00000010 /* non bootable image */ ) /* * Image trailer TLV types. */ const ( IMAGE_TLV_KEYHASH = 0x01 IMAGE_TLV_SHA256 = 0x10 IMAGE_TLV_RSA2048 = 0x20 IMAGE_TLV_ECDSA224 = 0x21 IMAGE_TLV_ECDSA256 = 0x22 IMAGE_TLV_RSA3072 = 0x23 IMAGE_TLV_ED25519 = 0x24 IMAGE_TLV_ENC_RSA = 0x30 IMAGE_TLV_ENC_KEK = 0x31 IMAGE_TLV_ENC_EC256 = 0x32 IMAGE_TLV_AES_NONCE_LEGACY = 0x50 IMAGE_TLV_SECRET_ID_LEGACY = 0x60 IMAGE_TLV_AES_NONCE = 0xa1 IMAGE_TLV_SECRET_ID = 0xa2 IMAGE_TLV_SECTION = 0xa3 ) var imageTlvTypeNameMap = map[uint8]string{ IMAGE_TLV_KEYHASH: "KEYHASH", IMAGE_TLV_SHA256: "SHA256", IMAGE_TLV_RSA2048: "RSA2048", IMAGE_TLV_ECDSA224: "ECDSA224", IMAGE_TLV_ECDSA256: "ECDSA256", IMAGE_TLV_RSA3072: "RSA3072", IMAGE_TLV_ED25519: "ED25519", IMAGE_TLV_ENC_RSA: "ENC_RSA", IMAGE_TLV_ENC_KEK: "ENC_KEK", IMAGE_TLV_ENC_EC256: "ENC_EC256", IMAGE_TLV_AES_NONCE: "AES_NONCE", IMAGE_TLV_SECRET_ID: "SEC_KEY_ID", IMAGE_TLV_AES_NONCE_LEGACY: "AES_NONCE", IMAGE_TLV_SECRET_ID_LEGACY: "SEC_KEY_ID", IMAGE_TLV_SECTION: "SECTION", } var imageTlvTypeSigTypeMap = map[uint8]sec.SigType{ IMAGE_TLV_RSA2048: sec.SIG_TYPE_RSA2048, IMAGE_TLV_ECDSA224: sec.SIG_TYPE_ECDSA224, IMAGE_TLV_ECDSA256: sec.SIG_TYPE_ECDSA256, IMAGE_TLV_RSA3072: sec.SIG_TYPE_RSA3072, IMAGE_TLV_ED25519: sec.SIG_TYPE_ED25519, } type ImageVersion struct { Major uint8 Minor uint8 Rev uint16 BuildNum uint32 } type ImageHdr struct { Magic uint32 Pad1 uint32 HdrSz uint16 ProtSz uint16 ImgSz uint32 Flags uint32 Vers ImageVersion Pad3 uint32 } type ImageTlvHdr struct { Type uint8 Pad uint8 Len uint16 } type ImageTlv struct { Header ImageTlvHdr Data []byte } type ImageTrailer struct { Magic uint16 TlvTotLen uint16 } type Image struct { Header ImageHdr Pad []byte Body []byte ProtTlvs []ImageTlv Tlvs []ImageTlv } type ImageOffsets struct { Header int Body int ProtTrailer int Trailer int ProtTlvs []int Tlvs []int TotalSize int } type Section struct { Name string Size int Offset int } func ImageTlvTypeIsValid(tlvType uint8) bool { _, ok := imageTlvTypeNameMap[tlvType] return ok } func ImageTlvTypeName(tlvType uint8) string { name, ok := imageTlvTypeNameMap[tlvType] if !ok { return "???" } return name } func ImageTlvTypeToSigType(tlvType uint8) (sec.SigType, bool) { typ, ok := imageTlvTypeSigTypeMap[tlvType] return typ, ok } func ImageTlvTypeIsSig(tlvType uint8) bool { return tlvType == IMAGE_TLV_RSA2048 || tlvType == IMAGE_TLV_RSA3072 || tlvType == IMAGE_TLV_ECDSA224 || tlvType == IMAGE_TLV_ECDSA256 || tlvType == IMAGE_TLV_ED25519 } func ImageTlvTypeIsSecret(tlvType uint8) bool { return tlvType == IMAGE_TLV_ENC_RSA || tlvType == IMAGE_TLV_ENC_KEK || tlvType == IMAGE_TLV_ENC_EC256 } func (ver ImageVersion) String() string { return fmt.Sprintf("%d.%d.%d.%d", ver.Major, ver.Minor, ver.Rev, ver.BuildNum) } func (tlv *ImageTlv) Clone() ImageTlv { return ImageTlv{ Header: tlv.Header, Data: append([]byte(nil), tlv.Data...), } } func (tlv *ImageTlv) Write(w io.Writer) (int, error) { totalSize := 0 err := binary.Write(w, binary.LittleEndian, &tlv.Header) if err != nil { return totalSize, errors.Wrapf(err, "failed to write image TLV header") } totalSize += IMAGE_TLV_SIZE size, err := w.Write(tlv.Data) if err != nil { return totalSize, errors.Wrapf(err, "failed to write image TLV data") } totalSize += size return totalSize, nil } // Clone performs a deep copy of an image. func (img *Image) Clone() Image { dup := Image{ Header: img.Header, Pad: append([]byte(nil), img.Pad...), Body: append([]byte(nil), img.Body...), ProtTlvs: make([]ImageTlv, len(img.ProtTlvs)), Tlvs: make([]ImageTlv, len(img.Tlvs)), } for i, tlv := range img.ProtTlvs { dup.ProtTlvs[i] = tlv.Clone() } for i, tlv := range img.Tlvs { dup.Tlvs[i] = tlv.Clone() } return dup } // FindTlvIndicesIf searches an image for TLVs satisfying the given predicate // and returns their indices. func (img *Image) FindTlvIndicesIf(pred func(tlv ImageTlv) bool) []int { var idxs []int for i, tlv := range img.Tlvs { if pred(tlv) { idxs = append(idxs, i) } } return idxs } // FindTlvIndices searches an image for TLVs of the specified type and // returns their indices. func (img *Image) FindTlvIndices(tlvType uint8) []int { return img.FindTlvIndicesIf(func(tlv ImageTlv) bool { return tlv.Header.Type == tlvType }) } // FindTlvIndices searches an image for TLVs satisfying the given predicate and // returns them. func (img *Image) FindTlvsIf(pred func(tlv ImageTlv) bool) []*ImageTlv { var tlvs []*ImageTlv idxs := img.FindTlvIndicesIf(pred) for _, idx := range idxs { tlvs = append(tlvs, &img.Tlvs[idx]) } return tlvs } // FindTlvs retrieves all TLVs in an image's footer with the specified type. func (img *Image) FindTlvs(tlvType uint8) []*ImageTlv { var tlvs []*ImageTlv idxs := img.FindTlvIndices(tlvType) for _, idx := range idxs { tlvs = append(tlvs, &img.Tlvs[idx]) } return tlvs } // FindUniqueTlv retrieves a TLV in an image's footer with the specified // type. It returns an error if there is more than one TLV with this type. func (i *Image) FindUniqueTlv(tlvType uint8) (*ImageTlv, error) { tlvs := i.FindTlvs(tlvType) if len(tlvs) == 0 { return nil, nil } if len(tlvs) > 1 { return nil, errors.Errorf("image contains %d TLVs with type %d", len(tlvs), tlvType) } return tlvs[0], nil } // RemoveTlvsIf removes all TLVs from an image that satisfy the supplied // predicate. It returns a slice of the removed TLVs. func (i *Image) RemoveTlvsIf(pred func(tlv ImageTlv) bool) []ImageTlv { rmed := []ImageTlv{} for idx := 0; idx < len(i.Tlvs); { tlv := i.Tlvs[idx] if pred(tlv) { rmed = append(rmed, tlv) i.Tlvs = append(i.Tlvs[:idx], i.Tlvs[idx+1:]...) } else { idx++ } } return rmed } // RemoveTlvsWithType removes from an image all TLVs with the specified type. // It returns a slice of the removed TLVs. func (i *Image) RemoveTlvsWithType(tlvType uint8) []ImageTlv { return i.RemoveTlvsIf(func(tlv ImageTlv) bool { return tlv.Header.Type == tlvType }) } // FindProtTlvIndicesIf searches an image for TLVs satisfying the given // predicate and returns their indices. func (img *Image) FindProtTlvIndicesIf(pred func(tlv ImageTlv) bool) []int { var idxs []int for i, tlv := range img.ProtTlvs { if pred(tlv) { idxs = append(idxs, i) } } return idxs } // FindProtTlvIndices searches an image for TLVs of the specified type and // returns their indices. func (img *Image) FindProtTlvIndices(tlvType uint8) []int { return img.FindProtTlvIndicesIf(func(tlv ImageTlv) bool { return tlv.Header.Type == tlvType }) } // FindTlvIndices searches an image for TLVs satisfying the given predicate and // returns them. func (img *Image) FindProtTlvsIf(pred func(tlv ImageTlv) bool) []*ImageTlv { var tlvs []*ImageTlv idxs := img.FindProtTlvIndicesIf(pred) for _, idx := range idxs { tlvs = append(tlvs, &img.ProtTlvs[idx]) } return tlvs } // FindProtTlvs retrieves all TLVs in an image's footer with the specified type. func (img *Image) FindProtTlvs(tlvType uint8) []*ImageTlv { var tlvs []*ImageTlv idxs := img.FindProtTlvIndices(tlvType) for _, idx := range idxs { tlvs = append(tlvs, &img.ProtTlvs[idx]) } return tlvs } // FindProtUniqueTlv retrieves a TLV in an image's footer with the specified // type. It returns an error if there is more than one TLV with this type. func (i *Image) FindProtUniqueTlv(tlvType uint8) (*ImageTlv, error) { tlvs := i.FindProtTlvs(tlvType) if len(tlvs) == 0 { return nil, nil } if len(tlvs) > 1 { return nil, errors.Errorf("image contains %d TLVs with type %d", len(tlvs), tlvType) } return tlvs[0], nil } // RemoveProtTlvsIf removes all TLVs from an image that satisfy the supplied // predicate. It returns a slice of the removed TLVs. func (i *Image) RemoveProtTlvsIf(pred func(tlv ImageTlv) bool) []ImageTlv { rmed := []ImageTlv{} for idx := 0; idx < len(i.ProtTlvs); { tlv := i.ProtTlvs[idx] if pred(tlv) { rmed = append(rmed, tlv) i.ProtTlvs = append(i.ProtTlvs[:idx], i.ProtTlvs[idx+1:]...) i.Header.ProtSz -= uint16(IMAGE_TLV_SIZE + len(tlv.Data)) } else { idx++ } } if len(i.ProtTlvs) == 0 { i.Header.ProtSz = 0 } return rmed } // RemoveProtTlvsWithType removes from an image all TLVs with the specified // type. It returns a slice of the removed TLVs. func (i *Image) RemoveProtTlvsWithType(tlvType uint8) []ImageTlv { return i.RemoveProtTlvsIf(func(tlv ImageTlv) bool { return tlv.Header.Type == tlvType }) } func (i *Image) FindAllTlvsIf(pred func(tlv ImageTlv) bool) []*ImageTlv { regTlvs := i.FindTlvsIf(pred) protTlvs := i.FindProtTlvsIf(pred) return append(regTlvs, protTlvs...) } func (i *Image) FindAllTlvs(tlvType uint8) []*ImageTlv { return i.FindAllTlvsIf(func(tlv ImageTlv) bool { return tlv.Header.Type == tlvType }) } func (i *Image) FindAllUniqueTlv(tlvType uint8) (*ImageTlv, error) { tlvs := i.FindAllTlvs(tlvType) if len(tlvs) == 0 { return nil, nil } if len(tlvs) > 1 { return nil, errors.Errorf( "image contains too many TLVs with type %d: have=%d want<=1", tlvType, len(tlvs)) } return tlvs[0], nil } // ProtTrailer constructs a protected ImageTrailer corresponding to the given // image. func (img *Image) ProtTrailer() ImageTrailer { trailer := ImageTrailer{ Magic: IMAGE_PROT_TRAILER_MAGIC, TlvTotLen: IMAGE_TRAILER_SIZE, } for _, tlv := range img.ProtTlvs { trailer.TlvTotLen += IMAGE_TLV_SIZE + tlv.Header.Len } return trailer } // Trailer constructs an ImageTrailer corresponding to the given image. func (img *Image) Trailer() ImageTrailer { trailer := ImageTrailer{ Magic: IMAGE_TRAILER_MAGIC, TlvTotLen: IMAGE_TRAILER_SIZE, } for _, tlv := range img.Tlvs { trailer.TlvTotLen += IMAGE_TLV_SIZE + tlv.Header.Len } return trailer } // Hash retrieves the contents of an image's SHA256 TLV. func (i *Image) Hash() ([]byte, error) { tlv, err := i.FindUniqueTlv(IMAGE_TLV_SHA256) if err != nil { return nil, errors.Wrapf(err, "failed to retrieve image hash") } if tlv == nil { return nil, errors.Errorf( "failed to retrieve image hash: image does not contain hash TLV") } return tlv.Data, nil } // CalcHash calculates a SHA256 of the given image. initialHash should be nil // for non-split-images. func (i *Image) CalcHash(initialHash []byte) ([]byte, error) { return calcHash(initialHash, i.Header, i.Pad, i.Body, i.ProtTlvs) } // WritePlusOffsets writes a binary image to the given writer. It returns // the offsets of the image components that got written. func (i *Image) WritePlusOffsets(w io.Writer) (ImageOffsets, error) { offs := ImageOffsets{} offset := 0 offs.Header = offset err := binary.Write(w, binary.LittleEndian, &i.Header) if err != nil { return offs, errors.Wrapf(err, "failed to write image header") } offset += IMAGE_HEADER_SIZE err = binary.Write(w, binary.LittleEndian, i.Pad) if err != nil { return offs, errors.Wrapf(err, "failed to write image padding") } offset += len(i.Pad) offs.Body = offset size, err := w.Write(i.Body) if err != nil { return offs, errors.Wrapf(err, "failed to write image body") } offset += size if i.Header.ProtSz > 0 { protTrailer := i.ProtTrailer() offs.ProtTrailer = offset err = binary.Write(w, binary.LittleEndian, &protTrailer) if err != nil { return offs, errors.Wrapf(err, "failed to write image trailer") } offset += IMAGE_TRAILER_SIZE for _, tlv := range i.ProtTlvs { offs.ProtTlvs = append(offs.ProtTlvs, offset) size, err := tlv.Write(w) if err != nil { return offs, errors.Wrapf(err, "failed to write image TLV") } offset += size } } trailer := i.Trailer() offs.Trailer = offset err = binary.Write(w, binary.LittleEndian, &trailer) if err != nil { return offs, errors.Wrapf(err, "failed to write image trailer") } offset += IMAGE_TRAILER_SIZE for _, tlv := range i.Tlvs { offs.Tlvs = append(offs.Tlvs, offset) size, err := tlv.Write(w) if err != nil { return offs, errors.Wrapf(err, "failed to write image TLV") } offset += size } offs.TotalSize = offset return offs, nil } // Offsets returns the offsets of each of an image's components if it were // serialized. func (i *Image) Offsets() (ImageOffsets, error) { return i.WritePlusOffsets(ioutil.Discard) } // TotalSize returns the size of the image if it were serialized, in bytes. func (i *Image) TotalSize() (int, error) { offs, err := i.Offsets() if err != nil { return 0, err } return offs.TotalSize, nil } // Write serializes and writes a Mynewt image. func (i *Image) Write(w io.Writer) (int, error) { offs, err := i.WritePlusOffsets(w) if err != nil { return 0, err } return offs.TotalSize, nil } // WriteToFile writes a Mynewt image to a file. func (i *Image) WriteToFile(filename string) error { f, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666) if err != nil { return errors.Wrapf(err, "failed to open image destination file") } if _, err := i.Write(f); err != nil { return errors.Wrapf(err, "failed to write image") } return nil } // CollectSigs returns a slice of all signatures present in an image's // trailer. func (img *Image) CollectSigs() ([]sec.Sig, error) { var sigs []sec.Sig var keyHashTlv *ImageTlv for i, _ := range img.Tlvs { t := &img.Tlvs[i] if t.Header.Type == IMAGE_TLV_KEYHASH { if keyHashTlv != nil { return nil, errors.Errorf( "image contains keyhash tlv without subsequent signature") } keyHashTlv = t } else { sigType, ok := ImageTlvTypeToSigType(t.Header.Type) if ok { if keyHashTlv == nil { return nil, errors.Errorf( "image contains signature tlv without preceding keyhash") } sigs = append(sigs, sec.Sig{ Type: sigType, KeyHash: keyHashTlv.Data, Data: t.Data, }) keyHashTlv = nil } } } return sigs, nil } // CollectSecret finds the "secret" TLV in an image and returns its body. It // returns nil if there is no "secret" TLV. func (img *Image) CollectSecret() ([]byte, error) { tlv, err := img.FindUniqueTlv(IMAGE_TLV_ENC_RSA) if err != nil { return nil, err } if tlv == nil { return nil, nil } return tlv.Data, nil } // ExtractSecret finds the "secret" TLV in an image, removes it, and returns // its body. It returns nil if there is no "secret" TLV. func (img *Image) ExtractSecret() ([]byte, error) { tlvs := img.RemoveTlvsWithType(IMAGE_TLV_ENC_RSA) if len(tlvs) == 0 { return nil, nil } if len(tlvs) > 1 { return nil, errors.Errorf( "image contains >1 ENC_RSA TLVs (%d)", len(tlvs)) } return tlvs[0].Data, nil } // Encrypt encrypts an image body and adds a "secret" TLV. It does NOT set the // "encrypted" flag in the image header. func Encrypt(img Image, pubEncKey sec.PubEncKey) (Image, error) { dup := img.Clone() tlvp, err := dup.FindUniqueTlv(IMAGE_TLV_ENC_RSA) if err != nil { return dup, err } if tlvp != nil { return dup, errors.Errorf("image already contains an ENC_RSA TLV") } plainSecret, err := GeneratePlainSecret() if err != nil { return dup, err } cipherSecret, err := pubEncKey.Encrypt(plainSecret) if err != nil { return dup, err } body, err := sec.EncryptAES(dup.Body, plainSecret, nil) if err != nil { return dup, err } dup.Body = body tlv, err := GenerateEncTlv(cipherSecret) if err != nil { return dup, err } dup.Tlvs = append(dup.Tlvs, tlv) return dup, nil } // Decrypt decrypts an image body and strips the "secret" TLV. It does NOT // clear the "encrypted" flag in the image header. func Decrypt(img Image, privEncKey sec.PrivEncKey) (Image, error) { dup := img.Clone() tlvs := dup.RemoveTlvsIf(func(tlv ImageTlv) bool { return ImageTlvTypeIsSecret(tlv.Header.Type) }) if len(tlvs) != 1 { return dup, errors.Errorf( "failed to decrypt image: wrong count of \"secret\" TLVs; "+ "have=%d want=1", len(tlvs)) } cipherSecret := tlvs[0].Data plainSecret, err := privEncKey.Decrypt(cipherSecret) if err != nil { return img, err } body, err := sec.EncryptAES(dup.Body, plainSecret, nil) if err != nil { return img, err } dup.Body = body return dup, nil } // DecryptHw decrypts a hardware-encrypted image. It does NOT strip the // "nonce" or "secret ID" protected TLVs. func DecryptHw(img Image, secret []byte) (Image, error) { dup := img.Clone() tlvs := dup.FindProtTlvs(IMAGE_TLV_AES_NONCE) if len(tlvs) != 1 { // try to find legacy TLV tlvs := dup.FindProtTlvs(IMAGE_TLV_AES_NONCE_LEGACY) if len(tlvs) != 1 { return dup, errors.Errorf( "failed to decrypt hw-encrypted image: "+ "wrong count of AES nonce TLVs; have=%d want=1", len(tlvs)) } } nonce := tlvs[0].Data body, err := sec.EncryptAES(dup.Body, secret, nonce) if err != nil { return dup, err } dup.Body = body return dup, nil } // DecryptHw decrypts a hardware-encrypted image and strips the "nonce" and // "secret ID" protected TLVs. func DecryptHwFull(img Image, secret []byte) (Image, error) { var err error img, err = DecryptHw(img, secret) if err != nil { return img, err } img.RemoveProtTlvsWithType(IMAGE_TLV_AES_NONCE) img.RemoveProtTlvsWithType(IMAGE_TLV_SECRET_ID) img.RemoveProtTlvsWithType(IMAGE_TLV_AES_NONCE_LEGACY) img.RemoveProtTlvsWithType(IMAGE_TLV_SECRET_ID_LEGACY) return img, nil } // IsEncrypted indicates whether an image's "encrypted" flag is set. func (img *Image) IsEncrypted() bool { return img.Header.Flags&IMAGE_F_ENCRYPTED != 0 } func (img *Image) Bin() ([]byte, error) { b := &bytes.Buffer{} w := bufio.NewWriter(b) _, err := img.Write(w) if err != nil { return nil, err } err = w.Flush() if err != nil { return nil, err } return b.Bytes(), nil } // HasEncryptionPayload indicates whether an image's contains a HW encryption payload. func (img *Image) HasEncryptionPayload() bool { enc := false res, _ := img.FindAllUniqueTlv(IMAGE_TLV_SECRET_ID_LEGACY) if res != nil { nonce, _ := img.FindAllUniqueTlv(IMAGE_TLV_AES_NONCE_LEGACY) if nonce != nil { enc = true } } if !enc { res, _ = img.FindAllUniqueTlv(IMAGE_TLV_SECRET_ID) if res != nil { nonce, _ := img.FindAllUniqueTlv(IMAGE_TLV_AES_NONCE) if nonce != nil { enc = true } } } return enc }