fleet/pkg/file/xar.go
Roberto Dip 12b79d5e46
add pkg to inspect xar files (#11015)
Part of #10213 this will be used to do various validations in the
provided bootstrap package.
2023-04-05 20:49:02 -03:00

149 lines
3.5 KiB
Go

package file
// Copyright 2023 SAS Software
//
// Licensed 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.
//
// xar contains utilities to parse xar files, most of the logic here is a
// simplified version extracted from the logic to sign xar files in
// https://github.com/sassoftware/relic
import (
"bytes"
"compress/zlib"
"crypto"
"encoding/binary"
"encoding/xml"
"errors"
"fmt"
"io"
)
// xarMagic is the [file signature][1] (or magic bytes) for xar
//
// [1]: https://en.wikipedia.org/wiki/List_of_file_signatures
const xarMagic = 0x78617221
const (
hashNone uint32 = iota
hashSHA1
hashMD5
hashSHA256
hashSHA512
)
var (
// ErrInvalidType is used to signal that the provided package can't be
// parsed because is an invalid file type.
ErrInvalidType = errors.New("invalid file type")
// ErrNotSigned is used to signal that the provided package doesn't
// contain a signature.
ErrNotSigned = errors.New("file is not signed")
)
type xarHeader struct {
Magic uint32
HeaderSize uint16
Version uint16
CompressedSize int64
UncompressedSize int64
HashType uint32
}
type tocXar struct {
TOC toc `xml:"toc"`
}
type toc struct {
Signature *any `xml:"signature"`
XSignature *any `xml:"x-signature"`
}
// CheckPKGSignature checks if the provided bytes correspond to a signed pkg
// (xar) file.
//
// - If the file is not xar, it returns a ErrInvalidType error
// - If the file is not signed, it returns a ErrNotSigned error
func CheckPKGSignature(pkg io.Reader) error {
buff := bytes.NewBuffer(nil)
if _, err := io.Copy(buff, pkg); err != nil {
return err
}
r := bytes.NewReader(buff.Bytes())
hdr, hashType, err := parseHeader(io.NewSectionReader(r, 0, 28))
if err != nil {
return err
}
base := int64(hdr.HeaderSize)
toc, err := parseTOC(io.NewSectionReader(r, base, hdr.CompressedSize), hashType)
if err != nil {
return err
}
if toc.Signature == nil && toc.XSignature == nil {
return ErrNotSigned
}
return nil
}
func decompress(r io.Reader) ([]byte, error) {
zr, err := zlib.NewReader(r)
if err != nil {
return nil, err
}
defer zr.Close()
return io.ReadAll(zr)
}
func parseTOC(r io.Reader, hashType crypto.Hash) (*toc, error) {
tocHash := hashType.New()
r = io.TeeReader(r, tocHash)
decomp, err := decompress(r)
if err != nil {
return nil, fmt.Errorf("decompressing TOC: %w", err)
}
var toc tocXar
if err := xml.Unmarshal(decomp, &toc); err != nil {
return nil, fmt.Errorf("decoding TOC: %w", err)
}
return &toc.TOC, nil
}
func parseHeader(r io.Reader) (xarHeader, crypto.Hash, error) {
var hdr xarHeader
if err := binary.Read(r, binary.BigEndian, &hdr); err != nil {
return xarHeader{}, 0, err
}
if hdr.Magic != xarMagic {
return hdr, 0, ErrInvalidType
}
var hashType crypto.Hash
switch hdr.HashType {
case hashSHA1:
hashType = crypto.SHA1
case hashSHA256:
hashType = crypto.SHA256
case hashSHA512:
hashType = crypto.SHA512
default:
return xarHeader{}, 0, fmt.Errorf("unknown hash algorithm %d", hdr.HashType)
}
return hdr, hashType, nil
}