diff --git a/pkg/integrity/sign.go b/pkg/integrity/sign.go index d38d74f7..749bca5a 100644 --- a/pkg/integrity/sign.go +++ b/pkg/integrity/sign.go @@ -310,16 +310,19 @@ type Signer struct { // By default, one digital signature is added per object group in f. To override this behavior, // consider using OptSignGroup and/or OptSignObjects. // -// By default, signature, header and descriptor timestamps are set to the current time. To override -// this behavior, consider using OptSignWithTime or OptSignDeterministic. +// By default, signature timestamps are set to the current time. To override this behavior, +// consider using OptSignWithTime. +// +// By default, header and descriptor timestamps are set to the current time for non-deterministic +// images, and unset otherwise. To override this behavior, consider using OptSignWithTime or +// OptSignDeterministic. func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) { if f == nil { return nil, fmt.Errorf("integrity: %w", errNilFileImage) } so := signOpts{ - timeFunc: time.Now, - ctx: context.Background(), + ctx: context.Background(), } // Apply options. @@ -346,7 +349,11 @@ func NewSigner(f *sif.FileImage, opts ...SignerOpt) (*Signer, error) { return nil, fmt.Errorf("integrity: %w", err) } case so.e != nil: - en = newClearsignEncoder(so.e, so.timeFunc) + timeFunc := time.Now + if so.timeFunc != nil { + timeFunc = so.timeFunc + } + en = newClearsignEncoder(so.e, timeFunc) commonOpts = append(commonOpts, optSignGroupFingerprint(so.e.PrimaryKey.Fingerprint)) default: return nil, fmt.Errorf("integrity: %w", ErrNoKeyMaterial) @@ -410,7 +417,7 @@ func (s *Signer) Sign() error { var opts []sif.AddOpt if s.opts.deterministic { opts = append(opts, sif.OptAddDeterministic()) - } else { + } else if s.opts.timeFunc != nil { opts = append(opts, sif.OptAddWithTime(s.opts.timeFunc())) } diff --git a/pkg/sif/create.go b/pkg/sif/create.go index 4d6cbb64..8f90adfe 100644 --- a/pkg/sif/create.go +++ b/pkg/sif/create.go @@ -251,7 +251,7 @@ func createContainer(rw ReadWriter, co createOpts) (*FileImage, error) { // By default, the image ID is set to a randomly generated value. To override this, consider using // OptCreateDeterministic or OptCreateWithID. // -// By default, the image creation time is set to time.Now(). To override this, consider using +// By default, the image creation time is set to the current time. To override this, consider using // OptCreateDeterministic or OptCreateWithTime. // // By default, the image will support a maximum of 48 descriptors. To change this, consider using @@ -296,7 +296,7 @@ func CreateContainer(rw ReadWriter, opts ...CreateOpt) (*FileImage, error) { // By default, the image ID is set to a randomly generated value. To override this, consider using // OptCreateDeterministic or OptCreateWithID. // -// By default, the image creation time is set to time.Now(). To override this, consider using +// By default, the image creation time is set to the current time. To override this, consider using // OptCreateDeterministic or OptCreateWithTime. // // By default, the image will support a maximum of 48 descriptors. To change this, consider using @@ -393,11 +393,13 @@ func OptAddWithTime(t time.Time) AddOpt { // AddObject adds a new data object and its descriptor into the specified SIF file. // -// By default, the image modification time is set to the current time. To override this, consider -// using OptAddDeterministic or OptAddWithTime. +// By default, the image modification time is set to the current time for non-deterministic images, +// and unset otherwise. To override this, consider using OptAddDeterministic or OptAddWithTime. func (f *FileImage) AddObject(di DescriptorInput, opts ...AddOpt) error { - ao := addOpts{ - t: time.Now(), + ao := addOpts{} + + if !f.isDeterministic() { + ao.t = time.Now() } for _, opt := range opts { @@ -506,11 +508,14 @@ var errCompactNotImplemented = errors.New("compact not implemented for non-last // To zero the data region of the deleted object, use OptDeleteZero. To compact the file following // object deletion, use OptDeleteCompact. // -// By default, the image modification time is set to time.Now(). To override this, consider using -// OptDeleteDeterministic or OptDeleteWithTime. +// By default, the image modification time is set to the current time for non-deterministic images, +// and unset otherwise. To override this, consider using OptDeleteDeterministic or +// OptDeleteWithTime. func (f *FileImage) DeleteObject(id uint32, opts ...DeleteOpt) error { - do := deleteOpts{ - t: time.Now(), + do := deleteOpts{} + + if !f.isDeterministic() { + do.t = time.Now() } for _, opt := range opts { @@ -596,11 +601,14 @@ var ( // SetPrimPart sets the specified system partition to be the primary one. // -// By default, the image/object modification times are set to time.Now(). To override this, -// consider using OptSetDeterministic or OptSetWithTime. +// By default, the image/object modification times are set to the current time for +// non-deterministic images, and unset otherwise. To override this, consider using +// OptSetDeterministic or OptSetWithTime. func (f *FileImage) SetPrimPart(id uint32, opts ...SetOpt) error { - so := setOpts{ - t: time.Now(), + so := setOpts{} + + if !f.isDeterministic() { + so.t = time.Now() } for _, opt := range opts { diff --git a/pkg/sif/create_test.go b/pkg/sif/create_test.go index 94722a4b..917ecee3 100644 --- a/pkg/sif/create_test.go +++ b/pkg/sif/create_test.go @@ -319,6 +319,17 @@ func TestAddObject(t *testing.T) { ), wantErr: errPrimaryPartition, }, + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + opts: []AddOpt{ + OptAddDeterministic(), + }, + }, { name: "WithTime", createOpts: []CreateOpt{ @@ -335,9 +346,6 @@ func TestAddObject(t *testing.T) { OptCreateDeterministic(), }, di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), - opts: []AddOpt{ - OptAddDeterministic(), - }, }, { name: "EmptyNotAligned", @@ -347,9 +355,6 @@ func TestAddObject(t *testing.T) { di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(0), ), - opts: []AddOpt{ - OptAddDeterministic(), - }, }, { name: "EmptyAligned", @@ -359,9 +364,6 @@ func TestAddObject(t *testing.T) { di: getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}, OptObjectAlignment(128), ), - opts: []AddOpt{ - OptAddDeterministic(), - }, }, { name: "NotEmpty", @@ -374,9 +376,6 @@ func TestAddObject(t *testing.T) { di: getDescriptorInput(t, DataPartition, []byte{0xfe, 0xed}, OptPartitionMetadata(FsSquash, PartPrimSys, "386"), ), - opts: []AddOpt{ - OptAddDeterministic(), - }, }, { name: "NotEmptyNotAligned", @@ -390,9 +389,6 @@ func TestAddObject(t *testing.T) { OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(0), ), - opts: []AddOpt{ - OptAddDeterministic(), - }, }, { name: "NotEmptyAligned", @@ -406,9 +402,6 @@ func TestAddObject(t *testing.T) { OptPartitionMetadata(FsSquash, PartPrimSys, "386"), OptObjectAlignment(128), ), - opts: []AddOpt{ - OptAddDeterministic(), - }, }, } @@ -461,7 +454,6 @@ func TestDeleteObject(t *testing.T) { }, id: 1, opts: []DeleteOpt{ - OptDeleteDeterministic(), OptDeleteZero(true), }, }, @@ -475,7 +467,6 @@ func TestDeleteObject(t *testing.T) { }, id: 1, opts: []DeleteOpt{ - OptDeleteDeterministic(), OptDeleteCompact(true), }, }, @@ -489,11 +480,24 @@ func TestDeleteObject(t *testing.T) { }, id: 1, opts: []DeleteOpt{ - OptDeleteDeterministic(), OptDeleteZero(true), OptDeleteCompact(true), }, }, + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithDescriptors( + getDescriptorInput(t, DataGeneric, []byte{0xfa, 0xce}), + ), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + id: 1, + opts: []DeleteOpt{ + OptDeleteDeterministic(), + }, + }, { name: "WithTime", createOpts: []CreateOpt{ @@ -518,9 +522,6 @@ func TestDeleteObject(t *testing.T) { ), }, id: 1, - opts: []DeleteOpt{ - OptDeleteDeterministic(), - }, }, } @@ -563,6 +564,22 @@ func TestSetPrimPart(t *testing.T) { id: 1, wantErr: ErrObjectNotFound, }, + { + name: "Deterministic", + createOpts: []CreateOpt{ + OptCreateWithID("de170c43-36ab-44a8-bca9-1ea1a070a274"), + OptCreateWithDescriptors( + getDescriptorInput(t, DataPartition, []byte{0xfa, 0xce}, + OptPartitionMetadata(FsRaw, PartSystem, "386"), + ), + ), + OptCreateWithTime(time.Unix(946702800, 0)), + }, + id: 1, + opts: []SetOpt{ + OptSetDeterministic(), + }, + }, { name: "WithTime", createOpts: []CreateOpt{ @@ -592,9 +609,6 @@ func TestSetPrimPart(t *testing.T) { ), }, id: 1, - opts: []SetOpt{ - OptSetDeterministic(), - }, }, { name: "Two", @@ -610,9 +624,6 @@ func TestSetPrimPart(t *testing.T) { ), }, id: 2, - opts: []SetOpt{ - OptSetDeterministic(), - }, }, } diff --git a/pkg/sif/sif.go b/pkg/sif/sif.go index 2d1c2091..74ff1007 100644 --- a/pkg/sif/sif.go +++ b/pkg/sif/sif.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022, Sylabs Inc. All rights reserved. +// Copyright (c) 2018-2023, 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 @@ -402,3 +402,9 @@ func (f *FileImage) DataSize() int64 { return f.h.DataSize } func (f *FileImage) GetHeaderIntegrityReader() io.Reader { return f.h.GetIntegrityReader() } + +// isDeterministic returns true if the UUID and timestamps in the header of f are set to +// deterministic values. +func (f *FileImage) isDeterministic() bool { + return f.h.ID == uuid.Nil && f.CreatedAt().IsZero() && f.ModifiedAt().IsZero() +} diff --git a/pkg/sif/testdata/TestAddObject/Deterministic.golden b/pkg/sif/testdata/TestAddObject/Deterministic.golden new file mode 100644 index 00000000..bd4a8385 Binary files /dev/null and b/pkg/sif/testdata/TestAddObject/Deterministic.golden differ diff --git a/pkg/sif/testdata/TestDeleteObject/Deterministic.golden b/pkg/sif/testdata/TestDeleteObject/Deterministic.golden new file mode 100644 index 00000000..0ea1e471 Binary files /dev/null and b/pkg/sif/testdata/TestDeleteObject/Deterministic.golden differ diff --git a/pkg/sif/testdata/TestSetPrimPart/Deterministic.golden b/pkg/sif/testdata/TestSetPrimPart/Deterministic.golden new file mode 100644 index 00000000..e7efa4e3 Binary files /dev/null and b/pkg/sif/testdata/TestSetPrimPart/Deterministic.golden differ