| /** |
| * 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. |
| */ |
| |
| // This file implements parsing and generation of version-1 images. Much of |
| // this code duplicates the v2 code. The expectation is that this file will be |
| // removed when version 1 is oficially retired (soon). |
| |
| package image |
| |
| import ( |
| "bytes" |
| "crypto" |
| "crypto/rand" |
| "crypto/rsa" |
| "crypto/sha256" |
| "encoding/binary" |
| "io" |
| "io/ioutil" |
| |
| "github.com/apache/mynewt-artifact/errors" |
| "github.com/apache/mynewt-artifact/sec" |
| ) |
| |
| const IMAGEv1_MAGIC = 0x96f3b83c /* Image header magic */ |
| |
| const ( |
| IMAGEv1_F_PIC = 0x00000001 |
| IMAGEv1_F_SHA256 = 0x00000002 /* Image contains hash TLV */ |
| IMAGEv1_F_PKCS15_RSA2048_SHA256 = 0x00000004 /* PKCS15 w/RSA2048 and SHA256 */ |
| IMAGEv1_F_ECDSA224_SHA256 = 0x00000008 /* ECDSA224 over SHA256 */ |
| IMAGEv1_F_NON_BOOTABLE = 0x00000010 /* non bootable image */ |
| IMAGEv1_F_ECDSA256_SHA256 = 0x00000020 /* ECDSA256 over SHA256 */ |
| IMAGEv1_F_PKCS1_PSS_RSA2048_SHA256 = 0x00000040 /* RSA-PSS w/RSA2048 and SHA256 */ |
| ) |
| |
| const ( |
| IMAGEv1_TLV_SHA256 = 1 |
| IMAGEv1_TLV_RSA2048 = 2 |
| IMAGEv1_TLV_ECDSA224 = 3 |
| IMAGEv1_TLV_ECDSA256 = 4 |
| ) |
| |
| // Set this to enable RSA-PSS for RSA signatures, instead of PKCS#1 |
| // v1.5. Eventually, this should be the default. |
| var UseRsaPss = false |
| |
| type ImageHdrV1 struct { |
| Magic uint32 |
| TlvSz uint16 |
| KeyId uint8 |
| Pad1 uint8 |
| HdrSz uint16 |
| Pad2 uint16 |
| ImgSz uint32 |
| Flags uint32 |
| Vers ImageVersion |
| Pad3 uint32 |
| } |
| |
| type ImageV1 struct { |
| Header ImageHdrV1 |
| Body []byte |
| Tlvs []ImageTlv |
| } |
| |
| func (img *ImageV1) FindTlvs(tlvType uint8) []ImageTlv { |
| var tlvs []ImageTlv |
| |
| for _, tlv := range img.Tlvs { |
| if tlv.Header.Type == tlvType { |
| tlvs = append(tlvs, tlv) |
| } |
| } |
| |
| return tlvs |
| } |
| |
| func (img *ImageV1) Hash() ([]byte, error) { |
| tlvs := img.FindTlvs(IMAGEv1_TLV_SHA256) |
| if len(tlvs) == 0 { |
| return nil, errors.Errorf("image does not contain hash TLV") |
| } |
| if len(tlvs) > 1 { |
| return nil, errors.Errorf("image contains %d hash TLVs", len(tlvs)) |
| } |
| |
| return tlvs[0].Data, nil |
| } |
| |
| func (img *ImageV1) WritePlusOffsets(w io.Writer) (ImageOffsets, error) { |
| offs := ImageOffsets{} |
| offset := 0 |
| |
| offs.Header = offset |
| |
| err := binary.Write(w, binary.LittleEndian, &img.Header) |
| if err != nil { |
| return offs, errors.Wrapf(err, "failed to write image header") |
| } |
| offset += IMAGE_HEADER_SIZE |
| |
| offs.Body = offset |
| size, err := w.Write(img.Body) |
| if err != nil { |
| return offs, errors.Wrapf(err, "failed to write image body") |
| } |
| offset += size |
| |
| for _, tlv := range img.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 |
| } |
| |
| func (img *ImageV1) Offsets() (ImageOffsets, error) { |
| return img.WritePlusOffsets(ioutil.Discard) |
| } |
| |
| func (img *ImageV1) TotalSize() (int, error) { |
| offs, err := img.Offsets() |
| if err != nil { |
| return 0, err |
| } |
| return offs.TotalSize, nil |
| } |
| |
| func (img *ImageV1) Write(w io.Writer) (int, error) { |
| offs, err := img.WritePlusOffsets(w) |
| if err != nil { |
| return 0, err |
| } |
| |
| return offs.TotalSize, nil |
| } |
| |
| func sigHdrTypeV1(key sec.PrivSignKey) (uint32, error) { |
| key.AssertValid() |
| |
| if key.Rsa != nil { |
| if UseRsaPss { |
| return IMAGEv1_F_PKCS1_PSS_RSA2048_SHA256, nil |
| } else { |
| return IMAGEv1_F_PKCS15_RSA2048_SHA256, nil |
| } |
| } else { |
| switch key.Ec.Curve.Params().Name { |
| case "P-224": |
| return IMAGEv1_F_ECDSA224_SHA256, nil |
| case "P-256": |
| return IMAGEv1_F_ECDSA256_SHA256, nil |
| default: |
| return 0, errors.Errorf("unsupported ECC curve") |
| } |
| } |
| } |
| |
| func sigTlvTypeV1(key sec.PrivSignKey) uint8 { |
| key.AssertValid() |
| |
| if key.Rsa != nil { |
| return IMAGEv1_TLV_RSA2048 |
| } else { |
| switch key.Ec.Curve.Params().Name { |
| case "P-224": |
| return IMAGEv1_TLV_ECDSA224 |
| case "P-256": |
| return IMAGEv1_TLV_ECDSA256 |
| default: |
| return 0 |
| } |
| } |
| } |
| |
| func generateV1SigRsa(key *rsa.PrivateKey, hash []byte) ([]byte, error) { |
| var signature []byte |
| var err error |
| |
| if UseRsaPss { |
| opts := rsa.PSSOptions{ |
| SaltLength: rsa.PSSSaltLengthEqualsHash, |
| } |
| signature, err = rsa.SignPSS( |
| rand.Reader, key, crypto.SHA256, hash, &opts) |
| } else { |
| signature, err = rsa.SignPKCS1v15( |
| rand.Reader, key, crypto.SHA256, hash) |
| } |
| if err != nil { |
| return nil, errors.Wrapf(err, "failed to compute signature") |
| } |
| |
| return signature, nil |
| } |
| |
| func generateV1SigTlvRsa(key sec.PrivSignKey, hash []byte) (ImageTlv, error) { |
| sig, err := generateV1SigRsa(key.Rsa, hash) |
| if err != nil { |
| return ImageTlv{}, err |
| } |
| |
| return ImageTlv{ |
| Header: ImageTlvHdr{ |
| Type: sigTlvTypeV1(key), |
| Pad: 0, |
| Len: 256, /* 2048 bits */ |
| }, |
| Data: sig, |
| }, nil |
| } |
| |
| func generateV1SigTlvEc(key sec.PrivSignKey, hash []byte) (ImageTlv, error) { |
| sig, err := GenerateSigEc(key, hash) |
| if err != nil { |
| return ImageTlv{}, err |
| } |
| |
| sigLen := key.SigLen() |
| if len(sig) > int(sigLen) { |
| return ImageTlv{}, errors.Errorf("signature truncated") |
| } |
| |
| b := &bytes.Buffer{} |
| |
| if _, err := b.Write(sig); err != nil { |
| return ImageTlv{}, errors.Wrapf(err, "failed to append sig") |
| } |
| |
| pad := make([]byte, int(sigLen)-len(sig)) |
| if _, err := b.Write(pad); err != nil { |
| return ImageTlv{}, errors.Wrapf(err, |
| "failed to serialize image trailer") |
| } |
| |
| return ImageTlv{ |
| Header: ImageTlvHdr{ |
| Type: sigTlvTypeV1(key), |
| Pad: 0, |
| Len: sigLen + uint16(len(pad)), |
| }, |
| Data: b.Bytes(), |
| }, nil |
| } |
| |
| func generateV1SigTlv(key sec.PrivSignKey, hash []byte) (ImageTlv, error) { |
| key.AssertValid() |
| |
| if key.Rsa != nil { |
| return generateV1SigTlvRsa(key, hash) |
| } else { |
| return generateV1SigTlvEc(key, hash) |
| } |
| } |
| |
| func calcHashV1(initialHash []byte, hdr ImageHdrV1, |
| plainBody []byte) ([]byte, error) { |
| |
| hash := sha256.New() |
| |
| add := func(itf interface{}) error { |
| if err := binary.Write(hash, binary.LittleEndian, itf); err != nil { |
| return errors.Wrapf(err, "failed to hash data") |
| } |
| |
| return nil |
| } |
| |
| if initialHash != nil { |
| if err := add(initialHash); err != nil { |
| return nil, err |
| } |
| } |
| |
| if err := add(hdr); err != nil { |
| return nil, err |
| } |
| |
| extra := hdr.HdrSz - IMAGE_HEADER_SIZE |
| if extra > 0 { |
| b := make([]byte, extra) |
| if err := add(b); err != nil { |
| return nil, err |
| } |
| } |
| |
| if err := add(plainBody); err != nil { |
| return nil, err |
| } |
| |
| return hash.Sum(nil), nil |
| } |
| |
| func (ic *ImageCreator) CreateV1() (ImageV1, error) { |
| ri := ImageV1{} |
| |
| if len(ic.SigKeys) > 1 { |
| return ri, errors.Errorf( |
| "v1 image format only allows one key, %d keys specified", |
| len(ic.SigKeys)) |
| } |
| |
| // First the header |
| hdr := ImageHdrV1{ |
| Magic: IMAGEv1_MAGIC, |
| TlvSz: 0, // Filled in later. |
| KeyId: 0, |
| Pad1: 0, |
| HdrSz: IMAGE_HEADER_SIZE, |
| Pad2: 0, |
| ImgSz: uint32(len(ic.Body)), |
| Flags: IMAGEv1_F_SHA256, |
| Vers: ic.Version, |
| Pad3: 0, |
| } |
| |
| if !ic.Bootable { |
| hdr.Flags |= IMAGEv1_F_NON_BOOTABLE |
| } |
| |
| 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. |
| */ |
| if ic.HeaderSize < IMAGE_HEADER_SIZE { |
| return ri, errors.Errorf( |
| "image header must be at least %d bytes", IMAGE_HEADER_SIZE) |
| } |
| |
| hdr.HdrSz = uint16(ic.HeaderSize) |
| } |
| |
| if len(ic.SigKeys) > 0 { |
| keyFlag, err := sigHdrTypeV1(ic.SigKeys[0]) |
| if err != nil { |
| return ri, err |
| } |
| hdr.Flags |= keyFlag |
| hdr.TlvSz = 4 + ic.SigKeys[0].SigLen() |
| } |
| hdr.TlvSz += 4 + 32 |
| |
| if hdr.HdrSz > IMAGE_HEADER_SIZE { |
| // 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, errors.Errorf( |
| "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) |
| } |
| } |
| |
| hashBytes, err := calcHashV1(ic.InitialHash, hdr, ic.Body) |
| if err != nil { |
| return ri, err |
| } |
| |
| /* |
| * Followed by data. |
| */ |
| dataBuf := make([]byte, 1024) |
| r := bytes.NewReader(ic.Body) |
| w := bytes.Buffer{} |
| for { |
| cnt, err := r.Read(dataBuf) |
| if err != nil && err != io.EOF { |
| return ri, errors.Wrapf(err, "failed to read from image body") |
| } |
| if cnt == 0 { |
| break |
| } |
| |
| if _, err = w.Write(dataBuf[0:cnt]); err != nil { |
| return ri, errors.Wrapf(err, "failed to write to image body") |
| } |
| } |
| ri.Body = w.Bytes() |
| |
| // Hash TLV. |
| tlv := ImageTlv{ |
| Header: ImageTlvHdr{ |
| Type: IMAGEv1_TLV_SHA256, |
| Pad: 0, |
| Len: uint16(len(hashBytes)), |
| }, |
| Data: hashBytes, |
| } |
| ri.Tlvs = append(ri.Tlvs, tlv) |
| |
| if len(ic.SigKeys) > 0 { |
| tlv, err := generateV1SigTlv(ic.SigKeys[0], hashBytes) |
| if err != nil { |
| return ri, err |
| } |
| ri.Tlvs = append(ri.Tlvs, tlv) |
| } |
| |
| offs, err := ri.Offsets() |
| if err != nil { |
| return ri, err |
| } |
| hdr.TlvSz = uint16(offs.TotalSize - offs.Tlvs[0]) |
| |
| ri.Header = hdr |
| |
| return ri, nil |
| } |
| |
| func GenerateV1Image(opts ImageCreateOpts) (ImageV1, error) { |
| ic := NewImageCreator() |
| |
| srcBin, err := ioutil.ReadFile(opts.SrcBinFilename) |
| if err != nil { |
| return ImageV1{}, errors.Wrapf(err, "can't read app binary") |
| } |
| |
| ic.Body = srcBin |
| ic.Version = opts.Version |
| ic.SigKeys = opts.SigKeys |
| |
| if opts.LoaderHash != nil { |
| ic.InitialHash = opts.LoaderHash |
| ic.Bootable = false |
| } else { |
| ic.Bootable = true |
| } |
| |
| if opts.SrcEncKeyFilename != "" { |
| plainSecret, err := GeneratePlainSecret() |
| if err != nil { |
| return ImageV1{}, err |
| } |
| |
| pubKeBytes, err := ioutil.ReadFile(opts.SrcEncKeyFilename) |
| if err != nil { |
| return ImageV1{}, errors.Wrapf(err, "error reading pubkey file") |
| } |
| |
| pubKe, err := sec.ParsePubEncKey(pubKeBytes) |
| if err != nil { |
| return ImageV1{}, err |
| } |
| |
| cipherSecret, err := pubKe.Encrypt(plainSecret) |
| if err != nil { |
| return ImageV1{}, err |
| } |
| |
| ic.PlainSecret = plainSecret |
| ic.CipherSecret = cipherSecret |
| } |
| |
| ri, err := ic.CreateV1() |
| if err != nil { |
| return ImageV1{}, err |
| } |
| |
| return ri, nil |
| } |