github.com/coreos/mantle@v0.13.0/update/payload.go (about) 1 // Copyright 2016 CoreOS, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package update 16 17 import ( 18 "encoding/binary" 19 "errors" 20 "fmt" 21 "hash" 22 "io" 23 "io/ioutil" 24 25 "github.com/golang/protobuf/proto" 26 27 "github.com/coreos/mantle/update/metadata" 28 "github.com/coreos/mantle/update/signature" 29 ) 30 31 var ( 32 InvalidMagic = errors.New("payload missing magic prefix") 33 InvalidVersion = errors.New("payload version unsupported") 34 InvalidBlockSize = errors.New("payload block size not 4096") 35 ) 36 37 const ( 38 // internal-only procedure type for mapping the special partition 39 // fields in DeltaArchiveManifest to the more generic data type. 40 installProcedure_partition metadata.InstallProcedure_Type = -1 41 ) 42 43 type Payload struct { 44 h hash.Hash 45 r io.Reader 46 47 // Offset is the number of bytes read from the payload, 48 // excluding the header and manifest. 49 Offset int64 50 51 // Parsed metadata contained in the payload. 52 Header metadata.DeltaArchiveHeader 53 Manifest metadata.DeltaArchiveManifest 54 Signatures metadata.Signatures 55 } 56 57 func NewPayloadFrom(r io.Reader) (*Payload, error) { 58 h := signature.NewSignatureHash() 59 p := &Payload{h: h, r: r} 60 61 if err := p.readHeader(); err != nil { 62 return nil, err 63 } 64 65 if err := p.readManifest(); err != nil { 66 return nil, err 67 } 68 69 // Reset offset to 0, all offset values in the manifest are 70 // relative to the end of the manifest within the payload. 71 p.Offset = 0 72 73 return p, nil 74 } 75 76 // Read reads from the raw payload stream, updating Hash and Offset for 77 // later verification. Behaves similarly to io.TeeReader. 78 func (p *Payload) Read(b []byte) (n int, err error) { 79 n, err = p.r.Read(b) 80 if n > 0 { 81 p.Offset += int64(n) 82 if n, err := p.h.Write(b[:n]); err != nil { 83 return n, err 84 } 85 } 86 return 87 } 88 89 // Sum returns the hash of the payload read so far. 90 func (p *Payload) Sum() []byte { 91 return p.h.Sum(nil) 92 } 93 94 func (p *Payload) readHeader() error { 95 if err := binary.Read(p, binary.BigEndian, &p.Header); err != nil { 96 return err 97 } 98 99 if string(p.Header.Magic[:]) != metadata.Magic { 100 return InvalidMagic 101 } 102 103 if p.Header.Version != metadata.Version { 104 return InvalidVersion 105 } 106 107 return nil 108 } 109 110 func (p *Payload) readManifest() error { 111 if p.Header.ManifestSize == 0 { 112 return fmt.Errorf("missing manifest") 113 } 114 115 buf := make([]byte, p.Header.ManifestSize) 116 if _, err := io.ReadFull(p, buf); err != nil { 117 return err 118 } 119 120 if err := proto.Unmarshal(buf, &p.Manifest); err != nil { 121 return err 122 } 123 124 if p.Manifest.GetBlockSize() != 4096 { 125 return InvalidBlockSize 126 } 127 128 return nil 129 } 130 131 // VerifySignature reads and checks for a valid signature. 132 func (p *Payload) VerifySignature() error { 133 if p.Manifest.GetSignaturesOffset() != uint64(p.Offset) { 134 return fmt.Errorf("expected signature offset %d, not %d", 135 p.Manifest.GetSignaturesOffset(), p.Offset) 136 } 137 138 // Get the final hash of the signed portion of the payload. 139 sum := p.Sum() 140 141 buf := make([]byte, p.Manifest.GetSignaturesSize()) 142 if _, err := io.ReadFull(p, buf); err != nil { 143 return err 144 } 145 146 if err := proto.Unmarshal(buf, &p.Signatures); err != nil { 147 return err 148 } 149 150 if err := signature.VerifySignature(sum, &p.Signatures); err != nil { 151 return err 152 } 153 154 // There shouldn't be any extra data following the signatures. 155 if n, err := io.Copy(ioutil.Discard, p); err != nil { 156 return fmt.Errorf("trailing read failure: %v", err) 157 } else if n != 0 { 158 return fmt.Errorf("found %d trailing bytes", n) 159 } 160 161 return nil 162 } 163 164 func (p *Payload) Procedures() []*metadata.InstallProcedure { 165 procs := []*metadata.InstallProcedure{ 166 &metadata.InstallProcedure{ 167 Type: installProcedure_partition.Enum(), 168 Operations: p.Manifest.PartitionOperations, 169 OldInfo: p.Manifest.OldPartitionInfo, 170 NewInfo: p.Manifest.NewPartitionInfo, 171 }, 172 } 173 return append(procs, p.Manifest.Procedures...) 174 } 175 176 func (p *Payload) Operations(proc *metadata.InstallProcedure) []*Operation { 177 ops := make([]*Operation, len(proc.Operations)) 178 for i, op := range proc.Operations { 179 ops[i] = NewOperation(p, proc, op) 180 } 181 return ops 182 } 183 184 // Verify reads the entire payload and checks it for errors. 185 func (p *Payload) Verify() error { 186 progress := 0 187 for _, proc := range p.Procedures() { 188 for _, op := range p.Operations(proc) { 189 progress++ 190 if err := op.Verify(); err != nil { 191 return fmt.Errorf("operation %d: %v\n%s", 192 progress, err, 193 proto.MarshalTextString(op.Operation)) 194 } 195 } 196 } 197 198 return p.VerifySignature() 199 }