summaryrefslogtreecommitdiff
path: root/pkg/sif/sif.go
blob: 2d1c2091dc28076e0041ce3a9f2d64cf354c2f6f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved.
// Copyright (c) 2017, SingularityWare, LLC. All rights reserved.
// Copyright (c) 2017, Yannick Cote <yhcote@gmail.com> 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 <jbloe@gmail.com>...")
	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()
}