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-newt.git
commit b7d290fd37bf66681c70ac9d4224d28fa4c0ce33 Author: Christopher Collins <ccoll...@apache.org> AuthorDate: Tue Dec 18 18:20:18 2018 -0800 Artifact library update --- artifact/flash/flash.go | 10 +-- artifact/image/create.go | 147 +++++++++++++++++--------------- artifact/image/image.go | 52 ++++++----- artifact/image/key.go | 66 +++++++++++--- artifact/image/v1.go | 16 ++-- artifact/mfg/mfg.go | 74 ++++++++++------ artifact/{mfg/paths.go => misc/misc.go} | 13 +-- 7 files changed, 228 insertions(+), 150 deletions(-) diff --git a/artifact/flash/flash.go b/artifact/flash/flash.go index c37d2dd..6b85814 100644 --- a/artifact/flash/flash.go +++ b/artifact/flash/flash.go @@ -38,11 +38,11 @@ var SYSTEM_AREA_NAME_ID_MAP = map[string]int{ } type FlashArea struct { - Name string - Id int - Device int - Offset int - Size int + Name string `json:"name"` + Id int `json:"id"` + Device int `json:"device"` + Offset int `json:"offset"` + Size int `json:"size"` } type areaOffSorter struct { diff --git a/artifact/image/create.go b/artifact/image/create.go index c3e8820..76e701d 100644 --- a/artifact/image/create.go +++ b/artifact/image/create.go @@ -69,7 +69,7 @@ func NewImageCreator() ImageCreator { } } -func generateEncTlv(cipherSecret []byte) (ImageTlv, error) { +func GenerateEncTlv(cipherSecret []byte) (ImageTlv, error) { var encType uint8 if len(cipherSecret) == 256 { @@ -90,7 +90,7 @@ func generateEncTlv(cipherSecret []byte) (ImageTlv, error) { }, nil } -func generateSigRsa(key ImageSigKey, hash []byte) ([]byte, error) { +func GenerateSigRsa(key ImageSigKey, hash []byte) ([]byte, error) { opts := rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthEqualsHash, } @@ -103,7 +103,7 @@ func generateSigRsa(key ImageSigKey, hash []byte) ([]byte, error) { return signature, nil } -func generateSigEc(key ImageSigKey, hash []byte) ([]byte, error) { +func GenerateSigEc(key ImageSigKey, hash []byte) ([]byte, error) { r, s, err := ecdsa.Sign(rand.Reader, key.Ec, hash) if err != nil { return nil, util.FmtNewtError("Failed to compute signature: %s", err) @@ -130,13 +130,13 @@ func generateSigEc(key ImageSigKey, hash []byte) ([]byte, error) { return signature, nil } -func generateSig(key ImageSigKey, hash []byte) ([]byte, error) { +func GenerateSig(key ImageSigKey, hash []byte) ([]byte, error) { key.assertValid() if key.Rsa != nil { - return generateSigRsa(key, hash) + return GenerateSigRsa(key, hash) } else { - return generateSigEc(key, hash) + return GenerateSigEc(key, hash) } } @@ -167,7 +167,7 @@ func BuildSigTlvs(keys []ImageSigKey, hash []byte) ([]ImageTlv, error) { tlvs = append(tlvs, tlv) // Signature TLV. - sig, err := generateSig(key, hash) + sig, err := GenerateSig(key, hash) if err != nil { return nil, err } @@ -205,13 +205,18 @@ func GenerateImage(opts ImageCreateOpts) (Image, error) { } if opts.SrcEncKeyFilename != "" { - plainSecret := make([]byte, 16) - if _, err := rand.Read(plainSecret); err != nil { + plainSecret, err := GeneratePlainSecret() + if err != nil { + return Image{}, err + } + + pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename) + if err != nil { return Image{}, util.FmtNewtError( - "Random generation error: %s\n", err) + "Error reading pubkey file: %s", err.Error()) } - cipherSecret, err := ReadEncKey(opts.SrcEncKeyFilename, plainSecret) + cipherSecret, err := GenerateCipherSecret(pubKeBytes, plainSecret) if err != nil { return Image{}, err } @@ -228,7 +233,7 @@ func GenerateImage(opts ImageCreateOpts) (Image, error) { return ri, nil } -func calcHash(initialHash []byte, hdr ImageHdr, +func calcHash(initialHash []byte, hdr ImageHdr, pad []byte, plainBody []byte) ([]byte, error) { hash := sha256.New() @@ -255,6 +260,10 @@ func calcHash(initialHash []byte, hdr ImageHdr, return nil, err } + if err := add(pad); err != nil { + return nil, err + } + extra := hdr.HdrSz - IMAGE_HEADER_SIZE if extra > 0 { b := make([]byte, extra) @@ -270,11 +279,43 @@ func calcHash(initialHash []byte, hdr ImageHdr, return hash.Sum(nil), nil } +func EncryptImageBody(imageBody []byte, secret []byte) ([]byte, error) { + block, err := aes.NewCipher(secret) + if err != nil { + return nil, util.NewNewtError("Failed to create block cipher") + } + nonce := make([]byte, 16) + stream := cipher.NewCTR(block, nonce) + + dataBuf := make([]byte, 16) + encBuf := make([]byte, 16) + r := bytes.NewReader(imageBody) + w := bytes.Buffer{} + for { + cnt, err := r.Read(dataBuf) + if err != nil && err != io.EOF { + return nil, util.FmtNewtError( + "Failed to read from image body: %s", err.Error()) + } + if cnt == 0 { + break + } + + stream.XORKeyStream(encBuf, dataBuf[0:cnt]) + if _, err = w.Write(encBuf[0:cnt]); err != nil { + return nil, util.FmtNewtError( + "Failed to write to image body: %s", err.Error()) + } + } + + return w.Bytes(), nil +} + func (ic *ImageCreator) Create() (Image, error) { - ri := Image{} + img := Image{} // First the header - hdr := ImageHdr{ + img.Header = ImageHdr{ Magic: IMAGE_MAGIC, Pad1: 0, HdrSz: IMAGE_HEADER_SIZE, @@ -286,75 +327,41 @@ func (ic *ImageCreator) Create() (Image, error) { } if !ic.Bootable { - hdr.Flags |= IMAGE_F_NON_BOOTABLE + img.Header.Flags |= IMAGE_F_NON_BOOTABLE } if ic.CipherSecret != nil { - hdr.Flags |= IMAGE_F_ENCRYPTED + img.Header.Flags |= IMAGE_F_ENCRYPTED } if ic.HeaderSize != 0 { - // Pad the header out to the given size. There will - // just be zeros between the header and the start of - // the image when it is padded. + // Pad the header out to the given size. There will just be zeros + // between the header and the start of the image when it is padded. extra := ic.HeaderSize - IMAGE_HEADER_SIZE if extra < 0 { - return ri, util.FmtNewtError("Image header must be at "+ + return img, util.FmtNewtError("Image header must be at "+ "least %d bytes", IMAGE_HEADER_SIZE) } - hdr.HdrSz = uint16(ic.HeaderSize) - for i := 0; i < extra; i++ { - ri.Body = append(ri.Body, 0) - } + img.Header.HdrSz = uint16(ic.HeaderSize) + img.Pad = make([]byte, extra) } - ri.Header = hdr - - hashBytes, err := calcHash(ic.InitialHash, hdr, ic.Body) + hashBytes, err := calcHash(ic.InitialHash, img.Header, img.Pad, ic.Body) if err != nil { - return ri, err + return img, err } - var stream cipher.Stream + // Followed by data. if ic.CipherSecret != nil { - block, err := aes.NewCipher(ic.PlainSecret) + encBody, err := EncryptImageBody(ic.Body, ic.PlainSecret) if err != nil { - return ri, util.NewNewtError("Failed to create block cipher") - } - nonce := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - stream = cipher.NewCTR(block, nonce) - } - - /* - * Followed by data. - */ - dataBuf := make([]byte, 16) - encBuf := make([]byte, 16) - r := bytes.NewReader(ic.Body) - w := bytes.Buffer{} - for { - cnt, err := r.Read(dataBuf) - if err != nil && err != io.EOF { - return ri, util.FmtNewtError( - "Failed to read from image body: %s", err.Error()) - } - if cnt == 0 { - break - } - - if ic.CipherSecret == nil { - _, err = w.Write(dataBuf[0:cnt]) - } else { - stream.XORKeyStream(encBuf, dataBuf[0:cnt]) - _, err = w.Write(encBuf[0:cnt]) - } - if err != nil { - return ri, util.FmtNewtError( - "Failed to write to image body: %s", err.Error()) + return img, err } + img.Body = append(img.Body, encBody...) + } else { + img.Body = append(img.Body, ic.Body...) } - ri.Body = append(ri.Body, w.Bytes()...) util.StatusMessage(util.VERBOSITY_VERBOSE, "Computed Hash for image as %s\n", hex.EncodeToString(hashBytes)) @@ -368,21 +375,21 @@ func (ic *ImageCreator) Create() (Image, error) { }, Data: hashBytes, } - ri.Tlvs = append(ri.Tlvs, tlv) + img.Tlvs = append(img.Tlvs, tlv) tlvs, err := BuildSigTlvs(ic.SigKeys, hashBytes) if err != nil { - return ri, err + return img, err } - ri.Tlvs = append(ri.Tlvs, tlvs...) + img.Tlvs = append(img.Tlvs, tlvs...) if ic.CipherSecret != nil { - tlv, err := generateEncTlv(ic.CipherSecret) + tlv, err := GenerateEncTlv(ic.CipherSecret) if err != nil { - return ri, err + return img, err } - ri.Tlvs = append(ri.Tlvs, tlv) + img.Tlvs = append(img.Tlvs, tlv) } - return ri, nil + return img, nil } diff --git a/artifact/image/image.go b/artifact/image/image.go index 1ed093f..705abe6 100644 --- a/artifact/image/image.go +++ b/artifact/image/image.go @@ -113,6 +113,7 @@ type ImageTrailer struct { type Image struct { Header ImageHdr + Pad []byte Body []byte Tlvs []ImageTlv } @@ -186,36 +187,36 @@ func (ver ImageVersion) String() string { func (h *ImageHdr) Map(offset int) map[string]interface{} { return map[string]interface{}{ - "Magic": h.Magic, - "HdrSz": h.HdrSz, - "ImgSz": h.ImgSz, - "Flags": h.Flags, - "Vers": h.Vers.String(), - "offset": offset, + "magic": h.Magic, + "hdr_sz": h.HdrSz, + "img_sz": h.ImgSz, + "flags": h.Flags, + "vers": h.Vers.String(), + "_offset": offset, } } func rawBodyMap(offset int) map[string]interface{} { return map[string]interface{}{ - "offset": offset, + "_offset": offset, } } func (t *ImageTrailer) Map(offset int) map[string]interface{} { return map[string]interface{}{ - "Magic": t.Magic, - "TlvTotLen": t.TlvTotLen, - "offset": offset, + "magic": t.Magic, + "tlv_tot_len": t.TlvTotLen, + "_offset": offset, } } func (t *ImageTlv) Map(offset int) map[string]interface{} { return map[string]interface{}{ - "Type": t.Header.Type, - "typestr": ImageTlvTypeName(t.Header.Type), - "Len": t.Header.Len, - "offset": offset, - "data": hex.EncodeToString(t.Data), + "type": t.Header.Type, + "len": t.Header.Len, + "data": hex.EncodeToString(t.Data), + "_typestr": ImageTlvTypeName(t.Header.Type), + "_offset": offset, } } @@ -297,19 +298,26 @@ func (i *Image) FindUniqueTlv(tlvType uint8) (*ImageTlv, error) { return &tlvs[0], nil } -func (i *Image) RemoveTlvsIf(pred func(tlv ImageTlv) bool) int { - numRmed := 0 +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:]...) - numRmed++ } else { idx++ } } - return numRmed + return rmed +} + +func (i *Image) RemoveTlvsWithType(tlvType uint8) []ImageTlv { + return i.RemoveTlvsIf(func(tlv ImageTlv) bool { + return tlv.Header.Type == tlvType + }) } func (img *Image) Trailer() ImageTrailer { @@ -349,6 +357,12 @@ func (i *Image) WritePlusOffsets(w io.Writer) (ImageOffsets, error) { } offset += IMAGE_HEADER_SIZE + err = binary.Write(w, binary.LittleEndian, i.Pad) + if err != nil { + return offs, util.ChildNewtError(err) + } + offset += len(i.Pad) + offs.Body = offset size, err := w.Write(i.Body) if err != nil { diff --git a/artifact/image/key.go b/artifact/image/key.go index 8345cd9..a343e2d 100644 --- a/artifact/image/key.go +++ b/artifact/image/key.go @@ -21,6 +21,7 @@ package image import ( "crypto/aes" + "crypto/cipher" "crypto/ecdsa" "crypto/rand" "crypto/rsa" @@ -212,7 +213,7 @@ func (key *ImageSigKey) sigTlvType() uint8 { } } -func parseEncKeyPem(keyBytes []byte, plainSecret []byte) ([]byte, error) { +func ParsePubKePem(keyBytes []byte) (*rsa.PublicKey, error) { b, _ := pem.Decode(keyBytes) if b == nil { return nil, nil @@ -237,6 +238,20 @@ func parseEncKeyPem(keyBytes []byte, plainSecret []byte) ([]byte, error) { "Error parsing pubkey file: %s", err.Error()) } + return pubk, nil +} + +func ParsePrivKeDer(keyBytes []byte) (*rsa.PrivateKey, error) { + privKey, err := x509.ParsePKCS1PrivateKey(keyBytes) + if err != nil { + return nil, util.FmtNewtError( + "Error parsing private key file: %s", err.Error()) + } + + return privKey, nil +} + +func EncryptSecretRsa(pubk *rsa.PublicKey, plainSecret []byte) ([]byte, error) { rng := rand.Reader cipherSecret, err := rsa.EncryptOAEP( sha256.New(), rng, pubk, plainSecret, nil) @@ -248,7 +263,21 @@ func parseEncKeyPem(keyBytes []byte, plainSecret []byte) ([]byte, error) { return cipherSecret, nil } -func parseEncKeyBase64(keyBytes []byte, plainSecret []byte) ([]byte, error) { +func DecryptSecretRsa(privk *rsa.PrivateKey, + cipherSecret []byte) ([]byte, error) { + + rng := rand.Reader + plainSecret, err := rsa.DecryptOAEP( + sha256.New(), rng, privk, cipherSecret, nil) + if err != nil { + return nil, util.FmtNewtError( + "Error from encryption: %s\n", err.Error()) + } + + return plainSecret, nil +} + +func ParseKeBase64(keyBytes []byte) (cipher.Block, error) { kek, err := base64.StdEncoding.DecodeString(string(keyBytes)) if err != nil { return nil, util.FmtNewtError( @@ -265,7 +294,11 @@ func parseEncKeyBase64(keyBytes []byte, plainSecret []byte) ([]byte, error) { "Error creating keywrap cipher: %s", err.Error()) } - cipherSecret, err := keywrap.Wrap(cipher, plainSecret) + return cipher, nil +} + +func encryptSecretAes(c cipher.Block, plainSecret []byte) ([]byte, error) { + cipherSecret, err := keywrap.Wrap(c, plainSecret) if err != nil { return nil, util.FmtNewtError("Error key-wrapping: %s", err.Error()) } @@ -273,27 +306,36 @@ func parseEncKeyBase64(keyBytes []byte, plainSecret []byte) ([]byte, error) { return cipherSecret, nil } -func ReadEncKey(filename string, plainSecret []byte) ([]byte, error) { - keyBytes, err := ioutil.ReadFile(filename) - if err != nil { +func GeneratePlainSecret() ([]byte, error) { + plainSecret := make([]byte, 16) + if _, err := rand.Read(plainSecret); err != nil { return nil, util.FmtNewtError( - "Error reading pubkey file: %s", err.Error()) + "Random generation error: %s\n", err) } + return plainSecret, nil +} + +func GenerateCipherSecret(pubKeBytes []byte, + plainSecret []byte) ([]byte, error) { + // Try reading as PEM (asymetric key). - cipherSecret, err := parseEncKeyPem(keyBytes, plainSecret) + rsaPubKe, err := ParsePubKePem(pubKeBytes) if err != nil { return nil, err } - if cipherSecret != nil { - return cipherSecret, nil + if rsaPubKe != nil { + return EncryptSecretRsa(rsaPubKe, plainSecret) } // Not PEM; assume this is a base64 encoded symetric key - cipherSecret, err = parseEncKeyBase64(keyBytes, plainSecret) + aesPubKe, err := ParseKeBase64(pubKeBytes) if err != nil { return nil, err } + if aesPubKe != nil { + return encryptSecretAes(aesPubKe, plainSecret) + } - return cipherSecret, nil + return nil, util.FmtNewtError("Invalid image-crypt key") } diff --git a/artifact/image/v1.go b/artifact/image/v1.go index 5540d85..0dc10a5 100644 --- a/artifact/image/v1.go +++ b/artifact/image/v1.go @@ -233,7 +233,7 @@ func generateV1SigTlvRsa(key ImageSigKey, hash []byte) (ImageTlv, error) { } func generateV1SigTlvEc(key ImageSigKey, hash []byte) (ImageTlv, error) { - sig, err := generateSigEc(key, hash) + sig, err := GenerateSigEc(key, hash) if err != nil { return ImageTlv{}, err } @@ -463,13 +463,17 @@ func GenerateV1Image(opts ImageCreateOpts) (ImageV1, error) { } if opts.SrcEncKeyFilename != "" { - plainSecret := make([]byte, 16) - if _, err := rand.Read(plainSecret); err != nil { - return ImageV1{}, util.FmtNewtError( - "Random generation error: %s\n", err) + plainSecret, err := GeneratePlainSecret() + if err != nil { + return ImageV1{}, err } - cipherSecret, err := ReadEncKey(opts.SrcEncKeyFilename, plainSecret) + pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename) + if err != nil { + return ImageV1{}, util.FmtNewtError( + "Error reading pubkey file: %s", err.Error()) + } + cipherSecret, err := GenerateCipherSecret(pubKeBytes, plainSecret) if err != nil { return ImageV1{}, err } diff --git a/artifact/mfg/mfg.go b/artifact/mfg/mfg.go index 8e999ad..3e29523 100644 --- a/artifact/mfg/mfg.go +++ b/artifact/mfg/mfg.go @@ -7,7 +7,7 @@ import ( ) const MFG_IMG_FILENAME = "mfgimg.bin" -const MFG_MANIFEST_FILENAME = "manifest.json" +const MANIFEST_FILENAME = "manifest.json" type Mfg struct { Bin []byte @@ -53,27 +53,6 @@ func AddPadding(b []byte, eraseVal byte, padLen int) []byte { return b } -func (m *Mfg) bytesZeroedHash(eraseVal byte) ([]byte, error) { - binCopy := make([]byte, len(m.Bin)) - copy(binCopy, m.Bin) - - m.Meta.ClearHash() - - metaBytes, err := m.Meta.Bytes() - if err != nil { - return nil, err - } - - padLen := m.MetaOff + len(metaBytes) - len(binCopy) - if padLen > 0 { - binCopy = AddPadding(binCopy, eraseVal, padLen) - } - - copy(binCopy[m.MetaOff:m.MetaOff+len(metaBytes)], metaBytes) - - return binCopy, nil -} - // Calculates the SHA256 hash, using the full manufacturing image as input. // Hash-calculation algorithm is as follows: // 1. Zero out the 32 bytes that will contain the hash. @@ -86,11 +65,16 @@ func CalcHash(bin []byte) []byte { return hash[:] } -func (m *Mfg) Bytes(eraseVal byte) ([]byte, error) { +func (m *Mfg) RecalcHash(eraseVal byte) error { + if m.Meta == nil || m.Meta.Hash() == nil { + return nil + } + // First, write with zeroed hash. - bin, err := m.bytesZeroedHash(eraseVal) + m.Meta.ClearHash() + bin, err := m.Bytes(eraseVal) if err != nil { - return nil, err + return err } // Calculate hash and fill TLV. @@ -101,13 +85,47 @@ func (m *Mfg) Bytes(eraseVal byte) ([]byte, error) { hashOff := m.MetaOff + m.Meta.HashOffset() if hashOff+META_HASH_SZ > len(bin) { - return nil, util.FmtNewtError( + return util.FmtNewtError( "unexpected error: hash extends beyond end " + "of manufacturing image") } + } + + return nil +} - copy(bin[hashOff:hashOff+META_HASH_SZ], tlv.Data) +func (m *Mfg) Hash() ([]byte, error) { + var hashBytes []byte + if m.Meta != nil { + hashBytes = m.Meta.Hash() + } + if hashBytes == nil { + // No hash TLV; calculate hash manually. + bin, err := m.Bytes(0xff) + if err != nil { + return nil, err + } + hashBytes = CalcHash(bin) } - return bin, nil + return hashBytes, nil +} + +func (m *Mfg) Bytes(eraseVal byte) ([]byte, error) { + binCopy := make([]byte, len(m.Bin)) + copy(binCopy, m.Bin) + + metaBytes, err := m.Meta.Bytes() + if err != nil { + return nil, err + } + + padLen := m.MetaOff + len(metaBytes) - len(binCopy) + if padLen > 0 { + binCopy = AddPadding(binCopy, eraseVal, padLen) + } + + copy(binCopy[m.MetaOff:m.MetaOff+len(metaBytes)], metaBytes) + + return binCopy, nil } diff --git a/artifact/mfg/paths.go b/artifact/misc/misc.go similarity index 70% rename from artifact/mfg/paths.go rename to artifact/misc/misc.go index 483aca2..2f685e1 100644 --- a/artifact/mfg/paths.go +++ b/artifact/misc/misc.go @@ -17,19 +17,12 @@ * under the License. */ -package mfg +package misc import ( "fmt" - "path/filepath" ) -const MANIFEST_FILENAME = "manifest.json" -const BOOT_DIR = "bootloader" -const BOOT_MANIFEST_PATH = BOOT_DIR + "/manifest.json" -const SECTION_BIN_DIR = "sections" - -func SectionBinPath(mfgPkgName string, sectionNum int) string { - return fmt.Sprintf("%s/%s-s%d.bin", SECTION_BIN_DIR, - filepath.Base(mfgPkgName), sectionNum) +func HashString(hash []byte) string { + return fmt.Sprintf("%x", hash) }