// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. // Copyright (c) 2017, SingularityWare, LLC. All rights reserved. // Copyright (c) 2017, Yannick Cote All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE file distributed with the sources of this project regarding your // rights to use or distribute this software. // Package sif implements data structures and routines to create // and access SIF files. // // Layout of a SIF file (example): // // .================================================. // | GLOBAL HEADER: Sifheader | // | - launch: "#!/usr/bin/env..." | // | - magic: "SIF_MAGIC" | // | - version: "1" | // | - arch: "4" | // | - uuid: b2659d4e-bd50-4ea5-bd17-eec5e54f918e | // | - ctime: 1504657553 | // | - mtime: 1504657653 | // | - ndescr: 3 | // | - descroff: 120 | --. // | - descrlen: 432 | | // | - dataoff: 4096 | | // | - datalen: 619362 | | // |------------------------------------------------| <-' // | DESCR[0]: Sifdeffile | // | - Sifcommon | // | - datatype: DATA_DEFFILE | // | - id: 1 | // | - groupid: 1 | // | - link: NONE | // | - fileoff: 4096 | --. // | - filelen: 222 | | // |------------------------------------------------| <-----. // | DESCR[1]: Sifpartition | | | // | - Sifcommon | | | // | - datatype: DATA_PARTITION | | | // | - id: 2 | | | // | - groupid: 1 | | | // | - link: NONE | | | // | - fileoff: 4318 | ----. | // | - filelen: 618496 | | | | // | - fstype: Squashfs | | | | // | - parttype: System | | | | // | - content: Linux | | | | // |------------------------------------------------| | | | // | DESCR[2]: Sifsignature | | | | // | - Sifcommon | | | | // | - datatype: DATA_SIGNATURE | | | | // | - id: 3 | | | | // | - groupid: NONE | | | | // | - link: 2 | ------' // | - fileoff: 622814 | ------. // | - filelen: 644 | | | | // | - hashtype: SHA384 | | | | // | - entity: @ | | | | // |------------------------------------------------| <-' | | // | Definition file data | | | // | . | | | // | . | | | // | . | | | // |------------------------------------------------| <---' | // | File system partition image | | // | . | | // | . | | // | . | | // |------------------------------------------------| <-----' // | Signed verification data | // | . | // | . | // | . | // `================================================' package sif import ( "bytes" "fmt" "io" "time" "github.com/google/uuid" ) // SIF header constants and quantities. const ( hdrLaunchLen = 32 // len("#!/usr/bin/env... ") hdrMagicLen = 10 // len("SIF_MAGIC") hdrVersionLen = 3 // len("99") ) var hdrMagic = [...]byte{'S', 'I', 'F', '_', 'M', 'A', 'G', 'I', 'C', '\x00'} // SpecVersion specifies a SIF specification version. type SpecVersion uint8 func (v SpecVersion) String() string { return fmt.Sprintf("%02d", v) } // bytes returns the value of b, formatted for direct inclusion in a SIF header. func (v SpecVersion) bytes() [hdrVersionLen]byte { var b [3]byte copy(b[:], fmt.Sprintf("%02d", v)) return b } // SIF specification versions. const ( version01 SpecVersion = iota + 1 ) // CurrentVersion specifies the current SIF specification version. const CurrentVersion = version01 const ( descrGroupMask = 0xf0000000 // groups start at that offset descrEntityLen = 256 // len("Joe Bloe ...") descrNameLen = 128 // descriptor name (string identifier) descrMaxPrivLen = 384 // size reserved for descriptor specific data ) // DataType represents the different SIF data object types stored in the image. type DataType int32 // List of supported SIF data types. const ( DataDeffile DataType = iota + 0x4001 // definition file data object DataEnvVar // environment variables data object DataLabels // JSON labels data object DataPartition // file system data object DataSignature // signing/verification data object DataGenericJSON // generic JSON meta-data DataGeneric // generic / raw data DataCryptoMessage // cryptographic message data object DataSBOM // software bill of materials ) // String returns a human-readable representation of t. func (t DataType) String() string { switch t { case DataDeffile: return "Def.FILE" case DataEnvVar: return "Env.Vars" case DataLabels: return "JSON.Labels" case DataPartition: return "FS" case DataSignature: return "Signature" case DataGenericJSON: return "JSON.Generic" case DataGeneric: return "Generic/Raw" case DataCryptoMessage: return "Cryptographic Message" case DataSBOM: return "SBOM" } return "Unknown" } // FSType represents the different SIF file system types found in partition data objects. type FSType int32 // List of supported file systems. const ( FsSquash FSType = iota + 1 // Squashfs file system, RDONLY FsExt3 // EXT3 file system, RDWR (deprecated) FsImmuObj // immutable data object archive FsRaw // raw data FsEncryptedSquashfs // Encrypted Squashfs file system, RDONLY ) // String returns a human-readable representation of t. func (t FSType) String() string { switch t { case FsSquash: return "Squashfs" case FsExt3: return "Ext3" case FsImmuObj: return "Archive" case FsRaw: return "Raw" case FsEncryptedSquashfs: return "Encrypted squashfs" } return "Unknown" } // PartType represents the different SIF container partition types (system and data). type PartType int32 // List of supported partition types. const ( PartSystem PartType = iota + 1 // partition hosts an operating system PartPrimSys // partition hosts the primary operating system PartData // partition hosts data only PartOverlay // partition hosts an overlay ) // String returns a human-readable representation of t. func (t PartType) String() string { switch t { case PartSystem: return "System" case PartPrimSys: return "*System" case PartData: return "Data" case PartOverlay: return "Overlay" } return "Unknown" } // hashType represents the different SIF hashing function types used to fingerprint data objects. type hashType int32 // List of supported hash functions. const ( hashSHA256 hashType = iota + 1 hashSHA384 hashSHA512 hashBLAKE2S hashBLAKE2B ) // FormatType represents the different formats used to store cryptographic message objects. type FormatType int32 // List of supported cryptographic message formats. const ( FormatOpenPGP FormatType = iota + 1 FormatPEM ) // String returns a human-readable representation of t. func (t FormatType) String() string { switch t { case FormatOpenPGP: return "OpenPGP" case FormatPEM: return "PEM" } return "Unknown" } // MessageType represents the different messages stored within cryptographic message objects. type MessageType int32 // List of supported cryptographic message formats. const ( // openPGP formatted messages. MessageClearSignature MessageType = 0x100 // PEM formatted messages. MessageRSAOAEP MessageType = 0x200 ) // String returns a human-readable representation of t. func (t MessageType) String() string { switch t { case MessageClearSignature: return "Clear Signature" case MessageRSAOAEP: return "RSA-OAEP" } return "Unknown" } // SBOMFormat represents the format used to store an SBOM object. type SBOMFormat int32 // List of supported SBOM formats. const ( SBOMFormatCycloneDXJSON SBOMFormat = iota + 1 // CycloneDX (JSON) SBOMFormatCycloneDXXML // CycloneDX (XML) SBOMFormatGitHubJSON // GitHub dependency snapshot (JSON) SBOMFormatSPDXJSON // SPDX (JSON) SBOMFormatSPDXRDF // SPDX (RDF/xml) SBOMFormatSPDXTagValue // SPDX (tag/value) SBOMFormatSPDXYAML // SPDX (YAML) SBOMFormatSyftJSON // Syft (JSON) ) // String returns a human-readable representation of f. func (f SBOMFormat) String() string { switch f { case SBOMFormatCycloneDXJSON: return "cyclonedx-json" case SBOMFormatCycloneDXXML: return "cyclonedx-xml" case SBOMFormatGitHubJSON: return "github-json" case SBOMFormatSPDXJSON: return "spdx-json" case SBOMFormatSPDXRDF: return "spdx-rdf" case SBOMFormatSPDXTagValue: return "spdx-tag-value" case SBOMFormatSPDXYAML: return "spdx-yaml" case SBOMFormatSyftJSON: return "syft-json" } return "unknown" } // header describes a loaded SIF file. type header struct { LaunchScript [hdrLaunchLen]byte Magic [hdrMagicLen]byte Version [hdrVersionLen]byte Arch archType ID uuid.UUID CreatedAt int64 ModifiedAt int64 DescriptorsFree int64 DescriptorsTotal int64 DescriptorsOffset int64 DescriptorsSize int64 DataOffset int64 DataSize int64 } // GetIntegrityReader returns an io.Reader that reads the integrity-protected fields from h. func (h header) GetIntegrityReader() io.Reader { return io.MultiReader( bytes.NewReader(h.LaunchScript[:]), bytes.NewReader(h.Magic[:]), bytes.NewReader(h.Version[:]), bytes.NewReader(h.ID[:]), ) } // ReadWriter describes the interface required to read and write SIF images. type ReadWriter interface { io.ReaderAt io.WriteSeeker Truncate(int64) error } // FileImage describes the representation of a SIF file in memory. type FileImage struct { rw ReadWriter // Backing storage for image. h header // Raw global header from image. rds []rawDescriptor // Raw descriptors from image. closeOnUnload bool // Close rw on Unload. minIDs map[uint32]uint32 // Minimum object IDs for each group ID. } // LaunchScript returns the image launch script. func (f *FileImage) LaunchScript() string { return string(bytes.TrimRight(f.h.LaunchScript[:], "\x00")) } // Version returns the SIF specification version of the image. func (f *FileImage) Version() string { return string(bytes.TrimRight(f.h.Version[:], "\x00")) } // PrimaryArch returns the primary CPU architecture of the image, or "unknown" if the primary CPU // architecture cannot be determined. func (f *FileImage) PrimaryArch() string { return f.h.Arch.GoArch() } // ID returns the ID of the image. func (f *FileImage) ID() string { return f.h.ID.String() } // CreatedAt returns the creation time of the image. func (f *FileImage) CreatedAt() time.Time { return time.Unix(f.h.CreatedAt, 0) } // ModifiedAt returns the last modification time of the image. func (f *FileImage) ModifiedAt() time.Time { return time.Unix(f.h.ModifiedAt, 0) } // DescriptorsFree returns the number of free descriptors in the image. func (f *FileImage) DescriptorsFree() int64 { return f.h.DescriptorsFree } // DescriptorsTotal returns the total number of descriptors in the image. func (f *FileImage) DescriptorsTotal() int64 { return f.h.DescriptorsTotal } // DescriptorsOffset returns the offset (in bytes) of the descriptors section in the image. func (f *FileImage) DescriptorsOffset() int64 { return f.h.DescriptorsOffset } // DescriptorsSize returns the size (in bytes) of the descriptors section in the image. func (f *FileImage) DescriptorsSize() int64 { return f.h.DescriptorsSize } // DataOffset returns the offset (in bytes) of the data section in the image. func (f *FileImage) DataOffset() int64 { return f.h.DataOffset } // DataSize returns the size (in bytes) of the data section in the image. func (f *FileImage) DataSize() int64 { return f.h.DataSize } // GetHeaderIntegrityReader returns an io.Reader that reads the integrity-protected fields from the // header of the image. func (f *FileImage) GetHeaderIntegrityReader() io.Reader { return f.h.GetIntegrityReader() }